sqlui 0.1.51 → 0.1.53

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: 1726ad80d8643d99cff2dd4dcf330d35cd109b8277bc6aaa09ae265a66b6018f
4
- data.tar.gz: 9b61be584f56fb65d3239566d79bab94c6a45352201555c853ab726e682c0e86
3
+ metadata.gz: aecad7b8b4d0000efae09f66acce234f8d0617e83103c2d21a7e19c2b14f4de0
4
+ data.tar.gz: 0e35efaf9d1116e7d758dee9a409343654963608ec14e2f5f9714866db85365f
5
5
  SHA512:
6
- metadata.gz: 57ea926197ec2949eea9af1ed9a55f0ddee7f3985fbfd8515d0401bbe4404a060f6514cbe71ac0f808aec1fc4d4670f3bd80029eda03b4f7a4bf88806f443225
7
- data.tar.gz: 850c957b833956feb133189cfb1ff0dbffbfa8d31e1ef7065038f26b7cd4a3f3035cc47dd122c344746a130b4bf4e6f6f40a4e24ff7a88a96d8a4c3d4f006475
6
+ metadata.gz: b8871b41338fff2bec4a4130d783e6fed8d5a9ee80e2b2786f48740ca2ed5d17887a37df2b9c0e3cc8c9a4b2bb13dcc3d74ca3a3f54b5f318f20f9d98df92f6e
7
+ data.tar.gz: 1bf1575c392641fb2d3710a093e467382ab69c8656315ec801c837e0afdde5a36c0e0a8134f271c0f38a9249f92156255d5f7ddfb3dd56685b26a20ea0fed791
data/.release-version ADDED
@@ -0,0 +1 @@
1
+ 0.1.53
data/app/server.rb CHANGED
@@ -4,12 +4,16 @@ require 'base64'
4
4
  require 'csv'
5
5
  require 'erb'
6
6
  require 'json'
7
+ require 'prometheus/middleware/collector'
8
+ require 'prometheus/middleware/exporter'
7
9
  require 'sinatra/base'
8
10
  require 'uri'
11
+ require 'webrick'
9
12
  require_relative 'database_metadata'
10
13
  require_relative 'mysql_types'
11
14
  require_relative 'sql_parser'
12
15
  require_relative 'sqlui'
16
+ require_relative 'version'
13
17
 
14
18
  # SQLUI Sinatra server.
15
19
  class Server < Sinatra::Base
@@ -18,6 +22,30 @@ class Server < Sinatra::Base
18
22
  end
19
23
 
20
24
  def self.init_and_run(config, resources_dir)
25
+ logger.info("Starting SQLUI v#{Version::SQLUI}")
26
+ logger.info("Airbrake enabled: #{config.airbrake[:server]&.[](:enabled) || false}")
27
+ if config.airbrake[:server]&.[](:enabled)
28
+ require 'airbrake'
29
+ require 'airbrake/rack'
30
+
31
+ Airbrake.configure do |c|
32
+ c.app_version = Version::SQLUI
33
+ c.environment = config.environment
34
+ c.logger.level = Logger::DEBUG if config.environment != :production?
35
+ config.airbrake[:server].each do |key, value|
36
+ c.send("#{key}=".to_sym, value) unless key == :enabled
37
+ end
38
+ end
39
+ Airbrake.add_filter(Airbrake::Rack::RequestBodyFilter.new)
40
+ Airbrake.add_filter(Airbrake::Rack::HttpParamsFilter.new)
41
+ Airbrake.add_filter(Airbrake::Rack::HttpHeadersFilter.new)
42
+ use Airbrake::Rack::Middleware
43
+ end
44
+
45
+ use Rack::Deflater
46
+ use Prometheus::Middleware::Collector
47
+ use Prometheus::Middleware::Exporter
48
+
21
49
  Mysql2::Client.default_query_options[:as] = :array
22
50
  Mysql2::Client.default_query_options[:cast_booleans] = true
23
51
  Mysql2::Client.default_query_options[:database_timezone] = :utc
@@ -53,17 +81,13 @@ class Server < Sinatra::Base
53
81
  end
54
82
 
55
83
  get "#{database.url_path}/sqlui.css" do
56
- @css ||= File.read(File.join(resources_dir, 'sqlui.css'))
57
- status 200
58
84
  headers 'Content-Type' => 'text/css; charset=utf-8'
59
- body @css
85
+ send_file File.join(resources_dir, 'sqlui.css')
60
86
  end
