admindb 2.3.1 → 2.4.0

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.
@@ -1 +1 @@
1
- <!DOCTYPE html>
2
1
  <meta charset="utf-8" />
3
2
  <meta name="viewport" content="width=device-width, initial-scale=1" />
4
3
  <title>{{title}} · AdminDB</title>
5
4
  <link rel="preconnect" href="https://fonts.googleapis.com" />
6
5
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
7
6
  <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" />
8
7
  <link rel="stylesheet" href="{{basePath}}/css/app.css" />
9
8
  <script>
10
9
  window.APP = {
11
10
  basePath: {{{json basePath}}},
12
11
  readonly: {{{json readonly}}},
13
12
  serverless: {{{json serverless}}},
14
13
  dialect: {{{json dialect}}},
15
14
  isSqlite: {{{json isSqlite}}},
16
15
  isPostgres: {{{json isPostgres}}}
17
16
  };
18
17
  // Apply the saved theme before first paint to avoid a flash.
19
18
  (function () {
20
19
  try {
21
20
  var t = localStorage.getItem('admindb-theme');
22
21
  if (t === 'dark') document.documentElement.setAttribute('data-theme', 'admindbDark');
23
22
  } catch (e) {}
24
23
  })();
25
24
  </script>
26
25
  <script>
27
26
  window.AdminIcons = {
28
27
  map: {{{json icons}}},
29
28
  get: function(name, customClass) {
30
29
  var svg = this.map[name] || '';
31
30
  if (!svg || !customClass) return svg;
32
31
  if (svg.indexOf('class="') !== -1) {
33
32
  return svg.replace(/class="[^"]*"/, 'class="' + customClass + '"');
34
33
  }
35
34
  return svg.replace('<svg', '<svg class="' + customClass + '"');
36
35
  }
37
36
  };
38
37
  </script>
39
38
  <script src="{{basePath}}/js/common.js" defer></script>
40
39
  <script src="{{basePath}}/js/api.js" defer></script>
41
40
  <script src="{{basePath}}/js/app.js" defer></script>
42
41
  <script src="{{basePath}}/js/inspector.js" defer></script>
43
42
  <div class="pointer-events-none fixed -left-24 top-32 -z-10 h-72 w-72 rounded-full bg-primary/15 blur-3xl"></div>
44
43
  <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>
