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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 580bda7206a3282d1dd0dae3754daa8bdf53b3522c5210a0e2542147d3a13aee
4
- data.tar.gz: 6fcfca4ed80216ef293f72e8be9e59896dbd82eb3da76f5761ad3671fa16c32b
3
+ metadata.gz: 9257bcf43bc98bcd5ed8a52d3b2643db1189d0d3b8c2c1444c5d949ff37ed03c
4
+ data.tar.gz: 6a96de39b01459a5b222a2781158523707fbd3e585400dd18a1b4469c55b4c9c
5
5
  SHA512:
6
- metadata.gz: 6ffaff0a56fd85efbf3c3fe69fad3c5f4750b23defc7716004420fb4fa9243983d6a105dbbe81892c3cf859332530920ab88f4312a9ffb7009e6d46103e3dd9d
7
- data.tar.gz: 94cea75b430cfe9289f28fc9eaaa9a5cdd026945d0fb2e66616cfcc0e6bcd3845fcfcc205ad9b047274e81f50c71313f004fe35ba0fc167216c06a9211c04317
6
+ metadata.gz: 32eb854b16a9b345456902379ec7cc8ba5e091562a62de90451f63d28146815a880315b0921303770233d036187d8b26f6164bf04e8dd935232bf3a6ef82e646
7
+ data.tar.gz: 0da14c2be2b04171df6ccef8dc4e4fb1c512440371d2394ae788ecc88994e032bf69633e683bab61e0cc4bc54ae367cb0ce6565cfc4b3f67559b90061ddbdaf9
data/.version CHANGED
@@ -1 +1 @@
1
- 0.1.28
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
- raise ArgumentError, "required parameter #{key} not a #{classes[0].to_s.downcase}" if classes.size == 1
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
- raise ArgumentError, "required parameter #{key} not #{classes.map(&:to_s).map(&:downcase).join(' or ')}"
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!(&:to_s)
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, Environment.server_port
19
- set :env, Environment.server_env
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': 'text/css'
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': 'text/javascript'
50
+ headers 'Content-Type' => 'text/javascript'
52
51
  body @js
53
52
  end
54
53
 
55
- get "#{database.url_path}/metadata" do
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': 'application/json'
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': 'application/json'
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': 'text/html'
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': 'application/json'
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('Content-Type': 'application/json')
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
- deep_symbolize!(config)
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
@@ -322,3 +322,9 @@ select {
322
322
  0% { transform: rotate(0deg); }
323
323
  100% { transform: rotate(360deg); }
324
324
  }
325
+
326
+ .result-time {
327
+ font-family: monospace;
328
+ color: #333;
329
+ font-size: 14px;
330
+ }
@@ -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;">
@@ -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
- const existingFetch = window.sqlFetch;
24304
- if (existingFetch?.state === 'pending') {
24305
- existingFetch.state = 'aborted';
24306
- existingFetch.fetchController.abort();
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 fetchSql (sqlFetch, selection, callback) {
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
- callback(sqlFetch);
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
- callback(sqlFetch);
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
- callback(sqlFetch);
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 run = ['1', 'true'].includes(params.get('run')?.toLowerCase());
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
- url.searchParams.delete('run');
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
- document.getElementById('cancel-button').style.display = 'flex';
24462
- document.getElementById('result-box').style.display = 'none';
24463
- document.getElementById('fetch-sql-box').style.display = 'flex';
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
- document.getElementById('cancel-button').style.display = 'flex';
24551
- document.getElementById('graph-box').style.display = 'none';
24552
- document.getElementById('fetch-sql-box').style.display = 'flex';
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: 'GET'
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.28
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-01 00:00:00.000000000 Z
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: puma
42
+ name: webrick
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '6.0'
62
- type: :development
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: '6.0'
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