many improvements!

This commit is contained in:
didier.amyot committed 2014-05-08 22:57:22 -04:00
1 parent da7cacb57e
commit dda4e0b6ae
13 files changed
+245 -31

No files matched your search

+32 -5
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"bitbucket.org/damyot/vulture/shared" "bitbucket.org/damyot/vulture/shared"
"encoding/json"
"errors" "errors"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"labix.org/v2/mgo" "labix.org/v2/mgo"
@@ -17,6 +18,7 @@ type VultureBackend struct {
func GetVultureBackend(req *http.Request) (*VultureBackend, error) { func GetVultureBackend(req *http.Request) (*VultureBackend, error) {
vars := mux.Vars(req) vars := mux.Vars(req)
serverURL, ok := vars["server"] serverURL, ok := vars["server"]
if !ok { if !ok {
return nil, errors.New("server field not specified") return nil, errors.New("server field not specified")
@@ -83,13 +85,13 @@ func (this *VultureBackend) getMetaData(query *mgo.Query) (map[string]interface{
} }
func (this *VultureBackend) GetDocumentByIndex(index int) (map[string]interface{}, error) { func (this *VultureBackend) GetDocumentByIndex(index int, query interface{}) (map[string]interface{}, error) {
doc := make(map[string]interface{}) doc := make(map[string]interface{})
query := this.Collection.Find(nil) queryResult := this.Collection.Find(query)
if err := query.Skip(index).One(&doc); err != nil { if err := queryResult.Skip(index).One(&doc); err != nil {
return nil, err return nil, err
} }
return this.wrapDocumentWithMetadata(doc, query) return this.wrapDocumentWithMetadata(doc, queryResult)
} }
func (this *VultureBackend) wrapDocumentWithMetadata(doc map[string]interface{}, query *mgo.Query) (map[string]interface{}, error) { func (this *VultureBackend) wrapDocumentWithMetadata(doc map[string]interface{}, query *mgo.Query) (map[string]interface{}, error) {
@@ -136,5 +138,30 @@ func getDocumentByIndex(w http.ResponseWriter, request *http.Request) (interface
if err != nil { if err != nil {
return nil, errors.New("index field is not an inteder ( " + indexAsString + ")") return nil, errors.New("index field is not an inteder ( " + indexAsString + ")")
} }
return vb.GetDocumentByIndex(index) return vb.GetDocumentByIndex(index, nil)
}
func getDocumentByQueryAndIndex(w http.ResponseWriter, request *http.Request) (interface{}, error) {
vb, err := GetVultureBackend(request)
if err != nil {
return nil, err
}
vars := mux.Vars(request)
indexAsString, ok := vars["index"]
if !ok {
return nil, errors.New("index field not specified")
}
index, err := strconv.Atoi(indexAsString)
if err != nil {
return nil, errors.New("index field is not an inteder ( " + indexAsString + ")")
}
queryString, ok := vars["query"]
if !ok || queryString == "" {
return vb.GetDocumentByIndex(index, nil)
}
query := make(map[string]interface{})
if err := json.Unmarshal([]byte(queryString), &query); err != nil {
return nil, errors.New("invalid json")
}
return vb.GetDocumentByIndex(index, query)
} }
+3 -1
View File
@@ -16,6 +16,8 @@
"angular-local-storage": "~0.0.1", "angular-local-storage": "~0.0.1",
"qunit": "~1.14.0", "qunit": "~1.14.0",
"zepto": "~1.1.3", "zepto": "~1.1.3",
"fontawesome": "~4.0.3" "fontawesome": "~4.0.3",
"angular-leaflet-directive": "~0.7.7",
"leaflet": "~0.7.2"
} }
} }
+93 -5
View File
@@ -1,24 +1,112 @@
root = this; root = this;
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) ->
if _.isArray(query)
for v, i in query
query[i] = fixQuery(v)
return query
if _.isRegExp(query)
options = ""
for k, option of {multiline: "m", global: "g", ignoreCase: "i"}
if query[k]
options += option
return { "$regex": query.source, "$options": options }
if _.isDate(query)
return { "$date": query.getTime()}
if query == undefined
return { "$undefined": true }
if _.isObject(query)
for k, v of query
query[k] = fixQuery(v)
return query
return query
return fixQuery(query)
testmongoJsToMongoJSON = () ->
assertEqual = (a, b) ->
if not _.isEqual(a, b)
console.log("error! ", a, " != ", b)
s = """{a: 89}"""
assertEqual(mongoJsToMongoJSON(s), {a : 89})
s = """{a: [1,3,{reg: /allo/gim}]}"""
assertEqual(mongoJsToMongoJSON(s), {a: [1,3, {reg: { "$regex":"allo", "$options": "mgi" }}]})
s = """{a: [
1,
3,
{reg:
[/allo/gim,
new Date("2014-05-08T21:52:24.320Z")
]
}
]
}"""
assertEqual(mongoJsToMongoJSON(s), {a: [1,3, {reg: [{ "$regex":"allo", "$options": "mgi" }, {"$date": 1399585944320}]}]})
testmongoJsToMongoJSON()
root.controllers.controller('documentCtrl', ['$scope', '$routeParams', '$http', ($scope, $routeParams, $http) -> root.controllers.controller('documentCtrl', ['$scope', '$routeParams', '$http', ($scope, $routeParams, $http) ->
$scope.idx = $routeParams.idx $scope.idx = $routeParams.idx
$scope.doc = {} $scope.doc = {}
$scope.idx = Number($routeParams.idx) $scope.idx = Number($routeParams.idx)
$scope.meta = {} $scope.meta = {}
$http.get("/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$routeParams.idx - 1}").then((res) -> $scope.newQuery = $routeParams.query or ""
url = "/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }"
if $routeParams.query
url = "#{url}/query/#{$routeParams.query}"
url = "#{ url }/idx/#{$routeParams.idx - 1}"
$http.get(url).then((res) ->
$scope.doc = res.data.document $scope.doc = res.data.document
$scope.meta = res.data.meta $scope.meta = res.data.meta
) )
$scope.parseQueryToJSON = (query) ->
try
ret = JSON.stringify(mongoJsToMongoJSON(query))
$scope.error = ""
return ret
catch error
$scope.error
return ""
$scope.getQueryPath = ()->
query = $scope.parseQueryToJSON($scope.newQuery)
if query == ""
return ""
return "#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/1/query/#{query}"
$scope.previousDocumentUrl = () -> $scope.previousDocumentUrl = () ->
"#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx - 1}" url = "#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx - 1}"
if $routeParams.query
url = "/query/#{$routeParams.query}"
return url
$scope.nextDocumentUrl = () -> $scope.nextDocumentUrl = () ->
"#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx + 1}" url = "#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx + 1}"
if $routeParams.query
url = "/query/#{$routeParams.query}"
return url
$scope.hasPreviousDocument = () -> $scope.hasPreviousDocument = () ->
$scope.idx > 1 $scope.idx > 1
])
])
@@ -0,0 +1,40 @@
root = this;
root.controllers.controller('geojsonmapCtrl', ['$scope', '$routeParams', '$http', ($scope, $routeParams, $http) ->
$scope.geojson = {}
$scope.geojsonData = {}
$scope.idx = Number($routeParams.idx)
$scope.center = # experimental point around Québec
lat: 46.1227
lng: -72.6169
zoom: 7
$http.get("/api/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$routeParams.idx - 1}").then((res) ->
$scope.geojson = res.data.document[$routeParams.key]
if not $scope.geojson.properties
$scope.geojson.properties = {}
$scope.geojson.properties["_vulture_url_link"] = ""
$scope.meta = res.data.meta
$scope.geojsonData =
data: $scope.geojson
style: undefined
resetStyleOnMouseout: true
)
$scope.previousDocumentUrl = () ->
"#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx - 1}/geojson/#{$routeParams.key}"
$scope.nextDocumentUrl = () ->
"#/#{ $routeParams.server }/#{ $routeParams.database }/#{ $routeParams.collection }/idx/#{$scope.idx + 1}/geojson/#{$routeParams.key}"
$scope.hasPreviousDocument = () ->
$scope.idx > 1
$scope.$on "leafletDirectiveMap.geojsonClick", (ev, featureSelected, leafletEvent) ->
a = 90
b = 89
])
+5 -1
View File
@@ -3,9 +3,11 @@ root = this;
root.controllers.controller('topBarCtrl', ['$scope', '$location', ($scope, $location) -> root.controllers.controller('topBarCtrl', ['$scope', '$location', ($scope, $location) ->
$scope.$on('$routeChangeSuccess', (next, current) -> $scope.$on('$routeChangeSuccess', (next, current) ->
$scope.path = _.filter($location.path().split("/"), (s) -> s) $scope.path = _.filter($location.path().split("/"), (s) -> s)
if $scope.path[5] == "query"
$scope.path = $scope.path[0...-1]
$scope.getHistoryUrl = (idx) -> $scope.getHistoryUrl = (idx) ->
ret = "#/" + ($scope.path[i] for i in [0...idx]).join("/") ret = "#/" + ($scope.path[i] for i in [0...idx]).join("/")
if $scope.path[idx] == "idx" if _.indexOf(["idx", "geojson"], $scope.path[idx]) != -1
return "" return ""
if idx == 0 if idx == 0
ret += "servers" ret += "servers"
@@ -13,6 +15,8 @@ root.controllers.controller('topBarCtrl', ['$scope', '$location', ($scope, $loca
ret += "/databases" ret += "/databases"
else if idx == 2 else if idx == 2
ret += "/collections" ret += "/collections"
else if idx == 4
ret += "/#{ $scope.path[4] }"
return ret return ret
) )
]) ])
+27 -6
View File
@@ -1,4 +1,20 @@
hasIndex = (keyName, indexes) ->
for index in indexes
if _.indexOf(index.keys, keyName) != -1
return true
return false
hasIndexAsc = (keyName, indexes) ->
hasIndex(keyName, indexes)
hasIndexDesc = (keyName, indexes) ->
hasIndex("-#{keyName}", indexes)
has2dIndex = (keyName, indexes) ->
hasIndex("$2dsphere:#{keyName}", indexes)
directive = () -> directive = () ->
directive = directive =
templateUrl: "/partials/directives/key.html" templateUrl: "/partials/directives/key.html"
@@ -6,12 +22,17 @@ directive = () ->
transclude: false, transclude: false,
restrict: 'E', restrict: 'E',
scope: scope:
value: "=" keyName: "="
controller:["$scope", ($scope) -> indexes: "="
$scope.hasIndexDown = false controller:["$scope", "$routeParams", ($scope, $routeParams) ->
$scope.hasIndexUp = false $scope.hasIndexDesc = hasIndexDesc($scope.keyName, $scope.indexes)
$scope.hasIndex = false $scope.hasIndexAsc = hasIndexAsc($scope.keyName, $scope.indexes)
$scope.has2DIndexUp = false $scope.has2DIndex = has2dIndex($scope.keyName, $scope.indexes)
$scope.showTools = false
$scope.hasIndex = $scope.hasIndexDown or $scope.hasIndexUp or $scope.has2DIndex
$scope.getGeoJsonUrl = () ->
r = $routeParams
return "#" + "/#{r.server}/#{r.database}/#{r.collection}/idx/#{r.idx}/geojson/#{$scope.keyName}"
] ]
return directive return directive
+3
View File
@@ -3,6 +3,7 @@ root = this;
angular.module('vulture', [ angular.module('vulture', [
'ngRoute' 'ngRoute'
'leaflet-directive'
'vulture.filters' 'vulture.filters'
'vulture.services' 'vulture.services'
'vulture.directives' 'vulture.directives'
@@ -14,6 +15,8 @@ config(['$routeProvider', ($routeProvider) ->
$routeProvider.when('/:server/databases', {templateUrl: 'partials/databases.html', controller: 'databasesCtrl'}) $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/collections', {templateUrl: 'partials/collections.html', controller: 'collectionsCtrl'})
$routeProvider.when('/:server/:database/:collection/idx/:idx', {templateUrl: 'partials/document.html', controller: 'documentCtrl'}) $routeProvider.when('/:server/:database/:collection/idx/:idx', {templateUrl: 'partials/document.html', controller: 'documentCtrl'})
$routeProvider.when('/:server/:database/:collection/idx/:idx/query/:query', {templateUrl: 'partials/document.html', controller: 'documentCtrl'})
$routeProvider.when('/:server/:database/:collection/idx/:idx/geojson/:key', {templateUrl: 'partials/geojsonmap.html', controller: 'geojsonmapCtrl'})
$routeProvider.otherwise({redirectTo: '/servers'}) $routeProvider.otherwise({redirectTo: '/servers'})
]) ])
+5
View File
@@ -10,6 +10,7 @@
<link rel="stylesheet" href="bower_components/foundation/css/foundation.css"> <link rel="stylesheet" href="bower_components/foundation/css/foundation.css">
<link rel="stylesheet" href="bower_components/foundation/css/normalize.css"> <link rel="stylesheet" href="bower_components/foundation/css/normalize.css">
<link rel="stylesheet" href="bower_components/fontawesome/css/font-awesome.css"> <link rel="stylesheet" href="bower_components/fontawesome/css/font-awesome.css">
<link rel="stylesheet" href="bower_components/leaflet-dist/leaflet.css">
<!-- If you are using the gem version, you need this only --> <!-- If you are using the gem version, you need this only -->
@@ -37,10 +38,13 @@
<div ng-view></div> <div ng-view></div>
<script src="bower_components/leaflet-dist/leaflet.js"></script>
<script src="bower_components/underscore/underscore.js"></script> <script src="bower_components/underscore/underscore.js"></script>
<script src="bower_components/jquery/dist/jquery.js"></script> <script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/foundation/js/foundation.js"></script> <script src="bower_components/foundation/js/foundation.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script> <script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-leaflet-directive/dist/angular-leaflet-directive.js"></script>
<script src="js/vultureApp.js"></script> <script src="js/vultureApp.js"></script>
<script src="js/filters.js"></script> <script src="js/filters.js"></script>
@@ -49,6 +53,7 @@
<script src="js/controllers/databasesCtrl.js"></script> <script src="js/controllers/databasesCtrl.js"></script>
<script src="js/controllers/collectionsCtrl.js"></script> <script src="js/controllers/collectionsCtrl.js"></script>
<script src="js/controllers/documentCtrl.js"></script> <script src="js/controllers/documentCtrl.js"></script>
<script src="js/controllers/geojsonmapCtrl.js"></script>
<script src="js/directives/key.js"></script> <script src="js/directives/key.js"></script>
<script src="js/directives/value.js"></script> <script src="js/directives/value.js"></script>
+12 -7
View File
@@ -1,10 +1,15 @@
<span> <span ng-mouseenter="showTools=true" ng-mouseleave="showTools=false">
{{value}} {{keyName}}
<span class="right" style="color:magenta">
<i class="fa fa-bolt" ng-show="hasIndex"></i> <span ng-style="{visibility: showTools ? 'inline': 'hidden'}">
<i class="fa fa-sort-numeric-asc" ng-show="hasIndexDown"></i> <!-- place to but some hoover information-->
<i class="fa fa-sort-numeric-desc" ng-show="hasIndexUp"></i> </span>
<i class="fa fa-map-marker" ng-show="has2DIndexUp"></i> <span class="right" style="color:#008CBA;">
<i class="fa fa-sort-numeric-asc" ng-show="hasIndexAsc"></i>
<i class="fa fa-sort-numeric-desc" ng-show="hasIndexDesc"></i>
<a class="fa fa-globe" ng-show="has2DIndex" ng-href="{{ getGeoJsonUrl() }}" target="_blank"></a>
</span> </span>
</span> </span>
+9 -5
View File
@@ -1,10 +1,10 @@
<div class="row"> <div class="row">
<div class="large-9 columns"> <div class="large-9 columns">
<input type="text"> <input type="text" ng-model="newQuery">
</div> </div>
<div class="large-3 columns right"> <div class="large-3 columns right">
<ul class="button-group"> <ul class="button-group">
<li><a class="button tiny">Query</a></li> <li><a class="button tiny" ng-href="{{ getQueryPath() }}">Query</a></li>
<li><a class="button tiny">Clear</a></li> <li><a class="button tiny">Clear</a></li>
</ul> </ul>
</div> </div>
@@ -13,7 +13,7 @@
<div class="row"> <div class="row">
<div class="large-12 columns" style="text-align: center;"> <div class="large-12 columns" style="text-align: center;">
<a class="fa fa-angle-left" ng-href="{{ previousDocumentUrl() }}" ng-show="{{ hasPreviousDocument() }}"></a> <a class="fa fa-angle-left" ng-href="{{ previousDocumentUrl() }}" ng-show="{{ hasPreviousDocument() }}"></a>
<kbd>{{idx}} / {{ meta.count}}</kbd> <kbd>{{idx}} / {{ meta.count | number | replaceAllCommasBySpaces }}</kbd>
<a class="fa fa-angle-right" ng-href="{{ nextDocumentUrl() }}"></a> <a class="fa fa-angle-right" ng-href="{{ nextDocumentUrl() }}"></a>
</div> </div>
</div> </div>
@@ -29,8 +29,12 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="(key, value) in doc"> <tr ng-repeat="(key, value) in doc">
<td style="vertical-align: top;"><vul-key value="key"></vul-key></td> <td style="vertical-align: top;">
<td><vul-value value="value"></vul-value></td> <vul-key key-name="key" indexes="meta.indexes" ></vul-key>
</td>
<td>
<vul-value value="value"></vul-value>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
+13
View File
@@ -0,0 +1,13 @@
<div class="row">
<div class="large-12 columns" style="text-align: center;">
<a class="fa fa-angle-left" ng-href="{{ previousDocumentUrl() }}" ng-show="{{ hasPreviousDocument() }}"></a>
<kbd>{{idx}} / {{ meta.count}}</kbd>
<a class="fa fa-angle-right" ng-href="{{ nextDocumentUrl() }}"></a>
</div>
</div>
<div class="row">
<div class="columns large-12">
<leaflet width="100%" height="500px" geojson="geojsonData" center="center"></leaflet>
</div>
</div>
+2
View File
@@ -2,6 +2,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
) )
@@ -14,6 +15,7 @@ type ApiHandler func(w http.ResponseWriter, request *http.Request) (interface{},
func (this ApiHandler) ServeHTTP(w http.ResponseWriter, request *http.Request) { func (this ApiHandler) ServeHTTP(w http.ResponseWriter, request *http.Request) {
ret, err := this(w, request) ret, err := this(w, request)
if err != nil { if err != nil {
log.Println(err)
ret = &errorWrapper{err} ret = &errorWrapper{err}
w.WriteHeader(500) w.WriteHeader(500)
} else { } else {
+1 -1
View File
@@ -70,7 +70,7 @@ func serveApi(router *mux.Router) {
router.Handle("/{server}/databases", ApiHandler(getAvailableDataBases)) router.Handle("/{server}/databases", ApiHandler(getAvailableDataBases))
router.Handle("/{server}/{database}/collections", ApiHandler(getAvailableCollections)) router.Handle("/{server}/{database}/collections", ApiHandler(getAvailableCollections))
router.Handle("/{server}/{database}/{collection}/idx/{index:[0-9]+}", ApiHandler(getDocumentByIndex)) router.Handle("/{server}/{database}/{collection}/idx/{index:[0-9]+}", ApiHandler(getDocumentByIndex))
router.Handle("/{server}/{database}/{collection}/query/{query}/idx/{index:[0-9]+}", ApiHandler(getDocumentByQueryAndIndex))
} }
func createMuxRouter() http.Handler { func createMuxRouter() http.Handler {