admindb 2.2.1 → 2.3.1

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.
Files changed (89) hide show
  1. package/README.md +368 -364
  2. package/dist/app.js +25 -16
  3. package/dist/auth/config.d.ts +1 -1
  4. package/dist/auth/config.js +18 -30
  5. package/dist/auth/crypto.js +11 -21
  6. package/dist/auth/middleware.js +9 -16
  7. package/dist/auth/routes.js +9 -15
  8. package/dist/cli/args.d.ts +1 -1
  9. package/dist/cli/args.js +20 -29
  10. package/dist/cli/config.d.ts +1 -1
  11. package/dist/cli/config.js +15 -18
  12. package/dist/cli/runner.js +13 -13
  13. package/dist/data/chain.d.ts +1 -1
  14. package/dist/data/chain.js +40 -617
  15. package/dist/data/datasets.d.ts +1 -1
  16. package/dist/data/datasets.js +43 -1
  17. package/dist/data/detector.js +45 -21
  18. package/dist/data/engine.d.ts +1 -1
  19. package/dist/data/engine.js +483 -142
  20. package/dist/data/index.d.ts +1 -1
  21. package/dist/data/index.js +8 -0
  22. package/dist/data/prng.d.ts +1 -0
  23. package/dist/data/prng.js +73 -0
  24. package/dist/data/registry.d.ts +1 -0
  25. package/dist/data/registry.js +805 -0
  26. package/dist/data/schema-graph.d.ts +1 -0
  27. package/dist/data/schema-graph.js +210 -0
  28. package/dist/data/strategies.d.ts +1 -1
  29. package/dist/data/strategies.js +44 -283
  30. package/dist/data/templates.d.ts +1 -0
  31. package/dist/data/templates.js +134 -0
  32. package/dist/data/types.d.ts +1 -1
  33. package/dist/data/validator.d.ts +1 -0
  34. package/dist/data/validator.js +160 -0
  35. package/dist/db/database.js +9 -14
  36. package/dist/db/export.js +32 -55
  37. package/dist/db/filters.d.ts +1 -1
  38. package/dist/db/filters.js +15 -38
  39. package/dist/db/introspection.js +4 -7
  40. package/dist/db/manager.d.ts +1 -1
  41. package/dist/db/manager.js +48 -53
  42. package/dist/db/migrations.js +7 -14
  43. package/dist/db/postgres.js +30 -78
  44. package/dist/index.d.ts +1 -1
  45. package/dist/public/css/app.css +1 -1
  46. package/dist/public/js/browse.js +1 -1
  47. package/dist/public/js/inspector.js +1 -1
  48. package/dist/public/js/query.js +1 -1
  49. package/dist/public/js/seed.js +1 -1
  50. package/dist/routes/api/erd.js +3 -8
  51. package/dist/routes/api/helpers.d.ts +1 -1
  52. package/dist/routes/api/helpers.js +75 -25
  53. package/dist/routes/api/import-export.js +1 -2
  54. package/dist/routes/api/query.js +2 -6
  55. package/dist/routes/api/rows.js +4 -8
  56. package/dist/routes/api/seed.js +35 -21
  57. package/dist/routes/api/tables.js +15 -30
  58. package/dist/routes/databases.js +8 -26
  59. package/dist/routes/pages.js +67 -18
  60. package/dist/serverless.js +31 -44
  61. package/dist/sql/generator.d.ts +1 -1
  62. package/dist/sql/generator.js +35 -109
  63. package/dist/types/api.d.ts +1 -1
  64. package/dist/utils/common.d.ts +1 -1
  65. package/dist/utils/common.js +45 -85
  66. package/dist/utils/csv.d.ts +1 -1
  67. package/dist/utils/csv.js +11 -17
  68. package/dist/utils/datatype.js +46 -60
  69. package/dist/utils/icons.js +8 -8
  70. package/dist/views/layouts/main.hbs +1 -87
  71. package/dist/views/pages/databases.hbs +1 -233
  72. package/dist/views/pages/designer.hbs +1 -89
  73. package/dist/views/pages/erd.hbs +1 -1241
  74. package/dist/views/pages/error.hbs +1 -10
  75. package/dist/views/pages/form.hbs +1 -59
  76. package/dist/views/pages/home.hbs +1 -220
  77. package/dist/views/pages/info.hbs +1 -107
  78. package/dist/views/pages/login.hbs +1 -98
  79. package/dist/views/pages/query.hbs +1 -129
  80. package/dist/views/pages/schema.hbs +1 -298
  81. package/dist/views/pages/seed-select.hbs +251 -0
  82. package/dist/views/pages/seed.hbs +256 -414
  83. package/dist/views/pages/table.hbs +1 -327
  84. package/dist/views/partials/navbar.hbs +1 -83
  85. package/dist/views/partials/sidebar.hbs +1 -129
  86. package/docs/API.md +149 -149
  87. package/docs/EXAMPLES.md +579 -579
  88. package/docs/SECURITY.md +265 -265
  89. package/package.json +80 -79
