sqlui 0.1.28 → 0.1.29
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.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/app/args.rb +10 -2
- data/app/deep.rb +20 -1
- data/app/server.rb +10 -11
- data/app/sqlui_config.rb +8 -16
- data/client/resources/sqlui.css +6 -0
- data/client/resources/sqlui.html +1 -0
- data/client/resources/sqlui.js +132 -57
- metadata +6 -35
- data/app/environment.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9257bcf43bc98bcd5ed8a52d3b2643db1189d0d3b8c2c1444c5d949ff37ed03c
|
4
|
+
data.tar.gz: 6a96de39b01459a5b222a2781158523707fbd3e585400dd18a1b4469c55b4c9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32eb854b16a9b345456902379ec7cc8ba5e091562a62de90451f63d28146815a880315b0921303770233d036187d8b26f6164bf04e8dd935232bf3a6ef82e646
|
7
|
+
data.tar.gz: 0da14c2be2b04171df6ccef8dc4e4fb1c512440371d2394ae788ecc88994e032bf69633e683bab61e0cc4bc54ae367cb0ce6565cfc4b3f67559b90061ddbdaf9
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.29
|
data/app/args.rb
CHANGED
@@ -9,6 +9,10 @@ class Args
|
|
9
9
|
value
|
10
10
|
end
|
11
11
|
|
12
|
+
def self.fetch_non_empty_int(hash, key)
|
13
|
+
fetch_non_nil(hash, key, Integer)
|
14
|
+
end
|
15
|
+
|
12
16
|
def self.fetch_non_empty_hash(hash, key)
|
13
17
|
value = fetch_non_nil(hash, key, Hash)
|
14
18
|
raise ArgumentError, "required parameter #{key} empty" if value.empty?
|
@@ -23,9 +27,13 @@ class Args
|
|
23
27
|
raise ArgumentError, "required parameter #{key} null" if value.nil?
|
24
28
|
|
25
29
|
if classes.size.positive? && !classes.find { |clazz| value.is_a?(clazz) }
|
26
|
-
|
30
|
+
if classes.size != 1
|
31
|
+
raise ArgumentError, "required parameter #{key} not #{classes.map(&:to_s).map(&:downcase).join(' or ')}"
|
32
|
+
end
|
27
33
|
|
28
|
-
|
34
|
+
class_name = classes[0].to_s.downcase
|
35
|
+
class_name = %w[a e i o u].include?(class_name[0]) ? "an #{class_name}" : "a #{class_name}"
|
36
|
+
raise ArgumentError, "required parameter #{key} not #{class_name}"
|
29
37
|
end
|
30
38
|
|
31
39
|
value
|
data/app/deep.rb
CHANGED
@@ -7,6 +7,10 @@ module Enumerable
|
|
7
7
|
self
|
8
8
|
end
|
9
9
|
|
10
|
+
def deep_symbolize_keys!
|
11
|
+
deep_transform_keys!(&:to_sym)
|
12
|
+
end
|
13
|
+
|
10
14
|
def deep_dup(result = {})
|
11
15
|
map do |value|
|
12
16
|
value.respond_to?(:deep_dup) ? value.deep_dup : value.clone
|
@@ -18,11 +22,15 @@ end
|
|
18
22
|
# Deep extensions for Hash.
|
19
23
|
class Hash
|
20
24
|
def deep_transform_keys!(&block)
|
21
|
-
transform_keys!(
|
25
|
+
transform_keys!(&block)
|
22
26
|
each_value { |value| value.deep_transform_keys!(&block) if value.respond_to?(:deep_transform_keys!) }
|
23
27
|
self
|
24
28
|
end
|
25
29
|
|
30
|
+
def deep_symbolize_keys!
|
31
|
+
deep_transform_keys!(&:to_sym)
|
32
|
+
end
|
33
|
+
|
26
34
|
def deep_dup(result = {})
|
27
35
|
each do |key, value|
|
28
36
|
result[key] = value.respond_to?(:deep_dup) ? value.deep_dup : value.clone
|
@@ -55,4 +63,15 @@ class Hash
|
|
55
63
|
self.[](path[0]).deep_delete(*path[1..])
|
56
64
|
end
|
57
65
|
end
|
66
|
+
|
67
|
+
def deep_merge!(hash)
|
68
|
+
hash.each do |key, value|
|
69
|
+
if self[key].is_a?(Hash) && value.is_a?(Hash)
|
70
|
+
self[key].deep_merge!(value)
|
71
|
+
else
|
72
|
+
self[key] = value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
self
|
76
|
+
end
|
58
77
|
end
|
data/app/server.rb
CHANGED
@@ -5,7 +5,6 @@ require 'json'
|
|
5
5
|
require 'sinatra/base'
|
6
6
|
require 'uri'
|
7
7
|
require_relative 'database_metadata'
|
8
|
-
require_relative 'environment'
|
9
8
|
require_relative 'mysql_types'
|
10
9
|
require_relative 'sql_parser'
|
11
10
|
require_relative 'sqlui'
|
@@ -15,8 +14,8 @@ class Server < Sinatra::Base
|
|
15
14
|
def self.init_and_run(config, resources_dir)
|
16
15
|
set :logging, true
|
17
16
|
set :bind, '0.0.0.0'
|
18
|
-
set :port,
|
19
|
-
set :
|
17
|
+
set :port, config.port
|
18
|
+
set :environment, config.environment
|
20
19
|
set :raise_errors, false
|
21
20
|
set :show_exceptions, false
|
22
21
|
|
@@ -41,18 +40,18 @@ class Server < Sinatra::Base
|
|
41
40
|
get "#{database.url_path}/sqlui.css" do
|
42
41
|
@css ||= File.read(File.join(resources_dir, 'sqlui.css'))
|
43
42
|
status 200
|
44
|
-
headers 'Content-Type'
|
43
|
+
headers 'Content-Type' => 'text/css'
|
45
44
|
body @css
|
46
45
|
end
|
47
46
|
|
48
47
|
get "#{database.url_path}/sqlui.js" do
|
49
48
|
@js ||= File.read(File.join(resources_dir, 'sqlui.js'))
|
50
49
|
status 200
|
51
|
-
headers 'Content-Type'
|
50
|
+
headers 'Content-Type' => 'text/javascript'
|
52
51
|
body @js
|
53
52
|
end
|
54
53
|
|
55
|
-
|
54
|
+
post "#{database.url_path}/metadata" do
|
56
55
|
metadata = database.with_client do |client|
|
57
56
|
{
|
58
57
|
server: "#{config.name} - #{database.display_name}",
|
@@ -77,7 +76,7 @@ class Server < Sinatra::Base
|
|
77
76
|
}
|
78
77
|
end
|
79
78
|
status 200
|
80
|
-
headers 'Content-Type'
|
79
|
+
headers 'Content-Type' => 'application/json'
|
81
80
|
body metadata.to_json
|
82
81
|
end
|
83
82
|
|
@@ -114,21 +113,21 @@ class Server < Sinatra::Base
|
|
114
113
|
result[:query] = full_sql
|
115
114
|
|
116
115
|
status 200
|
117
|
-
headers 'Content-Type'
|
116
|
+
headers 'Content-Type' => 'application/json'
|
118
117
|
body result.to_json
|
119
118
|
end
|
120
119
|
|
121
120
|
get(%r{#{Regexp.escape(database.url_path)}/(query|graph|structure|saved)}) do
|
122
121
|
@html ||= File.read(File.join(resources_dir, 'sqlui.html'))
|
123
122
|
status 200
|
124
|
-
headers 'Content-Type'
|
123
|
+
headers 'Content-Type' => 'text/html'
|
125
124
|
body @html
|
126
125
|
end
|
127
126
|
end
|
128
127
|
|
129
128
|
error do |e|
|
130
129
|
status 500
|
131
|
-
headers 'Content-Type'
|
130
|
+
headers 'Content-Type' => 'application/json'
|
132
131
|
message = e.message.lines.first&.strip || 'unexpected error'
|
133
132
|
message = "#{message[0..80]}…" if message.length > 80
|
134
133
|
result = {
|
@@ -145,7 +144,7 @@ class Server < Sinatra::Base
|
|
145
144
|
|
146
145
|
def client_error(message, stacktrace: nil)
|
147
146
|
status(400)
|
148
|
-
headers
|
147
|
+
headers 'Content-Type' => 'application/json'
|
149
148
|
body({ error: message, stacktrace: stacktrace }.compact.to_json)
|
150
149
|
end
|
151
150
|
|
data/app/sqlui_config.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
-
require_relative 'database_config'
|
5
4
|
require_relative 'args'
|
5
|
+
require_relative 'database_config'
|
6
|
+
require_relative 'deep'
|
6
7
|
|
7
8
|
# App config including database configs.
|
8
9
|
class SqluiConfig
|
9
|
-
attr_reader :name, :list_url_path, :database_configs
|
10
|
+
attr_reader :name, :port, :environment, :list_url_path, :database_configs
|
10
11
|
|
11
|
-
def initialize(filename)
|
12
|
-
config = YAML.safe_load(ERB.new(File.read(filename)).result)
|
13
|
-
|
12
|
+
def initialize(filename, overrides = {})
|
13
|
+
config = YAML.safe_load(ERB.new(File.read(filename)).result).deep_merge!(overrides)
|
14
|
+
config.deep_symbolize_keys!
|
14
15
|
@name = Args.fetch_non_empty_string(config, :name).strip
|
16
|
+
@port = Args.fetch_non_empty_int(config, :port)
|
17
|
+
@environment = Args.fetch_non_empty_string(config, :environment).strip
|
15
18
|
@list_url_path = Args.fetch_non_empty_string(config, :list_url_path).strip
|
16
19
|
raise ArgumentError, 'list_url_path should start with a /' unless @list_url_path.start_with?('/')
|
17
20
|
if @list_url_path.length > 1 && @list_url_path.end_with?('/')
|
@@ -30,15 +33,4 @@ class SqluiConfig
|
|
30
33
|
|
31
34
|
config
|
32
35
|
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def deep_symbolize!(object)
|
37
|
-
return object unless object.is_a? Hash
|
38
|
-
|
39
|
-
object.transform_keys!(&:to_sym)
|
40
|
-
object.each_value { |child| deep_symbolize!(child) }
|
41
|
-
|
42
|
-
object
|
43
|
-
end
|
44
36
|
end
|
data/client/resources/sqlui.css
CHANGED
data/client/resources/sqlui.html
CHANGED
@@ -40,6 +40,7 @@
|
|
40
40
|
|
41
41
|
<div id="fetch-sql-box" class="fetch-sql-box tab-content-element graph-element query-element" style="display: none;">
|
42
42
|
<div id="result-loader" class="loader"></div>
|
43
|
+
<p id="result-time" class="result-time"></p>
|
43
44
|
</div>
|
44
45
|
|
45
46
|
<div id="saved-box" class="saved-box tab-content-element saved-element" style="display: none;">
|
data/client/resources/sqlui.js
CHANGED
@@ -23963,10 +23963,10 @@
|
|
23963
23963
|
function selectTab (event, tab) {
|
23964
23964
|
const url = new URL(window.location);
|
23965
23965
|
setTabInUrl(url, tab);
|
23966
|
-
route(event.target, event, url);
|
23966
|
+
route(event.target, event, url, true);
|
23967
23967
|
}
|
23968
23968
|
|
23969
|
-
function route (target = null, event = null, url = null) {
|
23969
|
+
function route (target = null, event = null, url = null, internal = false) {
|
23970
23970
|
if (url) {
|
23971
23971
|
if (event) {
|
23972
23972
|
event.preventDefault();
|
@@ -24004,10 +24004,10 @@
|
|
24004
24004
|
|
24005
24005
|
switch (window.tab) {
|
24006
24006
|
case 'query':
|
24007
|
-
selectResultTab();
|
24007
|
+
selectResultTab(internal);
|
24008
24008
|
break
|
24009
24009
|
case 'graph':
|
24010
|
-
selectGraphTab();
|
24010
|
+
selectGraphTab(internal);
|
24011
24011
|
break
|
24012
24012
|
case 'saved':
|
24013
24013
|
selectSavedTab();
|
@@ -24158,21 +24158,21 @@
|
|
24158
24158
|
});
|
24159
24159
|
}
|
24160
24160
|
|
24161
|
-
function selectGraphTab () {
|
24161
|
+
function selectGraphTab (internal) {
|
24162
24162
|
document.getElementById('query-box').style.display = 'flex';
|
24163
24163
|
document.getElementById('submit-box').style.display = 'flex';
|
24164
24164
|
document.getElementById('graph-box').style.display = 'flex';
|
24165
24165
|
document.getElementById('graph-status').style.display = 'flex';
|
24166
24166
|
document.getElementById('fetch-sql-box').style.display = 'none';
|
24167
24167
|
document.getElementById('cancel-button').style.display = 'none';
|
24168
|
-
maybeFetchResult();
|
24168
|
+
maybeFetchResult(internal);
|
24169
24169
|
|
24170
24170
|
const selection = getSelection();
|
24171
24171
|
focus();
|
24172
24172
|
setSelection(selection);
|
24173
24173
|
}
|
24174
24174
|
|
24175
|
-
function selectResultTab () {
|
24175
|
+
function selectResultTab (internal) {
|
24176
24176
|
document.getElementById('query-box').style.display = 'flex';
|
24177
24177
|
document.getElementById('submit-box').style.display = 'flex';
|
24178
24178
|
document.getElementById('result-box').style.display = 'flex';
|
@@ -24182,7 +24182,7 @@
|
|
24182
24182
|
const selection = getSelection();
|
24183
24183
|
focus();
|
24184
24184
|
setSelection(selection);
|
24185
|
-
maybeFetchResult();
|
24185
|
+
maybeFetchResult(internal);
|
24186
24186
|
}
|
24187
24187
|
|
24188
24188
|
function selectSavedTab () {
|
@@ -24212,7 +24212,7 @@
|
|
24212
24212
|
viewLinkElement.href = viewUrl.pathname + viewUrl.search;
|
24213
24213
|
viewLinkElement.addEventListener('click', function (event) {
|
24214
24214
|
clearResult();
|
24215
|
-
route(event.target, event, viewUrl);
|
24215
|
+
route(event.target, event, viewUrl, true);
|
24216
24216
|
});
|
24217
24217
|
|
24218
24218
|
const runUrl = new URL(window.location.origin + window.location.pathname);
|
@@ -24226,7 +24226,7 @@
|
|
24226
24226
|
runLinkElement.href = runUrl.pathname + runUrl.search;
|
24227
24227
|
runLinkElement.addEventListener('click', function (event) {
|
24228
24228
|
clearResult();
|
24229
|
-
route(event.target, event, runUrl);
|
24229
|
+
route(event.target, event, runUrl, true);
|
24230
24230
|
});
|
24231
24231
|
|
24232
24232
|
const nameElement = document.createElement('h2');
|
@@ -24296,20 +24296,21 @@
|
|
24296
24296
|
url.searchParams.delete('run');
|
24297
24297
|
}
|
24298
24298
|
|
24299
|
-
route(target, event, url);
|
24299
|
+
route(target, event, url, true);
|
24300
24300
|
}
|
24301
24301
|
|
24302
24302
|
function clearResult () {
|
24303
|
-
|
24304
|
-
|
24305
|
-
|
24306
|
-
|
24303
|
+
if (window.sqlFetch?.state === 'pending' || window.sqlFetch?.spinner === 'always') {
|
24304
|
+
window.sqlFetch.state = 'aborted';
|
24305
|
+
window.sqlFetch.spinner = 'never';
|
24306
|
+
window.sqlFetch.fetchController.abort();
|
24307
|
+
displaySqlFetch(window.sqlFetch);
|
24308
|
+
return
|
24307
24309
|
}
|
24308
24310
|
window.sqlFetch = null;
|
24309
|
-
|
24311
|
+
clearSpinner();
|
24310
24312
|
clearGraphBox();
|
24311
24313
|
clearGraphStatus();
|
24312
|
-
|
24313
24314
|
clearResultBox();
|
24314
24315
|
clearResultStatus();
|
24315
24316
|
}
|
@@ -24336,7 +24337,30 @@
|
|
24336
24337
|
}
|
24337
24338
|
}
|
24338
24339
|
|
24339
|
-
function
|
24340
|
+
function updateResultTime (sqlFetch) {
|
24341
|
+
if (window.sqlFetch === sqlFetch) {
|
24342
|
+
if (sqlFetch.state === 'pending' || sqlFetch.spinner === 'always') {
|
24343
|
+
displaySqlFetch(sqlFetch);
|
24344
|
+
setTimeout(() => { updateResultTime(sqlFetch); }, 500);
|
24345
|
+
}
|
24346
|
+
}
|
24347
|
+
}
|
24348
|
+
|
24349
|
+
function fetchSql (sqlFetch) {
|
24350
|
+
window.sqlFetch = sqlFetch;
|
24351
|
+
updateResultTime(sqlFetch);
|
24352
|
+
setTimeout(function () {
|
24353
|
+
if (window.sqlFetch === sqlFetch && sqlFetch.state === 'pending') {
|
24354
|
+
window.sqlFetch.spinner = 'always';
|
24355
|
+
displaySqlFetch(sqlFetch);
|
24356
|
+
setTimeout(function () {
|
24357
|
+
if (window.sqlFetch === sqlFetch) {
|
24358
|
+
window.sqlFetch.spinner = 'if_pending';
|
24359
|
+
displaySqlFetch(sqlFetch);
|
24360
|
+
}
|
24361
|
+
}, 400); // If we display a spinner, ensure it is displayed for at least 400 ms
|
24362
|
+
}
|
24363
|
+
}, 300); // Don't display the spinner unless the response takes more than 300 ms
|
24340
24364
|
fetch('query', {
|
24341
24365
|
headers: {
|
24342
24366
|
Accept: 'application/json',
|
@@ -24345,7 +24369,7 @@
|
|
24345
24369
|
method: 'POST',
|
24346
24370
|
body: JSON.stringify({
|
24347
24371
|
sql: sqlFetch.sql,
|
24348
|
-
selection
|
24372
|
+
selection: sqlFetch.selection
|
24349
24373
|
}),
|
24350
24374
|
signal: sqlFetch.fetchController.signal
|
24351
24375
|
})
|
@@ -24368,14 +24392,14 @@
|
|
24368
24392
|
sqlFetch.error_message = 'failed to execute query';
|
24369
24393
|
}
|
24370
24394
|
}
|
24371
|
-
|
24395
|
+
displaySqlFetch(sqlFetch);
|
24372
24396
|
});
|
24373
24397
|
} else {
|
24374
24398
|
response.text().then((result) => {
|
24375
24399
|
sqlFetch.state = 'error';
|
24376
24400
|
sqlFetch.error_message = 'failed to execute query';
|
24377
24401
|
sqlFetch.error_details = result;
|
24378
|
-
|
24402
|
+
displaySqlFetch(sqlFetch);
|
24379
24403
|
});
|
24380
24404
|
}
|
24381
24405
|
})
|
@@ -24385,42 +24409,32 @@
|
|
24385
24409
|
sqlFetch.error_message = 'failed to execute query';
|
24386
24410
|
sqlFetch.error_details = error;
|
24387
24411
|
}
|
24388
|
-
|
24412
|
+
displaySqlFetch(sqlFetch);
|
24389
24413
|
});
|
24390
24414
|
}
|
24391
24415
|
|
24392
|
-
function maybeFetchResult () {
|
24416
|
+
function maybeFetchResult (internal) {
|
24393
24417
|
const url = new URL(window.location);
|
24394
24418
|
const params = url.searchParams;
|
24395
24419
|
const sql = params.get('sql');
|
24396
24420
|
const file = params.get('file');
|
24397
24421
|
const selection = params.get('selection');
|
24398
|
-
const
|
24422
|
+
const hasSqluiReferrer = document.referrer && new URL(document.referrer).origin === url.origin;
|
24423
|
+
|
24424
|
+
// Only allow auto-run if coming from another SQLUI page. The idea here is to let the app link to URLs with run=true
|
24425
|
+
// but not other apps. This allows meta/shift-clicking to run a query.
|
24426
|
+
let run = false;
|
24427
|
+
if (params.has('run')) {
|
24428
|
+
run = (internal || hasSqluiReferrer) && ['1', 'true'].includes(params.get('run')?.toLowerCase());
|
24429
|
+
url.searchParams.delete('run');
|
24430
|
+
window.history.replaceState({}, '', url);
|
24431
|
+
}
|
24399
24432
|
|
24400
24433
|
if (params.has('file') && params.has('sql')) {
|
24401
24434
|
// TODO: show an error.
|
24402
24435
|
throw new Error('You can only specify a file or sql, not both.')
|
24403
24436
|
}
|
24404
24437
|
|
24405
|
-
const sqlFetch = {
|
24406
|
-
fetchController: new AbortController(),
|
24407
|
-
state: 'pending',
|
24408
|
-
sql,
|
24409
|
-
file,
|
24410
|
-
selection
|
24411
|
-
};
|
24412
|
-
|
24413
|
-
if (params.has('file')) {
|
24414
|
-
const fileDetails = window.metadata.saved[params.get('file')];
|
24415
|
-
if (!fileDetails) {
|
24416
|
-
throw new Error(`no such file: ${params.get('file')}`)
|
24417
|
-
}
|
24418
|
-
sqlFetch.file = file;
|
24419
|
-
sqlFetch.sql = fileDetails.contents;
|
24420
|
-
} else if (params.has('sql')) {
|
24421
|
-
sqlFetch.sql = sql;
|
24422
|
-
}
|
24423
|
-
|
24424
24438
|
const existingRequest = window.sqlFetch;
|
24425
24439
|
if (existingRequest) {
|
24426
24440
|
const selectionMatches = selection === existingRequest.selection;
|
@@ -24439,14 +24453,11 @@
|
|
24439
24453
|
|
24440
24454
|
clearResult();
|
24441
24455
|
|
24456
|
+
const sqlFetch = buildSqlFetch(sql, file, selection);
|
24442
24457
|
if (params.has('sql') || params.has('file')) {
|
24443
24458
|
setValue(sqlFetch.sql);
|
24444
24459
|
if (run) {
|
24445
|
-
|
24446
|
-
window.history.replaceState({}, '', url);
|
24447
|
-
window.sqlFetch = sqlFetch;
|
24448
|
-
displaySqlFetch(sqlFetch);
|
24449
|
-
fetchSql(sqlFetch, selection, displaySqlFetch);
|
24460
|
+
fetchSql(sqlFetch);
|
24450
24461
|
}
|
24451
24462
|
}
|
24452
24463
|
if (params.has('selection')) {
|
@@ -24455,12 +24466,42 @@
|
|
24455
24466
|
}
|
24456
24467
|
}
|
24457
24468
|
|
24469
|
+
function buildSqlFetch (sql, file, selection) {
|
24470
|
+
const sqlFetch = {
|
24471
|
+
fetchController: new AbortController(),
|
24472
|
+
state: 'pending',
|
24473
|
+
startedAt: window.performance.now(),
|
24474
|
+
spinner: 'never',
|
24475
|
+
finished: null,
|
24476
|
+
sql,
|
24477
|
+
file,
|
24478
|
+
selection
|
24479
|
+
};
|
24480
|
+
|
24481
|
+
if (file) {
|
24482
|
+
const fileDetails = window.metadata.saved[file];
|
24483
|
+
if (!fileDetails) {
|
24484
|
+
throw new Error(`no such file: ${file}`)
|
24485
|
+
}
|
24486
|
+
sqlFetch.file = file;
|
24487
|
+
sqlFetch.sql = fileDetails.contents;
|
24488
|
+
} else if (sql) {
|
24489
|
+
sqlFetch.sql = sql;
|
24490
|
+
}
|
24491
|
+
|
24492
|
+
return sqlFetch
|
24493
|
+
}
|
24494
|
+
|
24458
24495
|
function displaySqlFetchInResultTab (fetch) {
|
24459
|
-
if (fetch.state === 'pending') {
|
24496
|
+
if (fetch.state === 'pending' || fetch.spinner === 'always') {
|
24460
24497
|
clearResultBox();
|
24461
|
-
|
24462
|
-
|
24463
|
-
|
24498
|
+
if (fetch.spinner === 'never') {
|
24499
|
+
document.getElementById('result-box').style.display = 'flex';
|
24500
|
+
clearSpinner();
|
24501
|
+
} else {
|
24502
|
+
document.getElementById('result-box').style.display = 'none';
|
24503
|
+
displaySpinner(fetch);
|
24504
|
+
}
|
24464
24505
|
return
|
24465
24506
|
}
|
24466
24507
|
|
@@ -24470,6 +24511,7 @@
|
|
24470
24511
|
|
24471
24512
|
if (fetch.state === 'aborted') {
|
24472
24513
|
clearResultBox();
|
24514
|
+
document.getElementById('result-status').innerText = 'query cancelled';
|
24473
24515
|
return
|
24474
24516
|
}
|
24475
24517
|
|
@@ -24544,12 +24586,44 @@
|
|
24544
24586
|
}
|
24545
24587
|
}
|
24546
24588
|
|
24589
|
+
function clearSpinner () {
|
24590
|
+
document.getElementById('cancel-button').style.display = 'none';
|
24591
|
+
document.getElementById('fetch-sql-box').style.display = 'none';
|
24592
|
+
}
|
24593
|
+
|
24594
|
+
function displaySpinner (fetch) {
|
24595
|
+
document.getElementById('cancel-button').style.display = 'flex';
|
24596
|
+
document.getElementById('fetch-sql-box').style.display = 'flex';
|
24597
|
+
|
24598
|
+
const elapsed = window.performance.now() - fetch.startedAt;
|
24599
|
+
const seconds = Math.floor((elapsed / 1000) % 60);
|
24600
|
+
const minutes = Math.floor((elapsed / 1000 / 60) % 60);
|
24601
|
+
let display = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
24602
|
+
if (elapsed >= 1000 * 60 * 60) {
|
24603
|
+
const hours = Math.floor((elapsed / 1000 / 60 / 60) % 24);
|
24604
|
+
display = `${hours.toString().padStart(2, '0')}:${display}`;
|
24605
|
+
if (elapsed >= 1000 * 60 * 60 * 24) {
|
24606
|
+
const days = Math.floor(elapsed / 1000 / 60 / 60 / 24);
|
24607
|
+
if (days === 1) {
|
24608
|
+
display = `${days} day ${display}`;
|
24609
|
+
} else {
|
24610
|
+
display = `${days.toLocaleString()} days ${display}`;
|
24611
|
+
}
|
24612
|
+
}
|
24613
|
+
}
|
24614
|
+
document.getElementById('result-time').innerText = display;
|
24615
|
+
}
|
24616
|
+
|
24547
24617
|
function displaySqlFetchInGraphTab (fetch) {
|
24548
|
-
if (fetch.state === 'pending') {
|
24618
|
+
if (fetch.state === 'pending' || fetch.spinner === 'always') {
|
24549
24619
|
clearGraphBox();
|
24550
|
-
|
24551
|
-
|
24552
|
-
|
24620
|
+
if (fetch.spinner === 'never') {
|
24621
|
+
document.getElementById('graph-box').style.display = 'flex';
|
24622
|
+
clearSpinner();
|
24623
|
+
} else {
|
24624
|
+
document.getElementById('graph-box').style.display = 'none';
|
24625
|
+
displaySpinner(fetch);
|
24626
|
+
}
|
24553
24627
|
return
|
24554
24628
|
}
|
24555
24629
|
|
@@ -24559,6 +24633,7 @@
|
|
24559
24633
|
|
24560
24634
|
if (fetch.state === 'aborted') {
|
24561
24635
|
clearGraphBox();
|
24636
|
+
document.getElementById('graph-status').innerText = 'query cancelled';
|
24562
24637
|
return
|
24563
24638
|
}
|
24564
24639
|
|
@@ -24647,7 +24722,7 @@
|
|
24647
24722
|
headers: {
|
24648
24723
|
Accept: 'application/json'
|
24649
24724
|
},
|
24650
|
-
method: '
|
24725
|
+
method: 'POST'
|
24651
24726
|
})
|
24652
24727
|
])
|
24653
24728
|
.then((results) => {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqlui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.29
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Dower
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mysql2
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: puma
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '6.0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '6.0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: sinatra
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,19 +39,19 @@ dependencies:
|
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '3.0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: webrick
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - "~>"
|
60
46
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
62
|
-
type: :
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
63
49
|
prerelease: false
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
65
51
|
requirements:
|
66
52
|
- - "~>"
|
67
53
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
54
|
+
version: '1.0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: rspec-core
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,20 +122,6 @@ dependencies:
|
|
136
122
|
- - "~>"
|
137
123
|
- !ruby/object:Gem::Version
|
138
124
|
version: '4.0'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: watir
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - "~>"
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '7.0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - "~>"
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '7.0'
|
153
125
|
description: A SQL UI.
|
154
126
|
email: nicholasdower@gmail.com
|
155
127
|
executables:
|
@@ -162,7 +134,6 @@ files:
|
|
162
134
|
- app/database_config.rb
|
163
135
|
- app/database_metadata.rb
|
164
136
|
- app/deep.rb
|
165
|
-
- app/environment.rb
|
166
137
|
- app/mysql_types.rb
|
167
138
|
- app/server.rb
|
168
139
|
- app/sql_parser.rb
|
data/app/environment.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Parses and provides access to environment variables.
|
4
|
-
class Environment
|
5
|
-
APP_ENV = ENV.fetch('APP_ENV', 'development').to_sym
|
6
|
-
APP_PORT = ENV.fetch('APP_PORT', 8080)
|
7
|
-
|
8
|
-
def self.server_env
|
9
|
-
APP_ENV
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.development?
|
13
|
-
APP_ENV == :development
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.production?
|
17
|
-
APP_ENV == :production
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.server_port
|
21
|
-
APP_PORT
|
22
|
-
end
|
23
|
-
end
|