45
44
  {{#if readonly}}
46
45
  <div class="flex items-center justify-center gap-2 bg-warning/15 px-4 py-2 text-center text-sm font-semibold text-warning">
47
46
  <span class="shrink-0">{{{icon "warning" class="h-4 w-4"}}}</span>
48
47
  {{#if serverless}}
49
48
  Serverless mode active — database is open in read-only mode. Writes and schema modifications are disabled.
50
49
  {{else}}
51
50
  Read-only mode — this database is open without write access. All writes are disabled.
52
51
  {{/if}}
53
52
  </div>
54
53
  {{/if}}
55
54
  {{#if noSidebar}}
56
55
  <div class="flex min-h-screen flex-col">
57
56
  {{> navbar}}
58
57
  <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
59
58
  {{{body}}}
60
59
  </main>
61
60
  <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
62
61
  AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
63
62
  </footer>
64
63
  </div>
65
64
  {{else}}
66
65
  <div class="drawer lg:drawer-open">
67
66
  <input id="sidebar-toggle" type="checkbox" class="drawer-toggle" />
68
67
  <div class="drawer-content flex min-h-screen flex-col">
69
68
  {{> navbar}}
70
69
  <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
71
70
  {{{body}}}
72
71
  </main>
73
72
  <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
74
73
  AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
75
74
  </footer>
76
75
  </div>
77
76
  <div class="drawer-side z-40">
78
77
  <label for="sidebar-toggle" aria-label="close sidebar" class="drawer-overlay"></label>
79
78
  {{> sidebar}}
80
79
  </div>
81
80
  </div>
82
81
  {{/if}}
83
82
  <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>
83
+ <!DOCTYPE html>
84
84
  <meta charset="utf-8" />
85
85
  <meta name="viewport" content="width=device-width, initial-scale=1" />
86
86
  <title>{{title}} · AdminDB</title>
87
87
  <link rel="preconnect" href="https://fonts.googleapis.com" />
88
88
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
89
89
  <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" />
90
90
  <link rel="stylesheet" href="{{basePath}}/css/app.css" />
91
91
  <script>
92
92
  window.APP = {
93
93
  basePath: {{{json basePath}}},
94
94
  dbId: {{{json dbId}}},
95
95
  readonly: {{{json readonly}}},
96
96
  serverless: {{{json serverless}}},
97
97
  dialect: {{{json dialect}}},
98
98
  isSqlite: {{{json isSqlite}}},
99
99
  isPostgres: {{{json isPostgres}}}
100
100
  };
101
101
  // Apply the saved theme before first paint to avoid a flash.
102
102
  (function () {
103
103
  try {
104
104
  var t = localStorage.getItem('admindb-theme');
105
105
  if (t === 'dark') document.documentElement.setAttribute('data-theme', 'admindbDark');
106
106
  } catch (e) {}
107
107
  })();
108
108
  </script>
109
109
  <script>
110
110
  window.AdminIcons = {
111
111
  map: {{{json icons}}},
112
112
  get: function(name, customClass) {
113
113
  var svg = this.map[name] || '';
114
114
  if (!svg || !customClass) return svg;
115
115
  if (svg.indexOf('class="') !== -1) {
116
116
  return svg.replace(/class="[^"]*"/, 'class="' + customClass + '"');
117
117
  }
118
118
  return svg.replace('<svg', '<svg class="' + customClass + '"');
119
119
  }
120
120
  };
121
121
  </script>
122
122
  <script src="{{basePath}}/js/common.js" defer></script>
123
123
  <script src="{{basePath}}/js/api.js" defer></script>
124
124
  <script src="{{basePath}}/js/app.js" defer></script>
125
125
  <script src="{{basePath}}/js/inspector.js" defer></script>
126
126
  <div class="pointer-events-none fixed inset-0 -z-10 overflow-hidden">
127
127
  <div class="absolute -left-24 top-32 h-72 w-72 rounded-full bg-primary/15 blur-3xl"></div>
128
128
  <div class="absolute -right-24 bottom-20 h-80 w-80 rounded-full bg-orange-500/10 blur-3xl"></div>
129
129
  </div>
130
130
  {{#if readonly}}
131
131
  <div class="flex items-center justify-center gap-2 bg-warning/15 px-4 py-2 text-center text-sm font-semibold text-warning">
132
132
  <span class="shrink-0">{{{icon "warning" class="h-4 w-4"}}}</span>
133
133
  {{#if serverless}}
134
134
  Serverless mode active — database is open in read-only mode. Writes and schema modifications are disabled.
135
135
  {{else}}
136
136
  Read-only mode — this database is open without write access. All writes are disabled.
137
137
  {{/if}}
138
138
  </div>
139
139
  {{/if}}
140
140
  {{#if noSidebar}}
141
141
  <div class="flex min-h-screen flex-col">
142
142
  {{> navbar}}
143
143
  <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
144
144
  {{{body}}}
145
145
  </main>
146
146
  <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
147
147
  AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
148
148
  </footer>
149
149
  </div>
150
150
  {{else}}
151
151
  <div class="drawer lg:drawer-open">
152
152
  <input id="sidebar-toggle" type="checkbox" class="drawer-toggle" />
153
153
  <div class="drawer-content flex min-h-screen flex-col">
154
154
  {{> navbar}}
155
155
  <main class="mx-auto w-full max-w-[1500px] flex-1 px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
156
156
  {{{body}}}
157
157
  </main>
158
158
  <footer class="border-t border-base-300 px-6 py-4 text-center text-xs text-base-content/40">
159
159
  AdminDB{{#if version}} v{{version}}{{/if}} · SQLite & PostgreSQL database administration
160
160
  </footer>
161
161
  </div>
162
162
  <div class="drawer-side z-40 lg:sticky lg:top-0 lg:h-screen lg:max-h-screen">
163
163
  <label for="sidebar-toggle" aria-label="close sidebar" class="drawer-overlay"></label>
164
164
  {{> sidebar}}
165
165
  </div>
166
166
  </div>
167
167
  {{/if}}
168
168
  <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>
@@ -1 +1 @@
1
- <div class="space-y-6">
2
1
  <div class="page-header">
3
2
  <div class="page-header-row">
4
3
  <div>
5
4
  <div class="page-pretitle">Manager</div>
6
5
  <h1 class="page-title">Databases</h1>
7
6
  <p class="mt-0.5 text-xs text-base-content/60">Connect to PostgreSQL databases, create SQLite files, or open existing local databases with configurable read/write access.</p>
8
7
  </div>
9
8
  </div>
10
9
  </div>
11
10
  <div class="grid grid-cols-1 gap-5 lg:grid-cols-2">
12
11
  <div class="app-card p-5 border border-info/30 bg-info/[0.02]">
13
12
  <div class="mb-3 flex items-center justify-between">
14
13
  <h2 class="flex items-center gap-2 text-sm font-bold uppercase tracking-wide text-info">
15
14
  {{{icon "database" class="h-4 w-4"}}}
16
15
  Connect PostgreSQL
17
16
  </h2>
18
17
  <span class="rounded bg-info/15 px-2 py-0.5 text-[10px] font-bold text-info">Remote / Local</span>
19
18
  </div>
20
19
  <div class="space-y-3">
21
20
  <div>
22
21
  <label class="field-label" for="pg-conn-name">Connection Name <span class="text-base-content/40">(optional)</span></label>
23
22
  <input id="pg-conn-name" class="field-input font-mono text-xs" placeholder="e.g. prod_postgres" autocomplete="off" spellcheck="false" />
24
23
  </div>
25
24
  <div>
26
25
  <label class="field-label" for="pg-conn-uri">Connection URI <span class="chip chip-type">required</span></label>
27
26
  <input id="pg-conn-uri" class="field-input font-mono text-xs" placeholder="postgresql://user:password@localhost:5432/mydb" autocomplete="off" spellcheck="false" />
28
27
  </div>
29
28
  <div>
30
29
  <label class="flex items-center gap-2 cursor-pointer pt-0.5">
31
30
  <input type="checkbox" id="pg-conn-readonly" class="checkbox checkbox-xs checkbox-primary rounded" />
32
31
  <span class="text-xs font-medium text-base-content/75">Open as Read-only (block writes & schema changes)</span>
33
32
  </label>
34
33
  </div>
35
34
  <div class="pt-1">
36
35
  {{#unless readonly}}
37
36
  <button id="connect-pg" type="button" class="btn btn-info btn-sm gap-1.5 w-full sm:w-auto">
38
37
  {{{icon "plus" class="h-4 w-4"}}}
39
38
  Connect PostgreSQL
40
39
  </button>
41
40
  {{else}}
42
41
  <span class="text-xs font-medium text-warning">Read-only mode — connecting databases is disabled.</span>
43
42
  {{/unless}}
44
43
  </div>
45
44
  </div>
46
45
  <p class="mt-3 text-[11px] text-base-content/45 leading-relaxed">
47
46
  Credentials are encrypted in-memory and sanitized in the UI. Passwords are never sent to the client.
48
47
  </p>
49
48
  </div>
50
49
  <div class="app-card p-5 border border-emerald-500/30 bg-emerald-500/[0.02]">
51
50
  <div class="mb-3 flex items-center justify-between">
52
51
  <h2 class="flex items-center gap-2 text-sm font-bold uppercase tracking-wide text-emerald-600 dark:text-emerald-400">
53
52
  {{{icon "plus" class="h-4 w-4"}}}
54
53
  Create SQLite Database
55
54
  </h2>
56
55
  <span class="rounded bg-emerald-500/15 px-2 py-0.5 text-[10px] font-bold text-emerald-600 dark:text-emerald-400">File-based</span>
57
56
  </div>
58
57
  <div class="space-y-3">
59
58
  <div>
60
59
  <label class="field-label" for="new-db-name">Database Name <span class="chip chip-type">required</span></label>
61
60
  <input id="new-db-name" class="field-input font-mono text-xs" placeholder="e.g. analytics" autocomplete="off" spellcheck="false" />
62
61
  </div>
63
62
  <div class="pt-1">
64
63
  {{#unless readonly}}
65
64
  <button id="create-db" type="button" class="btn btn-primary btn-sm gap-1.5 w-full sm:w-auto">
66
65
  {{{icon "plus" class="h-4 w-4"}}}
67
66
  Create SQLite DB
68
67
  </button>
69
68
  {{else}}
70
69
  <span class="text-xs font-medium text-warning">Read-only — creating databases is disabled.</span>
71
70
  {{/unless}}
72
71
  </div>
73
72
  </div>
74
73
  <p class="mt-3 text-[11px] text-base-content/45 leading-relaxed">
75
74
  Files are saved to <code class="font-mono">{{dbDir}}</code>. Any <code class="font-mono">.db</code> file dropped there is auto-discovered.
76
75
  </p>
77
76
  </div>
78
77
  </div>
79
78
  <div>
80
79
  <div class="mb-3 flex items-center justify-between">
81
80
  <h2 class="text-lg font-bold">Available databases</h2>
82
81
  <span class="text-sm text-base-content/45">{{databases.length}} total</span>
83
82
  </div>
84
83
  {{#if databases.length}}
85
84
  <div class="app-card overflow-hidden">
86
85
  <div class="overflow-x-auto">
87
86
  <table class="table table-sm">
88
87
  <thead>
89
88
  <tr class="bg-base-200/70 text-xs uppercase tracking-wide text-base-content/50">
90
89
  <th class="font-semibold">Name</th>
91
90
  <th class="font-semibold">Mode</th>
92
91
  <th class="text-right font-semibold">Tables</th>
93
92
  <th class="text-right font-semibold">Size</th>
94
93
  <th class="font-semibold">Status / Modified</th>
95
94
  <th class="text-right font-semibold">Actions</th>
96
95
  </tr>
97
96
  </thead>
98
97
  <tbody>
99
98
  {{#each databases}}
100
99
  <tr class="group hover:bg-primary/[0.04]">
101
100
  <td>
102
101
  <a href="{{../basePath}}/{{this.id}}" class="inline-flex items-center gap-2.5">
103
102
  <span class="grid h-8 w-8 shrink-0 place-items-center rounded-lg {{#if this.isPostgres}}bg-info/10 text-info group-hover:bg-info/20{{else}}bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 group-hover:bg-emerald-500/20{{/if}} transition-colors">
104
103
  {{{icon "database" class="h-4 w-4"}}}
105
104
  </span>
106
105
  <span class="min-w-0">
107
106
  <div class="flex items-center gap-1.5">
108
107
  <span class="truncate font-mono text-[13.5px] font-semibold">{{this.name}}</span>
109
108
  {{#if this.isPostgres}}
110
109
  <span class="rounded bg-info/15 px-1.5 py-0.2 text-[10px] font-bold text-info">PostgreSQL</span>
111
110
  {{else}}
112
111
  <span class="rounded bg-emerald-500/15 px-1.5 py-0.2 text-[10px] font-bold text-emerald-600 dark:text-emerald-400">SQLite</span>
113
112
  {{/if}}
114
113
  </div>
115
114
  <span class="block max-w-[260px] truncate font-mono text-[11px] text-base-content/40" title="{{this.path}}">{{this.path}}</span>
116
115
  </span>
117
116
  </a>
118
117
  </td>
119
118
  <td class="align-middle">
120
119
  {{#if ../readonly}}
121
120
  <span class="rounded bg-warning/20 px-2 py-0.5 text-[10px] font-bold uppercase text-warning">Read-only</span>
122
121
  {{else}}
123
122
  <button type="button" class="js-toggle-db-mode rounded px-2 py-0.5 text-[10px] font-bold uppercase transition hover:opacity-80 {{#if this.readonly}}bg-warning/20 text-warning{{else}}bg-success/20 text-success{{/if}}"
124
123
  data-id="{{this.id}}" data-readonly="{{#if this.readonly}}true{{else}}false{{/if}}" title="Click to toggle between Writable and Read-only">
125
124
  {{#if this.readonly}}Read-only{{else}}Writable{{/if}} ⇄
126
125
  </button>
127
126
  {{/if}}
128
127
  </td>
129
128
  <td class="text-right tabular-nums text-base-content/60">{{this.tables}}</td>
130
129
  <td class="text-right tabular-nums text-base-content/60">{{this.sizeLabel}}</td>
131
130
  <td class="text-[13px] text-base-content/55">{{this.modifiedLabel}}</td>
132
131
  <td class="whitespace-nowrap text-right">
133
132
  <div class="row-actions">
134
133
  <div class="join">
135
134
  <a href="{{../basePath}}/{{this.id}}" class="btn btn-ghost btn-xs join-item gap-1 text-base-content/70 hover:text-primary">
136
135
  {{{icon "arrowRight" class="h-3.5 w-3.5"}}}
137
136
  Open
138
137
  </a>
139
138
  <div class="dropdown dropdown-end">
140
139
  <label tabindex="0" role="button" class="btn btn-ghost btn-xs join-item px-1 text-base-content/60 hover:text-base-content" title="Open options">
141
140
  {{{icon "chevronDown" class="h-3 w-3"}}}
142
141
  </label>
143
142
  <ul tabindex="0" class="dropdown-content menu z-50 w-44 rounded-xl border border-base-300 bg-base-100 p-1.5 shadow-lift text-xs">
144
143
  <li>
145
144
  <a href="{{../basePath}}/{{this.id}}?readonly=0" class="flex items-center gap-1.5 text-success">
146
145
  {{{icon "check" class="h-3.5 w-3.5"}}}
147
146
  Open Writable
148
147
  </a>
149
148
  </li>
150
149
  <li>
151
150
  <a href="{{../basePath}}/{{this.id}}?readonly=1" class="flex items-center gap-1.5 text-warning">
152
151
  {{{icon "lock" class="h-3.5 w-3.5"}}}
153
152
  Open Read-only
154
153
  </a>
155
154
  </li>
156
155
  </ul>
157
156
  </div>
158
157
  </div>
159
158
  {{#unless ../readonly}}
160
159
  {{#if this.isPostgres}}
161
160
  <button type="button" class="btn btn-ghost btn-xs gap-1 text-warning js-disconnect-db" data-id="{{this.id}}" data-name="{{this.name}}" title="Disconnect this database from the manager list">
162
161
  {{{icon "logout" class="h-3.5 w-3.5"}}}
163
162
  Disconnect
164
163
  </button>
165
164
  {{else}}
166
165
  <button type="button" class="btn btn-ghost btn-xs gap-1 text-error js-delete-db" data-id="{{this.id}}" title="Delete database file from disk">
167
166
  {{{icon "trash" class="h-3.5 w-3.5"}}}
168
167
  Delete
169
168
  </button>
170
169
  {{/if}}
171
170
  {{/unless}}
172
171
  </div>
173
172
  </td>
174
173
  </tr>
175
174
  {{/each}}
176
175
  </tbody>
177
176
  </table>
178
177
  </div>
179
178
  </div>
180
179
  {{else}}
181
180
  <div class="empty-state">
182
181
  <span class="empty-icon">{{{icon "database" class="h-7 w-7"}}}</span>
183
182
  <p class="text-sm font-medium text-base-content/70">No databases yet</p>
184
183
  <p class="max-w-sm text-sm text-base-content/45">
185
184
  Connect a PostgreSQL database or create a new SQLite database above to get started.
186
185
  </p>
187
186
  </div>
188
187
  {{/if}}
189
188
  </div>
190
189
  {{#if allowBrowse}}
191
190
  <div class="app-card p-5">
192
191
  <div class="mb-3 flex flex-wrap items-center justify-between gap-2">
193
192
  <h2 class="text-sm font-semibold uppercase tracking-wide text-base-content/45">Open an existing SQLite file</h2>
194
193
  <span class="text-xs text-base-content/40">.db · .sqlite · .sqlite3</span>
195
194
  </div>
196
195
  <div class="flex flex-wrap items-center gap-2">
197
196
  <input id="fs-path" class="field-input font-mono sm:!w-96" placeholder="Absolute path to a folder, e.g. C:\data" autocomplete="off" spellcheck="false" />
198
197
  <button id="fs-go" type="button" class="btn btn-outline btn-sm gap-1.5">
199
198
  {{{icon "search" class="h-4 w-4"}}}
200
199
  Go
201
200
  </button>
202
201
  <button id="fs-up" type="button" class="btn btn-ghost btn-sm gap-1.5">
203
202
  {{{icon "arrowUp" class="h-4 w-4"}}}
204
203
  Up
205
204
  </button>
206
205
  </div>
207
206
  <div class="mt-2">
208
207
  <label class="flex items-center gap-2 cursor-pointer">
209
208
  <input type="checkbox" id="fs-readonly" class="checkbox checkbox-xs checkbox-primary rounded" />
210
209
  <span class="text-xs font-medium text-base-content/70">Open selected file in Read-only mode</span>
211
210
  </label>
212
211
  </div>
213
212
  <div class="mt-3 overflow-hidden rounded-xl border border-base-300">
214
213
  <div id="fs-current" class="border-b border-base-200 bg-base-200/50 px-3 py-2 font-mono text-xs text-base-content/60"></div>
215
214
  <div id="fs-listing" class="max-h-80 divide-y divide-base-200/70 overflow-auto"></div>
216
215
  </div>
217
216
  <p class="mt-2.5 text-xs text-base-content/45">
218
217
  Browse the filesystem to select an existing database; opening it adds it to the list above.
219
218
  {{#if browseRoot}}
220
219
  <span class="font-medium text-base-content/60">Browsing is limited to <code class="font-mono">{{browseRoot}}</code>.</span>
221
220
  {{/if}}
222
221
  </p>
223
222
  </div>
224
223
  {{/if}}
225
224
  <div class="rounded-xl border border-warning/30 bg-warning/[0.04] p-4 text-xs text-base-content/75 leading-relaxed">
226
225
  <div class="flex items-start gap-2.5">
227
226
  <span class="text-warning shrink-0 mt-0.5">
228
227
  {{{icon "warning" class="h-4 w-4"}}}
229
228
  </span>
230
229
  <div>
231
230
  <span class="font-bold text-warning">Security & Protection Notice:</span>
232
231
  AdminDB's native authentication provides basic single-user password protection. For production environments and internet-facing networks, <strong>it is entirely your responsibility to secure this interface</strong> by placing it behind your web application's own authentication system (e.g., custom Express/Next.js auth middleware, SSO/OAuth, NextAuth), a secure VPN, or an IP-whitelisted reverse proxy with HTTPS.
233
232
  </div>
234
233
  </div>
235
234
  </div>
235
+ <div class="space-y-6">
236
236
  <div class="page-header">
237
237
  <div class="page-header-row">
238
238
  <div>
239
239
  <div class="page-pretitle">Manager</div>
240
240
  <h1 class="page-title">Databases</h1>
241
241
  <p class="mt-0.5 text-xs text-base-content/60">Connect to PostgreSQL databases, create SQLite files, or open existing local databases with configurable read/write access.</p>
242
242
  </div>
243
243
  </div>
244
244
  </div>
245
245
  <div class="grid grid-cols-1 gap-5 lg:grid-cols-2">
246
246
  <div class="app-card p-5 border border-info/30 bg-info/[0.02]">
247
247
  <div class="mb-3 flex items-center justify-between">
248
248
  <h2 class="flex items-center gap-2 text-sm font-bold uppercase tracking-wide text-info">
249
249
  {{{icon "database" class="h-4 w-4"}}}
250
250
  Connect PostgreSQL
251
251
  </h2>
252
252
  <span class="rounded bg-info/15 px-2 py-0.5 text-[10px] font-bold text-info">Remote / Local</span>
253
253
  </div>
254
254
  <div class="space-y-3">
255
255
  <div>
256
256
  <label class="field-label" for="pg-conn-name">Connection Name <span class="text-base-content/40">(optional)</span></label>
257
257
  <input id="pg-conn-name" class="field-input font-mono text-xs" placeholder="e.g. prod_postgres" autocomplete="off" spellcheck="false" />
258
258
  </div>
259
259
  <div>
260
260
  <label class="field-label" for="pg-conn-uri">Connection URI <span class="chip chip-type">required</span></label>
261
261
  <input id="pg-conn-uri" class="field-input font-mono text-xs" placeholder="postgresql://user:password@localhost:5432/mydb" autocomplete="off" spellcheck="false" />
262
262
  </div>
263
263
  <div>
264
264
  <label class="flex items-center gap-2 cursor-pointer pt-0.5">
265
265
  <input type="checkbox" id="pg-conn-readonly" class="checkbox checkbox-xs checkbox-primary rounded" />
266
266
  <span class="text-xs font-medium text-base-content/75">Open as Read-only (block writes & schema changes)</span>
267
267
  </label>
268
268
  </div>
269
269
  <div class="pt-1">
270
270
  {{#unless readonly}}
271
271
  <button id="connect-pg" type="button" class="btn btn-info btn-sm gap-1.5 w-full sm:w-auto">
272
272
  {{{icon "plus" class="h-4 w-4"}}}
273
273
  Connect PostgreSQL
274
274
  </button>
275
275
  {{else}}
276
276
  <span class="text-xs font-medium text-warning">Read-only mode — connecting databases is disabled.</span>
277
277
  {{/unless}}
278
278
  </div>
279
279
  </div>
280
280
  <p class="mt-3 text-[11px] text-base-content/45 leading-relaxed">
281
281
  Credentials are encrypted in-memory and sanitized in the UI. Passwords are never sent to the client.
282
282
  </p>
283
283
  </div>
284
284
  <div class="app-card p-5 border border-emerald-500/30 bg-emerald-500/[0.02]">
285
285
  <div class="mb-3 flex items-center justify-between">
286
286
  <h2 class="flex items-center gap-2 text-sm font-bold uppercase tracking-wide text-emerald-600 dark:text-emerald-400">
287
287
  {{{icon "plus" class="h-4 w-4"}}}
288
288
  Create SQLite Database
289
289
  </h2>
290
290
  <span class="rounded bg-emerald-500/15 px-2 py-0.5 text-[10px] font-bold text-emerald-600 dark:text-emerald-400">File-based</span>
291
291
  </div>
292
292
  <div class="space-y-3">
293
293
  <div>
294
294
  <label class="field-label" for="new-db-name">Database Name <span class="chip chip-type">required</span></label>
295
295
  <input id="new-db-name" class="field-input font-mono text-xs" placeholder="e.g. analytics" autocomplete="off" spellcheck="false" />
296
296
  </div>
297
297
  <div class="pt-1">
298
298
  {{#unless readonly}}
299
299
  <button id="create-db" type="button" class="btn btn-primary btn-sm gap-1.5 w-full sm:w-auto">
300
300
  {{{icon "plus" class="h-4 w-4"}}}
301
301
  Create SQLite DB
302
302
  </button>
303
303
  {{else}}
304
304
  <span class="text-xs font-medium text-warning">Read-only — creating databases is disabled.</span>
305
305
  {{/unless}}
306
306
  </div>
307
307
  </div>
308
308
  <p class="mt-3 text-[11px] text-base-content/45 leading-relaxed">
309
309
  Files are saved to <code class="font-mono">{{dbDir}}</code>. Any <code class="font-mono">.db</code> file dropped there is auto-discovered.
310
310
  </p>
311
311
  </div>
312
312
  </div>
313
313
  <div>
314
314
  <div class="mb-3 flex items-center justify-between">
315
315
  <h2 class="text-lg font-bold">Available databases</h2>
316
316
  <span class="text-sm text-base-content/45">{{databases.length}} total</span>
317
317
  </div>
318
318
  {{#if databases.length}}
319
319
  <div class="app-card overflow-hidden">
320
320
  <div class="overflow-x-auto">
321
321
  <table class="table table-sm">
322
322
  <thead>
323
323
  <tr class="bg-base-200/70 text-xs uppercase tracking-wide text-base-content/50">
324
324
  <th class="font-semibold">Name</th>
325
325
  <th class="font-semibold">Mode</th>
326
326
  <th class="text-right font-semibold">Tables</th>
327
327
  <th class="text-right font-semibold">Size</th>
328
328
  <th class="font-semibold">Status / Modified</th>
329
329
  <th class="text-right font-semibold">Actions</th>
330
330
  </tr>
331
331
  </thead>
332
332
  <tbody>
333
333
  {{#each databases}}
334
334
  <tr class="group hover:bg-primary/[0.04]">
335
335
  <td>
336
336
  <a href="{{../basePath}}/home/{{encodeUri this.id}}" class="inline-flex items-center gap-2.5">
337
337
  <span class="grid h-8 w-8 shrink-0 place-items-center rounded-lg {{#if this.isPostgres}}bg-info/10 text-info group-hover:bg-info/20{{else}}bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 group-hover:bg-emerald-500/20{{/if}} transition-colors">
338
338
  {{{icon "database" class="h-4 w-4"}}}
339
339
  </span>
340
340
  <span class="min-w-0">
341
341
  <div class="flex items-center gap-1.5">
342
342
  <span class="truncate font-mono text-[13.5px] font-semibold">{{this.name}}</span>
343
343
  {{#if this.isPostgres}}
344
344
  <span class="rounded bg-info/15 px-1.5 py-0.2 text-[10px] font-bold text-info">PostgreSQL</span>
345
345
  {{else}}
346
346
  <span class="rounded bg-emerald-500/15 px-1.5 py-0.2 text-[10px] font-bold text-emerald-600 dark:text-emerald-400">SQLite</span>
347
347
  {{/if}}
348
348
  </div>
349
349
  <span class="block max-w-[260px] truncate font-mono text-[11px] text-base-content/40" title="{{this.path}}">{{this.path}}</span>
350
350
  </span>
351
351
  </a>
352
352
  </td>
353
353
  <td class="align-middle">
354
354
  {{#if ../readonly}}
355
355
  <span class="rounded bg-warning/20 px-2 py-0.5 text-[10px] font-bold uppercase text-warning">Read-only</span>
356
356
  {{else}}
357
357
  <button type="button" class="js-toggle-db-mode rounded px-2 py-0.5 text-[10px] font-bold uppercase transition hover:opacity-80 {{#if this.readonly}}bg-warning/20 text-warning{{else}}bg-success/20 text-success{{/if}}"
358
358
  data-id="{{this.id}}" data-readonly="{{#if this.readonly}}true{{else}}false{{/if}}" title="Click to toggle between Writable and Read-only">
359
359
  {{#if this.readonly}}Read-only{{else}}Writable{{/if}} ⇄
360
360
  </button>
361
361
  {{/if}}
362
362
  </td>
363
363
  <td class="text-right tabular-nums text-base-content/60">{{this.tables}}</td>
364
364
  <td class="text-right tabular-nums text-base-content/60">{{this.sizeLabel}}</td>
365
365
  <td class="text-[13px] text-base-content/55">{{this.modifiedLabel}}</td>
366
366
  <td class="whitespace-nowrap text-right">
367
367
  <div class="row-actions">
368
368
  <div class="join">
369
369
  <a href="{{../basePath}}/home/{{encodeUri this.id}}" class="btn btn-ghost btn-xs join-item gap-1 text-base-content/70 hover:text-primary">
370
370
  {{{icon "arrowRight" class="h-3.5 w-3.5"}}}
371
371
  Open
372
372
  </a>
373
373
  <div class="dropdown dropdown-end">
374
374
  <label tabindex="0" role="button" class="btn btn-ghost btn-xs join-item px-1 text-base-content/60 hover:text-base-content" title="Open options">
375
375
  {{{icon "chevronDown" class="h-3 w-3"}}}
376
376
  </label>
377
377
  <ul tabindex="0" class="dropdown-content menu z-50 w-44 rounded-xl border border-base-300 bg-base-100 p-1.5 shadow-lift text-xs">
378
378
  <li>
379
379
  <a href="{{../basePath}}/home/{{encodeUri this.id}}?readonly=0" class="flex items-center gap-1.5 text-success">
380
380
  {{{icon "check" class="h-3.5 w-3.5"}}}
381
381
  Open Writable
382
382
  </a>
383
383
  </li>
384
384
  <li>
385
385
  <a href="{{../basePath}}/home/{{encodeUri this.id}}?readonly=1" class="flex items-center gap-1.5 text-warning">
386
386
  {{{icon "lock" class="h-3.5 w-3.5"}}}
387
387
  Open Read-only
388
388
  </a>
389
389
  </li>
390
390
  </ul>
391
391
  </div>
392
392
  </div>
393
393
  {{#unless ../readonly}}
394
394
  {{#if this.isPostgres}}
395
395
  <button type="button" class="btn btn-ghost btn-xs gap-1 text-warning js-disconnect-db" data-id="{{this.id}}" data-name="{{this.name}}" title="Disconnect this database from the manager list">
396
396
  {{{icon "logout" class="h-3.5 w-3.5"}}}
397
397
  Disconnect
398
398
  </button>
399
399
  {{else}}
400
400
  <button type="button" class="btn btn-ghost btn-xs gap-1 text-error js-delete-db" data-id="{{this.id}}" title="Delete database file from disk">
401
401
  {{{icon "trash" class="h-3.5 w-3.5"}}}
402
402
  Delete
403
403
  </button>
404
404
  {{/if}}
405
405
  {{/unless}}
406
406
  </div>
407
407
  </td>
408
408
  </tr>
409
409
  {{/each}}
410
410
  </tbody>
411
411
  </table>
412
412
  </div>
413
413
  </div>
414
414
  {{else}}
415
415
  <div class="empty-state">
416
416
  <span class="empty-icon">{{{icon "database" class="h-7 w-7"}}}</span>
417
417
  <p class="text-sm font-medium text-base-content/70">No databases yet</p>
418
418
  <p class="max-w-sm text-sm text-base-content/45">
419
419
  Connect a PostgreSQL database or create a new SQLite database above to get started.
420
420
  </p>
421
421
  </div>
422
422
  {{/if}}
423
423
  </div>
424
424
  {{#if allowBrowse}}
425
425
  <div class="app-card p-5">
426
426
  <div class="mb-3 flex flex-wrap items-center justify-between gap-2">
427
427
  <h2 class="text-sm font-semibold uppercase tracking-wide text-base-content/45">Open an existing SQLite file</h2>
428
428
  <span class="text-xs text-base-content/40">.db · .sqlite · .sqlite3</span>
429
429
  </div>
430
430
  <div class="flex flex-wrap items-center gap-2">
431
431
  <input id="fs-path" class="field-input font-mono sm:!w-96" placeholder="Absolute path to a folder, e.g. C:\data" autocomplete="off" spellcheck="false" />
432
432
  <button id="fs-go" type="button" class="btn btn-outline btn-sm gap-1.5">
433
433
  {{{icon "search" class="h-4 w-4"}}}
434
434
  Go
435
435
  </button>
436
436
  <button id="fs-up" type="button" class="btn btn-ghost btn-sm gap-1.5">
437
437
  {{{icon "arrowUp" class="h-4 w-4"}}}
438
438
  Up
439
439
  </button>
440
440
  </div>
441
441
  <div class="mt-2">
442
442
  <label class="flex items-center gap-2 cursor-pointer">
443
443
  <input type="checkbox" id="fs-readonly" class="checkbox checkbox-xs checkbox-primary rounded" />
444
444
  <span class="text-xs font-medium text-base-content/70">Open selected file in Read-only mode</span>
445
445
  </label>
446
446
  </div>
447
447
  <div class="mt-3 overflow-hidden rounded-xl border border-base-300">
448
448
  <div id="fs-current" class="border-b border-base-200 bg-base-200/50 px-3 py-2 font-mono text-xs text-base-content/60"></div>
449
449
  <div id="fs-listing" class="max-h-80 divide-y divide-base-200/70 overflow-auto"></div>
450
450
  </div>
451
451
  <p class="mt-2.5 text-xs text-base-content/45">
452
452
  Browse the filesystem to select an existing database; opening it adds it to the list above.
453
453
  {{#if browseRoot}}
454
454
  <span class="font-medium text-base-content/60">Browsing is limited to <code class="font-mono">{{browseRoot}}</code>.</span>
455
455
  {{/if}}
456
456
  </p>
457
457
  </div>
458
458
  {{/if}}
459
459
  <div class="rounded-xl border border-warning/30 bg-warning/[0.04] p-4 text-xs text-base-content/75 leading-relaxed">
460
460
  <div class="flex items-start gap-2.5">
461
461
  <span class="text-warning shrink-0 mt-0.5">
462
462
  {{{icon "warning" class="h-4 w-4"}}}
463
463
  </span>
464
464
  <div>
465
465
  <span class="font-bold text-warning">Security & Protection Notice:</span>
466
466
  AdminDB's native authentication provides basic single-user password protection. For production environments and internet-facing networks, <strong>it is entirely your responsibility to secure this interface</strong> by placing it behind your web application's own authentication system (e.g., custom Express/Next.js auth middleware, SSO/OAuth, NextAuth), a secure VPN, or an IP-whitelisted reverse proxy with HTTPS.
467
467
  </div>
468
468
  </div>
469
469
  </div>