mongo_fe 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.DS_Store +0 -0
- data/.gitignore +34 -0
- data/.rspec +2 -0
- data/CHANGES.md +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +138 -0
- data/LICENSE +22 -0
- data/Procfile +2 -0
- data/README.md +72 -0
- data/Rakefile +14 -0
- data/TODO.tasks +9 -0
- data/bin/config.ru +28 -0
- data/bin/mongofe +54 -0
- data/lib/mongo_fe/application_controller.rb +91 -0
- data/lib/mongo_fe/controllers/collections_controller.rb +278 -0
- data/lib/mongo_fe/controllers/databases_controller.rb +94 -0
- data/lib/mongo_fe/helpers/helpers.rb +128 -0
- data/lib/mongo_fe/public/app/application.js +3 -0
- data/lib/mongo_fe/public/bootstrap/css/bootstrap-responsive.css +567 -0
- data/lib/mongo_fe/public/bootstrap/css/bootstrap.css +3380 -0
- data/lib/mongo_fe/public/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/lib/mongo_fe/public/bootstrap/img/glyphicons-halflings.png +0 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-alert.js +91 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-button.js +98 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-carousel.js +154 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-collapse.js +136 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-dropdown.js +92 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-modal.js +209 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-popover.js +95 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-scrollspy.js +125 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-tab.js +130 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-tooltip.js +270 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-transition.js +51 -0
- data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-typeahead.js +271 -0
- data/lib/mongo_fe/public/bootstrap/js/bootstrap.js +1722 -0
- data/lib/mongo_fe/public/bootstrap/js/bootstrap.min.js +1 -0
- data/lib/mongo_fe/public/bootstrap/js/jquery.js +9252 -0
- data/lib/mongo_fe/public/bootstrap/js/underscore-min.js +32 -0
- data/lib/mongo_fe/public/bootstrap/js/underscore.js +1059 -0
- data/lib/mongo_fe/public/css/digg_pagination.css +28 -0
- data/lib/mongo_fe/public/css/jsoneditor.css +70 -0
- data/lib/mongo_fe/public/css/styles.css +11 -0
- data/lib/mongo_fe/public/images/missing_avatar_small.png +0 -0
- data/lib/mongo_fe/public/js/collection-tools.js +63 -0
- data/lib/mongo_fe/public/js/db-tools.js +45 -0
- data/lib/mongo_fe/public/js/jsoneditor/jquery.jsoneditor.min.LICENSE +20 -0
- data/lib/mongo_fe/public/js/jsoneditor/jquery.jsoneditor.min.js +6 -0
- data/lib/mongo_fe/public/js/jsoneditor/json2.js +482 -0
- data/lib/mongo_fe/version.rb +6 -0
- data/lib/mongo_fe/views/collections/_document_attributes.haml +6 -0
- data/lib/mongo_fe/views/collections/_documents.haml +35 -0
- data/lib/mongo_fe/views/collections/_documents_page.haml +82 -0
- data/lib/mongo_fe/views/collections/_indexes.haml +76 -0
- data/lib/mongo_fe/views/collections/index.haml +80 -0
- data/lib/mongo_fe/views/databases/_list_users.haml +27 -0
- data/lib/mongo_fe/views/databases/info.haml +80 -0
- data/lib/mongo_fe/views/footer.haml +4 -0
- data/lib/mongo_fe/views/index.haml +7 -0
- data/lib/mongo_fe/views/layout.haml +148 -0
- data/lib/mongo_fe/views/navbar.haml +17 -0
- data/lib/mongo_fe.rb +170 -0
- data/mongo_fe.gemspec +50 -0
- data/screens/example_db_view.png +0 -0
- data/screens/example_doc_view.png +0 -0
- data/screens/example_indexes.png +0 -0
- data/spec/config_spec.rb +17 -0
- data/spec/controllers/collections_controller_spec.rb +151 -0
- data/spec/controllers/db_controller_spec.rb +69 -0
- data/spec/factories/factories.rb +19 -0
- data/spec/spec_helper.rb +88 -0
- metadata +559 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
%p
|
|
2
|
+
- unless @indexes.nil?
|
|
3
|
+
%table.table-striped
|
|
4
|
+
%thead
|
|
5
|
+
%tr.well
|
|
6
|
+
%th(width="20%") NAME
|
|
7
|
+
%th(width="48%" align="left") INDEXED FIELD(S)
|
|
8
|
+
%th(width="7%" align="center") UNIQUE
|
|
9
|
+
%th(width="9%" align="center") SPARSE
|
|
10
|
+
%th(width="10%" align="right") SIZE (MB)
|
|
11
|
+
%th(width="9%" nowrap)
|
|
12
|
+
%tbody
|
|
13
|
+
- @indexes.each_pair do |k,v|
|
|
14
|
+
%tr
|
|
15
|
+
%td(valign="top" align="left")
|
|
16
|
+
=k
|
|
17
|
+
%td(valign="top" align="left")
|
|
18
|
+
=v['key']
|
|
19
|
+
%td(valign="top" align="center")
|
|
20
|
+
=v['unique'] ? '✓': '-'
|
|
21
|
+
%td(valign="top" align="center")
|
|
22
|
+
=v['sparse'] ? '✓': '-'
|
|
23
|
+
%td(valign="top" align="right")
|
|
24
|
+
='%.3f' % bytes_to_mb(collection.stats['indexSizes'][k])
|
|
25
|
+
%td(valign="top" nowrap align="right")
|
|
26
|
+
- unless k.eql? '_id_'
|
|
27
|
+
%a#confirm-delete-index-button(href="#" data-index-name="#{k}" data-toggle="modal" data-target="#confirm-delete-index-dialog")
|
|
28
|
+
%i.icon-remove
|
|
29
|
+
- else
|
|
30
|
+
no indexes defined.
|
|
31
|
+
%p
|
|
32
|
+
%a.btn.btn-primary.btn-small#new-index-button(href="#new-index-dialog" data-toggle="modal") new index
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
#new-index-dialog.modal.hide
|
|
36
|
+
.modal-header
|
|
37
|
+
%a.close(data-dismiss="modal")
|
|
38
|
+
%icon.icon-remove
|
|
39
|
+
%h3 Create a new index
|
|
40
|
+
.modal-body
|
|
41
|
+
%form.form-horizontal{:method => "post", :action => "/databases/#{db.name}/collections/#{collection.name}/indexes"}
|
|
42
|
+
%fieldset
|
|
43
|
+
.control-group
|
|
44
|
+
%label.control-label( for='index-fields') Indexed fields
|
|
45
|
+
.controls
|
|
46
|
+
%textarea#index-fields.input-xlarge.field.span10(name="index_fields" rows="5" placeholder='ex. name:ASC, created_at:DESC')
|
|
47
|
+
%p#index_fields-msg.help-block
|
|
48
|
+
.control-group
|
|
49
|
+
%label.control-label
|
|
50
|
+
.controls
|
|
51
|
+
%label.checkbox.inline
|
|
52
|
+
%input#is-unique{ :type=>'checkbox', :name=>'unique', :value => false} unique?
|
|
53
|
+
%label.checkbox.inline
|
|
54
|
+
%input#is-sparse{ :type=>'checkbox', :name=>'sparse', :value => false} sparse?
|
|
55
|
+
.form-actions.modal-footer
|
|
56
|
+
%a.btn(href="#" data-dismiss="modal") Close
|
|
57
|
+
%button#new-index-dialog-submit-button.btn.btn-primary.closeModal(type="submit") Create
|
|
58
|
+
|
|
59
|
+
#confirm-delete-index-dialog.modal.hide
|
|
60
|
+
.modal-header
|
|
61
|
+
%a.close(data-dismiss="modal")
|
|
62
|
+
%icon.icon-remove
|
|
63
|
+
%h3 Delete index
|
|
64
|
+
.modal-body
|
|
65
|
+
%p.lead
|
|
66
|
+
You are about to delete a collection index. This procedure is irreversible.
|
|
67
|
+
%p.well.well-small
|
|
68
|
+
delete:
|
|
69
|
+
%span.lead.index-name.muted
|
|
70
|
+
%form.form-horizontal{:method => "post", :action => "/databases/#{db.name}/collections/#{collection.name}/indexes"}
|
|
71
|
+
%input{:type => "hidden", :name => "_method", :value => "delete"}
|
|
72
|
+
%input.index-name-field{:type => "hidden", :name => "index_name", :value => ""}
|
|
73
|
+
%fieldset
|
|
74
|
+
.form-actions.modal-footer
|
|
75
|
+
%a.btn(href="#" data-dismiss="modal") Close
|
|
76
|
+
%button#new-document-modal-submit-button.btn.btn-danger.closeModal(type="submit") Delete
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
-unless @collection.nil?
|
|
2
|
+
%ul.nav.nav-tabs
|
|
3
|
+
%li.inactive
|
|
4
|
+
%button.btn.btn-medium.btn-primary.disabled
|
|
5
|
+
-# we know: #{current_db.name}.#{@collection.name}
|
|
6
|
+
collection: #{@collection.name}
|
|
7
|
+
.span1
|
|
8
|
+
|
|
9
|
+
%li
|
|
10
|
+
%a#collections-info(href="#collections-info-tab" data-toggle="tab") Info
|
|
11
|
+
%li
|
|
12
|
+
%a#collections-documents(href="#collections-documents-tab" data-toggle="tab") Documents
|
|
13
|
+
%li
|
|
14
|
+
%a#collections-indexes(href="#collections-indexes-tab" data-toggle="tab") Indexes
|
|
15
|
+
|
|
16
|
+
#collections-tab-contents.tab-content
|
|
17
|
+
.tab-pane.fade#collections-info-tab
|
|
18
|
+
%span5.well
|
|
19
|
+
%pre
|
|
20
|
+
=pretty_json(@collection.stats)
|
|
21
|
+
.pull-right
|
|
22
|
+
.span4
|
|
23
|
+
%button#rename-collection-button.btn.btn-medium.btn-info( data-toggle="modal" data-target="#rename-collection-dialog" data-collection-name="#{h @collection.name}" data-db-name="#{h current_db.name}")
|
|
24
|
+
RENAME
|
|
25
|
+
.span1
|
|
26
|
+
.span4
|
|
27
|
+
%button#delete-collection-button.btn.btn-medium.btn-danger( data-toggle="modal" data-target="#confirm-collection-deletion" data-collection-name="#{h @collection.name}" data-db-name="#{h current_db.name}")
|
|
28
|
+
DELETE
|
|
29
|
+
.tab-pane.fade#collections-documents-tab
|
|
30
|
+
=partial 'collections/documents', :locals=>{:db=>current_db, :collection=>@collection}
|
|
31
|
+
.tab-pane.fade#collections-indexes-tab
|
|
32
|
+
=partial 'collections/indexes', :locals=>{:db=>current_db, :collection=>@collection}
|
|
33
|
+
|
|
34
|
+
.row
|
|
35
|
+
- else
|
|
36
|
+
Unknown collection: #{params[:name]}
|
|
37
|
+
|
|
38
|
+
#rename-collection-dialog.modal(style="display:none;")
|
|
39
|
+
.modal-header
|
|
40
|
+
%a.close( href='#' data-dismiss='modal') ×
|
|
41
|
+
%h3
|
|
42
|
+
Rename:
|
|
43
|
+
%span.old_collection_name
|
|
44
|
+
.modal-body
|
|
45
|
+
%form.primary.form-horizontal{:method => "post", :action => "#"}
|
|
46
|
+
%input#verb-type{:type => "hidden", :name => "_method", :value => "put"}
|
|
47
|
+
%fieldset
|
|
48
|
+
.control-group
|
|
49
|
+
%label.control-label( for="input-name") New name:
|
|
50
|
+
.controls
|
|
51
|
+
%input.collection-name.input-xlarge( type="text" name="collection_new_name" style="height: 28px;")
|
|
52
|
+
%p.help-block please enter the new name of this collection.
|
|
53
|
+
.form-actions.modal-footer
|
|
54
|
+
%button#btn-create.btn.btn-medium.btn-danger.closeModal( type="submit")
|
|
55
|
+
RENAME
|
|
56
|
+
%button.btn.btn-medium.btn-success( data-dismiss = 'modal')
|
|
57
|
+
CANCEL
|
|
58
|
+
|
|
59
|
+
#confirm-collection-deletion.modal.fit-content( style="display:none;")
|
|
60
|
+
%button.close( data-dismiss='modal') x
|
|
61
|
+
.modal-header
|
|
62
|
+
%h2.no-wrap
|
|
63
|
+
Delete:
|
|
64
|
+
%span#delete-collection-dialog-title
|
|
65
|
+
?
|
|
66
|
+
.modal-body
|
|
67
|
+
%p
|
|
68
|
+
%h3
|
|
69
|
+
You will delete all the documents in this collection.
|
|
70
|
+
This cannot be undone.
|
|
71
|
+
.modal-footer
|
|
72
|
+
%form.primary{:method => "post", :action => "#"}
|
|
73
|
+
%input{:type => "hidden", :name => "_method", :value => "delete"}
|
|
74
|
+
%input.collection-name{:type => "hidden", :name => "collection_name", :value => ""}
|
|
75
|
+
%button.btn.btn-medium.btn-danger.span2.closeModal( type="submit")
|
|
76
|
+
DELETE
|
|
77
|
+
#cancel.btn.btn-medium.btn-success.span3( data-dismiss = 'modal')
|
|
78
|
+
CANCEL
|
|
79
|
+
|
|
80
|
+
%script{ :type => "text/javascript", :src =>"/js/collection-tools.js"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
%p
|
|
2
|
+
-#%h6 database users
|
|
3
|
+
-unless users.count == 0
|
|
4
|
+
%table.table.table-striped.table-condensed
|
|
5
|
+
%thead
|
|
6
|
+
%tr
|
|
7
|
+
%th(width='75%' valign='top')
|
|
8
|
+
User name
|
|
9
|
+
%th(width='15%')
|
|
10
|
+
r/o
|
|
11
|
+
%th(width='10%')
|
|
12
|
+
|
|
13
|
+
%tbody
|
|
14
|
+
-users.each do |u|
|
|
15
|
+
%tr
|
|
16
|
+
%td
|
|
17
|
+
%a{:href => "#"}
|
|
18
|
+
=u['user']
|
|
19
|
+
%td
|
|
20
|
+
%span.pull-right
|
|
21
|
+
=u['readOnly'] ? '✓': '-'
|
|
22
|
+
%td(nowrap)
|
|
23
|
+
%a.delete-user(href="#" data-db-name="#{h db_name}"data-id="#{h u['_id']}" data-user-name="#{h u['user']}")
|
|
24
|
+
%i.icon-remove
|
|
25
|
+
-else
|
|
26
|
+
%h4 no users
|
|
27
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
%button.btn.btn-medium.btn-primary.disabled
|
|
2
|
+
db: #{@db.name}
|
|
3
|
+
|
|
4
|
+
.row
|
|
5
|
+
.span5.well.well-small
|
|
6
|
+
%h3 Info
|
|
7
|
+
%pre
|
|
8
|
+
=pretty_json(@db.stats)
|
|
9
|
+
.pull-right
|
|
10
|
+
Delete this database?
|
|
11
|
+
%button.btn.btn-medium.btn-danger( data-toggle="modal" data-target="#confirm-db-deletion")
|
|
12
|
+
DELETE
|
|
13
|
+
.span4
|
|
14
|
+
-# http://learnmongo.com/posts/quick-tip-mongodb-users/
|
|
15
|
+
= partial :'databases/list_users', :locals => {:users=>@db.users, :db_name => @db.name}
|
|
16
|
+
%button#add-user.btn.btn-small.btn-info( data-toggle="modal" data-target="#user-dialog")
|
|
17
|
+
Add a user
|
|
18
|
+
.row
|
|
19
|
+
|
|
20
|
+
#user-dialog.modal.fit-content( style="display:none;")
|
|
21
|
+
%button.close( data-dismiss='modal') x
|
|
22
|
+
.modal-header
|
|
23
|
+
%h2#title Add user
|
|
24
|
+
.modal-body
|
|
25
|
+
%form.form-horizontal{:method => "post", :action => "/databases/#{@db.name}/users"}
|
|
26
|
+
%input{:type => "hidden", :name => "_method", :value => "post"}
|
|
27
|
+
%fieldset
|
|
28
|
+
.control-group
|
|
29
|
+
%label.control-label( for="input-name") Name:
|
|
30
|
+
.controls
|
|
31
|
+
%input#input-name.input-xlarge( type="text" name="username" style="height: 28px;")
|
|
32
|
+
%p.help-block please enter the user name
|
|
33
|
+
.control-group
|
|
34
|
+
%label.control-label( for="input-password") Password:
|
|
35
|
+
.controls
|
|
36
|
+
%input#input-password.input-xlarge( type="password" name="password" style="height: 28px;")
|
|
37
|
+
%p.help-block please enter the user password
|
|
38
|
+
.control-group
|
|
39
|
+
%label.control-label( for='is-read-only')
|
|
40
|
+
.controls
|
|
41
|
+
%label.checkbox.inline
|
|
42
|
+
%input#is-read-only{ :type=>'checkbox', :name=>'readonly'} Read-only?
|
|
43
|
+
.form-actions.modal-footer
|
|
44
|
+
%button.btn.btn-medium.btn-danger( data-dismiss = 'modal')
|
|
45
|
+
CANCEL
|
|
46
|
+
%button#btn-create.btn.btn-medium.btn-primary.closeModal( type="submit")
|
|
47
|
+
SAVE USER
|
|
48
|
+
|
|
49
|
+
#confirm-db-deletion.modal.fit-content( style="display:none;")
|
|
50
|
+
%button.close( data-dismiss='modal') x
|
|
51
|
+
.modal-header
|
|
52
|
+
%h2 Delete '#{@db.name}'?
|
|
53
|
+
.modal-body
|
|
54
|
+
%p This cannot be undone.
|
|
55
|
+
.modal-footer
|
|
56
|
+
%form{:method => "post", :action => "/databases/#{@db.name}"}
|
|
57
|
+
%input{:type => "hidden", :name => "_method", :value => "delete"}
|
|
58
|
+
#cancel.btn.btn-medium.btn-success.span3( data-dismiss = 'modal')
|
|
59
|
+
CANCEL
|
|
60
|
+
%button.btn.btn-medium.btn-danger.span2.closeModal( type="submit")
|
|
61
|
+
DELETE
|
|
62
|
+
|
|
63
|
+
#confirm-delete-user.modal.fit-content(style="display:none;")
|
|
64
|
+
.modal-header
|
|
65
|
+
%h1#delete-dialog-title Delete user
|
|
66
|
+
.modal-body
|
|
67
|
+
#user-name
|
|
68
|
+
%p
|
|
69
|
+
%h5
|
|
70
|
+
Warning, this operation cannot be undone! Are you sure you want to delete this user?
|
|
71
|
+
.modal-footer
|
|
72
|
+
%form.primary{:method => "post", :action => ""}
|
|
73
|
+
%input{:type => "hidden", :name => "_method", :value => "delete"}
|
|
74
|
+
%input.username{:type => "hidden", :name => "username", :value => ""}
|
|
75
|
+
%button.btn.btn-medium.btn-danger.span2.closeModal( type="submit")
|
|
76
|
+
DELETE
|
|
77
|
+
#cancel.btn.btn-medium.btn-success.span3( data-dismiss = 'modal')
|
|
78
|
+
CANCEL
|
|
79
|
+
|
|
80
|
+
%script{ :type => "text/javascript", :src =>"/js/db-tools.js"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
:redcarpet
|
|
2
|
+
### MongoFE
|
|
3
|
+
- a simple MongoBD administrative front-end that helps you play and learn MongoDB or manage simple MongoDB administrative tasks.
|
|
4
|
+
|
|
5
|
+
### Description
|
|
6
|
+
This is a simple Sinatra based web front-end that can be used for experimenting and learning MongoDB. The MongoFe gem can also be used for simple administrative tasks, managing collections and document basic operations such as: create new documents, delete existing ones, search by various criteria and document indexing.
|
|
7
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
!!!
|
|
2
|
+
%html
|
|
3
|
+
%head
|
|
4
|
+
%meta{:charset => "utf-8"}
|
|
5
|
+
%title MongoFE - A MongoDB simple administrative front-end
|
|
6
|
+
%meta{:name => "description", :content => "A very simple yet versatile MongoDB administrative user interface"}
|
|
7
|
+
%meta{:name => "author", :content => "Florin T.PATRASCU"}
|
|
8
|
+
:plain
|
|
9
|
+
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
|
|
10
|
+
<!--[if lt IE 9]>
|
|
11
|
+
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
|
12
|
+
<![endif]-->
|
|
13
|
+
|
|
14
|
+
/ Le styles
|
|
15
|
+
%link{ :href => "/bootstrap/css/bootstrap.css", :type => "text/css", :rel => "stylesheet" }
|
|
16
|
+
/ add body padding styles
|
|
17
|
+
%link{ :href => "/css/styles.css", :type => "text/css", :rel => "stylesheet" }
|
|
18
|
+
%link{ :href => "/css/digg_pagination.css", :type => "text/css", :rel => "stylesheet" }
|
|
19
|
+
|
|
20
|
+
-# JSON Editor, the CSS
|
|
21
|
+
%link{ :href => "/css/jsoneditor.css", :type => "text/css", :rel => "stylesheet" }
|
|
22
|
+
%link{ :href => "/bootstrap/css/bootstrap-responsive.css", :type => "text/css", :rel => "stylesheet" }
|
|
23
|
+
|
|
24
|
+
/ Le fav and touch icons
|
|
25
|
+
%link{ rel: "apple-touch-icon", href: "/bootstrap/images/apple-touch-icon.png"}
|
|
26
|
+
%link{ rel: "shortcut icon", href: "/bootstrap/images/favicon.ico"}
|
|
27
|
+
%link{ rel: "apple-touch-icon", sizes: "72x72", href: "/bootstrap/images/apple-touch-icon-72x72.png"}
|
|
28
|
+
%link{ rel: "apple-touch-icon", sizes: "114x114", href: "/bootstrap/images/apple-touch-icon-114x114.png"}
|
|
29
|
+
|
|
30
|
+
%script{ :type => "text/javascript", :src =>"/js/jsoneditor/json2.js"}
|
|
31
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/jquery.js"}
|
|
32
|
+
%script{ :type => "text/javascript", :src =>"/js/jsoneditor/jquery.jsoneditor.min.js"}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
%body
|
|
36
|
+
|
|
37
|
+
%div= haml :navbar
|
|
38
|
+
%div
|
|
39
|
+
.container-fluid
|
|
40
|
+
.row-fluid
|
|
41
|
+
.span3
|
|
42
|
+
.well.sidebar-nav
|
|
43
|
+
%ul.nav-list.nav
|
|
44
|
+
%li.nav-header
|
|
45
|
+
%span
|
|
46
|
+
Databases
|
|
47
|
+
%a#new-db(href="#" data-toggle="modal" data-target="#new-db-dialog")
|
|
48
|
+
%i.icon-plus-sign
|
|
49
|
+
- dbs = MongoFe::MongoDB.available_databases
|
|
50
|
+
- unless dbs.nil?
|
|
51
|
+
- dbs.each do |d|
|
|
52
|
+
%li
|
|
53
|
+
%a{:href => "/databases/#{h d}"}
|
|
54
|
+
-if current_db? && d == current_db.name
|
|
55
|
+
%i.icon-ok-circle
|
|
56
|
+
-else
|
|
57
|
+
%i.icon-minus.icon-white
|
|
58
|
+
#{d}
|
|
59
|
+
-if current_db?
|
|
60
|
+
%li.nav-header
|
|
61
|
+
%span
|
|
62
|
+
Collections
|
|
63
|
+
-if current_db?
|
|
64
|
+
%a#add-collection( data-target="#new-collection-dialog" href="#" data-toggle="modal" data-db-name="#{h current_db.name}")
|
|
65
|
+
%i.icon-plus-sign
|
|
66
|
+
- collections = current_db.collection_names - %w[system.users system.indexes]
|
|
67
|
+
- unless collections.nil?
|
|
68
|
+
- collections.each do |c|
|
|
69
|
+
%li
|
|
70
|
+
%a{:href => "/databases/#{h current_db.name}/collections/#{h c}"}
|
|
71
|
+
-if !collection.nil? && c == collection.name
|
|
72
|
+
%i.icon-ok-circle
|
|
73
|
+
-else
|
|
74
|
+
%i.icon-minus.icon-white
|
|
75
|
+
#{c}
|
|
76
|
+
- else
|
|
77
|
+
%li empty
|
|
78
|
+
.span9
|
|
79
|
+
- flash.each do |name, msg|
|
|
80
|
+
.alert.fade.in{:class => "alert alert-#{name}", "data-alert" => "alert"}
|
|
81
|
+
%a.close{"data-dismiss" => "alert", :href => "#"}
|
|
82
|
+
%i.icon-remove
|
|
83
|
+
%p=msg
|
|
84
|
+
- flash.clear if flash.count > 0
|
|
85
|
+
|
|
86
|
+
.span9
|
|
87
|
+
= yield
|
|
88
|
+
%br
|
|
89
|
+
%hr
|
|
90
|
+
%h6= haml :footer
|
|
91
|
+
|
|
92
|
+
-# FIXME: the next two dialogs can be one
|
|
93
|
+
#new-db-dialog.modal(style="display:none;")
|
|
94
|
+
.modal-header
|
|
95
|
+
%a.close( href='#' data-dismiss='modal') ×
|
|
96
|
+
%h2#dialog-title New database
|
|
97
|
+
.modal-body
|
|
98
|
+
%form.form-horizontal{:method => "post", :action => "/databases"}
|
|
99
|
+
%input#verb-type{:type => "hidden", :name => "_method", :value => "post"}
|
|
100
|
+
%fieldset
|
|
101
|
+
.control-group
|
|
102
|
+
%label.control-label( for="input-name") Name:
|
|
103
|
+
.controls
|
|
104
|
+
%input#input-name.input-xlarge( type="text" name="name" style="height: 28px;")
|
|
105
|
+
%p.help-block please enter the name of the database.
|
|
106
|
+
.form-actions.modal-footer
|
|
107
|
+
%button#btn-create.btn.btn-medium.btn-primary.closeModal( type="submit")
|
|
108
|
+
CREATE
|
|
109
|
+
%button.btn.btn-medium.btn-danger( data-dismiss = 'modal')
|
|
110
|
+
CANCEL
|
|
111
|
+
|
|
112
|
+
#new-collection-dialog.modal(style="display:none;")
|
|
113
|
+
.modal-header
|
|
114
|
+
%a.close( href='#' data-dismiss='modal') ×
|
|
115
|
+
%h2#dialog-title New collection
|
|
116
|
+
.modal-body
|
|
117
|
+
%form.primary.form-horizontal{:method => "post", :action => "#"}
|
|
118
|
+
%input#verb-type{:type => "hidden", :name => "_method", :value => "post"}
|
|
119
|
+
%fieldset
|
|
120
|
+
.control-group
|
|
121
|
+
%label.control-label( for="input-name") Name:
|
|
122
|
+
.controls
|
|
123
|
+
%input.collection-name.input-xlarge( type="text" name="collection_name" style="height: 28px;")
|
|
124
|
+
%p.help-block please enter the name of the collection.
|
|
125
|
+
.form-actions.modal-footer
|
|
126
|
+
%button#btn-create.btn.btn-medium.btn-primary.closeModal( type="submit")
|
|
127
|
+
CREATE
|
|
128
|
+
%button.btn.btn-medium.btn-danger( data-dismiss = 'modal')
|
|
129
|
+
CANCEL
|
|
130
|
+
|
|
131
|
+
/ Placed at the end of the document so the pages load faster
|
|
132
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-transition.js"}
|
|
133
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-alert.js"}
|
|
134
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-modal.js"}
|
|
135
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-dropdown.js"}
|
|
136
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-scrollspy.js"}
|
|
137
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-tab.js"}
|
|
138
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-tooltip.js"}
|
|
139
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-popover.js"}
|
|
140
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-button.js"}
|
|
141
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-collapse.js"}
|
|
142
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-carousel.js"}
|
|
143
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/basic/bootstrap-typeahead.js"}
|
|
144
|
+
%script{ :type => "text/javascript", :src =>"/bootstrap/js/underscore-min.js"}
|
|
145
|
+
|
|
146
|
+
%script{ :type => "text/javascript", :src =>"/app/application.js"}
|
|
147
|
+
%script{ :type => "text/javascript", :src =>"/js/db-tools.js"}
|
|
148
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.navbar.navbar-fixed-top
|
|
2
|
+
.navbar-inner
|
|
3
|
+
.container-fluid
|
|
4
|
+
%a.btn.btn-navbar{ "data-toggle".to_sym => "collapse", "data-target".to_sym => ".nav-collapse"}
|
|
5
|
+
%span.icon-bar
|
|
6
|
+
%span.icon-bar
|
|
7
|
+
%span.icon-bar
|
|
8
|
+
%a.brand{:href => '/'} MongoFE
|
|
9
|
+
.nav-collapse
|
|
10
|
+
%p.navbar-text.pull-right
|
|
11
|
+
-if current_db?
|
|
12
|
+
=current_db.name
|
|
13
|
+
- if collection?
|
|
14
|
+
|
|
|
15
|
+
=collection.name
|
|
16
|
+
-else
|
|
17
|
+
database|collection
|
data/lib/mongo_fe.rb
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# encoding=utf-8
|
|
2
|
+
|
|
3
|
+
require File.dirname(__FILE__) + '/mongo_fe/version'
|
|
4
|
+
require "mongo" # API: http://api.mongodb.org/ruby/1.6.4/
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module MongoFe
|
|
8
|
+
|
|
9
|
+
module MongoDB
|
|
10
|
+
@@user_db = nil
|
|
11
|
+
|
|
12
|
+
# simple proxy to Mongo::DB
|
|
13
|
+
class Database
|
|
14
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
|
|
15
|
+
|
|
16
|
+
def initialize(db)
|
|
17
|
+
@db=db
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def users
|
|
21
|
+
if db_users = @db[Mongo::DB::SYSTEM_USER_COLLECTION]
|
|
22
|
+
db_users.find.to_a
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def find_user(username=nil)
|
|
27
|
+
if db_users = @db[Mongo::DB::SYSTEM_USER_COLLECTION]
|
|
28
|
+
db_users.find_one({:user => username}) unless username.nil?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# todo: Add more convenient methods
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
def method_missing(name, *args, &block)
|
|
36
|
+
@db.send(name, *args, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class SearchDocuments
|
|
41
|
+
def initialize(a_db, a_collection)
|
|
42
|
+
@db=a_db
|
|
43
|
+
@collection=a_collection
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def list(user_query, page=1, per_page=10)
|
|
47
|
+
page_nr = page > 0 ? page : 1
|
|
48
|
+
query = user_query.nil? ? [{}, [], []] : user_query
|
|
49
|
+
|
|
50
|
+
q = query.first.dup
|
|
51
|
+
|
|
52
|
+
# verify if we have query containing a regexp
|
|
53
|
+
q.each_pair do |k, v|
|
|
54
|
+
if v.is_a?(String) && v.index(/^\//)
|
|
55
|
+
q[k] = Regexp.new(v.gsub("/", ''))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# quickly find the total hits
|
|
60
|
+
count = @db.collection(@collection.name).find(q).count
|
|
61
|
+
|
|
62
|
+
# get the docs and paginate.
|
|
63
|
+
documents_list = WillPaginate::Collection.create(page_nr, per_page, count) do |pager|
|
|
64
|
+
results = @collection.find( q,
|
|
65
|
+
:sort => sort_by(query.last),
|
|
66
|
+
:skip => ((page_nr-1) * per_page).to_i,
|
|
67
|
+
:limit => per_page,
|
|
68
|
+
:fields => just_these_fields(query[1])
|
|
69
|
+
).to_a
|
|
70
|
+
pager.replace(results)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
[count, documents_list]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private #---------------- private stuff
|
|
77
|
+
|
|
78
|
+
# @param [String] criteria_string
|
|
79
|
+
def sort_by(criteria_string)
|
|
80
|
+
criteria_string.empty? ? [['_id', Mongo::DESCENDING]] : MongoFe::Utils.split_index_specs(criteria_string)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Retrieving a Subset of Fields info here: http://bit.ly/P2ZLiV
|
|
84
|
+
def just_these_fields(fields_string)
|
|
85
|
+
fields = nil #all
|
|
86
|
+
|
|
87
|
+
unless fields_string.nil?
|
|
88
|
+
fields_string = fields_string.delete(' ')
|
|
89
|
+
unless fields_string.nil?
|
|
90
|
+
fields = []
|
|
91
|
+
fields_string.split(/[\s,]+/).each do |f|
|
|
92
|
+
fields << f.to_sym unless f.nil?
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
fields
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.use(db_name=nil)
|
|
102
|
+
if !@@user_db.nil? && db_name != @@user_db
|
|
103
|
+
raise 'Invalid database name. Not authorized, maybe?!'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Database.new(self.connection.db(db_name))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.uri
|
|
110
|
+
@@uri = nil unless defined?(@@uri)
|
|
111
|
+
@@uri
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.uri=(uri)
|
|
115
|
+
begin
|
|
116
|
+
@@uri = URI.parse(uri)
|
|
117
|
+
@@host = @@uri.host
|
|
118
|
+
@@port = @@uri.port
|
|
119
|
+
rescue => e
|
|
120
|
+
raise "#{e.message}; #{uri}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.connection
|
|
125
|
+
begin
|
|
126
|
+
unless defined?(@@connection)
|
|
127
|
+
uri = @@uri.to_s
|
|
128
|
+
@@connection = Mongo::Connection.from_uri(uri)
|
|
129
|
+
if @@uri.user && @@uri.password
|
|
130
|
+
if !@@uri.path.nil?
|
|
131
|
+
@@user_db = @@uri.path.gsub(/^\//, '')
|
|
132
|
+
@@connection.add_auth(@@user_db, @@uri.user, @@uri.password)
|
|
133
|
+
else
|
|
134
|
+
raise "username and password provided but the db name is missing. Please verify the config uri."
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
@@connection
|
|
140
|
+
rescue Mongo::ConnectionFailure => e
|
|
141
|
+
raise "Please verify that you have a MongoDB started at: #{@@host}:#{@@port} error: #{e.message}"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def self.available_databases
|
|
146
|
+
if @@user_db
|
|
147
|
+
[@@user_db]
|
|
148
|
+
else
|
|
149
|
+
self.connection.database_names - %w[admin local slave]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class Utils
|
|
155
|
+
|
|
156
|
+
def self.split_index_specs(fields=nil)
|
|
157
|
+
specs = []
|
|
158
|
+
|
|
159
|
+
unless fields.empty?
|
|
160
|
+
fields.delete(' ').split(/[\s,]+/).each do |s|
|
|
161
|
+
f= s.split(/[\s:]+/)
|
|
162
|
+
specs << [f[0].to_sym, f[1].downcase.start_with?('asc') ? Mongo::ASCENDING : Mongo::DESCENDING]
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
specs
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
data/mongo_fe.gemspec
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/mongo_fe/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.name = "mongo_fe"
|
|
6
|
+
gem.version = MongoFe::VERSION
|
|
7
|
+
gem.authors = ["Florin T.PATRASCU"]
|
|
8
|
+
gem.email = %W(florin.patrascu@gmail.com)
|
|
9
|
+
gem.summary = %q{Play and learn MongoDB or manage simple MongoDB administrative tasks}
|
|
10
|
+
gem.description = %q{A simple Sinatra based web front-end that can be used for experimenting and learning MongoDB. The MongoFe gem can also be used for simple administrative tasks, managing collections and document basic operations such as: create new documents, delete existing ones, search by various criteria and document indexing.}
|
|
11
|
+
gem.homepage = ""
|
|
12
|
+
|
|
13
|
+
gem.files = `git ls-files`.split($\)
|
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
16
|
+
gem.require_paths = %W(lib)
|
|
17
|
+
gem.extra_rdoc_files = %w(LICENSE README.md)
|
|
18
|
+
|
|
19
|
+
# Gem dependencies for runtime
|
|
20
|
+
gem.add_runtime_dependency "hashie"
|
|
21
|
+
gem.add_runtime_dependency "haml", "~> 3.1.4"
|
|
22
|
+
gem.add_runtime_dependency "vegas", "~> 0.1.11"
|
|
23
|
+
gem.add_runtime_dependency "chronic", "~> 0.6.7"
|
|
24
|
+
gem.add_runtime_dependency "mongo", "~> 1.6.2"
|
|
25
|
+
gem.add_runtime_dependency "yajl-ruby"
|
|
26
|
+
gem.add_runtime_dependency "bson"
|
|
27
|
+
gem.add_runtime_dependency "bson_ext"
|
|
28
|
+
gem.add_runtime_dependency "json"
|
|
29
|
+
gem.add_runtime_dependency 'will_paginate', '>= 3.0.0'
|
|
30
|
+
gem.add_runtime_dependency 'will_paginate-bootstrap'
|
|
31
|
+
gem.add_runtime_dependency "sinatra", "~> 1.3.2"
|
|
32
|
+
gem.add_runtime_dependency "sinatra-contrib", "~> 1.3.1"
|
|
33
|
+
gem.add_runtime_dependency 'redcarpet', '~> 2.1.0'
|
|
34
|
+
gem.add_runtime_dependency 'coderay', '~> 1.0.5'
|
|
35
|
+
gem.add_runtime_dependency 'tilt', '~> 1.3.0'
|
|
36
|
+
|
|
37
|
+
# Gem dependencies for development
|
|
38
|
+
gem.add_development_dependency "rspec", "~> 2.9"
|
|
39
|
+
gem.add_development_dependency 'bundler', '~> 1.1.0'
|
|
40
|
+
gem.add_development_dependency 'appraisal', '~> 0.4.1'
|
|
41
|
+
gem.add_development_dependency 'cucumber'
|
|
42
|
+
gem.add_development_dependency 'rspec', '~> 2.9.0'
|
|
43
|
+
gem.add_development_dependency 'capybara', '~> 1.1.2'
|
|
44
|
+
gem.add_development_dependency 'webrat', '~> 0.7.3'
|
|
45
|
+
gem.add_development_dependency 'shoulda-matchers', '~> 1.1.0'
|
|
46
|
+
gem.add_development_dependency "factory_girl", "~> 3.0"
|
|
47
|
+
gem.add_development_dependency "simplecov", ">=0.4.2"
|
|
48
|
+
gem.add_development_dependency "logger"
|
|
49
|
+
|
|
50
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|