sqlui 0.1.53 → 0.1.54

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: aecad7b8b4d0000efae09f66acce234f8d0617e83103c2d21a7e19c2b14f4de0
4
- data.tar.gz: 0e35efaf9d1116e7d758dee9a409343654963608ec14e2f5f9714866db85365f
3
+ metadata.gz: 62f95d3843d478f8bec0c3b78beb490f731fc00608126c6ac6a6093235912ec7
4
+ data.tar.gz: 756c530843ef0f7f684f40f919d7e71c8284bb0910082758ea05dc4c9358d20c
5
5
  SHA512:
6
- metadata.gz: b8871b41338fff2bec4a4130d783e6fed8d5a9ee80e2b2786f48740ca2ed5d17887a37df2b9c0e3cc8c9a4b2bb13dcc3d74ca3a3f54b5f318f20f9d98df92f6e
7
- data.tar.gz: 1bf1575c392641fb2d3710a093e467382ab69c8656315ec801c837e0afdde5a36c0e0a8134f271c0f38a9249f92156255d5f7ddfb3dd56685b26a20ea0fed791
6
+ metadata.gz: a4abb367d1945feda08acf76583501baf457dd15ef3b8b7a76c39160e6171628a89dff645116c845ecd8e01cb9b150378f4e052f68686479ae2fd5c4ea0e1f39
7
+ data.tar.gz: 6327b8f5e755c37b5399f10a16187b61fd975c42c48a83f282861dee6a6e0cf44702acd34ae30a3b681db9a2f4e4c2622162d99d77101fe429fcd4e485314d8f
data/.release-version CHANGED
@@ -1 +1 @@
1
- 0.1.53
1
+ 0.1.54
data/app/server.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'base64'
4
4
  require 'csv'
5
+ require 'digest/md5'
5
6
  require 'erb'
6
7
  require 'json'
7
8
  require 'prometheus/middleware/collector'
@@ -58,6 +59,10 @@ class Server < Sinatra::Base
58
59
  set :raise_errors, false
59
60
  set :show_exceptions, false
60
61
 
62
+ favicon_hash = Digest::MD5.hexdigest(File.read(File.join(resources_dir, 'favicon.svg')))
63
+ css_hash = Digest::MD5.hexdigest(File.read(File.join(resources_dir, 'sqlui.css')))
64
+ js_hash = Digest::MD5.hexdigest(File.read(File.join(resources_dir, 'sqlui.js')))
65
+
61
66
  get '/-/health' do
62
67
  status 200
63
68
  body 'OK'
@@ -68,10 +73,12 @@ class Server < Sinatra::Base
68
73
  end
69
74
 
70
75
  get '/favicon.svg' do
76
+ headers 'Cache-Control' => 'max-age=31536000'
71
77
  send_file File.join(resources_dir, 'favicon.svg')
72
78
  end
73
79
 
74
80
  get "#{config.list_url_path}/?" do
81
+ headers 'Cache-Control' => 'no-cache'
75
82
  erb :databases, locals: { config: config }
76
83
  end
77
84
 
@@ -82,11 +89,13 @@ class Server < Sinatra::Base
82
89
 
83
90
  get "#{database.url_path}/sqlui.css" do
84
91
  headers 'Content-Type' => 'text/css; charset=utf-8'
92
+ headers 'Cache-Control' => 'max-age=31536000'
85
93
  send_file File.join(resources_dir, 'sqlui.css')
86
94
  end
87
95
 
88
96
  get "#{database.url_path}/sqlui.js" do
89
97
  headers 'Content-Type' => 'text/javascript; charset=utf-8'
98
+ headers 'Cache-Control' => 'max-age=31536000'
90
99
  send_file File.join(resources_dir, 'sqlui.js')
91
100
  end
92
101
 
@@ -185,6 +194,7 @@ class Server < Sinatra::Base
185
194
  sql = find_selected_query(sql, params[:selection])
186
195
 
187
196
  content_type 'application/csv; charset=utf-8'
197
+ headers 'Cache-Control' => 'no-cache'
188
198
  attachment 'result.csv'
189
199
  status 200
190
200
 
@@ -201,12 +211,16 @@ class Server < Sinatra::Base
201
211
 