@@ -6,60 +6,66 @@ exports.analyzeBlob = analyzeBlob;
6
6
  exports.isJsonString = isJsonString;
7
7
  exports.formatJsonSafely = formatJsonSafely;
8
8
  const common_js_1 = require("./common.js");
9
+ const mimeResult = (mime, ext, flags = {}) => ({
10
+ mime,
11
+ ext,
12
+ isImage: false,
13
+ isText: false,
14
+ isPdf: false,
15
+ isAudio: false,
16
+ isVideo: false,
17
+ ...flags,
18
+ });
9
19
  function sniffMimeType(buf) {
10
20
  const len = buf.length;
11
- if (len === 0) {
12
- return { mime: 'application/octet-stream', ext: 'bin', isImage: false, isText: false, isPdf: false, isAudio: false, isVideo: false };
13
- }
21
+ if (len === 0)
22
+ return mimeResult('application/octet-stream', 'bin');
14
23
  if (len >= 8 && buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47 && buf[4] === 0x0d && buf[5] === 0x0a && buf[6] === 0x1a && buf[7] === 0x0a) {
15
- return { mime: 'image/png', ext: 'png', isImage: true, isText: false, isPdf: false, isAudio: false, isVideo: false };
24
+ return mimeResult('image/png', 'png', { isImage: true });
16
25
  }
17
26
  if (len >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
18
- return { mime: 'image/jpeg', ext: 'jpg', isImage: true, isText: false, isPdf: false, isAudio: false, isVideo: false };
27
+ return mimeResult('image/jpeg', 'jpg', { isImage: true });
19
28
  }
20
29
  if (len >= 6 && buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38 && (buf[4] === 0x37 || buf[4] === 0x39) && buf[5] === 0x61) {
21
- return { mime: 'image/gif', ext: 'gif', isImage: true, isText: false, isPdf: false, isAudio: false, isVideo: false };
30
+ return mimeResult('image/gif', 'gif', { isImage: true });
22
31
  }
23
32
  if (len >= 12 && buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) {
24
- return { mime: 'image/webp', ext: 'webp', isImage: true, isText: false, isPdf: false, isAudio: false, isVideo: false };
33
+ return mimeResult('image/webp', 'webp', { isImage: true });
25
34
  }
26
35
  if (len >= 2 && buf[0] === 0x42 && buf[1] === 0x4d) {
27
- return { mime: 'image/bmp', ext: 'bmp', isImage: true, isText: false, isPdf: false, isAudio: false, isVideo: false };
36
+ return mimeResult('image/bmp', 'bmp', { isImage: true });
28
37
  }
29
38
  if (len >= 4 && buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0x01 && buf[3] === 0x00) {
30
- return { mime: 'image/x-icon', ext: 'ico', isImage: true, isText: false, isPdf: false, isAudio: false, isVideo: false };
39
+ return mimeResult('image/x-icon', 'ico', { isImage: true });
31
40
  }
32
41
  if (len >= 4 && buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) {
33
- return { mime: 'application/pdf', ext: 'pdf', isImage: false, isText: false, isPdf: true, isAudio: false, isVideo: false };
34
- }
35
- if (len >= 3 && buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) {
36
- return { mime: 'audio/mpeg', ext: 'mp3', isImage: false, isText: false, isPdf: false, isAudio: true, isVideo: false };
42
+ return mimeResult('application/pdf', 'pdf', { isPdf: true });
37
43
  }
38
- if (len >= 2 && buf[0] === 0xff && (buf[1] & 0xe0) === 0xe0) {
39
- return { mime: 'audio/mpeg', ext: 'mp3', isImage: false, isText: false, isPdf: false, isAudio: true, isVideo: false };
44
+ if ((len >= 3 && buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) || (len >= 2 && buf[0] === 0xff && (buf[1] & 0xe0) === 0xe0)) {
45
+ return mimeResult('audio/mpeg', 'mp3', { isAudio: true });
40
46
  }
41
47
  if (len >= 12 && buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) {
42
- return { mime: 'audio/wav', ext: 'wav', isImage: false, isText: false, isPdf: false, isAudio: true, isVideo: false };
48
+ return mimeResult('audio/wav', 'wav', { isAudio: true });
43
49
  }
44
50
  if (len >= 4 && buf[0] === 0x4f && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) {
45
- return { mime: 'audio/ogg', ext: 'ogg', isImage: false, isText: false, isPdf: false, isAudio: true, isVideo: false };
51
+ return mimeResult('audio/ogg', 'ogg', { isAudio: true });
46
52
  }
47
53
  if (len >= 8 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) {
48
- return { mime: 'video/mp4', ext: 'mp4', isImage: false, isText: false, isPdf: false, isAudio: false, isVideo: true };
54
+ return mimeResult('video/mp4', 'mp4', { isVideo: true });
49
55
  }
50
56
  if (len >= 4 && buf[0] === 0x1a && buf[1] === 0x45 && buf[2] === 0xdf && buf[3] === 0xa3) {
51
- return { mime: 'video/webm', ext: 'webm', isImage: false, isText: false, isPdf: false, isAudio: false, isVideo: true };
57
+ return mimeResult('video/webm', 'webm', { isVideo: true });
52
58
  }
53
59
  const sampleLen = Math.min(len, 1024);
54
60
  const sample = Buffer.from(buf.subarray(0, sampleLen)).toString('utf8');
55
61
  const trimmed = sample.trim();
56
62
  if (trimmed.startsWith('<svg') || (trimmed.startsWith('<?xml') && trimmed.includes('<svg'))) {
57
- return { mime: 'image/svg+xml', ext: 'svg', isImage: true, isText: true, isPdf: false, isAudio: false, isVideo: false };
63
+ return mimeResult('image/svg+xml', 'svg', { isImage: true, isText: true });
58
64
  }
59
65
  if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
60
66
  try {
61
67
  JSON.parse(sample);
62
- return { mime: 'application/json', ext: 'json', isImage: false, isText: true, isPdf: false, isAudio: false, isVideo: false };
68
+ return mimeResult('application/json', 'json', { isText: true });
63
69
  }
64
70
  catch {
65
71
  }
@@ -72,15 +78,16 @@ function sniffMimeType(buf) {
72
78
  }
73
79
  }
74
80
  if (nonPrintable === 0) {
75
- return { mime: 'text/plain; charset=utf-8', ext: 'txt', isImage: false, isText: true, isPdf: false, isAudio: false, isVideo: false };
81
+ return mimeResult('text/plain; charset=utf-8', 'txt', { isText: true });
76
82
  }
77
- return { mime: 'application/octet-stream', ext: 'bin', isImage: false, isText: false, isPdf: false, isAudio: false, isVideo: false };
83
+ return mimeResult('application/octet-stream', 'bin');
78
84
  }
