sqlui 0.1.53 → 0.1.54

Sign up to get free protection for your applications and to get access to all the features.
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