202
212
  get(%r{#{Regexp.escape(database.url_path)}/(query|graph|structure|saved)}) do
203
213
  status 200
214
+ headers 'Cache-Control' => 'no-cache'
204
215
  client_config = config.airbrake[:client] || {}
205
216
  erb :sqlui, locals: {
206
217
  environment: config.environment.to_s,
207
218
  airbrake_enabled: client_config[:enabled] || false,
208
219
  airbrake_project_id: client_config[:project_id] || '',
209
- airbrake_project_key: client_config[:project_key] || ''
220
+ airbrake_project_key: client_config[:project_key] || '',
221
+ js_hash: js_hash,
222
+ css_hash: css_hash,
223
+ favicon_hash: favicon_hash
210
224
  }
211
225
  end
212
226
  end
data/app/views/sqlui.erb CHANGED
@@ -1,8 +1,9 @@
1
+ <!DOCTYPE html>
1
2
  <html lang="en">
2
3
  <head>
3
4
  <meta charset="utf-8">
4
5
  <title>SQLUI</title>
5
- <link rel="icon" type="image/x-icon" href="/favicon.svg">
6
+ <link rel="icon" type="image/x-icon" href="/favicon.svg?<%= favicon_hash %>">
6
7
  <!-- Initialize Airbrake before loading the main app JS so that we can catch errors as early as possible. -->
7
8
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@airbrake/browser"></script>
8
9
  <script type="text/javascript">
@@ -18,9 +19,9 @@
18
19
  window.airbrake?.notify(error)
19
20
  }
20
21
  </script>
21
- <script type="text/javascript" src="sqlui.js"></script>
22
+ <script type="text/javascript" src="sqlui.js?<%= js_hash %>"></script>
22
23
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
23
- <link rel="stylesheet" href="sqlui.css">
24
+ <link rel="stylesheet" href="sqlui.css?<%= css_hash %>">
24
25
  </head>
25
26
 
26
27
  <body>
@@ -1,4 +1,9 @@
1
+ html {
2
+ height: 100%;
3
+ }
4
+
1
5
  body {
6
+ height: 100%;
2
7
  margin: 0;
3
8
  overflow: hidden;
4
9
  }
@@ -245,8 +250,8 @@ p {
245
250
  display: flex;
246
251
  flex-direction: column;
247
252
  }
248
- #result-table tbody tr td{
249
- height: calc(21px + 10px); // 21 for text, 10 for top and bottom padding of 5
253
+ table tbody tr td {
254
+ height: 21px;
250
255
  }
251
256
 
252
257
  #result-table tbody tr td abbr a {
@@ -264,10 +269,7 @@ p {
264
269
  margin: 0 5px 0 0;
265
270
  text-decoration: none;
266
271
  position: relative;
267
- }
268
-
269
- #result-table tbody tr td abbr:last-child {
270
- margin: 0;
272
+ bottom: 2px; /* To make the links look vertically centered. */
271
273
  }
272
274
 
273
275
  .fetch-sql-box {
@@ -281,41 +283,62 @@ p {
281
283
 
282
284
  table {
283
285
  font-family: monospace;
284
- flex: 1;
285
286
  border-spacing: 0;
286
- display: flex;
287
- width: 100%;
288
287
  color: #333;
289
288
  font-size: 18px;
289
+ width: 100%;
290
+ table-layout: auto; /* Will be overridden if a column is resized. */
290
291
  }
291
292
 
292
293
  table td:last-child, table th:last-child {
293
- width: 100%;
294
294
  border-right: none !important;
295
295
  }
296
296
 
297
297
  td, th {
298
- padding: 5px 10px;
299
298
  font-weight: normal;
300
299
  white-space: nowrap;
301
300
  max-width: 500px;
302
- overflow: hidden;
303
- text-overflow: ellipsis;
304
301
  }
305
302
 
306
303
  td {
304
+ overflow: hidden;
305
+ text-overflow: ellipsis;
306
+ padding: 5px 10px;
307
307
  text-align: right;
308
308
  }
309
309
 
310
+ #result-table tbody tr td .cell-content-wrapper {
311
+ display: flex;
312
+ }
313
+
314
+ #result-table tbody tr td .cell-value {
315
+ flex: 1;
316
+ }
317
+
310
318
  th {
311
- text-align: left;
312
319
  font-weight: bold;
313
320
  border-bottom: 1px solid #ddd;
314
321
  border-right: 1px solid #ddd;
315
322
  }
316
323
 
