From e305aa8f2a31aa65e125dd106def80158e1e10ba Mon Sep 17 00:00:00 2001 From: didier amyot Date: Mon, 25 May 2015 12:07:43 -0400 Subject: [PATCH] Can now use pipeline as query --- README.md | 4 +- api.go | 136 ++++++++++++++++-- client/bower.json | 40 ++++-- client/coffee/controllers/documentCtrl.coffee | 127 +++++++++++----- client/coffee/controllers/keyStatCtrl.coffee | 18 +-- client/coffee/controllers/topBarCtrl.coffee | 12 +- client/coffee/directives/key.coffee | 7 +- client/coffee/directives/searchBar.coffee | 47 +++--- client/coffee/directives/value.coffee | 2 +- client/coffee/filters.coffee | 6 +- client/coffee/vultureApp.coffee | 9 +- client/partials/directives/key.html | 4 +- client/partials/directives/searchBar.html | 15 +- client/partials/directives/value.html | 2 +- client/partials/document.html | 14 +- main.go | 3 + 16 files changed, 323 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index 62a00f3..cd2f64f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ project is inspired by [mongs](http://whit537.org/mongs/) but has additional fea * Support for [GeoJson](http://geojson.org/) indexes. * View basic statistics * Easy filtering support +* Use of (aggregation pipeline)[http://docs.mongodb.org/manual/core/aggregation-pipeline/] + in the user interface ### run vulture @@ -22,7 +24,7 @@ project is inspired by [mongs](http://whit537.org/mongs/) but has additional fea ### params You can add mongodb servers or change the vulture listening port in the -*params.yml* file. +*params.yml* file. ### screenshots diff --git a/api.go b/api.go index 8ae70bd..3814646 100644 --- a/api.go +++ b/api.go @@ -3,7 +3,6 @@ package main import ( "encoding/json" "errors" - "fmt" "net/http" "net/url" "strconv" @@ -67,7 +66,6 @@ func (this *VultureBackend) GetDataBases() ([]string, error) { } else if strings.Contains(err.Error(), "authorized") { return this.getDatabaseFromMongoURL() } else { - fmt.Println(">>> ", err.Error()) return nil, err } } @@ -114,7 +112,6 @@ func (this *VultureBackend) getMetaData(query *mgo.Query) (map[string]interface{ } return meta, nil - } func (this *VultureBackend) GetDocumentByIndex(index int, query interface{}) (map[string]interface{}, error) { @@ -136,12 +133,8 @@ func (this *VultureBackend) wrapDocumentWithMetadata(doc interface{}, query *mgo return ret, nil } -func (this *VultureBackend) GetStat(query bson.M, key string) (interface{}, error) { +func (this *VultureBackend) getStat(iter *mgo.Iter, key string) (interface{}, error) { s := NewStatAggregator() - if _, ok := query[key]; !ok { - query[key] = bson.M{"$exists": true} - } - iter := this.Collection.Find(query).Select(bson.M{key: 1}).Iter() result := make(map[string]interface{}) in := make(chan float64) @@ -165,12 +158,8 @@ func (this *VultureBackend) GetStat(query bson.M, key string) (interface{}, erro return <-out, nil } -func (this *VultureBackend) GetHistogram(query bson.M, key string, min, max float64, numberOfBins int) (interface{}, error) { +func (this *VultureBackend) getHistogram(iter *mgo.Iter, key string, min, max float64, numberOfBins int) (interface{}, error) { hm := &HistogramMaker{min, max, numberOfBins} - if _, ok := query[key]; !ok { - query[key] = bson.M{"$exists": true} - } - iter := this.Collection.Find(query).Select(bson.M{key: 1}).Iter() result := make(map[string]interface{}) in := make(chan float64) @@ -191,7 +180,32 @@ func (this *VultureBackend) GetHistogram(query bson.M, key string, min, max floa return nil, err } return <-out, nil +} +func (this *VultureBackend) GetStat(query bson.M, key string) (interface{}, error) { + if _, ok := query[key]; !ok { + query[key] = bson.M{"$exists": true} + } + iter := this.Collection.Find(query).Select(bson.M{key: 1}).Iter() + return this.getStat(iter, key) +} + +func (this *VultureBackend) GetStatFromPipeline(pipeline []interface{}, key string) (interface{}, error) { + iter := this.Collection.Pipe(pipeline).Iter() + return this.getStat(iter, key) +} + +func (this *VultureBackend) GetHistogram(query bson.M, key string, min, max float64, numberOfBins int) (interface{}, error) { + if _, ok := query[key]; !ok { + query[key] = bson.M{"$exists": true} + } + iter := this.Collection.Find(query).Select(bson.M{key: 1}).Iter() + return this.getHistogram(iter, key, min, max, numberOfBins) +} + +func (this *VultureBackend) GetHistogramFromPipeline(pipeline []interface{}, key string, min, max float64, numberOfBins int) (interface{}, error) { + iter := this.Collection.Pipe(pipeline).Iter() + return this.getHistogram(iter, key, min, max, numberOfBins) } func (this *VultureBackend) GetAllDocuments(query interface{}) (interface{}, error) { @@ -206,6 +220,16 @@ func (this *VultureBackend) GetAllDocuments(query interface{}) (interface{}, err return this.wrapDocumentWithMetadata(result, nil) } +func (this *VultureBackend) GetAggregatePipelineResult(pipeline interface{}) (interface{}, error) { + var result []map[string]interface{} + pipe := this.Collection.Pipe(pipeline) + if err := pipe.All(&result); err != nil { + return nil, err + } + + return this.wrapDocumentWithMetadata(result, nil) +} + func getAvailableServers(w http.ResponseWriter, request *http.Request) (interface{}, error) { ret := make([]string, len(params.MongoServers)) for i, ms := range params.MongoServers { @@ -280,6 +304,23 @@ func getDocumentById(w http.ResponseWriter, request *http.Request) (interface{}, return vb.GetDocumentByIndex(0, bson.M{"_id": bson.ObjectIdHex(id)}) } +func getAggregatePipelireResult(w http.ResponseWriter, request *http.Request) (interface{}, error) { + vb, err := GetVultureBackend(request) + if err != nil { + return nil, err + } + vars := mux.Vars(request) + pipelineString, ok := vars["pipeline"] + if !ok { + return nil, errors.New("pipeline field not specified") + } + var pipeline []interface{} + if err := json.Unmarshal([]byte(pipelineString), &pipeline); err != nil { + return nil, err + } + return vb.GetAggregatePipelineResult(pipeline) +} + func getDocumentByQueryAndIndex(w http.ResponseWriter, request *http.Request) (interface{}, error) { vb, err := GetVultureBackend(request) if err != nil { @@ -305,6 +346,28 @@ func getDocumentByQueryAndIndex(w http.ResponseWriter, request *http.Request) (i return vb.GetDocumentByIndex(index, query) } +func getStatsFromPipeline(w http.ResponseWriter, request *http.Request) (interface{}, error) { + vb, err := GetVultureBackend(request) + if err != nil { + return nil, err + } + vars := mux.Vars(request) + pipelineString, ok := vars["pipeline"] + if !ok { + return nil, errors.New("pipeline field not specified") + } + var pipeline []interface{} + if err := json.Unmarshal([]byte(pipelineString), &pipeline); err != nil { + return nil, err + } + + key, ok := vars["key"] + if !ok { + return nil, errors.New("key field not specified") + } + return vb.GetStatFromPipeline(pipeline, key) +} + func getStats(w http.ResponseWriter, request *http.Request) (interface{}, error) { vb, err := GetVultureBackend(request) if err != nil { @@ -319,7 +382,6 @@ func getStats(w http.ResponseWriter, request *http.Request) (interface{}, error) return nil, errors.New("invalid json") } } - fmt.Println(">>>", queryString) vars := mux.Vars(request) key, ok := vars["key"] @@ -374,3 +436,49 @@ func getHistogram(w http.ResponseWriter, request *http.Request) (interface{}, er return vb.GetHistogram(query, key, min, max, numberOfBins) } + +func getHistogramFromPipeline(w http.ResponseWriter, request *http.Request) (interface{}, error) { + vb, err := GetVultureBackend(request) + vars := mux.Vars(request) + smin, ok := vars["min"] + if !ok { + return nil, errors.New("min field not specified") + } + min, err := strconv.ParseFloat(smin, 64) + if err != nil { + return nil, errors.New("min value is not an float64") + } + + smax, ok := vars["max"] + if !ok { + return nil, errors.New("max field not specified") + } + max, err := strconv.ParseFloat(smax, 64) + if err != nil { + return nil, errors.New("max value is not an float64") + } + sNumberOfBins, ok := vars["number_of_bins"] + if !ok { + return nil, errors.New("number_of_bins field not specified") + } + numberOfBins, err := strconv.Atoi(sNumberOfBins) + if err != nil { + return nil, errors.New("numberOfBins value is not an int") + } + + key, ok := vars["key"] + if !ok { + return nil, errors.New("key field not specified") + } + + pipelineString, ok := vars["pipeline"] + if !ok { + return nil, errors.New("pipeline field not specified") + } + var pipeline []interface{} + if err := json.Unmarshal([]byte(pipelineString), &pipeline); err != nil { + return nil, err + } + + return vb.GetHistogramFromPipeline(pipeline, key, min, max, numberOfBins) +} diff --git a/client/bower.json b/client/bower.json index 3ad36c0..66169dc 100644 --- a/client/bower.json +++ b/client/bower.json @@ -9,24 +9,36 @@ "license": "MIT", "private": true, "dependencies": { - "underscore": "~1.6.0", - "foundation": "~5.2.1", - "angular": "1.3.x", - "angular-route": "~1.2.14", - "angular-local-storage": "~0.0.1", - "qunit": "~1.14.0", + "underscore": "~1.8.3", + "foundation": "~5.5.2", + "angular": "~1.3.15", + "angular-route": "~1.3.15", + "angular-local-storage": "~0.2.1", + "qunit": "~1.18.0", "zepto": "~1.1.3", - "fontawesome": "~4.0.3", - "angular-leaflet-directive": "~0.7.7", + "fontawesome": "~4.3.0", + "angular-leaflet-directive": "~0.8.1", "leaflet": "~0.7.2", - "codemirror": "~3.19.0", - "angular-ui-codemirror": "~0.1.1", - "spin.js": "~2.0.1", - "angular-spinner": "~0.4.0", + "codemirror": "~5.3.0", + "angular-ui-codemirror": "~0.3.0", + "spin.js": "~2.1.0", + "angular-spinner": "~0.6.1", "highcharts-ng": "~0.0.6", - "highcharts": "~3.0.7" + "highcharts": "~4.1.5" }, "resolutions": { - "angular": "1.3.x" + "angular": "~1.3.15", + "underscore": "~1.8.3", + "foundation": "~5.5.2", + "angular-route": "~1.3.15", + "angular-local-storage": "~0.2.1", + "qunit": "~1.18.0", + "fontawesome": "~4.3.0", + "angular-leaflet-directive": "~0.8.1", + "codemirror": "~5.3.0", + "angular-ui-codemirror": "~0.3.0", + "spin.js": "~2.1.0", + "angular-spinner": "~0.6.1", + "highcharts": "~4.1.5" } } diff --git a/client/coffee/controllers/documentCtrl.coffee b/client/coffee/controllers/documentCtrl.coffee index 6368c0b..7266f41 100644 --- a/client/coffee/controllers/documentCtrl.coffee +++ b/client/coffee/controllers/documentCtrl.coffee @@ -1,56 +1,105 @@ root = this; -root.controllers.controller('documentCtrl', ['$scope', '$routeParams', '$location', 'util', ($scope, $routeParams, $location, util) -> - $scope.idx = $routeParams.idx - $scope.doc = {} - $scope.idx = Number($routeParams.idx) or 1 - $scope.meta = {} - $scope.query = $routeParams.query or "" - $scope.notfound = false - $scope.error = "" - $scope.codeMirrorOptions = - lineWrapping : true - lineNumbers: true - mode: 'text/typescript' - - url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }" - if $routeParams.query - url = "#{url}/query/#{$routeParams.query}" - if $routeParams.idx - url = "#{ url }/idx/#{$routeParams.idx - 1}" - else if $routeParams.id - url = "#{ url }/_id/#{$routeParams.id}" - $scope.raw_url = url - util.get(url).then((res) -> - $scope.doc = res.data.document - $scope.meta = res.data.meta - $scope.enlapsed_time = res.data.enlapsed_time - ).catch( (res) -> - if res.data.error == "not found" - $scope.notfound = true +root.controllers.controller('documentCtrl', ['$scope', '$routeParams', '$location', 'util', 'documentCtrlUtil', ($scope, $routeParams, $location, util, documentCtrlUtil) -> + + $scope.initScopeVariables = -> + $scope.idx = $routeParams.idx + $scope.doc = {} + $scope.idx = Number($routeParams.idx) or 1 + $scope.meta = {} + $scope.query = $routeParams.query or $routeParams.pipeline or "" + $scope.notfound = false + $scope.error = "" + $scope.codeMirrorOptions = + lineWrapping : true + lineNumbers: true + mode: 'text/javascript' + + $scope.init = -> + $scope.initScopeVariables() + url = documentCtrlUtil.getUrl($routeParams) + $scope.raw_url = url + + if $scope.isDocumentFromPipeline() + promise = documentCtrlUtil.getPipeline(url, $scope.idx - 1) else + promise = util.get(url) + + promise.then((res) -> + $scope.doc = res.data.document + $scope.meta = res.data.meta + $scope.total = res.data.meta.count + $scope.idx - 1 + $scope.enlapsed_time = res.data.enlapsed_time + ).catch((res) -> $scope.error = res.data.error - ) - + ) + + $scope.isDocumentFromPipeline = -> + return $routeParams.pipeline + $scope.search = (query) -> if query == "" return "" $location.path "/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/1/query/#{query}" - + + $scope.executePipeline = (pipeline) -> + if pipeline == "" + return + $location.path "/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/1/pipeline/#{pipeline}" + $scope.previousDocumentUrl = () -> - url = "#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx - 1}" - if $routeParams.query - url = "#{url}/query/#{$routeParams.query}" - return url + return '#' + $location.path().replace(/\/idx\/\d+/, "/idx/#{$scope.idx - 1}") $scope.nextDocumentUrl = () -> - url = "#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx + 1}" - if $routeParams.query - url = "#{url}/query/#{$routeParams.query}" - return url + return '#' + $location.path().replace(/\/idx\/\d+/, "/idx/#{$scope.idx + 1}") $scope.hasPreviousDocument = () -> $scope.idx > 1 + + $scope.hasNextDocument = -> + $scope.total > 0 and $scope.idx < $scope.total + + $scope.init() ]) + +root = this + +root.services.factory('documentCtrlUtil', ['util', (util)-> + service = + _cache: {} + + getUrl: ($routeParams) -> + url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }" + if $routeParams.query + url = "#{url}/query/#{$routeParams.query}" + else if $routeParams.pipeline + return "#{url}/pipeline/#{$routeParams.pipeline}" + + if $routeParams.idx + url = "#{ url }/idx/#{$routeParams.idx - 1}" + else if $routeParams.id + url = "#{ url }/_id/#{$routeParams.id}" + return url + + wrapPipelinePromise: (promise, idx) -> + return promise.then (res) -> + ret = + data: + document: res.data.document[idx] + meta: + count: res.data.document.length - idx + enlapsed_time: res.data.enlapsed_time + + return promise + + getPipeline: (url, idx) -> + if url in _.keys(service._cache) + return service.wrapPipelinePromise(service._cache[url], idx) + service._cache[url] = util.get(url) + return service.getPipeline(url, idx) + + return service + +]); diff --git a/client/coffee/controllers/keyStatCtrl.coffee b/client/coffee/controllers/keyStatCtrl.coffee index a09e145..9774dc0 100644 --- a/client/coffee/controllers/keyStatCtrl.coffee +++ b/client/coffee/controllers/keyStatCtrl.coffee @@ -22,13 +22,16 @@ getAxisLabel = (min, stepSize, numberOfBins) -> root.controllers.controller('keyStatCtrl', ['$scope', '$routeParams', '$location', 'util', ($scope, $routeParams, $location, util) -> $scope.name = $routeParams.key $scope.stats = [] - $scope.queryObject = $routeParams.query or '{}' + $scope.queryObject = $routeParams.query or $routeParams.pipeline or '{}' - base_url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/query/#{$scope.queryObject}" + base_url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }" + if $routeParams.pipeline + base_url = "#{base_url}/pipeline/#{$routeParams.pipeline}" + else + base_url = "#{base_url}/query/#{$scope.queryObject}" stat_url = "#{ base_url }/stats/#{$routeParams.key}" - $scope.alert=alert - + $scope.chartConfig = options: chart: @@ -54,8 +57,8 @@ root.controllers.controller('keyStatCtrl', ['$scope', '$routeParams', '$location return "" r = $routeParams $location.path "/#{ r.server }/#{ r.database }/#{ r.collection }/stats/#{ r.key }/query/#{query}" - - + + util.get(stat_url).then((res) -> for key, val of res.data $scope.stats.push({name: humanValueOfStatisticalAbreviation[key], value: val }) @@ -68,6 +71,5 @@ root.controllers.controller('keyStatCtrl', ['$scope', '$routeParams', '$location $scope.chartConfig.series[0].data = res.data.values $scope.chartConfig.xAxis.categories = getAxisLabel(res.data.min, res.data.step_size, res.data.values.length) ) - -]) +]) diff --git a/client/coffee/controllers/topBarCtrl.coffee b/client/coffee/controllers/topBarCtrl.coffee index c180b8c..417a5b7 100644 --- a/client/coffee/controllers/topBarCtrl.coffee +++ b/client/coffee/controllers/topBarCtrl.coffee @@ -5,16 +5,16 @@ joinArray = (a, b)-> return a root.controllers.controller('topBarCtrl', ['$scope', '$location', ($scope, $location) -> - + $scope.loading = false - + $scope.$on('$routeChangeSuccess', (next, current) -> $scope.path = _.filter($location.path().split("/"), (s) -> s) - if $scope.path[5] == "query" + if $scope.path[5] in ["query", "pipeline"] $scope.path = joinArray($scope.path[0...6], $scope.path[7...-1]) $scope.getHistoryUrl = (idx) -> ret = "#/" + ($scope.path[i] for i in [0...idx]).join("/") - if _.indexOf(["idx", "geojson", "query", "_id", "field", "all", "stats"], $scope.path[idx]) != -1 + if _.indexOf(["idx", "geojson", "query", "_id", "field", "all", "stats", "pipeline"], $scope.path[idx]) != -1 return "" if idx == 0 ret += "servers" @@ -28,7 +28,7 @@ root.controllers.controller('topBarCtrl', ['$scope', '$location', ($scope, $loca ) $scope.$on "loading", ()-> $scope.loading = true - + $scope.$on "loadingFinished", ()-> $scope.loading = false - ]) \ No newline at end of file + ]) diff --git a/client/coffee/directives/key.coffee b/client/coffee/directives/key.coffee index b5d1976..2ea9695 100644 --- a/client/coffee/directives/key.coffee +++ b/client/coffee/directives/key.coffee @@ -26,10 +26,12 @@ getStatsUrl = (keyName, routeParams)-> ret = "#/#{r.server}/#{r.database}/#{r.collection}/stats/#{ keyName }" if r.query ret = "#{ ret }/query/#{ r.query }" + else if r.pipeline + ret = "#{ret}/pipeline/#{r.pipeline}" return ret directive = () -> - directive = + directive = templateUrl: "/partials/directives/key.html" replace: true, transclude: false, @@ -39,6 +41,7 @@ directive = () -> value: "=" indexes: "=" controller:["$scope", "$routeParams", ($scope, $routeParams) -> + $scope.indexes = $scope.indexes or [] $scope.canMakeHistogram = _.isNumber($scope.value) $scope.statsUrl = getStatsUrl($scope.keyName, $routeParams) $scope.hasIndexDesc = hasIndexDesc($scope.keyName, $scope.indexes) @@ -56,5 +59,5 @@ directive = () -> return "#{url}/geojson/#{$scope.keyName}" ] return directive - + directives.directive( "vulKey", [directive]) diff --git a/client/coffee/directives/searchBar.coffee b/client/coffee/directives/searchBar.coffee index 8433332..0c2b5eb 100644 --- a/client/coffee/directives/searchBar.coffee +++ b/client/coffee/directives/searchBar.coffee @@ -2,17 +2,17 @@ mongoJsToMongoJSON = (queryString) -> ObjectId = (id) -> return {"$oid": id} - + DBRef = (name, id) -> return {"$ref": name, "$id": id} - + Timestamp = (t, i) -> return {"t": t, "i": i} NumberLong = (number) -> return { "$numberLong": String(number)} MaxKey = { "$maxKey": 1 } MinKey = { "$minKey": 1 } - + query = "" eval("query = " + queryString) fixQuery = (query) -> @@ -35,7 +35,7 @@ mongoJsToMongoJSON = (queryString) -> query[k] = fixQuery(v) return query return query - + return fixQuery(query) testmongoJsToMongoJSON = () -> @@ -57,27 +57,29 @@ testmongoJsToMongoJSON = () -> ] }""" assertEqual(mongoJsToMongoJSON(s), {a: [1,3, {reg: [{ "$regex":"allo", "$options": "mgi" }, {"$date": 1399585944320}]}]}) - + testmongoJsToMongoJSON() directive = () -> - directive = + directive = templateUrl: "/partials/directives/searchBar.html" replace: true, transclude: false, restrict: 'E', scope: query: "@" - callback: "=" + querycallback: "=" + pipelinecallback: "=" controller:["$scope", "$routeParams", ($scope, $routeParams) -> $scope.bigInput = false $scope.newQuery = $scope.query or "" $scope.codeMirrorOptions = lineWrapping : true lineNumbers: true + indentUnit: 4 mode: 'text/typescript' - + $scope.parseQueryToJSON = (query) -> try query = mongoJsToMongoJSON(query) @@ -87,18 +89,29 @@ directive = () -> catch error $scope.error return "" - - $scope.isValidEntry = ()-> + + $scope.isValidQueryEntry = ()-> query = $scope.parseQueryToJSON($scope.newQuery) - return query != "" - - $scope.callCallback = () -> + return query != "" and not _.isArray(JSON.parse(query)) + + $scope.isValidPipelineEntry = ()-> query = $scope.parseQueryToJSON($scope.newQuery) - if query != "" + query != "" and _.isArray(JSON.parse(query)) + + $scope.queryCallback = () -> + if $scope.isValidQueryEntry() + query = $scope.parseQueryToJSON($scope.newQuery) f = () -> - $scope.callback(query) + $scope.querycallback(query) + $scope.$eval(f) + + $scope.pipelineCallback = () -> + if $scope.isValidPipelineEntry() + query = $scope.parseQueryToJSON($scope.newQuery) + f = () -> + $scope.pipelinecallback(query) $scope.$eval(f) ] return directive - -directives.directive( "vulSearchBar", [directive]) \ No newline at end of file + +directives.directive( "vulSearchBar", [directive]) diff --git a/client/coffee/directives/value.coffee b/client/coffee/directives/value.coffee index 9a3cd3d..51530fc 100644 --- a/client/coffee/directives/value.coffee +++ b/client/coffee/directives/value.coffee @@ -30,7 +30,7 @@ directive = () -> return "#/#{rp.server}/#{rp.database}/#{rp.collection}/_id/#{s.docId}/field/#{s.key}" $scope.isObjectId = (val) -> objectIdRegexp = /^[01234567890abcdef]{24}$/ - return _.isString(val) and objectIdRegexp.test(val) + _.isString(val) and objectIdRegexp.test(val) $scope.getDateFromObjectId = (objectId) -> util.parseObjectId(objectId).date.toISOString() diff --git a/client/coffee/filters.coffee b/client/coffee/filters.coffee index 30ad54a..0a80cc8 100644 --- a/client/coffee/filters.coffee +++ b/client/coffee/filters.coffee @@ -6,7 +6,9 @@ root.filters.filter 'escape', () -> root.filters.filter 'replaceAllCommasBySpaces', () -> return (text) -> - return text.replace(new RegExp(",","g"), " ") + if _.isString(text) + return text.replace(new RegExp(",","g"), " ") + return text root.filters.filter 'niceJSON', () -> return (obj) -> @@ -21,4 +23,4 @@ root.filters.filter 'onlyNthLines', () -> if not _.isString(text) return "" s = text.split("\n") - return s[0...n].join("\n") \ No newline at end of file + return s[0...n].join("\n") diff --git a/client/coffee/vultureApp.coffee b/client/coffee/vultureApp.coffee index 490aea4..b032cb7 100644 --- a/client/coffee/vultureApp.coffee +++ b/client/coffee/vultureApp.coffee @@ -16,15 +16,17 @@ config(['$routeProvider', ($routeProvider) -> $routeProvider.when('/servers', {templateUrl: 'partials/servers.html', controller: 'serversCtrl'}) $routeProvider.when('/about', {templateUrl: 'partials/about.html', controller: 'aboutCtrl'}) $routeProvider.when('/:server/databases', {templateUrl: 'partials/databases.html', controller: 'databasesCtrl'}) - + $routeProvider.when('/:server/:database/collections', {templateUrl: 'partials/collections.html', controller: 'collectionsCtrl'}) $routeProvider.when('/:server/:database/:collection/_id/:id', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) $routeProvider.when('/:server/:database/:collection/_id/:id/field/:field', {templateUrl: 'partials/field.html', controller: 'fieldCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) $routeProvider.when('/:server/:database/:collection/stats/:key', {templateUrl: 'partials/keyStat.html', controller: 'keyStatCtrl'}) $routeProvider.when('/:server/:database/:collection/stats/:key/query/:query', {templateUrl: 'partials/keyStat.html', controller: 'keyStatCtrl'}) + $routeProvider.when('/:server/:database/:collection/stats/:key/pipeline/:pipeline', {templateUrl: 'partials/keyStat.html', controller: 'keyStatCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx/query/:query', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) - + $routeProvider.when('/:server/:database/:collection/idx/:idx/pipeline/:pipeline', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) + $routeProvider.when('/:server/:database/:collection/idx/all/geojson/:key', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/all/geojson/:key/location/:loc', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx/geojson/:key', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) @@ -33,7 +35,7 @@ config(['$routeProvider', ($routeProvider) -> $routeProvider.when('/:server/:database/:collection/idx/all/query/:query/geojson/:key/location/:loc', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx/query/:query/geojson/:key', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx/query/:query/geojson/:key/location/:loc', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'}) - + $routeProvider.otherwise({redirectTo: '/servers'}) ]) @@ -41,4 +43,3 @@ root.filters = angular.module('vulture.filters', []) root.services = angular.module('vulture.services', []) root.directives = angular.module('vulture.directives', []) root.controllers = angular.module('vulture.controllers', []) - diff --git a/client/partials/directives/key.html b/client/partials/directives/key.html index 0418c67..1885c52 100644 --- a/client/partials/directives/key.html +++ b/client/partials/directives/key.html @@ -1,6 +1,6 @@ {{keyName}} - + @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/client/partials/directives/searchBar.html b/client/partials/directives/searchBar.html index 8c975df..76e24d3 100644 --- a/client/partials/directives/searchBar.html +++ b/client/partials/directives/searchBar.html @@ -1,15 +1,20 @@
-
+
-
+
- +
-
\ No newline at end of file +
diff --git a/client/partials/directives/value.html b/client/partials/directives/value.html index f0eb2e5..86fe420 100644 --- a/client/partials/directives/value.html +++ b/client/partials/directives/value.html @@ -1,5 +1,5 @@ - +
 {{value}}  [ {{getDateFromObjectId(value)}} ]
diff --git a/client/partials/document.html b/client/partials/document.html index 912e026..78ea861 100644 --- a/client/partials/document.html +++ b/client/partials/document.html @@ -1,18 +1,18 @@ - +
- {{idx}} / {{ meta.count + idx - 1 || 0 | number | replaceAllCommasBySpaces }} - + {{idx}} / {{ total | number | replaceAllCommasBySpaces }} +
-
+
- No document Found! + {{ error }} ×
@@ -38,11 +38,11 @@ - +
took {{enlapsed_time | number | replaceAllCommasBySpaces}} seconds | raw
-
\ No newline at end of file +
diff --git a/main.go b/main.go index 8976bf5..c66aeed 100644 --- a/main.go +++ b/main.go @@ -94,9 +94,12 @@ func serveApi(router *mux.Router) { router.Handle("/{server}/{database}/{collection}/_id/{id}", ApiHandler(getDocumentById)) router.Handle("/{server}/{database}/{collection}/query/{query}/stats/{key}", ApiHandler(getStats)) router.Handle("/{server}/{database}/{collection}/query/{query}/histogram/{key}/{min}/{max}/{number_of_bins}", ApiHandler(getHistogram)) + router.Handle("/{server}/{database}/{collection}/pipeline/{pipeline}/histogram/{key}/{min}/{max}/{number_of_bins}", ApiHandler(getHistogramFromPipeline)) router.Handle("/{server}/{database}/{collection}/all", ApiHandler(getAllDocuments)) router.Handle("/{server}/{database}/{collection}/query/{query}/idx/{index:[0-9]+}", ApiHandler(getDocumentByQueryAndIndex)) router.Handle("/{server}/{database}/{collection}/query/{query}/all", ApiHandler(getAllDocuments)) + router.Handle("/{server}/{database}/{collection}/pipeline/{pipeline}", ApiHandler(getAggregatePipelireResult)) + router.Handle("/{server}/{database}/{collection}/pipeline/{pipeline}/stats/{key}", ApiHandler(getStatsFromPipeline)) }