@pixagram/lacerta-db 0.6.0 → 0.6.2
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/browser.min.js +7 -7
- package/dist/index.min.js +7 -7
- package/index.js +111 -11
- package/package.json +1 -1
- package/readme.md +1075 -949
- package/dist/lacertadb-interface.html +0 -1641
|
@@ -1,1641 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>LacertaDB Management Interface</title>
|
|
7
|
-
<style>
|
|
8
|
-
* {
|
|
9
|
-
margin: 0;
|
|
10
|
-
padding: 0;
|
|
11
|
-
box-sizing: border-box;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
:root {
|
|
15
|
-
--primary: #2563eb;
|
|
16
|
-
--primary-dark: #1d4ed8;
|
|
17
|
-
--secondary: #64748b;
|
|
18
|
-
--success: #22c55e;
|
|
19
|
-
--danger: #ef4444;
|
|
20
|
-
--warning: #f59e0b;
|
|
21
|
-
--dark: #1e293b;
|
|
22
|
-
--light: #f1f5f9;
|
|
23
|
-
--border: #e2e8f0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
body {
|
|
27
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
28
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
29
|
-
min-height: 100vh;
|
|
30
|
-
color: #334155;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.container {
|
|
34
|
-
display: flex;
|
|
35
|
-
height: 100vh;
|
|
36
|
-
background: rgba(255, 255, 255, 0.95);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* Sidebar */
|
|
40
|
-
.sidebar {
|
|
41
|
-
width: 250px;
|
|
42
|
-
background: var(--dark);
|
|
43
|
-
color: white;
|
|
44
|
-
padding: 20px 0;
|
|
45
|
-
overflow-y: auto;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.logo {
|
|
49
|
-
padding: 0 20px 30px;
|
|
50
|
-
border-bottom: 1px solid rgba(255,255,255,0.1);
|
|
51
|
-
margin-bottom: 20px;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.logo h1 {
|
|
55
|
-
font-size: 24px;
|
|
56
|
-
font-weight: 700;
|
|
57
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
58
|
-
-webkit-background-clip: text;
|
|
59
|
-
-webkit-text-fill-color: transparent;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.nav-item {
|
|
63
|
-
padding: 12px 20px;
|
|
64
|
-
cursor: pointer;
|
|
65
|
-
transition: all 0.3s;
|
|
66
|
-
display: flex;
|
|
67
|
-
align-items: center;
|
|
68
|
-
gap: 12px;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.nav-item:hover {
|
|
72
|
-
background: rgba(255,255,255,0.1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.nav-item.active {
|
|
76
|
-
background: var(--primary);
|
|
77
|
-
position: relative;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.nav-item.active::before {
|
|
81
|
-
content: '';
|
|
82
|
-
position: absolute;
|
|
83
|
-
left: 0;
|
|
84
|
-
top: 0;
|
|
85
|
-
bottom: 0;
|
|
86
|
-
width: 4px;
|
|
87
|
-
background: white;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.nav-icon {
|
|
91
|
-
font-size: 18px;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/* Main Content */
|
|
95
|
-
.main-content {
|
|
96
|
-
flex: 1;
|
|
97
|
-
overflow-y: auto;
|
|
98
|
-
padding: 30px;
|
|
99
|
-
background: #f8fafc;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.panel {
|
|
103
|
-
display: none;
|
|
104
|
-
animation: fadeIn 0.3s;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.panel.active {
|
|
108
|
-
display: block;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
@keyframes fadeIn {
|
|
112
|
-
from { opacity: 0; transform: translateY(10px); }
|
|
113
|
-
to { opacity: 1; transform: translateY(0); }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.panel-header {
|
|
117
|
-
margin-bottom: 30px;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.panel-title {
|
|
121
|
-
font-size: 28px;
|
|
122
|
-
font-weight: 700;
|
|
123
|
-
color: var(--dark);
|
|
124
|
-
margin-bottom: 10px;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.panel-subtitle {
|
|
128
|
-
color: var(--secondary);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/* Cards */
|
|
132
|
-
.card {
|
|
133
|
-
background: white;
|
|
134
|
-
border-radius: 12px;
|
|
135
|
-
padding: 24px;
|
|
136
|
-
margin-bottom: 20px;
|
|
137
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.card-title {
|
|
141
|
-
font-size: 18px;
|
|
142
|
-
font-weight: 600;
|
|
143
|
-
margin-bottom: 15px;
|
|
144
|
-
color: var(--dark);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/* Stats Grid */
|
|
148
|
-
.stats-grid {
|
|
149
|
-
display: grid;
|
|
150
|
-
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
151
|
-
gap: 20px;
|
|
152
|
-
margin-bottom: 30px;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.stat-card {
|
|
156
|
-
background: white;
|
|
157
|
-
padding: 20px;
|
|
158
|
-
border-radius: 12px;
|
|
159
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
160
|
-
transition: transform 0.3s, box-shadow 0.3s;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.stat-card:hover {
|
|
164
|
-
transform: translateY(-5px);
|
|
165
|
-
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.stat-label {
|
|
169
|
-
font-size: 14px;
|
|
170
|
-
color: var(--secondary);
|
|
171
|
-
margin-bottom: 5px;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.stat-value {
|
|
175
|
-
font-size: 32px;
|
|
176
|
-
font-weight: 700;
|
|
177
|
-
color: var(--primary);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/* Forms */
|
|
181
|
-
.form-group {
|
|
182
|
-
margin-bottom: 20px;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
label {
|
|
186
|
-
display: block;
|
|
187
|
-
margin-bottom: 8px;
|
|
188
|
-
font-weight: 500;
|
|
189
|
-
color: var(--dark);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
input, textarea, select {
|
|
193
|
-
width: 100%;
|
|
194
|
-
padding: 10px 15px;
|
|
195
|
-
border: 1px solid var(--border);
|
|
196
|
-
border-radius: 8px;
|
|
197
|
-
font-size: 14px;
|
|
198
|
-
transition: all 0.3s;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
input:focus, textarea:focus, select:focus {
|
|
202
|
-
outline: none;
|
|
203
|
-
border-color: var(--primary);
|
|
204
|
-
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
textarea {
|
|
208
|
-
resize: vertical;
|
|
209
|
-
min-height: 100px;
|
|
210
|
-
font-family: 'Monaco', 'Courier New', monospace;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/* Buttons */
|
|
214
|
-
.btn {
|
|
215
|
-
padding: 10px 20px;
|
|
216
|
-
border: none;
|
|
217
|
-
border-radius: 8px;
|
|
218
|
-
font-size: 14px;
|
|
219
|
-
font-weight: 500;
|
|
220
|
-
cursor: pointer;
|
|
221
|
-
transition: all 0.3s;
|
|
222
|
-
display: inline-flex;
|
|
223
|
-
align-items: center;
|
|
224
|
-
gap: 8px;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.btn-sm {
|
|
228
|
-
padding: 5px 10px;
|
|
229
|
-
font-size: 12px;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
.btn-primary {
|
|
233
|
-
background: var(--primary);
|
|
234
|
-
color: white;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.btn-primary:hover {
|
|
238
|
-
background: var(--primary-dark);
|
|
239
|
-
transform: translateY(-2px);
|
|
240
|
-
box-shadow: 0 5px 15px rgba(37, 99, 235, 0.3);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.btn-secondary {
|
|
244
|
-
background: var(--secondary);
|
|
245
|
-
color: white;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.btn-success {
|
|
249
|
-
background: var(--success);
|
|
250
|
-
color: white;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.btn-danger {
|
|
254
|
-
background: var(--danger);
|
|
255
|
-
color: white;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.btn-warning {
|
|
259
|
-
background: var(--warning);
|
|
260
|
-
color: white;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.btn-group {
|
|
264
|
-
display: flex;
|
|
265
|
-
gap: 10px;
|
|
266
|
-
margin-top: 20px;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/* Tables */
|
|
270
|
-
.table-container {
|
|
271
|
-
overflow-x: auto;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
table {
|
|
275
|
-
width: 100%;
|
|
276
|
-
border-collapse: collapse;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
th {
|
|
280
|
-
background: var(--light);
|
|
281
|
-
padding: 12px;
|
|
282
|
-
text-align: left;
|
|
283
|
-
font-weight: 600;
|
|
284
|
-
color: var(--dark);
|
|
285
|
-
border-bottom: 2px solid var(--border);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
td {
|
|
289
|
-
padding: 12px;
|
|
290
|
-
border-bottom: 1px solid var(--border);
|
|
291
|
-
white-space: nowrap;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
tr:hover {
|
|
295
|
-
background: var(--light);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
td.actions {
|
|
299
|
-
display: flex;
|
|
300
|
-
gap: 5px;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
td.document-id {
|
|
304
|
-
max-width: 200px;
|
|
305
|
-
overflow: hidden;
|
|
306
|
-
text-overflow: ellipsis;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/* Console */
|
|
310
|
-
.console {
|
|
311
|
-
background: #1a1a1a;
|
|
312
|
-
color: #00ff00;
|
|
313
|
-
padding: 15px;
|
|
314
|
-
border-radius: 8px;
|
|
315
|
-
font-family: 'Monaco', 'Courier New', monospace;
|
|
316
|
-
font-size: 13px;
|
|
317
|
-
height: 400px;
|
|
318
|
-
overflow-y: auto;
|
|
319
|
-
margin-top: 20px;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
.console-line {
|
|
323
|
-
margin-bottom: 5px;
|
|
324
|
-
white-space: pre-wrap;
|
|
325
|
-
word-break: break-all;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
.console-prompt {
|
|
329
|
-
color: #00ff00;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.console-error {
|
|
333
|
-
color: #ff4444;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
.console-success {
|
|
337
|
-
color: #00ff00;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/* Query Builder */
|
|
341
|
-
.query-builder {
|
|
342
|
-
display: flex;
|
|
343
|
-
gap: 20px;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
.query-section {
|
|
347
|
-
flex: 1;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
.query-results {
|
|
351
|
-
margin-top: 20px;
|
|
352
|
-
max-height: 500px;
|
|
353
|
-
overflow: auto;
|
|
354
|
-
background: white;
|
|
355
|
-
padding: 20px;
|
|
356
|
-
border-radius: 8px;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/* Modal */
|
|
360
|
-
.modal {
|
|
361
|
-
display: none;
|
|
362
|
-
position: fixed;
|
|
363
|
-
top: 0;
|
|
364
|
-
left: 0;
|
|
365
|
-
right: 0;
|
|
366
|
-
bottom: 0;
|
|
367
|
-
background: rgba(0,0,0,0.5);
|
|
368
|
-
z-index: 1000;
|
|
369
|
-
align-items: center;
|
|
370
|
-
justify-content: center;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
.modal.show {
|
|
374
|
-
display: flex;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
.modal-content {
|
|
378
|
-
background: white;
|
|
379
|
-
padding: 30px;
|
|
380
|
-
border-radius: 12px;
|
|
381
|
-
max-width: 600px;
|
|
382
|
-
width: 90%;
|
|
383
|
-
max-height: 80vh;
|
|
384
|
-
overflow-y: auto;
|
|
385
|
-
animation: fadeIn 0.3s;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.modal-header {
|
|
389
|
-
margin-bottom: 20px;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.modal-title {
|
|
393
|
-
font-size: 24px;
|
|
394
|
-
font-weight: 700;
|
|
395
|
-
color: var(--dark);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/* Tabs */
|
|
399
|
-
.tabs {
|
|
400
|
-
display: flex;
|
|
401
|
-
gap: 10px;
|
|
402
|
-
border-bottom: 2px solid var(--border);
|
|
403
|
-
margin-bottom: 20px;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
.tab {
|
|
407
|
-
padding: 10px 20px;
|
|
408
|
-
cursor: pointer;
|
|
409
|
-
border-bottom: 2px solid transparent;
|
|
410
|
-
transition: all 0.3s;
|
|
411
|
-
color: var(--secondary);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
.tab:hover {
|
|
415
|
-
color: var(--primary);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
.tab.active {
|
|
419
|
-
color: var(--primary);
|
|
420
|
-
border-bottom-color: var(--primary);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
.tab-content {
|
|
424
|
-
display: none;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
.tab-content.active {
|
|
428
|
-
display: block;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/* Loading Spinner */
|
|
432
|
-
.spinner {
|
|
433
|
-
width: 40px;
|
|
434
|
-
height: 40px;
|
|
435
|
-
border: 4px solid var(--border);
|
|
436
|
-
border-top: 4px solid var(--primary);
|
|
437
|
-
border-radius: 50%;
|
|
438
|
-
animation: spin 1s linear infinite;
|
|
439
|
-
margin: 20px auto;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
@keyframes spin {
|
|
443
|
-
0% { transform: rotate(0deg); }
|
|
444
|
-
100% { transform: rotate(360deg); }
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/* Notifications */
|
|
448
|
-
.notification {
|
|
449
|
-
position: fixed;
|
|
450
|
-
top: 20px;
|
|
451
|
-
right: 20px;
|
|
452
|
-
padding: 15px 20px;
|
|
453
|
-
border-radius: 8px;
|
|
454
|
-
color: white;
|
|
455
|
-
font-weight: 500;
|
|
456
|
-
animation: slideIn 0.3s forwards;
|
|
457
|
-
z-index: 2000;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
@keyframes slideOut {
|
|
461
|
-
to { transform: translateX(110%); opacity: 0; }
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
@keyframes slideIn {
|
|
465
|
-
from { transform: translateX(100%); }
|
|
466
|
-
to { transform: translateX(0); }
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
.notification.success {
|
|
470
|
-
background: var(--success);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
.notification.error {
|
|
474
|
-
background: var(--danger);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
.notification.warning {
|
|
478
|
-
background: var(--warning);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/* File Upload */
|
|
482
|
-
.file-upload {
|
|
483
|
-
border: 2px dashed var(--border);
|
|
484
|
-
padding: 30px;
|
|
485
|
-
text-align: center;
|
|
486
|
-
border-radius: 8px;
|
|
487
|
-
cursor: pointer;
|
|
488
|
-
transition: all 0.3s;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
.file-upload:hover {
|
|
492
|
-
border-color: var(--primary);
|
|
493
|
-
background: var(--light);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
.file-upload.dragover {
|
|
497
|
-
border-color: var(--primary);
|
|
498
|
-
background: rgba(37, 99, 235, 0.1);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/* Code Editor / JSON Viewer */
|
|
502
|
-
.code-editor, .json-viewer {
|
|
503
|
-
font-family: 'Monaco', 'Courier New', monospace;
|
|
504
|
-
font-size: 14px;
|
|
505
|
-
line-height: 1.5;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
.json-viewer {
|
|
509
|
-
background: #fdfdff;
|
|
510
|
-
border: 1px solid var(--border);
|
|
511
|
-
color: #333;
|
|
512
|
-
padding: 15px;
|
|
513
|
-
border-radius: 8px;
|
|
514
|
-
overflow: auto;
|
|
515
|
-
max-height: 400px;
|
|
516
|
-
white-space: pre;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
.json-key {
|
|
520
|
-
color: #a31515;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
.json-string {
|
|
524
|
-
color: #0451a5;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
.json-number {
|
|
528
|
-
color: #098658;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
.json-boolean {
|
|
532
|
-
color: #0000ff;
|
|
533
|
-
}
|
|
534
|
-
.json-null {
|
|
535
|
-
color: #ff5555;
|
|
536
|
-
}
|
|
537
|
-
</style>
|
|
538
|
-
</head>
|
|
539
|
-
<body>
|
|
540
|
-
<div class="container">
|
|
541
|
-
<!-- Sidebar -->
|
|
542
|
-
<div class="sidebar">
|
|
543
|
-
<div class="logo">
|
|
544
|
-
<h1>🦎 LacertaDB</h1>
|
|
545
|
-
</div>
|
|
546
|
-
<nav>
|
|
547
|
-
<div class="nav-item active" data-panel="dashboard">
|
|
548
|
-
<span class="nav-icon">📊</span>
|
|
549
|
-
<span>Dashboard</span>
|
|
550
|
-
</div>
|
|
551
|
-
<div class="nav-item" data-panel="documents">
|
|
552
|
-
<span class="nav-icon">📄</span>
|
|
553
|
-
<span>Documents</span>
|
|
554
|
-
</div>
|
|
555
|
-
<div class="nav-item" data-panel="query">
|
|
556
|
-
<span class="nav-icon">🔍</span>
|
|
557
|
-
<span>Query Builder</span>
|
|
558
|
-
</div>
|
|
559
|
-
<div class="nav-item" data-panel="collections">
|
|
560
|
-
<span class="nav-icon">📁</span>
|
|
561
|
-
<span>Collections</span>
|
|
562
|
-
</div>
|
|
563
|
-
<div class="nav-item" data-panel="sync">
|
|
564
|
-
<span class="nav-icon">☁️</span>
|
|
565
|
-
<span>Sync & Backup</span>
|
|
566
|
-
</div>
|
|
567
|
-
<div class="nav-item" data-panel="performance">
|
|
568
|
-
<span class="nav-icon">⚡</span>
|
|
569
|
-
<span>Performance</span>
|
|
570
|
-
</div>
|
|
571
|
-
<div class="nav-item" data-panel="settings">
|
|
572
|
-
<span class="nav-icon">⚙️</span>
|
|
573
|
-
<span>Settings</span>
|
|
574
|
-
</div>
|
|
575
|
-
<div class="nav-item" data-panel="developer">
|
|
576
|
-
<span class="nav-icon">🔧</span>
|
|
577
|
-
<span>Developer</span>
|
|
578
|
-
</div>
|
|
579
|
-
</nav>
|
|
580
|
-
</div>
|
|
581
|
-
|
|
582
|
-
<!-- Main Content -->
|
|
583
|
-
<div class="main-content">
|
|
584
|
-
<!-- Dashboard Panel -->
|
|
585
|
-
<div class="panel active" id="dashboard">
|
|
586
|
-
<div class="panel-header">
|
|
587
|
-
<h2 class="panel-title">Dashboard</h2>
|
|
588
|
-
<p class="panel-subtitle">Database overview and statistics</p>
|
|
589
|
-
</div>
|
|
590
|
-
|
|
591
|
-
<div class="stats-grid">
|
|
592
|
-
<div class="stat-card">
|
|
593
|
-
<div class="stat-label">Total Databases</div>
|
|
594
|
-
<div class="stat-value" id="stat-databases">0</div>
|
|
595
|
-
</div>
|
|
596
|
-
<div class="stat-card">
|
|
597
|
-
<div class="stat-label">Total Collections</div>
|
|
598
|
-
<div class="stat-value" id="stat-collections">0</div>
|
|
599
|
-
</div>
|
|
600
|
-
<div class="stat-card">
|
|
601
|
-
<div class="stat-label">Total Documents</div>
|
|
602
|
-
<div class="stat-value" id="stat-documents">0</div>
|
|
603
|
-
</div>
|
|
604
|
-
<div class="stat-card">
|
|
605
|
-
<div class="stat-label">Storage Used</div>
|
|
606
|
-
<div class="stat-value" id="stat-storage">0 KB</div>
|
|
607
|
-
</div>
|
|
608
|
-
</div>
|
|
609
|
-
|
|
610
|
-
<div class="card">
|
|
611
|
-
<h3 class="card-title">Quick Actions</h3>
|
|
612
|
-
<div class="btn-group">
|
|
613
|
-
<button class="btn btn-primary" onclick="createNewDatabase()">Create Database</button>
|
|
614
|
-
<button class="btn btn-secondary" onclick="openCreateCollectionModal()">Create Collection</button>
|
|
615
|
-
<button class="btn btn-success" onclick="createBackup()">Backup All</button>
|
|
616
|
-
</div>
|
|
617
|
-
</div>
|
|
618
|
-
</div>
|
|
619
|
-
|
|
620
|
-
<!-- Documents Panel -->
|
|
621
|
-
<div class="panel" id="documents">
|
|
622
|
-
<div class="panel-header">
|
|
623
|
-
<h2 class="panel-title">Documents</h2>
|
|
624
|
-
<p class="panel-subtitle">Manage your database documents</p>
|
|
625
|
-
</div>
|
|
626
|
-
|
|
627
|
-
<div class="card">
|
|
628
|
-
<h3 class="card-title" id="doc-form-title">Add New Document</h3>
|
|
629
|
-
<input type="hidden" id="doc-edit-id">
|
|
630
|
-
<div class="form-group">
|
|
631
|
-
<label>Database</label>
|
|
632
|
-
<select id="doc-database">
|
|
633
|
-
<option>Select Database</option>
|
|
634
|
-
</select>
|
|
635
|
-
</div>
|
|
636
|
-
<div class="form-group">
|
|
637
|
-
<label>Collection</label>
|
|
638
|
-
<select id="doc-collection">
|
|
639
|
-
<option>Select Collection</option>
|
|
640
|
-
</select>
|
|
641
|
-
</div>
|
|
642
|
-
<div class="form-group">
|
|
643
|
-
<label>Document ID (optional for new docs)</label>
|
|
644
|
-
<input type="text" id="doc-id" placeholder="Auto-generated if empty">
|
|
645
|
-
</div>
|
|
646
|
-
<div class="form-group">
|
|
647
|
-
<label>Document Data (JSON)</label>
|
|
648
|
-
<textarea id="doc-data" class="code-editor">{
|
|
649
|
-
"name": "Example Document",
|
|
650
|
-
"type": "test",
|
|
651
|
-
"value": 42
|
|
652
|
-
}</textarea>
|
|
653
|
-
</div>
|
|
654
|
-
<div class="form-group">
|
|
655
|
-
<label>Attachments</label>
|
|
656
|
-
<input type="file" id="doc-attachments" multiple>
|
|
657
|
-
</div>
|
|
658
|
-
<div class="form-group">
|
|
659
|
-
<label>
|
|
660
|
-
<input type="checkbox" id="doc-encrypted"> Encrypt Document
|
|
661
|
-
</label>
|
|
662
|
-
<input type="password" id="doc-password" placeholder="Encryption password" style="margin-top: 10px; display: none;">
|
|
663
|
-
</div>
|
|
664
|
-
<div class="form-group">
|
|
665
|
-
<label>
|
|
666
|
-
<input type="checkbox" id="doc-compressed" checked> Compress Document
|
|
667
|
-
</label>
|
|
668
|
-
</div>
|
|
669
|
-
<div class="form-group">
|
|
670
|
-
<label>
|
|
671
|
-
<input type="checkbox" id="doc-permanent"> Permanent Document
|
|
672
|
-
</label>
|
|
673
|
-
</div>
|
|
674
|
-
<div class="btn-group">
|
|
675
|
-
<button class="btn btn-primary" id="doc-submit-btn" onclick="addOrUpdateDocument()">Add Document</button>
|
|
676
|
-
<button class="btn btn-secondary" id="doc-cancel-btn" style="display: none;" onclick="cancelEditDocument()">Cancel Edit</button>
|
|
677
|
-
</div>
|
|
678
|
-
</div>
|
|
679
|
-
|
|
680
|
-
<div class="card">
|
|
681
|
-
<h3 class="card-title">Document List</h3>
|
|
682
|
-
<div class="table-container">
|
|
683
|
-
<table id="documents-table">
|
|
684
|
-
<thead>
|
|
685
|
-
<tr>
|
|
686
|
-
<th>ID</th>
|
|
687
|
-
<th>Created</th>
|
|
688
|
-
<th>Modified</th>
|
|
689
|
-
<th>Permanent</th>
|
|
690
|
-
<th>Actions</th>
|
|
691
|
-
</tr>
|
|
692
|
-
</thead>
|
|
693
|
-
<tbody id="documents-table-body">
|
|
694
|
-
<tr><td colspan="5" style="text-align: center;">Select a database and collection to view documents.</td></tr>
|
|
695
|
-
</tbody>
|
|
696
|
-
</table>
|
|
697
|
-
</div>
|
|
698
|
-
</div>
|
|
699
|
-
</div>
|
|
700
|
-
|
|
701
|
-
<!-- Query Builder Panel -->
|
|
702
|
-
<div class="panel" id="query">
|
|
703
|
-
<div class="panel-header">
|
|
704
|
-
<h2 class="panel-title">Query Builder</h2>
|
|
705
|
-
<p class="panel-subtitle">Build and execute complex queries</p>
|
|
706
|
-
</div>
|
|
707
|
-
|
|
708
|
-
<div class="card">
|
|
709
|
-
<div class="query-builder">
|
|
710
|
-
<div class="query-section">
|
|
711
|
-
<h3 class="card-title">Query</h3>
|
|
712
|
-
<div class="form-group">
|
|
713
|
-
<label>Database</label>
|
|
714
|
-
<select id="query-database">
|
|
715
|
-
<option>Select Database</option>
|
|
716
|
-
</select>
|
|
717
|
-
</div>
|
|
718
|
-
<div class="form-group">
|
|
719
|
-
<label>Collection</label>
|
|
720
|
-
<select id="query-collection">
|
|
721
|
-
<option>Select Collection</option>
|
|
722
|
-
</select>
|
|
723
|
-
</div>
|
|
724
|
-
<div class="form-group">
|
|
725
|
-
<label>Filter (MongoDB-style JSON)</label>
|
|
726
|
-
<textarea id="query-filter" class="code-editor">{
|
|
727
|
-
"type": "test"
|
|
728
|
-
}</textarea>
|
|
729
|
-
</div>
|
|
730
|
-
<div class="form-group">
|
|
731
|
-
<label>Projection (optional)</label>
|
|
732
|
-
<textarea id="query-projection" class="code-editor">{}</textarea>
|
|
733
|
-
</div>
|
|
734
|
-
<div class="form-group">
|
|
735
|
-
<label>Sort (optional)</label>
|
|
736
|
-
<textarea id="query-sort" class="code-editor">{}</textarea>
|
|
737
|
-
</div>
|
|
738
|
-
<div class="form-group">
|
|
739
|
-
<label>Limit</label>
|
|
740
|
-
<input type="number" id="query-limit" value="100">
|
|
741
|
-
</div>
|
|
742
|
-
<button class="btn btn-primary" onclick="executeQuery()">Execute Query</button>
|
|
743
|
-
</div>
|
|
744
|
-
|
|
745
|
-
<div class="query-section">
|
|
746
|
-
<h3 class="card-title">Aggregation Pipeline</h3>
|
|
747
|
-
<div class="form-group">
|
|
748
|
-
<label>Pipeline Stages (JSON Array)</label>
|
|
749
|
-
<textarea id="agg-pipeline" class="code-editor" style="height: 300px;">[
|
|
750
|
-
{
|
|
751
|
-
"$match": {
|
|
752
|
-
"type": "test"
|
|
753
|
-
}
|
|
754
|
-
},
|
|
755
|
-
{
|
|
756
|
-
"$group": {
|
|
757
|
-
"_id": "$category",
|
|
758
|
-
"count": { "$count": {} }
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
]</textarea>
|
|
762
|
-
</div>
|
|
763
|
-
<button class="btn btn-primary" onclick="executeAggregation()">Execute Pipeline</button>
|
|
764
|
-
</div>
|
|
765
|
-
</div>
|
|
766
|
-
|
|
767
|
-
<div class="query-results">
|
|
768
|
-
<h3 class="card-title">Results</h3>
|
|
769
|
-
<div id="query-results-content" class="json-viewer"></div>
|
|
770
|
-
</div>
|
|
771
|
-
</div>
|
|
772
|
-
</div>
|
|
773
|
-
|
|
774
|
-
<!-- Collections Panel -->
|
|
775
|
-
<div class="panel" id="collections">
|
|
776
|
-
<div class="panel-header">
|
|
777
|
-
<h2 class="panel-title">Collections</h2>
|
|
778
|
-
<p class="panel-subtitle">Manage database collections</p>
|
|
779
|
-
</div>
|
|
780
|
-
|
|
781
|
-
<div class="card">
|
|
782
|
-
<h3 class="card-title">Create Collection</h3>
|
|
783
|
-
<div class="form-group">
|
|
784
|
-
<label>Database</label>
|
|
785
|
-
<select id="coll-database">
|
|
786
|
-
<option>Select Database</option>
|
|
787
|
-
</select>
|
|
788
|
-
</div>
|
|
789
|
-
<div class="form-group">
|
|
790
|
-
<label>Collection Name</label>
|
|
791
|
-
<input type="text" id="coll-name" placeholder="my-collection">
|
|
792
|
-
</div>
|
|
793
|
-
<button class="btn btn-primary" onclick="createCollection()">Create Collection</button>
|
|
794
|
-
</div>
|
|
795
|
-
|
|
796
|
-
<div class="card">
|
|
797
|
-
<h3 class="card-title">Collections List</h3>
|
|
798
|
-
<div id="collections-list" class="table-container"></div>
|
|
799
|
-
</div>
|
|
800
|
-
</div>
|
|
801
|
-
|
|
802
|
-
<!-- Sync & Backup Panel -->
|
|
803
|
-
<div class="panel" id="sync">
|
|
804
|
-
<div class="panel-header">
|
|
805
|
-
<h2 class="panel-title">Sync & Backup</h2>
|
|
806
|
-
<p class="panel-subtitle">Backup and restore your databases</p>
|
|
807
|
-
</div>
|
|
808
|
-
|
|
809
|
-
<div class="card">
|
|
810
|
-
<h3 class="card-title">Create Backup</h3>
|
|
811
|
-
<div class="form-group">
|
|
812
|
-
<label>Backup Type</label>
|
|
813
|
-
<select id="backup-type">
|
|
814
|
-
<option value="all">All Databases</option>
|
|
815
|
-
<option value="single">Single Database</option>
|
|
816
|
-
</select>
|
|
817
|
-
</div>
|
|
818
|
-
<div class="form-group" id="backup-db-select" style="display: none;">
|
|
819
|
-
<label>Database</label>
|
|
820
|
-
<select id="backup-database">
|
|
821
|
-
<option>Select Database</option>
|
|
822
|
-
</select>
|
|
823
|
-
</div>
|
|
824
|
-
<div class="form-group">
|
|
825
|
-
<label>
|
|
826
|
-
<input type="checkbox" id="backup-encrypted"> Encrypt Backup
|
|
827
|
-
</label>
|
|
828
|
-
<input type="password" id="backup-password" placeholder="Encryption password" style="margin-top: 10px; display: none;">
|
|
829
|
-
</div>
|
|
830
|
-
<button class="btn btn-success" onclick="createBackup()">Create Backup</button>
|
|
831
|
-
</div>
|
|
832
|
-
|
|
833
|
-
<div class="card">
|
|
834
|
-
<h3 class="card-title">Restore Backup</h3>
|
|
835
|
-
<div class="file-upload" id="restore-upload">
|
|
836
|
-
<p>Drop backup file here or click to browse</p>
|
|
837
|
-
<input type="file" id="restore-file" style="display: none;" accept=".json,.backup,.txt">
|
|
838
|
-
</div>
|
|
839
|
-
<div class="form-group" style="margin-top: 20px;">
|
|
840
|
-
<label>
|
|
841
|
-
<input type="checkbox" id="restore-encrypted"> Backup is Encrypted
|
|
842
|
-
</label>
|
|
843
|
-
<input type="password" id="restore-password" placeholder="Decryption password" style="margin-top: 10px; display: none;">
|
|
844
|
-
</div>
|
|
845
|
-
<button class="btn btn-warning" onclick="restoreBackup()">Restore Backup</button>
|
|
846
|
-
</div>
|
|
847
|
-
</div>
|
|
848
|
-
|
|
849
|
-
<!-- Performance Panel -->
|
|
850
|
-
<div class="panel" id="performance">
|
|
851
|
-
<div class="panel-header">
|
|
852
|
-
<h2 class="panel-title">Performance</h2>
|
|
853
|
-
<p class="panel-subtitle">Monitor database performance</p>
|
|
854
|
-
</div>
|
|
855
|
-
|
|
856
|
-
<div class="stats-grid">
|
|
857
|
-
<div class="stat-card">
|
|
858
|
-
<div class="stat-label">Operations/Sec</div>
|
|
859
|
-
<div class="stat-value" id="perf-ops">0</div>
|
|
860
|
-
</div>
|
|
861
|
-
<div class="stat-card">
|
|
862
|
-
<div class="stat-label">Avg Latency</div>
|
|
863
|
-
<div class="stat-value" id="perf-latency">0ms</div>
|
|
864
|
-
</div>
|
|
865
|
-
<div class="stat-card">
|
|
866
|
-
<div class="stat-label">Cache Hit Rate</div>
|
|
867
|
-
<div class="stat-value" id="perf-cache">0%</div>
|
|
868
|
-
</div>
|
|
869
|
-
<div class="stat-card">
|
|
870
|
-
<div class="stat-label">Memory Usage</div>
|
|
871
|
-
<div class="stat-value" id="perf-memory">0MB</div>
|
|
872
|
-
</div>
|
|
873
|
-
</div>
|
|
874
|
-
|
|
875
|
-
<div class="card">
|
|
876
|
-
<h3 class="card-title">Performance Monitoring</h3>
|
|
877
|
-
<div class="btn-group">
|
|
878
|
-
<button class="btn btn-primary" id="start-monitoring-btn" onclick="startMonitoring()">Start Monitoring</button>
|
|
879
|
-
<button class="btn btn-secondary" id="stop-monitoring-btn" onclick="stopMonitoring()">Stop Monitoring</button>
|
|
880
|
-
</div>
|
|
881
|
-
</div>
|
|
882
|
-
|
|
883
|
-
<div class="card">
|
|
884
|
-
<h3 class="card-title">Optimization Tips</h3>
|
|
885
|
-
<div id="optimization-tips"></div>
|
|
886
|
-
</div>
|
|
887
|
-
</div>
|
|
888
|
-
|
|
889
|
-
<!-- Settings Panel -->
|
|
890
|
-
<div class="panel" id="settings">
|
|
891
|
-
<div class="panel-header">
|
|
892
|
-
<h2 class="panel-title">Settings</h2>
|
|
893
|
-
<p class="panel-subtitle">Configure database settings</p>
|
|
894
|
-
</div>
|
|
895
|
-
|
|
896
|
-
<div class="form-group">
|
|
897
|
-
<label>Select Database to Configure</label>
|
|
898
|
-
<select id="settings-database">
|
|
899
|
-
<option>Select Database</option>
|
|
900
|
-
</select>
|
|
901
|
-
</div>
|
|
902
|
-
|
|
903
|
-
<div class="card">
|
|
904
|
-
<h3 class="card-title">Storage Settings</h3>
|
|
905
|
-
<div class="form-group">
|
|
906
|
-
<label>Size Limit (KB)</label>
|
|
907
|
-
<input type="number" id="size-limit" placeholder="Infinity">
|
|
908
|
-
</div>
|
|
909
|
-
<div class="form-group">
|
|
910
|
-
<label>Buffer Limit (KB)</label>
|
|
911
|
-
<input type="number" id="buffer-limit" placeholder="80% of size limit">
|
|
912
|
-
</div>
|
|
913
|
-
<div class="form-group">
|
|
914
|
-
<label>Cleanup Interval (ms)</label>
|
|
915
|
-
<input type="number" id="cleanup-interval" value="10000">
|
|
916
|
-
</div>
|
|
917
|
-
<button class="btn btn-primary" onclick="updateDbSettings()">Update Settings</button>
|
|
918
|
-
</div>
|
|
919
|
-
|
|
920
|
-
<div class="card">
|
|
921
|
-
<h3 class="card-title">Danger Zone</h3>
|
|
922
|
-
<button class="btn btn-danger" onclick="dropDatabase()">Drop Selected Database</button>
|
|
923
|
-
<button class="btn btn-danger" onclick="clearAllData()">Clear All Data From All Databases</button>
|
|
924
|
-
</div>
|
|
925
|
-
</div>
|
|
926
|
-
|
|
927
|
-
<!-- Developer Panel -->
|
|
928
|
-
<div class="panel" id="developer">
|
|
929
|
-
<div class="panel-header">
|
|
930
|
-
<h2 class="panel-title">Developer Console</h2>
|
|
931
|
-
<p class="panel-subtitle">Direct database interaction</p>
|
|
932
|
-
</div>
|
|
933
|
-
|
|
934
|
-
<div class="card">
|
|
935
|
-
<h3 class="card-title">JavaScript Console</h3>
|
|
936
|
-
<div class="form-group">
|
|
937
|
-
<label>Execute JavaScript Code</label>
|
|
938
|
-
<textarea id="dev-code" class="code-editor" style="height: 200px;">// Access the database instance via the `db` variable
|
|
939
|
-
// Example: await db.getDatabase('my-db-name');
|
|
940
|
-
const myDb = await db.getDatabase('test');
|
|
941
|
-
const coll = await myDb.getCollection('users').catch(() => myDb.createCollection('users'));
|
|
942
|
-
await coll.add({ name: 'John Doe', age: 30, timestamp: new Date() });
|
|
943
|
-
const docs = await coll.getAll();
|
|
944
|
-
console.log('Documents in "users":', docs);</textarea>
|
|
945
|
-
</div>
|
|
946
|
-
<button class="btn btn-primary" onclick="executeCode()">Execute</button>
|
|
947
|
-
</div>
|
|
948
|
-
|
|
949
|
-
<div class="console" id="dev-console">
|
|
950
|
-
<div class="console-line">
|
|
951
|
-
<span class="console-prompt">></span> Ready for commands...
|
|
952
|
-
</div>
|
|
953
|
-
</div>
|
|
954
|
-
</div>
|
|
955
|
-
</div>
|
|
956
|
-
</div>
|
|
957
|
-
|
|
958
|
-
<!-- Modal -->
|
|
959
|
-
<div class="modal" id="modal">
|
|
960
|
-
<div class="modal-content">
|
|
961
|
-
<div class="modal-header">
|
|
962
|
-
<h3 class="modal-title" id="modal-title">Modal Title</h3>
|
|
963
|
-
</div>
|
|
964
|
-
<div id="modal-body"></div>
|
|
965
|
-
<div class="btn-group" id="modal-buttons">
|
|
966
|
-
<button class="btn btn-primary" onclick="closeModal()">Close</button>
|
|
967
|
-
</div>
|
|
968
|
-
</div>
|
|
969
|
-
</div>
|
|
970
|
-
|
|
971
|
-
<script src="browser.min.js"></script>
|
|
972
|
-
<script>
|
|
973
|
-
// --- Globals ---
|
|
974
|
-
let db = null;
|
|
975
|
-
let perfInterval = null;
|
|
976
|
-
|
|
977
|
-
// --- Initialization ---
|
|
978
|
-
window.addEventListener('DOMContentLoaded', async () => {
|
|
979
|
-
try {
|
|
980
|
-
let LDB;
|
|
981
|
-
if (window.LacertaDB) {
|
|
982
|
-
LDB = window.LacertaDB;
|
|
983
|
-
} else if (window.lacerta && window.lacerta.LacertaDB) {
|
|
984
|
-
// Fallback for older bundle structure
|
|
985
|
-
LDB = window.lacerta.LacertaDB;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
if (LDB) {
|
|
989
|
-
db = new LDB();
|
|
990
|
-
console.log('LacertaDB v4 Initialized Successfully');
|
|
991
|
-
await updateDashboard();
|
|
992
|
-
} else {
|
|
993
|
-
showNotification('Failed to load LacertaDB module. Make sure browser.min.js is loaded.', 'error');
|
|
994
|
-
}
|
|
995
|
-
} catch (error) {
|
|
996
|
-
console.error('Failed to initialize LacertaDB:', error);
|
|
997
|
-
showNotification(`Initialization failed: ${error.message}`, 'error');
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
// --- Navigation & UI Updates ---
|
|
1002
|
-
document.querySelectorAll('.nav-item').forEach(item => {
|
|
1003
|
-
item.addEventListener('click', () => {
|
|
1004
|
-
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
|
|
1005
|
-
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
|
1006
|
-
|
|
1007
|
-
item.classList.add('active');
|
|
1008
|
-
const panelId = item.dataset.panel;
|
|
1009
|
-
document.getElementById(panelId).classList.add('active');
|
|
1010
|
-
|
|
1011
|
-
// Update panel-specific data on view
|
|
1012
|
-
switch (panelId) {
|
|
1013
|
-
case 'dashboard': updateDashboard(); break;
|
|
1014
|
-
case 'collections': updateCollectionsList(); break;
|
|
1015
|
-
case 'documents': updateDatabaseSelects(); break;
|
|
1016
|
-
case 'query': updateDatabaseSelects(); break;
|
|
1017
|
-
case 'sync': updateDatabaseSelects(); break;
|
|
1018
|
-
case 'settings': updateDatabaseSelects(); break;
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
async function updateDashboard() {
|
|
1024
|
-
if (!db) return;
|
|
1025
|
-
try {
|
|
1026
|
-
const databases = db.listDatabases();
|
|
1027
|
-
document.getElementById('stat-databases').textContent = databases.length;
|
|
1028
|
-
|
|
1029
|
-
let totalCollections = 0, totalDocuments = 0, totalSize = 0;
|
|
1030
|
-
|
|
1031
|
-
for (const dbName of databases) {
|
|
1032
|
-
const database = await db.getDatabase(dbName);
|
|
1033
|
-
const stats = database.getStats();
|
|
1034
|
-
totalCollections += stats.collections.length;
|
|
1035
|
-
totalDocuments += stats.totalDocuments;
|
|
1036
|
-
totalSize += stats.totalSizeKB;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
document.getElementById('stat-collections').textContent = totalCollections;
|
|
1040
|
-
document.getElementById('stat-documents').textContent = totalDocuments;
|
|
1041
|
-
document.getElementById('stat-storage').textContent = formatSize(totalSize * 1024);
|
|
1042
|
-
|
|
1043
|
-
await updateDatabaseSelects();
|
|
1044
|
-
} catch (error) {
|
|
1045
|
-
console.error('Dashboard update error:', error);
|
|
1046
|
-
showNotification('Could not update dashboard stats.', 'error');
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
async function updateDatabaseSelects() {
|
|
1051
|
-
if (!db) return;
|
|
1052
|
-
const databases = db.listDatabases();
|
|
1053
|
-
const selects = [
|
|
1054
|
-
'doc-database', 'query-database', 'coll-database',
|
|
1055
|
-
'backup-database', 'settings-database'
|
|
1056
|
-
];
|
|
1057
|
-
selects.forEach(id => {
|
|
1058
|
-
const select = document.getElementById(id);
|
|
1059
|
-
if (select) {
|
|
1060
|
-
const currentVal = select.value;
|
|
1061
|
-
select.innerHTML = '<option value="">Select Database</option>';
|
|
1062
|
-
databases.forEach(dbName => {
|
|
1063
|
-
const option = new Option(dbName, dbName);
|
|
1064
|
-
select.appendChild(option);
|
|
1065
|
-
});
|
|
1066
|
-
if (databases.includes(currentVal)) {
|
|
1067
|
-
select.value = currentVal;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
});
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
async function updateCollectionSelect(dbName, selectId) {
|
|
1074
|
-
const select = document.getElementById(selectId);
|
|
1075
|
-
select.innerHTML = '<option value="">Select Collection</option>';
|
|
1076
|
-
if (dbName) {
|
|
1077
|
-
const database = await db.getDatabase(dbName);
|
|
1078
|
-
const collections = database.listCollections();
|
|
1079
|
-
collections.forEach(collName => {
|
|
1080
|
-
select.add(new Option(collName, collName));
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// --- Database & Collection Management ---
|
|
1086
|
-
async function createNewDatabase() {
|
|
1087
|
-
const name = prompt('Enter new database name:');
|
|
1088
|
-
if (name && name.trim()) {
|
|
1089
|
-
try {
|
|
1090
|
-
await db.getDatabase(name.trim());
|
|
1091
|
-
showNotification(`Database "${name}" created`, 'success');
|
|
1092
|
-
await updateDashboard();
|
|
1093
|
-
} catch (error) {
|
|
1094
|
-
showNotification(`Error: ${error.message}`, 'error');
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
async function createCollection() {
|
|
1100
|
-
try {
|
|
1101
|
-
const dbName = document.getElementById('coll-database').value;
|
|
1102
|
-
const collName = document.getElementById('coll-name').value;
|
|
1103
|
-
if (!dbName || !collName) {
|
|
1104
|
-
showNotification('Database and collection name are required.', 'warning');
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
const database = await db.getDatabase(dbName);
|
|
1108
|
-
await database.createCollection(collName);
|
|
1109
|
-
showNotification(`Collection "${collName}" created`, 'success');
|
|
1110
|
-
await updateCollectionsList();
|
|
1111
|
-
await updateDashboard();
|
|
1112
|
-
document.getElementById('coll-name').value = '';
|
|
1113
|
-
} catch (error) {
|
|
1114
|
-
showNotification(`Error: ${error.message}`, 'error');
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
async function updateCollectionsList() {
|
|
1119
|
-
if (!db) return;
|
|
1120
|
-
const container = document.getElementById('collections-list');
|
|
1121
|
-
container.innerHTML = '<table><thead><tr><th>Database</th><th>Collection</th><th>Documents</th><th>Size</th><th>Actions</th></tr></thead><tbody id="collections-table-body"></tbody></table>';
|
|
1122
|
-
const tableBody = document.getElementById('collections-table-body');
|
|
1123
|
-
tableBody.innerHTML = '';
|
|
1124
|
-
|
|
1125
|
-
const databases = db.listDatabases();
|
|
1126
|
-
if (databases.length === 0) {
|
|
1127
|
-
tableBody.innerHTML = '<tr><td colspan="5" style="text-align: center;">No databases found.</td></tr>';
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
for (const dbName of databases) {
|
|
1132
|
-
const database = await db.getDatabase(dbName);
|
|
1133
|
-
const stats = database.getStats();
|
|
1134
|
-
if (stats.collections.length === 0) {
|
|
1135
|
-
const row = tableBody.insertRow();
|
|
1136
|
-
row.innerHTML = `<td>${dbName}</td><td colspan="4" style="text-align: center;">No collections in this database.</td>`;
|
|
1137
|
-
} else {
|
|
1138
|
-
stats.collections.forEach(coll => {
|
|
1139
|
-
const row = tableBody.insertRow();
|
|
1140
|
-
row.innerHTML = `
|
|
1141
|
-
<td>${dbName}</td>
|
|
1142
|
-
<td>${coll.name}</td>
|
|
1143
|
-
<td>${coll.documents}</td>
|
|
1144
|
-
<td>${formatSize(coll.sizeKB * 1024)}</td>
|
|
1145
|
-
<td class="actions">
|
|
1146
|
-
<button class="btn btn-danger btn-sm" onclick="dropCollection('${dbName}', '${coll.name}')">Drop</button>
|
|
1147
|
-
</td>
|
|
1148
|
-
`;
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
async function dropCollection(dbName, collName) {
|
|
1155
|
-
showModal(
|
|
1156
|
-
'Confirm Deletion',
|
|
1157
|
-
`<p>Are you sure you want to drop collection "<strong>${collName}</strong>" from "<strong>${dbName}</strong>"? This action is irreversible.</p>`,
|
|
1158
|
-
[
|
|
1159
|
-
{ text: 'Cancel', class: 'btn-secondary' },
|
|
1160
|
-
{ text: 'Drop Collection', class: 'btn-danger', onClick: async () => {
|
|
1161
|
-
try {
|
|
1162
|
-
const database = await db.getDatabase(dbName);
|
|
1163
|
-
await database.dropCollection(collName);
|
|
1164
|
-
showNotification(`Collection "${collName}" dropped`, 'success');
|
|
1165
|
-
await updateCollectionsList();
|
|
1166
|
-
await updateDashboard();
|
|
1167
|
-
} catch (error) { showNotification(`Error: ${error.message}`, 'error'); }
|
|
1168
|
-
}}
|
|
1169
|
-
]
|
|
1170
|
-
);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
// --- Document Management ---
|
|
1174
|
-
async function addOrUpdateDocument() {
|
|
1175
|
-
try {
|
|
1176
|
-
const dbName = document.getElementById('doc-database').value;
|
|
1177
|
-
const collName = document.getElementById('doc-collection').value;
|
|
1178
|
-
const editId = document.getElementById('doc-edit-id').value;
|
|
1179
|
-
const docId = document.getElementById('doc-id').value || undefined;
|
|
1180
|
-
let docData;
|
|
1181
|
-
try {
|
|
1182
|
-
docData = JSON.parse(document.getElementById('doc-data').value);
|
|
1183
|
-
} catch(e) {
|
|
1184
|
-
showNotification('Invalid JSON in document data.', 'error');
|
|
1185
|
-
return;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
const files = document.getElementById('doc-attachments').files;
|
|
1189
|
-
const options = {
|
|
1190
|
-
id: docId,
|
|
1191
|
-
encrypted: document.getElementById('doc-encrypted').checked,
|
|
1192
|
-
password: document.getElementById('doc-password').value || undefined,
|
|
1193
|
-
compressed: document.getElementById('doc-compressed').checked,
|
|
1194
|
-
permanent: document.getElementById('doc-permanent').checked,
|
|
1195
|
-
attachments: files.length > 0 ? Array.from(files) : undefined
|
|
1196
|
-
};
|
|
1197
|
-
|
|
1198
|
-
const database = await db.getDatabase(dbName);
|
|
1199
|
-
const collection = await database.getCollection(collName);
|
|
1200
|
-
|
|
1201
|
-
if (editId) {
|
|
1202
|
-
await collection.update(editId, docData, options);
|
|
1203
|
-
showNotification(`Document ${editId} updated successfully.`, 'success');
|
|
1204
|
-
} else {
|
|
1205
|
-
const newId = await collection.add(docData, options);
|
|
1206
|
-
showNotification(`Document added with ID: ${newId}`, 'success');
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
cancelEditDocument(); // Reset form
|
|
1210
|
-
await listDocuments(dbName, collName);
|
|
1211
|
-
await updateDashboard();
|
|
1212
|
-
|
|
1213
|
-
} catch (error) {
|
|
1214
|
-
showNotification(`Error: ${error.message}`, 'error');
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
async function listDocuments(dbName, collName) {
|
|
1219
|
-
const tableBody = document.getElementById('documents-table-body');
|
|
1220
|
-
tableBody.innerHTML = '<tr><td colspan="5" style="text-align: center;"><div class="spinner"></div></td></tr>';
|
|
1221
|
-
if (!dbName || !collName) {
|
|
1222
|
-
tableBody.innerHTML = '<tr><td colspan="5" style="text-align: center;">Select a database and collection.</td></tr>';
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
try {
|
|
1226
|
-
const database = await db.getDatabase(dbName);
|
|
1227
|
-
const collection = await database.getCollection(collName);
|
|
1228
|
-
// We'll get just the metadata to list, not the full data for performance.
|
|
1229
|
-
const docs = await collection.query({}, { projection: { _id: 1, _created: 1, _modified: 1, _permanent: 1 } });
|
|
1230
|
-
|
|
1231
|
-
if (docs.length === 0) {
|
|
1232
|
-
tableBody.innerHTML = '<tr><td colspan="5" style="text-align: center;">No documents in this collection.</td></tr>';
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
tableBody.innerHTML = '';
|
|
1237
|
-
docs.forEach(doc => {
|
|
1238
|
-
const row = tableBody.insertRow();
|
|
1239
|
-
row.innerHTML = `
|
|
1240
|
-
<td class="document-id" title="${doc._id}">${doc._id}</td>
|
|
1241
|
-
<td>${new Date(doc._created).toLocaleString()}</td>
|
|
1242
|
-
<td>${new Date(doc._modified).toLocaleString()}</td>
|
|
1243
|
-
<td>${doc._permanent ? 'Yes' : 'No'}</td>
|
|
1244
|
-
<td class="actions">
|
|
1245
|
-
<button class="btn btn-primary btn-sm" onclick="viewDocument('${dbName}', '${collName}', '${doc._id}')">View</button>
|
|
1246
|
-
<button class="btn btn-warning btn-sm" onclick="editDocument('${dbName}', '${collName}', '${doc._id}')">Edit</button>
|
|
1247
|
-
<button class="btn btn-danger btn-sm" onclick="deleteDocument('${dbName}', '${collName}', '${doc._id}')">Delete</button>
|
|
1248
|
-
</td>
|
|
1249
|
-
`;
|
|
1250
|
-
});
|
|
1251
|
-
} catch(error) {
|
|
1252
|
-
showNotification(`Error listing documents: ${error.message}`, 'error');
|
|
1253
|
-
tableBody.innerHTML = `<tr><td colspan="5" style="text-align: center; color: var(--danger);">Error loading documents.</td></tr>`;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
async function viewDocument(dbName, collName, docId) {
|
|
1258
|
-
try {
|
|
1259
|
-
const database = await db.getDatabase(dbName);
|
|
1260
|
-
const collection = await database.getCollection(collName);
|
|
1261
|
-
const doc = await collection.get(docId, { includeAttachments: true });
|
|
1262
|
-
|
|
1263
|
-
const content = document.createElement('div');
|
|
1264
|
-
content.innerHTML = `<div class="json-viewer">${syntaxHighlight(JSON.stringify(doc, null, 2))}</div>`;
|
|
1265
|
-
showModal(`Document: ${docId}`, content);
|
|
1266
|
-
} catch (error) {
|
|
1267
|
-
showNotification(`Error getting document: ${error.message}`, 'error');
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
async function editDocument(dbName, collName, docId) {
|
|
1272
|
-
try {
|
|
1273
|
-
const database = await db.getDatabase(dbName);
|
|
1274
|
-
const collection = await database.getCollection(collName);
|
|
1275
|
-
const doc = await collection.get(docId); // get raw data without metadata
|
|
1276
|
-
|
|
1277
|
-
document.getElementById('doc-form-title').textContent = `Editing Document: ${docId}`;
|
|
1278
|
-
document.getElementById('doc-submit-btn').textContent = 'Update Document';
|
|
1279
|
-
document.getElementById('doc-cancel-btn').style.display = 'inline-flex';
|
|
1280
|
-
|
|
1281
|
-
document.getElementById('doc-edit-id').value = doc._id;
|
|
1282
|
-
document.getElementById('doc-id').value = doc._id;
|
|
1283
|
-
document.getElementById('doc-id').readOnly = true;
|
|
1284
|
-
|
|
1285
|
-
// Separate metadata from user data for editing
|
|
1286
|
-
const { _id, _created, _modified, _permanent, _attachments, ...dataToEdit } = doc;
|
|
1287
|
-
document.getElementById('doc-data').value = JSON.stringify(dataToEdit, null, 2);
|
|
1288
|
-
document.getElementById('doc-permanent').checked = _permanent;
|
|
1289
|
-
// Note: cannot edit encryption/compression on existing docs this way yet. This is a simplification.
|
|
1290
|
-
|
|
1291
|
-
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1292
|
-
|
|
1293
|
-
} catch (error) {
|
|
1294
|
-
showNotification(`Error loading document for editing: ${error.message}`, 'error');
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
function cancelEditDocument() {
|
|
1299
|
-
document.getElementById('doc-form-title').textContent = 'Add New Document';
|
|
1300
|
-
document.getElementById('doc-submit-btn').textContent = 'Add Document';
|
|
1301
|
-
document.getElementById('doc-cancel-btn').style.display = 'none';
|
|
1302
|
-
document.getElementById('doc-edit-id').value = '';
|
|
1303
|
-
document.getElementById('doc-id').value = '';
|
|
1304
|
-
document.getElementById('doc-id').readOnly = false;
|
|
1305
|
-
document.getElementById('doc-data').value = '{\n "key": "value"\n}';
|
|
1306
|
-
document.getElementById('doc-attachments').value = '';
|
|
1307
|
-
document.getElementById('doc-encrypted').checked = false;
|
|
1308
|
-
document.getElementById('doc-permanent').checked = false;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
async function deleteDocument(dbName, collName, docId) {
|
|
1312
|
-
showModal(
|
|
1313
|
-
'Confirm Deletion',
|
|
1314
|
-
`<p>Are you sure you want to delete document "<strong>${docId}</strong>"? This action is irreversible.</p>`,
|
|
1315
|
-
[
|
|
1316
|
-
{ text: 'Cancel', class: 'btn-secondary' },
|
|
1317
|
-
{ text: 'Delete Document', class: 'btn-danger', onClick: async () => {
|
|
1318
|
-
try {
|
|
1319
|
-
const database = await db.getDatabase(dbName);
|
|
1320
|
-
const collection = await database.getCollection(collName);
|
|
1321
|
-
await collection.delete(docId);
|
|
1322
|
-
showNotification(`Document "${docId}" deleted`, 'success');
|
|
1323
|
-
await listDocuments(dbName, collName);
|
|
1324
|
-
await updateDashboard();
|
|
1325
|
-
} catch (error) { showNotification(`Error: ${error.message}`, 'error'); }
|
|
1326
|
-
}}
|
|
1327
|
-
]
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
// --- Query & Aggregation ---
|
|
1332
|
-
async function executeQuery() {
|
|
1333
|
-
try {
|
|
1334
|
-
const dbName = document.getElementById('query-database').value;
|
|
1335
|
-
const collName = document.getElementById('query-collection').value;
|
|
1336
|
-
const filter = JSON.parse(document.getElementById('query-filter').value);
|
|
1337
|
-
const projection = JSON.parse(document.getElementById('query-projection').value);
|
|
1338
|
-
const sort = JSON.parse(document.getElementById('query-sort').value);
|
|
1339
|
-
const limit = parseInt(document.getElementById('query-limit').value);
|
|
1340
|
-
|
|
1341
|
-
const database = await db.getDatabase(dbName);
|
|
1342
|
-
const collection = await database.getCollection(collName);
|
|
1343
|
-
|
|
1344
|
-
const results = await collection.query(filter, {
|
|
1345
|
-
projection: Object.keys(projection).length > 0 ? projection : undefined,
|
|
1346
|
-
sort: Object.keys(sort).length > 0 ? sort : undefined,
|
|
1347
|
-
limit
|
|
1348
|
-
});
|
|
1349
|
-
displayQueryResults(results);
|
|
1350
|
-
} catch (error) {
|
|
1351
|
-
showNotification(`Query error: ${error.message}`, 'error');
|
|
1352
|
-
displayQueryResults({ error: error.message });
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
async function executeAggregation() {
|
|
1357
|
-
try {
|
|
1358
|
-
const dbName = document.getElementById('query-database').value;
|
|
1359
|
-
const collName = document.getElementById('query-collection').value;
|
|
1360
|
-
const pipeline = JSON.parse(document.getElementById('agg-pipeline').value);
|
|
1361
|
-
|
|
1362
|
-
const database = await db.getDatabase(dbName);
|
|
1363
|
-
const collection = await database.getCollection(collName);
|
|
1364
|
-
|
|
1365
|
-
const results = await collection.aggregate(pipeline);
|
|
1366
|
-
displayQueryResults(results);
|
|
1367
|
-
} catch (error) {
|
|
1368
|
-
showNotification(`Aggregation error: ${error.message}`, 'error');
|
|
1369
|
-
displayQueryResults({ error: error.message });
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
function displayQueryResults(results) {
|
|
1374
|
-
const container = document.getElementById('query-results-content');
|
|
1375
|
-
container.innerHTML = syntaxHighlight(JSON.stringify(results, null, 2));
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
// --- Backup & Restore ---
|
|
1379
|
-
async function createBackup() {
|
|
1380
|
-
try {
|
|
1381
|
-
const type = document.getElementById('backup-type').value;
|
|
1382
|
-
const encrypted = document.getElementById('backup-encrypted').checked;
|
|
1383
|
-
const password = document.getElementById('backup-password').value;
|
|
1384
|
-
|
|
1385
|
-
let backupData;
|
|
1386
|
-
let fileName = `lacerta-backup-${Date.now()}`;
|
|
1387
|
-
if (type === 'all') {
|
|
1388
|
-
backupData = await db.createBackup(encrypted ? password : null);
|
|
1389
|
-
} else {
|
|
1390
|
-
const dbName = document.getElementById('backup-database').value;
|
|
1391
|
-
if (!dbName) {
|
|
1392
|
-
showNotification('Please select a database to back up.', 'warning');
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
fileName += `-${dbName}`;
|
|
1396
|
-
const database = await db.getDatabase(dbName);
|
|
1397
|
-
backupData = await database.export(encrypted ? 'encrypted' : 'json', password);
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
const blob = new Blob([backupData], { type: 'text/plain' });
|
|
1401
|
-
const url = URL.createObjectURL(blob);
|
|
1402
|
-
const a = document.createElement('a');
|
|
1403
|
-
a.href = url;
|
|
1404
|
-
a.download = `${fileName}.txt`;
|
|
1405
|
-
document.body.appendChild(a);
|
|
1406
|
-
a.click();
|
|
1407
|
-
a.remove();
|
|
1408
|
-
URL.revokeObjectURL(url);
|
|
1409
|
-
showNotification('Backup created successfully', 'success');
|
|
1410
|
-
} catch (error) {
|
|
1411
|
-
showNotification(`Backup error: ${error.message}`, 'error');
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
async function restoreBackup() {
|
|
1416
|
-
const fileInput = document.getElementById('restore-file');
|
|
1417
|
-
if (fileInput.files.length === 0) {
|
|
1418
|
-
return showNotification('Please select a backup file.', 'warning');
|
|
1419
|
-
}
|
|
1420
|
-
const file = fileInput.files[0];
|
|
1421
|
-
const reader = new FileReader();
|
|
1422
|
-
reader.onload = async (e) => {
|
|
1423
|
-
const backupData = e.target.result;
|
|
1424
|
-
const encrypted = document.getElementById('restore-encrypted').checked;
|
|
1425
|
-
const password = document.getElementById('restore-password').value;
|
|
1426
|
-
try {
|
|
1427
|
-
const result = await db.restoreBackup(backupData, encrypted ? password : null);
|
|
1428
|
-
showNotification(`Restored: ${result.databases} DBs, ${result.collections} collections, ${result.documents} docs.`, 'success');
|
|
1429
|
-
await updateDashboard();
|
|
1430
|
-
} catch (error) {
|
|
1431
|
-
showNotification(`Restore failed: ${error.message}`, 'error');
|
|
1432
|
-
}
|
|
1433
|
-
};
|
|
1434
|
-
reader.readAsText(file);
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
// --- Performance ---
|
|
1438
|
-
function startMonitoring() {
|
|
1439
|
-
if (db && db.performanceMonitor && !perfInterval) {
|
|
1440
|
-
db.performanceMonitor.startMonitoring();
|
|
1441
|
-
perfInterval = setInterval(updatePerformance, 1000);
|
|
1442
|
-
showNotification('Performance monitoring started', 'success');
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
function stopMonitoring() {
|
|
1446
|
-
if (db && db.performanceMonitor && perfInterval) {
|
|
1447
|
-
db.performanceMonitor.stopMonitoring();
|
|
1448
|
-
clearInterval(perfInterval);
|
|
1449
|
-
perfInterval = null;
|
|
1450
|
-
showNotification('Performance monitoring stopped', 'success');
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
function updatePerformance() {
|
|
1454
|
-
if (db && db.performanceMonitor) {
|
|
1455
|
-
const stats = db.performanceMonitor.getStats();
|
|
1456
|
-
document.getElementById('perf-ops').textContent = stats.opsPerSec;
|
|
1457
|
-
document.getElementById('perf-latency').textContent = `${stats.avgLatency}ms`;
|
|
1458
|
-
document.getElementById('perf-cache').textContent = `${stats.cacheHitRate}%`;
|
|
1459
|
-
document.getElementById('perf-memory').textContent = `${stats.memoryUsageMB}MB`;
|
|
1460
|
-
|
|
1461
|
-
const tips = db.performanceMonitor.getOptimizationTips();
|
|
1462
|
-
document.getElementById('optimization-tips').innerHTML = tips.map(tip => `<p>• ${tip}</p>`).join('');
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
// --- Settings ---
|
|
1467
|
-
async function updateDbSettings() {
|
|
1468
|
-
const dbName = document.getElementById('settings-database').value;
|
|
1469
|
-
if (!dbName) return showNotification('Select a database to configure.', 'warning');
|
|
1470
|
-
try {
|
|
1471
|
-
const database = await db.getDatabase(dbName);
|
|
1472
|
-
const settings = {
|
|
1473
|
-
sizeLimitKB: parseFloat(document.getElementById('size-limit').value) || Infinity,
|
|
1474
|
-
bufferLimitKB: parseFloat(document.getElementById('buffer-limit').value) || undefined,
|
|
1475
|
-
freeSpaceEvery: parseInt(document.getElementById('cleanup-interval').value) || 10000,
|
|
1476
|
-
};
|
|
1477
|
-
database.updateSettings(settings);
|
|
1478
|
-
showNotification(`Settings for "${dbName}" updated.`, 'success');
|
|
1479
|
-
} catch(error) {
|
|
1480
|
-
showNotification(`Error updating settings: ${error.message}`, 'error');
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
async function dropDatabase() {
|
|
1485
|
-
const dbName = document.getElementById('settings-database').value;
|
|
1486
|
-
if (!dbName) return showNotification('Select a database to drop.', 'warning');
|
|
1487
|
-
showModal('Confirm Drop Database', `<p>Really drop database "<strong>${dbName}</strong>"? All collections and documents will be lost.</p>`, [
|
|
1488
|
-
{ text: 'Cancel', class: 'btn-secondary' },
|
|
1489
|
-
{ text: 'Drop Database', class: 'btn-danger', onClick: async () => {
|
|
1490
|
-
try {
|
|
1491
|
-
await db.dropDatabase(dbName);
|
|
1492
|
-
showNotification(`Database "${dbName}" dropped.`, 'success');
|
|
1493
|
-
await updateDashboard();
|
|
1494
|
-
} catch (error) { showNotification(`Error: ${error.message}`, 'error'); }
|
|
1495
|
-
}}
|
|
1496
|
-
]);
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
async function clearAllData() {
|
|
1500
|
-
showModal('Confirm Clear All Data', `<p>This will permanently delete ALL data from EVERY database. Are you absolutely sure?</p>`, [
|
|
1501
|
-
{ text: 'Cancel', class: 'btn-secondary' },
|
|
1502
|
-
{ text: 'DELETE EVERYTHING', class: 'btn-danger', onClick: async () => {
|
|
1503
|
-
try {
|
|
1504
|
-
const databases = db.listDatabases();
|
|
1505
|
-
for (const dbName of databases) {
|
|
1506
|
-
await db.dropDatabase(dbName);
|
|
1507
|
-
}
|
|
1508
|
-
showNotification('All data cleared.', 'success');
|
|
1509
|
-
await updateDashboard();
|
|
1510
|
-
} catch (error) { showNotification(`Error: ${error.message}`, 'error'); }
|
|
1511
|
-
}}
|
|
1512
|
-
]);
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
// --- Developer Console ---
|
|
1516
|
-
async function executeCode() {
|
|
1517
|
-
const code = document.getElementById('dev-code').value;
|
|
1518
|
-
const consoleDiv = document.getElementById('dev-console');
|
|
1519
|
-
|
|
1520
|
-
const customConsole = {
|
|
1521
|
-
log: (...args) => {
|
|
1522
|
-
const output = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg).join(' ');
|
|
1523
|
-
const line = document.createElement('div');
|
|
1524
|
-
line.className = 'console-line console-success';
|
|
1525
|
-
line.innerHTML = `<span class="console-prompt">></span> ${output}`;
|
|
1526
|
-
consoleDiv.appendChild(line);
|
|
1527
|
-
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
|
1528
|
-
},
|
|
1529
|
-
error: (...args) => {
|
|
1530
|
-
const output = args.map(arg => arg instanceof Error ? arg.message : arg).join(' ');
|
|
1531
|
-
const line = document.createElement('div');
|
|
1532
|
-
line.className = 'console-line console-error';
|
|
1533
|
-
line.innerHTML = `<span class="console-prompt">!</span> ${output}`;
|
|
1534
|
-
consoleDiv.appendChild(line);
|
|
1535
|
-
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
|
-
|
|
1539
|
-
try {
|
|
1540
|
-
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
1541
|
-
const fn = new AsyncFunction('db', 'console', code);
|
|
1542
|
-
await fn(db, customConsole);
|
|
1543
|
-
} catch (error) {
|
|
1544
|
-
customConsole.error(error);
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
// --- Event Listeners ---
|
|
1549
|
-
document.getElementById('doc-encrypted').addEventListener('change', (e) => {
|
|
1550
|
-
document.getElementById('doc-password').style.display = e.target.checked ? 'block' : 'none';
|
|
1551
|
-
});
|
|
1552
|
-
document.getElementById('backup-encrypted').addEventListener('change', (e) => {
|
|
1553
|
-
document.getElementById('backup-password').style.display = e.target.checked ? 'block' : 'none';
|
|
1554
|
-
});
|
|
1555
|
-
document.getElementById('restore-encrypted').addEventListener('change', (e) => {
|
|
1556
|
-
document.getElementById('restore-password').style.display = e.target.checked ? 'block' : 'none';
|
|
1557
|
-
});
|
|
1558
|
-
document.getElementById('backup-type').addEventListener('change', (e) => {
|
|
1559
|
-
document.getElementById('backup-db-select').style.display = e.target.value === 'single' ? 'block' : 'none';
|
|
1560
|
-
});
|
|
1561
|
-
document.getElementById('doc-database').addEventListener('change', (e) => updateCollectionSelect(e.target.value, 'doc-collection'));
|
|
1562
|
-
document.getElementById('query-database').addEventListener('change', (e) => updateCollectionSelect(e.target.value, 'query-collection'));
|
|
1563
|
-
document.getElementById('doc-collection').addEventListener('change', (e) => listDocuments(document.getElementById('doc-database').value, e.target.value));
|
|
1564
|
-
|
|
1565
|
-
// --- Helpers ---
|
|
1566
|
-
function formatSize(bytes) {
|
|
1567
|
-
if (bytes === 0) return '0 B';
|
|
1568
|
-
const k = 1024;
|
|
1569
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
1570
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1571
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
function showNotification(message, type = 'success') {
|
|
1575
|
-
const notification = document.createElement('div');
|
|
1576
|
-
notification.className = `notification ${type}`;
|
|
1577
|
-
notification.textContent = message;
|
|
1578
|
-
document.body.appendChild(notification);
|
|
1579
|
-
setTimeout(() => {
|
|
1580
|
-
notification.style.animation = 'slideOut 0.3s forwards';
|
|
1581
|
-
notification.addEventListener('animationend', () => notification.remove());
|
|
1582
|
-
}, 3000);
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
function showModal(title, body, buttons = []) {
|
|
1586
|
-
document.getElementById('modal-title').textContent = title;
|
|
1587
|
-
const modalBody = document.getElementById('modal-body');
|
|
1588
|
-
modalBody.innerHTML = '';
|
|
1589
|
-
if (typeof body === 'string') {
|
|
1590
|
-
modalBody.innerHTML = body;
|
|
1591
|
-
} else {
|
|
1592
|
-
modalBody.appendChild(body);
|
|
1593
|
-
}
|
|
1594
|
-
const btnGroup = document.getElementById('modal-buttons');
|
|
1595
|
-
btnGroup.innerHTML = '';
|
|
1596
|
-
|
|
1597
|
-
if (buttons.length > 0) {
|
|
1598
|
-
buttons.forEach(btnInfo => {
|
|
1599
|
-
const button = document.createElement('button');
|
|
1600
|
-
button.className = `btn ${btnInfo.class || 'btn-secondary'}`;
|
|
1601
|
-
button.textContent = btnInfo.text;
|
|
1602
|
-
button.onclick = () => { if (btnInfo.onClick) btnInfo.onClick(); closeModal(); };
|
|
1603
|
-
btnGroup.appendChild(button);
|
|
1604
|
-
});
|
|
1605
|
-
}
|
|
1606
|
-
const closeButton = document.createElement('button');
|
|
1607
|
-
closeButton.className = 'btn btn-primary';
|
|
1608
|
-
closeButton.textContent = buttons.length > 0 ? 'Close' : 'OK';
|
|
1609
|
-
closeButton.onclick = closeModal;
|
|
1610
|
-
btnGroup.appendChild(closeButton);
|
|
1611
|
-
|
|
1612
|
-
document.getElementById('modal').classList.add('show');
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
function closeModal() {
|
|
1616
|
-
document.getElementById('modal').classList.remove('show');
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
function syntaxHighlight(json) {
|
|
1620
|
-
if (!json) return '';
|
|
1621
|
-
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1622
|
-
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
|
1623
|
-
let cls = 'json-number';
|
|
1624
|
-
if (/^"/.test(match)) {
|
|
1625
|
-
if (/:$/.test(match)) {
|
|
1626
|
-
cls = 'json-key';
|
|
1627
|
-
} else {
|
|
1628
|
-
cls = 'json-string';
|
|
1629
|
-
}
|
|
1630
|
-
} else if (/true|false/.test(match)) {
|
|
1631
|
-
cls = 'json-boolean';
|
|
1632
|
-
} else if (/null/.test(match)) {
|
|
1633
|
-
cls = 'json-null';
|
|
1634
|
-
}
|
|
1635
|
-
return '<span class="' + cls + '">' + match + '</span>';
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
</script>
|
|
1640
|
-
</body>
|
|
1641
|
-
</html>
|