317
- table {
318
- display: block;
324
+ th div.col-content-wrapper {
325
+ display: flex;
326
+ }
327
+
328
+ th div.col-name {
329
+ overflow: hidden;
330
+ text-overflow: ellipsis;
331
+ flex: 1;
332
+ height: 21px;
333
+ text-align: left;
334
+ padding: 5px 10px;
335
+ }
336
+
337
+ th div.col-resizer {
338
+ width: 7px;
339
+ position: relative;
340
+ left: 5px; /* center over the right border */
341
+ cursor: col-resize;
319
342
  }
320
343
 
321
344
  thead {
@@ -325,7 +348,6 @@ thead {
325
348
  position: sticky;
326
349
  top: 0;
327
350
  z-index: 1;
328
- table-layout: fixed;
329
351
  }
330
352
 
331
353
  .highlighted-row {
@@ -24182,27 +24182,152 @@
24182
24182
  return match ? match[1] : identifier
24183
24183
  }
24184
24184
 
24185
- function createTable (columns, rows, id, headerRenderer, cellRenderer) {
24185
+ const drag = {
24186
+ containerElement: null,
24187
+ tableElement: null,
24188
+ thElement: null,
24189
+ colElement: null,
24190
+ otherColWidths: null,
24191
+ lastColElement: null
24192
+ };
24193
+
24194
+ document.addEventListener('mousedown', (event) => {
24195
+ if (event.target.classList.contains('col-resizer')) {
24196
+ event.preventDefault();
24197
+ const thElement = event.target.parentElement.parentElement;
24198
+ if (thElement.tagName.toLowerCase() !== 'th') {
24199
+ throw new Error(`expected th element, found: ${thElement}`)
24200
+ }
24201
+ const trElement = thElement.parentElement;
24202
+ const theadElement = trElement.parentElement;
24203
+ drag.tableElement = theadElement.parentElement;
24204
+ drag.containerElement = drag.tableElement.parentElement;
24205
+ drag.thElement = thElement;
24206
+ drag.colElement = document.getElementById(event.target.dataset.colId);
24207
+
24208
+ const colElements = Array.from(drag.colElement.parentElement.childNodes);
24209
+ drag.lastColElement = colElements[colElements.length - 1];
24210
+ drag.otherColWidths = [];
24211
+ for (let i = 0; i < colElements.length - 1; i++) {
24212
+ if (colElements[i] !== drag.colElement) {
24213
+ drag.otherColWidths.push(colElements[i].getBoundingClientRect().width);
24214
+ }
24215
+ }
24216
+ colElements.forEach((element) => {
24217
+ element.style.width = `${element.getBoundingClientRect().width}px`;
24218
+ });
24219
+ drag.tableElement.style.tableLayout = 'fixed';
24220
+ }
24221
+ });
24222
+
24223
+ document.addEventListener('mouseup', (event) => {
24224
+ drag.containerElement = null;
24225
+ drag.tableElement = null;
24226
+ drag.thElement = null;
24227
+ drag.colElement = null;
24228
+ drag.otherColWidths = null;
24229
+ drag.lastColElement = null;
24230
+ });
24231
+
24232
+ document.addEventListener('mousemove', (event) => {
24233
+ if (drag.colElement) {
24234
+ const scrollOffset = drag.containerElement.scrollLeft;
24235
+ const scrolled = scrollOffset > 0;
24236
+ const containerOffset = drag.containerElement.offsetLeft;
24237
+ const newColumnWidth = Math.max(0, scrollOffset + (event.clientX - containerOffset) - drag.thElement.offsetLeft);
24238
+ if (newColumnWidth < drag.colElement.getBoundingClientRect().width && newColumnWidth < 30) return
24239
+
24240
+ drag.colElement.style.width = `${newColumnWidth}px`;
24241
+ let runningWidth = newColumnWidth;
24242
+ drag.otherColWidths.forEach((width) => {
24243
+ runningWidth += width;
24244
+ });
24245
+ let remainingWidth;
24246
+ if (scrolled) {
24247
+ remainingWidth = (scrollOffset + drag.containerElement.getBoundingClientRect().width) - runningWidth;
24248
+ } else {
24249
+ remainingWidth = Math.max(10, drag.containerElement.getBoundingClientRect().width - runningWidth);
24250
+ }
24251
+ drag.lastColElement.style.width = `${remainingWidth}px`;
24252
+ runningWidth += remainingWidth;
24253
+ drag.tableElement.style.width = `${runningWidth}px`;
24254
+ }
24255
+ });
24256
+
24257
+ function createTable (containerElement, columns, rows, id, cellRenderer) {
24258
+ if (!containerElement) throw new Error('missing table containerElement')
24259
+ if (!columns) throw new Error('missing table columns')
24260
+ if (!rows) throw new Error('missing table rows')
24261
+ if (!id) throw new Error('missing table id')
24262
+
24186
24263
  const tableElement = document.createElement('table');
24187
24264
  if (id) tableElement.id = id;
24265
+
24266
+ const colgroupElement = document.createElement('colgroup');
24267
+ tableElement.appendChild(colgroupElement);
24268
+
24188
24269
  const theadElement = document.createElement('thead');
24189
- const headerTrElement = document.createElement('tr');
24190
- const tbodyElement = document.createElement('tbody');
24191
- theadElement.appendChild(headerTrElement);
24192
24270
  tableElement.appendChild(theadElement);
24271
+
24272
+ const tbodyElement = document.createElement('tbody');
24193
24273
  tableElement.appendChild(tbodyElement);
24194
24274
 
24195
- columns.forEach(function (columnName) {
24196
- if (headerRenderer) {
24197
- headerRenderer(headerTrElement, columnName);
24198
- } else {
24199
- const headerElement = document.createElement('th');
24200
- headerElement.innerText = columnName;
24201
- headerTrElement.appendChild(headerElement);
24202
- }
24275
+ const headerTrElement = document.createElement('tr');
24276
+ theadElement.appendChild(headerTrElement);
24277
+
24278
+ const nonLastColElements = [];
24279
+ columns.forEach(function (column, index) {
24280
+ const headerElement = document.createElement('th');
24281
+ headerTrElement.appendChild(headerElement);
24282
+
24283
+ const contentWrapperElement = document.createElement('div');
24284
+ contentWrapperElement.classList.add('col-content-wrapper');
24285
+ headerElement.appendChild(contentWrapperElement);
24286
+
24287
+ const nameElement = document.createElement('div');
24288
+ nameElement.classList.add('col-name');
24289
+ contentWrapperElement.appendChild(nameElement);
24290
+
24291
+ const colElement = document.createElement('col');
24292
+ colElement.id = `${id}-col-${index}`;
24293
+ colgroupElement.appendChild(colElement);
24294
+ nonLastColElements.push(colElement);
24295
+
24296
+ const resizerElement = document.createElement('div');
24297
+ resizerElement.classList.add('col-resizer');
24298
+ resizerElement.dataset.colId = colElement.id;
24299
+ contentWrapperElement.appendChild(resizerElement);
24300
+
24301
+ nameElement.innerText = column;
24203
24302
  });
24204
24303
  if (columns.length > 0) {
24205
24304
  headerTrElement.appendChild(document.createElement('th'));
24305
+ const lastColElement = document.createElement('col');
24306
+ lastColElement.style.width = '100%';
24307
+ function resize () {
24308
+ let runningWidth = 0;
24309
+ const colElements = Array.from(tableElement.getElementsByTagName('col'));
24310
+ nonLastColElements.forEach((element, index) => {
24311
+ runningWidth += element.getBoundingClientRect().width;
24312
+ });
24313
+ const remainingWidth = Math.max(10, containerElement.getBoundingClientRect().width - runningWidth);
24314
+ colElements[colElements.length - 1].style.width = `${remainingWidth}px`;
24315
+ runningWidth += remainingWidth;
24316
+ tableElement.style.width = `${runningWidth}px`;
24317
+ }
24318
+
24319
+ const resizeObserver = new ResizeObserver(resize);
24320
+ resizeObserver.observe(containerElement);
24321
+
24322
+ const mutationObserver = new MutationObserver((mutationList, observer) => {
24323
+ if (!tableElement.parentElement) {
24324
+ resizeObserver.unobserve(containerElement);
24325
+ resizeObserver.unobserve(containerElement);
24326
+ observer.disconnect();
24327
+ }
24328
+ });
24329
+ mutationObserver.observe(containerElement, { childList: true });
24330
+ colgroupElement.appendChild(lastColElement);
24206
24331
  }
24207
24332
  let highlight = false;
24208
24333
  rows.forEach(function (row) {
@@ -24212,9 +24337,9 @@
24212
24337
  }
24213
24338
  highlight = !highlight;
24214
24339
  tbodyElement.appendChild(rowElement);
24215
- row.forEach(function (value, i) {
24340
+ row.forEach(function (value, index) {
24216
24341
  if (cellRenderer) {
24217
- cellRenderer(rowElement, columns[i], value);
24342
+ cellRenderer(rowElement, index, value);
24218
24343
  } else {
24219
24344
  const cellElement = document.createElement('td');
24220
24345
  cellElement.innerText = value;
@@ -24223,7 +24348,7 @@
24223
24348
  });
24224
24349
  rowElement.appendChild(document.createElement('td'));
24225
24350
  });
24226
- return tableElement
24351
+ containerElement.appendChild(tableElement);
24227
24352
  }
24228
24353
 
24229
24354
  /* global google */
@@ -24540,7 +24665,13 @@
24540
24665
  }
24541
24666
  rows.push(row);
24542
24667
  }
24543
- columnsElement.appendChild(createTable(columns, rows, null));
24668
+ const cellRenderer = function (rowElement, _columnIndex, value) {
24669
+ const cellElement = document.createElement('td');
24670
+ cellElement.style.textAlign = (typeof value) === 'string' ? 'left' : 'right';
24671
+ cellElement.innerText = value;
24672
+ rowElement.appendChild(cellElement);
24673
+ };
24674
+ createTable(columnsElement, columns, rows, 'columns-table', cellRenderer);
24544
24675
  }
24545
24676
 
24546
24677
  const indexEntries = Object.entries(table.indexes);
@@ -24560,7 +24691,13 @@
24560
24691
  rows.push(row);
24561
24692
  }
24562
24693
  }
24563
- indexesElement.appendChild(createTable(columns, rows, null));
24694
+ const cellRenderer = function (rowElement, _columnIndex, value) {
24695
+ const cellElement = document.createElement('td');
24696
+ cellElement.style.textAlign = (typeof value) === 'string' ? 'left' : 'right';
24697
+ cellElement.innerText = value;
24698
+ rowElement.appendChild(cellElement);
24699
+ };
24700
+ createTable(indexesElement, columns, rows, 'tables-table', cellRenderer);
24564
24701
  }
24565
24702
  });
