drixio 1.1.7 → 1.1.8
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.
- package/dist/cli.js +1067 -254
- package/dist/studio/assets/index-Dos1aeov.css +1 -0
- package/dist/studio/assets/index-DsIbymPZ.js +1228 -0
- package/dist/studio/index.html +3 -3
- package/package.json +1 -1
- package/dist/studio/assets/index-CM4X0SRj.css +0 -1
- package/dist/studio/assets/index-dfKs6wCl.js +0 -497
|
@@ -0,0 +1,1228 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`
|
|
2
|
+
<header>
|
|
3
|
+
<div id="current-path" aria-label="Breadcrumbs">
|
|
4
|
+
<div class="breadcrumb-item db-crumb">
|
|
5
|
+
<span class="material-symbols-outlined breadcrumb-icon">database</span>
|
|
6
|
+
<span id="db-name">database</span>
|
|
7
|
+
</div>
|
|
8
|
+
<span id="slash" class="material-symbols-outlined breadcrumb-separator">chevron_right</span>
|
|
9
|
+
<div id="current-table" class="breadcrumb-item table-crumb">
|
|
10
|
+
<span class="material-symbols-outlined" id="table-icon">table_chart</span>
|
|
11
|
+
<span id="table-name">users</span>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="header-actions">
|
|
16
|
+
<!-- Import Dropdown -->
|
|
17
|
+
<div class="header-dropdown-wrap" style="position: relative;">
|
|
18
|
+
<button id="import-btn" class="header-btn secondary" title="Import data, ERD layout or database">
|
|
19
|
+
<span id="import-icon" class="material-symbols-outlined">upload</span>
|
|
20
|
+
<span>Import</span>
|
|
21
|
+
<span id="import-arrow-icon" class="material-symbols-outlined">arrow_drop_down</span>
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
<ul id="import-dropdown" class="dropdown-menu hidden" role="menu">
|
|
25
|
+
<li class="dropdown-submenu">
|
|
26
|
+
<a class="dropdown-item">
|
|
27
|
+
<span class="material-symbols-outlined">table_chart</span>
|
|
28
|
+
<span class="dropdown-item-label">Data</span>
|
|
29
|
+
<span class="caret material-symbols-outlined">chevron_right</span>
|
|
30
|
+
</a>
|
|
31
|
+
<ul class="dropdown-menu sub-menu">
|
|
32
|
+
<li>
|
|
33
|
+
<button id="import-data-csv-btn" class="dropdown-item">
|
|
34
|
+
<span class="material-symbols-outlined">description</span>
|
|
35
|
+
<span class="dropdown-item-label">Table Records</span>
|
|
36
|
+
<span class="dropdown-badge">CSV</span>
|
|
37
|
+
</button>
|
|
38
|
+
</li>
|
|
39
|
+
<li>
|
|
40
|
+
<button id="import-data-json-btn" class="dropdown-item">
|
|
41
|
+
<span class="material-symbols-outlined">data_object</span>
|
|
42
|
+
<span class="dropdown-item-label">Table Records</span>
|
|
43
|
+
<span class="dropdown-badge">JSON</span>
|
|
44
|
+
</button>
|
|
45
|
+
</li>
|
|
46
|
+
</ul>
|
|
47
|
+
</li>
|
|
48
|
+
|
|
49
|
+
<li class="dropdown-submenu">
|
|
50
|
+
<a class="dropdown-item">
|
|
51
|
+
<span class="material-symbols-outlined">account_tree</span>
|
|
52
|
+
<span class="dropdown-item-label">ERD</span>
|
|
53
|
+
<span class="caret material-symbols-outlined">chevron_right</span>
|
|
54
|
+
</a>
|
|
55
|
+
<ul class="dropdown-menu sub-menu">
|
|
56
|
+
<li>
|
|
57
|
+
<button id="import-erd-json-btn" class="dropdown-item">
|
|
58
|
+
<span class="material-symbols-outlined">layers</span>
|
|
59
|
+
<span class="dropdown-item-label">Canvas Layout</span>
|
|
60
|
+
<span class="dropdown-badge">JSON</span>
|
|
61
|
+
</button>
|
|
62
|
+
</li>
|
|
63
|
+
</ul>
|
|
64
|
+
</li>
|
|
65
|
+
|
|
66
|
+
<li class="dropdown-divider"></li>
|
|
67
|
+
|
|
68
|
+
<li>
|
|
69
|
+
<button id="import-db-sql-btn" class="dropdown-item danger">
|
|
70
|
+
<span class="material-symbols-outlined">database</span>
|
|
71
|
+
<span class="dropdown-item-label">Restore Database</span>
|
|
72
|
+
<span class="dropdown-badge danger">SQL</span>
|
|
73
|
+
</button>
|
|
74
|
+
</li>
|
|
75
|
+
</ul>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<!-- Export Dropdown -->
|
|
79
|
+
<div class="header-dropdown-wrap" style="position: relative;">
|
|
80
|
+
<button id="export-btn" class="header-btn primary" title="Export database or table data">
|
|
81
|
+
<span id="export-icon" class="material-symbols-outlined">download</span>
|
|
82
|
+
<span>Export</span>
|
|
83
|
+
<span id="export-arrow-icon" class="material-symbols-outlined">arrow_drop_down</span>
|
|
84
|
+
</button>
|
|
85
|
+
|
|
86
|
+
<ul id="export-dropdown" class="dropdown-menu hidden" role="menu">
|
|
87
|
+
<li class="dropdown-submenu">
|
|
88
|
+
<a class="dropdown-item">
|
|
89
|
+
<span class="material-symbols-outlined">table_chart</span>
|
|
90
|
+
<span class="dropdown-item-label">Data</span>
|
|
91
|
+
<span class="caret material-symbols-outlined">chevron_right</span>
|
|
92
|
+
</a>
|
|
93
|
+
<ul class="dropdown-menu sub-menu">
|
|
94
|
+
<li>
|
|
95
|
+
<button id="export-data-csv-btn" class="dropdown-item">
|
|
96
|
+
<span class="material-symbols-outlined">description</span>
|
|
97
|
+
<span class="dropdown-item-label">Table Records</span>
|
|
98
|
+
<span class="dropdown-badge">CSV</span>
|
|
99
|
+
</button>
|
|
100
|
+
</li>
|
|
101
|
+
<li>
|
|
102
|
+
<button id="export-data-json-btn" class="dropdown-item">
|
|
103
|
+
<span class="material-symbols-outlined">data_object</span>
|
|
104
|
+
<span class="dropdown-item-label">Table Records</span>
|
|
105
|
+
<span class="dropdown-badge">JSON</span>
|
|
106
|
+
</button>
|
|
107
|
+
</li>
|
|
108
|
+
<li class="data-tab-only" style="display: none;">
|
|
109
|
+
<button id="export-data-filtered-csv-btn" class="dropdown-item" style="color: var(--color-primary);">
|
|
110
|
+
<span class="material-symbols-outlined" style="color: var(--color-primary);">filter_alt</span>
|
|
111
|
+
<span class="dropdown-item-label">Filtered Records</span>
|
|
112
|
+
<span class="dropdown-badge" style="color: var(--color-primary); border-color: rgba(59, 130, 246, 0.3);">CSV</span>
|
|
113
|
+
</button>
|
|
114
|
+
</li>
|
|
115
|
+
<li class="data-tab-only" style="display: none;">
|
|
116
|
+
<button id="export-data-filtered-json-btn" class="dropdown-item" style="color: var(--color-primary);">
|
|
117
|
+
<span class="material-symbols-outlined" style="color: var(--color-primary);">filter_alt</span>
|
|
118
|
+
<span class="dropdown-item-label">Filtered Records</span>
|
|
119
|
+
<span class="dropdown-badge" style="color: var(--color-primary); border-color: rgba(59, 130, 246, 0.3);">JSON</span>
|
|
120
|
+
</button>
|
|
121
|
+
</li>
|
|
122
|
+
</ul>
|
|
123
|
+
</li>
|
|
124
|
+
|
|
125
|
+
<li class="dropdown-submenu">
|
|
126
|
+
<a class="dropdown-item">
|
|
127
|
+
<span class="material-symbols-outlined">schema</span>
|
|
128
|
+
<span class="dropdown-item-label">Schema</span>
|
|
129
|
+
<span class="caret material-symbols-outlined">chevron_right</span>
|
|
130
|
+
</a>
|
|
131
|
+
<ul class="dropdown-menu sub-menu">
|
|
132
|
+
<li>
|
|
133
|
+
<button id="export-schema-dict-btn" class="dropdown-item">
|
|
134
|
+
<span class="material-symbols-outlined">menu_book</span>
|
|
135
|
+
<span class="dropdown-item-label">Data Dictionary</span>
|
|
136
|
+
<span class="dropdown-badge">MD</span>
|
|
137
|
+
</button>
|
|
138
|
+
</li>
|
|
139
|
+
<li>
|
|
140
|
+
<button id="export-schema-sql-btn" class="dropdown-item">
|
|
141
|
+
<span class="material-symbols-outlined">code</span>
|
|
142
|
+
<span class="dropdown-item-label">DDL Schema Only</span>
|
|
143
|
+
<span class="dropdown-badge">SQL</span>
|
|
144
|
+
</button>
|
|
145
|
+
</li>
|
|
146
|
+
</ul>
|
|
147
|
+
</li>
|
|
148
|
+
|
|
149
|
+
<li class="dropdown-submenu console-tab-only" style="display: none;">
|
|
150
|
+
<a class="dropdown-item">
|
|
151
|
+
<span class="material-symbols-outlined">terminal</span>
|
|
152
|
+
<span class="dropdown-item-label">SQL Console</span>
|
|
153
|
+
<span class="caret material-symbols-outlined">chevron_right</span>
|
|
154
|
+
</a>
|
|
155
|
+
<ul class="dropdown-menu sub-menu">
|
|
156
|
+
<li>
|
|
157
|
+
<button id="export-console-csv-btn" class="dropdown-item">
|
|
158
|
+
<span class="material-symbols-outlined">description</span>
|
|
159
|
+
<span class="dropdown-item-label">Query Result</span>
|
|
160
|
+
<span class="dropdown-badge">CSV</span>
|
|
161
|
+
</button>
|
|
162
|
+
</li>
|
|
163
|
+
<li>
|
|
164
|
+
<button id="export-console-json-btn" class="dropdown-item">
|
|
165
|
+
<span class="material-symbols-outlined">data_object</span>
|
|
166
|
+
<span class="dropdown-item-label">Query Result</span>
|
|
167
|
+
<span class="dropdown-badge">JSON</span>
|
|
168
|
+
</button>
|
|
169
|
+
</li>
|
|
170
|
+
</ul>
|
|
171
|
+
</li>
|
|
172
|
+
|
|
173
|
+
<li class="dropdown-submenu">
|
|
174
|
+
<a class="dropdown-item">
|
|
175
|
+
<span class="material-symbols-outlined">account_tree</span>
|
|
176
|
+
<span class="dropdown-item-label">ERD</span>
|
|
177
|
+
<span class="caret material-symbols-outlined">chevron_right</span>
|
|
178
|
+
</a>
|
|
179
|
+
<ul class="dropdown-menu sub-menu">
|
|
180
|
+
<li>
|
|
181
|
+
<button id="export-erd-json-btn" class="dropdown-item">
|
|
182
|
+
<span class="material-symbols-outlined">layers</span>
|
|
183
|
+
<span class="dropdown-item-label">Canvas Layout</span>
|
|
184
|
+
<span class="dropdown-badge">JSON</span>
|
|
185
|
+
</button>
|
|
186
|
+
</li>
|
|
187
|
+
</ul>
|
|
188
|
+
</li>
|
|
189
|
+
|
|
190
|
+
<li class="dropdown-divider"></li>
|
|
191
|
+
|
|
192
|
+
<li>
|
|
193
|
+
<button id="export-db-sql-btn" class="dropdown-item">
|
|
194
|
+
<span class="material-symbols-outlined">database</span>
|
|
195
|
+
<span class="dropdown-item-label">Full Database</span>
|
|
196
|
+
<span class="dropdown-badge">SQL</span>
|
|
197
|
+
</button>
|
|
198
|
+
</li>
|
|
199
|
+
</ul>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</header>
|
|
203
|
+
`,t=`
|
|
204
|
+
<aside id="sidebar-panel">
|
|
205
|
+
<div id="sidebar-header">
|
|
206
|
+
<div class="brand-left">
|
|
207
|
+
<div class="brand-logo-badge">
|
|
208
|
+
<span class="material-symbols-outlined" id="database-icon">database</span>
|
|
209
|
+
</div>
|
|
210
|
+
<div class="brand-container">
|
|
211
|
+
<div class="brand-name">
|
|
212
|
+
<h1 id="brand-title">DRIXIO</h1>
|
|
213
|
+
<span id="brand-subtitle">STUDIO</span>
|
|
214
|
+
</div>
|
|
215
|
+
<span id="brand-by">By Horizora</span>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
<button id="sidebar-collapse-btn" class="sidebar-icon-btn" title="Toggle Sidebar Width" aria-label="Toggle Sidebar Width">
|
|
219
|
+
<span class="material-symbols-outlined" id="sidebar-collapse-icon">menu_open</span>
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div id="sidebar-content">
|
|
224
|
+
<div id="search-bar" role="search">
|
|
225
|
+
<span class="material-symbols-outlined" id="search-icon">search</span>
|
|
226
|
+
<input type="text" placeholder="Filter tables..." id="search-input" aria-label="Search tables" autocomplete="off" spellcheck="false" />
|
|
227
|
+
<button id="search-clear-btn" class="search-clear-btn hidden" title="Clear filter" aria-label="Clear filter">✕</button>
|
|
228
|
+
<kbd class="search-kbd" title="Shortcut">/</kbd>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div class="sidebar-section-header">
|
|
232
|
+
<div class="section-title-wrap">
|
|
233
|
+
<span class="section-title">TABLES</span>
|
|
234
|
+
<span id="table-count-badge" class="section-count">0</span>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="section-actions">
|
|
237
|
+
<button id="refresh-tables-btn" class="sidebar-icon-btn" title="Refresh Table List" aria-label="Refresh Table List">
|
|
238
|
+
<span class="material-symbols-outlined">refresh</span>
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<nav id="table-nav" aria-label="Database Tables">
|
|
244
|
+
<!-- Dynamically populated tables -->
|
|
245
|
+
</nav>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div id="sidebar-footer">
|
|
249
|
+
<div class="sidebar-db-status" title="Database Connection Status">
|
|
250
|
+
<span class="status-indicator-dot" id="sidebar-db-status-dot"></span>
|
|
251
|
+
<span id="brand-db-type">CONNECTING...</span>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div class="sidebar-footer-actions">
|
|
255
|
+
<button id="theme-toggle" class="theme-toggle-btn" title="Toggle Theme" aria-label="Toggle Theme">
|
|
256
|
+
<span class="material-symbols-outlined" id="theme-icon">dark_mode</span>
|
|
257
|
+
<span id="theme-text">Theme</span>
|
|
258
|
+
</button>
|
|
259
|
+
<span class="sidebar-version-tag" title="Drixio Studio Edition">v1.1.8</span>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</aside>
|
|
263
|
+
`,n=`
|
|
264
|
+
<nav id="tab-nav" role="tablist" aria-label="Views and tools">
|
|
265
|
+
<!-- Table Scoped Underline Tabs -->
|
|
266
|
+
<div class="table-tabs" role="tablist">
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
class="tab-btn isCurrentTab"
|
|
270
|
+
onclick="handleSwitchTab('data-btn')"
|
|
271
|
+
id="data-btn"
|
|
272
|
+
role="tab"
|
|
273
|
+
title="View table data records"
|
|
274
|
+
>
|
|
275
|
+
<span class="material-symbols-outlined tab-icon" id="data-icon">table_chart</span>
|
|
276
|
+
<span>Data</span>
|
|
277
|
+
</button>
|
|
278
|
+
<button
|
|
279
|
+
type="button"
|
|
280
|
+
class="tab-btn"
|
|
281
|
+
onclick="handleSwitchTab('schema-btn')"
|
|
282
|
+
id="schema-btn"
|
|
283
|
+
role="tab"
|
|
284
|
+
title="View and edit table schema"
|
|
285
|
+
>
|
|
286
|
+
<span class="material-symbols-outlined tab-icon" id="schema-icon">schema</span>
|
|
287
|
+
<span>Schema</span>
|
|
288
|
+
</button>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div class="flex-1"></div>
|
|
292
|
+
|
|
293
|
+
<!-- Global Database Tools (Clean flat buttons) -->
|
|
294
|
+
<div class="global-tools" role="toolbar" aria-label="Global tools">
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
class="tab-btn tool-btn"
|
|
298
|
+
onclick="handleSwitchTab('status-btn')"
|
|
299
|
+
id="status-btn"
|
|
300
|
+
role="tab"
|
|
301
|
+
title="Database metrics and health"
|
|
302
|
+
>
|
|
303
|
+
<span class="material-symbols-outlined tab-icon" id="status-icon">monitoring</span>
|
|
304
|
+
<span>Status</span>
|
|
305
|
+
</button>
|
|
306
|
+
<button
|
|
307
|
+
type="button"
|
|
308
|
+
class="tab-btn tool-btn"
|
|
309
|
+
onclick="handleSwitchTab('erd-btn')"
|
|
310
|
+
id="erd-btn"
|
|
311
|
+
role="tab"
|
|
312
|
+
title="Entity relationship diagram"
|
|
313
|
+
>
|
|
314
|
+
<span class="material-symbols-outlined tab-icon" id="erd-icon">account_tree</span>
|
|
315
|
+
<span>ERD</span>
|
|
316
|
+
</button>
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
class="tab-btn tool-btn"
|
|
320
|
+
onclick="handleSwitchTab('sql-btn')"
|
|
321
|
+
id="sql-btn"
|
|
322
|
+
role="tab"
|
|
323
|
+
title="Execute SQL queries"
|
|
324
|
+
>
|
|
325
|
+
<span class="material-symbols-outlined tab-icon" id="sql-icon">terminal</span>
|
|
326
|
+
<span>Console</span>
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
</nav>
|
|
330
|
+
`;async function r(){return await(await fetch(`/api/tables`)).json()}async function i(){return await(await fetch(`/api/config`)).json()}async function a(){return await(await fetch(`/api/tables/stats`)).json()}async function o(e,t={}){let{where:n=``,limit:r=50,offset:i=0,orderCol:a,orderAsc:o}=t,s=new URLSearchParams;n&&s.append(`where`,n),s.append(`limit`,r.toString()),s.append(`offset`,i.toString()),a&&(s.append(`orderCol`,a),s.append(`orderAsc`,o===!1?`false`:`true`));let c=`/api/tables/${e}/data?${s.toString()}`;return await(await fetch(c)).json()}async function s(e){return await(await fetch(`/api/tables/${e}/schema`)).json()}async function c(e){return await(await fetch(`/api/tables/${e}/indexes`)).json()}async function l(e){return await(await fetch(`/api/query`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sql:e})})).json()}var u=null;function d(){u||(u=document.createElement(`div`),u.className=`drixio-context-menu`,document.body.appendChild(u),document.addEventListener(`click`,e=>{u.classList.contains(`visible`)&&!u.contains(e.target)&&p()}),document.addEventListener(`keydown`,e=>{e.key===`Escape`&&u.classList.contains(`visible`)&&p()}),window.addEventListener(`scroll`,e=>{u.classList.contains(`visible`)&&(u.contains(e.target)||p())},!0))}function f(e,t){e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation(),d(),u.innerHTML=``,u.classList.remove(`visible`),t.forEach(e=>{if(e.type===`divider`||e===`divider`){let e=document.createElement(`div`);e.className=`drixio-context-menu-divider`,u.appendChild(e);return}let t=document.createElement(`div`);t.className=`drixio-context-menu-item`,e.danger&&t.classList.add(`danger`),e.disabled&&t.classList.add(`disabled`),t.innerHTML=`
|
|
331
|
+
${e.icon?`<span class="material-symbols-outlined drixio-menu-icon">${e.icon}</span>`:``}
|
|
332
|
+
<span class="drixio-menu-label">${e.label}</span>
|
|
333
|
+
${e.shortcut?`<span class="drixio-menu-shortcut">${e.shortcut}</span>`:``}
|
|
334
|
+
`,t.addEventListener(`click`,t=>{e.disabled||(t.stopPropagation(),p(),e.action&&e.action())}),u.appendChild(t)}),requestAnimationFrame(()=>{let t=u.getBoundingClientRect(),n=e.clientX,r=e.clientY,i=`left`,a=`top`;n+t.width>window.innerWidth-8&&(n=Math.max(8,window.innerWidth-t.width-8),i=`right`),r+t.height>window.innerHeight-8&&(r=Math.max(8,window.innerHeight-t.height-8),a=`bottom`),u.style.transformOrigin=`${a} ${i}`,u.style.left=`${n}px`,u.style.top=`${r}px`,u.classList.add(`visible`)})}function p(){u&&u.classList.remove(`visible`)}async function m(e,t,n){let r=document.getElementById(`mock-data-modal-overlay`);r||(r=document.createElement(`div`),r.id=`mock-data-modal-overlay`,r.className=`mock-modal-overlay`,document.body.appendChild(r)),r.innerHTML=`
|
|
335
|
+
<div class="mock-modal-container">
|
|
336
|
+
<div class="mock-modal-header">
|
|
337
|
+
<div class="mock-modal-title">
|
|
338
|
+
<span class="material-symbols-outlined" style="color: var(--color-primary);">auto_fix_high</span>
|
|
339
|
+
<h3>Generate Mock Data</h3>
|
|
340
|
+
<span class="mock-target-badge">${e}</span>
|
|
341
|
+
</div>
|
|
342
|
+
<button type="button" class="mock-modal-close" id="btn-mock-close">
|
|
343
|
+
<span class="material-symbols-outlined">close</span>
|
|
344
|
+
</button>
|
|
345
|
+
</div>
|
|
346
|
+
<div class="mock-modal-body" style="padding: 40px; text-align: center;">
|
|
347
|
+
<div style="display: inline-flex; align-items: center; gap: 8px; color: var(--color-text-soft);">
|
|
348
|
+
<span class="material-symbols-outlined animate-spin icon-18">progress_activity</span>
|
|
349
|
+
<span>Analyzing schema and preparing preview...</span>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
`,r.querySelector(`#btn-mock-close`)?.addEventListener(`click`,i),r.classList.add(`visible`);function i(){r.classList.remove(`visible`)}let a=10,o=null;try{let t=await(await fetch(`/api/tables/${encodeURIComponent(e)}/mock/preview`)).json();if(!t.success||!t.data)throw Error(t.error||`Failed to load preview`);o=t.data}catch(e){r.innerHTML=`
|
|
354
|
+
<div class="mock-modal-container">
|
|
355
|
+
<div class="mock-modal-header">
|
|
356
|
+
<div class="mock-modal-title">
|
|
357
|
+
<span class="material-symbols-outlined" style="color: #ef4444;">error</span>
|
|
358
|
+
<h3>Failed to Load Mock Generator</h3>
|
|
359
|
+
</div>
|
|
360
|
+
<button type="button" class="mock-modal-close" id="btn-mock-close">
|
|
361
|
+
<span class="material-symbols-outlined">close</span>
|
|
362
|
+
</button>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="mock-modal-body" style="padding: 24px;">
|
|
365
|
+
<p style="color: var(--color-text-soft); font-size: 13px;">${e.message}</p>
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
`,r.querySelector(`#btn-mock-close`)?.addEventListener(`click`,i);return}function s(){let{rules:t,previewRows:c}=o,l=t.filter(e=>e.strategy.type!==`pk_auto`),u=l.map(e=>`<th>${e.column.name}</th>`).join(``),d=c.map(e=>`<tr>${l.map(t=>`<td title="${String(e[t.column.name]??``)}">${String(e[t.column.name]??``)}</td>`).join(``)}</tr>`).join(``),f=t.map(({column:e,strategy:t})=>`
|
|
369
|
+
<div class="mock-rule-item">
|
|
370
|
+
<div class="mock-rule-col">
|
|
371
|
+
<span class="mock-col-name">${e.name}</span>
|
|
372
|
+
<span class="mock-col-type">${e.type||`TEXT`}</span>
|
|
373
|
+
</div>
|
|
374
|
+
<span class="mock-rule-strategy ${t.type===`fk`?`is-fk`:``}">
|
|
375
|
+
${t.label}
|
|
376
|
+
</span>
|
|
377
|
+
</div>
|
|
378
|
+
`).join(``);r.innerHTML=`
|
|
379
|
+
<div class="mock-modal-container">
|
|
380
|
+
<div class="mock-modal-header">
|
|
381
|
+
<div class="mock-modal-title">
|
|
382
|
+
<span class="material-symbols-outlined" style="color: var(--color-primary);">auto_fix_high</span>
|
|
383
|
+
<h3>Generate Mock Data</h3>
|
|
384
|
+
<span class="mock-target-badge">${e}</span>
|
|
385
|
+
</div>
|
|
386
|
+
<button type="button" class="mock-modal-close" id="btn-mock-close">
|
|
387
|
+
<span class="material-symbols-outlined">close</span>
|
|
388
|
+
</button>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div class="mock-modal-body">
|
|
392
|
+
<!-- Row Count Selector -->
|
|
393
|
+
<div class="mock-section">
|
|
394
|
+
<label class="mock-section-label">Number of Rows</label>
|
|
395
|
+
<div class="mock-count-pills">
|
|
396
|
+
<button type="button" class="mock-pill ${a===10?`active`:``}" data-count="10">10 Rows</button>
|
|
397
|
+
<button type="button" class="mock-pill ${a===50?`active`:``}" data-count="50">50 Rows</button>
|
|
398
|
+
<button type="button" class="mock-pill ${a===100?`active`:``}" data-count="100">100 Rows</button>
|
|
399
|
+
<div class="mock-custom-input-wrap">
|
|
400
|
+
<input type="number" id="mock-custom-count" min="1" max="500" placeholder="Custom" value="${[10,50,100].includes(a)?``:a}" />
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<!-- Column Strategies -->
|
|
406
|
+
<div class="mock-section">
|
|
407
|
+
<label class="mock-section-label">Inferred Column Rules (${t.length} columns)</label>
|
|
408
|
+
<div class="mock-rules-list">
|
|
409
|
+
${f}
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<!-- Sample Preview -->
|
|
414
|
+
<div class="mock-section">
|
|
415
|
+
<label class="mock-section-label">Sample Preview (First 3 Rows)</label>
|
|
416
|
+
<div class="mock-preview-table-wrap">
|
|
417
|
+
<table class="mock-preview-table">
|
|
418
|
+
<thead><tr>${u}</tr></thead>
|
|
419
|
+
<tbody>${d}</tbody>
|
|
420
|
+
</table>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
<div class="mock-modal-footer">
|
|
426
|
+
<button type="button" class="mock-btn-secondary" id="btn-mock-cancel">Cancel</button>
|
|
427
|
+
<button type="button" class="mock-btn-primary" id="btn-mock-generate">
|
|
428
|
+
<span class="material-symbols-outlined icon-16">auto_fix_high</span>
|
|
429
|
+
<span>Generate & Insert ${a} Rows</span>
|
|
430
|
+
</button>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
`,r.querySelector(`#btn-mock-close`)?.addEventListener(`click`,i),r.querySelector(`#btn-mock-cancel`)?.addEventListener(`click`,i),r.querySelectorAll(`.mock-pill`).forEach(e=>{e.addEventListener(`click`,()=>{a=parseInt(e.dataset.count,10),s()})});let p=r.querySelector(`#mock-custom-count`);p&&p.addEventListener(`change`,e=>{let t=parseInt(e.target.value,10);t&&t>0&&t<=500&&(a=t,s())}),r.querySelector(`#btn-mock-generate`)?.addEventListener(`click`,async()=>{let t=r.querySelector(`#btn-mock-generate`);if(t){t.disabled=!0,t.innerHTML=`<span class="material-symbols-outlined animate-spin icon-16">progress_activity</span> Inserting...`;try{let t=await(await fetch(`/api/tables/${encodeURIComponent(e)}/mock`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({count:a})})).json();if(!t.success)throw Error(t.error||`Failed to insert mock data`);i(),window.loadTableStats?.(),window.updateSidebarActiveTable?.(e),window.showToast&&window.showToast(`Successfully generated and inserted ${t.count||a} rows into "${e}"!`,`success`),n&&n()}catch(e){alert(`Failed to insert mock data: ${e.message}`),t.disabled=!1,t.innerHTML=`<span>Try Again</span>`}}})}s()}var h=!1;async function g(e=!1){try{let t=document.getElementById(`table-nav`);if(!t)return;e&&(t.innerHTML=`
|
|
434
|
+
<div class="sidebar-loading-state">
|
|
435
|
+
<span class="material-symbols-outlined animate-spin icon-18">progress_activity</span>
|
|
436
|
+
<span>Refreshing tables...</span>
|
|
437
|
+
</div>
|
|
438
|
+
`);let n=await r();if(t.innerHTML=``,y(),n.success&&n.data&&n.data.length>0){let r=n.data,i=document.getElementById(`table-count-badge`);if(i&&(i.textContent=r.length.toString()),r.forEach(e=>{let n=document.createElement(`button`);n.type=`button`,n.className=`table-btn`,n.dataset.table=e,n.title=e,n.innerHTML=`
|
|
439
|
+
<div class="table-btn-label">
|
|
440
|
+
<i class="material-symbols-outlined table-item-icon">table_chart</i>
|
|
441
|
+
<span class="table-name-text">${e}</span>
|
|
442
|
+
<span class="dirty-indicator-dot hidden" title="Unsaved changes"></span>
|
|
443
|
+
</div>
|
|
444
|
+
<span class="table-btn-badge hidden" id="badge-${e}"></span>
|
|
445
|
+
`,n.onclick=()=>{window.fkNavHistory&&window.fkNavHistory.toTable!==e&&(window.fkNavHistory=null),window.AppState.currentTable=e,window.AppState.currentTableBtnElement=n,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),n.classList.add(`active`),window.AppState.currentTab===`erd-btn`||window.AppState.currentTab===`sql-btn`||window.AppState.currentTab===`status-btn`?window.handleSwitchTab(`data-btn`):window.renderCurrentView()},n.oncontextmenu=t=>{f(t,[{icon:`content_copy`,label:`Copy Table Name`,action:async()=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement(`textarea`);t.value=e,document.body.appendChild(t),t.select(),document.execCommand(`copy`),t.remove()}window.showToast&&window.showToast(`Copied "${e}"`,`success`)}},{icon:`auto_fix_high`,label:`Generate Mock Data...`,action:async()=>{let t=await s(e);m(e,t.success?t.data:[],()=>{window.AppState.currentTable===e&&window.renderCurrentView()})}},{icon:`table_chart`,label:`View Data`,action:()=>{n.click(),window.handleSwitchTab(`data-btn`)}},{icon:`schema`,label:`View Schema`,action:()=>{n.click(),window.handleSwitchTab(`schema-btn`)}},{type:`divider`},{icon:`download`,label:`Export as CSV`,action:()=>{window.open(`/api/tables/${e}/export?format=csv`,`_blank`)}},{icon:`download`,label:`Export as JSON`,action:()=>{window.open(`/api/tables/${e}/export?format=json`,`_blank`)}},{type:`divider`},{icon:`mop`,label:`Truncate Table (Clear Rows)`,danger:!0,action:async()=>{if(confirm(`Are you sure you want to delete ALL data from table "${e}"? This cannot be undone!`)){let t=await l(`DELETE FROM "${e}";`);t.success?(window.showToast&&window.showToast(`Cleared all rows from "${e}"`,`info`),window.AppState.currentTable===e&&window.renderCurrentView(),_()):alert(`Failed to clear table: ${t.error}`)}}}])},t.appendChild(n)}),window.AppState.currentTable){let e=document.querySelector(`.table-btn[data-table="${window.AppState.currentTable}"]`);e&&(e.classList.add(`active`),window.AppState.currentTableBtnElement=e)}else e||window.handleSwitchTab(`data-btn`);_();let a=document.getElementById(`search-input`);a&&a.value.trim()&&b(a.value.trim())}else{let n=document.getElementById(`table-count-badge`);n&&(n.textContent=`0`),t.innerHTML=`
|
|
446
|
+
<div id="not-found-msg">
|
|
447
|
+
<span class="material-symbols-outlined icon-20 text-soft">inventory_2</span>
|
|
448
|
+
<span>No Tables Found</span>
|
|
449
|
+
</div>
|
|
450
|
+
`,e||window.handleSwitchTab(`data-btn`)}h||=(ee(),!0)}catch(e){console.error(`Failed to fetch tables:`,e);let t=document.getElementById(`table-nav`);t&&(t.innerHTML=`
|
|
451
|
+
<div id="not-found-msg" class="text-error">
|
|
452
|
+
<span class="material-symbols-outlined icon-20">warning</span>
|
|
453
|
+
<span>Failed to load tables</span>
|
|
454
|
+
</div>
|
|
455
|
+
`);let n=document.getElementById(`sidebar-db-status-dot`),r=document.getElementById(`brand-db-type`);n&&(n.classList.remove(`status-connected`),n.classList.add(`status-error`)),r&&(r.textContent=`ERROR`)}}async function _(){try{let e=await a();e.success&&e.data&&Object.entries(e.data).forEach(([e,t])=>{let n=document.getElementById(`badge-${e}`);n&&(n.textContent=Number(t).toLocaleString(),n.classList.remove(`hidden`),n.style.display=`inline-flex`)})}catch(e){console.error(`Failed to load table stats:`,e)}}window.loadTableStats=_;function v(e){let t=e||window.AppState?.currentTable;if(!t)return;document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`));let n=document.querySelector(`.table-btn[data-table="${t}"]`);n&&(n.classList.add(`active`),window.AppState&&(window.AppState.currentTableBtnElement=n))}window.updateSidebarActiveTable=v;async function y(){let e=document.getElementById(`sidebar-db-status-dot`),t=document.getElementById(`brand-db-type`);try{if(!t)return;if(window.AppState?.dbType){t.textContent=window.AppState.dbType.toUpperCase(),e&&(e.classList.remove(`status-error`),e.classList.add(`status-connected`));return}let n=await i();n?.success&&n.data?.dbType?(t.textContent=n.data.dbType.toUpperCase(),e&&(e.classList.remove(`status-error`),e.classList.add(`status-connected`))):(e&&(e.classList.remove(`status-connected`),e.classList.add(`status-error`)),t&&(t.textContent=`OFFLINE`))}catch{e&&(e.classList.remove(`status-connected`),e.classList.add(`status-error`)),t&&(t.textContent=`DISCONNECTED`)}}function b(e){let t=e.toLowerCase().trim(),n=document.querySelectorAll(`.table-btn`),r=0;n.forEach(e=>{(e.dataset.table||``).toLowerCase().includes(t)?(e.style.display=`flex`,r++):e.style.display=`none`});let i=document.getElementById(`table-nav`),a=document.getElementById(`sidebar-no-match`);r===0&&n.length>0?a||(a=document.createElement(`div`),a.id=`sidebar-no-match`,a.className=`sidebar-empty-state`,a.innerHTML=`
|
|
456
|
+
<span class="material-symbols-outlined icon-20 text-soft">search_off</span>
|
|
457
|
+
<span>No matching tables</span>
|
|
458
|
+
`,i?.appendChild(a)):a&&a.remove();let o=document.getElementById(`table-count-badge`);o&&(o.textContent=r.toString())}function ee(){let e=document.getElementById(`search-input`),t=document.getElementById(`search-clear-btn`),n=document.getElementById(`refresh-tables-btn`),r=document.getElementById(`sidebar-collapse-btn`),i=document.getElementById(`sidebar-panel`);if(e&&(e.addEventListener(`input`,e=>{let n=e.target.value;t&&(n?t.classList.remove(`hidden`):t.classList.add(`hidden`)),b(n)}),e.addEventListener(`keydown`,t=>{if(t.key===`Enter`){t.preventDefault();let n=document.querySelector(`.table-btn:not([style*="display: none"])`);n&&(n.click(),e.blur())}}),t&&t.addEventListener(`click`,()=>{e.value=``,t.classList.add(`hidden`),b(``),e.focus()})),n&&n.addEventListener(`click`,async()=>{n.classList.add(`animate-spin`),await g(!0),setTimeout(()=>{n.classList.remove(`animate-spin`),window.showToast&&window.showToast(`Tables refreshed`,`success`)},300)}),i){localStorage.getItem(`drixio_sidebar_collapsed`)===`true`&&(i.classList.add(`collapsed`),x(!0)),r&&r.addEventListener(`click`,e=>{e.stopPropagation(),i.classList.toggle(`collapsed`);let t=i.classList.contains(`collapsed`);localStorage.setItem(`drixio_sidebar_collapsed`,t.toString()),x(t)});let e=document.getElementById(`sidebar-header`);e&&e.addEventListener(`click`,()=>{i.classList.contains(`collapsed`)&&(i.classList.remove(`collapsed`),localStorage.setItem(`drixio_sidebar_collapsed`,`false`),x(!1))})}document.addEventListener(`keydown`,n=>{if(n.key===`/`&&document.activeElement?.tagName!==`INPUT`&&document.activeElement?.tagName!==`TEXTAREA`){if(n.preventDefault(),window.AppState?.currentTab===`erd-btn`){let e=document.getElementById(`erd-search-input`);if(e){e.focus(),e.select();return}}e?.focus(),e?.select()}else n.key===`Escape`&&document.activeElement===e&&(e.value&&(e.value=``,t?.classList.add(`hidden`),b(``)),e.blur())})}function x(e){let t=document.getElementById(`sidebar-collapse-icon`),n=document.getElementById(`sidebar-header`);t&&(t.textContent=e?`menu`:`menu_open`),n&&(n.title=e?`Click to expand sidebar`:``)}function S(e,t){document.querySelectorAll(`#${e} .cell-in-range, #${e} .cell-selected, #${e} .range-top, #${e} .range-bottom, #${e} .range-left, #${e} .range-right, #${e} .row-header-selected`).forEach(e=>{e.classList.remove(`cell-in-range`,`cell-selected`,`range-top`,`range-bottom`,`range-left`,`range-right`,`row-header-selected`)});let n=t.selection;if(n.startRow===-1)return;let r=Math.min(n.startRow,n.endRow),i=Math.max(n.startRow,n.endRow),a=Math.min(n.startCol,n.endCol),o=Math.max(n.startCol,n.endCol);document.querySelectorAll(`#${e} td.data-cell`).forEach(e=>{let s=parseInt(e.dataset.rowIdx),c=parseInt(e.dataset.colIdx);s>=r&&s<=i&&c>=a&&c<=o&&(e.classList.add(`cell-in-range`),s===r&&e.classList.add(`range-top`),s===i&&e.classList.add(`range-bottom`),c===a&&e.classList.add(`range-left`),c===o&&e.classList.add(`range-right`),s===n.startRow&&c===n.startCol&&(e.classList.add(`cell-selected`),t.selectedCell=e))}),n.isDraggingRow&&document.querySelectorAll(`#${e} td.row-header`).forEach(e=>{let t=parseInt(e.dataset.rowIdx);t>=r&&t<=i&&e.classList.add(`row-header-selected`)})}function te(e,t,n,r){e.addEventListener(`mousedown`,e=>{if(e.target.tagName===`INPUT`||e.target.tagName===`SELECT`)return;let i=e.target.closest(`td.row-header`);if(i&&e.button!==2){let e=parseInt(i.dataset.rowIdx);n.selection={isDragging:!1,isDraggingRow:!0,isRowSelection:!0,startRow:e,endRow:e,startCol:0,endCol:r-1},S(t,n);return}let a=e.target.closest(`td.data-cell`);if(!a)return;let o=parseInt(a.dataset.rowIdx),s=parseInt(a.dataset.colIdx);n.selection={isDragging:!0,isRowSelection:!1,startRow:o,startCol:s,endRow:o,endCol:s},S(t,n)}),e.addEventListener(`mouseover`,e=>{if(n.selection.isDraggingRow){let r=e.target.closest(`td.row-header`);if(!r)return;n.selection.endRow=parseInt(r.dataset.rowIdx),S(t,n);return}if(!n.selection.isDragging)return;let r=e.target.closest(`td.data-cell`);r&&(n.selection.endRow=parseInt(r.dataset.rowIdx),n.selection.endCol=parseInt(r.dataset.colIdx),S(t,n))}),e.addEventListener(`mouseup`,()=>{n.selection&&(n.selection.isDragging=!1,n.selection.isDraggingRow=!1)})}function ne(e,t){let n=document.createElement(`div`);n.className=`resizer`,e.appendChild(n),n.addEventListener(`click`,e=>e.stopPropagation()),n.addEventListener(`mousedown`,n=>{n.preventDefault(),n.stopPropagation();let r=n.pageX,i=e.offsetWidth,a=!1,o=n=>{a=!0,t&&(t.isResizing=!0);let o=i+(n.pageX-r);e.style.width=o+`px`,e.style.minWidth=o+`px`,e.style.maxWidth=o+`px`},s=()=>{document.removeEventListener(`mousemove`,o),document.removeEventListener(`mouseup`,s),document.body.style.cursor=``,a&&setTimeout(()=>{t&&(t.isResizing=!1)},100)};document.body.style.cursor=`col-resize`,document.addEventListener(`mousemove`,o),document.addEventListener(`mouseup`,s)})}async function re(){if(!window.DataGrid)return;let{pendingEdits:e,pendingInserts:t,pkColumn:n}=window.DataGrid,r=window.AppState.currentTable;if(!r||!n){alert(`Cannot save: No Primary Key detected for this table.`);return}let i=[];for(let[t,a]of Object.entries(e)){if(Object.keys(a).length===0)continue;let e=Object.entries(a).map(([e,t])=>t===``?`"${e}" = NULL`:`"${e}" = '${t.replace(/'/g,`''`)}'`).join(`, `);i.push(`UPDATE "${r}" SET ${e} WHERE "${n}" = '${t.replace(/'/g,`''`)}';`)}for(let e=0;e<t.length;e++){let n=t[e];if(Object.keys(n).length===0||!Object.values(n).some(e=>e!==``))continue;let a=Object.keys(n),o=a.map(e=>{let t=n[e];return t===``?`NULL`:`'${t.replace(/'/g,`''`)}'`});i.push(`INSERT INTO "${r}" ("${a.join(`", "`)}") VALUES (${o.join(`, `)});`)}if(window.DataGrid.pendingDeletes)for(let e of window.DataGrid.pendingDeletes)i.push(`DELETE FROM "${r}" WHERE "${n}" = '${e.replace(/'/g,`''`)}';`);if(i.length===0)return;let a=!0,o=``;for(let e of i)try{let t=await l(e);if(!t.success){a=!1,o=t.error;break}}catch(e){a=!1,o=e.message;break}a?(window.DataGrid&&(window.DataGrid.pendingEdits={},window.DataGrid.pendingInserts=[{}],window.DataGrid.pendingDeletes=new Set,window.DataGrid.history=[],window.DataGrid.currentTransaction=null,window.DataGrid.refreshData?window.DataGrid.refreshData():window.renderCurrentView()),window.updateSidebarActiveTable?.(),window.loadTableStats?.(),window.showToast&&window.showToast(`Data saved successfully!`)):(window.showToast?window.showToast(`Save failed: `+o,`error`):alert(`Save failed:
|
|
459
|
+
`+o),document.querySelectorAll(`.cell-edited`).forEach(e=>{e.classList.remove(`cell-edited`),e.classList.add(`cell-error`)})),window.updateSidebarDirtyState?.()}function C(e,t,n,r=!0){if(r&&window.DataGrid.currentTransaction){let n=e.textContent;(n===`null`||e.classList.contains(`ghost-row`))&&(n=``),n!==t&&window.DataGrid.currentTransaction.push({td:e,oldVal:n,newVal:t})}let i=e.dataset.col,a=e.dataset.colIdx===`0`?`<span class="ghost-cell-hint">+ Add Row</span>`:``;if(e.innerHTML=t||(e.dataset.insertIndex===void 0?`<em>null</em>`:a),e.dataset.insertIndex!==void 0){let r=parseInt(e.dataset.insertIndex);if(window.DataGrid.pendingInserts[r]||(window.DataGrid.pendingInserts[r]={}),t){if(window.DataGrid.pendingInserts[r][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`ghost-row`),r===window.DataGrid.pendingInserts.length-1){e.closest(`tr`).classList.remove(`ghost-row-tr`),window.DataGrid.pendingInserts.push({});let t=document.querySelector(`#data-grid-table-${window.AppState.currentTable} tbody`),i=document.createElement(`tr`);i.className=`ghost-row-tr`;let a=parseInt(e.dataset.rowIdx)+1;i.innerHTML+=`<td class="row-header" data-row-idx="${a}">*</td>`,n.forEach((e,t)=>{let n=t===0?`<span class="ghost-cell-hint">+ Add Row</span>`:``;i.innerHTML+=`<td class="data-cell ghost-row" data-row-idx="${a}" data-col-idx="${t}" data-insert-index="${r+1}" data-col="${e}">${n}</td>`}),t.appendChild(i)}}else window.DataGrid.pendingInserts[r]&&delete window.DataGrid.pendingInserts[r][i],e.classList.remove(`cell-edited`),e.classList.add(`ghost-row`)}else{let n=e.dataset.pk;t===e.dataset.original?(e.classList.remove(`cell-edited`),e.classList.remove(`cell-error`),window.DataGrid.pendingEdits[n]&&(delete window.DataGrid.pendingEdits[n][i],Object.keys(window.DataGrid.pendingEdits[n]).length===0&&delete window.DataGrid.pendingEdits[n])):(window.DataGrid.pendingEdits[n]||(window.DataGrid.pendingEdits[n]={}),window.DataGrid.pendingEdits[n][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`cell-error`))}window.updateSidebarDirtyState?.()}function w(e,t=!0){let n=document.querySelector(`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!n||n.classList.contains(`ghost-row-tr`)||n.classList.contains(`row-deleted`))return;let r=n.querySelector(`td.data-cell`),i=r?.dataset.pk;if(i!==void 0)t&&window.DataGrid.currentTransaction&&window.DataGrid.currentTransaction.push({type:`delete`,rowIdx:e,pk:i}),window.DataGrid.pendingDeletes.add(i),n.classList.add(`row-deleted`);else if(r?.dataset.insertIndex!==void 0){let e=parseInt(r.dataset.insertIndex);window.DataGrid.pendingInserts[e]&&delete window.DataGrid.pendingInserts[e],n.remove()}window.updateSidebarDirtyState?.()}function ie(e,t){let n=document.querySelector(`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);n&&n.classList.remove(`row-deleted`),window.DataGrid.pendingDeletes.delete(t),window.updateSidebarDirtyState?.()}function ae(e,t){e.forEach(e=>{let n=window.AppState.currentTable,r=document.querySelector(`#data-grid-table-${n} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!r||r.classList.contains(`ghost-row-tr`))return;let i=document.querySelector(`#data-grid-table-${n} .ghost-row-tr`);i&&t.forEach((e,n)=>{let a=r.querySelector(`td.data-cell[data-col-idx="${n}"]`),o=i.querySelector(`td.data-cell[data-col-idx="${n}"]`);if(a&&o){let n=a.dataset.insertIndex===void 0?a.classList.contains(`cell-edited`)?window.DataGrid.pendingEdits[a.dataset.pk]?.[e]:a.dataset.original:window.DataGrid.pendingInserts[a.dataset.insertIndex][e];n!=null&&n!==`null`&&C(o,n,t,!0)}})})}function oe(e,t,n){e.addEventListener(`dblclick`,e=>{let r=e.target.closest(`td.data-cell`);if(!r||r.querySelector(`input, select`))return;let i=r.dataset.col,a=t.find(e=>e.name===i),o=a?.type?.toUpperCase()||``,s=o.includes(`ENUM`),c=o===`DATE`,u=o.includes(`DATETIME`)||o.includes(`TIMESTAMP`),d=o.includes(`BOOL`)||o===`TINYINT(1)`,f=r.classList.contains(`ghost-row`)||r.dataset.insertIndex!==void 0&&!r.classList.contains(`cell-edited`),p=(r.dataset.original===void 0?r.textContent:r.dataset.original).trim(),m=f||p===`null`||p===`+ New`||p===`+ Add Row`?``:r.dataset.original===void 0?r.querySelector(`.cell-text`)?.textContent.trim()||r.textContent.trim():r.dataset.original,h;if(a&&a.fkTarget){h=document.createElement(`select`);let e=document.createElement(`option`);e.value=m,e.textContent=`Loading...`,h.appendChild(e);let{table:t,column:n}=a.fkTarget;l(`SELECT * FROM "${t}" LIMIT 100`).then(e=>{if(e.success&&e.data&&e.data.rows){if(h.innerHTML=``,a.nullable){let e=document.createElement(`option`);e.value=``,e.textContent=`-- None --`,h.appendChild(e)}let t=n;if(e.data.columns){let n=[`name`,`title`,`label`,`description`],r=e.data.columns.find(e=>n.includes(e.toLowerCase()));r&&(t=r)}if(e.data.rows.forEach(e=>{let r=String(e[n]),i=t===n?r:`${r} - ${e[t]}`,a=document.createElement(`option`);a.value=r,a.textContent=i,r===m&&(a.selected=!0),h.appendChild(a)}),m&&!e.data.rows.find(e=>String(e[n])===m)){let e=document.createElement(`option`);e.value=m,e.textContent=`${m} (Not in limit)`,e.selected=!0,h.appendChild(e)}}}).catch(t=>{e.textContent=`Error loading options`})}else if(s){let e=a.type.match(/enum\((.*?)\)/i),t=[];e&&(t=e[1].split(`,`).map(e=>e.trim().replace(/^'|'$/g,``))),h=document.createElement(`select`),t.forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,e===m&&(t.selected=!0),h.appendChild(t)})}else if(c)h=document.createElement(`input`),h.type=`date`,h.value=m;else if(u){h=document.createElement(`input`),h.type=`datetime-local`;let e=m.replace(` `,`T`).slice(0,16);h.value=e}else d?(h=document.createElement(`input`),h.type=`checkbox`,h.checked=m===`1`||m.toLowerCase()===`true`):(h=document.createElement(`input`),h.type=`text`,h.value=m);let g=!1,_=()=>{g=!0;let e=document.getElementById(`modal-editor-overlay`),t=document.getElementById(`modal-textarea`),n=document.getElementById(`modal-cancel-btn`),r=document.getElementById(`modal-save-btn`),a=document.getElementById(`modal-title`);if(!e||!t)return;a.textContent=`Edit ${i}`,t.value=h.value,e.classList.remove(`hidden`),e.style.display=`flex`,t.focus();let o=()=>{e.classList.add(`hidden`),e.style.display=`none`,r.onclick=null,n.onclick=null,g=!1,h.focus()};n.onclick=o,r.onclick=()=>{h.value=t.value,o(),v()},e.onclick=t=>{t.target===e&&o()}};if(r.innerHTML=``,r.appendChild(h),o===``||!s&&!c&&!u&&!d&&!(a&&a.fkTarget)){let e=document.createElement(`span`);e.className=`material-symbols-outlined cell-expand-btn`,e.textContent=`open_in_full`,e.title=`Open Full Editor (Shift + Enter)`,e.onmousedown=e=>{e.preventDefault(),e.stopPropagation()},e.onclick=e=>{e.preventDefault(),e.stopPropagation(),_()},r.appendChild(e),h.style.width=`100%`,h.style.paddingRight=`32px`,h.addEventListener(`keydown`,e=>{e.key===`Enter`&&e.shiftKey?(e.preventDefault(),_()):e.key===`Enter`&&h.blur(),e.key===`Tab`&&(e.preventDefault(),h.blur(),document.body.dispatchEvent(new KeyboardEvent(`keydown`,{key:`Tab`,shiftKey:e.shiftKey,bubbles:!0})))})}else h.addEventListener(`keydown`,e=>{e.key===`Enter`&&h.blur(),e.key===`Tab`&&(e.preventDefault(),h.blur(),document.body.dispatchEvent(new KeyboardEvent(`keydown`,{key:`Tab`,shiftKey:e.shiftKey,bubbles:!0})))});h.focus();let v=()=>{if(g)return;let e=d?h.checked?`1`:`0`:h.value;u&&e&&(e=e.replace(`T`,` `)+`:00`),window.DataGrid.currentTransaction=[],C(r,e,n),window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null};h.addEventListener(`blur`,()=>{setTimeout(()=>{g||v()},100)})})}function T(){let e=window.AppState?.currentTable;if(!e)return``;let t=document.getElementById(`filter-val-${e}`)?.value.trim(),n=document.getElementById(`filter-op-${e}`)?.value,r=document.getElementById(`filter-col-${e}`)?.value,i=``;if(t||n===`IS NULL`){let e=t;e&&!e.startsWith(`'`)&&!e.endsWith(`'`)&&isNaN(Number(e))&&(e.toUpperCase().includes(` AND `)||e.toUpperCase().includes(` OR `)||(e=`'${e.replace(/'/g,`''`)}'`)),i=`"${r}" ${n} ${e}`}return i}function se(e,t,n,r,i=[]){let a=`<td class="row-header" data-row-idx="${t}">${t+1}</td>`,o=n?e[n]:t;return r.forEach((n,r)=>{let s=i.find(e=>e.name===n),c=e[n]===null?`null`:String(e[n]),l=c.replace(/"/g,`"`),u=c===`null`?`<em>null</em>`:c.replace(/</g,`<`);if(s&&s.fkTarget&&c!==`null`){let e=s.fkTarget.table,i=s.fkTarget.column;a+=`<td class="data-cell is-fk-cell" data-row-idx="${t}" data-col-idx="${r}" data-pk="${o}" data-col="${n}" data-original="${l}" data-fk-table="${e}" data-fk-col="${i}"><span class="cell-text">${u}</span><button type="button" class="fk-jump-btn" title="View & jump to ${e} (${i} = ${l})" data-fk-table="${e}" data-fk-col="${i}" data-fk-val="${l}"><span class="material-symbols-outlined">open_in_new</span></button></td>`}else a+=`<td class="data-cell" data-row-idx="${t}" data-col-idx="${r}" data-pk="${o}" data-col="${n}" data-original="${l}"><span class="cell-text">${u}</span></td>`}),a}var E=null,D=null,O=null,k=null;function ce(){return E||(E=document.createElement(`div`),E.id=`fk-preview-popover`,E.className=`fk-preview-popover hidden`,document.body.appendChild(E),E.addEventListener(`mouseenter`,()=>{O&&=(clearTimeout(O),null)}),E.addEventListener(`mouseleave`,e=>{e.relatedTarget&&k&&k.contains(e.relatedTarget)||A()}),document.addEventListener(`keydown`,e=>{e.key===`Escape`&&E&&!E.classList.contains(`hidden`)&&A()}),window.addEventListener(`scroll`,e=>{E&&!E.classList.contains(`hidden`)&&(E.contains(e.target)||A())},!0),E)}function A(){D&&=(clearTimeout(D),null),E&&E.classList.add(`hidden`),k=null}function le(e,t,n,r){A(),window.fkNavHistory={fromTable:r,toTable:e,timestamp:Date.now()};let i=`"${t}" = ${/^-?\d+(\.\d+)?$/.test(n)?n:`'${n.replace(/'/g,`''`)}'`}`,a=document.querySelector(`.table-btn[data-table="${e}"]`)||Array.from(document.querySelectorAll(`.table-btn`)).find(t=>(t.dataset.table||t.querySelector(`span`)?.textContent.trim())===e);window.AppState.currentTable=e,window.AppState.currentTableBtnElement=a||null,window.AppState.currentTab=`data-btn`,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),a&&a.classList.add(`active`);let o=`view-data-btn-${e}`,s=document.getElementById(o);s&&(s.innerHTML=``),window.renderCurrentView(i),window.showToast&&window.showToast(`Jumped to ${e} (${i})`,`info`)}function ue(e,t){let n=ce();e.addEventListener(`click`,e=>{let n=e.target.closest(`.fk-jump-btn`);if(!n)return;e.stopPropagation();let r=n.dataset.fkTable,i=n.dataset.fkCol,a=n.dataset.fkVal;r&&i&&a!==void 0&&le(r,i,a,t)}),e.addEventListener(`mouseover`,e=>{let r=e.target.closest(`.is-fk-cell`);r&&(O&&=(clearTimeout(O),null),k!==r&&(k=r,D&&clearTimeout(D),D=setTimeout(async()=>{let e=r.dataset.fkTable,i=r.dataset.fkCol,a=r.dataset.original;if(!e||!i||a===void 0||a===`null`)return;n.innerHTML=`
|
|
460
|
+
<div class="fk-popover-header">
|
|
461
|
+
<div class="fk-popover-title">
|
|
462
|
+
<span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
|
|
463
|
+
<span class="fk-popover-table">${e}</span>
|
|
464
|
+
<span class="fk-popover-badge">FK → ${i}</span>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
<div class="fk-popover-body">
|
|
468
|
+
<div class="fk-popover-loading">
|
|
469
|
+
<span class="material-symbols-outlined animate-spin icon-16">progress_activity</span>
|
|
470
|
+
<span>Loading relation...</span>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
`,j(r,n),n.classList.remove(`hidden`);let o=`SELECT * FROM "${e}" WHERE "${i}" = ${/^-?\d+(\.\d+)?$/.test(a)?a:`'${a.replace(/'/g,`''`)}'`} LIMIT 1`;try{let s=await l(o);if(k!==r)return;if(s.success&&s.data&&s.data.rows&&s.data.rows.length>0){let o=s.data.rows[0],c=Object.keys(o).slice(0,7),l=``;c.forEach(e=>{let t=o[e];t=t==null?`null`:typeof t==`object`?JSON.stringify(t):String(t),l+=`
|
|
474
|
+
<div class="fk-field-row">
|
|
475
|
+
<span class="fk-field-name">${e}</span>
|
|
476
|
+
<span class="fk-field-value ${t===`null`?`is-null`:``}" title="${t.replace(/"/g,`"`)}">${t}</span>
|
|
477
|
+
</div>
|
|
478
|
+
`}),n.innerHTML=`
|
|
479
|
+
<div class="fk-popover-header">
|
|
480
|
+
<div class="fk-popover-title">
|
|
481
|
+
<span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
|
|
482
|
+
<span class="fk-popover-table">${e}</span>
|
|
483
|
+
<span class="fk-popover-badge">${i} = ${a}</span>
|
|
484
|
+
</div>
|
|
485
|
+
<button type="button" class="fk-popover-jump-btn" id="btn-fk-popover-jump">
|
|
486
|
+
<span>Jump</span>
|
|
487
|
+
<span class="material-symbols-outlined icon-14">arrow_forward</span>
|
|
488
|
+
</button>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="fk-popover-body">
|
|
491
|
+
<div class="fk-fields-list">${l}</div>
|
|
492
|
+
</div>
|
|
493
|
+
<div class="fk-popover-footer">
|
|
494
|
+
<span class="material-symbols-outlined icon-14">touch_app</span>
|
|
495
|
+
<span>Click to inspect full record</span>
|
|
496
|
+
</div>
|
|
497
|
+
`,n.querySelector(`#btn-fk-popover-jump`)?.addEventListener(`click`,n=>{n.stopPropagation(),le(e,i,a,t)}),n.onclick=()=>{le(e,i,a,t)},j(r,n)}else n.innerHTML=`
|
|
498
|
+
<div class="fk-popover-header">
|
|
499
|
+
<div class="fk-popover-title">
|
|
500
|
+
<span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
|
|
501
|
+
<span class="fk-popover-table">${e}</span>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="fk-popover-body">
|
|
505
|
+
<div class="fk-popover-empty">
|
|
506
|
+
<span class="material-symbols-outlined icon-18">error_outline</span>
|
|
507
|
+
<span>No matching row found for ${i} = ${a}</span>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
`,j(r,n)}catch(e){if(k!==r)return;n.innerHTML=`
|
|
511
|
+
<div class="fk-popover-body">
|
|
512
|
+
<div class="fk-popover-empty">
|
|
513
|
+
<span>Failed to fetch preview: ${e.message}</span>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
`,j(r,n)}},250)))}),e.addEventListener(`mouseout`,e=>{let t=e.target.closest(`.is-fk-cell`);t&&(e.relatedTarget&&(t.contains(e.relatedTarget)||n&&n.contains(e.relatedTarget))||(D&&=(clearTimeout(D),null),O=setTimeout(()=>{A()},350)))})}function j(e,t){let n=e.getBoundingClientRect(),r=t.getBoundingClientRect(),i=n.bottom+2,a=n.left;i+r.height>window.innerHeight-10&&(i=Math.max(10,n.top-r.height-2)),a+r.width>window.innerWidth-10&&(a=Math.max(10,window.innerWidth-r.width-10)),t.style.top=`${i}px`,t.style.left=`${a}px`}async function M(e,t,n=``,r=!1,i=null){if(window.updateSidebarActiveTable)window.updateSidebarActiveTable(e);else{document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`));let n=t||document.querySelector(`.table-btn[data-table="${e}"]`);n&&(n.classList.add(`active`),window.AppState&&(window.AppState.currentTableBtnElement=n))}let a=document.getElementById(`table-name`);a&&(a.textContent=e);let c=i||document.getElementById(`main-content`);r||(c.innerHTML=`<div class='p-6'>Loading...</div>`);try{let i=await s(e);if(!i.success)throw Error(i.error);let a=i.data,u=a.find(e=>e.isPk)?.name;(!r||!window.DataGrid)&&(window.DataGrid={schema:a,pkColumn:u,pendingEdits:{},pendingInserts:[{}],pendingDeletes:new Set,selectedCell:null,history:[],currentTransaction:null,sortState:{col:u||a[0].name,asc:!0},pagination:{limit:50,offset:0,isLoading:!1,hasMore:!0},selection:{isDragging:!1,isDraggingRow:!1,startRow:-1,startCol:-1,endRow:-1,endCol:-1}}),window.TableStates&&window.TableStates[e]&&(window.TableStates[e].dataGrid=window.DataGrid);let d=await o(e,{where:n,limit:window.DataGrid.pagination.limit,offset:window.DataGrid.pagination.offset,orderCol:window.DataGrid.sortState.col,orderAsc:window.DataGrid.sortState.asc});if(d.success&&d.data){window.loadTableStats?.();let i=d.data.rows,s=d.data.columns;window.DataGrid.pagination.hasMore=i.length===window.DataGrid.pagination.limit;let p=s.map(e=>`<option value="${e}">${e}</option>`).join(``);r||(c.innerHTML=`
|
|
517
|
+
<div class="toolbar">
|
|
518
|
+
${window.fkNavHistory&&window.fkNavHistory.toTable===e?`<button type="button" id="btn-fk-back-${e}" class="fk-back-btn" title="Back to table '${window.fkNavHistory.fromTable}'">
|
|
519
|
+
<span class="material-symbols-outlined icon-14">arrow_back</span>
|
|
520
|
+
<span>Back to ${window.fkNavHistory.fromTable}</span>
|
|
521
|
+
</button>`:``}
|
|
522
|
+
<div class="filter-group">
|
|
523
|
+
<div class="filter-icon-container">
|
|
524
|
+
<span class="material-symbols-outlined" id="filter-icon">filter_list</span>
|
|
525
|
+
<span>Filter</span>
|
|
526
|
+
</div>
|
|
527
|
+
<select id="filter-col-${e}" class="filter-select">${p}</select>
|
|
528
|
+
<select id="filter-op-${e}" class="filter-select">
|
|
529
|
+
<option value="=">=</option>
|
|
530
|
+
<option value=">">></option>
|
|
531
|
+
<option value="<"><</option>
|
|
532
|
+
<option value=">=">>=</option>
|
|
533
|
+
<option value="<="><=</option>
|
|
534
|
+
<option value="LIKE">LIKE</option>
|
|
535
|
+
<option value="!=">!=</option>
|
|
536
|
+
</select>
|
|
537
|
+
<div class="filter-input-wrapper">
|
|
538
|
+
<input type="text" id="filter-val-${e}" class="filter-input" placeholder="Filter value..." />
|
|
539
|
+
<button type="button" id="btn-clear-filter-${e}" class="filter-clear-btn hidden" title="Clear filter">
|
|
540
|
+
<span class="material-symbols-outlined">close</span>
|
|
541
|
+
</button>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
<button id="btn-refresh-data-${e}" class="refresh-btn" title="Refresh Data (F5)">
|
|
545
|
+
<span class="material-symbols-outlined">refresh</span>
|
|
546
|
+
</button>
|
|
547
|
+
</div>
|
|
548
|
+
<div class="table-container" id="data-grid-container-${e}"></div>
|
|
549
|
+
`);let h=document.getElementById(`data-grid-container-${e}`);if(!h)return;if((!i||i.length===0)&&!window.forceEmptyGrid)if(n){h.innerHTML=`
|
|
550
|
+
<div class="table-empty-container">
|
|
551
|
+
<div class="table-empty-card">
|
|
552
|
+
<div class="table-empty-icon-wrap">
|
|
553
|
+
<span class="material-symbols-outlined table-empty-icon">search_off</span>
|
|
554
|
+
</div>
|
|
555
|
+
<h3 class="table-empty-title">No matching records found</h3>
|
|
556
|
+
<p class="table-empty-desc">No records in "${e}" matched the current filter conditions.</p>
|
|
557
|
+
<div class="table-empty-actions">
|
|
558
|
+
<button type="button" class="table-empty-btn-primary" id="btn-empty-clear-filter-${e}">
|
|
559
|
+
<span class="material-symbols-outlined icon-16">filter_alt_off</span>
|
|
560
|
+
<span>Clear Filter</span>
|
|
561
|
+
</button>
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
`;let n=document.getElementById(`btn-empty-clear-filter-${e}`);n&&(n.onclick=()=>{let n=document.getElementById(`filter-val-${e}`);n&&(n.value=``),M(e,t,``,!0)})}else{h.innerHTML=`
|
|
566
|
+
<div class="table-empty-container">
|
|
567
|
+
<div class="table-empty-card">
|
|
568
|
+
<div class="table-empty-icon-wrap">
|
|
569
|
+
<span class="material-symbols-outlined table-empty-icon">dataset</span>
|
|
570
|
+
</div>
|
|
571
|
+
<h3 class="table-empty-title">Table "${e}" is empty</h3>
|
|
572
|
+
<p class="table-empty-desc">This table currently contains 0 records. You can insert a new row manually or generate realistic mock data with one click.</p>
|
|
573
|
+
<div class="table-empty-actions">
|
|
574
|
+
<button type="button" class="table-empty-btn-secondary" id="btn-empty-add-row-${e}">
|
|
575
|
+
<span class="material-symbols-outlined icon-16">add</span>
|
|
576
|
+
<span>Insert Row</span>
|
|
577
|
+
</button>
|
|
578
|
+
<button type="button" class="table-empty-btn-primary" id="btn-empty-mock-${e}">
|
|
579
|
+
<span class="material-symbols-outlined icon-16">auto_fix_high</span>
|
|
580
|
+
<span>Generate Mock Data</span>
|
|
581
|
+
</button>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
`;let n=document.getElementById(`btn-empty-mock-${e}`);n&&(n.onclick=()=>{m(e,a,()=>{window.forceEmptyGrid=!1,M(e,t,``,!0)})});let r=document.getElementById(`btn-empty-add-row-${e}`);r&&(r.onclick=()=>{window.forceEmptyGrid=!0,M(e,t,``,!0)})}else{let n=`<table class="data-table" id="data-grid-table-${e}"><thead><tr>`;n+=`<th class="row-header table-corner-header" id="th-row-header-${e}" title="Click or right-click for table actions (Mock Data, Export, Truncate)">#</th>`,s.forEach(e=>{let t=a.find(t=>t.name===e),r=``,i=t?.type||``;if(t){let e=!!t.isPk,n=!!t.fkTarget;e&&n?r=`<span class="col-badge badge-pfk" title="Primary Foreign Key (referencing ${t.fkTarget.table}.${t.fkTarget.column})">PFK</span>`:e?r=`<span class="col-badge badge-pk" title="Primary Key">PK</span>`:(n||t.name.toLowerCase().includes(`id`))&&(r=`<span class="col-badge badge-fk" title="Foreign Key">FK</span>`)}let o=``,s=window.DataGrid.sortState.col===e;s&&(o=`<span class="material-symbols-outlined th-sort-icon">${window.DataGrid.sortState.asc?`arrow_upward`:`arrow_downward`}</span>`),n+=`<th class="sortable ${s?`sorted`:``}" data-col="${e}">
|
|
586
|
+
<div class="th-content-wrapper">
|
|
587
|
+
<div class="th-col-info">
|
|
588
|
+
<div class="th-name-row">
|
|
589
|
+
<span class="th-col-name">${e}</span>
|
|
590
|
+
${r}
|
|
591
|
+
</div>
|
|
592
|
+
${i?`<span class="th-col-type">${i}</span>`:``}
|
|
593
|
+
</div>
|
|
594
|
+
<div class="th-sort-indicator ${s?`active`:``}">
|
|
595
|
+
${o}
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
</th>`}),n+=`</tr></thead><tbody>`,i&&i.length>0&&i.forEach((e,t)=>{n+=`<tr>${se(e,t,u,s,a)}</tr>`}),n+=`<tr class="ghost-row-tr">`;let r=i?i.length:0;n+=`<td class="row-header" data-row-idx="${r}">*</td>`,s.forEach((e,t)=>{n+=`<td class="data-cell ghost-row" data-row-idx="${r}" data-col-idx="${t}" data-insert-index="0" data-col="${e}">${t===0?`<span class="ghost-cell-hint">+ Add Row</span>`:``}</td>`}),n+=`</tr></tbody></table>`,h.innerHTML=n,h.querySelectorAll(`th.sortable`).forEach(n=>{n.onclick=r=>{if(window.DataGrid&&window.DataGrid.isResizing)return;if(Object.keys(window.DataGrid.pendingEdits).length>0||window.DataGrid.pendingInserts.length>1||window.DataGrid.pendingDeletes.size>0){alert(`You have unsaved changes. Please save changes before sorting.`);return}let i=n.dataset.col,a=e,o=window.TableStates?.[a]?.dataGrid||window.DataGrid;o&&(o.sortState.col===i?o.sortState.asc=!o.sortState.asc:(o.sortState.col=i,o.sortState.asc=!0),o.pagination.offset=0),M(a,t,T(),!0)},ne(n,window.DataGrid)}),te(h,`data-grid-table-${e}`,window.DataGrid,s.length),oe(h,a,s),ue(h,e);let o=document.getElementById(`th-row-header-${e}`);if(o){let n=n=>{n.preventDefault&&n.preventDefault(),n.stopPropagation&&n.stopPropagation();let r=T(),i=window.DataGrid?.sortState,o=!!r,s=t=>{let n=`/api/tables/${encodeURIComponent(e)}/export?format=${t}`;return r&&(n+=`&where=${encodeURIComponent(r)}`),i&&i.col&&(n+=`&orderCol=${encodeURIComponent(i.col)}&orderAsc=${i.asc}`),n};f(n,[{icon:`auto_fix_high`,label:`Generate Mock Data...`,action:()=>{m(e,a,()=>{M(e,t,T(),!0)})}},{icon:`content_copy`,label:`Copy Table Name`,action:async()=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement(`textarea`);t.value=e,document.body.appendChild(t),t.select(),document.execCommand(`copy`),t.remove()}window.showToast&&window.showToast(`Copied "${e}"`,`success`)}},{type:`divider`},{icon:`download`,label:o?`Export as CSV (Filtered)`:`Export as CSV`,action:()=>{window.open(s(`csv`),`_blank`)}},{icon:`download`,label:o?`Export as JSON (Filtered)`:`Export as JSON`,action:()=>{window.open(s(`json`),`_blank`)}},{type:`divider`},{icon:`mop`,label:`Truncate Table (Clear Rows)`,danger:!0,action:async()=>{if(confirm(`Are you sure you want to delete ALL rows from table "${e}"? This cannot be undone!`)){let n=await l(`DELETE FROM "${e}";`);n.success?(window.showToast&&window.showToast(`Cleared all rows from "${e}"`,`info`),M(e,t,``,!0)):alert(`Failed to clear table: ${n.error}`)}}}])};o.addEventListener(`click`,n),o.addEventListener(`contextmenu`,n)}}let g=document.getElementById(`btn-fk-back-${e}`);if(g&&(g.onclick=()=>{let e=window.fkNavHistory?.fromTable;if(window.fkNavHistory=null,e){let t=document.querySelector(`.table-btn[data-table="${e}"]`)||Array.from(document.querySelectorAll(`.table-btn`)).find(t=>t.textContent.includes(e));window.AppState.currentTable=e,window.AppState.currentTableBtnElement=t||null,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),t&&t.classList.add(`active`);let n=`view-data-btn-${e}`,r=document.getElementById(n);r&&(r.innerHTML=``),window.renderCurrentView()}}),!r){let n=document.getElementById(`filter-val-${e}`),r=(n=!0)=>{n&&window.DataGrid&&(window.DataGrid.pagination.offset=0),M(e,t,T(),!0)},i=document.getElementById(`btn-clear-filter-${e}`),c=()=>{i&&(n?.value.trim()?i.classList.remove(`hidden`):i.classList.add(`hidden`))};i&&n&&i.addEventListener(`click`,()=>{n.value=``,i.classList.add(`hidden`),r(!0),n.focus()});let l;n&&n.addEventListener(`input`,()=>{c(),clearTimeout(l),l=setTimeout(()=>r(!0),400)});let d=document.getElementById(`filter-col-${e}`),f=document.getElementById(`filter-op-${e}`);d&&d.addEventListener(`change`,()=>r(!0)),f&&f.addEventListener(`change`,()=>r(!0));let p=()=>{(Object.keys(window.DataGrid.pendingEdits).length>0||window.DataGrid.pendingInserts.length>1||window.DataGrid.pendingDeletes.size>0)&&!confirm(`You have unsaved changes. Are you sure you want to refresh and discard them?`)||(window.DataGrid.pendingEdits={},window.DataGrid.pendingInserts=[{}],window.DataGrid.pendingDeletes=new Set,window.DataGrid.history=[],window.DataGrid.currentTransaction=null,window.updateSidebarDirtyState?.(),r(!1))},m=document.getElementById(`btn-refresh-data-${e}`);m&&(m.onclick=p),window.DataGrid.refreshData=p;let h=async()=>{if(window.DataGrid.pagination.isLoading||!window.DataGrid.pagination.hasMore)return;window.DataGrid.pagination.isLoading=!0,window.DataGrid.pagination.offset+=window.DataGrid.pagination.limit;let t={where:T(),limit:window.DataGrid.pagination.limit,offset:window.DataGrid.pagination.offset,orderCol:window.DataGrid.sortState.col,orderAsc:window.DataGrid.sortState.asc};try{let n=await o(e,t);if(window.DataGrid.pagination.isLoading=!1,n.success&&n.data){let t=n.data.rows;window.DataGrid.pagination.hasMore=t.length===window.DataGrid.pagination.limit;let r=document.querySelector(`#data-grid-table-${e} tbody`);if(!r)return;let i=r.querySelector(`td[data-insert-index]`)?.closest(`tr`),o=window.DataGrid.pagination.offset;t.forEach((e,t)=>{let n=document.createElement(`tr`);n.innerHTML=se(e,o+t,u,s,a),i?r.insertBefore(n,i):r.appendChild(n)});let c=o+t.length,l=r.querySelectorAll(`td[data-insert-index]`),d=new Set;l.forEach(e=>{let t=e.closest(`tr`);if(d.has(t))return;d.add(t);let n=t.querySelector(`.row-header`);n&&(n.dataset.rowIdx=c),t.querySelectorAll(`td.data-cell`).forEach(e=>e.dataset.rowIdx=c),c++})}}catch{window.DataGrid.pagination.isLoading=!1}};setTimeout(()=>{let t=document.getElementById(`data-grid-container-${e}`);t&&t.addEventListener(`scroll`,e=>{let{scrollTop:t,scrollHeight:n,clientHeight:r}=e.target;t+r>=n-50&&h()})},50)}}else c.innerHTML=`<div class="p-6 text-error">Error: ${d.error}</div>`}catch(e){c.innerHTML=`<div class="p-6 text-error">Failed to load data: ${e.message}</div>`}}async function de(){if(!window.SchemaGrid)return;let{pendingEdits:e,pendingInserts:t,pendingDeletes:n}=window.SchemaGrid,r=window.AppState.currentTable;if(!r)return;let{pendingIndexEdits:i}=window.SchemaGrid,a=n&&n.size>0||Object.keys(e||{}).length>0||t&&t.some(e=>e.name&&e.name.trim()!==``),o=i&&(i.added.length>0||i.dropped.length>0);if(!a&&!o)return;if(window.AppState.dbType===`sqlite`&&a)try{let a=window.SchemaGrid.schema||[],s={},c=[];for(let t of a){if(n&&n.has(t.name))continue;let r=e[t.name]||{},i=t.name;r.name&&r.name!==t.name&&(s[t.name]=r.name,i=r.name);let a=r.type===void 0?t.type:r.type,o=t.isPk,l=t.fkTarget;if(r.isPk!==void 0){let e=String(r.isPk).toUpperCase();if(o=e===`1`||e===`TRUE`||e===`YES`||e.includes(`PK`)||e===`KEY`,e.includes(`FK:`)||e.includes(`PFK:`)){let e=String(r.isPk).match(/(?:FK|PFK):\s*([^\s,]+)/i);if(e&&e[1]){let t=e[1].split(`.`);t.length===2&&(l={table:t[0],column:t[1]})}}else l=void 0}let u=r.nullable===void 0?t.nullable:r.nullable,d=!(u===`0`||u===`No`||u===!1),f=r.defaultValue===void 0?t.defaultValue:r.defaultValue,p=t.isUnique;r.isUnique!==void 0&&(p=r.isUnique===`Yes`||r.isUnique===`1`||r.isUnique===!0),c.push({name:i,type:a||`TEXT`,isPk:!!o,nullable:d,defaultValue:f||void 0,isUnique:!!p,fkTarget:l})}if(t)for(let e of t){if(!e.name||e.name.trim()===``)continue;let t=e.name.trim(),n=e.type||`TEXT`,r=String(e.isPk||``).toUpperCase(),i=r===`1`||r===`TRUE`||r===`YES`||r.includes(`PK`)||r===`KEY`,a;if(r.includes(`FK:`)||r.includes(`PFK:`)){let t=String(e.isPk).match(/(?:FK|PFK):\s*([^\s,]+)/i);if(t&&t[1]){let e=t[1].split(`.`);e.length===2&&(a={table:e[0],column:e[1]})}}let o=!(e.nullable===`0`||e.nullable===`false`||e.nullable===`No`),s=String(e.isUnique||``).toUpperCase()===`YES`||e.isUnique===`1`||e.isUnique===!0;c.push({name:t,type:n,isPk:i,nullable:o,defaultValue:e.defaultValue||void 0,isUnique:s,fkTarget:a})}let u=await(await fetch(`/api/tables/${encodeURIComponent(r)}/schema`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({columns:c,renames:s})})).json();if(!u.success)throw Error(u.error||`Failed to recreate table`);if(o){for(let e of i.dropped)e&&await l(`DROP INDEX "${e}";`);for(let e of i.added){let t=e.name?`"${e.name}"`:`"idx_${r}_${e.columns.join(`_`)}_${Date.now()}"`;await l(`CREATE ${e.isUnique?`UNIQUE`:``} INDEX IF NOT EXISTS ${t} ON "${r}" (${e.columns.map(e=>`"${e}"`).join(`, `)});`)}}window.SchemaGrid.pendingEdits={},window.SchemaGrid.pendingInserts=[{}],window.SchemaGrid.pendingDeletes=new Set,window.SchemaGrid.pendingIndexEdits={added:[],dropped:[]},window.SchemaGrid.history=[],window.SchemaGrid.currentTransaction=null,window.TableStates?.[r]&&(window.TableStates[r].dataGrid=null);let d=document.getElementById(`btn-refresh-schema-${r}`);d?d.click():window.renderCurrentView(),window.updateSidebarDirtyState?.(),window.loadTableStats?.(),window.showToast&&window.showToast(`Schema saved successfully via Table Re-creation!`,`success`);return}catch(e){window.showToast?window.showToast(`Migration failed: ${e.message}`,`error`):alert(`Migration failed: ${e.message}`);return}let s=[];if(n)for(let e of n)s.push(`ALTER TABLE "${r}" DROP COLUMN "${e}";`);for(let[t,n]of Object.entries(e)){let e=t;if(n.name&&n.name!==t&&(s.push(`ALTER TABLE "${r}" RENAME COLUMN "${t}" TO "${n.name}";`),e=n.name),Object.keys(n).filter(e=>e!==`name`).length>0){let i=window.SchemaGrid.schema?.find(e=>e.name===t)||{},a=n.type===void 0?i.type:n.type,o=[],c=n.isPk===void 0?i.isPk:n.isPk,l=String(c).toUpperCase();if((l===`1`||l===`TRUE`||l===`YES`||l.includes(`PK`)||l===`KEY`)&&o.push(`PRIMARY KEY`),l.includes(`FK:`)){let e=l.match(/FK:\s*([^\s,]+)/i);if(e&&e[1]){let t=e[1].split(`.`);t.length===2&&o.push(`REFERENCES "${t[0]}"("${t[1]}")`)}}let u=n.nullable===void 0?i.nullable:n.nullable;(u===`0`||u===`No`||u===!1)&&o.push(`NOT NULL`);let d=n.defaultValue===void 0?i.defaultValue:n.defaultValue;if(d&&o.push(`DEFAULT '${String(d).replace(/'/g,`''`)}'`),window.AppState.dbType===`sqlite`){alert(`SQLite does not support altering column types directly. Please recreate the table or use Raw SQL.`);return}else window.AppState.dbType===`postgres`?(s.push(`ALTER TABLE "${r}" ALTER COLUMN "${e}" TYPE ${a};`),o.includes(`NOT NULL`)&&s.push(`ALTER TABLE "${r}" ALTER COLUMN "${e}" SET NOT NULL;`),d&&s.push(`ALTER TABLE "${r}" ALTER COLUMN "${e}" SET DEFAULT '${String(d).replace(/'/g,`''`)}';`)):s.push(`ALTER TABLE "${r}" MODIFY COLUMN "${e}" ${a} ${o.join(` `)};`)}if(n.isUnique!==void 0){let t=n.isUnique===`Yes`||n.isUnique===`1`||n.isUnique===!0,i=`idx_${r}_${e}_unique`;t?s.push(`CREATE UNIQUE INDEX IF NOT EXISTS "${i}" ON "${r}" ("${e}");`):s.push(`DROP INDEX IF EXISTS "${i}";`)}}for(let e=0;e<t.length;e++){let n=t[e];if(Object.keys(n).length===0||!n.name||n.name.trim()===``)continue;let i=n.name.trim(),a=n.type||`TEXT`,o=[],c=String(n.isPk||``).toUpperCase();if((c===`1`||c===`TRUE`||c===`YES`||c.includes(`PK`)||c===`KEY`)&&o.push(`PRIMARY KEY`),c.includes(`FK:`)){let e=c.match(/FK:\s*([^\s,]+)/i);if(e&&e[1]){let t=e[1].split(`.`);t.length===2&&o.push(`REFERENCES "${t[0]}"("${t[1]}")`)}}(n.nullable===`0`||n.nullable===`false`||n.nullable===`No`)&&o.push(`NOT NULL`);let l=String(n.isUnique||``).toUpperCase();(l===`YES`||l===`1`||l===`TRUE`)&&o.push(`UNIQUE`),n.defaultValue&&o.push(`DEFAULT '${n.defaultValue.replace(/'/g,`''`)}'`),s.push(`ALTER TABLE "${r}" ADD COLUMN "${i}" ${a} ${o.join(` `)};`)}if(window.SchemaGrid.pendingIndexEdits){for(let e of window.SchemaGrid.pendingIndexEdits.dropped)e&&e.trim()!==``&&s.push(`DROP INDEX "${e}";`);for(let e of window.SchemaGrid.pendingIndexEdits.added){let t=``;t=e.name&&e.name.trim()!==``?`"${e.name}"`:`"idx_${r}_${e.columns.join(`_`)}_${Date.now()}"`;let n=e.isUnique?`UNIQUE`:``,i=e.columns.map(e=>`"${e}"`).join(`, `);s.push(`CREATE ${n} INDEX IF NOT EXISTS ${t} ON "${r}" (${i});`)}}if(s.length===0)return;let c=!0,u=``;for(let e of s)try{let t=await l(e);if(!t.success){c=!1,u=t.error;break}}catch(e){c=!1,u=e.message;break}if(c){window.SchemaGrid&&(window.SchemaGrid.pendingEdits={},window.SchemaGrid.pendingInserts=[{}],window.SchemaGrid.pendingDeletes=new Set,window.SchemaGrid.pendingIndexEdits={added:[],dropped:[]},window.SchemaGrid.history=[],window.SchemaGrid.currentTransaction=null);let e=document.getElementById(`btn-refresh-schema-${r}`);e?e.click():window.renderCurrentView(),window.showToast&&window.showToast(`Schema saved successfully!`)}else u.includes(`near "MODIFY": syntax error`)?u=`SQLite: Currently does not support modifying existing column types or constraints directly. You can only Rename columns or Add new columns.`:u.includes(`syntax error`)&&(u=`SQLite: `+u),window.showToast?window.showToast(`Save failed: `+u,`error`):alert(`Save failed:
|
|
599
|
+
`+u),document.querySelectorAll(`.cell-edited`).forEach(e=>{e.classList.remove(`cell-edited`),e.classList.add(`cell-error`)});window.updateSidebarDirtyState?.()}function N(e,t,n,r=!0){if(r&&window.SchemaGrid.currentTransaction){let n=e.textContent;(n===`null`||e.classList.contains(`ghost-row`))&&(n=``),n!==t&&window.SchemaGrid.currentTransaction.push({td:e,oldVal:n,newVal:t})}let i=e.dataset.colKey,a=e.dataset.colIdx===`0`?`<span class="ghost-cell-hint">+ Add column</span>`:``;if(e.innerHTML=t||(e.dataset.insertIndex===void 0?`<span class="text-soft">-</span>`:a),e.dataset.insertIndex!==void 0){let r=parseInt(e.dataset.insertIndex);if(window.SchemaGrid.pendingInserts[r]||(window.SchemaGrid.pendingInserts[r]={}),t){if(window.SchemaGrid.pendingInserts[r][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`ghost-row`),r===window.SchemaGrid.pendingInserts.length-1){e.closest(`tr`).classList.remove(`ghost-row-tr`),window.SchemaGrid.pendingInserts.push({});let t=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} tbody`),i=document.createElement(`tr`);i.className=`ghost-row-tr`;let a=parseInt(e.dataset.rowIdx)+1;i.innerHTML+=`<td class="row-header" data-row-idx="${a}">*</td>`,n.forEach((e,t)=>{if(e===`indexing`)return;let n=t===0?`<span class="ghost-cell-hint">+ Add column</span>`:``;i.innerHTML+=`<td class="data-cell ghost-row" data-row-idx="${a}" data-col-idx="${t}" data-insert-index="${r+1}" data-col-key="${e}">${n}</td>`}),t.appendChild(i)}}else window.SchemaGrid.pendingInserts[r]&&delete window.SchemaGrid.pendingInserts[r][i],e.classList.remove(`cell-edited`),e.classList.add(`ghost-row`)}else{let n=e.dataset.pk,r=e.dataset.original;r===`-`&&(r=``),t===r?(e.classList.remove(`cell-edited`),e.classList.remove(`cell-error`),window.SchemaGrid.pendingEdits[n]&&(delete window.SchemaGrid.pendingEdits[n][i],Object.keys(window.SchemaGrid.pendingEdits[n]).length===0&&delete window.SchemaGrid.pendingEdits[n])):(window.SchemaGrid.pendingEdits[n]||(window.SchemaGrid.pendingEdits[n]={}),window.SchemaGrid.pendingEdits[n][i]=t,e.classList.add(`cell-edited`),e.classList.remove(`cell-error`))}window.updateSidebarDirtyState?.()}function P(e,t=!0){let n=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!n||n.classList.contains(`ghost-row-tr`)||n.classList.contains(`row-deleted`))return;let r=n.querySelector(`td.data-cell`)?.dataset.pk;r!==void 0&&(t&&window.SchemaGrid.currentTransaction&&window.SchemaGrid.currentTransaction.push({type:`delete`,rowIdx:e,pk:r}),window.SchemaGrid.pendingDeletes.add(r),n.classList.add(`row-deleted`)),window.updateSidebarDirtyState?.()}function fe(e,t){let n=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);n&&n.classList.remove(`row-deleted`),window.SchemaGrid.pendingDeletes.delete(t),window.updateSidebarDirtyState?.()}function pe(e,t){e.forEach(e=>{let n=window.AppState.currentTable,r=document.querySelector(`#schema-grid-table-${n} td.data-cell[data-row-idx="${e}"]`)?.closest(`tr`);if(!r||r.classList.contains(`ghost-row-tr`))return;let i=document.querySelector(`#schema-grid-table-${n} .ghost-row-tr`);i&&t.forEach((e,n)=>{let a=r.querySelector(`td.data-cell[data-col-idx="${n}"]`),o=i.querySelector(`td.data-cell[data-col-idx="${n}"]`);if(a&&o){let n=a.dataset.insertIndex===void 0?a.classList.contains(`cell-edited`)?window.SchemaGrid.pendingEdits[a.dataset.pk]?.[e]:a.dataset.original:window.SchemaGrid.pendingInserts[a.dataset.insertIndex][e];n!=null&&n!==`null`&&(e===`name`&&(n+=`_copy`),N(o,n,t,!0))}})})}function me(){let e=window.SchemaGrid;if(!e)return;let t=document.getElementById(`index-modal`);t&&t.remove();let n=[...e.indexes],r=[...e.pendingIndexEdits.added],i=new Set(e.pendingIndexEdits.dropped),a=n.filter(e=>!i.has(e.name)).concat(r),o=e.schema.map(e=>e.name);e.pendingInserts.forEach(e=>{e.name&&e.name.trim()!==``&&o.push(e.name.trim())});let s=[];t=document.createElement(`div`),t.id=`index-modal`,t.className=`modal-overlay`,t.innerHTML=`
|
|
600
|
+
<div class="modal-container idx-modal-container">
|
|
601
|
+
<div class="modal-header">
|
|
602
|
+
<div class="flex items-center gap-2">
|
|
603
|
+
<span class="material-symbols-outlined text-primary" style="font-size:20px;">key</span>
|
|
604
|
+
<h3 class="m-0 text-15 font-semibold">Manage Indexes</h3>
|
|
605
|
+
</div>
|
|
606
|
+
<button id="close-idx-modal" class="modal-close-btn" title="Close"><span class="material-symbols-outlined">close</span></button>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
<div class="modal-body-scroll" style="max-height: 220px; padding: 16px;">
|
|
610
|
+
<div class="idx-section-title">Current Indexes</div>
|
|
611
|
+
<div id="idx-list-container"></div>
|
|
612
|
+
</div>
|
|
613
|
+
|
|
614
|
+
<div class="modal-section-bg" style="padding: 16px 20px;">
|
|
615
|
+
<div class="idx-section-title" style="margin-bottom: 24px;">+ Create New Index</div>
|
|
616
|
+
<div class="flex flex-col gap-3">
|
|
617
|
+
<div>
|
|
618
|
+
<label class="text-11 text-secondary block mb-1.5">Index Name</label>
|
|
619
|
+
<input type="text" id="new-idx-name" placeholder="Optional (auto-generated if empty)" class="modal-input w-full" style="padding: 8px 12px; font-size: 13px;" />
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<div>
|
|
623
|
+
<div class="flex items-center justify-between mb-1">
|
|
624
|
+
<label class="text-11 text-secondary">Columns (click to select in order)</label>
|
|
625
|
+
<button type="button" id="btn-clear-selected-cols" class="idx-clear-btn hidden" title="Clear column selection">
|
|
626
|
+
<span class="material-symbols-outlined" style="font-size:12px; line-height:1;">close</span>
|
|
627
|
+
<span>Clear selection</span>
|
|
628
|
+
</button>
|
|
629
|
+
</div>
|
|
630
|
+
<div id="idx-chips-container" class="idx-chips-container"></div>
|
|
631
|
+
</div>
|
|
632
|
+
|
|
633
|
+
<div class="flex items-center justify-between pt-1">
|
|
634
|
+
<label class="flex items-center gap-2 cursor-pointer select-none text-13">
|
|
635
|
+
<input type="checkbox" id="new-idx-unique" style="accent-color: var(--color-primary);" />
|
|
636
|
+
<span>Unique Index</span>
|
|
637
|
+
</label>
|
|
638
|
+
<button type="button" id="add-idx-btn" class="btn-add-idx">
|
|
639
|
+
<span class="material-symbols-outlined">add</span>
|
|
640
|
+
<span>Add to List</span>
|
|
641
|
+
</button>
|
|
642
|
+
</div>
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<div class="modal-footer">
|
|
647
|
+
<button id="cancel-idx-btn" class="btn-secondary">Cancel</button>
|
|
648
|
+
<button id="save-idx-btn" class="btn-primary">Save Changes</button>
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
`,document.body.appendChild(t);let c=()=>t.remove();document.getElementById(`close-idx-modal`).onclick=c,document.getElementById(`cancel-idx-btn`).onclick=c;let l=()=>{let e=document.getElementById(`idx-chips-container`),t=document.getElementById(`btn-clear-selected-cols`);e&&(t&&(s.length>0?(t.classList.remove(`hidden`),t.onclick=()=>{s=[],l()}):t.classList.add(`hidden`)),e.innerHTML=o.map(e=>{let t=s.indexOf(e),n=t!==-1,r=n?`<span class="idx-chip-badge">${t+1}</span>`:``;return`
|
|
652
|
+
<div class="idx-chip ${n?`selected`:``}" data-col="${e}">
|
|
653
|
+
${r}
|
|
654
|
+
<span>${e}</span>
|
|
655
|
+
</div>
|
|
656
|
+
`}).join(``),e.querySelectorAll(`.idx-chip`).forEach(e=>{e.onclick=()=>{let t=e.dataset.col,n=s.indexOf(t);n===-1?s.push(t):s.splice(n,1),l()}}))};l();let u=()=>{let e=document.getElementById(`idx-list-container`);if(e){if(a.length===0){e.innerHTML=`
|
|
657
|
+
<div style="display:flex; flex-direction:column; align-items:center; justify-content:center; padding: 24px 0; color: var(--color-text-soft);">
|
|
658
|
+
<span class="material-symbols-outlined" style="font-size:32px; opacity:0.4; margin-bottom:6px;">dataset</span>
|
|
659
|
+
<div style="font-size:12px;">No indexes defined for this table yet.</div>
|
|
660
|
+
</div>
|
|
661
|
+
`;return}e.innerHTML=a.map((e,t)=>`
|
|
662
|
+
<div class="idx-card">
|
|
663
|
+
<div class="idx-card-info">
|
|
664
|
+
<div class="idx-card-header">
|
|
665
|
+
<span class="idx-name">${e.name||`unnamed_idx`}</span>
|
|
666
|
+
${e.isUnique?`<span class="badge-unique" style="font-size:10px; padding:1px 6px; line-height:14px;">UNIQUE</span>`:``}
|
|
667
|
+
</div>
|
|
668
|
+
<div class="idx-cols-tags">
|
|
669
|
+
${e.columns.map(e=>`<span class="idx-col-pill">${e}</span>`).join(``)}
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
<button type="button" class="idx-del-btn delete-idx-btn" data-idx="${t}" title="Remove index">
|
|
673
|
+
<span class="material-symbols-outlined">delete</span>
|
|
674
|
+
</button>
|
|
675
|
+
</div>
|
|
676
|
+
`).join(``),e.querySelectorAll(`.delete-idx-btn`).forEach(e=>{e.onclick=e=>{let t=parseInt(e.currentTarget.dataset.idx,10);a.splice(t,1),u()}})}};u(),document.getElementById(`add-idx-btn`).onclick=()=>{if(s.length===0){window.showToast?window.showToast(`Please select at least one column for the index.`,`warning`):alert(`Please select at least one column for the index.`);return}let e=window.AppState?.currentTable||`table`,t=document.getElementById(`new-idx-name`).value.trim()||`idx_${e}_${s.join(`_`)}`,n=document.getElementById(`new-idx-unique`).checked;a.push({name:t,columns:[...s],isUnique:n}),document.getElementById(`new-idx-name`).value=``,document.getElementById(`new-idx-unique`).checked=!1,s=[],l(),u()},document.getElementById(`save-idx-btn`).onclick=()=>{let t=[],r=new Set;n.forEach(e=>{a.find(t=>t.name===e.name&&JSON.stringify(t.columns)===JSON.stringify(e.columns))||r.add(e.name)}),a.forEach(e=>{n.find(t=>t.name===e.name&&JSON.stringify(t.columns)===JSON.stringify(e.columns))||t.push(e)}),e.pendingIndexEdits.added=t,e.pendingIndexEdits.dropped=Array.from(r),window.updateSidebarDirtyState?.(),window.renderSchemaGrid(),c()}}async function he(e,t){if(!window.SchemaGrid)return;let n=document.getElementById(`pkfk-modal`);n&&n.remove(),e.dataset.insertIndex;let r=e.dataset.original||t||``,i=r.includes(`PK`),a=r.includes(`FK`),o=``,s=``,c=r.match(/(?:FK|PFK)(?:\s*\(|:\s*|\s*→\s*|\s*->\s*)([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)/i);c&&(o=c[1],s=c[2],a=!0),n=document.createElement(`div`),n.id=`pkfk-modal`,n.style.cssText=`
|
|
677
|
+
position: fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.5);
|
|
678
|
+
display:flex; justify-content:center; align-items:center; z-index:9999;
|
|
679
|
+
`,n.innerHTML=`
|
|
680
|
+
<div class="modal-container" class="w-400">
|
|
681
|
+
<div class="modal-header">
|
|
682
|
+
<h3 class="m-0">Manage Keys</h3>
|
|
683
|
+
<button id="close-pkfk-modal" class="modal-close-btn"><span class="material-symbols-outlined">close</span></button>
|
|
684
|
+
</div>
|
|
685
|
+
<div class="modal-body">
|
|
686
|
+
<label class="items-center gap-2 cursor-pointer">
|
|
687
|
+
<input type="checkbox" id="modal-is-pk" ${i?`checked`:``} />
|
|
688
|
+
<strong>Primary Key (PK)</strong>
|
|
689
|
+
</label>
|
|
690
|
+
|
|
691
|
+
<div class="divider-y"></div>
|
|
692
|
+
|
|
693
|
+
<label class="flex items-center gap-2 cursor-pointer">
|
|
694
|
+
<input type="checkbox" id="modal-is-fk" ${a?`checked`:``} />
|
|
695
|
+
<strong>Foreign Key (FK)</strong>
|
|
696
|
+
</label>
|
|
697
|
+
|
|
698
|
+
<div id="fk-settings-container" class="flex-col gap-2 pl-6 ${a?`flex`:`hidden`}">
|
|
699
|
+
<div>
|
|
700
|
+
<div class="text-12 text-secondary mb-1">Target Table</div>
|
|
701
|
+
<select id="modal-fk-table" class="modal-input w-full">
|
|
702
|
+
<option value="">Loading tables...</option>
|
|
703
|
+
</select>
|
|
704
|
+
</div>
|
|
705
|
+
<div>
|
|
706
|
+
<div class="text-12 text-secondary mb-1">Target Column</div>
|
|
707
|
+
<select id="modal-fk-col" class="modal-input w-full" ${o?``:`disabled`}>
|
|
708
|
+
<option value="">Select a table first</option>
|
|
709
|
+
</select>
|
|
710
|
+
</div>
|
|
711
|
+
</div>
|
|
712
|
+
</div>
|
|
713
|
+
<div class="modal-footer">
|
|
714
|
+
<button id="cancel-pkfk-btn" class="secondary" class="btn-secondary">Cancel</button>
|
|
715
|
+
<button id="save-pkfk-btn" class="primary" class="btn-primary">Save</button>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
`,document.body.appendChild(n);let l=()=>n.remove();document.getElementById(`close-pkfk-modal`).onclick=l,document.getElementById(`cancel-pkfk-btn`).onclick=l;let u=document.getElementById(`modal-is-fk`),d=document.getElementById(`fk-settings-container`),f=document.getElementById(`modal-fk-table`),p=document.getElementById(`modal-fk-col`);u.addEventListener(`change`,e=>{d.style.display=e.target.checked?`flex`:`none`});try{let e=await(await fetch(`/api/tables`)).json();e.success&&e.data&&(f.innerHTML=`<option value="">-- Select Table --</option>`+e.data.map(e=>`<option value="${e}" ${e===o?`selected`:``}>${e}</option>`).join(``),o&&m(o,s))}catch{f.innerHTML=`<option value="">Error loading tables</option>`}async function m(e,t=``){p.disabled=!0,p.innerHTML=`<option value="">Loading columns...</option>`;try{let n=await(await fetch(`/api/tables/${e}/schema`)).json();n.success&&n.data&&(p.innerHTML=`<option value="">-- Select Column --</option>`+n.data.map(e=>`<option value="${e.name}" ${e.name===t?`selected`:``}>${e.name}</option>`).join(``),p.disabled=!1)}catch{p.innerHTML=`<option value="">Error loading columns</option>`}}f.addEventListener(`change`,e=>{let t=e.target.value;t?m(t):(p.innerHTML=`<option value="">Select a table first</option>`,p.disabled=!0)}),document.getElementById(`save-pkfk-btn`).onclick=()=>{let t=document.getElementById(`modal-is-pk`).checked,n=document.getElementById(`modal-is-fk`).checked,r=f.value,c=p.value;if(t===i&&n===a&&r===o&&c===s){l();return}let u=``;if(t&&n){if(!r||!c){alert(`Please select both Target Table and Target Column for the Foreign Key.`);return}u=`PK, FK: ${r}.${c}`}else if(t)u=`PK`;else if(n){if(!r||!c){alert(`Please select both Target Table and Target Column for the Foreign Key.`);return}u=`FK: ${r}.${c}`}window.SchemaGrid.currentTransaction=[],N(e,u,[`name`,`type`,`isPk`,`nullable`,`defaultValue`,`indexing`]),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null,l()}}function ge(e,t){e.addEventListener(`click`,e=>{e.target.closest(`.manage-indexes-cell`)&&me()}),e.addEventListener(`dblclick`,e=>{let n=e.target.closest(`td.data-cell`);if(!n||n.querySelector(`input, select`))return;let r=n.dataset.colKey,i=n.dataset.insertIndex!==void 0,a=i||n.textContent===`-`||n.textContent===`+ New`||n.textContent.includes(`Add column`)||n.textContent===`+ Add Row`||n.textContent===`null`?``:n.textContent.trim().startsWith(`Yes`)?`1`:n.textContent.trim()===`No`?``:n.textContent,o;if(r===`isPk`){he(n,n.textContent);return}else if(r===`nullable`){let e=n.dataset.pk,t=window.SchemaGrid?.schema?.find(t=>t.name===e),r=t?t.isPk:!1,s=i&&window.SchemaGrid?.pendingInserts?.[parseInt(n.dataset.insertIndex)]?.isPk;if(r||s){window.showToast?window.showToast(`Primary Key cannot be nullable.`,`error`):alert(`Primary Key cannot be nullable.`);return}o=document.createElement(`select`),[`No`,`Yes`].forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,(e===`Yes`&&a===`1`||e===`No`&&a===``)&&(t.selected=!0),o.appendChild(t)})}else if(r===`isUnique`){let e=n.dataset.pk,t=window.SchemaGrid?.schema?.find(t=>t.name===e),r=t?t.isPk:!1,s=i&&window.SchemaGrid?.pendingInserts?.[parseInt(n.dataset.insertIndex)]?.isPk;if(r||s){window.showToast?window.showToast(`Primary Key is always unique and locked.`,`error`):alert(`Primary Key is always unique and locked.`);return}o=document.createElement(`select`),[`No`,`Yes`].forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,(e===`Yes`&&a===`1`||e===`No`&&a===``)&&(t.selected=!0),o.appendChild(t)})}else if(r===`type`){o=document.createElement(`select`);let e=[`INTEGER`,`TEXT`,`REAL`,`BLOB`,`NUMERIC`,`BOOLEAN`,`DATE`,`DATETIME`,`JSON`,`VARCHAR(255)`,`DECIMAL(10,2)`,`UUID`];a&&!e.some(e=>e.toUpperCase()===a.toUpperCase())&&e.unshift(a),e.forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,e.toUpperCase()===a.toUpperCase()&&(t.selected=!0),o.appendChild(t)})}else o=document.createElement(`input`),o.type=`text`,o.value=a;n.innerHTML=``,n.appendChild(o),o.style.width=`100%`,o.addEventListener(`keydown`,e=>{e.key===`Enter`&&o.blur(),e.key===`Tab`&&(e.preventDefault(),o.blur(),document.body.dispatchEvent(new KeyboardEvent(`keydown`,{key:`Tab`,shiftKey:e.shiftKey,bubbles:!0})))}),o.focus();let s=()=>{let e=o.value;window.SchemaGrid.currentTransaction=[],N(n,e,t),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null};o.addEventListener(`blur`,()=>{s()})})}async function _e(e,t,n=null){document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),t&&t.classList.add(`active`);let r=document.getElementById(`table-name`);r&&(r.textContent=e+` (Schema)`);let i=n||document.getElementById(`main-content`);i.innerHTML=`<div class='p-6'>Loading Schema...</div>`;try{let[n,r]=await Promise.all([s(e),c(e)]);if(n.success&&n.data){let a=n.data,o=r.success&&r.data?r.data:[];window.SchemaGrid={schema:a,indexes:o,pendingEdits:{},pendingIndexEdits:{added:[],dropped:[]},pendingInserts:[{}],pendingDeletes:new Set,selectedCell:null,history:[],currentTransaction:null,sortState:{colKey:null,asc:!0},filterText:``,isResizing:!1,selection:{startRow:-1,startCol:-1,endRow:-1,endCol:-1,isDragging:!1}},window.TableStates&&window.TableStates[e]&&(window.TableStates[e].schemaGrid=window.SchemaGrid),i.innerHTML=`
|
|
719
|
+
<div class="toolbar">
|
|
720
|
+
<div class="filter-group">
|
|
721
|
+
<div class="filter-icon-container">
|
|
722
|
+
<span class="material-symbols-outlined">search</span>
|
|
723
|
+
<span>Search</span>
|
|
724
|
+
</div>
|
|
725
|
+
<div class="filter-input-wrapper">
|
|
726
|
+
<input type="text" id="schema-search-val-${e}" class="filter-input" placeholder="Search name or type..." value="${window.SchemaGrid.filterText}" />
|
|
727
|
+
<button type="button" id="btn-clear-schema-search-${e}" class="filter-clear-btn ${window.SchemaGrid.filterText?``:`hidden`}" title="Clear search">
|
|
728
|
+
<span class="material-symbols-outlined">close</span>
|
|
729
|
+
</button>
|
|
730
|
+
</div>
|
|
731
|
+
</div>
|
|
732
|
+
<div class="flex-1"></div>
|
|
733
|
+
<button id="btn-refresh-schema-${e}" class="refresh-btn" title="Refresh Schema (F5)"><span class="material-symbols-outlined">refresh</span></button>
|
|
734
|
+
</div>
|
|
735
|
+
<div id="schema-grid-container-${e}" class="table-container"></div>
|
|
736
|
+
`,window.renderSchemaGrid=()=>{let t=document.getElementById(`schema-grid-container-${e}`);if(!t)return;let n=[...window.SchemaGrid.schema];if(window.SchemaGrid.filterText){let e=window.SchemaGrid.filterText.toLowerCase();n=n.filter(t=>t.name&&t.name.toLowerCase().includes(e)||t.type&&t.type.toLowerCase().includes(e))}window.SchemaGrid.sortState.colKey&&n.sort((e,t)=>{let n=window.SchemaGrid.sortState.colKey,r=e[n],i=t[n];return r??=``,i??=``,r<i?window.SchemaGrid.sortState.asc?-1:1:r>i?window.SchemaGrid.sortState.asc?1:-1:0});let r=[`name`,`type`,`isPk`,`nullable`,`isUnique`,`defaultValue`,`indexing`],i=[`Name`,`Type`,`PK / FK`,`Nullable`,`Unique`,`Default Value`,`Indexing`],a=`<table class="data-table" id="schema-grid-table-${e}"><thead><tr><th class="row-header">#</th>`;r.forEach((e,t)=>{if(e===`indexing`){a+=`<th class="th-indexing" data-col-key="indexing">
|
|
737
|
+
<div class="th-content-wrapper">
|
|
738
|
+
<span class="th-name">${i[t]}</span>
|
|
739
|
+
</div>
|
|
740
|
+
</th>`;return}let n=``;window.SchemaGrid.sortState.colKey===e&&(n=`<span class="material-symbols-outlined th-sort-icon">${window.SchemaGrid.sortState.asc?`arrow_upward`:`arrow_downward`}</span>`),a+=`<th class="sortable" data-col-key="${e}">
|
|
741
|
+
<div class="th-content-wrapper">
|
|
742
|
+
<span class="th-name">${i[t]}</span>
|
|
743
|
+
${n}
|
|
744
|
+
</div>
|
|
745
|
+
</th>`}),a+=`</tr></thead><tbody>`,n&&n.length>0&&n.forEach((e,t)=>{a+=`<tr><td class="row-header" data-row-idx="${t}">${t+1}</td>`,r.forEach((r,i)=>{if(r===`indexing`){if(t===0){let e=(n?n.length:0)+1,t=window.SchemaGrid.indexes.length,r=window.SchemaGrid.pendingIndexEdits.added.length,i=window.SchemaGrid.pendingIndexEdits.dropped.length,o=t+r-i;a+=`<td class="manage-indexes-cell" rowspan="${e}">
|
|
746
|
+
<div class="manage-indexes-wrapper">
|
|
747
|
+
<button type="button" class="btn-manage-indexes" title="Open Index Manager">
|
|
748
|
+
<span class="material-symbols-outlined">key</span>
|
|
749
|
+
<span>Manage Indexes</span>
|
|
750
|
+
<span class="index-count-pill">${o}</span>
|
|
751
|
+
</button>
|
|
752
|
+
</div>
|
|
753
|
+
</td>`}return}let o=e[r],s=``,c=``;if(r===`name`)c=String(o||``).replace(/"/g,`"`),s=`<strong>${String(o||``).replace(/</g,`<`)}</strong>`;else if(r===`type`)c=String(o||``).replace(/"/g,`"`),s=`<span class="schema-type-mono">${String(o||``).replace(/</g,`<`)}</span>`;else if(r===`isPk`){let t=!!e.isPk,n=!!e.fkTarget;t&&n?(c=`PFK: ${e.fkTarget.table}.${e.fkTarget.column}`,s=`<span class="col-badge badge-pfk" title="Primary Foreign Key (Composite Key referencing ${e.fkTarget.table}.${e.fkTarget.column})">PFK → ${e.fkTarget.table}.${e.fkTarget.column}</span>`):t?(c=`PK`,s=`<span class="col-badge badge-pk">PK</span>`):n?(c=`FK: ${e.fkTarget.table}.${e.fkTarget.column}`,s=`<span class="col-badge badge-fk" title="References ${e.fkTarget.table}.${e.fkTarget.column}">FK → ${e.fkTarget.table}.${e.fkTarget.column}</span>`):(c=`-`,s=`<span class="text-soft">-</span>`)}else if(r===`nullable`){let t=!e.isPk&&!!e.nullable;c=t?`Yes`:`No`,s=t?`<span class="text-soft">Yes</span>`:`<span class="badge-notnull">No</span>`}else if(r===`isUnique`){let t=!!e.isPk,n=t||!!e.isUnique;c=n?`Yes`:`No`,s=t?`<span class="badge-unique-pk" title="Primary Key is always unique"><span class="material-symbols-outlined text-12">lock</span> Yes</span>`:n?`<span class="badge-unique">Yes</span>`:`<span class="text-soft">-</span>`}else r===`defaultValue`?(c=String(o??``).replace(/"/g,`"`),s=o!=null&&o!==``?`<span class="schema-type-mono text-soft">${String(o).replace(/</g,`<`)}</span>`:`<span class="text-soft">-</span>`):(c=String(o||``).replace(/"/g,`"`),s=String(o||``).replace(/</g,`<`));a+=`<td class="data-cell" data-row-idx="${t}" data-col-idx="${i}" data-pk="${e.name}" data-col-key="${r}" data-original="${c}">${s}</td>`}),a+=`</tr>`}),a+=`<tr class="ghost-row-tr">`;let o=n?n.length:0;a+=`<td class="row-header" data-row-idx="${o}">*</td>`,r.forEach((e,t)=>{e!==`indexing`&&(a+=`<td class="data-cell ghost-row" data-row-idx="${o}" data-col-idx="${t}" data-insert-index="0" data-col-key="${e}">${t===0?`<span class="ghost-cell-hint">+ Add Column</span>`:``}</td>`)}),a+=`</tr></tbody></table>`,t.innerHTML=a,document.querySelectorAll(`#schema-grid-table-${e} th.sortable`).forEach(e=>{e.onclick=t=>{if(window.SchemaGrid.isResizing)return;if(Object.keys(window.SchemaGrid.pendingEdits).length>0||window.SchemaGrid.pendingInserts.length>1||window.SchemaGrid.pendingDeletes.size>0||window.SchemaGrid.pendingIndexEdits.added.length>0||window.SchemaGrid.pendingIndexEdits.dropped.length>0){alert(`You have unsaved changes! Please press Ctrl+S to save them before sorting.`);return}let n=e.dataset.colKey;window.SchemaGrid.sortState.colKey===n?window.SchemaGrid.sortState.asc=!window.SchemaGrid.sortState.asc:(window.SchemaGrid.sortState.colKey=n,window.SchemaGrid.sortState.asc=!0),window.SchemaGrid.selection={startRow:-1,startCol:-1,endRow:-1,endCol:-1,isDragging:!1},window.renderSchemaGrid()},ne(e,window.SchemaGrid)}),te(t,`schema-grid-table-${e}`,window.SchemaGrid,r.length),ge(t,r)},window.renderSchemaGrid();let s=document.getElementById(`schema-search-val-${e}`),c=document.getElementById(`btn-clear-schema-search-${e}`),l=()=>{!c||!s||(s.value.trim()===``?c.classList.add(`hidden`):c.classList.remove(`hidden`))};s&&s.addEventListener(`input`,e=>{window.SchemaGrid.filterText=e.target.value,l(),window.SchemaGrid.selection={startRow:-1,startCol:-1,endRow:-1,endCol:-1,isDragging:!1},window.renderSchemaGrid()}),c&&(c.onclick=()=>{s&&(s.value=``,window.SchemaGrid.filterText=``,l(),window.renderSchemaGrid(),s.focus())});let u=document.getElementById(`btn-refresh-schema-${e}`);u&&(u.onclick=()=>{(Object.keys(window.SchemaGrid.pendingEdits).length>0||window.SchemaGrid.pendingInserts.length>1||window.SchemaGrid.pendingDeletes.size>0||window.SchemaGrid.pendingIndexEdits.added.length>0||window.SchemaGrid.pendingIndexEdits.dropped.length>0)&&!confirm(`You have unsaved changes. Are you sure you want to refresh and discard them?`)||(window.SchemaGrid.pendingEdits={},window.SchemaGrid.pendingInserts=[{}],window.SchemaGrid.pendingDeletes.clear(),window.SchemaGrid.pendingIndexEdits={added:[],dropped:[]},window.SchemaGrid.history=[],window.SchemaGrid.currentTransaction=null,window.updateSidebarDirtyState?.(),_e(e,t,document.getElementById(`view-schema-btn-${e}`)))})}else i.innerHTML=`<div class="p-6 text-error">Error: ${n.error}</div>`}catch(e){i.innerHTML=`<div class="p-6 text-error">Failed to load schema: ${e.message}</div>`}}var ve=0,ye=(e,t,n)=>{let r=[e.map(e=>`"${String(e).replace(/"/g,`""`)}"`).join(`,`),...t.map(t=>e.map(e=>`"${(t[e]===null?``:String(t[e])).replace(/"/g,`""`)}"`).join(`,`))].join(`\\n`),i=new Blob([r],{type:`text/csv;charset=utf-8;`}),a=URL.createObjectURL(i),o=document.createElement(`a`);o.setAttribute(`href`,a),o.setAttribute(`download`,n),o.style.visibility=`hidden`,document.body.appendChild(o),o.click(),document.body.removeChild(o)},be=async(e,t,n)=>{let r=e||t.value.substring(t.selectionStart,t.selectionEnd).trim();if(r||=t.value.trim(),!r)return;if(r.toLowerCase()===`clear`||r.toLowerCase()===`clear;`){n.innerHTML=``,e||(t.selectionStart=t.selectionEnd,t.value=``);return}window.AppState.lastQuery=t.value,window.AppState.queryHistory||(window.AppState.queryHistory=[]);let i=window.AppState.queryHistory;i.includes(r)||(i.unshift(r),i.length>10&&i.pop(),window.dispatchEvent(new Event(`query-history-updated`))),ve++;let a=`history-block-${ve}`,o=document.getElementById(`console-welcome-msg`);o&&(o.style.display=`none`);let s=document.createElement(`div`);s.className=`console-history-block`,s.innerHTML=`
|
|
754
|
+
<div class="console-history-header">
|
|
755
|
+
<div class="console-history-sql">${r.replace(/</g,`<`).replace(/>/g,`>`)}</div>
|
|
756
|
+
<div class="console-history-actions">
|
|
757
|
+
<button class="icon-btn copy-btn" title="Copy SQL"><span class="material-symbols-outlined" class="icon-16">content_copy</span></button>
|
|
758
|
+
<button class="icon-btn rerun-btn" title="Re-run"><span class="material-symbols-outlined" class="icon-16">refresh</span></button>
|
|
759
|
+
</div>
|
|
760
|
+
</div>
|
|
761
|
+
<div id="${a}-results" class="console-history-results">
|
|
762
|
+
<div class="console-msg-executing">
|
|
763
|
+
<span class="material-symbols-outlined" class="animate-spin">sync</span> Executing...
|
|
764
|
+
</div>
|
|
765
|
+
</div>
|
|
766
|
+
`,n.appendChild(s),s.querySelector(`.copy-btn`).onclick=()=>{navigator.clipboard.writeText(r),window.showToast?window.showToast(`SQL copied to clipboard`):alert(`Copied`)},s.querySelector(`.rerun-btn`).onclick=()=>be(r,t,n),n.scrollTop=n.scrollHeight,e||(t.selectionStart=t.selectionEnd,t.value=``);try{let e=performance.now(),t=await l(r),i=(performance.now()-e).toFixed(1),o=document.getElementById(`${a}-results`);if(!t.success){o.innerHTML=`<div class="console-msg-error"><span class="material-symbols-outlined" class="icon-16 mt-2px">error</span><span>Error: ${t.error}</span></div>`,n.scrollTop=n.scrollHeight;return}let s=t.data.rows||[],c=s.length;if(s.length===1&&s[0].Result===`Success`&&s[0].AffectedRows!==void 0||c===0&&t.data.affectedRows!==void 0){let e=t.data.affectedRows??s[0]?.AffectedRows??0;o.innerHTML=`
|
|
767
|
+
<div class="console-mutation-banner">
|
|
768
|
+
<div class="console-mutation-info">
|
|
769
|
+
<span class="material-symbols-outlined icon">task_alt</span>
|
|
770
|
+
<span>Query OK, <b>${e}</b> row${e===1?``:`s`} affected</span>
|
|
771
|
+
<span class="text-soft">·</span>
|
|
772
|
+
<span class="text-soft">${i}ms</span>
|
|
773
|
+
</div>
|
|
774
|
+
</div>
|
|
775
|
+
`,n.scrollTop=n.scrollHeight;return}if(c===0){o.innerHTML=`
|
|
776
|
+
<div class="console-mutation-banner">
|
|
777
|
+
<div class="console-mutation-info">
|
|
778
|
+
<span class="material-symbols-outlined icon">task_alt</span>
|
|
779
|
+
<span>Query OK, 0 rows returned</span>
|
|
780
|
+
<span class="text-soft">·</span>
|
|
781
|
+
<span class="text-soft">${i}ms</span>
|
|
782
|
+
</div>
|
|
783
|
+
</div>
|
|
784
|
+
`,n.scrollTop=n.scrollHeight;return}let u=Object.keys(s[0]),d=document.createElement(`div`);d.className=`console-stats-header console-stats-divider`;let f=s.slice(0,1e3),p=c>1e3?` (Showing first 1000)`:``,m=t.data.affectedRows!==void 0&&t.data.affectedRows>0?` · ${t.data.affectedRows} affected`:``;d.innerHTML=`
|
|
785
|
+
<div class="console-stats-info">
|
|
786
|
+
<span class="material-symbols-outlined icon">check_circle</span>
|
|
787
|
+
<span>Query OK, <b>${c}</b> row${c===1?``:`s`}${p}${m} · ${i}ms</span>
|
|
788
|
+
</div>
|
|
789
|
+
<button class="console-export-btn export-btn" title="Export current results as CSV">
|
|
790
|
+
<span class="material-symbols-outlined" style="font-size: 15px;">download</span> CSV
|
|
791
|
+
</button>
|
|
792
|
+
`,o.innerHTML=``,o.appendChild(d),d.querySelector(`.export-btn`).onclick=()=>{ye(u,s,`drixio_export_${new Date().getTime()}.csv`)};let h=document.createElement(`div`);h.className=`table-container`,h.style.border=`1px solid var(--color-border)`,h.style.borderRadius=`6px`,h.style.overflow=`auto`,h.style.maxHeight=`320px`;let g=document.createElement(`table`);g.className=`data-table`;let _=document.createElement(`thead`);_.style.position=`sticky`,_.style.top=`0`,_.style.zIndex=`10`;let v=document.createElement(`tr`),y=document.createElement(`th`);y.className=`row-header`,y.textContent=`#`,v.appendChild(y),u.forEach(e=>{let t=document.createElement(`th`);t.textContent=e,v.appendChild(t)}),_.appendChild(v),g.appendChild(_);let b=document.createElement(`tbody`);f.forEach((e,t)=>{let n=document.createElement(`tr`),r=document.createElement(`td`);r.className=`row-header`,r.textContent=t+1,n.appendChild(r),u.forEach(t=>{let r=document.createElement(`td`);r.className=`data-cell data-cell-truncate`;let i=e[t];if(i===null){let e=document.createElement(`span`);e.textContent=`NULL`,e.classList.add(`text-soft`,`italic`),r.appendChild(e)}else{let e=String(i);r.textContent=e,e.length>40&&(r.title=e)}n.appendChild(r)}),b.appendChild(n)}),g.appendChild(b),h.appendChild(g),o.appendChild(h),setTimeout(()=>n.scrollTop=n.scrollHeight,10)}catch(e){let t=document.getElementById(`${a}-results`);t&&(t.innerHTML=`<div class="console-msg-error"><span class="material-symbols-outlined" class="icon-16 mt-2px">error</span><span>Error: ${e.message}</span></div>`,n.scrollTop=n.scrollHeight)}},xe=(e,t)=>{let n=-1,r=``,i=()=>{be(null,e,t),n=-1,r=``,c()},a=document.getElementById(`run-sql-btn`);a&&(a.onclick=()=>i());let o=document.getElementById(`console-clear-editor-btn`);o&&(o.onclick=()=>{e.value=``,c(),e.focus()}),document.querySelectorAll(`.template-chip`).forEach(t=>{t.addEventListener(`click`,()=>{let n=t.getAttribute(`data-sql`);n&&(e.value=n,c(),e.focus())})});let s=document.getElementById(`sql-highlight-layer`),c=()=>{let t=e.value;if(t===``){s&&(s.innerHTML=`<span class="hl-placeholder">Write your SQL query here (e.g. SELECT * FROM users)...</span>`);return}t=t.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`);let n=RegExp(`('.*?'|".*?")|\\b(\\d+)\\b|\\b(${`SELECT.FROM.WHERE.AND.OR.IN.NOT.NULL.IS.ORDER.BY.GROUP.ASC.DESC.LIMIT.OFFSET.JOIN.INNER.LEFT.RIGHT.ON.AS.CREATE.TABLE.DROP.ALTER.INSERT.INTO.VALUES.UPDATE.SET.DELETE.PRAGMA.EXPLAIN.QUERY.PLAN.WITH.UNION.ALL.HAVING.LIKE.BETWEEN.EXISTS.CASE.WHEN.THEN.ELSE.END.CAST.DEFAULT.PRIMARY.KEY.FOREIGN.UNIQUE.CHECK.REFERENCES.AUTOINCREMENT.COUNT.SUM.AVG.MIN.MAX.COALESCE.BEGIN.COMMIT.ROLLBACK.INDEX.VIEW.TRIGGER.REPLACE.CROSS.FULL.OUTER`.split(`.`).join(`|`)})\\b`,`gi`);t=t.replace(n,(e,t,n,r)=>t?`<span class="hl-string">${t}</span>`:n?`<span class="hl-number">${n}</span>`:r?`<span class="hl-keyword">${r.toUpperCase()}</span>`:e),t.endsWith(`\\n`)&&(t+=` `),s&&(s.innerHTML=t)},l=document.getElementById(`console-autocomplete-popup`),u=0,d=[],f=null,p=`SELECT.FROM.WHERE.INSERT INTO.UPDATE.SET.DELETE FROM.JOIN.LEFT JOIN.RIGHT JOIN.INNER JOIN.ON.ORDER BY.GROUP BY.HAVING.LIMIT.OFFSET.AND.OR.NOT.IN.IS NULL.IS NOT NULL.LIKE.BETWEEN.EXISTS.CASE.WHEN.THEN.ELSE.END.CREATE TABLE.ALTER TABLE.DROP TABLE.PRIMARY KEY.FOREIGN KEY.COUNT(*).SUM().AVG().MIN().MAX().DISTINCT.AS.VALUES.BEGIN.COMMIT.ROLLBACK.PRAGMA.EXPLAIN.WITH.UNION ALL`.split(`.`),m=()=>{let e=document.querySelectorAll(`.table-btn[data-table]`);return Array.from(e).map(e=>e.getAttribute(`data-table`)).filter(Boolean)},h=()=>{l&&(l.classList.add(`hidden`),l.innerHTML=``),d=[],u=0,f=null},g=()=>{if(!l||d.length===0){h();return}l.innerHTML=d.map((e,t)=>`
|
|
793
|
+
<div class="autocomplete-item ${t===u?`active`:``}" data-idx="${t}">
|
|
794
|
+
<div class="autocomplete-item-left">
|
|
795
|
+
<span class="material-symbols-outlined autocomplete-item-icon">${e.icon}</span>
|
|
796
|
+
<span class="autocomplete-item-text">${e.text}</span>
|
|
797
|
+
</div>
|
|
798
|
+
<span class="autocomplete-item-type">${e.type}</span>
|
|
799
|
+
</div>
|
|
800
|
+
`).join(``),l.classList.remove(`hidden`),l.querySelectorAll(`.autocomplete-item`).forEach(e=>{e.onmousedown=t=>{t.preventDefault();let n=parseInt(e.dataset.idx,10);v(n)}})},_=()=>{let t=e.selectionStart,n=e.value.slice(0,t).match(/([a-zA-Z0-9_]+)$/);if(!n){h();return}let r=n[1];f={prefix:r,start:t-r.length,end:t};let i=r.toLowerCase(),a=[];m().forEach(e=>{e.toLowerCase().startsWith(i)&&e.toLowerCase()!==i&&a.push({text:e,type:`table`,icon:`table_chart`})}),p.forEach(e=>{e.toLowerCase().startsWith(i)&&e.toLowerCase()!==i&&a.push({text:e,type:`keyword`,icon:`code`})}),d=a.slice(0,8),u=0,d.length>0?g():h()},v=(t=u)=>{let n=d[t];if(!n||!f)return;let r=e.value.slice(0,f.start),i=e.value.slice(f.end),a=n.text+` `;e.value=r+a+i;let o=f.start+a.length;e.selectionStart=o,e.selectionEnd=o,h(),c(),e.focus()};e.addEventListener(`input`,()=>{c(),_()}),e.addEventListener(`blur`,()=>{setTimeout(h,150)}),e.addEventListener(`scroll`,()=>{s&&(s.scrollTop=e.scrollTop,s.scrollLeft=e.scrollLeft)}),c(),e.addEventListener(`keydown`,t=>{if(l&&!l.classList.contains(`hidden`)&&d.length>0){if(t.key===`ArrowDown`){t.preventDefault(),u=(u+1)%d.length,g();return}else if(t.key===`ArrowUp`){t.preventDefault(),u=(u-1+d.length)%d.length,g();return}else if(t.key===`Tab`||t.key===`Enter`&&!t.ctrlKey&&!t.metaKey){t.preventDefault(),v(u);return}else if(t.key===`Escape`){t.preventDefault(),h();return}}let a=window.AppState.queryHistory||[];t.key===`ArrowUp`?e.selectionStart===0&&e.selectionEnd===0&&n<a.length-1&&(t.preventDefault(),n===-1&&(r=e.value),n++,e.value=a[n],c(),setTimeout(()=>{e.selectionStart=e.value.length,e.selectionEnd=e.value.length},0)):t.key===`ArrowDown`?e.selectionStart===e.value.length&&e.selectionEnd===e.value.length&&n>-1&&(t.preventDefault(),n--,n===-1?e.value=r:e.value=a[n],c(),setTimeout(()=>{e.selectionStart=e.value.length,e.selectionEnd=e.value.length},0)):((t.ctrlKey||t.metaKey)&&t.key===`Enter`||t.key===`Enter`&&!t.shiftKey&&e.value.trim().endsWith(`;`))&&(t.preventDefault(),h(),i())})};function Se(e,t,n){document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),t&&t.classList.add(`active`);let r=document.getElementById(`table-name`);r&&(r.textContent=`SQL Console`),n.innerHTML=`
|
|
801
|
+
<div class="sql-console-wrapper">
|
|
802
|
+
<div id="console-history-pane">
|
|
803
|
+
<div id="console-welcome-msg">
|
|
804
|
+
<div class="console-welcome-icon-box">
|
|
805
|
+
<span class="material-symbols-outlined">terminal</span>
|
|
806
|
+
</div>
|
|
807
|
+
<h2 class="console-welcome-heading">Drixio SQL Terminal</h2>
|
|
808
|
+
<p class="console-welcome-sub">Execute arbitrary queries with real-time profiling, syntax highlighting, and CSV export.</p>
|
|
809
|
+
|
|
810
|
+
<div class="console-shortcuts-hint">
|
|
811
|
+
<span class="shortcut-pill"><kbd>Ctrl</kbd> + <kbd>Enter</kbd> to execute</span>
|
|
812
|
+
<span class="shortcut-pill"><kbd>↑</kbd> <kbd>↓</kbd> history navigation</span>
|
|
813
|
+
<span class="shortcut-pill">End with <kbd>;</kbd> + <kbd>Enter</kbd></span>
|
|
814
|
+
</div>
|
|
815
|
+
|
|
816
|
+
<div class="console-quick-templates">
|
|
817
|
+
<span class="quick-template-label">Quick Queries:</span>
|
|
818
|
+
<button type="button" class="template-chip" data-sql="SELECT * FROM sqlite_master WHERE type='table';">Show Tables</button>
|
|
819
|
+
<button type="button" class="template-chip" data-sql="SELECT 1 + 1 as ping;">Ping Database</button>
|
|
820
|
+
</div>
|
|
821
|
+
</div>
|
|
822
|
+
</div>
|
|
823
|
+
|
|
824
|
+
<div id="console-input-pane">
|
|
825
|
+
<div class="console-editor-toolbar">
|
|
826
|
+
<div class="editor-toolbar-left">
|
|
827
|
+
<span class="editor-mode-tag">
|
|
828
|
+
<span class="material-symbols-outlined" style="font-size: 14px;">code</span>
|
|
829
|
+
SQL
|
|
830
|
+
</span>
|
|
831
|
+
<span class="editor-shortcut-info">Press <b>Ctrl+Enter</b> to run</span>
|
|
832
|
+
</div>
|
|
833
|
+
<div class="editor-toolbar-right">
|
|
834
|
+
<button type="button" id="console-clear-editor-btn" class="header-btn secondary" style="height: 28px; padding: 0 10px; font-size: 12px;" title="Clear Editor">
|
|
835
|
+
<span class="material-symbols-outlined" style="font-size: 14px;">backspace</span>
|
|
836
|
+
Clear
|
|
837
|
+
</button>
|
|
838
|
+
<button type="button" id="run-sql-btn" class="header-btn primary" style="height: 28px; padding: 0 12px; font-size: 12px;" title="Run SQL Query (Ctrl+Enter)">
|
|
839
|
+
<span class="material-symbols-outlined" style="font-size: 15px;">play_arrow</span>
|
|
840
|
+
Run
|
|
841
|
+
</button>
|
|
842
|
+
</div>
|
|
843
|
+
</div>
|
|
844
|
+
|
|
845
|
+
<div class="sql-editor-wrapper">
|
|
846
|
+
<div class="sql-editor-layer-container">
|
|
847
|
+
<!-- Autocomplete Floating Popup (VS Code style) -->
|
|
848
|
+
<div id="console-autocomplete-popup" class="console-autocomplete-popup hidden"></div>
|
|
849
|
+
<!-- Syntax Highlight Layer (Background) -->
|
|
850
|
+
<div id="sql-highlight-layer" class="sql-editor"></div>
|
|
851
|
+
<!-- Real Textarea (Foreground) -->
|
|
852
|
+
<textarea id="sql-editor" class="sql-editor" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"></textarea>
|
|
853
|
+
</div>
|
|
854
|
+
</div>
|
|
855
|
+
</div>
|
|
856
|
+
</div>
|
|
857
|
+
`;let i=document.getElementById(`sql-editor`),a=document.getElementById(`console-history-pane`);window.AppState.lastQuery&&(i.value=window.AppState.lastQuery),i.focus(),xe(i,a)}var F={erdData:[],undoStack:[],selectedTables:new Set,clipboard:[],draggingNode:null,draggingColInfo:null,draggedNodesStart:[],dragStartMouse:{x:0,y:0},targetColForDelete:null,zoom:1,isPanning:!1,panStart:{x:0,y:0},scrollStart:{left:0,top:0},drawingFkStartCol:null,drawingFkStartCoords:null,contextMenuPos:{x:0,y:0},snapToGrid:!0,pushUndoState(){this.undoStack.push(JSON.parse(JSON.stringify(this.erdData))),this.undoStack.length>50&&this.undoStack.shift()},undo(){return this.undoStack.length>0?(this.erdData=this.undoStack.pop(),!0):!1},clearSelection(){this.selectedTables.clear()},toggleSelection(e){this.selectedTables.has(e)?this.selectedTables.delete(e):this.selectedTables.add(e)},setSelection(e){this.selectedTables.clear(),this.selectedTables.add(e)}};async function Ce(){let e=await r();if(!e||!e.success||!e.data)return[];let t=e.data,n=[],i=t.map(async e=>{let t=await s(e);t&&t.success&&n.push({table:e,columns:t.data})});return await Promise.all(i),n}function I(){try{let e=localStorage.getItem(`drixio-erd-positions`);return e?JSON.parse(e):{}}catch{return{}}}function L(e){localStorage.setItem(`drixio-erd-positions`,JSON.stringify(e))}async function we(e){let t=[];if(e.forEach(e=>{if(e.isDraft){let n=e.columns.map(e=>{let t=`${e.name} ${e.type}`;return e.isPk&&(e.type.toUpperCase()===`INTEGER`?t+=` PRIMARY KEY AUTOINCREMENT`:t+=` PRIMARY KEY`),!e.nullable&&!e.isPk&&(t+=` NOT NULL DEFAULT ''`),e.fkTarget&&(t+=` REFERENCES ${e.fkTarget.table}(${e.fkTarget.column})`),t}).join(`, `);t.push(`CREATE TABLE ${e.table} (${n});`)}else e.columns.forEach(n=>{if(n.isDraft){let r=`ALTER TABLE ${e.table} ADD COLUMN ${n.name} ${n.type}`;n.fkTarget&&(r+=` REFERENCES ${n.fkTarget.table}(${n.fkTarget.column})`),t.push(r+`;`)}})}),t.length===0)return!0;try{for(let e of t){let t=await l(e);if(!t.success)throw Error(t.error||`Query failed`)}return!0}catch(e){return window.showToast(`Failed to save: `+e.message,`error`),!1}}function Te(e){if(!e)return 0;let t=0;return e.forEach(e=>{e.isDraft?t++:e.columns.forEach(e=>{e.isDraft&&t++})}),t}function Ee(e){if(!e||e.length===0)return;let t={};e.forEach(e=>t[e.table]=new Set),e.forEach(e=>{e.columns.forEach(n=>{n.fkTarget&&n.fkTarget.table&&t[e.table]&&n.fkTarget.table!==e.table&&t[e.table].add(n.fkTarget.table)})});let n={};e.forEach(e=>n[e.table]=0);let r=!0,i=0;for(;r&&i<e.length+5;){r=!1,i++;for(let i of e){let e=n[i.table];for(let a of t[i.table])n[a]!==void 0&&n[a]>=e&&(n[i.table]=n[a]+1,r=!0)}}let a={};for(let t of e){let e=n[t.table]||0;a[e]||(a[e]=[]),a[e].push(t.table)}let o=I()||{};return Object.keys(a).forEach(e=>{let t=parseInt(e);a[e].forEach((e,n)=>{o[e]={x:600+t*360,y:400+n*260}})}),L(o),o}function R(){if(F.selectedTables.size===0)return;let e=[];F.selectedTables.forEach(t=>{let n=F.erdData.find(e=>e.table===t);if(n){let t=JSON.parse(JSON.stringify(n));t.columns.forEach(e=>{e.fkTarget&&delete e.fkTarget}),e.push(t)}}),F.clipboard=e,window.showToast(`Copied ${e.length} table(s)`,`success`)}function z(){F.selectedTables.size!==0&&(F.pushUndoState(),F.erdData.forEach(e=>{e.columns.forEach(e=>{e.fkTarget&&F.selectedTables.has(e.fkTarget.table)&&delete e.fkTarget})}),F.erdData=F.erdData.filter(e=>!F.selectedTables.has(e.table)),F.clearSelection(),U(),window.showToast(`Deleted selected table(s)`,`success`))}function De(){if(!F.clipboard||F.clipboard.length===0)return;F.pushUndoState(),F.clearSelection();let e=I();F.clipboard.forEach((t,n)=>{let r=JSON.parse(JSON.stringify(t)),i=`_copy`,a=r.table+i,o=1;for(;F.erdData.some(e=>e.table===a);)o++,a=r.table+i+o;r.table=a,r.isDraft=!0,r.columns.forEach(e=>e.isDraft=!0),e[t.table]?e[a]={x:e[t.table].x+40+n*20,y:e[t.table].y+40+n*20}:e[a]={x:100+n*20,y:100+n*20},F.erdData.push(r),F.selectedTables.add(a)}),L(e),U(),window.showToast(`Pasted ${F.clipboard.length} table(s)`,`success`)}function Oe(){document.addEventListener(`keydown`,async e=>{window.AppState&&window.AppState.currentTab!==`erd-btn`||e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`||((e.ctrlKey||e.metaKey)&&e.key===`s`&&(e.preventDefault(),await we(F.erdData)&&(window.showToast(`ERD Schema saved successfully!`,`success`),Be(document.getElementById(`main-content`)))),(e.ctrlKey||e.metaKey)&&e.key===`z`&&(e.preventDefault(),F.undo()?(U(),window.showToast(`Undid last action`,`success`)):window.showToast(`Nothing to undo`,`info`)),e.ctrlKey&&e.key===`c`&&(e.preventDefault(),R()),e.ctrlKey&&e.key===`v`&&(e.preventDefault(),De()),e.key===`Delete`&&(e.preventDefault(),z()))})}function ke(){let e=document.getElementById(`erd-workspace`);document.querySelectorAll(`.erd-node`).forEach(t=>{let n=F.erdData.find(e=>e.table===t.dataset.table);if(!n)return;let r=t.querySelector(`.erd-node-header`);if(r.addEventListener(`mousedown`,r=>{if(r.button===1||r.target.tagName===`INPUT`)return;if(!F.selectedTables.has(n.table))!r.ctrlKey&&!r.metaKey&&F.clearSelection(),F.selectedTables.add(n.table),document.querySelectorAll(`.erd-node`).forEach(e=>{F.selectedTables.has(e.dataset.table)?e.classList.add(`is-selected`):e.classList.remove(`is-selected`)});else if(r.ctrlKey||r.metaKey){F.selectedTables.delete(n.table),t.classList.remove(`is-selected`);return}F.draggingNode=t;let i=e.getBoundingClientRect();F.dragStartMouse={x:r.clientX,y:r.clientY},F.draggedNodesStart=[],F.selectedTables.forEach(e=>{let t=document.querySelector(`.erd-node[data-table="${e}"]`);if(t){let e=t.getBoundingClientRect();F.draggedNodesStart.push({node:t,startX:(e.left-i.left)/F.zoom,startY:(e.top-i.top)/F.zoom}),t.style.zIndex=`10`}}),document.body.style.cursor=`grabbing`}),t.addEventListener(`contextmenu`,e=>{e.preventDefault(),e.stopPropagation(),F.selectedTables.has(n.table)||(F.setSelection(n.table),document.querySelectorAll(`.erd-node`).forEach(e=>{F.selectedTables.has(e.dataset.table)?e.classList.add(`is-selected`):e.classList.remove(`is-selected`)})),f(e,[{label:`Copy`,icon:`content_copy`,action:R},{label:`Cut`,icon:`content_cut`,action:()=>{R(),z()}},`divider`,{label:`Delete`,icon:`delete`,danger:!0,action:z}])}),t.querySelector(`.erd-add-col-btn`).addEventListener(`click`,()=>{F.pushUndoState(),n.columns.push({name:`new_col_${n.columns.length}`,type:`TEXT`,isDraft:!0,nullable:!0}),U()}),n.isDraft){let e=r.querySelector(`.erd-node-name`);r.addEventListener(`dblclick`,t=>{if(t.target.tagName===`INPUT`)return;t.stopPropagation();let r=document.createElement(`input`);r.value=n.table,r.className=`erd-inline-input`,e.replaceWith(r),r.focus(),r.addEventListener(`blur`,()=>{let e=n.table,t=r.value.trim()||e;if(e!==t){F.pushUndoState(),n.table=t;let r=I();r[e]&&(r[t]=r[e],delete r[e],L(r)),F.erdData.forEach(n=>{n.columns.forEach(n=>{n.fkTarget&&n.fkTarget.table===e&&(n.fkTarget.table=t)})}),F.selectedTables.has(e)&&(F.selectedTables.delete(e),F.selectedTables.add(t))}U()}),r.addEventListener(`keydown`,e=>{e.key===`Enter`&&r.blur()})})}else r.addEventListener(`dblclick`,e=>{e.stopPropagation(),window.AppState.currentTable=t.dataset.table;let n=document.querySelector(`.table-btn[data-table="${t.dataset.table}"]`);n&&(window.AppState.currentTableBtnElement=n,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),n.classList.add(`active`)),window.handleSwitchTab?window.handleSwitchTab(`schema-btn`):window.renderCurrentView&&(window.AppState.currentTab=`schema-btn`,window.renderCurrentView())});let i=t.querySelectorAll(`.erd-column`);i.forEach(t=>{let r=parseInt(t.dataset.index),i=n.columns[r];if(t.addEventListener(`contextmenu`,e=>{i.isPk||!i.isDraft&&!n.isDraft||(e.preventDefault(),e.stopPropagation(),f(e,[{label:`Delete Column`,icon:`delete`,danger:!0,action:()=>{F.pushUndoState(),F.erdData.forEach(e=>{e.columns.forEach(e=>{e.fkTarget&&e.fkTarget.table===n.table&&e.fkTarget.column===i.name&&delete e.fkTarget})}),n.columns.splice(r,1),U()}}]))}),!i.isDraft&&!n.isDraft)return;t.querySelectorAll(`.editable-field`).forEach(e=>{e.addEventListener(`dblclick`,t=>{if(t.target.tagName===`INPUT`||t.target.tagName===`SELECT`)return;t.stopPropagation();let r=e.dataset.field,a;if(r===`type`){a=document.createElement(`select`),a.className=`erd-inline-input`;let e=[`INTEGER`,`TEXT`,`REAL`,`BLOB`,`BOOLEAN`,`DATE`,`DATETIME`];if(e.forEach(e=>{let t=document.createElement(`option`);t.value=e,t.textContent=e,i.type.toUpperCase()===e&&(t.selected=!0),a.appendChild(t)}),!e.includes(i.type.toUpperCase())){let e=document.createElement(`option`);e.value=i.type,e.textContent=i.type,e.selected=!0,a.appendChild(e)}a.addEventListener(`change`,()=>{a.blur()})}else a=document.createElement(`input`),a.value=r===`name`&&i.nullable?i.name+` ?`:i[r],a.className=`erd-inline-input`;e.replaceWith(a),a.focus(),a.addEventListener(`blur`,()=>{let e=i[r],t=r===`type`?a.value.toUpperCase():a.value;if(t=t.trim()||e,e===t&&!t.endsWith(`?`)&&!t.endsWith(`!`)){U();return}F.pushUndoState(),r===`name`&&(t.endsWith(`?`)?(i.nullable=!0,t=t.slice(0,-1).trim()):(i.nullable=!1,t.endsWith(`!`)&&(t=t.slice(0,-1).trim()))),r===`name`&&i.isPk&&e!==t&&F.erdData.forEach(r=>{r.columns.forEach(r=>{r.fkTarget&&r.fkTarget.table===n.table&&r.fkTarget.column===e&&(r.fkTarget.column=t)})}),i[r]=t,U()}),a.addEventListener(`keydown`,e=>{e.key===`Enter`&&a.blur()})})});let a=t.querySelector(`.erd-drag-handle`);a&&a.addEventListener(`mousedown`,t=>{t.stopPropagation(),F.drawingFkStartCol={table:n.table,col:i.name,colIndex:r};let o=a.getBoundingClientRect(),s=e.getBoundingClientRect();F.drawingFkStartCoords={x:(o.right-s.left)/F.zoom,y:(o.top+o.height/2-s.top)/F.zoom}})}),t.querySelectorAll(`.erd-reorder-handle`).forEach(e=>{e.addEventListener(`dragstart`,t=>{let r=e.closest(`.erd-column`);F.draggingColInfo={table:n.table,colIndex:parseInt(r.dataset.index)},t.dataTransfer.effectAllowed=`move`,t.dataTransfer.setData(`text/plain`,n.table)})}),i.forEach(e=>{e.addEventListener(`dragover`,e=>{!F.draggingColInfo||F.draggingColInfo.table!==n.table||(e.preventDefault(),e.dataTransfer.dropEffect=`move`)}),e.addEventListener(`drop`,t=>{if(t.preventDefault(),!F.draggingColInfo||F.draggingColInfo.table!==n.table)return;let r=F.draggingColInfo.colIndex,i=parseInt(e.dataset.index);if(r!==i){F.pushUndoState();let[e]=n.columns.splice(r,1);n.columns.splice(i,0,e),U()}F.draggingColInfo=null})})})}var Ae=!1;function je(){Ae||(Ae=!0,document.addEventListener(`mousemove`,e=>{if(F.drawingFkStartCol){let t=document.getElementById(`erd-workspace`);if(!t)return;let n=t.getBoundingClientRect(),r=(e.clientX-n.left)/F.zoom,i=(e.clientY-n.top)/F.zoom,a=document.getElementById(`erd-temp-path`),o=document.getElementById(`erd-svg-layer`);a||(a=document.createElementNS(`http://www.w3.org/2000/svg`,`path`),a.id=`erd-temp-path`,a.setAttribute(`fill`,`none`),a.setAttribute(`stroke`,`var(--color-primary)`),a.setAttribute(`stroke-width`,`2`),a.setAttribute(`stroke-dasharray`,`5,5`),o&&o.appendChild(a)),a&&a.setAttribute(`d`,`M ${F.drawingFkStartCoords.x} ${F.drawingFkStartCoords.y} L ${r} ${i}`);return}if(!F.draggingNode)return;let t=(e.clientX-F.dragStartMouse.x)/F.zoom,n=(e.clientY-F.dragStartMouse.y)/F.zoom;F.draggedNodesStart.forEach(e=>{let r=e.startX+t,i=e.startY+n;r<0&&(r=0),i<0&&(i=0),F.snapToGrid&&(r=Math.round(r/20)*20,i=Math.round(i/20)*20),e.node.style.left=`${r}px`,e.node.style.top=`${i}px`}),W()}),document.addEventListener(`mouseup`,e=>{if(F.drawingFkStartCol){let t=document.elementFromPoint(e.clientX,e.clientY),n=t?t.closest(`.erd-node`):null;if(n){let e=n.dataset.table;if(e!==F.drawingFkStartCol.table){let t=F.erdData.find(t=>t.table===e).columns.find(e=>e.isPk);if(t){let n=confirm(`Create Foreign Key to ${e}?\n\nClick OK for Nullable (0..N)\nClick Cancel for Not Nullable (1..N)`);F.pushUndoState();let r=F.erdData.find(e=>e.table===F.drawingFkStartCol.table),i=r.columns[F.drawingFkStartCol.colIndex];i.fkTarget={table:e,column:t.name},i.nullable=n,r.columns.splice(F.drawingFkStartCol.colIndex,1),r.columns.push(i),U()}else window.showToast(`Target table has no Primary Key!`,`error`)}}else{let e=F.erdData.find(e=>e.table===F.drawingFkStartCol.table).columns[F.drawingFkStartCol.colIndex];e.fkTarget&&(F.pushUndoState(),delete e.fkTarget,U())}let r=document.getElementById(`erd-temp-path`);r&&r.remove(),F.drawingFkStartCol=null}if(F.draggingNode){document.body.style.cursor=``;let e=I();F.draggedNodesStart.forEach(t=>{t.node.style.zIndex=``,e[t.node.dataset.table]={x:parseFloat(t.node.style.left),y:parseFloat(t.node.style.top)}}),L(e),G(),F.draggingNode=null,F.draggedNodesStart=[]}}))}var B=!1,V={x:0,y:0},H=null;function Me(e){let t=()=>{if(!e)return;let t=20*F.zoom,n=Math.max(1,1.25*F.zoom);e.style.backgroundSize=`${t}px ${t}px`,e.style.backgroundImage=`radial-gradient(var(--color-border) ${n}px, transparent ${n}px)`,e.style.backgroundPosition=`${-(e.scrollLeft%t)}px ${-(e.scrollTop%t)}px`};e.addEventListener(`scroll`,()=>{G(),t()}),e.addEventListener(`mousedown`,t=>{t.button===1||t.button===0&&t.altKey?(t.preventDefault(),F.isPanning=!0,F.panStart.x=t.clientX,F.panStart.y=t.clientY,F.scrollStart.left=e.scrollLeft,F.scrollStart.top=e.scrollTop,document.body.style.cursor=`grabbing`):t.button===0&&(t.target.id===`erd-workspace`||t.target.id===`erd-svg-layer`||t.target.id===`erd-nodes-layer`||t.target.id===`erd-wrapper`)&&(B=!0,V={x:t.clientX,y:t.clientY},!t.ctrlKey&&!t.metaKey&&(F.clearSelection(),U()),H=document.createElement(`div`),H.style.position=`fixed`,H.style.border=`1px solid var(--color-primary)`,H.style.backgroundColor=`rgba(59, 130, 246, 0.12)`,H.style.borderRadius=`4px`,H.style.zIndex=`1000`,H.style.pointerEvents=`none`,document.body.appendChild(H))});let n=()=>{let e=document.getElementById(`btn-erd-zoom-reset`);e&&(e.textContent=Math.round(F.zoom*100)+`%`)},r=e=>{F.zoom=Math.max(.2,Math.min(e,2.5));let r=document.getElementById(`erd-workspace`);r&&(r.style.transform=`scale(${F.zoom})`,W(),G(),n(),t())},i=document.getElementById(`btn-erd-zoom-in`);i&&(i.onclick=()=>r(F.zoom+.15));let a=document.getElementById(`btn-erd-zoom-out`);a&&(a.onclick=()=>r(F.zoom-.15));let o=document.getElementById(`btn-erd-zoom-reset`);o&&(o.onclick=()=>r(1));let s=()=>{let n=document.querySelectorAll(`.erd-node`);if(!n||n.length===0)return;let i=1/0,a=1/0,o=-1/0,s=-1/0;if(n.forEach(e=>{let t=parseFloat(e.style.left)||0,n=parseFloat(e.style.top)||0,r=e.offsetWidth||290,c=e.offsetHeight||150;t<i&&(i=t),n<a&&(a=n),t+r>o&&(o=t+r),n+c>s&&(s=n+c)}),i===1/0)return;let c=o-i+240,l=s-a+240,u=e.clientWidth,d=e.clientHeight,f=u/c,p=d/l,m=Math.min(1.1,Math.max(.35,Math.min(f,p)));r(m);let h=(i+o)/2,g=(a+s)/2;e.scrollLeft=h*m-u/2,e.scrollTop=g*m-d/2,t()},c=document.getElementById(`btn-erd-fit-view`);c&&(c.onclick=s);let l=document.getElementById(`btn-erd-toggle-minimap`),u=document.getElementById(`btn-erd-close-minimap`),d=document.getElementById(`erd-minimap-container`),p=e=>{if(!d)return;let t=d.classList.contains(`hidden`)||d.style.display===`none`;(e===void 0?t:e)?(d.classList.remove(`hidden`),d.style.display=`block`,l&&l.classList.add(`active`),G()):(d.classList.add(`hidden`),d.style.display=`none`,l&&l.classList.remove(`active`))};l&&(l.onclick=()=>p()),u&&(u.onclick=e=>{e.stopPropagation(),p(!1)});let m=document.getElementById(`btn-erd-add-table`);m&&(m.onclick=()=>{let t=e.scrollLeft/F.zoom,n=e.scrollTop/F.zoom,r=e.clientWidth/F.zoom,i=e.clientHeight/F.zoom;ze(Math.max(60,t+r/2-150),Math.max(60,n+i/2-100)),window.showToast(`Created new table draft`,`success`)});let h=document.getElementById(`btn-erd-auto-layout`);h&&(h.onclick=()=>{F.pushUndoState(),Ee(F.erdData),U(),setTimeout(s,50),window.showToast(`Auto Layout Applied`,`success`)});let g=document.getElementById(`btn-erd-snap-grid`);g&&(g.onclick=()=>{F.snapToGrid=!F.snapToGrid,F.snapToGrid?(g.classList.add(`active`),window.showToast(`Grid Snapping: ON (20px)`,`info`)):(g.classList.remove(`active`),window.showToast(`Grid Snapping: OFF (Free)`,`info`))});let _=document.getElementById(`btn-erd-save-drafts`);_&&(_.onclick=async()=>{await we(F.erdData)&&(window.showToast(`ERD Schema saved successfully!`,`success`),Be(document.getElementById(`main-content`)))}),e.addEventListener(`wheel`,e=>{(e.ctrlKey||e.metaKey)&&(e.preventDefault(),r(F.zoom-e.deltaY*.001))},{passive:!1}),document.addEventListener(`mousemove`,n=>{if(B&&H){let e=Math.min(n.clientX,V.x),t=Math.min(n.clientY,V.y),r=Math.abs(n.clientX-V.x),i=Math.abs(n.clientY-V.y);H.style.left=e+`px`,H.style.top=t+`px`,H.style.width=r+`px`,H.style.height=i+`px`;return}if(F.isPanning){if(e){let r=n.clientX-F.panStart.x,i=n.clientY-F.panStart.y;e.scrollLeft=F.scrollStart.left-r,e.scrollTop=F.scrollStart.top-i,t()}return}}),document.addEventListener(`mouseup`,e=>{if(B&&(B=!1,H)){let e=H.getBoundingClientRect();H.remove(),H=null;let t=document.querySelectorAll(`.erd-node`),n=!1;t.forEach(t=>{let r=t.getBoundingClientRect();e.right<r.left||e.left>r.right||e.bottom<r.top||e.top>r.bottom||(F.selectedTables.add(t.dataset.table),n=!0)}),n&&U()}F.isPanning&&(F.isPanning=!1,document.body.style.cursor=``)}),e.addEventListener(`contextmenu`,e=>{(e.target.id===`erd-workspace`||e.target.id===`erd-svg-layer`||e.target.id===`erd-nodes-layer`||e.target.id===`erd-wrapper`)&&(e.preventDefault(),e.stopPropagation(),f(e,[{label:`Add Table`,icon:`add_box`,action:()=>{m&&m.click()}},`divider`,{label:`Auto Layout`,icon:`account_tree`,action:()=>{h&&h.click()}}]))});let v=document.getElementById(`erd-search-input`),y=document.getElementById(`erd-search-results`);if(v&&y){v.addEventListener(`input`,n=>{let r=n.target.value.toLowerCase().trim();if(y.innerHTML=``,!r){y.classList.add(`hidden`);return}let i=F.erdData.filter(e=>e.table.toLowerCase().includes(r));if(i.length===0){y.innerHTML=`<div class="erd-search-item" style="color: var(--color-text-soft);">No tables found</div>`,y.classList.remove(`hidden`);return}i.forEach(n=>{let r=document.createElement(`div`);r.className=`erd-search-item`,r.innerHTML=`
|
|
858
|
+
<span class="material-symbols-outlined icon-16" style="color: var(--color-primary);">table_chart</span>
|
|
859
|
+
<span>${n.table}</span>
|
|
860
|
+
<span class="erd-search-col-badge">${n.columns.length} cols</span>
|
|
861
|
+
`,r.onclick=()=>{F.setSelection(n.table);let r=document.querySelector(`.erd-node[data-table="${n.table}"]`);if(r){let n=r.getBoundingClientRect(),i=e.getBoundingClientRect(),a=parseFloat(r.style.left),o=parseFloat(r.style.top);e.scrollLeft=a*F.zoom-i.width/2+n.width/2,e.scrollTop=o*F.zoom-i.height/2+n.height/2,t()}U(),y.classList.add(`hidden`),v.value=``},y.appendChild(r)}),y.classList.remove(`hidden`)}),document.addEventListener(`click`,e=>{!v.contains(e.target)&&!y.contains(e.target)&&y.classList.add(`hidden`)});let n=document.querySelector(`.erd-search-shortcut`);n&&(n.onclick=()=>v.focus(),n.style.cursor=`pointer`)}e.addEventListener(`mouseover`,e=>{let t=e.target.closest(`.erd-relationship-path`);if(t){t.classList.add(`is-hovered`);let e=t.dataset.fromTable,n=t.dataset.toTable;document.querySelector(`.erd-node[data-table="${e}"]`)?.classList.add(`is-relationship-highlight`),document.querySelector(`.erd-node[data-table="${n}"]`)?.classList.add(`is-relationship-highlight`);return}let n=e.target.closest(`.erd-column.is-fk`);if(n){let e=n.closest(`.erd-node`),t=n.dataset.col;if(e){let n=document.querySelector(`.erd-relationship-path[data-from-table="${e.dataset.table}"][data-from-col="${t}"]`);n&&n.classList.add(`is-hovered`)}}}),e.addEventListener(`mouseout`,e=>{let t=e.target.closest(`.erd-relationship-path`);if(t){t.classList.remove(`is-hovered`),document.querySelectorAll(`.erd-node.is-relationship-highlight`).forEach(e=>e.classList.remove(`is-relationship-highlight`));return}e.target.closest(`.erd-column.is-fk`)&&document.querySelectorAll(`.erd-relationship-path.is-hovered`).forEach(e=>e.classList.remove(`is-hovered`))}),t()}function Ne(e){let t=document.getElementById(`erd-minimap`);if(!t)return;let n=!1,r=n=>{let r=t.getBoundingClientRect(),i=n.clientX-r.left,a=n.clientY-r.top,{scale:o,offsetX:s,offsetY:c,minX:l,minY:u,vW:d,vH:f}=Re(e,Array.from(document.querySelectorAll(`.erd-node`))),p=l+(i-s)/o,m=u+(a-c)/o;e.scrollLeft=(p-d/2)*F.zoom,e.scrollTop=(m-f/2)*F.zoom};t.addEventListener(`mousedown`,e=>{if(e.stopPropagation(),e.button!==0)return;n=!0,r(e);let t=e=>{n&&r(e)},i=()=>{n=!1,document.removeEventListener(`mousemove`,t),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,t),document.addEventListener(`mouseup`,i)})}var Pe=!1;function Fe(){if(Pe)return;let e=document.getElementById(`erd-wrapper`);e&&(Me(e),Ne(e),Oe(),je(),Pe=!0)}function Ie(){let e=document.getElementById(`btn-erd-save-drafts`);if(!e)return;let t=Te(F.erdData),n=e.querySelector(`.erd-draft-count-badge`);t>0?(e.classList.remove(`disabled`),e.classList.add(`has-drafts`),n&&(n.textContent=t,n.style.display=`inline-flex`)):(e.classList.remove(`has-drafts`),n&&(n.style.display=`none`))}function U(){let e=document.getElementById(`erd-nodes-layer`);if(!e)return;if(e.innerHTML=``,!F.erdData||F.erdData.length===0){e.innerHTML=`
|
|
862
|
+
<div class="erd-empty-workspace">
|
|
863
|
+
<div class="erd-empty-icon-box">
|
|
864
|
+
<span class="material-symbols-outlined">account_tree</span>
|
|
865
|
+
</div>
|
|
866
|
+
<h3>No tables in this database</h3>
|
|
867
|
+
<p>Right-click the canvas or click "+ Add Table" above to create an entity.</p>
|
|
868
|
+
</div>
|
|
869
|
+
`,Ie();return}let t=I(),n=600,r=400,i=0;F.erdData.forEach(a=>{let o,s;t[a.table]?(o=t[a.table].x,s=t[a.table].y):(o=n,s=r,n+=340,n>1800&&(n=600,r+=i+60,i=0));let c=Le(a,o,s);F.selectedTables.has(a.table)&&c.classList.add(`is-selected`),e.appendChild(c);let l=44+a.columns.length*34+36;l>i&&(i=l)}),ke(),Ie(),setTimeout(()=>{W(),G()},10)}function Le(e,t,n){let r=document.createElement(`div`);r.className=`erd-node ${e.isDraft?`is-draft`:``}`,r.dataset.table=e.table,r.style.left=`${t}px`,r.style.top=`${n}px`;let i=e.columns.map((t,n)=>{let r=t.fkTarget?`is-fk`:``,i=t.isDraft?`is-draft`:``,a=t.type.toUpperCase();return`
|
|
870
|
+
<div class="erd-column ${r} ${i}" data-col="${t.name}" data-index="${n}">
|
|
871
|
+
<div class="erd-col-left">
|
|
872
|
+
<span class="erd-reorder-handle" draggable="true" title="Drag to reorder column">
|
|
873
|
+
${t.isPk?`<span class="erd-badge-pk" title="Primary Key">PK</span>`:t.fkTarget?`<span class="erd-badge-fk" title="Foreign Key to ${t.fkTarget.table}.${t.fkTarget.column}">FK</span>`:`<span class="erd-col-bullet"></span>`}
|
|
874
|
+
</span>
|
|
875
|
+
<span class="erd-col-name editable-field ${t.isPk?`is-pk`:``}" data-field="name">${t.name}</span>
|
|
876
|
+
${t.nullable?`<span class="erd-col-nullable" title="Nullable column">?</span>`:``}
|
|
877
|
+
</div>
|
|
878
|
+
<div class="erd-col-right">
|
|
879
|
+
<span class="erd-col-type editable-field" data-field="type">${a}</span>
|
|
880
|
+
${(t.isDraft||e.isDraft)&&!t.isPk?`<div class="erd-drag-handle" title="Drag to create Foreign Key"></div>`:``}
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
`}).join(``);return r.innerHTML=`
|
|
884
|
+
<div class="erd-node-header" title="${e.isDraft?`Double-click to rename table`:`Double-click to open Schema view`}">
|
|
885
|
+
<div class="erd-node-header-left">
|
|
886
|
+
<div class="erd-node-icon-box">
|
|
887
|
+
<span class="material-symbols-outlined erd-node-icon">table_chart</span>
|
|
888
|
+
</div>
|
|
889
|
+
<span class="erd-node-name">${e.table}</span>
|
|
890
|
+
${e.isDraft?`<span class="erd-badge-draft">DRAFT</span>`:``}
|
|
891
|
+
</div>
|
|
892
|
+
<div class="erd-node-header-right">
|
|
893
|
+
<span class="erd-col-count" title="${e.columns.length} columns">${e.columns.length}</span>
|
|
894
|
+
<span class="material-symbols-outlined erd-node-drag-handle">drag_indicator</span>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
<div class="erd-node-body">
|
|
898
|
+
${i}
|
|
899
|
+
</div>
|
|
900
|
+
<div class="erd-add-col-btn">
|
|
901
|
+
<span class="material-symbols-outlined" style="font-size: 15px;">add</span>
|
|
902
|
+
<span>Add Column</span>
|
|
903
|
+
</div>
|
|
904
|
+
`,r}function W(){let e=document.getElementById(`erd-svg-layer`);if(!e)return;let t=``;F.erdData.forEach(e=>{let n=e.table,r=document.querySelector(`.erd-node[data-table="${n}"]`);r&&e.columns.forEach(e=>{if(e.fkTarget){let i=e.fkTarget.table,a=e.fkTarget.column,o=document.querySelector(`.erd-node[data-table="${i}"]`);if(!o)return;let s=r.querySelector(`.erd-column[data-col="${e.name}"]`),c=o.querySelector(`.erd-column[data-col="${a}"]`);if(!s||!c)return;let l=s.getBoundingClientRect(),u=c.getBoundingClientRect(),d=document.getElementById(`erd-workspace`).getBoundingClientRect(),f,p,m,h;l.right<u.left+20?(f=(l.right-d.left)/F.zoom,m=(u.left-d.left)/F.zoom):(f=(l.left-d.left)/F.zoom,m=(u.right-d.left)/F.zoom),p=(l.top+l.height/2-d.top)/F.zoom,h=(u.top+u.height/2-d.top)/F.zoom;let g=Math.abs(m-f),_=Math.max(g*.5,50),v=f<m?f+_:f-_,y=p,b=f<m?m-_:m+_,ee=h,x=e.nullable?`url(#zero-or-one)`:`url(#one-and-only-one)`;t+=`
|
|
905
|
+
<path
|
|
906
|
+
class="erd-relationship-path"
|
|
907
|
+
d="M ${f} ${p} C ${v} ${y}, ${b} ${ee}, ${m} ${h}"
|
|
908
|
+
fill="none"
|
|
909
|
+
marker-start="url(#crows-foot-zero-many)"
|
|
910
|
+
marker-end="${x}"
|
|
911
|
+
data-from-table="${n}"
|
|
912
|
+
data-from-col="${e.name}"
|
|
913
|
+
data-to-table="${i}"
|
|
914
|
+
data-to-col="${a}"
|
|
915
|
+
/>
|
|
916
|
+
`}})}),e.innerHTML=`
|
|
917
|
+
<defs>
|
|
918
|
+
<!-- FK side: Zero or Many (0<) -->
|
|
919
|
+
<marker id="crows-foot-zero-many" viewBox="0 0 16 10" refX="16" refY="5" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
|
|
920
|
+
<path d="M 16 0 L 8 5 M 16 10 L 8 5 M 16 5 L 8 5 M 4 5 L 0 5" fill="none" stroke="currentColor" stroke-width="1.5" />
|
|
921
|
+
<circle cx="5" cy="5" r="3" fill="var(--color-bg-primary)" stroke="currentColor" stroke-width="1.5" />
|
|
922
|
+
</marker>
|
|
923
|
+
|
|
924
|
+
<!-- PK side: One and Only One (||) -->
|
|
925
|
+
<marker id="one-and-only-one" viewBox="0 0 16 10" refX="13" refY="5" markerWidth="10" markerHeight="10" orient="auto">
|
|
926
|
+
<path d="M 10 0 L 10 10 M 6 0 L 6 10" fill="none" stroke="currentColor" stroke-width="1.5" />
|
|
927
|
+
</marker>
|
|
928
|
+
|
|
929
|
+
<!-- PK side: Zero or One (0|) -->
|
|
930
|
+
<marker id="zero-or-one" viewBox="0 0 16 10" refX="13" refY="5" markerWidth="10" markerHeight="10" orient="auto">
|
|
931
|
+
<path d="M 10 0 L 10 10 M 6 0 L 6 10" fill="none" stroke="currentColor" stroke-width="1.5" />
|
|
932
|
+
<circle cx="5" cy="5" r="3" fill="var(--color-bg-primary)" stroke="currentColor" stroke-width="1.5" />
|
|
933
|
+
</marker>
|
|
934
|
+
</defs>
|
|
935
|
+
${t}
|
|
936
|
+
`}function Re(e,t){if(!e||!t||t.length===0)return{mapW:180,mapH:120,scale:.1,offsetX:0,offsetY:0,minX:0,minY:0,vW:800,vH:600,vLeft:0,vTop:0};let n=e.clientWidth/F.zoom,r=e.clientHeight/F.zoom,i=e.scrollLeft/F.zoom,a=e.scrollTop/F.zoom,o=1/0,s=1/0,c=-1/0,l=-1/0;t.forEach(e=>{let t=parseFloat(e.style.left)||0,n=parseFloat(e.style.top)||0,r=e.offsetWidth||290,i=e.offsetHeight||150;t<o&&(o=t),n<s&&(s=n),t+r>c&&(c=t+r),n+i>l&&(l=n+i)}),o===1/0&&(o=0,s=0,c=800,l=600),o-=200,s-=200,c+=200,l+=200;let u=c-o,d=l-s,f=Math.max(u,n),p=Math.max(d,r),m=o+u/2,h=s+d/2,g=m-f/2,_=h-p/2,v=Math.min(180/f,120/p);return{mapW:180,mapH:120,scale:v,offsetX:(180-f*v)/2,offsetY:(120-p*v)/2,minX:g,minY:_,vW:n,vH:r,vLeft:i,vTop:a}}function G(){let e=document.getElementById(`erd-minimap-content`);if(!e)return;e.innerHTML=``;let t=document.getElementById(`erd-nodes-layer`),n=document.getElementById(`erd-wrapper`);if(!t||!n)return;let r=Array.from(t.querySelectorAll(`.erd-node`));if(r.length===0)return;let{scale:i,offsetX:a,offsetY:o,minX:s,minY:c,vW:l,vH:u,vLeft:d,vTop:f}=Re(n,r);r.forEach(t=>{let n=parseFloat(t.style.left)||0,r=parseFloat(t.style.top)||0,l=t.offsetWidth||290,u=t.offsetHeight||150,d=t.dataset.table,f=document.createElement(`div`);f.style.position=`absolute`,f.style.left=`${a+(n-s)*i}px`,f.style.top=`${o+(r-c)*i}px`,f.style.width=`${Math.max(l*i,8)}px`,f.style.height=`${Math.max(u*i,6)}px`,f.style.backgroundColor=`var(--color-primary)`,f.style.opacity=F.selectedTables.has(d)?`1`:`0.6`,f.style.borderRadius=`2px`,e.appendChild(f)});let p=document.getElementById(`erd-minimap-viewport`);p&&(p.style.left=`${a+(d-s)*i}px`,p.style.top=`${o+(f-c)*i}px`,p.style.width=`${l*i}px`,p.style.height=`${u*i}px`)}function ze(e=120,t=120){let n=`new_table_${Math.floor(Math.random()*1e3)}`,r={table:n,isDraft:!0,columns:[{name:`id`,type:`INTEGER`,isPk:!0,isDraft:!0,nullable:!1}]},i=I();i[n]={x:e,y:t},L(i),F.pushUndoState(),F.erdData.push(r),F.setSelection(n),U()}async function Be(e){let t=document.getElementById(`table-name`);t&&(t.textContent=`Entity Relationship Diagram`);let n=document.getElementById(`table-icon`);n&&(n.textContent=`account_tree`),document.getElementById(`erd-main-viewport`)||(e.innerHTML=`
|
|
937
|
+
<div id="erd-main-viewport" class="erd-main-viewport">
|
|
938
|
+
<!-- Canvas Scrollable Workspace -->
|
|
939
|
+
<div id="erd-wrapper" class="erd-grid-bg erd-workspace-container">
|
|
940
|
+
<div id="erd-workspace" class="erd-workspace-canvas">
|
|
941
|
+
<svg id="erd-svg-layer" class="erd-svg-layer"></svg>
|
|
942
|
+
<div id="erd-nodes-layer" class="erd-nodes-layer"></div>
|
|
943
|
+
</div>
|
|
944
|
+
</div>
|
|
945
|
+
|
|
946
|
+
<!-- Top Floating Action HUD -->
|
|
947
|
+
<div class="erd-top-hud">
|
|
948
|
+
<div class="erd-hud-left">
|
|
949
|
+
<div class="erd-search-box">
|
|
950
|
+
<span class="material-symbols-outlined search-icon">search</span>
|
|
951
|
+
<input type="text" id="erd-search-input" placeholder="Search tables..." autocomplete="off">
|
|
952
|
+
<kbd class="erd-search-shortcut">/</kbd>
|
|
953
|
+
<div id="erd-search-results" class="erd-search-results hidden"></div>
|
|
954
|
+
</div>
|
|
955
|
+
</div>
|
|
956
|
+
|
|
957
|
+
<div class="erd-hud-right">
|
|
958
|
+
<button type="button" id="btn-erd-add-table" class="erd-hud-btn" title="Add a new draft table">
|
|
959
|
+
<span class="material-symbols-outlined icon-18">add</span>
|
|
960
|
+
<span>Add Table</span>
|
|
961
|
+
</button>
|
|
962
|
+
|
|
963
|
+
<button type="button" id="btn-erd-auto-layout" class="erd-hud-btn" title="Auto arrange tables neatly">
|
|
964
|
+
<span class="material-symbols-outlined icon-18">account_tree</span>
|
|
965
|
+
<span>Auto Layout</span>
|
|
966
|
+
</button>
|
|
967
|
+
|
|
968
|
+
<button type="button" id="btn-erd-snap-grid" class="erd-hud-btn active" title="Toggle magnetic grid snapping">
|
|
969
|
+
<span class="material-symbols-outlined icon-18">grid_4x4</span>
|
|
970
|
+
<span>Snap</span>
|
|
971
|
+
</button>
|
|
972
|
+
|
|
973
|
+
<div class="erd-hud-divider"></div>
|
|
974
|
+
|
|
975
|
+
<button type="button" id="btn-erd-save-drafts" class="erd-hud-btn primary disabled" title="Save draft tables and columns (Ctrl+S)">
|
|
976
|
+
<span class="material-symbols-outlined icon-18">save</span>
|
|
977
|
+
<span>Save</span>
|
|
978
|
+
<span class="erd-draft-count-badge" style="display: none;">0</span>
|
|
979
|
+
</button>
|
|
980
|
+
</div>
|
|
981
|
+
</div>
|
|
982
|
+
|
|
983
|
+
<!-- Bottom-Left Shortcuts Helper -->
|
|
984
|
+
<div class="erd-shortcuts-helper">
|
|
985
|
+
<span class="erd-shortcut-item"><kbd>Alt</kbd>+Drag Pan</span>
|
|
986
|
+
<span class="erd-shortcut-item"><kbd>Ctrl</kbd>+Scroll Zoom</span>
|
|
987
|
+
<span class="erd-shortcut-item"><kbd>Ctrl</kbd>+<kbd>S</kbd> Save</span>
|
|
988
|
+
</div>
|
|
989
|
+
|
|
990
|
+
<!-- Bottom-Right Floating Navigation Dock -->
|
|
991
|
+
<div class="erd-bottom-dock">
|
|
992
|
+
<!-- Collapsible Minimap -->
|
|
993
|
+
<div id="erd-minimap-container" class="erd-minimap-container">
|
|
994
|
+
<div class="erd-minimap-header">
|
|
995
|
+
<span class="erd-minimap-title">
|
|
996
|
+
<span class="material-symbols-outlined icon-14">map</span>
|
|
997
|
+
Minimap
|
|
998
|
+
</span>
|
|
999
|
+
<button type="button" id="btn-erd-close-minimap" class="icon-btn" title="Hide minimap">
|
|
1000
|
+
<span class="material-symbols-outlined icon-14">close</span>
|
|
1001
|
+
</button>
|
|
1002
|
+
</div>
|
|
1003
|
+
<div id="erd-minimap" class="erd-minimap">
|
|
1004
|
+
<div id="erd-minimap-content" class="erd-minimap-content"></div>
|
|
1005
|
+
<div id="erd-minimap-viewport" class="erd-minimap-viewport"></div>
|
|
1006
|
+
</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
|
|
1009
|
+
<!-- Zoom HUD Controls -->
|
|
1010
|
+
<div class="erd-zoom-controls">
|
|
1011
|
+
<button type="button" id="btn-erd-zoom-out" class="erd-dock-btn" title="Zoom Out">
|
|
1012
|
+
<span class="material-symbols-outlined icon-18">remove</span>
|
|
1013
|
+
</button>
|
|
1014
|
+
<button type="button" id="btn-erd-zoom-reset" class="erd-dock-btn erd-zoom-value" title="Reset Zoom to 100%">
|
|
1015
|
+
100%
|
|
1016
|
+
</button>
|
|
1017
|
+
<button type="button" id="btn-erd-zoom-in" class="erd-dock-btn" title="Zoom In">
|
|
1018
|
+
<span class="material-symbols-outlined icon-18">add</span>
|
|
1019
|
+
</button>
|
|
1020
|
+
<button type="button" id="btn-erd-fit-view" class="erd-dock-btn" title="Fit tables to view">
|
|
1021
|
+
<span class="material-symbols-outlined icon-18">crop_free</span>
|
|
1022
|
+
</button>
|
|
1023
|
+
<div class="erd-dock-divider"></div>
|
|
1024
|
+
<button type="button" id="btn-erd-toggle-minimap" class="erd-dock-btn active" title="Toggle Minimap">
|
|
1025
|
+
<span class="material-symbols-outlined icon-18">map</span>
|
|
1026
|
+
</button>
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
`,document.getElementById(`erd-wrapper`).addEventListener(`contextmenu`,e=>{if(e.preventDefault(),e.target.closest(`.erd-node`))return;let t=document.getElementById(`erd-workspace`).getBoundingClientRect(),n=(e.clientX-t.left)/F.zoom,r=(e.clientY-t.top)/F.zoom;f(e,[{label:`Add Table`,icon:`table_chart`,action:()=>ze(n,r)},{label:`Paste`,icon:`content_paste`,action:De}])}),Fe());let r=document.getElementById(`erd-nodes-layer`);r&&(r.innerHTML=`<div class="erd-loading-state"><span class="material-symbols-outlined animate-spin icon-24">progress_activity</span><span>Loading Schema Diagrams...</span></div>`),F.erdData=await Ce()||[],F.erdData.forEach(e=>{e.columns.sort((e,t)=>e.isPk&&!t.isPk?-1:!e.isPk&&t.isPk||e.fkTarget&&!t.fkTarget?1:!e.fkTarget&&t.fkTarget?-1:0),e.columns.forEach(e=>{e.isPk&&(e.nullable=!1)})}),F.undoStack=[],F.clearSelection(),U(),setTimeout(()=>{let e=document.getElementById(`erd-wrapper`);if(e&&e.scrollLeft===0&&e.scrollTop===0){let t=document.querySelectorAll(`.erd-node`);if(t.length>0){let n=1/0,r=1/0,i=-1/0,a=-1/0;t.forEach(e=>{let t=parseFloat(e.style.left)||0,o=parseFloat(e.style.top)||0,s=e.offsetWidth||290,c=e.offsetHeight||150;t<n&&(n=t),o<r&&(r=o),t+s>i&&(i=t+s),o+c>a&&(a=o+c)});let o=(n+i)/2,s=(r+a)/2;e.scrollLeft=o*F.zoom-e.clientWidth/2,e.scrollTop=s*F.zoom-e.clientHeight/2}}},40)}function Ve(e){if(!e||e<=0)return`N/A`;let t=Math.floor(e/(3600*24)),n=Math.floor(e%(3600*24)/3600),r=Math.floor(e%3600/60),i=``;return t>0&&(i+=`${t}d `),n>0&&(i+=`${n}h `),i+=`${r}m`,i||`< 1m`}function He(e){if(e===0||!e)return`0 B`;let t=1024,n=[`B`,`KB`,`MB`,`GB`,`TB`],r=Math.floor(Math.log(e)/Math.log(t));return parseFloat((e/t**r).toFixed(2))+` `+n[r]}var K=null,q=null;function J(e,t,n,r,i=``,a=null){let o=``;return a!==null&&!isNaN(a)&&(o=`
|
|
1031
|
+
<div class="status-card-progress">
|
|
1032
|
+
<div class="status-card-progress-bar" style="width: ${Math.min(100,Math.max(0,a))}%"></div>
|
|
1033
|
+
</div>
|
|
1034
|
+
`),`
|
|
1035
|
+
<div class="status-card ${r}">
|
|
1036
|
+
<div class="status-card-header">
|
|
1037
|
+
<span class="status-card-title">${t}</span>
|
|
1038
|
+
<div class="status-card-icon">
|
|
1039
|
+
<span class="material-symbols-outlined">${e}</span>
|
|
1040
|
+
</div>
|
|
1041
|
+
</div>
|
|
1042
|
+
<div class="status-card-body">
|
|
1043
|
+
<div class="status-card-value">${n}</div>
|
|
1044
|
+
${o}
|
|
1045
|
+
${i?`<div class="status-card-subtext">${i}</div>`:``}
|
|
1046
|
+
</div>
|
|
1047
|
+
</div>
|
|
1048
|
+
`}function Ue(e){let t=document.getElementById(`status-cards-container`);if(!t)return;let n=``,r=Date.now();if(n+=J(`table_chart`,`Total Tables`,e.totalTables==null?`0`:e.totalTables,`card-blue`,`Active schemas & base tables`),n+=J(`hard_drive`,`Storage Used`,He(e.sizeBytes),`card-amber`,e.dbName?`File / DB: ${e.dbName}`:`Disk footprint`),n+=J(`hub`,`Active Connections`,e.activeConnections||`1`,`card-emerald`,e.dbType===`sqlite`?`Single-process runtime`:`Connected clients`),e.uptime&&(n+=J(`schedule`,`Server Uptime`,Ve(e.uptime),`card-indigo`,`Continuous runtime`)),e.dbType===`mysql`){let t=0;if(K&&q&&e.queries){let n=(r-q)/1e3,i=e.queries-K.queries;t=Math.max(0,i/n)}n+=J(`speed`,`Query Throughput`,`${t.toFixed(1)} /s`,`card-blue`,`Total: ${(e.queries||0).toLocaleString()} queries`)}else if(e.dbType===`postgres`){let t=0;if(K&&q&&e.transactions){let n=(r-q)/1e3,i=e.transactions-K.transactions;t=Math.max(0,i/n)}n+=J(`receipt_long`,`Transactions Rate`,`${t.toFixed(1)} /s`,`card-blue`,`Total: ${(e.transactions||0).toLocaleString()} commits/rollbacks`)}else if(e.dbType===`sqlite`){let t=e.osMemTotal?(e.osMemUsed/e.osMemTotal*100).toFixed(1):0;n+=J(`memory`,`Host Memory`,`${t}%`,`card-blue`,`${He(e.osMemUsed)} / ${He(e.osMemTotal)}`,parseFloat(t)),n+=J(`memory_alt`,`Host CPU Load`,(e.osCpuUsage||0).toFixed(2),`card-indigo`,`1-min system load average`)}t.innerHTML=n,K=e,q=r}window.navigateToTableData=function(e){let t=document.querySelector(`.table-btn[data-table="${e}"]`);t&&(window.AppState.currentTable=e,window.AppState.currentTableBtnElement=t,window.handleSwitchTab(`data-btn`))};function We(e){let t=document.getElementById(`status-distribution-container`),n=document.getElementById(`status-toptables-container`);if(!t||!n)return;let r=Object.entries(e).sort((e,t)=>t[1]-e[1]);if(r.length===0){n.classList.add(`hidden`),t.classList.add(`hidden`);return}let i=r.slice(0,5),a=r.reduce((e,[t,n])=>e+n,0),o=[`#3b82f6`,`#10b981`,`#f59e0b`,`#8b5cf6`,`#ec4899`],s=`
|
|
1049
|
+
<div class="analytics-panel-header">
|
|
1050
|
+
<h3 class="analytics-panel-title">Top Largest Tables</h3>
|
|
1051
|
+
<span class="analytics-panel-badge">${r.length} tables</span>
|
|
1052
|
+
</div>
|
|
1053
|
+
<div class="top-tables-list">
|
|
1054
|
+
`,c=i[0]?Math.max(i[0][1],1):1;i.forEach(([e,t],n)=>{let r=(t/c*100).toFixed(1),i=a>0?(t/a*100).toFixed(1):0;s+=`
|
|
1055
|
+
<div class="top-table-item hover-clickable" onclick="window.navigateToTableData('${e}')" title="Click to view ${e} data">
|
|
1056
|
+
<div class="top-table-bar-fill" style="width: ${r}%; background-color: ${o[n]}"></div>
|
|
1057
|
+
<div class="top-table-content">
|
|
1058
|
+
<div class="top-table-left">
|
|
1059
|
+
<span class="top-table-rank">#${n+1}</span>
|
|
1060
|
+
<span class="top-table-name">${e}</span>
|
|
1061
|
+
</div>
|
|
1062
|
+
<div class="top-table-right">
|
|
1063
|
+
<span class="top-table-pct">${i}%</span>
|
|
1064
|
+
<span class="top-table-rows">${t.toLocaleString()} rows</span>
|
|
1065
|
+
<span class="material-symbols-outlined top-table-arrow">arrow_forward</span>
|
|
1066
|
+
</div>
|
|
1067
|
+
</div>
|
|
1068
|
+
</div>
|
|
1069
|
+
`}),s+=`</div>`,n.innerHTML=s,n.classList.remove(`hidden`),n.classList.add(`block`);let l=`
|
|
1070
|
+
<div class="analytics-panel-header">
|
|
1071
|
+
<h3 class="analytics-panel-title">Row Count Distribution</h3>
|
|
1072
|
+
<span class="analytics-panel-badge">${a.toLocaleString()} total</span>
|
|
1073
|
+
</div>
|
|
1074
|
+
<div class="distribution-content">
|
|
1075
|
+
`,u=`<div class="distribution-legend">`,d=[],f=0;if(a>0){let e=0;i.forEach(([t,n],r)=>{let i=n/a*100;e+=n;let s=f,c=f+i;d.push(`${o[r]} ${s.toFixed(2)}% ${c.toFixed(2)}%`),f=c,u+=`
|
|
1076
|
+
<div class="legend-item" onclick="window.navigateToTableData('${t}')" style="cursor: pointer;" title="View ${t}">
|
|
1077
|
+
<div class="legend-color" style="background: ${o[r]}"></div>
|
|
1078
|
+
<span class="legend-name">${t}</span>
|
|
1079
|
+
<span class="legend-pct">${i.toFixed(1)}%</span>
|
|
1080
|
+
</div>
|
|
1081
|
+
`});let t=a-e;if(t>0&&r.length>5){let e=t/a*100,n=f,i=f+e;d.push(`#64748b ${n.toFixed(2)}% ${i.toFixed(2)}%`),f=i,u+=`
|
|
1082
|
+
<div class="legend-item">
|
|
1083
|
+
<div class="legend-color" style="background: #64748b"></div>
|
|
1084
|
+
<span class="legend-name">Others (${r.length-5})</span>
|
|
1085
|
+
<span class="legend-pct">${e.toFixed(1)}%</span>
|
|
1086
|
+
</div>
|
|
1087
|
+
`}l+=`
|
|
1088
|
+
<div class="distribution-pie-wrapper">
|
|
1089
|
+
<div class="distribution-pie" style="background: conic-gradient(${d.join(`, `)});"></div>
|
|
1090
|
+
<div class="donut-center-info">
|
|
1091
|
+
<span class="donut-total-num">${a>999999?(a/1e6).toFixed(1)+`M`:a.toLocaleString()}</span>
|
|
1092
|
+
<span class="donut-total-label">Total Rows</span>
|
|
1093
|
+
</div>
|
|
1094
|
+
</div>
|
|
1095
|
+
`}else l+=`
|
|
1096
|
+
<div class="distribution-pie-wrapper">
|
|
1097
|
+
<div class="distribution-pie" style="background: var(--color-border);"></div>
|
|
1098
|
+
<div class="donut-center-info">
|
|
1099
|
+
<span class="donut-total-num">0</span>
|
|
1100
|
+
<span class="donut-total-label">No Rows</span>
|
|
1101
|
+
</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
`,u+=`<div class="text-soft" style="font-size: 13px;">No data rows found</div>`;u+=`</div>`,l+=u+`</div>`,t.innerHTML=l,t.classList.remove(`hidden`),t.classList.add(`block`)}function Ge(){Y()}async function Ke(e){e.innerHTML=`
|
|
1104
|
+
<div class="status-dashboard">
|
|
1105
|
+
<!-- 1. Hero Overview Panel -->
|
|
1106
|
+
<div class="hero-overview-panel">
|
|
1107
|
+
<div class="status-db-icon-container">
|
|
1108
|
+
<span class="material-symbols-outlined status-db-icon" id="status-db-icon">database</span>
|
|
1109
|
+
<span id="connection-dot" class="connection-dot green"></span>
|
|
1110
|
+
</div>
|
|
1111
|
+
<div class="hero-info">
|
|
1112
|
+
<div class="hero-title-row">
|
|
1113
|
+
<h1 id="header-db-name" class="hero-db-name">-</h1>
|
|
1114
|
+
<span id="header-status-badge" class="status-badge connected">
|
|
1115
|
+
<span class="status-badge-dot"></span>
|
|
1116
|
+
<span id="header-status-text">Connected</span>
|
|
1117
|
+
</span>
|
|
1118
|
+
</div>
|
|
1119
|
+
<div class="hero-subinfo">
|
|
1120
|
+
<span id="header-db-type" class="db-type-badge">-</span>
|
|
1121
|
+
<span class="hero-dot-separator">•</span>
|
|
1122
|
+
<span id="header-db-version" class="hero-db-version">-</span>
|
|
1123
|
+
<span class="hero-dot-separator">•</span>
|
|
1124
|
+
<span id="header-last-updated" class="hero-update-text">Just now</span>
|
|
1125
|
+
</div>
|
|
1126
|
+
</div>
|
|
1127
|
+
<div class="hero-actions">
|
|
1128
|
+
<button id="status-live-toggle" class="header-btn secondary" title="Toggle Auto-refresh">
|
|
1129
|
+
<span class="material-symbols-outlined" style="font-size: 15px; color: var(--color-success);">radio_button_checked</span>
|
|
1130
|
+
<span id="status-live-text">Live (3s)</span>
|
|
1131
|
+
</button>
|
|
1132
|
+
<button id="status-refresh-btn" class="header-btn secondary" title="Refresh Now">
|
|
1133
|
+
<span class="material-symbols-outlined" style="font-size: 16px;">refresh</span>
|
|
1134
|
+
<span>Refresh</span>
|
|
1135
|
+
</button>
|
|
1136
|
+
</div>
|
|
1137
|
+
</div>
|
|
1138
|
+
|
|
1139
|
+
<!-- 2. Metric Cards Grid -->
|
|
1140
|
+
<div class="status-grid" id="status-cards-container">
|
|
1141
|
+
<!-- Cards will be injected here -->
|
|
1142
|
+
</div>
|
|
1143
|
+
|
|
1144
|
+
<!-- 3. Analytics Split View (Top Tables & Donut Chart) -->
|
|
1145
|
+
<div class="analytics-split-view">
|
|
1146
|
+
<div id="status-toptables-container" class="top-tables-container hidden">
|
|
1147
|
+
<!-- Top tables injected here -->
|
|
1148
|
+
</div>
|
|
1149
|
+
<div id="status-distribution-container" class="distribution-container hidden">
|
|
1150
|
+
<!-- Distribution Bar injected here -->
|
|
1151
|
+
</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
|
|
1154
|
+
<!-- 4. Quick Actions Panel -->
|
|
1155
|
+
<div class="quick-actions-panel">
|
|
1156
|
+
<div class="analytics-panel-header">
|
|
1157
|
+
<h3 class="analytics-panel-title">Quick Operations</h3>
|
|
1158
|
+
<span class="analytics-panel-badge">Database Tools</span>
|
|
1159
|
+
</div>
|
|
1160
|
+
<div class="quick-actions-grid">
|
|
1161
|
+
<button type="button" class="quick-action-card" id="btn-quick-export-backup" title="Export full database backup (.sql)">
|
|
1162
|
+
<div class="quick-action-icon" style="color: var(--color-primary); background: rgba(59, 130, 246, 0.1);">
|
|
1163
|
+
<span class="material-symbols-outlined">download</span>
|
|
1164
|
+
</div>
|
|
1165
|
+
<div class="quick-action-info">
|
|
1166
|
+
<div class="quick-action-title">Export Database Backup</div>
|
|
1167
|
+
<div class="quick-action-desc">Download complete SQL schema & data dump</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
</button>
|
|
1170
|
+
|
|
1171
|
+
<button type="button" class="quick-action-card" id="btn-quick-export-dict" title="Download database markdown data dictionary">
|
|
1172
|
+
<div class="quick-action-icon" style="color: #10b981; background: rgba(16, 185, 129, 0.1);">
|
|
1173
|
+
<span class="material-symbols-outlined">menu_book</span>
|
|
1174
|
+
</div>
|
|
1175
|
+
<div class="quick-action-info">
|
|
1176
|
+
<div class="quick-action-title">Data Dictionary</div>
|
|
1177
|
+
<div class="quick-action-desc">Generate detailed markdown schema documentation</div>
|
|
1178
|
+
</div>
|
|
1179
|
+
</button>
|
|
1180
|
+
|
|
1181
|
+
<button type="button" class="quick-action-card" id="btn-quick-open-console" title="Open SQL Query Console">
|
|
1182
|
+
<div class="quick-action-icon" style="color: #f59e0b; background: rgba(245, 158, 11, 0.1);">
|
|
1183
|
+
<span class="material-symbols-outlined">terminal</span>
|
|
1184
|
+
</div>
|
|
1185
|
+
<div class="quick-action-info">
|
|
1186
|
+
<div class="quick-action-title">SQL Console</div>
|
|
1187
|
+
<div class="quick-action-desc">Write, execute and test arbitrary SQL queries</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
</button>
|
|
1190
|
+
|
|
1191
|
+
<button type="button" class="quick-action-card" id="btn-quick-open-erd" title="View Entity Relationship Diagram">
|
|
1192
|
+
<div class="quick-action-icon" style="color: #8b5cf6; background: rgba(139, 92, 246, 0.1);">
|
|
1193
|
+
<span class="material-symbols-outlined">hub</span>
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="quick-action-info">
|
|
1196
|
+
<div class="quick-action-title">Visual ERD</div>
|
|
1197
|
+
<div class="quick-action-desc">Explore schema relationships & table foreign keys</div>
|
|
1198
|
+
</div>
|
|
1199
|
+
</button>
|
|
1200
|
+
</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
|
|
1203
|
+
<!-- 5. Feature Prompts (if any) -->
|
|
1204
|
+
<div id="feature-prompts-container" class="flex-col gap-4 hidden">
|
|
1205
|
+
<!-- Feature prompts will be injected here -->
|
|
1206
|
+
</div>
|
|
1207
|
+
</div>
|
|
1208
|
+
`,await Y();let t=document.getElementById(`btn-quick-export-backup`);t&&t.addEventListener(`click`,()=>{window.open(`/api/database/export`,`_blank`)});let n=document.getElementById(`btn-quick-export-dict`);n&&n.addEventListener(`click`,()=>{window.open(`/api/database/dictionary`,`_blank`)});let r=document.getElementById(`btn-quick-open-console`);r&&r.addEventListener(`click`,()=>{typeof window.handleSwitchTab==`function`&&window.handleSwitchTab(`console-btn`)});let i=document.getElementById(`btn-quick-open-erd`);i&&i.addEventListener(`click`,()=>{typeof window.handleSwitchTab==`function`&&window.handleSwitchTab(`erd-btn`)});let a=document.getElementById(`status-refresh-btn`);a&&a.addEventListener(`click`,()=>{let e=a.querySelector(`.material-symbols-outlined`);e&&(e.style.transition=`transform 0.4s ease`,e.style.transform=`rotate(${(e.dataset.rot||0)*1+360}deg)`,e.dataset.rot=(e.dataset.rot||0)*1+360),Y()});let o=!0,s=document.getElementById(`status-live-toggle`);s&&s.addEventListener(`click`,()=>{o=!o;let e=s.querySelector(`.material-symbols-outlined`),t=document.getElementById(`status-live-text`);o?(e&&(e.style.color=`var(--color-success)`),t&&(t.textContent=`Live (3s)`)):(e&&(e.style.color=`var(--color-text-soft)`),t&&(t.textContent=`Paused`))}),window.statusPollingInterval&&clearInterval(window.statusPollingInterval),window.statusPollingInterval=setInterval(()=>{window.AppState.currentTab===`status-btn`&&o&&Y()},3e3)}async function Y(){let e=performance.now();try{let[t,n]=await Promise.all([fetch(`/api/status`),fetch(`/api/tables/stats`)]),r=await t.json(),i=await n.json(),a=Math.round(performance.now()-e);r.success?qe(r.data,i.data,a):Je(r.error)}catch(e){Je(e.message)}}function qe(e,t={},n=null){let r=document.getElementById(`status-db-icon`),i=document.getElementById(`connection-dot`),a=document.getElementById(`header-status-badge`),o=document.getElementById(`header-status-text`);if(!r||!i)return;if(e.status===`connected`)r.textContent=`database`,i.className=`connection-dot green`,a&&(a.className=`status-badge connected`),o&&(o.textContent=`Connected`);else{r.textContent=`database_off`,i.className=`connection-dot red`,a&&(a.className=`status-badge disconnected`),o&&(o.textContent=`Disconnected`);return}let s=document.getElementById(`header-db-name`),c=document.getElementById(`header-db-type`),l=document.getElementById(`header-db-version`),u=document.getElementById(`header-last-updated`);if(s&&(s.textContent=e.dbName||`Database`),c&&(c.textContent=e.dbType?e.dbType.toUpperCase():`UNKNOWN`,c.setAttribute(`data-db-type`,e.dbType||`unknown`)),l&&(l.textContent=e.version?`v${e.version}`:`N/A`),u){let e=new Date,t=`${e.getHours().toString().padStart(2,`0`)}:${e.getMinutes().toString().padStart(2,`0`)}:${e.getSeconds().toString().padStart(2,`0`)}`;u.textContent=n===null?t:`${t} (${n}ms)`}Ue(e);let d=document.getElementById(`feature-prompts-container`);d&&(e.dbType===`sqlite`?(d.classList.add(`hidden`),d.classList.remove(`flex`),d.innerHTML=``):(d.innerHTML=`
|
|
1209
|
+
<div class="feature-prompt">
|
|
1210
|
+
<span class="material-symbols-outlined icon">info</span>
|
|
1211
|
+
<div class="feature-prompt-content">
|
|
1212
|
+
<div class="prompt-title">Avg Query Latency & Slow Queries</div>
|
|
1213
|
+
<div class="prompt-desc">This metric requires database plugins to be enabled on your server (e.g. <code>pg_stat_statements</code> for PostgreSQL, or <code>Performance Schema</code> for MySQL).</div>
|
|
1214
|
+
</div>
|
|
1215
|
+
<button type="button" class="feature-prompt-close" onclick="this.closest('#feature-prompts-container').classList.add('hidden')" title="Dismiss">
|
|
1216
|
+
<span class="material-symbols-outlined">close</span>
|
|
1217
|
+
</button>
|
|
1218
|
+
</div>
|
|
1219
|
+
`,d.classList.remove(`hidden`),d.classList.add(`flex`))),We(t)}function Je(e){let t=document.getElementById(`status-db-icon`),n=document.getElementById(`connection-dot`);t&&n&&(t.textContent=`database_off`,n.className=`connection-dot red`);let r=document.getElementById(`status-cards-container`);r&&(r.innerHTML=`<div class="text-red-500">Failed to load database status: ${e}</div>`)}var Ye=()=>{let e=localStorage.getItem(`drixio-theme`),t=window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches,n=e=>{let t=document.getElementById(`theme-icon`),n=document.getElementById(`theme-text`);t&&(t.textContent=e?`light_mode`:`dark_mode`),n&&(n.textContent=e?`Light`:`Dark`)};e===`dark`||!e&&t?(document.documentElement.setAttribute(`data-theme`,`dark`),n(!0)):n(!1);let r=document.getElementById(`theme-toggle`);r&&r.addEventListener(`click`,()=>{let e=document.documentElement.getAttribute(`data-theme`)===`dark`,t=e?`light`:`dark`;document.documentElement.setAttribute(`data-theme`,t),localStorage.setItem(`drixio-theme`,t),n(!e)})},Xe=()=>{window.showToast=function(e,t=`success`){let n=document.getElementById(`toast-container`);if(!n)return;let r=document.createElement(`div`);r.className=`toast ${t}`,r.innerHTML=`<span class="material-symbols-outlined">${t===`success`?`check_circle`:`error`}</span> <span>${e}</span>`,n.appendChild(r),setTimeout(()=>{r.parentNode&&r.parentNode.removeChild(r)},3e3)}},Ze=()=>{document.addEventListener(`mouseup`,()=>{window.DataGrid&&window.DataGrid.selection&&(window.DataGrid.selection.isDragging=!1),window.SchemaGrid&&window.SchemaGrid.selection&&(window.SchemaGrid.selection.isDragging=!1)}),document.addEventListener(`contextmenu`,e=>{let t=window.AppState?.currentTab===`data-btn`,n=window.AppState?.currentTab===`schema-btn`;if(!t&&!n||t&&!window.DataGrid||n&&!window.SchemaGrid)return;let r=e.target.closest(`td.row-header`);if(!r)return;e.preventDefault();let i=parseInt(r.dataset.rowIdx),a=e=>{let r=[i],a=t?window.DataGrid:window.SchemaGrid;if(a&&a.selection&&a.selection.startRow!==-1){let e=a.selection,t=Math.min(e.startRow,e.endRow),n=Math.max(e.startRow,e.endRow);if(i>=t&&i<=n){r=[];for(let e=t;e<=n;e++)r.push(e)}}t?(window.DataGrid.currentTransaction=[],e===`delete`?r.forEach(e=>w(e)):e===`duplicate`&&(ae(r,window.DataGrid.schema.map(e=>e.name)),setTimeout(()=>{let e=document.getElementById(`data-grid-container-${window.AppState.currentTable}`);e&&(e.scrollTop=e.scrollHeight)},50)),window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null):n&&(window.SchemaGrid.currentTransaction=[],e===`delete`?r.forEach(e=>P(e)):e===`duplicate`&&(pe(r,[`name`,`type`,`isPk`,`nullable`,`defaultValue`,`Index`]),setTimeout(()=>{let e=document.getElementById(`schema-grid-container-${window.AppState.currentTable}`);e&&(e.scrollTop=e.scrollHeight)},50)),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null)};f(e,[{label:`Duplicate Row(s)`,icon:`content_copy`,action:()=>a(`duplicate`)},`divider`,{label:`Delete Row(s)`,icon:`delete`,danger:!0,action:()=>a(`delete`)}])}),document.addEventListener(`keydown`,e=>{let t=window.AppState?.currentTab===`data-btn`,n=window.AppState?.currentTab===`schema-btn`;if(e.key===`F5`){t&&window.DataGrid?.refreshData?(e.preventDefault(),window.DataGrid.refreshData()):n&&document.getElementById(`btn-refresh-schema`)&&(e.preventDefault(),document.getElementById(`btn-refresh-schema`).click());return}if(!t&&!n)return;if(e.key.toLowerCase()===`s`&&(e.ctrlKey||e.metaKey)){e.preventDefault(),(e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`)&&e.target.blur(),setTimeout(()=>{t&&window.saveDataGridEdits?.(),n&&window.saveSchemaEdits?.()},50);return}if(e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`)return;if([`ArrowUp`,`ArrowDown`,`ArrowLeft`,`ArrowRight`].includes(e.key)){let n=t?window.DataGrid:window.SchemaGrid;if(!n||!n.selection)return;e.preventDefault();let r=n.selection;if(r.startRow===-1)return;let i=t?n.schema.length:5,a=window.AppState?.currentTable,o=document.querySelectorAll(t?`#data-grid-table-${a} tbody tr`:`#schema-grid-table-${a} tbody tr`),s=o.length>0?o.length-1:0,c=e.shiftKey?r.endRow:r.startRow,l=e.shiftKey?r.endCol:r.startCol;e.key===`ArrowUp`&&(c=Math.max(0,c-1)),e.key===`ArrowDown`&&(c=Math.min(s,c+1)),e.key===`ArrowLeft`&&(l=Math.max(0,l-1)),e.key===`ArrowRight`&&(l=Math.min(i-1,l+1)),e.shiftKey?(r.endRow=c,r.endCol=l):(r.startRow=c,r.endRow=c,r.startCol=l,r.endCol=l),S(t?`data-grid-table-${a}`:`schema-grid-table-${a}`,n),setTimeout(()=>{let e=document.querySelector(t?`#data-grid-table-${a} td.data-cell[data-row-idx="${c}"][data-col-idx="${l}"]`:`#schema-grid-table-${a} td.data-cell[data-row-idx="${c}"][data-col-idx="${l}"]`);if(e){let n=document.getElementById(t?`data-grid-container-${a}`:`schema-grid-container-${a}`);if(n){let t=e.getBoundingClientRect(),r=n.getBoundingClientRect();t.bottom>r.bottom?n.scrollTop+=t.bottom-r.bottom+5:t.top<r.top+30&&(n.scrollTop-=r.top+30-t.top),t.right>r.right?n.scrollLeft+=t.right-r.right+5:t.left<r.left+50&&(n.scrollLeft-=r.left+50-t.left)}}},5);return}let r=e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey,i=e.key===`F2`;if(e.key===`Enter`||r||i){let n=t?window.DataGrid:window.SchemaGrid;if(!n||!n.selection||n.selection.startRow===-1)return;let a=window.AppState?.currentTable;if(!a)return;let o=Math.min(n.selection.startRow,n.selection.endRow),s=Math.min(n.selection.startCol,n.selection.endCol),c=document.querySelector(t?`#data-grid-table-${a} td.data-cell[data-row-idx="${o}"][data-col-idx="${s}"]`:`#schema-grid-table-${a} td.data-cell[data-row-idx="${o}"][data-col-idx="${s}"]`);if(c&&!c.classList.contains(`manage-indexes-cell`)&&!c.querySelector(`input, select`)){e.preventDefault();let t=new MouseEvent(`dblclick`,{bubbles:!0,cancelable:!0,view:window});c.dispatchEvent(t);let n=c.querySelector(`input`);if(n){if(r)n.value=e.key,n.setSelectionRange(e.key.length,e.key.length);else if(i){let e=n.value.length;n.setSelectionRange(e,e)}}else{let t=c.querySelector(`select`);if(t&&r){let n=Array.from(t.options).find(t=>t.text.toLowerCase().startsWith(e.key.toLowerCase()));n&&(t.value=n.value,t.dispatchEvent(new Event(`change`,{bubbles:!0})))}}return}}if(e.key===`Tab`){let n=t?window.DataGrid:window.SchemaGrid;if(!n||!n.selection||n.selection.startRow===-1)return;e.preventDefault();let r=window.AppState?.currentTable,i=Math.min(n.selection.startRow,n.selection.endRow),a=Math.min(n.selection.startCol,n.selection.endCol),o=t?n.schema.length:5,s=document.querySelectorAll(t?`#data-grid-table-${r} tbody tr`:`#schema-grid-table-${r} tbody tr`),c=s.length>0?s.length-1:0;e.shiftKey?(--a,a<0&&(a=o-1,i=Math.max(0,i-1))):(a+=1,a>=o&&(a=0,i=Math.min(c,i+1))),n.selection.startRow=i,n.selection.endRow=i,n.selection.startCol=a,n.selection.endCol=a,S(t?`data-grid-table-${r}`:`schema-grid-table-${r}`,n),setTimeout(()=>{let e=document.querySelector(t?`#data-grid-table-${r} td.data-cell[data-row-idx="${i}"][data-col-idx="${a}"]`:`#schema-grid-table-${r} td.data-cell[data-row-idx="${i}"][data-col-idx="${a}"]`);if(e){let n=document.getElementById(t?`data-grid-container-${r}`:`schema-grid-container-${r}`);if(n){let t=e.getBoundingClientRect(),r=n.getBoundingClientRect();t.bottom>r.bottom?n.scrollTop+=t.bottom-r.bottom+5:t.top<r.top+30&&(n.scrollTop-=r.top+30-t.top),t.right>r.right?n.scrollLeft+=t.right-r.right+5:t.left<r.left+50&&(n.scrollLeft-=r.left+50-t.left)}}},5);return}if(e.key===`Delete`||e.key===`Backspace`){let e=t?window.DataGrid:window.SchemaGrid;if(!e||!e.selection||e.selection.startRow===-1)return;let r=e.selection,i=Math.min(r.startRow,r.endRow),a=Math.max(r.startRow,r.endRow),o=Math.min(r.startCol,r.endCol),s=Math.max(r.startCol,r.endCol),c=t?window.DataGrid?.schema?.length||1:6,l=r.isRowSelection===!0||o===0&&s>=c-1;if(t)if(l){window.DataGrid.currentTransaction=[];for(let e=i;e<=a;e++)w(e);window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null,window.updateSidebarDirtyState?.()}else window.DataGrid.currentTransaction=[],document.querySelectorAll(`#data-grid-table-${window.AppState.currentTable} .cell-in-range`).forEach(e=>C(e,``,window.DataGrid.schema.map(e=>e.name))),window.DataGrid.currentTransaction.length>0&&window.DataGrid.history.push(window.DataGrid.currentTransaction),window.DataGrid.currentTransaction=null;else if(n)if(l){window.SchemaGrid.currentTransaction=[];for(let e=i;e<=a;e++)P(e);window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null,window.updateSidebarDirtyState?.()}else{window.SchemaGrid.currentTransaction=[];let e=[`name`,`type`,`isPk`,`nullable`,`defaultValue`];document.querySelectorAll(`#schema-grid-table-${window.AppState.currentTable} .cell-in-range`).forEach(t=>{(t.dataset.insertIndex!==void 0||t.dataset.colKey===`name`)&&N(t,``,e)}),window.SchemaGrid.currentTransaction.length>0&&window.SchemaGrid.history.push(window.SchemaGrid.currentTransaction),window.SchemaGrid.currentTransaction=null}}if(e.key.toLowerCase()===`s`&&(e.ctrlKey||e.metaKey)&&(e.preventDefault(),t&&window.saveDataGridEdits(),n&&window.saveSchemaEdits()),e.key.toLowerCase()===`c`&&(e.ctrlKey||e.metaKey)){let e=(t?window.DataGrid:window.SchemaGrid).selection;if(e.startRow===-1)return;let n=Math.min(e.startRow,e.endRow),r=Math.max(e.startRow,e.endRow),i=Math.min(e.startCol,e.endCol),a=Math.max(e.startCol,e.endCol),o=``;for(let e=n;e<=r;e++){let n=[];for(let r=i;r<=a;r++){let i=document.querySelector(t?`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"][data-col-idx="${r}"]`:`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${e}"][data-col-idx="${r}"]`);if(i){let e=i.textContent.replace(/^null$|^\+ New$/,``);n.push(e)}}o+=n.join(` `)+`
|
|
1220
|
+
`}navigator.clipboard.writeText(o.trimEnd()),document.querySelectorAll(`.cell-in-range`).forEach(e=>{e.style.backgroundColor=`var(--color-copy-flash)`,setTimeout(()=>e.style.backgroundColor=``,150)})}if(e.key===`-`&&(e.ctrlKey||e.metaKey)){e.preventDefault();let r=t?window.DataGrid:window.SchemaGrid,i=r.selection;if(i.startRow===-1)return;let a=Math.min(i.startRow,i.endRow),o=Math.max(i.startRow,i.endRow);if(t){r.currentTransaction=[];for(let e=a;e<=o;e++)w(e);r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}else if(n){r.currentTransaction=[];for(let e=a;e<=o;e++)P(e);r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}}if(e.key.toLowerCase()===`z`&&(e.ctrlKey||e.metaKey)){if(e.preventDefault(),t){let e=window.DataGrid.history?.pop();if(e){let t=window.DataGrid.schema.map(e=>e.name);for(let n=e.length-1;n>=0;n--){let r=e[n];r.type===`delete`?ie(r.rowIdx,r.pk):C(r.td,r.oldVal,t,!1)}}}else if(n){let e=window.SchemaGrid.history?.pop();if(e){let t=[`name`,`type`,`isPk`,`nullable`,`defaultValue`];for(let n=e.length-1;n>=0;n--){let r=e[n];r.type===`delete`?fe(r.rowIdx,r.pk):N(r.td,r.oldVal,t,!1)}}}}}),document.addEventListener(`paste`,e=>{let t=window.AppState?.currentTab===`data-btn`,n=window.AppState?.currentTab===`schema-btn`;if(!t&&!n)return;let r=t?window.DataGrid:window.SchemaGrid;if(!r||e.target.tagName===`INPUT`||e.target.tagName===`SELECT`||e.target.tagName===`TEXTAREA`)return;let i=r.selection;if(i.startRow===-1)return;let a=e.clipboardData.getData(`text`);if(!a)return;e.preventDefault();let o=a.split(/\r?\n/).map(e=>e.split(` `)),s=Math.min(i.startRow,i.endRow),c=Math.min(i.startCol,i.endCol);if(r.currentTransaction=[],t){let e=r.schema.map(e=>e.name);o.forEach(t=>{t.forEach((t,n)=>{let r=c+n,i=document.querySelector(`#data-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${s}"][data-col-idx="${r}"]`);i&&C(i,t,e)}),s++}),r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}else if(n){let e=[`name`,`type`,`isPk`,`nullable`,`defaultValue`];o.forEach(t=>{t.forEach((t,n)=>{let r=c+n,i=document.querySelector(`#schema-grid-table-${window.AppState.currentTable} td.data-cell[data-row-idx="${s}"][data-col-idx="${r}"]`);i&&(i.dataset.insertIndex!==void 0||i.dataset.colKey===`name`)&&N(i,t,e)}),s++}),r.currentTransaction.length>0&&r.history.push(r.currentTransaction),r.currentTransaction=null}})},Qe=document.getElementById(`header-container`),$e=document.getElementById(`sidebar-container`),et=document.getElementById(`tab-container`);Qe.innerHTML=e,$e.innerHTML=t,et.innerHTML=n;var X=document.getElementById(`export-btn`),Z=document.getElementById(`export-dropdown`),Q=document.getElementById(`import-btn`),$=document.getElementById(`import-dropdown`);if(Q&&$){Q.addEventListener(`click`,e=>{e.stopPropagation();let t=$.classList.contains(`hidden`);$.classList.toggle(`hidden`),Q.classList.toggle(`is-open`,t),Z&&(Z.classList.add(`hidden`),X?.classList.remove(`is-open`))});let e=e=>{$.classList.add(`hidden`),Q.classList.remove(`is-open`);let t=window.AppState.currentTable;if(!t){window.showToast(`Please select a table from the sidebar first.`,`error`);return}let n=document.createElement(`input`);n.type=`file`,n.accept=e===`json`?`.json`:`.csv`,n.onchange=async n=>{let r=n.target.files[0];if(!r||!confirm(`⚠️ IMPORT DATA CONFIRMATION\n\nTarget Table: ${t}\nFile: ${r.name} (${(r.size/1024).toFixed(1)} KB)\nFormat: ${e.toUpperCase()}\n\nAre you sure you want to insert records from this file into '${t}'?`))return;let i=new FormData;i.append(`file`,r),i.append(`format`,e),window.showToast(`Importing ${r.name} into '${t}'...`,`success`);try{let e=await(await fetch(`/api/tables/${encodeURIComponent(t)}/import`,{method:`POST`,body:i})).json();if(e.success){if(window.showToast(e.message||`Import completed successfully`,`success`),window.AppState.currentTab===`data-btn`){let e=`view-data-btn-${t}`,n=document.getElementById(e);n&&(n.innerHTML=``),window.renderCurrentView(``,!0)}}else window.showToast(e.error||`Import failed`,`error`)}catch{window.showToast(`Network error during data import`,`error`)}},n.click()};document.getElementById(`import-data-csv-btn`)?.addEventListener(`click`,()=>{e(`csv`)}),document.getElementById(`import-data-json-btn`)?.addEventListener(`click`,()=>{e(`json`)}),document.getElementById(`import-erd-json-btn`)?.addEventListener(`click`,()=>{$.classList.add(`hidden`),Q.classList.remove(`is-open`);let e=document.createElement(`input`);e.type=`file`,e.accept=`.json`,e.onchange=async e=>{let t=e.target.files[0];if(t&&confirm(`⚠️ IMPORT ERD LAYOUT CONFIRMATION\n\nFile: ${t.name}\n\nAre you sure you want to import this ERD layout? This will update table coordinates on your diagram canvas.`))try{let e=await t.text(),n=JSON.parse(e);if(typeof n!=`object`||!n)throw Error(`Invalid ERD layout JSON structure.`);let r=n.positions||n;localStorage.setItem(`drixio-erd-positions`,JSON.stringify(r)),window.showToast(`ERD layout imported successfully!`,`success`);let i=document.getElementById(`view-erd-btn`);i&&(i.innerHTML=``,window.AppState.currentTab===`erd-btn`&&window.renderCurrentView())}catch(e){window.showToast(`Failed to parse ERD layout: ${e.message}`,`error`)}},e.click()}),document.getElementById(`import-db-sql-btn`)?.addEventListener(`click`,()=>{$.classList.add(`hidden`),Q.classList.remove(`is-open`);let e=document.createElement(`input`);e.type=`file`,e.accept=`.sql`,e.onchange=async e=>{let t=e.target.files[0];if(!t||!confirm(`🚨 HIGH RISK ACTION: DATABASE RESTORE\n\nFile: ${t.name} (${(t.size/1024).toFixed(1)} KB)\n\nExecuting this SQL script directly against the database may modify, truncate, or drop existing tables and records.\n\nAre you sure you want to proceed?`))return;let n=new FormData;n.append(`file`,t),window.showToast(`Executing ${t.name} on database...`,`success`);try{let e=await(await fetch(`/api/database/import`,{method:`POST`,body:n})).json();e.success?(window.showToast(e.message||`Database restored successfully`,`success`),setTimeout(()=>location.reload(),1500)):window.showToast(e.error||`Import failed`,`error`)}catch{window.showToast(`Network error during database import`,`error`)}},e.click()})}if(X&&Z){X.addEventListener(`click`,e=>{e.stopPropagation();let t=Z.classList.contains(`hidden`);Z.classList.toggle(`hidden`),X.classList.toggle(`is-open`,t),$&&($.classList.add(`hidden`),Q?.classList.remove(`is-open`));let n=window.AppState.currentTab===`data-btn`,r=window.AppState.currentTab===`console-btn`;document.querySelectorAll(`.data-tab-only`).forEach(e=>{e.style.display=n?`list-item`:`none`}),document.querySelectorAll(`.console-tab-only`).forEach(e=>{e.style.display=r?`list-item`:`none`})}),document.addEventListener(`click`,e=>{!X.contains(e.target)&&!Z.contains(e.target)&&(!Q||!Q.contains(e.target))&&(!$||!$.contains(e.target))&&(Z.classList.add(`hidden`),X.classList.remove(`is-open`),$&&($.classList.add(`hidden`),Q?.classList.remove(`is-open`)))});let e=(e,t,n=!1)=>{if(Z.classList.add(`hidden`),X.classList.remove(`is-open`),e===`/api/query/export`){let e=document.querySelector(`#sql-editor textarea`)||document.querySelector(`#sql-editor`),n=``;if(e&&e.value&&(n=e.value),!n&&window.ViewCache&&window.ViewCache.lastQuery&&(n=window.ViewCache.lastQuery),!n){alert(`No SQL query found to export.`);return}fetch(`/api/query/export?format=${t}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sql:n})}).then(e=>e.ok?e.blob():e.json().then(e=>{throw Error(e.error||`Export failed`)})).then(e=>{let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=`query_result_${new Date().getTime()}.${t}`,document.body.appendChild(r),r.click(),r.remove()}).catch(e=>alert(`Failed to export query: `+e.message));return}let r=`${e}?format=${t}`;if(n&&window.DataGrid){let e=T?T():``,t=window.DataGrid.sortState?.col||``,n=window.DataGrid.sortState?.asc??!0;r+=`&where=${encodeURIComponent(e)}&orderCol=${t}&orderAsc=${n}`}window.open(r,`_blank`)};document.getElementById(`export-data-csv-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`csv`)}),document.getElementById(`export-data-json-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`json`)}),document.getElementById(`export-data-filtered-csv-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`csv`,!0)}),document.getElementById(`export-data-filtered-json-btn`)?.addEventListener(`click`,()=>{window.AppState.currentTable&&e(`/api/tables/${window.AppState.currentTable}/export`,`json`,!0)}),document.getElementById(`export-schema-dict-btn`)?.addEventListener(`click`,()=>{window.open(`/api/database/dictionary`,`_blank`),Z.classList.add(`hidden`),X.classList.remove(`is-open`)}),document.getElementById(`export-schema-sql-btn`)?.addEventListener(`click`,()=>{window.open(`/api/database/schema-only`,`_blank`),Z.classList.add(`hidden`),X.classList.remove(`is-open`)}),document.getElementById(`export-console-csv-btn`)?.addEventListener(`click`,()=>{e(`/api/query/export`,`csv`)}),document.getElementById(`export-console-json-btn`)?.addEventListener(`click`,()=>{e(`/api/query/export`,`json`)}),document.getElementById(`export-erd-json-btn`)?.addEventListener(`click`,()=>{let e=localStorage.getItem(`drixio_erd_drafts`);if(!e){alert(`No ERD layout found to export.`);return}let t=new Blob([e],{type:`application/json`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`erd_layout_${new Date().getTime()}.json`,r.click(),Z.classList.add(`hidden`),X.classList.remove(`is-open`)}),document.getElementById(`export-db-sql-btn`)?.addEventListener(`click`,()=>{window.open(`/api/database/export`,`_blank`),Z.classList.add(`hidden`),X.classList.remove(`is-open`)})}window.AppState={currentTable:null,currentTab:`data-btn`,currentTableBtnElement:null,dbType:null},i().then(e=>{if(e&&e.success&&e.data&&(window.AppState.dbType=e.data.dbType,e.data.dbName)){let t=document.getElementById(`db-name`);t&&(t.textContent=e.data.dbName)}}),window.TableStates={},window.ViewCache={},window.updateSidebarDirtyState=function(){document.querySelectorAll(`.table-btn`).forEach(e=>{let t=e.dataset.table,n=e.querySelector(`span`).textContent,r=t||n.replace(/\s*\*$/,``),i=window.TableStates[r],a=!1;if(i){if(i.dataGrid){let e=i.dataGrid;(Object.keys(e.pendingEdits||{}).length>0||e.pendingInserts&&e.pendingInserts.some(e=>Object.keys(e).length>0)||e.pendingDeletes&&e.pendingDeletes.size>0)&&(a=!0)}if(i.schemaGrid){let e=i.schemaGrid,t=e.pendingIndexEdits&&(e.pendingIndexEdits.added.length>0||e.pendingIndexEdits.dropped.length>0);(Object.keys(e.pendingEdits||{}).length>0||e.pendingInserts&&e.pendingInserts.some(e=>Object.keys(e).length>0)||e.pendingDeletes&&e.pendingDeletes.size>0||t)&&(a=!0)}}let o=e.querySelector(`span`),s=o.textContent.replace(/\s*\*$/,``),c=e.querySelector(`.dirty-indicator-dot`);o.textContent=s,a?(o.style.fontWeight=`600`,c&&c.classList.remove(`hidden`)):(o.style.fontWeight=`500`,c&&c.classList.add(`hidden`))})},window.handleSwitchTab=function(e){document.querySelectorAll(`.tab-btn`).forEach(e=>e.classList.remove(`isCurrentTab`));let t=document.getElementById(e);if(t&&t.classList.add(`isCurrentTab`),window.AppState.currentTab=e,e===`erd-btn`||e===`sql-btn`||e===`status-btn`)window.AppState.currentTable=null,window.AppState.currentTableBtnElement=null,document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`));else if(e===`data-btn`||e===`schema-btn`)if(window.AppState.currentTable)window.updateSidebarActiveTable?.(window.AppState.currentTable);else{let e=document.querySelector(`.table-btn`);e&&(window.AppState.currentTable=e.dataset.table||e.querySelector(`span`).textContent.replace(/\s*\*$/,``),window.AppState.currentTableBtnElement=e,e.classList.add(`active`))}window.renderCurrentView()},window.renderEmptyState=function(e){e.innerHTML=`
|
|
1221
|
+
<div class="empty-state">
|
|
1222
|
+
<div class="icon-container">
|
|
1223
|
+
<span class="material-symbols-outlined">database</span>
|
|
1224
|
+
</div>
|
|
1225
|
+
<h2>No Table Selected</h2>
|
|
1226
|
+
<p>Select a table from the sidebar to view its data, schema, or run SQL queries.</p>
|
|
1227
|
+
</div>
|
|
1228
|
+
`},window.renderCurrentView=function(e=``,t=!1){let n=[`erd-btn`,`sql-btn`,`status-btn`].includes(window.AppState.currentTab),r=document.getElementById(`main-content`);document.querySelectorAll(`.view-container`).forEach(e=>e.style.display=`none`);let i=n?`view-${window.AppState.currentTab}`:`view-${window.AppState.currentTab}-${window.AppState.currentTable}`,a=document.getElementById(i);if(a||(a=document.createElement(`div`),a.id=i,a.className=`view-container`,a.style.width=`100%`,a.style.flex=`1`,a.style.display=`flex`,a.style.flexDirection=`column`,a.style.overflow=`hidden`,r.appendChild(a)),a.style.display=`flex`,!window.AppState.currentTable&&!n){window.renderEmptyState(a);return}let o=window.AppState.currentTable;o&&!window.TableStates[o]&&(window.TableStates[o]={dataGrid:null,schemaGrid:null}),window.AppState.currentTab===`data-btn`?(window.TableStates[o].dataGrid&&(window.DataGrid=window.TableStates[o].dataGrid),!window.TableStates[o].dataGrid||!a.hasChildNodes()?M(o,window.AppState.currentTableBtnElement,e,t,a):window.updateSidebarActiveTable?.(o)):window.AppState.currentTab===`schema-btn`?(window.TableStates[o].schemaGrid&&(window.SchemaGrid=window.TableStates[o].schemaGrid),!window.TableStates[o].schemaGrid||!a.hasChildNodes()?_e(o,window.AppState.currentTableBtnElement,a):window.updateSidebarActiveTable?.(o)):window.AppState.currentTab===`sql-btn`?a.hasChildNodes()||Se(o,window.AppState.currentTableBtnElement,a):window.AppState.currentTab===`erd-btn`?a.hasChildNodes()||Be(a):window.AppState.currentTab===`status-btn`&&(a.hasChildNodes()?Ge():Ke(a));let s=document.getElementById(`table-icon`),c=document.getElementById(`table-name`);s&&c&&(o?(s.textContent=`table_chart`,c.textContent=o):window.AppState.currentTab===`erd-btn`?(s.textContent=`account_tree`,c.textContent=`ER Diagram`):window.AppState.currentTab===`sql-btn`?(s.textContent=`terminal`,c.textContent=`SQL Console`):window.AppState.currentTab===`status-btn`?(s.textContent=`monitoring`,c.textContent=`Dashboard`):(s.textContent=`table_chart`,c.textContent=`none`)),document.querySelectorAll(`.table-btn`).forEach(e=>e.classList.remove(`active`)),window.AppState.currentTableBtnElement&&window.AppState.currentTableBtnElement.classList.add(`active`)},window.saveDataGridEdits=re,window.saveSchemaEdits=de,document.addEventListener(`DOMContentLoaded`,()=>{g(),Ye(),Xe(),Ze();let e=document.getElementById(`header-refresh-btn`);e&&e.addEventListener(`click`,()=>{let t=e.querySelector(`.material-symbols-outlined`);t?.classList.add(`animate-spin`);let n=window.AppState.currentTab,r=window.AppState.currentTable;if(r){let e=`view-${n}-${r}`,t=document.getElementById(e);t&&(t.innerHTML=``)}window.renderCurrentView(``,!0),setTimeout(()=>{t?.classList.remove(`animate-spin`),window.showToast&&window.showToast(`Data refreshed`,`success`)},350)})});
|