sqlui 0.1.14 → 0.1.16
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/environment.rb +20 -0
- data/app/server.rb +3 -1
- data/app/sqlui.rb +65 -64
- data/app/views/databases.erb +65 -0
- data/client/resources/sqlui.css +242 -0
- data/client/resources/sqlui.html +60 -0
- data/client/resources/sqlui.js +24458 -0
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7c438724dd4d58432da2908650a10bb82c3452a2faf4f0560c5dd23a8fd6d66
|
4
|
+
data.tar.gz: 949a64296520f3e7fcfec48ae1a4cc8b4a4056184d88a88ca52b5813b1ad8064
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab6348bdcbeb1b2dfd7e38c206b7d72bf388f28ae992e15c40823b00db1bc3bdfb705cc3764373ca0f1de053b32ea546f84bdacf99328b5b6def0f9aea40954e
|
7
|
+
data.tar.gz: a55564765cd9c90fe47ef06d521aefde3bc78f903527cd4a49b6817267364ff6cd609d47b645a9508c382fcc1798b355b414b12ea861782038b538dbaa90a861
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.16
|
data/app/environment.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
class Environment
|
2
|
+
SERVER_ENV = ENV.fetch('SERVER_ENV', 'development').to_sym
|
3
|
+
SERVER_PORT = ENV.fetch('SERVER_PORT', 8080)
|
4
|
+
|
5
|
+
def self.server_env
|
6
|
+
SERVER_ENV
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.development?
|
10
|
+
SERVER_ENV == :development
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.production?
|
14
|
+
SERVER_ENV == :production
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.server_port
|
18
|
+
SERVER_PORT
|
19
|
+
end
|
20
|
+
end
|
data/app/server.rb
CHANGED
@@ -4,6 +4,7 @@ require 'mysql2'
|
|
4
4
|
require 'sinatra/base'
|
5
5
|
require_relative 'sqlui'
|
6
6
|
require 'yaml'
|
7
|
+
require_relative 'environment'
|
7
8
|
|
8
9
|
if ARGV.include?('-v') || ARGV.include?('--version')
|
9
10
|
puts File.read('.version')
|
@@ -16,7 +17,8 @@ raise "configuration file does not exist" unless File.exist?(ARGV[0])
|
|
16
17
|
class Server < Sinatra::Base
|
17
18
|
set :logging, true
|
18
19
|
set :bind, '0.0.0.0'
|
19
|
-
set :port,
|
20
|
+
set :port, Environment.server_port
|
21
|
+
set :env, Environment.server_env
|
20
22
|
|
21
23
|
class Client
|
22
24
|
def initialize(params)
|
data/app/sqlui.rb
CHANGED
@@ -11,7 +11,7 @@ class SQLUI
|
|
11
11
|
@name = name
|
12
12
|
@saved_path = saved_path
|
13
13
|
@max_rows = max_rows
|
14
|
-
@resources_dir = File.join(File.expand_path('..', File.dirname(__FILE__)), 'resources')
|
14
|
+
@resources_dir = File.join(File.expand_path('..', File.dirname(__FILE__)), 'client', 'resources')
|
15
15
|
end
|
16
16
|
|
17
17
|
def get(params)
|
@@ -78,25 +78,6 @@ class SQLUI
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def load_metadata
|
81
|
-
if @table_schema
|
82
|
-
where_clause = "where table_schema = '#{@table_schema}'"
|
83
|
-
else
|
84
|
-
where_clause = "where table_schema not in('mysql', 'sys')"
|
85
|
-
end
|
86
|
-
stats_result = @client.query(
|
87
|
-
<<~SQL
|
88
|
-
select
|
89
|
-
table_schema,
|
90
|
-
table_name,
|
91
|
-
index_name,
|
92
|
-
seq_in_index,
|
93
|
-
non_unique,
|
94
|
-
column_name
|
95
|
-
from information_schema.statistics
|
96
|
-
#{where_clause}
|
97
|
-
order by table_schema, table_name, if(index_name = "PRIMARY", 0, index_name), seq_in_index;
|
98
|
-
SQL
|
99
|
-
)
|
100
81
|
result = {
|
101
82
|
server: @name,
|
102
83
|
schemas: {},
|
@@ -107,40 +88,11 @@ class SQLUI
|
|
107
88
|
}
|
108
89
|
end
|
109
90
|
}
|
110
|
-
stats_result.each do |row|
|
111
|
-
table_schema = row['table_schema']
|
112
|
-
unless result[:schemas][table_schema]
|
113
|
-
result[:schemas][table_schema] = {
|
114
|
-
tables: {}
|
115
|
-
}
|
116
|
-
end
|
117
|
-
tables = result[:schemas][table_schema][:tables]
|
118
|
-
table_name = row['table_name']
|
119
|
-
unless tables[table_name]
|
120
|
-
tables[table_name] = {
|
121
|
-
indexes: {},
|
122
|
-
columns: {}
|
123
|
-
}
|
124
|
-
end
|
125
|
-
indexes = tables[table_name][:indexes]
|
126
|
-
index_name = row['index_name']
|
127
|
-
unless indexes[index_name]
|
128
|
-
indexes[index_name] = {}
|
129
|
-
end
|
130
|
-
index = indexes[index_name]
|
131
|
-
column_name = row['column_name']
|
132
|
-
index[column_name] = {}
|
133
|
-
column = index[column_name]
|
134
|
-
column[:name] = index_name
|
135
|
-
column[:seq_in_index] = row['seq_in_index']
|
136
|
-
column[:non_unique] = row['non_unique']
|
137
|
-
column[:column_name] = row['column_name']
|
138
|
-
end
|
139
91
|
|
140
92
|
if @table_schema
|
141
93
|
where_clause = "where table_schema = '#{@table_schema}'"
|
142
94
|
else
|
143
|
-
where_clause = "where table_schema not in('
|
95
|
+
where_clause = "where table_schema not in('mysql', 'sys', 'information_schema', 'performance_schema')"
|
144
96
|
end
|
145
97
|
column_result = @client.query(
|
146
98
|
<<~SQL
|
@@ -155,29 +107,78 @@ class SQLUI
|
|
155
107
|
column_default,
|
156
108
|
extra
|
157
109
|
from information_schema.columns
|
158
|
-
|
110
|
+
#{where_clause}
|
159
111
|
order by table_schema, table_name, column_name, ordinal_position;
|
160
|
-
|
112
|
+
SQL
|
161
113
|
)
|
162
114
|
column_result.each do |row|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
115
|
+
row = row.transform_keys(&:downcase).transform_keys(&:to_sym)
|
116
|
+
table_schema = row[:table_schema]
|
117
|
+
unless result[:schemas][table_schema]
|
118
|
+
result[:schemas][table_schema] = {
|
119
|
+
tables: {}
|
120
|
+
}
|
121
|
+
end
|
122
|
+
table_name = row[:table_name]
|
123
|
+
tables = result[:schemas][table_schema][:tables]
|
124
|
+
unless tables[table_name]
|
125
|
+
tables[table_name] = {
|
126
|
+
indexes: {},
|
127
|
+
columns: {}
|
128
|
+
}
|
129
|
+
end
|
169
130
|
columns = result[:schemas][table_schema][:tables][table_name][:columns]
|
131
|
+
column_name = row[:column_name]
|
170
132
|
unless columns[column_name]
|
171
133
|
columns[column_name] = {}
|
172
134
|
end
|
173
135
|
column = columns[column_name]
|
174
136
|
column[:name] = column_name
|
175
|
-
column[:data_type] = row[
|
176
|
-
column[:length] = row[
|
177
|
-
column[:allow_null] = row[
|
178
|
-
column[:key] = row[
|
179
|
-
column[:default] = row[
|
180
|
-
column[:extra] = row[
|
137
|
+
column[:data_type] = row[:data_type]
|
138
|
+
column[:length] = row[:character_maximum_length]
|
139
|
+
column[:allow_null] = row[:is_nullable]
|
140
|
+
column[:key] = row[:column_key]
|
141
|
+
column[:default] = row[:column_default]
|
142
|
+
column[:extra] = row[:extra]
|
143
|
+
end
|
144
|
+
|
145
|
+
if @table_schema
|
146
|
+
where_clause = "where table_schema = '#{@table_schema}'"
|
147
|
+
else
|
148
|
+
where_clause = "where table_schema not in('mysql', 'sys', 'information_schema', 'performance_schema')"
|
149
|
+
end
|
150
|
+
stats_result = @client.query(
|
151
|
+
<<~SQL
|
152
|
+
select
|
153
|
+
table_schema,
|
154
|
+
table_name,
|
155
|
+
index_name,
|
156
|
+
seq_in_index,
|
157
|
+
non_unique,
|
158
|
+
column_name
|
159
|
+
from information_schema.statistics
|
160
|
+
#{where_clause}
|
161
|
+
order by table_schema, table_name, if(index_name = "PRIMARY", 0, index_name), seq_in_index;
|
162
|
+
SQL
|
163
|
+
)
|
164
|
+
stats_result.each do |row|
|
165
|
+
row = row.transform_keys(&:downcase).transform_keys(&:to_sym)
|
166
|
+
table_schema = row[:table_schema]
|
167
|
+
tables = result[:schemas][table_schema][:tables]
|
168
|
+
table_name = row[:table_name]
|
169
|
+
indexes = tables[table_name][:indexes]
|
170
|
+
index_name = row[:index_name]
|
171
|
+
unless indexes[index_name]
|
172
|
+
indexes[index_name] = {}
|
173
|
+
end
|
174
|
+
index = indexes[index_name]
|
175
|
+
column_name = row[:column_name]
|
176
|
+
index[column_name] = {}
|
177
|
+
column = index[column_name]
|
178
|
+
column[:name] = index_name
|
179
|
+
column[:seq_in_index] = row[:seq_in_index]
|
180
|
+
column[:non_unique] = row[:non_unique]
|
181
|
+
column[:column_name] = row[:column_name]
|
181
182
|
end
|
182
183
|
|
183
184
|
result
|
@@ -0,0 +1,65 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Databases</title>
|
4
|
+
|
5
|
+
<style>
|
6
|
+
body {
|
7
|
+
font-family: Helvetica;
|
8
|
+
}
|
9
|
+
|
10
|
+
h1 {
|
11
|
+
font-size: 30px;
|
12
|
+
margin-bottom: 30px;
|
13
|
+
}
|
14
|
+
|
15
|
+
.database a {
|
16
|
+
margin-right: 10px;
|
17
|
+
color: darkblue;
|
18
|
+
font-size: 16px;
|
19
|
+
}
|
20
|
+
|
21
|
+
.database h2 {
|
22
|
+
margin: 0px;
|
23
|
+
margin-top: 10px;
|
24
|
+
margin-bottom: 0px;
|
25
|
+
font-size: 20px;
|
26
|
+
font-weight: bold;
|
27
|
+
}
|
28
|
+
|
29
|
+
.database p {
|
30
|
+
margin: 0px;
|
31
|
+
margin-top: 10px;
|
32
|
+
padding-bottom: 20px;
|
33
|
+
font-size: 16px;
|
34
|
+
}
|
35
|
+
|
36
|
+
.database {
|
37
|
+
cursor: pointer;
|
38
|
+
border-bottom: 1px solid #eeeeee;
|
39
|
+
}
|
40
|
+
|
41
|
+
.database:last-child {
|
42
|
+
border-bottom: none;
|
43
|
+
}
|
44
|
+
|
45
|
+
.database:hover {
|
46
|
+
background: #eee;
|
47
|
+
}
|
48
|
+
</style>
|
49
|
+
</head>
|
50
|
+
|
51
|
+
<body>
|
52
|
+
<h1>Databases</h1>
|
53
|
+
<% databases.values.each do |database| %>
|
54
|
+
<div class="database" onclick="window.location='<%= "/db/#{database['url_path']}/app" %>'">
|
55
|
+
<h2><%= database['name'] %></h2>
|
56
|
+
<a href="/db/<%= database['url_path'] %>/app">query</a>
|
57
|
+
<a href="/db/<%= database['url_path'] %>/app?tab=saved">saved</a>
|
58
|
+
<a href="/db/<%= database['url_path'] %>/app?tab=structure">structure</a>
|
59
|
+
<p>
|
60
|
+
<%= database['description'] %>
|
61
|
+
</p>
|
62
|
+
</div>
|
63
|
+
<% end %>
|
64
|
+
</body>
|
65
|
+
</html>
|
@@ -0,0 +1,242 @@
|
|
1
|
+
body {
|
2
|
+
font-size: 16px;
|
3
|
+
margin: 0;
|
4
|
+
}
|
5
|
+
|
6
|
+
.main-box {
|
7
|
+
display: flex;
|
8
|
+
flex-direction: column;
|
9
|
+
flex: 1;
|
10
|
+
margin: 0;
|
11
|
+
height: 100%;
|
12
|
+
min-height: 100%;
|
13
|
+
}
|
14
|
+
|
15
|
+
.header {
|
16
|
+
display: flex;
|
17
|
+
flex: 1;
|
18
|
+
align-items: center;
|
19
|
+
justify-content: start;
|
20
|
+
padding-left: 5px;
|
21
|
+
color: #333;
|
22
|
+
font-weight: bold;
|
23
|
+
}
|
24
|
+
|
25
|
+
.tabs-box {
|
26
|
+
display: flex;
|
27
|
+
flex-direction: row;
|
28
|
+
border-bottom: 1px solid #ddd;
|
29
|
+
height: 36px;
|
30
|
+
font-size: 16px;
|
31
|
+
font-family: Helvetica;
|
32
|
+
}
|
33
|
+
|
34
|
+
.tab-button, .selected-tab-button {
|
35
|
+
border: none;
|
36
|
+
outline: none;
|
37
|
+
cursor: pointer;
|
38
|
+
width: 150px;
|
39
|
+
padding: 2px;
|
40
|
+
margin: 0px;
|
41
|
+
display: flex;
|
42
|
+
justify-content: center;
|
43
|
+
background-color: #fff;
|
44
|
+
}
|
45
|
+
|
46
|
+
.tab-button {
|
47
|
+
color: #888;
|
48
|
+
}
|
49
|
+
|
50
|
+
.tab-button:hover {
|
51
|
+
color: #333;
|
52
|
+
}
|
53
|
+
|
54
|
+
.selected-tab-button {
|
55
|
+
color: #333;
|
56
|
+
font-weight: bold;
|
57
|
+
}
|
58
|
+
|
59
|
+
.tab-content-element {
|
60
|
+
display: none;
|
61
|
+
}
|
62
|
+
|
63
|
+
.query-box {
|
64
|
+
display: flex;
|
65
|
+
flex-direction: column;
|
66
|
+
}
|
67
|
+
|
68
|
+
.query {
|
69
|
+
display: flex;
|
70
|
+
flex: 1;
|
71
|
+
}
|
72
|
+
|
73
|
+
.submit-box {
|
74
|
+
display: flex;
|
75
|
+
border-top: 1px solid #ddd;
|
76
|
+
height: 36px;
|
77
|
+
justify-content: right;
|
78
|
+
}
|
79
|
+
|
80
|
+
.status {
|
81
|
+
display: flex;
|
82
|
+
justify-content: center;
|
83
|
+
align-content: center;
|
84
|
+
flex-direction: column;
|
85
|
+
font-family: Helvetica;
|
86
|
+
}
|
87
|
+
|
88
|
+
.result-box, .saved-box, .graph-box, .structure-box {
|
89
|
+
flex: 1;
|
90
|
+
overflow: auto;
|
91
|
+
display: flex;
|
92
|
+
flex-direction: column;
|
93
|
+
}
|
94
|
+
|
95
|
+
.graph-box, .result-box {
|
96
|
+
border-top: 1px solid #ddd;
|
97
|
+
}
|
98
|
+
|
99
|
+
.graph-box {
|
100
|
+
padding: 20px;
|
101
|
+
}
|
102
|
+
|
103
|
+
table {
|
104
|
+
font-family: monospace;
|
105
|
+
flex: 1;
|
106
|
+
border-spacing: 0px;
|
107
|
+
display: flex;
|
108
|
+
width: 100%;
|
109
|
+
}
|
110
|
+
|
111
|
+
table td:last-child, table th:last-child {
|
112
|
+
width: 100%;
|
113
|
+
border-right: none !important;
|
114
|
+
}
|
115
|
+
|
116
|
+
td, th {
|
117
|
+
padding: 5px 20px 5px 5px;
|
118
|
+
font-weight: normal;
|
119
|
+
white-space: nowrap;
|
120
|
+
max-width: 500px;
|
121
|
+
overflow: hidden;
|
122
|
+
text-overflow: ellipsis;
|
123
|
+
}
|
124
|
+
|
125
|
+
td {
|
126
|
+
text-align: right;
|
127
|
+
}
|
128
|
+
|
129
|
+
th {
|
130
|
+
text-align: left;
|
131
|
+
font-weight: bold;
|
132
|
+
border-bottom: 1px solid #ddd;
|
133
|
+
border-right: 1px solid #ddd;
|
134
|
+
}
|
135
|
+
|
136
|
+
table {
|
137
|
+
display: block;
|
138
|
+
}
|
139
|
+
|
140
|
+
thead {
|
141
|
+
padding: 0px;
|
142
|
+
background-color: #fff;
|
143
|
+
position: -webkit-sticky;
|
144
|
+
position: sticky;
|
145
|
+
top: 0px;
|
146
|
+
z-index: 100;
|
147
|
+
table-layout:fixed;
|
148
|
+
}
|
149
|
+
|
150
|
+
.highlighted-row {
|
151
|
+
background: #eee;
|
152
|
+
}
|
153
|
+
|
154
|
+
.status-box {
|
155
|
+
padding: 5px;
|
156
|
+
display: flex;
|
157
|
+
flex-direction: rows;
|
158
|
+
justify-content: space-between;
|
159
|
+
border-top: 1px solid #ddd;
|
160
|
+
height: 30px;
|
161
|
+
}
|
162
|
+
|
163
|
+
.CodeMirror pre.CodeMirror-placeholder {
|
164
|
+
color: #999;
|
165
|
+
}
|
166
|
+
|
167
|
+
.tabs-box {
|
168
|
+
display: flex;
|
169
|
+
}
|
170
|
+
|
171
|
+
.saved-box {
|
172
|
+
font-family: Helvetica;
|
173
|
+
}
|
174
|
+
|
175
|
+
.saved-box h1 {
|
176
|
+
margin: 0px;
|
177
|
+
padding-left: 10px;
|
178
|
+
padding-right: 10px;
|
179
|
+
padding-top: 10px;
|
180
|
+
padding-bottom: 10px;
|
181
|
+
font-size: 16px;
|
182
|
+
font-weight: bold;
|
183
|
+
}
|
184
|
+
|
185
|
+
.saved-box div:first-child {
|
186
|
+
border-top: none !important;
|
187
|
+
}
|
188
|
+
|
189
|
+
.saved-box p {
|
190
|
+
margin: 0px;
|
191
|
+
padding-left: 20px;
|
192
|
+
padding-bottom: 20px;
|
193
|
+
}
|
194
|
+
|
195
|
+
.saved-box div {
|
196
|
+
cursor: pointer;
|
197
|
+
border-top: 1px solid #eeeeee;
|
198
|
+
}
|
199
|
+
|
200
|
+
.saved-box div:hover {
|
201
|
+
background: #eee;
|
202
|
+
}
|
203
|
+
|
204
|
+
.submit-button {
|
205
|
+
cursor: pointer;
|
206
|
+
margin-right: 10px;
|
207
|
+
}
|
208
|
+
|
209
|
+
.cm-editor.cm-focused {
|
210
|
+
outline: none !important;
|
211
|
+
}
|
212
|
+
|
213
|
+
.schemas, .tables {
|
214
|
+
border: none;
|
215
|
+
display: flex;
|
216
|
+
min-width: 200px;
|
217
|
+
}
|
218
|
+
|
219
|
+
.table-info {
|
220
|
+
display: grid;
|
221
|
+
grid-template-columns: 1;
|
222
|
+
grid-template-rows: 0.5fr 0.5fr;
|
223
|
+
justify-items: stretch;
|
224
|
+
flex: 1;
|
225
|
+
}
|
226
|
+
|
227
|
+
.columns {
|
228
|
+
border-bottom: 1px solid #ddd;
|
229
|
+
overflow: auto;
|
230
|
+
grid-column: 1;
|
231
|
+
grid-row: 1;
|
232
|
+
}
|
233
|
+
|
234
|
+
.indexes {
|
235
|
+
overflow: auto;
|
236
|
+
grid-column: 1;
|
237
|
+
grid-row: 2;
|
238
|
+
}
|
239
|
+
|
240
|
+
select {
|
241
|
+
outline: none;
|
242
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<head>
|
2
|
+
|
3
|
+
<title>SQLUI</title>
|
4
|
+
|
5
|
+
<script src="sqlui.js"></script>
|
6
|
+
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
7
|
+
<link rel="stylesheet" href="sqlui.css">
|
8
|
+
</head>
|
9
|
+
|
10
|
+
<body>
|
11
|
+
<div class="main-box">
|
12
|
+
<div class="tabs-box">
|
13
|
+
<div id="header" class="header">SQLUI</div>
|
14
|
+
<input id="query-tab-button" class="tab-button" type="button" value="Query" onclick="sqlui.selectTab('query')"></input>
|
15
|
+
<input id="graph-tab-button" class="tab-button" type="button" value="Graph" onclick="sqlui.selectTab('graph')"></input>
|
16
|
+
<input id="saved-tab-button" class="tab-button" type="button" value="Saved" onclick="sqlui.selectTab('saved')"></input>
|
17
|
+
<input id="structure-tab-button" class="tab-button" type="button" value="Structure" onclick="sqlui.selectTab('structure')"></input>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div id="query-box" class="query-box tab-content-element graph-element query-element" style="display: none;">
|
21
|
+
<div id="query"></div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<!--
|
25
|
+
<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="submit" onclick="submit();"></input>
|
27
|
+
</div>
|
28
|
+
-->
|
29
|
+
|
30
|
+
<div id="result-box" class="result-box tab-content-element query-element" style="display: none;">
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div id="graph-box" class="graph-box tab-content-element graph-element" style="display: none;">
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<div id="saved-box" class="saved-box tab-content-element saved-element" style="display: none;">
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<div id="structure-box" class="structure-box tab-content-element structure-element" style="display: none;">
|
40
|
+
<div style="display: flex; flex: 1; flex-direction: row; align-items: stretch;">
|
41
|
+
<select id="schemas" class="schemas" size="4">
|
42
|
+
</select>
|
43
|
+
<select id="tables" class="tables" size="4">
|
44
|
+
</select>
|
45
|
+
<div id="table-info" class="table-info">
|
46
|
+
<div id="columns" class="columns">
|
47
|
+
</div>
|
48
|
+
<div id="indexes" class="indexes">
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</div>
|
53
|
+
|
54
|
+
<div id="status-box" class="status-box">
|
55
|
+
<div id="query-status" class="status tab-content-element query-element"></div>
|
56
|
+
<div id="graph-status" class="status tab-content-element graph-element"></div>
|
57
|
+
<div id="saved-status" class="status tab-content-element saved-element"></div>
|
58
|
+
</div>
|
59
|
+
</div>
|
60
|
+
</body>
|