61
87
 
62
88
  get "#{database.url_path}/sqlui.js" do
63
- @js ||= File.read(File.join(resources_dir, 'sqlui.js'))
64
- status 200
65
89
  headers 'Content-Type' => 'text/javascript; charset=utf-8'
66
- body @js
90
+ send_file File.join(resources_dir, 'sqlui.js')
67
91
  end
68
92
 
69
93
  post "#{database.url_path}/metadata" do
@@ -99,7 +123,9 @@ class Server < Sinatra::Base
99
123
  end
100
124
 
101
125
  post "#{database.url_path}/query" do
102
- params.merge!(JSON.parse(request.body.read, symbolize_names: true))
126
+ data = request.body.read
127
+ request.body.rewind # since Airbrake will read the body on error
128
+ params.merge!(JSON.parse(data, symbolize_names: true))
103
129
  break client_error('missing sql') unless params[:sql]
104
130
 
105
131
  variables = params[:variables] || {}
@@ -111,29 +137,42 @@ class Server < Sinatra::Base
111
137
  database.with_client do |client|
112
138
  query_result = execute_query(client, variables, sql)
113
139
  stream do |out|
114
- json = <<~RES.chomp
115
- {
116
- "columns": #{query_result.fields.to_json},
117
- "column_types": #{MysqlTypes.map_to_google_charts_types(query_result.field_types).to_json},
118
- "total_rows": #{query_result.size.to_json},
119
- "selection": #{params[:selection].to_json},
120
- "query": #{params[:sql].to_json},
121
- "rows": [
122
- RES
123
- out << json
124
- bytes = json.bytesize
125
- query_result.each_with_index do |row, i|
126
- json = "#{i.zero? ? '' : ','}\n #{row.to_json}"
127
- bytes += json.bytesize
128
- break if i == Sqlui::MAX_ROWS || bytes > Sqlui::MAX_BYTES
129
-
140
+ if query_result
141
+ json = <<~RES.chomp
142
+ {
143
+ "columns": #{query_result.fields.to_json},
144
+ "column_types": #{MysqlTypes.map_to_google_charts_types(query_result.field_types).to_json},
145
+ "total_rows": #{query_result.size.to_json},
146
+ "selection": #{params[:selection].to_json},
147
+ "query": #{params[:sql].to_json},
148
+ "rows": [
149
+ RES
130
150
  out << json
131
- end
132
- out << <<~RES
151
+ bytes = json.bytesize
152
+ query_result.each_with_index do |row, i|
153
+ json = "#{i.zero? ? '' : ','}\n #{row.to_json}"
154
+ bytes += json.bytesize
155
+ break if i == Sqlui::MAX_ROWS || bytes > Sqlui::MAX_BYTES
156
+
157
+ out << json
158
+ end
159
+ out << <<~RES
133
160
 
134
- ]
135
- }
136
- RES
161
+ ]
162
+ }
163
+ RES
164
+ else
165
+ out << <<~RES
166
+ {
167
+ "columns": [],
168
+ "column_types": [],
169
+ "total_rows": 0,
170
+ "selection": #{params[:selection].to_json},
171
+ "query": #{params[:sql].to_json},
172
+ "rows": []
173
+ }
174
+ RES
175
+ end
137
176
  end
138
177
  end
139
178
  end
@@ -161,10 +200,14 @@ class Server < Sinatra::Base
161
200
  end
162
201
 