79
85
  function generateHexDump(buf, maxBytes = 4096, bytesPerLine = 16) {
80
86
  const totalBytes = buf.length;
81
87
  const limit = Math.min(totalBytes, maxBytes);
82
88
  const truncated = totalBytes > maxBytes;
83
89
  const lines = [];
90
+ const mid = Math.floor(bytesPerLine / 2);
84
91
  for (let i = 0; i < limit; i += bytesPerLine) {
85
92
  const offset = i.toString(16).padStart(8, '0');
86
93
  const chunk = buf.subarray(i, Math.min(i + bytesPerLine, limit));
@@ -90,18 +97,16 @@ function generateHexDump(buf, maxBytes = 4096, bytesPerLine = 16) {
90
97
  if (j < chunk.length) {
91
98
  const byte = chunk[j];
92
99
  hexParts.push(byte.toString(16).padStart(2, '0'));
93
- asciiPart += (byte >= 32 && byte <= 126) ? String.fromCharCode(byte) : '.';
100
+ asciiPart += byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.';
94
101
  }
95
102
  else {
96
103
  hexParts.push(' ');
97
104
  asciiPart += ' ';
98
105
  }
99
106
  }
100
- const mid = Math.floor(bytesPerLine / 2);
101
- const hexFormatted = hexParts.slice(0, mid).join(' ') + ' ' + hexParts.slice(mid).join(' ');
102
107
  lines.push({
103
108
  offset,
104
- hex: hexFormatted,
109
+ hex: `${hexParts.slice(0, mid).join(' ')} ${hexParts.slice(mid).join(' ')}`,
105
110
  ascii: asciiPart,
106
111
  });
107
112
  }
@@ -110,12 +115,7 @@ function generateHexDump(buf, maxBytes = 4096, bytesPerLine = 16) {
110
115
  function analyzeBlob(data) {
111
116
  let buf;
112
117
  if (typeof data === 'string') {
113
- if (data.startsWith('0x') || data.startsWith('0X')) {
114
- buf = Buffer.from(data.slice(2), 'hex');
115
- }
116
- else {
117
- buf = Buffer.from(data, 'utf8');
118
- }
118
+ buf = /^0x/i.test(data) ? Buffer.from(data.slice(2), 'hex') : Buffer.from(data, 'utf8');
119
119
  }
120
120
  else {
121
121
  buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
@@ -144,38 +144,24 @@ function isJsonString(value) {
144
144
  if (typeof value !== 'string')
145
145
  return false;
146
146
  const trimmed = value.trim();
147
- if (!trimmed)
147
+ if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('[')))
148
+ return false;
149
+ try {
150
+ JSON.parse(trimmed);
151
+ return true;
152
+ }
153
+ catch {
148
154
  return false;
149
- if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
150
- try {
151
- JSON.parse(trimmed);
152
- return true;
153
- }
154
- catch {
155
- return false;
156
- }
157
155
  }
158
- return false;
159
156
  }
160
157
  function formatJsonSafely(value, indent = 2) {
161
- if (value === null || value === undefined)
158
+ if (value == null)
162
159
  return null;
163
- if (typeof value === 'object') {
164
- try {
165
- return JSON.stringify(value, null, indent);
166
- }
167
- catch {
168
- return null;
169
- }
160
+ try {
161
+ const obj = typeof value === 'string' ? JSON.parse(value) : value;
162
+ return typeof obj === 'object' && obj !== null ? JSON.stringify(obj, null, indent) : null;
170
163
  }
171
- if (typeof value === 'string') {
172
- try {
173
- const parsed = JSON.parse(value);
174
- return JSON.stringify(parsed, null, indent);
175
- }
176
- catch {
177
- return null;
178
- }
164
+ catch {
165
+ return null;
179
166
  }
180
- return null;
181
167
  }
@@ -45,6 +45,9 @@ exports.ICONS = {
45
45
  spinner: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>',
46
46
  terminal: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>',
47
47
  format: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h10M4 18h14"/></svg>',
48
+ columns: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 4.5v15m6-15v15m-10.875 0h15.75c.621 0 1.125-.504 1.125-1.125V5.625c0-.621-.504-1.125-1.125-1.125H4.125C3.504 4.5 3 5.004 3 5.625v12.75c0 .621.504 1.125 1.125 1.125z" /></svg>',
49
+ pin: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 13.5l-5-5m3-3.5a2.121 2.121 0 00-3 0L9.5 9.5 5 10l5 5 .5 4.5 4.5-4.5a2.121 2.121 0 000-3zM4 20l5-5" /></svg>',
50
+ grip: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><circle cx="9" cy="7" r="1.2" fill="currentColor"/><circle cx="15" cy="7" r="1.2" fill="currentColor"/><circle cx="9" cy="12" r="1.2" fill="currentColor"/><circle cx="15" cy="12" r="1.2" fill="currentColor"/><circle cx="9" cy="17" r="1.2" fill="currentColor"/><circle cx="15" cy="17" r="1.2" fill="currentColor"/></svg>',
48
51
  check: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>',
49
52
  x: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>',
50
53
  warning: '<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>',
@@ -70,12 +73,9 @@ exports.ICONS = {
70
73
  };
71
74
  function renderIcon(name, customClass) {
72
75
  const svg = exports.ICONS[name];
73
- if (!svg)
74
- return '';
75
- if (!customClass)
76
- return svg;
77
- if (svg.includes('class="')) {
78
- return svg.replace(/class="[^"]*"/, `class="${customClass}"`);
79
- }
80
- return svg.replace('<svg', `<svg class="${customClass}"`);
76
+ if (!svg || !customClass)
77
+ return svg || '';
78
+ return svg.includes('class="')
79
+ ? svg.replace(/class="[^"]*"/, `class="${customClass}"`)
80
+ : svg.replace('<svg', `<svg class="${customClass}"`);
81
81
  }
@@ -1,87 +1 @@
1
- <!DOCTYPE html>
2
- <html lang="en" data-theme="admindb">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>{{title}} · AdminDB</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
10
- <link rel="stylesheet" href="{{basePath}}/css/app.css" />
11
- <script>
12
- window.APP = {
13
- basePath: {{{json basePath}}},
14
- readonly: {{{json readonly}}},
15
- serverless: {{{json serverless}}},
16
- dialect: {{{json dialect}}},
17
- isSqlite: {{{json isSqlite}}},
18
- isPostgres: {{{json isPostgres}}}
19
- };
20
- // Apply the saved theme before first paint to avoid a flash.
21
- (function () {
22
- try {
23
- var t = localStorage.getItem('admindb-theme');
24
- if (t === 'dark') document.documentElement.setAttribute('data-theme', 'admindbDark');
25
- } catch (e) {}
26
- })();
27
- </script>
28
- <script>
29
- window.AdminIcons = {
30
- map: {{{json icons}}},
31
- get: function(name, customClass) {
32
- var svg = this.map[name] || '';
33
- if (!svg || !customClass) return svg;
34
- if (svg.indexOf('class="') !== -1) {
35
- return svg.replace(/class="[^"]*"/, 'class="' + customClass + '"');
36
- }
37
- return svg.replace('<svg', '<svg class="' + customClass + '"');
38
- }
39
- };
40
- </script>
41
- <script src="{{basePath}}/js/common.js" defer></script>
42
- <script src="{{basePath}}/js/api.js" defer></script>
43
- <script src="{{basePath}}/js/app.js" defer></script>
44
- <script src="{{basePath}}/js/inspector.js" defer></script>
45
- </head>
46
- <body class="min-h-screen bg-base-200 text-base-content">
47
- {{#if readonly}}
48
- <div class="flex items-center justify-center gap-2 bg-warning/15 px-4 py-2 text-center text-sm font-semibold text-warning">
49
- <span class="shrink-0">{{{icon "warning" class="h-4 w-4"}}}</span>
50
- {{#if serverless}}
51
- Serverless mode active — database is open in read-only mode. Writes and schema modifications are disabled.
52
- {{else}}
53
- Read-only mode — this database is open without write access. All writes are disabled.
54
- {{/if}}
55
- </div>
56
- {{/if}}
57
- {{#if noSidebar}}
58
- <div class="flex min-h-screen flex-col">
59
- {{> navbar}}
60
- <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
61
- {{{body}}}
62
- </main>
63
- <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
64
- AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
65
- </footer>
66
- </div>
67
- {{else}}
68
- <div class="drawer lg:drawer-open">
69
- <input id="sidebar-toggle" type="checkbox" class="drawer-toggle" />
70
- <div class="drawer-content flex min-h-screen flex-col">
71
- {{> navbar}}
72
- <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
73
- {{{body}}}
74
- </main>
75
- <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
76
- AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
77
- </footer>
78
- </div>
79
- <div class="drawer-side z-40">
80
- <label for="sidebar-toggle" aria-label="close sidebar" class="drawer-overlay"></label>
81
- {{> sidebar}}
82
- </div>
83
- </div>
84
- {{/if}}
85
- <div id="toast-container" class="pointer-events-none fixed inset-x-0 top-4 z-50 flex flex-col items-center gap-2 px-4"></div>
86
- </body>
87
- </html>
1
+ <!DOCTYPE html>
88
2
  <meta charset="utf-8" />
89
3
  <meta name="viewport" content="width=device-width, initial-scale=1" />
90
4
  <title>{{title}} · AdminDB</title>
91
5
  <link rel="preconnect" href="https://fonts.googleapis.com" />
92
6
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
93
7
  <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
94
8
  <link rel="stylesheet" href="{{basePath}}/css/app.css" />
95
9
  <script>
96
10
  window.APP = {
97
11
  basePath: {{{json basePath}}},
98
12
  readonly: {{{json readonly}}},
99
13
  serverless: {{{json serverless}}},
100
14
  dialect: {{{json dialect}}},
101
15
  isSqlite: {{{json isSqlite}}},
102
16
  isPostgres: {{{json isPostgres}}}
103
17
  };
104
18
  // Apply the saved theme before first paint to avoid a flash.
105
19
  (function () {
106
20
  try {
107
21
  var t = localStorage.getItem('admindb-theme');
108
22
  if (t === 'dark') document.documentElement.setAttribute('data-theme', 'admindbDark');
109
23
  } catch (e) {}
110
24
  })();
111
25
  </script>
112
26
  <script>
113
27
  window.AdminIcons = {
114
28
  map: {{{json icons}}},
115
29
  get: function(name, customClass) {
116
30
  var svg = this.map[name] || '';
117
31
  if (!svg || !customClass) return svg;
118
32
  if (svg.indexOf('class="') !== -1) {
119
33
  return svg.replace(/class="[^"]*"/, 'class="' + customClass + '"');
120
34
  }
121
35
  return svg.replace('<svg', '<svg class="' + customClass + '"');
122
36
  }
123
37
  };
124
38
  </script>
125
39
  <script src="{{basePath}}/js/common.js" defer></script>
126
40
  <script src="{{basePath}}/js/api.js" defer></script>
127
41
  <script src="{{basePath}}/js/app.js" defer></script>
128
42
  <script src="{{basePath}}/js/inspector.js" defer></script>
129
43
  <div class="pointer-events-none fixed -left-24 top-32 -z-10 h-72 w-72 rounded-full bg-primary/15 blur-3xl"></div>
130
44
  <div class="pointer-events-none fixed -right-24 bottom-20 -z-10 h-80 w-80 rounded-full bg-orange-500/10 blur-3xl"></div>
131
45
  {{#if readonly}}
132
46
  <div class="flex items-center justify-center gap-2 bg-warning/15 px-4 py-2 text-center text-sm font-semibold text-warning">
133
47
  <span class="shrink-0">{{{icon "warning" class="h-4 w-4"}}}</span>
134
48
  {{#if serverless}}
135
49
  Serverless mode active — database is open in read-only mode. Writes and schema modifications are disabled.
136
50
  {{else}}
137
51
  Read-only mode — this database is open without write access. All writes are disabled.
138
52
  {{/if}}
139
53
  </div>
140
54
  {{/if}}
141
55
  {{#if noSidebar}}
142
56
  <div class="flex min-h-screen flex-col">
143
57
  {{> navbar}}
144
58
  <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
145
59
  {{{body}}}
146
60
  </main>
147
61
  <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
148
62
  AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
149
63
  </footer>
150
64
  </div>
151
65
  {{else}}
152
66
  <div class="drawer lg:drawer-open">
153
67
  <input id="sidebar-toggle" type="checkbox" class="drawer-toggle" />
154
68
  <div class="drawer-content flex min-h-screen flex-col">
155
69
  {{> navbar}}
156
70
  <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
157
71
  {{{body}}}
158
72
  </main>
159
73
  <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
160
74
  AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
161
75
  </footer>
162
76
  </div>
163
77
  <div class="drawer-side z-40">
164
78
  <label for="sidebar-toggle" aria-label="close sidebar" class="drawer-overlay"></label>
165
79
  {{> sidebar}}
166
80
  </div>
167
81
  </div>
168
82
  {{/if}}
169
83
  <div id="toast-container" class="pointer-events-none fixed inset-x-0 top-4 z-50 flex flex-col items-center gap-2 px-4"></div>