oculus 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +34 -0
- data/Rakefile +18 -0
- data/TODO.md +12 -0
- data/bin/oculus +32 -0
- data/features/query.feature +26 -0
- data/features/step_definitions/query_steps.rb +35 -0
- data/features/support/env.rb +22 -0
- data/lib/oculus/connection/mysql2.rb +22 -0
- data/lib/oculus/connection.rb +7 -0
- data/lib/oculus/presenters/query_presenter.rb +40 -0
- data/lib/oculus/presenters.rb +1 -0
- data/lib/oculus/query.rb +61 -0
- data/lib/oculus/server/public/css/bootstrap.min.css +689 -0
- data/lib/oculus/server/public/css/codemirror.css +112 -0
- data/lib/oculus/server/public/css/reset.css +47 -0
- data/lib/oculus/server/public/css/style.css +107 -0
- data/lib/oculus/server/public/img/glyphicons-halflings-white.png +0 -0
- data/lib/oculus/server/public/img/glyphicons-halflings.png +0 -0
- data/lib/oculus/server/public/js/application.js +160 -0
- data/lib/oculus/server/public/js/bootstrap.min.js +6 -0
- data/lib/oculus/server/public/js/codemirror.min.js +1 -0
- data/lib/oculus/server/public/js/jquery.min.js +4 -0
- data/lib/oculus/server/public/js/spin.min.js +2 -0
- data/lib/oculus/server/views/history.erb +35 -0
- data/lib/oculus/server/views/index.erb +97 -0
- data/lib/oculus/server/views/layout.erb +37 -0
- data/lib/oculus/server/views/show.erb +59 -0
- data/lib/oculus/server.rb +88 -0
- data/lib/oculus/storage/file_store.rb +129 -0
- data/lib/oculus/storage.rb +7 -0
- data/lib/oculus/version.rb +3 -0
- data/lib/oculus.rb +29 -0
- data/oculus.gemspec +26 -0
- data/spec/connection_spec.rb +61 -0
- data/spec/file_store_spec.rb +111 -0
- data/spec/query_presenter_spec.rb +54 -0
- data/spec/query_spec.rb +107 -0
- metadata +173 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Paul Rosania
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Oculus
|
2
|
+
|
3
|
+
![The Oculus of the Pantheon](http://upload.wikimedia.org/wikipedia/commons/1/17/Oculus_of_the_Pantheon.jpg)
|
4
|
+
|
5
|
+
[![Build Status](https://secure.travis-ci.org/paulrosania/oculus.png?branch=master)](http://travis-ci.org/paulrosania/oculus)
|
6
|
+
[![Dependency Status](https://gemnasium.com/paulrosania/oculus.png)](https://gemnasium.com/paulrosania/oculus)
|
7
|
+
|
8
|
+
Oculus is a web-based logging SQL client. It keeps a history of your queries
|
9
|
+
and the results they returned, so your research is always at hand, easy to share
|
10
|
+
and easy to repeat or reproduce in the future.
|
11
|
+
|
12
|
+
**Oculus will not prevent you from doing stupid things! I recommend using a
|
13
|
+
readonly MySQL account.**
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
$ gem install oculus
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Oculus is a Sinatra app. Run it from the command line, or mount `Oculus::Server`
|
22
|
+
as middleware in your Rack application.
|
23
|
+
|
24
|
+
For details on command line options, run:
|
25
|
+
|
26
|
+
oculus --help
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
1. Fork it
|
31
|
+
2. Make your changes
|
32
|
+
3. Send me a pull request
|
33
|
+
|
34
|
+
If you're making a big change, please open an Issue first, so we can discuss.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'cucumber/rake/task'
|
6
|
+
|
7
|
+
desc 'Run RSpec tests'
|
8
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
9
|
+
task.rspec_opts = %w[--color --format documentation]
|
10
|
+
task.pattern = 'spec/*_spec.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Run Cucumber features'
|
14
|
+
Cucumber::Rake::Task.new(:cucumber) do |task|
|
15
|
+
task.cucumber_opts = 'features --format pretty'
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => [:spec, :cucumber]
|
data/TODO.md
ADDED
data/bin/oculus
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
begin
|
5
|
+
require 'vegas'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'vegas'
|
9
|
+
end
|
10
|
+
require 'oculus/server'
|
11
|
+
|
12
|
+
|
13
|
+
Vegas::Runner.new(Oculus::Server, 'oculus') do |runner, opts, app|
|
14
|
+
opts.on("-h", "--host HOST", "Database server hostname") do |host|
|
15
|
+
Oculus.connection_options[:host] = host
|
16
|
+
end
|
17
|
+
opts.on("-P", "--port PORT", "Database server port") do |port|
|
18
|
+
Oculus.connection_options[:port] = port.to_i
|
19
|
+
end
|
20
|
+
opts.on("-u", "--username USER", "Database username") do |username|
|
21
|
+
Oculus.connection_options[:username] = username
|
22
|
+
end
|
23
|
+
opts.on("-p", "--password PASSWORD", "Database password") do |password|
|
24
|
+
Oculus.connection_options[:password] = password
|
25
|
+
end
|
26
|
+
opts.on("-D", "--database DATABASE", "Database to use") do |db|
|
27
|
+
Oculus.connection_options[:database] = db
|
28
|
+
end
|
29
|
+
opts.on("-d", "--data DIRECTORY", "Data cache path (default: tmp/data)") do |path|
|
30
|
+
Oculus.cache_path = path
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Users can query the database
|
2
|
+
|
3
|
+
@javascript
|
4
|
+
Scenario: Running a new query
|
5
|
+
When I execute "SELECT * FROM oculus_users"
|
6
|
+
Then I should see 3 rows of results
|
7
|
+
|
8
|
+
Scenario: Loading a cached query
|
9
|
+
Given a query is cached with results:
|
10
|
+
| id | users |
|
11
|
+
| 1 | Paul |
|
12
|
+
| 2 | Amy |
|
13
|
+
| 3 | Peter |
|
14
|
+
When I load the cached query
|
15
|
+
Then I should see 3 rows of results
|
16
|
+
|
17
|
+
@javascript
|
18
|
+
Scenario: Deleting a query
|
19
|
+
Given a query is cached with results:
|
20
|
+
| id | users |
|
21
|
+
| 1 | Paul |
|
22
|
+
| 2 | Amy |
|
23
|
+
| 3 | Peter |
|
24
|
+
And I am on the history page
|
25
|
+
When I click delete
|
26
|
+
Then I should not see any queries
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Given /^a query is cached with results:$/ do |results|
|
2
|
+
Oculus::Query.create(:name => "all users", :query => "SELECT * FROM oculus_users", :results => results.raw)
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^I am on the history page$/ do
|
6
|
+
visit '/history'
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I execute "([^"]*)"$/ do |query|
|
10
|
+
visit '/'
|
11
|
+
find('.CodeMirror :first-child :first-child').native.send_keys(query)
|
12
|
+
click_button 'Run'
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^I load the cached query$/ do
|
16
|
+
visit '/history'
|
17
|
+
click_link 'all users'
|
18
|
+
end
|
19
|
+
|
20
|
+
When /^I click delete$/ do
|
21
|
+
find('.delete').click
|
22
|
+
end
|
23
|
+
|
24
|
+
Then /^I should see (\d+) rows of results$/ do |result_count|
|
25
|
+
page.has_css?(".results", :visible => true)
|
26
|
+
within('.results') do
|
27
|
+
all('tr').length.should == result_count.to_i
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Then /^I should not see any queries$/ do
|
32
|
+
within('#history') do
|
33
|
+
all('li').length.should == 0
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
$: << File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
2
|
+
|
3
|
+
require 'oculus'
|
4
|
+
require 'oculus/server'
|
5
|
+
require 'capybara/cucumber'
|
6
|
+
|
7
|
+
Capybara.app = Oculus::Server
|
8
|
+
|
9
|
+
Oculus.cache_path = 'tmp/test_cache'
|
10
|
+
Oculus.connection_options = {
|
11
|
+
:host => 'localhost',
|
12
|
+
:username => 'root',
|
13
|
+
:database => 'test'
|
14
|
+
}
|
15
|
+
|
16
|
+
Before do
|
17
|
+
FileUtils.mkdir_p('tmp/test_cache')
|
18
|
+
end
|
19
|
+
|
20
|
+
After do
|
21
|
+
FileUtils.rm_r('tmp/test_cache')
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
|
3
|
+
module Oculus
|
4
|
+
module Connection
|
5
|
+
class Mysql2
|
6
|
+
def initialize(options = {})
|
7
|
+
@connection = ::Mysql2::Client.new(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute(sql)
|
11
|
+
results = @connection.query(sql)
|
12
|
+
[results.fields] + results.map(&:values) if results
|
13
|
+
rescue ::Mysql2::Error => e
|
14
|
+
raise Connection::Error.new(e.message)
|
15
|
+
end
|
16
|
+
|
17
|
+
def thread_id
|
18
|
+
@connection.thread_id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Oculus
|
4
|
+
module Presenters
|
5
|
+
class QueryPresenter < SimpleDelegator
|
6
|
+
def formatted_date
|
7
|
+
date.strftime("%Y-%m-%d %H:%M") if date
|
8
|
+
end
|
9
|
+
|
10
|
+
def status
|
11
|
+
if complete?
|
12
|
+
if error
|
13
|
+
"error"
|
14
|
+
else
|
15
|
+
"done"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
"loading"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def description
|
23
|
+
if name && name != ""
|
24
|
+
name
|
25
|
+
else
|
26
|
+
query = self.query
|
27
|
+
if query && query.length > 100
|
28
|
+
"#{query[0..97]}..."
|
29
|
+
else
|
30
|
+
query
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def named?
|
36
|
+
!!name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'oculus/presenters/query_presenter'
|
data/lib/oculus/query.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Oculus
|
2
|
+
class Query
|
3
|
+
attr_accessor :id
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :author
|
6
|
+
attr_accessor :query
|
7
|
+
attr_accessor :results
|
8
|
+
attr_accessor :error
|
9
|
+
attr_accessor :date
|
10
|
+
attr_accessor :thread_id
|
11
|
+
|
12
|
+
def initialize(attributes = {})
|
13
|
+
attributes.each do |attr, value|
|
14
|
+
send("#{attr}=", value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
attrs = {
|
20
|
+
:name => name,
|
21
|
+
:author => author,
|
22
|
+
:query => query,
|
23
|
+
:date => date,
|
24
|
+
:thread_id => thread_id
|
25
|
+
}
|
26
|
+
attrs[:error] = error if error
|
27
|
+
attrs
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute(connection)
|
31
|
+
self.results = connection.execute(query)
|
32
|
+
rescue Connection::Error => e
|
33
|
+
self.error = e.message
|
34
|
+
end
|
35
|
+
|
36
|
+
def save
|
37
|
+
@date = Time.now
|
38
|
+
Oculus.data_store.save_query(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def complete?
|
42
|
+
!!error || (!results.nil? && !results.empty?)
|
43
|
+
end
|
44
|
+
|
45
|
+
def succeeded?
|
46
|
+
complete? && !error
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
def create(attributes)
|
51
|
+
query = new(attributes)
|
52
|
+
query.save
|
53
|
+
query
|
54
|
+
end
|
55
|
+
|
56
|
+
def find(id)
|
57
|
+
Oculus.data_store.load_query(id)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|