163
202
  get(%r{#{Regexp.escape(database.url_path)}/(query|graph|structure|saved)}) do
164
- @html ||= File.read(File.join(resources_dir, 'sqlui.html'))
165
203
  status 200
166
- headers 'Content-Type' => 'text/html; charset=utf-8'
167
- body @html
204
+ client_config = config.airbrake[:client] || {}
205
+ erb :sqlui, locals: {
206
+ environment: config.environment.to_s,
207
+ airbrake_enabled: client_config[:enabled] || false,
208
+ airbrake_project_id: client_config[:project_id] || '',
209
+ airbrake_project_key: client_config[:project_key] || ''
210
+ }
168
211
  end
169
212
  end
170
213
 
data/app/sqlui.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'sqlui_config'
4
4
  require_relative 'server'
5
+ require_relative 'version'
5
6
 
6
7
  # Main entry point.
7
8
  class Sqlui
@@ -25,7 +26,7 @@ class Sqlui
25
26
 
26
27
  def self.from_command_line(args)
27
28
  if args.include?('-v') || args.include?('--version')
28
- puts File.read('.version')
29
+ puts Version::SQLUI
29
30
  exit
30
31
  end
31
32
 
data/app/sqlui_config.rb CHANGED
@@ -8,7 +8,7 @@ require_relative 'deep'
8
8
 
9
9
  # App config including database configs.
10
10
  class SqluiConfig
11
- attr_reader :name, :port, :environment, :list_url_path, :database_configs
11
+ attr_reader :name, :port, :environment, :list_url_path, :database_configs, :airbrake
12
12
 
13
13
  def initialize(filename, overrides = {})
14
14
  config = YAML.safe_load(ERB.new(File.read(filename)).result, aliases: true).deep_merge!(overrides)
@@ -29,6 +29,7 @@ class SqluiConfig
29
29
  @database_configs = databases.map do |_, current|
30
30
  DatabaseConfig.new(current)
31
31
  end
32
+ @airbrake = Args.fetch_optional_hash(config, :airbrake) || { enabled: false }
32
33
  end
33
34
 
34
35
  def database_config_for(url_path:)
data/app/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Version
4
+ SQLUI = File.read(File.expand_path(File.join(__dir__, '..', '.release-version'))).strip
5
+ end
@@ -1,3 +1,4 @@
1
+ <!DOCTYPE html>
1
2
  <html lang="en">
2
3
  <head>
3
4
  <meta charset="utf-8">
data/app/views/error.erb CHANGED
@@ -1,3 +1,4 @@
1
+ <!DOCTYPE html>
1
2
  <html>
2
3
  <html lang="en">
3
4
  <head>
@@ -3,7 +3,22 @@
3
3
  <meta charset="utf-8">
4
4
  <title>SQLUI</title>
5
5
  <link rel="icon" type="image/x-icon" href="/favicon.svg">
6
- <script src="sqlui.js"></script>
6
+ <!-- Initialize Airbrake before loading the main app JS so that we can catch errors as early as possible. -->
7
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@airbrake/browser"></script>
8
+ <script type="text/javascript">
9
+ <% if airbrake_enabled %>
10
+ window.airbrake = new Airbrake.Notifier({
11
+ environment: "<%= environment %>",
12
+ projectId: "<%= airbrake_project_id %>",
13
+ projectKey: "<%= airbrake_project_key %>"
14
+ })
15
+ <% end %>
16
+
17
+ window.notifyAirbrake = function (error) {
18
+ window.airbrake?.notify(error)
19
+ }
20
+ </script>
21
+ <script type="text/javascript" src="sqlui.js"></script>
7
22
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
8
23
  <link rel="stylesheet" href="sqlui.css">
9
24
  </head>
@@ -245,6 +245,9 @@ p {
245
245
  display: flex;
246
246
  flex-direction: column;
247
247
  }
248
+ #result-table tbody tr td{
249
+ height: calc(21px + 10px); // 21 for text, 10 for top and bottom padding of 5
250
+ }
248
251
 
249
252
  #result-table tbody tr td abbr a {
250
253
  color: #555;
@@ -23922,6 +23922,7 @@
23922
23922
  const schemas = Object.entries(metadata.schemas);
23923
23923
  const editorSchema = {};
23924
23924
  const tables = [];
23925
+ const aliases = [];
23925
23926
  schemas.forEach(([schemaName, schema]) => {
23926
23927
  Object.entries(schema.tables).forEach(([tableName, table]) => {
23927
23928
  const qualifiedTableName = schemas.length === 1 ? tableName : `${schemaName}.${tableName}`;
@@ -23929,30 +23930,48 @@
23929
23930
  const columns = Object.keys(table.columns);
23930
23931
  editorSchema[qualifiedTableName] = columns;
23931
23932
  const alias = metadata.tables[qualifiedTableName]?.alias;
23932
- const boost = metadata.tables[qualifiedTableName]?.boost;
23933
+ if (alias) {
23934
+ aliases.push(alias);
23935
+ aliases.push(`\`${alias}\``);
23936
+ }
23937
+
23938
+ let boost = metadata.tables[qualifiedTableName]?.boost;
23939
+ boost = boost ? boost * 2 : null;
23933
23940
  if (alias) {
23934
23941
  editorSchema[alias] = columns;
23942
+ // Add a completion which inserts the table name and alias for use just after join or from.
23935
23943
  tables.push({
23936
- label: qualifiedTableName,
23937
- detail: alias,
23938
- boost,
23939
- alias_type: 'with',
23944
+ label: `${qualifiedTableName} ${alias}`,
23945
+ boost: boost + 1,
23946
+ completion_types: ['table_with_alias'],
23940
23947
  quoted: `${quotedQualifiedTableName} \`${alias}\``,
23941
23948
  unquoted: `${qualifiedTableName} ${alias}`
23942
23949
  });
23950
+ // Add a completion which only inserts the alias for use with "select" or "where" or "on".
23943
23951
  tables.push({
23944
- label: qualifiedTableName,
23945
- detail: alias,
23946
- boost,
23947
- alias_type: 'only',
23952
+ label: `${qualifiedTableName} ${alias}`,
23953
+ boost: boost + 1,
23954
+ type: 'constant',
23955
+ completion_types: ['alias_only'],
23948
23956
  quoted: '`' + alias + '`',
23949
23957
  unquoted: alias
23950
23958
  });
23951
- } else {
23952
23959
  tables.push({
23953
- label: qualifiedTableName
23960
+ label: alias,
23961
+ boost: boost + 1,
23962
+ type: 'constant',
23963
+ completion_types: ['alias_only'],
23964
+ quoted: '`' + alias + '`',
23965
+ unquoted: alias
23954
23966
  });
23955
23967
  }
23968
+ tables.push({
23969
+ label: qualifiedTableName,
23970
+ boost,
23971
+ completion_types: ['table_with_alias', 'alias_only', 'table_only'],
23972
+ quoted: quotedQualifiedTableName,
23973
+ unquoted: qualifiedTableName
23974
+ });
23956
23975
  });
23957
23976
  });
23958
23977
  // I prefer to use Cmd-Enter/Ctrl-Enter to submit the query. Here I am replacing the default mapping.
@@ -23992,66 +24011,52 @@
23992
24011
  tables
23993
24012
  };
