#!/usr/bin/env ruby
#encoding: utf-8
require 'sinatra'
require 'mysql'
require 'csv'
require_relative 'visual_db/version'
helpers do
include Rack::Utils
alias_method :h, :escape_html
def sql_connect(host, username = "root", password = "", database = "", port = 3306)
begin
$sql_conn = Mysql.init
$sql_conn.options(Mysql::OPT_CONNECT_TIMEOUT, 4)
$sql_conn.real_connect(host, username, password, database, port)
redirect "/databases"
rescue Mysql::Error => e
$sql_conn = false
error_logger("Error connecting to MySQL as #{username} at #{host}:#{port}", e)
redirect "/?error=true"
end
end
def sql_close
$sql_conn.close
$sql_conn = $db_list = $db_name = $db_tables = $table_name = false
end
def connection_redir(location)
if !$sql_conn
error_logger("Connect to a MySQL instance before configuring databases")
redirect "#{location}?error=true"
end
end
def database_redir(location)
if !$db_name
error_logger("Select a Database before configuring tables")
redirect "#{location}?error=true"
end
end
def result_redirect(result, path)
if result == "success"
redirect "#{path}?success=true"
else
redirect "#{path}?error=true"
end
end
def query(full_query, failure_message, update_on_success = false, update_on_fail = false)
begin
data = $sql_conn.query full_query
$db_success = $sql_conn.affected_rows
$full_table = data if update_on_success
return "success", data
rescue Mysql::Error => e
error_logger(failure_message, e)
set_table($db_name, $table_name) if update_on_fail
return "error", nil
end
end
def get_table
"#{$db_name}.#{$table_name}"
end
def set_table(db, table)
begin
$full_table = $sql_conn.query "SELECT * FROM #{db}.#{table}"
rescue
$full_table = $sql_conn.query "SELECT * FROM #{get_table}"
end
end
def error_logger(message, exception = nil)
$db_error = "#{message} #{'=> ' + exception.errno.to_s + ' : ' + exception.error unless exception.nil?}"
end
end
configure do
set :public_dir, File.expand_path('../../public', __FILE__)
set :views, File.expand_path('../../views', __FILE__)
Dir.glob("../*.csv").each { |csv| File.delete(csv) }
$sql_conn = false # The my SQL connection object
$db_list = false # List of available Databases
$db_name = false # Name of currently selected Database
$db_tables = false # List of Tables for currently selected Database
$table_name = false # Name of currently selected Table
$table_cols = nil # Table Column information
$db_error = nil # To propagate error/success from SQL queries
$db_success = nil # SQL Rows Affected response
API_VERSION = "1.0"
NO_AUTH_PATHS = ["/", "/connect", "/disconnect", "/about"]
set :bind, '0.0.0.0'
set :show_exceptions, true
set :raise_errors, true
set :dump_errors, true
end
## General Filters
before do
connection_redir '/' unless NO_AUTH_PATHS.include? request.path_info
$db_error = nil unless params[:error]
$db_success = nil unless params[:success]
$table_cols = nil unless params[:query_action] == "showcols"
end
after do
$table_cols = nil unless params[:query_action] == "showcols"
end
## Connection
get '/?' do
erb :index
end
post '/connect' do
port = Integer(params[:port]) rescue nil
sql_connect(params[:hostname], params[:username], nil, params[:password], port)
end
post '/disconnect' do
sql_close
redirect '/'
end
get '/about' do
erb :about
end
## Databases
get '/databases' do
show_dbs = $sql_conn.query "SHOW DATABASES"
$db_list = []
show_dbs.each { |db| $db_list += db }
erb :databases
end
post '/databases' do
$db_name = params[:select_database]
$db_tables, $table_name, $full_table = false, false, false
redirect '/tables'
end
post '/database' do
if params[:action] == "create"
result, _ = query( "CREATE DATABASE #{params[:database]}",
"Error creating \"#{params[:database]}\"" )
elsif params[:action] == "delete"
result, _ = query( "DROP DATABASE IF EXISTS #{params[:database]}",
"Error deleting \"#{params[:database]}\"" )
$db_name = nil if params[:database] == $db_name
end
result_redirect(result, "/databases")
end
## Selected Database - All Tables
get '/tables' do
database_redir '/databases'
db_tables = $sql_conn.query "SHOW TABLES from #{$db_name}"
$db_tables = []
db_tables.each { |table| $db_tables += table }
set_table($db_name, $table_name) if $table_name
erb :tables
end
post '/tables' do
$table_name = params[:select_table]
redirect '/tables'
end
## Selected Database - Specific Table Actions
post '/table' do
if params[:action] == "create"
result, _ = query( "CREATE TABLE #{$db_name}.#{params[:new_table]}",
"Error creating table \"#{params[:new_table]}\"" )
elsif params[:action] == "delete"
result, _ = query( "DROP TABLE IF EXISTS #{$db_name}.#{params[:table]}",
"Error dropping table \"#{$db_name}.#{params[:table]}\"" )
$table_name = nil if params[:table] == $table_name
end
result_redirect(result, "/tables")
end
post '/table/query' do
if params[:query_action] == "filter"
query( "SELECT * FROM #{get_table} WHERE #{params[:query_params]}",
"Error filtering #{$table_name} by \"#{params[:query_params]}\"",
true, true )
elsif params[:query_action] == "insert"
query( "INSERT INTO #{get_table} #{params[:set_params]}",
"Error inserting \"#{params[:set_params]}\" into #{$db_name}",
true, true )
elsif params[:query_action] == "delete"
query( "DELETE FROM #{get_table} WHERE #{params[:query_params]}",
"Error removing \"#{params[:query_params]}\" from #{$db_name}",
true, true )
elsif params[:query_action] == "update"
query( "UPDATE #{get_table} SET #{params[:set_params]} WHERE #{params[:query_params]}",
"Error setting \"#{params[:set_params]}\" to queries matching \"#{params[:query_params]}\"",
true, true )
elsif params[:query_action] == "alter"
query( "ALTER TABLE #{get_table} #{params[:set_params]}",
"Error setting \"#{params[:set_params]}\"",
true, true )
elsif params[:query_action] == "showcols"
_, $table_cols = query( "SHOW COLUMNS FROM #{get_table}", "Error showing columns" )
end
erb :tables
end
## Manual Query
get '/query' do
erb :query
end
post '/query' do
result, _ = query("#{params[:query]}", "Query \"#{params[:query]}\" resulted in error")
result_redirect(result, "/query")
end
## Download database table as CSV
post '/csv' do
unless [$db_name, $table_name, $full_table].include? nil
csv_file = "#{get_table}.csv"
headers = []
CSV.open(csv_file, "wb") do |csv|
$full_table.num_fields.times do |i|
headers << $full_table.fetch_field_direct(i).name
end
csv << headers
$sql_conn.query("SELECT * FROM #{get_table}").each { |row| csv << row }
end
send_file csv_file, :filename => csv_file, :type => :csv
end
end
## Environment
get '/env' do
<<-ENDRESPONSE
MySQL: v#{$sql_conn.get_server_info rescue "?"}
Rack: #{Rack::VERSION rescue "?"}
Sinatra: #{Sinatra::VERSION rescue "?"}
API: v#{API_VERSION rescue "?"}
Gem: v#{VisualDb::VERSION rescue "?"}
ENDRESPONSE
end
## Kills process (work around for Vegas Gem not catching SIGINT)
get '/quit' do
sql_close
redirect to('/'), 200
end
after '/quit' do
puts "\nExiting..."
exit!
end
## Default to Connection page
not_found do
redirect '/'
end