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.
Files changed (71) hide show
  1. data/.DS_Store +0 -0
  2. data/.gitignore +34 -0
  3. data/.rspec +2 -0
  4. data/CHANGES.md +3 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +138 -0
  7. data/LICENSE +22 -0
  8. data/Procfile +2 -0
  9. data/README.md +72 -0
  10. data/Rakefile +14 -0
  11. data/TODO.tasks +9 -0
  12. data/bin/config.ru +28 -0
  13. data/bin/mongofe +54 -0
  14. data/lib/mongo_fe/application_controller.rb +91 -0
  15. data/lib/mongo_fe/controllers/collections_controller.rb +278 -0
  16. data/lib/mongo_fe/controllers/databases_controller.rb +94 -0
  17. data/lib/mongo_fe/helpers/helpers.rb +128 -0
  18. data/lib/mongo_fe/public/app/application.js +3 -0
  19. data/lib/mongo_fe/public/bootstrap/css/bootstrap-responsive.css +567 -0
  20. data/lib/mongo_fe/public/bootstrap/css/bootstrap.css +3380 -0
  21. data/lib/mongo_fe/public/bootstrap/img/glyphicons-halflings-white.png +0 -0
  22. data/lib/mongo_fe/public/bootstrap/img/glyphicons-halflings.png +0 -0
  23. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-alert.js +91 -0
  24. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-button.js +98 -0
  25. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-carousel.js +154 -0
  26. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-collapse.js +136 -0
  27. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-dropdown.js +92 -0
  28. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-modal.js +209 -0
  29. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-popover.js +95 -0
  30. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-scrollspy.js +125 -0
  31. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-tab.js +130 -0
  32. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-tooltip.js +270 -0
  33. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-transition.js +51 -0
  34. data/lib/mongo_fe/public/bootstrap/js/basic/bootstrap-typeahead.js +271 -0
  35. data/lib/mongo_fe/public/bootstrap/js/bootstrap.js +1722 -0
  36. data/lib/mongo_fe/public/bootstrap/js/bootstrap.min.js +1 -0
  37. data/lib/mongo_fe/public/bootstrap/js/jquery.js +9252 -0
  38. data/lib/mongo_fe/public/bootstrap/js/underscore-min.js +32 -0
  39. data/lib/mongo_fe/public/bootstrap/js/underscore.js +1059 -0
  40. data/lib/mongo_fe/public/css/digg_pagination.css +28 -0
  41. data/lib/mongo_fe/public/css/jsoneditor.css +70 -0
  42. data/lib/mongo_fe/public/css/styles.css +11 -0
  43. data/lib/mongo_fe/public/images/missing_avatar_small.png +0 -0
  44. data/lib/mongo_fe/public/js/collection-tools.js +63 -0
  45. data/lib/mongo_fe/public/js/db-tools.js +45 -0
  46. data/lib/mongo_fe/public/js/jsoneditor/jquery.jsoneditor.min.LICENSE +20 -0
  47. data/lib/mongo_fe/public/js/jsoneditor/jquery.jsoneditor.min.js +6 -0
  48. data/lib/mongo_fe/public/js/jsoneditor/json2.js +482 -0
  49. data/lib/mongo_fe/version.rb +6 -0
  50. data/lib/mongo_fe/views/collections/_document_attributes.haml +6 -0
  51. data/lib/mongo_fe/views/collections/_documents.haml +35 -0
  52. data/lib/mongo_fe/views/collections/_documents_page.haml +82 -0
  53. data/lib/mongo_fe/views/collections/_indexes.haml +76 -0
  54. data/lib/mongo_fe/views/collections/index.haml +80 -0
  55. data/lib/mongo_fe/views/databases/_list_users.haml +27 -0
  56. data/lib/mongo_fe/views/databases/info.haml +80 -0
  57. data/lib/mongo_fe/views/footer.haml +4 -0
  58. data/lib/mongo_fe/views/index.haml +7 -0
  59. data/lib/mongo_fe/views/layout.haml +148 -0
  60. data/lib/mongo_fe/views/navbar.haml +17 -0
  61. data/lib/mongo_fe.rb +170 -0
  62. data/mongo_fe.gemspec +50 -0
  63. data/screens/example_db_view.png +0 -0
  64. data/screens/example_doc_view.png +0 -0
  65. data/screens/example_indexes.png +0 -0
  66. data/spec/config_spec.rb +17 -0
  67. data/spec/controllers/collections_controller_spec.rb +151 -0
  68. data/spec/controllers/db_controller_spec.rb +69 -0
  69. data/spec/factories/factories.rb +19 -0
  70. data/spec/spec_helper.rb +88 -0
  71. metadata +559 -0