23994
24013
  const originalSchemaCompletionSource = schemaCompletionSource(sqlConfig);
23995
- const originalKeywordCompletionSource = keywordCompletionSource(MySQL, true);
23996
- const keywordCompletions = [];
24014
+
24015
+ const joinCompletions = [];
23997
24016
  metadata.joins.forEach((join) => {
23998
- ['JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'CROSS JOIN'].forEach((type) => {
23999
- keywordCompletions.push({ label: `${type} ${join.label}`, apply: `${type} ${join.apply}`, type: 'keyword' });
24000
- });
24017
+ joinCompletions.push({ label: join.label, apply: join.apply, type: 'keyword' });
24001
24018
  });
24002
- let combinedKeywordCompletionSource;
24003
- if (keywordCompletions.length > 0) {
24004
- const customKeywordCompletionSource = ifNotIn(['QuotedIdentifier', 'SpecialVar', 'String', 'LineComment', 'BlockComment', '.'], completeFromList(keywordCompletions));
24005
- combinedKeywordCompletionSource = function (context) {
24006
- const original = originalKeywordCompletionSource(context);
24007
- const custom = customKeywordCompletionSource(context);
24008
- if (original?.options && custom?.options) {
24009
- original.options = original.options.concat(custom.options);
24010
- }
24011
- return original
24012
- };
24013
- } else {
24014
- combinedKeywordCompletionSource = originalKeywordCompletionSource;
24015
- }
24019
+ const customJoinCompletionSource = completeFromList(joinCompletions);
24020
+
24016
24021
  const sqlExtension = new LanguageSupport(
24017
24022
  MySQL.language,
24018
24023
  [
24019
24024
  MySQL.language.data.of({
24020
24025
  autocomplete: (context) => {
24021
24026
  const result = originalSchemaCompletionSource(context);
24022
- if (!result?.options) return result
24027
+ if (!result?.options || result.options.length === 0) return result
24023
24028
 
24024
24029
  const tree = syntaxTree(context.state);
24025
24030
  let node = tree.resolveInner(context.pos, -1);
24026
24031
  if (!node) return result
24027
24032
 
24028
- // We are trying to identify the case where we are autocompleting a table name after "from" or "join"
24029
-
24030
- // TODO: we don't handle the case where a user typed "select table.foo from". In that case we probably
24031
- // shouldn't autocomplete the alias. Though, if the user typed "select table.foo, t.bar", we won't know
24032
- // what to do. Maybe it is ok to force users to simply delete the alias after autocompleting.
24033
-
24034
- // TODO: if table aliases aren't enabled, we don't need to override autocomplete.
24035
-
24033
+ // We want to customize the autocomplete options based on context. For instance, after select or where, we
24034
+ // should autocomplete aliases, if they are defined, otherwise table names. After from, we should autocomplete
24035
+ // table names with aliases. Etc. We start by trying to identify the node before our current position. The
24036
+ // method for accomplishing this seems to vary based on the user's context.
24036
24037
  let foundSchema;
24037
24038
  if (node.name === 'Statement') {
24038
- // The node can be a Statement if the cursor is at the end of "from " and there is a complete
24039
- // statement in the editor (semicolon present). In that case we want to find the node just before the
24040
- // current position so that we can check whether it is "from" or "join".
24039
+ // The current node can be a Statement if the cursor is at the end of "from " (for instance) and there is a
24040
+ // complete statement in the editor (semicolon present). In that case we want to find the node just before
24041
+ // the current position.
24041
24042
  node = node.childBefore(context.pos);
24042
24043
  } else if (node.name === 'Script') {
24043
24044
  // It seems the node can sometimes be a Script if the cursor is at the end of the last statement in the
24044
24045
  // editor and the statement doesn't end in a semicolon. In that case we can find the last statement in the
24045
- // Script so that we can check whether it is "from" or "join".
24046
+ // Script.
24046
24047
  node = node.lastChild?.childBefore(context.pos);
24048
+ } else if (node.name === 'Parens') {
24049
+ // The current node can be a Parens if we are inside of a function or sub query, for instance just after a
24050
+ // space after a "select" in "select * from (select ) as foo". In that case we can find the last statement
24051
+ // in the Parens.
24052
+ node = node.childBefore(context.pos);
24047
24053
  } else if (['Identifier', 'QuotedIdentifier', 'Keyword', '.'].includes(node.name)) {
24048
24054
  // If the node is an Identifier, we might be in the middle of typing the table name. If the node is a
24049
- // Keyword but isn't "from" or "join", we might be in the middle of typing a table name that is similar
24050
- // to a Keyword, for instance "orders" or "selections" or "fromages". In these cases, look for the previous
24051
- // sibling so that we can check whether it is "from" or "join". If we found a '.' or if the previous
24052
- // sibling is a '.', we might be in the middle of typing something like "schema.table" or
24053
- // "`schema`.table" or "`schema`.`table`". In these cases we need to record the schema used so that we
24054
- // can autocomplete table names with aliases.
24055
+ // Keyword, we might be in the middle of typing a table name that is similar to a Keyword, for instance
24056
+ // "orders" or "selections" or "fromages". In these cases, look for the previous sibling. If the node is a
24057
+ // '.' or if the previous sibling is a '.', we might be in the middle of typing something like
24058
+ // "schema.table" or "`schema`.table" or "`schema`.`table`". In these cases we need to record the schema
24059
+ // used so that we can autocomplete table names with aliases.
24055
24060
  if (node.name !== '.') {
24056
24061
  node = node.prevSibling;
24057
24062
  }
@@ -24068,29 +24073,54 @@
24068
24073
  }
24069
24074
  }
24070
24075
 
24076
+ // We now have the node before the node the user is currently creating. Step back or up until we find a Keyword.
24077
+ while (true) {
24078
+ const nodeText = node ? context.state.doc.sliceString(node.from, node.to).toLowerCase() : null;
24079
+ if (!node || (node.name === 'Keyword' &&
24080
+ ['where', 'select', 'from', 'into', 'join', 'straight_join', 'database', 'as'].includes(nodeText))) {
24081
+ break
24082
+ }
24083
+
24084
+ node = node?.prevSibling || node?.parent;
24085
+ }
24071
24086
  const nodeText = node ? context.state.doc.sliceString(node.from, node.to).toLowerCase() : null;
24072
- if (node?.name === 'Keyword' && ['from', 'join'].includes(nodeText)) {
24073
- result.options = result.options.filter((option) => {
24074
- return option.alias_type === undefined || option.alias_type === 'with'
24075
- });
24076
- } else {
24077
- result.options = result.options.filter((option) => {
24078
- return option.alias_type === undefined || option.alias_type === 'only'
24079
- });
24087
+ let completionType = 'table_only';
24088
+ if (node?.name === 'Keyword') {
24089
+ if (['from', 'join', 'straight_join'].includes(nodeText)) {
24090
+ completionType = 'table_with_alias';
24091
+ } else if (['where', 'select'].includes(nodeText)) {
24092
+ completionType = 'alias_only';
24093
+ }
24094
+
24095
+ if (['join', 'straight_join'].includes(nodeText)) {
24096
+ const customJoins = customJoinCompletionSource(context);
24097
+ if (customJoins?.options) {
24098
+ result.options = result.options.concat(customJoins.options);
24099
+ }
24100
+ }
24080
24101
  }
24102
+ result.options = result.options.filter((option) => {
24103
+ if (option.completion_types === undefined && option.type === 'constant' && aliases.includes(option.label)) {
24104
+ // The default options already include an alias if the statement includes one in the from clause.
24105
+ // In that case we want to remove it in favor of our own alias option.
24106
+ return false
24107
+ }
24108
+ // Allow any options we didn't create plus options we created which are of the expected type.
24109
+ return option.completion_types === undefined || option.completion_types.includes(completionType)
24110
+ });
24081
24111
  result.options = result.options.map((option) => {
24082
24112
  // Some shenanigans. If the default autocomplete function quoted the label, we want to ensure the quote
24083
24113
  // only applies to the table name and not the alias. You might think we could do this by overriding the
24084
24114
  // apply function but apply is set to null when quoting.
24085
24115
  // See https://github.com/codemirror/lang-sql/blob/ebf115fffdbe07f91465ccbd82868c587f8182bc/src/complete.ts#L90
24086
- if (option.alias_type) {
24116
+ if (option.quoted) {
24087
24117
  if (option.label.match(/^`.*`$/)) {
24088
24118
  option.apply = option.quoted;
24089
24119
  } else {
24090
24120
  option.apply = option.unquoted;
24091
24121
  }
24092
24122
  }
24093
- if (foundSchema) {
24123
+ if (foundSchema && completionType === 'table_with_alias') {
24094
24124
  const unquotedLabel = unquoteSqlId(option.label);
24095
24125
  const quoted = unquotedLabel !== option.label;
24096
24126
  const tableConfig = metadata.tables[`${foundSchema}.${unquotedLabel}`];
@@ -24113,7 +24143,7 @@
24113
24143
  }
24114
24144
  }),
24115
24145
  MySQL.language.data.of({
24116
- autocomplete: combinedKeywordCompletionSource
24146
+ autocomplete: keywordCompletionSource(MySQL, true)
24117
24147
  })
24118
24148
  ]
24119
24149
  );
@@ -24952,9 +24982,11 @@
24952
24982
  const cellRenderer = function (rowElement, column, value) {
24953
24983
  if (window.metadata.columns[column]?.links?.length > 0) {
24954
24984
  const linksColumnElement = document.createElement('td');
24955
- window.metadata.columns[column].links.forEach((link) => {
24956
- linksColumnElement.appendChild(createLink(link, value));
24957
- });
24985
+ if (value) {
24986
+ window.metadata.columns[column].links.forEach((link) => {
24987
+ linksColumnElement.appendChild(createLink(link, value));
24988
+ });
24989
+ }
24958
24990
  rowElement.appendChild(linksColumnElement);
24959
24991
  const textColumnElement = document.createElement('td');
24960
24992
  textColumnElement.innerText = value;
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.51
4
+ version: 0.1.53
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-28 00:00:00.000000000 Z
11
+ date: 2022-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: airbrake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: mysql2
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: prometheus-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: sinatra
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -129,7 +157,7 @@ executables:
129
157
  extensions: []
130
158
  extra_rdoc_files: []
131
159
  files:
132
- - ".version"
160
+ - ".release-version"
133
161
  - app/args.rb
134
162
  - app/database_config.rb
135
163
  - app/database_metadata.rb
@@ -139,12 +167,13 @@ files:
139
167
  - app/sql_parser.rb
140
168
  - app/sqlui.rb
141
169
  - app/sqlui_config.rb
170
+ - app/version.rb
142
171
  - app/views/databases.erb
143
172
  - app/views/error.erb
173
+ - app/views/sqlui.erb
144
174
  - bin/sqlui
145
175
  - client/resources/favicon.svg
146
176
  - client/resources/sqlui.css
147
- - client/resources/sqlui.html
148
177
  - client/resources/sqlui.js
149
178
  homepage: https://github.com/nicholasdower/sqlui
150
179
  licenses:
data/.version DELETED
@@ -1 +0,0 @@
1
- 0.1.51