mongo_browser 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/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/Gemfile +19 -0
- data/LICENSE +22 -0
- data/README.md +31 -0
- data/README.rdoc +19 -0
- data/Rakefile +61 -0
- data/app/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/app/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/app/assets/javascripts/app/table_filter.js.coffee +50 -0
- data/app/assets/javascripts/application.js.coffee +32 -0
- data/app/assets/javascripts/vendor/bootbox.js +508 -0
- data/app/assets/javascripts/vendor/bootstrap.js +2025 -0
- data/app/assets/javascripts/vendor/jquery.js +9266 -0
- data/app/assets/stylesheets/application.css.scss +22 -0
- data/app/assets/stylesheets/vendor/bootstrap-responsive.css +1088 -0
- data/app/assets/stylesheets/vendor/bootstrap.css +5893 -0
- data/app/views/collections/show.erb +61 -0
- data/app/views/databases/show.erb +59 -0
- data/app/views/index.erb +29 -0
- data/app/views/layout.erb +19 -0
- data/app/views/layout/_breadcrumb.erb +13 -0
- data/app/views/layout/_flash_messages.erb +10 -0
- data/app/views/layout/_navbar.erb +21 -0
- data/app/views/server_info.erb +20 -0
- data/app/views/shared/_filter.erb +14 -0
- data/bin/mongo_browser +53 -0
- data/config.ru +4 -0
- data/features/mongo_browser.feature +18 -0
- data/features/step_definitions/mongo_browser_steps.rb +1 -0
- data/features/support/env.rb +18 -0
- data/lib/mongo_browser.rb +32 -0
- data/lib/mongo_browser/application.rb +108 -0
- data/lib/mongo_browser/middleware/sprockets_sinatra.rb +24 -0
- data/lib/mongo_browser/version.rb +3 -0
- data/mongo_browser.gemspec +32 -0
- data/spec/features/collections_list_spec.rb +105 -0
- data/spec/features/databases_list_spec.rb +60 -0
- data/spec/features/documents_list_spec.rb +136 -0
- data/spec/features/server_info_spec.rb +17 -0
- data/spec/fixtures/databases.json +40 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/support/have_flash_message_matcher.rb +16 -0
- data/spec/support/integration.rb +26 -0
- data/spec/support/mongo_test_server.rb +50 -0
- metadata +313 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module MongoBrowser
|
2
|
+
module Middleware
|
3
|
+
class SprocketsSinatra
|
4
|
+
def initialize(app, options = {})
|
5
|
+
@app = app
|
6
|
+
@root = options[:root]
|
7
|
+
path = options[:path] || "assets"
|
8
|
+
@matcher = /^\/#{path}\/*/
|
9
|
+
@environment = ::Sprockets::Environment.new(@root)
|
10
|
+
@environment.append_path "assets/javascripts"
|
11
|
+
@environment.append_path "assets/javascripts/vendor"
|
12
|
+
@environment.append_path "assets/stylesheets"
|
13
|
+
@environment.append_path "assets/stylesheets/vendor"
|
14
|
+
@environment.append_path "assets/images"
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
return @app.call(env) unless @matcher =~ env["PATH_INFO"]
|
19
|
+
env["PATH_INFO"].sub!(@matcher, "")
|
20
|
+
@environment.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/mongo_browser/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Lukasz Bandzarewicz"]
|
6
|
+
gem.email = ["lucassus@gmail.com"]
|
7
|
+
gem.description = %q{Simple but powerful tool for managing mongodb databases}
|
8
|
+
gem.summary = %q{Web-based application for managing mongodb databases}
|
9
|
+
gem.homepage = "https://github.com/lucassus/mongo_browser"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "mongo_browser"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = MongoBrowser::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency("rdoc")
|
19
|
+
gem.add_development_dependency("aruba")
|
20
|
+
gem.add_development_dependency("rake", "~> 0.9.2")
|
21
|
+
|
22
|
+
gem.add_dependency("mongo", "~> 1.7.0")
|
23
|
+
gem.add_dependency("bson_ext", "~> 1.7.0")
|
24
|
+
gem.add_dependency("methadone", "~> 1.2.2")
|
25
|
+
gem.add_dependency("foreverb", "~> 0.3.2")
|
26
|
+
gem.add_dependency("sinatra", "~> 1.3.3")
|
27
|
+
gem.add_dependency("sinatra-contrib", "~> 1.3.2")
|
28
|
+
gem.add_dependency("sinatra-flash", "~> 0.3.0")
|
29
|
+
gem.add_dependency("will_paginate-bootstrap", "~> 0.2.1")
|
30
|
+
gem.add_dependency("json", "~> 1.7.5")
|
31
|
+
gem.add_dependency("awesome_print", "~> 1.1.0")
|
32
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Collections list", type: :request do
|
4
|
+
before do
|
5
|
+
visit "/"
|
6
|
+
|
7
|
+
within("table.databases") { click_link "first_database" }
|
8
|
+
end
|
9
|
+
|
10
|
+
it "displays the breadcrumb" do
|
11
|
+
within ".breadcrumb" do
|
12
|
+
within "li:nth-child(1)" do
|
13
|
+
expect(page).to have_link("Databases")
|
14
|
+
end
|
15
|
+
|
16
|
+
within "li:nth-child(2)" do
|
17
|
+
expect(page).to have_content("db: first_database")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "displays all available collections for the selected database" do
|
23
|
+
within "table" do
|
24
|
+
expect(page).to have_link("first_collection")
|
25
|
+
expect(page).to have_link("second_collection")
|
26
|
+
expect(page).to have_link("third_collection")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "displays information about the database", js: true do
|
31
|
+
click_link "Stats"
|
32
|
+
|
33
|
+
within "table" do
|
34
|
+
%w(db collection objects avgObjSize dataSize storageSize numExtents indexes indexSize nsSizeMB ok).each do |field|
|
35
|
+
expect(page).to have_content(field)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "filtering", js: true do
|
41
|
+
it "filters collections by name" do
|
42
|
+
fill_in_filter("second")
|
43
|
+
|
44
|
+
within "table.collections" do
|
45
|
+
expect(page).to have_link("second_collection")
|
46
|
+
expect(page).to_not have_link("first_collection")
|
47
|
+
expect(page).to_not have_link("third_collection")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "displays a notification when nothing has been found" do
|
52
|
+
fill_in_filter("fifth")
|
53
|
+
|
54
|
+
expect(page).to_not have_css("table.collections")
|
55
|
+
expect(page).to have_content("Nothing has been found.")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "click on delete collection button", js: true do
|
60
|
+
it "deletes a collection" do
|
61
|
+
click_delete_button_for("second_collection")
|
62
|
+
confirm_dialog
|
63
|
+
|
64
|
+
expect(page).to have_flash_message("Collection second_collection has been deleted.")
|
65
|
+
|
66
|
+
within "table.collections" do
|
67
|
+
expect(page).to have_link("first_collection")
|
68
|
+
expect(page).to_not have_link("second_collection")
|
69
|
+
expect(page).to have_link("third_collection")
|
70
|
+
end
|
71
|
+
|
72
|
+
click_delete_button_for("first_collection")
|
73
|
+
confirm_dialog
|
74
|
+
expect(page).to have_flash_message("Collection first_collection has been deleted.")
|
75
|
+
|
76
|
+
within "table.collections" do
|
77
|
+
expect(page).to_not have_link("first_collection")
|
78
|
+
expect(page).to_not have_link("second_collection")
|
79
|
+
expect(page).to have_link("third_collection")
|
80
|
+
end
|
81
|
+
|
82
|
+
click_delete_button_for("third_collection")
|
83
|
+
confirm_dialog
|
84
|
+
expect(page).to have_flash_message("Collection third_collection has been deleted.")
|
85
|
+
|
86
|
+
should_hide_the_table_and_display_a_notification
|
87
|
+
end
|
88
|
+
|
89
|
+
it "displays error message when the collection cannot be deleted" do
|
90
|
+
click_delete_button_for("system.indexes")
|
91
|
+
confirm_dialog
|
92
|
+
|
93
|
+
expect(page).to have_flash_message("Database command 'drop' failed")
|
94
|
+
|
95
|
+
within "table.collections" do
|
96
|
+
expect(page).to have_link("system.indexes")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def click_delete_button_for(collection_name)
|
101
|
+
collection_row = find(:xpath, %Q{//table//tr//*[contains(text(), "#{collection_name}")]/../..})
|
102
|
+
within(collection_row) { click_link "Delete" }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Databases list", type: :request do
|
4
|
+
before { visit "/" }
|
5
|
+
|
6
|
+
it "hides the breadcrumb" do
|
7
|
+
expect(page).not_to have_css(".breadcrumb")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "displays list with available databases" do
|
11
|
+
expect(page).to have_content("Available databases")
|
12
|
+
|
13
|
+
within "table" do
|
14
|
+
expect(page).to have_link("first_database")
|
15
|
+
expect(page).to have_link("second_database")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "filtering", js: true do
|
20
|
+
it "filters databases by name" do
|
21
|
+
fill_in_filter("first")
|
22
|
+
|
23
|
+
within "table.databases" do
|
24
|
+
expect(page).to have_link("first_database")
|
25
|
+
expect(page).to_not have_link("second_database")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "displays a notification when nothing has been found" do
|
30
|
+
fill_in_filter("third")
|
31
|
+
should_hide_the_table_and_display_a_notification
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "click on delete database button", js: true do
|
36
|
+
it "deletes a database" do
|
37
|
+
click_delete_button_for("second_database")
|
38
|
+
confirm_dialog
|
39
|
+
|
40
|
+
expect(page).to have_flash_message("Database second_database has been deleted.")
|
41
|
+
|
42
|
+
within "table.databases" do
|
43
|
+
expect(page).to have_link("first_database")
|
44
|
+
expect(page).to_not have_link("second_database")
|
45
|
+
end
|
46
|
+
|
47
|
+
click_delete_button_for("first_database")
|
48
|
+
confirm_dialog
|
49
|
+
|
50
|
+
expect(page).to have_flash_message("Database first_database has been deleted.")
|
51
|
+
|
52
|
+
should_hide_the_table_and_display_a_notification
|
53
|
+
end
|
54
|
+
|
55
|
+
def click_delete_button_for(database_name)
|
56
|
+
database_row = find(:xpath, %Q{//table//tr//*[contains(text(), "#{database_name}")]/../..})
|
57
|
+
within(database_row) { click_link "Delete" }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Documents list", type: :request do
|
4
|
+
let(:connection) { MongoTestServer.connection }
|
5
|
+
|
6
|
+
before do
|
7
|
+
visit "/"
|
8
|
+
|
9
|
+
within("table.databases") { click_link "first_database" }
|
10
|
+
within("table.collections") { click_link current_collection_name }
|
11
|
+
end
|
12
|
+
|
13
|
+
shared_examples "breadcrumbs for documents list" do
|
14
|
+
it "displays the breadcrumb" do
|
15
|
+
within ".breadcrumb" do
|
16
|
+
within "li:nth-child(1)" do
|
17
|
+
expect(page).to have_link("Databases")
|
18
|
+
end
|
19
|
+
|
20
|
+
within "li:nth-child(2)" do
|
21
|
+
expect(page).to have_link("db: first_database")
|
22
|
+
end
|
23
|
+
|
24
|
+
within "li:nth-child(3)" do
|
25
|
+
expect(page).to have_content("collection: #{current_collection_name}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with small number for documents" do
|
32
|
+
let(:current_collection_name) { "first_collection" }
|
33
|
+
|
34
|
+
include_examples "breadcrumbs for documents list"
|
35
|
+
|
36
|
+
it "displays all documents for the selected collection" do
|
37
|
+
expect(page).to have_css("tr.document", count: 2)
|
38
|
+
|
39
|
+
within "table" do
|
40
|
+
expect(page).to have_content("This is a sample record")
|
41
|
+
expect(page).to have_content("This is the second sample record")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "displays information about the collection", js: true do
|
46
|
+
click_link "Stats"
|
47
|
+
|
48
|
+
within "table" do
|
49
|
+
%w(ns count size avgObjSize storageSize numExtents nindexes lastExtentSize paddingFactor systemFlags userFlags totalIndexSize indexSizes ok).each do |field|
|
50
|
+
expect(page).to have_content(field)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "click on delete document button", js: true do
|
56
|
+
let(:document) do
|
57
|
+
database = connection.db("first_database")
|
58
|
+
collection = database.collection(current_collection_name)
|
59
|
+
collection.find_one(name: "This is the second sample record")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "removes a document from the collection" do
|
63
|
+
click_delete_button_for(document)
|
64
|
+
confirm_dialog
|
65
|
+
|
66
|
+
expect(page).to have_flash_message("Document #{document["_id"]} has been deleted.")
|
67
|
+
|
68
|
+
expect(page).to have_css("tr.document", count: 1)
|
69
|
+
|
70
|
+
within "table" do
|
71
|
+
expect(page).to have_content("This is a sample record")
|
72
|
+
expect(page).not_to have_content(document["name"])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def click_delete_button_for(document)
|
77
|
+
database_row = find(:xpath, %Q{//table//tr/td[1][contains(text(), "#{document["_id"]}")]/..})
|
78
|
+
within(database_row) { click_link "Delete" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "with large number of documents" do
|
84
|
+
let(:current_collection_name) { "second_collection" }
|
85
|
+
|
86
|
+
before do
|
87
|
+
database = connection.db("first_database")
|
88
|
+
collection = database.collection(current_collection_name)
|
89
|
+
|
90
|
+
70.times do |n|
|
91
|
+
collection.insert(name: "Document #{n}", position: n)
|
92
|
+
end
|
93
|
+
|
94
|
+
visit current_path
|
95
|
+
end
|
96
|
+
|
97
|
+
include_examples "breadcrumbs for documents list"
|
98
|
+
|
99
|
+
it "displays a pagination" do
|
100
|
+
expect(page).to have_css("div.pagination", count: 2)
|
101
|
+
within "div.pagination" do
|
102
|
+
(1..3).each do |n|
|
103
|
+
expect(page).to have_link(n.to_s)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "paginates documents" do
|
109
|
+
within "table.documents" do
|
110
|
+
(0...25).each do |n|
|
111
|
+
expect(page).to have_content("Document #{n}")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
within "div.pagination" do
|
116
|
+
click_link "2"
|
117
|
+
end
|
118
|
+
|
119
|
+
within "table.documents" do
|
120
|
+
(25...50).each do |n|
|
121
|
+
expect(page).to have_content("Document #{n}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
within "div.pagination" do
|
126
|
+
click_link "3"
|
127
|
+
end
|
128
|
+
|
129
|
+
within "table.documents" do
|
130
|
+
(50...70).each do |n|
|
131
|
+
expect(page).to have_content("Document #{n}")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Server info", type: :request do
|
4
|
+
before do
|
5
|
+
visit "/"
|
6
|
+
|
7
|
+
within(".navbar") { click_link "Server Info" }
|
8
|
+
end
|
9
|
+
|
10
|
+
it "displays information about the server" do
|
11
|
+
within "table" do
|
12
|
+
%w(version gitVersion sysInfo versionArray bits debug maxBsonObjectSize ok).each do |field|
|
13
|
+
expect(page).to have_content(field)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"name": "first_database",
|
4
|
+
"collections": [
|
5
|
+
{
|
6
|
+
"name": "first_collection",
|
7
|
+
"documents": [
|
8
|
+
{
|
9
|
+
"name": "This is a sample record",
|
10
|
+
"position": {
|
11
|
+
"x": 123,
|
12
|
+
"y": "135"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"name": "This is the second sample record",
|
17
|
+
"position": {
|
18
|
+
"x": 567,
|
19
|
+
"y": "246"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
]
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"name": "second_collection"
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"name": "third_collection"
|
29
|
+
}
|
30
|
+
]
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"name": "second_database",
|
34
|
+
"collections": [
|
35
|
+
{
|
36
|
+
"name": "first_collection"
|
37
|
+
}
|
38
|
+
]
|
39
|
+
}
|
40
|
+
]
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
3
|
+
|
4
|
+
require "simplecov"
|
5
|
+
SimpleCov.start do
|
6
|
+
add_group "Application", "lib"
|
7
|
+
end
|
8
|
+
|
9
|
+
require "mongo_browser"
|
10
|
+
|
11
|
+
require "debugger"
|
12
|
+
require "rspec"
|
13
|
+
require "capybara"
|
14
|
+
require "capybara/rspec"
|
15
|
+
require "socket"
|
16
|
+
|
17
|
+
def find_available_port
|
18
|
+
server = TCPServer.new("127.0.0.1", 0)
|
19
|
+
server.addr[1]
|
20
|
+
ensure
|
21
|
+
server.close if server
|
22
|
+
end
|
23
|
+
|
24
|
+
MongoBrowser.mongodb_host = "localhost"
|
25
|
+
MongoBrowser.mongodb_port = find_available_port
|
26
|
+
|
27
|
+
require "capybara/webkit"
|
28
|
+
Capybara.javascript_driver = :webkit
|
29
|
+
|
30
|
+
Capybara.ignore_hidden_elements = true
|
31
|
+
Capybara.app = MongoBrowser::Application
|
32
|
+
|
33
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
34
|
+
# from spec/support/ and its subdirectories.
|
35
|
+
Dir[File.expand_path("spec/support/**/*.rb")].each { |f| require f }
|
36
|
+
|
37
|
+
RSpec.configure do |config|
|
38
|
+
config.include Integration
|
39
|
+
|
40
|
+
config.before do
|
41
|
+
MongoTestServer.ensure_test_server_is_running
|
42
|
+
MongoTestServer.load_fixtures
|
43
|
+
end
|
44
|
+
|
45
|
+
config.after do
|
46
|
+
# Take a screenshot when the scenario has failed
|
47
|
+
if example.metadata[:js] and example.exception
|
48
|
+
file_name = example.full_description.downcase.gsub(/\s/, "-")
|
49
|
+
page.driver.render("/tmp/#{file_name}.png", full: true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
at_exit do
|
55
|
+
MongoTestServer.clean_up
|
56
|
+
end
|