sqlui 0.1.19 → 0.1.20
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 +33 -0
- data/app/database_config.rb +7 -7
- data/app/database_metadata.rb +116 -0
- data/app/deep.rb +58 -0
- data/app/mysql_types.rb +33 -0
- data/app/server.rb +124 -218
- data/app/sqlui.rb +36 -0
- data/app/sqlui_config.rb +11 -16
- data/bin/sqlui +3 -1
- data/client/resources/sqlui.css +263 -242
- data/client/resources/sqlui.html +8 -5
- data/client/resources/sqlui.js +166 -81
- metadata +7 -2
data/app/sqlui.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'sqlui_config'
|
4
|
+
require_relative 'server'
|
5
|
+
|
6
|
+
# Main entry point.
|
7
|
+
class Sqlui
|
8
|
+
MAX_ROWS = 1_000
|
9
|
+
|
10
|
+
def initialize(config_file)
|
11
|
+
raise 'you must specify a configuration file' unless config_file
|
12
|
+
raise 'configuration file does not exist' unless File.exist?(config_file)
|
13
|
+
|
14
|
+
@config = SqluiConfig.new(config_file)
|
15
|
+
@resources_dir = File.join(File.expand_path('..', File.dirname(__FILE__)), 'client', 'resources')
|
16
|
+
|
17
|
+
# Connect to each database to verify each can be connected to.
|
18
|
+
@config.database_configs.each { |database| database.with_client { |client| client } }
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
Server.init_and_run(@config, @resources_dir)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_command_line(args)
|
26
|
+
if args.include?('-v') || args.include?('--version')
|
27
|
+
puts File.read('.version')
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
raise 'you must specify a configuration file' unless args.size == 1
|
32
|
+
raise 'configuration file does not exist' unless File.exist?(args[0])
|
33
|
+
|
34
|
+
Sqlui.new(args[0])
|
35
|
+
end
|
36
|
+
end
|
data/app/sqlui_config.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'yaml'
|
4
4
|
require_relative 'database_config'
|
5
|
+
require_relative 'args'
|
5
6
|
|
6
7
|
# App config including database configs.
|
7
8
|
class SqluiConfig
|
@@ -10,40 +11,34 @@ class SqluiConfig
|
|
10
11
|
def initialize(filename)
|
11
12
|
config = YAML.safe_load(ERB.new(File.read(filename)).result)
|
12
13
|
deep_symbolize!(config)
|
13
|
-
@name = fetch_non_empty_string(config, :name).strip
|
14
|
-
@list_url_path = fetch_non_empty_string(config, :list_url_path).strip
|
14
|
+
@name = Args.fetch_non_empty_string(config, :name).strip
|
15
|
+
@list_url_path = Args.fetch_non_empty_string(config, :list_url_path).strip
|
15
16
|
raise ArgumentError, 'list_url_path should start with a /' unless @list_url_path.start_with?('/')
|
16
17
|
if @list_url_path.length > 1 && @list_url_path.end_with?('/')
|
17
18
|
raise ArgumentError, 'list_url_path should not end with a /'
|
18
19
|
end
|
19
20
|
|
20
|
-
databases = config
|
21
|
-
if databases.nil? || !databases.is_a?(Hash) || databases.empty?
|
22
|
-
raise ArgumentError, 'required parameter databases missing'
|
23
|
-
end
|
24
|
-
|
21
|
+
databases = Args.fetch_non_empty_hash(config, :databases)
|
25
22
|
@database_configs = databases.map do |_, current|
|
26
23
|
DatabaseConfig.new(current)
|
27
24
|
end
|
28
25
|
end
|
29
26
|
|
30
27
|
def database_config_for(url_path:)
|
31
|
-
@database_configs.find { |database| database.url_path == url_path }
|
28
|
+
config = @database_configs.find { |database| database.url_path == url_path }
|
29
|
+
raise ArgumentError, "no config found for path #{url_path}" unless config
|
30
|
+
|
31
|
+
config
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
-
def fetch_non_empty_string(hash, key)
|
37
|
-
value = hash[key]
|
38
|
-
raise ArgumentError, "required parameter #{key} missing" if value.nil? || !value.is_a?(String) || value.strip.empty?
|
39
|
-
|
40
|
-
value.strip
|
41
|
-
end
|
42
|
-
|
43
36
|
def deep_symbolize!(object)
|
44
|
-
return unless object.is_a? Hash
|
37
|
+
return object unless object.is_a? Hash
|
45
38
|
|
46
39
|
object.transform_keys!(&:to_sym)
|
47
40
|
object.each_value { |child| deep_symbolize!(child) }
|
41
|
+
|
42
|
+
object
|
48
43
|
end
|
49
44
|
end
|
data/bin/sqlui
CHANGED
data/client/resources/sqlui.css
CHANGED
@@ -1,242 +1,263 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
1
|
+
body {
|
2
|
+
margin: 0;
|
3
|
+
}
|
4
|
+
|
5
|
+
.loading-box {
|
6
|
+
font-family: monospace;
|
7
|
+
display: flex;
|
8
|
+
flex-direction: column;
|
9
|
+
flex: 1;
|
10
|
+
margin: 0;
|
11
|
+
height: 100%;
|
12
|
+
min-height: 100%;
|
13
|
+
align-items: start;
|
14
|
+
justify-content: start;
|
15
|
+
padding: 10px;
|
16
|
+
}
|
17
|
+
|
18
|
+
.main-box {
|
19
|
+
display: flex;
|
20
|
+
flex-direction: column;
|
21
|
+
flex: 1;
|
22
|
+
margin: 0;
|
23
|
+
height: 100%;
|
24
|
+
min-height: 100%;
|
25
|
+
}
|
26
|
+
|
27
|
+
.header, .server-name {
|
28
|
+
display: flex;
|
29
|
+
align-items: center;
|
30
|
+
justify-content: start;
|
31
|
+
color: #333;
|
32
|
+
}
|
33
|
+
|
34
|
+
.header {
|
35
|
+
font-weight: bold;
|
36
|
+
padding-left: 5px;
|
37
|
+
}
|
38
|
+
|
39
|
+
.server-name {
|
40
|
+
flex: 1;
|
41
|
+
padding-left: 15px;
|
42
|
+
}
|
43
|
+
|
44
|
+
.tabs-box {
|
45
|
+
display: flex;
|
46
|
+
flex-direction: row;
|
47
|
+
border-bottom: 1px solid #ddd;
|
48
|
+
height: 36px;
|
49
|
+
font-family: Helvetica;
|
50
|
+
}
|
51
|
+
|
52
|
+
.tab-button, .selected-tab-button {
|
53
|
+
border: none;
|
54
|
+
outline: none;
|
55
|
+
cursor: pointer;
|
56
|
+
width: 150px;
|
57
|
+
padding: 2px;
|
58
|
+
margin: 0px;
|
59
|
+
display: flex;
|
60
|
+
justify-content: center;
|
61
|
+
background-color: #fff;
|
62
|
+
}
|
63
|
+
|
64
|
+
.tab-button {
|
65
|
+
color: #888;
|
66
|
+
}
|
67
|
+
|
68
|
+
.tab-button:hover {
|
69
|
+
color: #333;
|
70
|
+
}
|
71
|
+
|
72
|
+
.selected-tab-button {
|
73
|
+
color: #333;
|
74
|
+
font-weight: bold;
|
75
|
+
}
|
76
|
+
|
77
|
+
.tab-content-element {
|
78
|
+
display: none;
|
79
|
+
}
|
80
|
+
|
81
|
+
.query-box {
|
82
|
+
display: flex;
|
83
|
+
flex-direction: column;
|
84
|
+
}
|
85
|
+
|
86
|
+
.query {
|
87
|
+
display: flex;
|
88
|
+
flex: 1;
|
89
|
+
}
|
90
|
+
|
91
|
+
.submit-box {
|
92
|
+
display: flex;
|
93
|
+
border-top: 1px solid #ddd;
|
94
|
+
justify-content: right;
|
95
|
+
}
|
96
|
+
|
97
|
+
.submit-button {
|
98
|
+
cursor: pointer;
|
99
|
+
margin: 5px;
|
100
|
+
border: 1px solid #888;
|
101
|
+
background: none;
|
102
|
+
padding: 5px;
|
103
|
+
}
|
104
|
+
|
105
|
+
.status {
|
106
|
+
display: flex;
|
107
|
+
justify-content: center;
|
108
|
+
align-content: center;
|
109
|
+
flex-direction: column;
|
110
|
+
font-family: Helvetica;
|
111
|
+
white-space: nowrap;
|
112
|
+
overflow: hidden;
|
113
|
+
}
|
114
|
+
|
115
|
+
.result-box, .saved-box, .graph-box, .structure-box {
|
116
|
+
flex: 1;
|
117
|
+
overflow: auto;
|
118
|
+
display: flex;
|
119
|
+
flex-direction: column;
|
120
|
+
}
|
121
|
+
|
122
|
+
.graph-box, .result-box {
|
123
|
+
border-top: 1px solid #ddd;
|
124
|
+
}
|
125
|
+
|
126
|
+
.graph-box {
|
127
|
+
padding: 20px;
|
128
|
+
}
|
129
|
+
|
130
|
+
table {
|
131
|
+
font-family: monospace;
|
132
|
+
flex: 1;
|
133
|
+
border-spacing: 0px;
|
134
|
+
display: flex;
|
135
|
+
width: 100%;
|
136
|
+
}
|
137
|
+
|
138
|
+
table td:last-child, table th:last-child {
|
139
|
+
width: 100%;
|
140
|
+
border-right: none !important;
|
141
|
+
}
|
142
|
+
|
143
|
+
td, th {
|
144
|
+
padding: 5px 20px 5px 5px;
|
145
|
+
font-weight: normal;
|
146
|
+
white-space: nowrap;
|
147
|
+
max-width: 500px;
|
148
|
+
overflow: hidden;
|
149
|
+
text-overflow: ellipsis;
|
150
|
+
}
|
151
|
+
|
152
|
+
td {
|
153
|
+
text-align: right;
|
154
|
+
}
|
155
|
+
|
156
|
+
th {
|
157
|
+
text-align: left;
|
158
|
+
font-weight: bold;
|
159
|
+
border-bottom: 1px solid #ddd;
|
160
|
+
border-right: 1px solid #ddd;
|
161
|
+
}
|
162
|
+
|
163
|
+
table {
|
164
|
+
display: block;
|
165
|
+
}
|
166
|
+
|
167
|
+
thead {
|
168
|
+
padding: 0px;
|
169
|
+
background-color: #fff;
|
170
|
+
position: -webkit-sticky;
|
171
|
+
position: sticky;
|
172
|
+
top: 0px;
|
173
|
+
z-index: 100;
|
174
|
+
table-layout: fixed;
|
175
|
+
}
|
176
|
+
|
177
|
+
.highlighted-row {
|
178
|
+
background: #eee;
|
179
|
+
}
|
180
|
+
|
181
|
+
.status-box {
|
182
|
+
padding: 5px;
|
183
|
+
display: flex;
|
184
|
+
flex-direction: rows;
|
185
|
+
justify-content: space-between;
|
186
|
+
border-top: 1px solid #ddd;
|
187
|
+
height: 30px;
|
188
|
+
}
|
189
|
+
|
190
|
+
.CodeMirror pre.CodeMirror-placeholder {
|
191
|
+
color: #999;
|
192
|
+
}
|
193
|
+
|
194
|
+
.tabs-box {
|
195
|
+
display: flex;
|
196
|
+
}
|
197
|
+
|
198
|
+
.saved-box {
|
199
|
+
font-family: Helvetica;
|
200
|
+
}
|
201
|
+
|
202
|
+
.saved-box h1 {
|
203
|
+
margin: 0px;
|
204
|
+
padding-left: 10px;
|
205
|
+
padding-right: 10px;
|
206
|
+
padding-top: 10px;
|
207
|
+
padding-bottom: 10px;
|
208
|
+
font-weight: bold;
|
209
|
+
}
|
210
|
+
|
211
|
+
.saved-box div:first-child {
|
212
|
+
border-top: none !important;
|
213
|
+
}
|
214
|
+
|
215
|
+
.saved-box p {
|
216
|
+
margin: 0px;
|
217
|
+
padding-left: 20px;
|
218
|
+
padding-bottom: 20px;
|
219
|
+
}
|
220
|
+
|
221
|
+
.saved-box div {
|
222
|
+
cursor: pointer;
|
223
|
+
border-top: 1px solid #eeeeee;
|
224
|
+
}
|
225
|
+
|
226
|
+
.saved-box div:hover {
|
227
|
+
background: #eee;
|
228
|
+
}
|
229
|
+
|
230
|
+
.cm-editor.cm-focused {
|
231
|
+
outline: none !important;
|
232
|
+
}
|
233
|
+
|
234
|
+
.schemas, .tables {
|
235
|
+
border: none;
|
236
|
+
display: flex;
|
237
|
+
min-width: 200px;
|
238
|
+
}
|
239
|
+
|
240
|
+
.table-info {
|
241
|
+
display: grid;
|
242
|
+
grid-template-columns: 1;
|
243
|
+
grid-template-rows: 0.5fr 0.5fr;
|
244
|
+
justify-items: stretch;
|
245
|
+
flex: 1;
|
246
|
+
}
|
247
|
+
|
248
|
+
.columns {
|
249
|
+
border-bottom: 1px solid #ddd;
|
250
|
+
overflow: auto;
|
251
|
+
grid-column: 1;
|
252
|
+
grid-row: 1;
|
253
|
+
}
|
254
|
+
|
255
|
+
.indexes {
|
256
|
+
overflow: auto;
|
257
|
+
grid-column: 1;
|
258
|
+
grid-row: 2;
|
259
|
+
}
|
260
|
+
|
261
|
+
select {
|
262
|
+
outline: none;
|
263
|
+
}
|
data/client/resources/sqlui.html
CHANGED
@@ -8,9 +8,13 @@
|
|
8
8
|
</head>
|
9
9
|
|
10
10
|
<body>
|
11
|
-
<div class="
|
11
|
+
<div id="loading-box" class="loading-box">
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div id="main-box" class="main-box" style="display:none">
|
12
15
|
<div class="tabs-box">
|
13
|
-
<div
|
16
|
+
<div class="header">SQLUI</div>
|
17
|
+
<div id="server-name" class="server-name"></div>
|
14
18
|
<input id="query-tab-button" class="tab-button" type="button" value="Query" onclick="sqlui.selectTab('query')"></input>
|
15
19
|
<input id="graph-tab-button" class="tab-button" type="button" value="Graph" onclick="sqlui.selectTab('graph')"></input>
|
16
20
|
<input id="saved-tab-button" class="tab-button" type="button" value="Saved" onclick="sqlui.selectTab('saved')"></input>
|
@@ -21,11 +25,10 @@
|
|
21
25
|
<div id="query"></div>
|
22
26
|
</div>
|
23
27
|
|
24
|
-
<!--
|
25
28
|
<div id="submit-box" class="submit-box tab-content-element graph-element query-element" style="display: none;">
|
26
|
-
<input id="submit-button" class="submit-button" type="button" value="
|
29
|
+
<input id="submit-all-button" class="submit-button" type="button" value="execute (ctrl-shift-enter)" onclick="sqlui.submitAll();"></input>
|
30
|
+
<input id="submit-current-button" class="submit-button" type="button" value="execute selection (ctrl-enter)" onclick="sqlui.submitCurrent();"></input>
|
27
31
|
</div>
|
28
|
-
-->
|
29
32
|
|
30
33
|
<div id="result-box" class="result-box tab-content-element query-element" style="display: none;">
|
31
34
|
</div>
|