24566
24703
  window.structureLoaded = true;
@@ -24970,35 +25107,37 @@
24970
25107
  return abbrElement
24971
25108
  };
24972
25109
 
24973
- const headerRenderer = function (rowElement, column) {
24974
- const headerElement = document.createElement('th');
24975
- headerElement.innerText = column;
24976
- if (window.metadata.columns[column]) {
24977
- headerElement.colSpan = 2;
24978
- }
24979
- rowElement.appendChild(headerElement);
24980
- };
25110
+ const cellRenderer = function (rowElement, columnIndex, value) {
25111
+ const column = fetch.result.columns[columnIndex];
25112
+ const columnType = fetch.result.column_types[columnIndex];
24981
25113
 
24982
- const cellRenderer = function (rowElement, column, value) {
24983
- if (window.metadata.columns[column]?.links?.length > 0) {
24984
- const linksColumnElement = document.createElement('td');
24985
- if (value) {
24986
- window.metadata.columns[column].links.forEach((link) => {
24987
- linksColumnElement.appendChild(createLink(link, value));
24988
- });
24989
- }
24990
- rowElement.appendChild(linksColumnElement);
24991
- const textColumnElement = document.createElement('td');
24992
- textColumnElement.innerText = value;
24993
- rowElement.appendChild(textColumnElement);
25114
+ if (value && window.metadata.columns[column]?.links?.length > 0) {
25115
+ const linksElement = document.createElement('div');
25116
+ window.metadata.columns[column].links.forEach((link) => {
25117
+ linksElement.appendChild(createLink(link, value));
25118
+ });
25119
+
25120
+ const textElement = document.createElement('div');
25121
+ textElement.classList.add('cell-value');
25122
+ textElement.style.textAlign = columnType === 'string' ? 'left' : 'right';
25123
+ textElement.innerText = value;
25124
+
25125
+ const wrapperElement = document.createElement('div');
25126
+ wrapperElement.classList.add('cell-content-wrapper');
25127
+ wrapperElement.appendChild(linksElement);
25128
+ wrapperElement.appendChild(textElement);
25129
+
25130
+ const columnElement = document.createElement('td');
25131
+ columnElement.appendChild(wrapperElement);
25132
+ rowElement.appendChild(columnElement);
24994
25133
  } else {
24995
25134
  const cellElement = document.createElement('td');
25135
+ cellElement.style.textAlign = columnType === 'string' ? 'left' : 'right';
24996
25136
  cellElement.innerText = value;
24997
25137
  rowElement.appendChild(cellElement);
24998
25138
  }
24999
25139
  };
25000
- document.getElementById('result-box')
25001
- .appendChild(createTable(fetch.result.columns, fetch.result.rows, 'result-table', headerRenderer, cellRenderer));
25140
+ createTable(document.getElementById('result-box'), fetch.result.columns, fetch.result.rows, 'result-table', cellRenderer);
25002
25141
  }
25003
25142
 
25004
25143
  function disableDownloadButtons () {
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.53
4
+ version: 0.1.54
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-12-01 00:00:00.000000000 Z
11
+ date: 2022-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: airbrake