@@ -0,0 +1,278 @@
1
+ # encoding: utf-8
2
+ require "json"
3
+ require File.dirname(__FILE__) + '/../application_controller'
4
+ require 'will_paginate'
5
+ require 'will_paginate/view_helpers/sinatra'
6
+ require "will_paginate/bootstrap"
7
+
8
+ module MongoFe
9
+ # This controller works as a collection manager for the current db, a database specified by
10
+ # name as a REST parameter.
11
+
12
+ class CollectionsController < ApplicationController
13
+ namespace '/databases/:db_name/collections' do
14
+
15
+ before do
16
+ session[:db]=params[:db_name]
17
+ end
18
+
19
+ get "/?" do
20
+ haml :'/collections/index'
21
+ end
22
+
23
+ get "/:collection_name/?" do
24
+
25
+ collection_name = params[:collection_name]
26
+
27
+ unless collection_name.nil?
28
+ session[:collection] = collection_name
29
+ @collection = current_db.collection collection_name
30
+ @indexes = @collection.index_information
31
+ @page = params[:page].to_i || 1
32
+ @query= session[:query]
33
+
34
+ begin
35
+ @total, @documents = MongoFe::MongoDB::SearchDocuments.new(current_db, @collection).list(@query, @page, 10)
36
+ flash_search_results @query, @total
37
+ rescue => e
38
+ session[:query] = nil
39
+ @total, @documents = MongoFe::MongoDB::SearchDocuments.new(current_db, @collection).list(nil, 1, 10)
40
+
41
+ flash[:error] = "Resetting to find all. MongoDB error: #{e.message}"
42
+ end
43
+
44
+ end
45
+
46
+ haml :'/collections/index'
47
+ end
48
+
49
+ post "/?" do
50
+ if (collection_name=params[:collection_name])
51
+ current_db.create_collection(collection_name)
52
+ flash[:notice] = "#{collection_name}, successfully created!"
53
+ redirect "/databases/#{current_db.name}/collections/#{collection_name}"
54
+ else
55
+ flash[:error] = "Error: you must provide a collection name."
56
+ redirect "/databases/#{current_db.name}"
57
+ end
58
+ end
59
+
60
+ post "/:collection_name/search/?" do |db_name, collection_name|
61
+
62
+ if params[:reset_query].nil?
63
+ json_query = params[:json_query].empty? ? '{}' : params[:json_query].gsub(/'/, "\"")
64
+ json_query_show = params[:json_query_show]
65
+ json_query_sort = params[:json_query_sort]
66
+
67
+ begin
68
+ @json_query = JSON.parse json_query
69
+ puts "{query}: #{@json_query.inspect}"
70
+ session[:query] = [@json_query, json_query_show, json_query_sort]
71
+ rescue => e
72
+ flash[:error] = "Invalid query; #{e.message}"
73
+ end
74
+ else
75
+ session[:query] = nil
76
+ end
77
+
78
+ redirect "/databases/#{current_db.name}/collections/#{collection_name}"
79
+ end
80
+
81
+ delete "/:collection_name/documents*" do |db_name, collection_name, doc_id|
82
+ if (doc_id = params[:doc_id])
83
+ begin
84
+ collection = current_db.collection collection_name
85
+ collection.remove(:_id => BSON::ObjectId.from_string(doc_id))
86
+ flash[:notice] = "Document id: #{doc_id}, deleted from: #{db_name}.#{collection.name}"
87
+ rescue => e
88
+ flash[:error] = "Cannot delete document id: #{doc_id}, from: #{db_name}.#{collection_name}; #{e.message}"
89
+ end
90
+ else
91
+ flash[:error] = "The document id must be specified"
92
+ end
93
+
94
+ redirect "/databases/#{current_db.name}/collections/#{collection_name}"
95
+ end
96
+
97
+ put "/:collection_name/documents*" do |db_name, collection_name, z|
98
+ if (doc_id = params[:doc_id])
99
+ begin
100
+ @collection = current_db.collection collection_name
101
+ id = BSON::ObjectId.from_string(doc_id)
102
+ r = @collection.find(:_id => id).to_a
103
+ raise "this document is missing!" if r.nil?
104
+
105
+ begin
106
+ if (doc=params[:json_doc_attributes])
107
+ begin
108
+ @doc_id = collection.update({"_id" => id}, JSON.parse(doc)) # rewrite the doc; in the future will use an atomic operator
109
+ flash[:notice] = "Document id: #{doc_id}, updated"
110
+ rescue => e
111
+ raise "Invalid JSON structure: #{e.message}"
112
+ end
113
+ else
114
+ raise "Invalid document."
115
+ end
116
+ rescue => e
117
+ flash[:error] = "Cannot update document; #{e.message}"
118
+ end
119
+
120
+ rescue => e
121
+ flash[:error] = "Cannot update the document with id: #{doc_id}; #{e.message}"
122
+ end
123
+ else
124
+ flash[:error] = "The document id must be specified"
125
+ end
126
+
127
+ redirect "/databases/#{current_db.name}/collections/#{collection_name}"
128
+ end
129
+
130
+ # create a new index
131
+ post "/:collection_name/indexes*" do |db_name, collection_name, z|
132
+ @collection = current_db.collection collection_name
133
+ session[:collection] = @collection.name
134
+
135
+ unique = !params[:unique].nil? && params[:unique]
136
+ sparse = !params[:sparse].nil? && params[:sparse]
137
+
138
+ if (fields = params[:index_fields])
139
+ begin
140
+ index_name = @collection.create_index(MongoFe::Utils.split_index_specs(fields), {:unique => unique, :sparse => sparse})
141
+
142
+ flash[:notice]="The index: '#{index_name}' was successfully created"
143
+ rescue => e
144
+ flash[:error]="Cannot create the index; #{e.message}"
145
+ end
146
+ else
147
+ flash[:error]="Specify either a single field name or an array of field name, direction pairs"
148
+ end
149
+
150
+ redirect "/databases/#{current_db.name}/collections/#{collection_name}"
151
+ end
152
+
153
+
154
+ # delete an existing index
155
+ #
156
+ delete "/:collection_name/indexes*" do |db_name, collection_name, z|
157
+ @collection = current_db.collection collection_name
158
+
159
+ if (index_name = params[:index_name])
160
+ begin
161
+ @collection.drop_index index_name
162
+ flash[:notice] = "The index: '#{index_name}', was deleted successfully."
163
+ rescue => e
164
+ flash[:error] = "The index: '#{index_name}', cannot be deleted; #{e.message}"
165
+ end
166
+
167
+ else
168
+ flash[:error] = "Please specify the index name."
169
+ end
170
+
171
+ session[:collection] = @collection.name
172
+ redirect "/databases/#{current_db.name}/collections/#{collection_name}"
173
+ end
174
+
175
+
176
+ # create a new document
177
+ #
178
+ post "/:collection_name/documents*" do
179
+ @collection = current_db.collection(params[:collection_name])
180
+
181
+ if @collection.nil?
182
+ flash[:error] = "Error: you must provide a valid collection name."
183
+ redirect "/databases/#{current_db.name}"
184
+ else
185
+ session[:collection] = @collection.name # move this and the one above to a before or such
186
+
187
+ begin
188
+ if (doc=params[:json_doc_attributes])
189
+ begin
190
+ @doc_id = @collection.insert(JSON.parse(doc))
191
+ flash[:notice] = "Document id: #{@doc_id.to_s}, added to: #{current_db.name}.#{@collection.name}"
192
+ rescue => e
193
+ raise "Invalid JSON structure; #{e.message}"
194
+ end
195
+ else
196
+ raise "Invalid document."
197
+ end
198
+ rescue => e
199
+ flash[:error] = "Cannot insert document; error: #{e.message}"
200
+ end
201
+
202
+ end
203
+
204
+ unless @collection.nil?
205
+ @page = params[:page].to_i || 1
206
+ @query= session[:query]
207
+
208
+ begin
209
+ @total, @documents = MongoFe::MongoDB::SearchDocuments.new(current_db, @collection).list(@query, @page, 10)
210
+ flash_search_results @query, @total
211
+ rescue => e
212
+ session[:query] = nil
213
+ @total, @documents = MongoFe::MongoDB::SearchDocuments.new(current_db, @collection).list(nil, 1, 10)
214
+
215
+ flash[:error] = "Resetting to find all. Cause: #{e.message}"
216
+ end
217
+
218
+ end
219
+
220
+ haml :'/collections/index'
221
+ end
222
+
223
+ put "/:collection_name/?" do
224
+ if (collection_name=params[:collection_name])
225
+ collection = current_db.collection(collection_name)
226
+
227
+ if collection.nil?
228
+ flash[:error] = "The db: #{current_db.name} doesn't know this collection: #{collection_name}"
229
+ else
230
+ if (collection_new_name = params[:collection_new_name])
231
+
232
+ begin
233
+ current_db.rename_collection(collection_name, collection_new_name)
234
+ flash[:notice] = "Collection: #{collection_name}, successfully renamed to: #{collection_new_name}"
235
+ redirect "/databases/#{current_db.name}/collections/#{collection_new_name}"
236
+ rescue => e
237
+ flash[:error] = "Cannot rename: #{collection_name}, to: #{collection_new_name}; error: #{e.message}"
238
+ end
239
+
240
+ else
241
+ flash[:error] = "You must provide a collection name."
242
+ end
243
+ redirect "/databases/#{current_db.name}/collections/#{collection_name}"
244
+ end
245
+
246
+ else
247
+ flash[:error] = "Error: you must provide a collection name."
248
+ redirect "/databases/#{current_db.name}"
249
+ end
250
+ end
251
+
252
+ delete "/:collection_name/?" do
253
+ db=current_db
254
+ if (collection_name=params[:collection_name])
255
+ if (collection = db.collection(collection_name))
256
+ begin
257
+ collection.drop
258
+ flash[:notice] = "#{collection_name}, deleted."
259
+ rescue => e
260
+ flash[:error] = "Error: #{e.message}, while trying to delete the collection: #{collection_name}"
261
+ end
262
+ end
263
+ else
264
+ flash[:error] = "Error: you must provide a collection name."
265
+ end
266
+
267
+ redirect "/databases/#{db.name}"
268
+ end
269
+
270
+ end
271
+
272
+ private
273
+ def flash_search_results(q, total)
274
+ flash[:notice]="query: #{query.string}<br/>#{total} document(s) found." unless (q.nil? || query.string.start_with?("ex."))
275
+ end
276
+ end
277
+
278
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: utf-8
2
+ require "mongo"
3
+ require File.dirname(__FILE__) + '/../application_controller'
4
+
5
+ module MongoFe
6
+ class DatabasesController < ApplicationController
7
+
8
+ get '/databases/?' do
9
+ "Move along, nothing to see here!"
10
+ end
11
+
12
+ # info about a specific database
13
+ get '/databases/:name/?' do
14
+ db_name = params[:name]
15
+ begin
16
+ @db = MongoFe::MongoDB.use(db_name)
17
+ session[:db] = db_name
18
+ session[:collection] = nil
19
+ # flash[:notice] = "DB Selected: #{db_name}. <a href='/databases/#{db_name}/info'>More info?</a>"
20
+ haml :'/databases/info'
21
+ rescue => e
22
+ session[:db]=nil
23
+ flash[:error] = "You must provide a database name; #{e.message}"
24
+ redirect "/"
25
+ end
26
+ end
27
+
28
+ # create a new database
29
+ post '/databases/?' do
30
+ if (d=params[:name])
31
+ db = MongoFe::MongoDB.use(d)
32
+ session[:db]=d
33
+ flash[:notice] = "A new database: `#{d}`, was created."
34
+ redirect "/databases/#{d}"
35
+ else
36
+ flash[:error] = "Error: you must provide a database name."
37
+ haml :index
38
+ end
39
+ end
40
+
41
+ # Delete database
42
+ delete '/databases/:name/?' do |d|
43
+ if d
44
+ begin
45
+ MongoFe::MongoDB.connection.drop_database(d)
46
+ flash[:notice] = "Database: #{d} was deleted."
47
+ session[:db]=nil
48
+ rescue => e
49
+ flash[:error] = "Error: #{e.message }; The database: `#{d}` was not deleted."
50
+ end
51
+ else
52
+ flash[:notice] = "Error: you must provide a database name."
53
+ end
54
+ haml :index
55
+ end
56
+
57
+ # create a new database user
58
+ post '/databases/:name/users/?' do |d|
59
+ if d && username=params[:username]
60
+ db = MongoFe::MongoDB.use(d)
61
+ if db.find_user(username)
62
+ flash[:error] = "the user: '#{username}' is already defined."
63
+ else
64
+ is_readonly = params[:readonly].nil? ? false : true
65
+ db.add_user(username, params[:password], is_readonly)
66
+ flash[:notice] = "User: '#{username}', was added successfully."
67
+ end
68
+ else
69
+ flash[:error] = "you must provide a user name."
70
+ end
71
+
72
+ redirect "/databases/#{d}"
73
+ end
74
+
75
+ # delete an exiting user
76
+ delete '/databases/:name/users/?' do |d|
77
+ if d && username=params[:username]
78
+ db = MongoFe::MongoDB.use(d)
79
+ if db.find_user(username)
80
+ db.remove_user(username)
81
+ flash[:notice] = "User deleted: '#{username}'"
82
+ else
83
+ flash[:error] = "Not a valid username: '#{username}'"
84
+ end
85
+ else
86
+ flash[:error] = "Please provide a user name."
87
+ end
88
+
89
+ redirect "/databases/#{d}"
90
+ end
91
+
92
+ end
93
+ end
94
+
@@ -0,0 +1,128 @@
1
+ require "json"
2
+ require "bson"
3
+ require 'will_paginate'
4
+ require 'will_paginate/view_helpers/base'
5
+ require 'will_paginate/view_helpers/link_renderer'
6
+
7
+ module Helpers
8
+ include Rack::Utils
9
+ include WillPaginate::ViewHelpers::Base
10
+
11
+ alias_method :h, :escape_html
12
+
13
+ def current_page
14
+ url_path request.path_info.sub('/', '')
15
+ end
16
+
17
+ def url_path(*path_parts)
18
+ [path_prefix, path_parts].join("/").squeeze('/')
19
+ end
20
+
21
+ alias_method :u, :url_path
22
+
23
+ def current_db?
24
+ true if session[:db]
25
+ end
26
+
27
+ def current_db
28
+ MongoFe::MongoDB.connection.db(session[:db]) if current_db?
29
+ end
30
+
31
+ def current_db_name
32
+ session[:db] if current_db?
33
+ end
34
+
35
+ def collection
36
+ current_db.collection(session[:collection]) if current_db? && collection?
37
+ end
38
+
39
+ def collection?
40
+ true if session[:collection]
41
+ end
42
+
43
+ def link_to(name, location, alternative = false)
44
+ if alternative && alternative[:condition]
45
+ "<a href=#{alternative[:location]}>#{alternative[:name]}</a>"
46
+ else
47
+ "<a href=#{location}>#{name}</a>"
48
+ end
49
+ end
50
+
51
+ # prettifying a JSON structure
52
+ def pretty_json(json)
53
+ JSON.pretty_generate(json)
54
+ end
55
+
56
+ # truncate a string to max characters and append '...' at the end
57
+ def truncate(str, max=0)
58
+ return '' if str.blank?
59
+
60
+ if str.length <= 1
61
+ t_string = str
62
+ else
63
+ t_string = str[0..[str.length, max].min]
64
+ t_string = str.length > t_string.length ? "#{t_string}..." : t_string
65
+ end
66
+
67
+ t_string
68
+ end
69
+
70
+ # poor man flash solution
71
+ def flash
72
+ session[:flash] = Hashie::Mash.new if session[:flash].nil?
73
+ session[:flash]
74
+ end
75
+
76
+ PLACEHOLDER_SORT = "ex. name:ASC, age:DESC"
77
+ PLACEHOLDER_QUERY_STRING = 'ex. {"name":"foo"}'
78
+ PLACEHOLDER_SHOW = "ex. name, age"
79
+
80
+ # check if there is a query in the session and return its elements as an openstruct object
81
+ def query
82
+ if (q = session[:query])
83
+ OpenStruct.new(:string => q.first.empty? ? '' : q.first.to_json,
84
+ :show => q[1].empty? ? '' : q[1],
85
+ :sort => q.last.empty? ? '' : q.last)
86
+ else
87
+ OpenStruct.new(:string => PLACEHOLDER_QUERY_STRING, :show => PLACEHOLDER_SHOW, :sort => PLACEHOLDER_SORT)
88
+ end
89
+ end
90
+
91
+ # stolen from http://github.com/cschneid/irclogger/blob/master/lib/partials.rb
92
+ # and made a lot more robust by me: https://gist.github.com/119874.
93
+ # Another me, not me :)
94
+ def partial(template, *args)
95
+ template_array = template.to_s.split('/')
96
+ template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
97
+ options = args.last.is_a?(Hash) ? args.pop : {}
98
+ options.merge!(:layout => false)
99
+ locals = options[:locals] || {}
100
+
101
+ if (collection = options.delete(:collection))
102
+ collection.inject([]) do |buffer, member|
103
+ buffer << haml(:"#{template}",
104
+ options.merge(:layout => false,
105
+ :locals => {template_array[-1].to_sym => member}.merge(locals)))
106
+ end.join("\n")
107
+ else
108
+ haml(:"#{template}", options)
109
+ end
110
+ end
111
+
112
+ # generate a simple random string
113
+ def random_string(len)
114
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
115
+ str = ""
116
+ 1.upto(len) { |i| str << chars[rand(chars.size-1)] }
117
+ str
118
+ end
119
+
120
+
121
+ MEGABYTE = 1024.0 * 1024.0
122
+
123
+ # convert Bytes to MegaBytes
124
+ def bytes_to_mb bytes=0
125
+ bytes / MEGABYTE
126
+ end
127
+
128
+ end
@@ -0,0 +1,3 @@
1
+ var MongoFE = {
2
+
3
+ };