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.
- package/README.md +368 -364
- package/dist/app.js +25 -16
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +18 -30
- package/dist/auth/crypto.js +11 -21
- package/dist/auth/middleware.js +9 -16
- package/dist/auth/routes.js +9 -15
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.js +20 -29
- package/dist/cli/config.d.ts +1 -1
- package/dist/cli/config.js +15 -18
- package/dist/cli/runner.js +13 -13
- package/dist/data/chain.d.ts +1 -1
- package/dist/data/chain.js +40 -617
- package/dist/data/datasets.d.ts +1 -1
- package/dist/data/datasets.js +43 -1
- package/dist/data/detector.js +45 -21
- package/dist/data/engine.d.ts +1 -1
- package/dist/data/engine.js +483 -142
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.js +8 -0
- package/dist/data/prng.d.ts +1 -0
- package/dist/data/prng.js +73 -0
- package/dist/data/registry.d.ts +1 -0
- package/dist/data/registry.js +805 -0
- package/dist/data/schema-graph.d.ts +1 -0
- package/dist/data/schema-graph.js +210 -0
- package/dist/data/strategies.d.ts +1 -1
- package/dist/data/strategies.js +44 -283
- package/dist/data/templates.d.ts +1 -0
- package/dist/data/templates.js +134 -0
- package/dist/data/types.d.ts +1 -1
- package/dist/data/validator.d.ts +1 -0
- package/dist/data/validator.js +160 -0
- package/dist/db/database.js +9 -14
- package/dist/db/export.js +32 -55
- package/dist/db/filters.d.ts +1 -1
- package/dist/db/filters.js +15 -38
- package/dist/db/introspection.js +4 -7
- package/dist/db/manager.d.ts +1 -1
- package/dist/db/manager.js +48 -53
- package/dist/db/migrations.js +7 -14
- package/dist/db/postgres.js +30 -78
- package/dist/index.d.ts +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.js +3 -8
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/helpers.js +75 -25
- package/dist/routes/api/import-export.js +1 -2
- package/dist/routes/api/query.js +2 -6
- package/dist/routes/api/rows.js +4 -8
- package/dist/routes/api/seed.js +35 -21
- package/dist/routes/api/tables.js +15 -30
- package/dist/routes/databases.js +8 -26
- package/dist/routes/pages.js +67 -18
- package/dist/serverless.js +31 -44
- package/dist/sql/generator.d.ts +1 -1
- package/dist/sql/generator.js +35 -109
- package/dist/types/api.d.ts +1 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +45 -85
- package/dist/utils/csv.d.ts +1 -1
- package/dist/utils/csv.js +11 -17
- package/dist/utils/datatype.js +46 -60
- package/dist/utils/icons.js +8 -8
- package/dist/views/layouts/main.hbs +1 -87
- package/dist/views/pages/databases.hbs +1 -233
- package/dist/views/pages/designer.hbs +1 -89
- package/dist/views/pages/erd.hbs +1 -1241
- package/dist/views/pages/error.hbs +1 -10
- package/dist/views/pages/form.hbs +1 -59
- package/dist/views/pages/home.hbs +1 -220
- package/dist/views/pages/info.hbs +1 -107
- package/dist/views/pages/login.hbs +1 -98
- package/dist/views/pages/query.hbs +1 -129
- package/dist/views/pages/schema.hbs +1 -298
- package/dist/views/pages/seed-select.hbs +251 -0
- package/dist/views/pages/seed.hbs +256 -414
- package/dist/views/pages/table.hbs +1 -327
- package/dist/views/partials/navbar.hbs +1 -83
- package/dist/views/partials/sidebar.hbs +1 -129
- package/docs/API.md +149 -149
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- package/package.json +80 -79
|
@@ -1,327 +1 @@
|
|
|
1
|
-
<div class="space-y-5">
|
|
2
|
-
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
3
|
-
<div class="flex items-center gap-3.5">
|
|
4
|
-
<span class="grid h-12 w-12 place-items-center rounded-xl bg-primary/10 text-primary">
|
|
5
|
-
{{{icon "table" class="h-6 w-6"}}}
|
|
6
|
-
</span>
|
|
7
|
-
<div>
|
|
8
|
-
<div class="flex items-center gap-2">
|
|
9
|
-
<h1 class="font-mono text-2xl font-bold tracking-tight">{{table}}</h1>
|
|
10
|
-
{{#if isPostgres}}
|
|
11
|
-
<span class="rounded bg-info/15 px-2 py-0.5 text-[11px] font-bold font-sans text-info">PostgreSQL</span>
|
|
12
|
-
{{else}}
|
|
13
|
-
<span class="rounded bg-emerald-500/15 px-2 py-0.5 text-[11px] font-bold font-sans text-emerald-600 dark:text-emerald-400">SQLite</span>
|
|
14
|
-
{{/if}}
|
|
15
|
-
</div>
|
|
16
|
-
<p class="text-sm text-base-content/55">
|
|
17
|
-
{{count}} row(s) · {{info.columns.length}} column(s)
|
|
18
|
-
{{#if pkCols.length}}· PK <span class="font-mono">{{join pkCols ", "}}</span>{{/if}}
|
|
19
|
-
</p>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
<div class="flex flex-wrap gap-2">
|
|
23
|
-
<a href="{{basePath}}/tables/{{table}}/schema" class="btn btn-ghost btn-sm gap-1.5">
|
|
24
|
-
{{{icon "schema" class="h-4 w-4"}}}
|
|
25
|
-
Edit schema
|
|
26
|
-
</a>
|
|
27
|
-
<a href="{{basePath}}/export" class="btn btn-ghost btn-sm gap-1.5">
|
|
28
|
-
{{{icon "export" class="h-4 w-4"}}}
|
|
29
|
-
SQL dump
|
|
30
|
-
</a>
|
|
31
|
-
<div class="dropdown dropdown-end">
|
|
32
|
-
<div tabindex="0" role="button" class="btn btn-outline btn-sm gap-1.5">
|
|
33
|
-
{{{icon "export" class="h-4 w-4"}}}
|
|
34
|
-
Export
|
|
35
|
-
{{{icon "chevronDown" class="h-3.5 w-3.5"}}}
|
|
36
|
-
</div>
|
|
37
|
-
<ul tabindex="0" class="dropdown-content menu z-50 w-40 rounded-xl border border-base-300 bg-base-100 p-1.5 shadow-lift">
|
|
38
|
-
<li><a href="{{basePath}}/api/tables/{{table}}/export?format=csv">CSV</a></li>
|
|
39
|
-
<li><a href="{{basePath}}/api/tables/{{table}}/export?format=json">JSON</a></li>
|
|
40
|
-
</ul>
|
|
41
|
-
</div>
|
|
42
|
-
{{#unless readonly}}
|
|
43
|
-
<button id="import-csv" type="button" class="btn btn-outline btn-sm gap-1.5">
|
|
44
|
-
{{{icon "import" class="h-4 w-4"}}}
|
|
45
|
-
Import CSV
|
|
46
|
-
</button>
|
|
47
|
-
{{/unless}}
|
|
48
|
-
<a href="{{basePath}}/tables/{{table}}/seed" class="btn btn-outline btn-sm gap-1.5">
|
|
49
|
-
{{{icon "seed" class="h-4 w-4"}}}
|
|
50
|
-
Seed data
|
|
51
|
-
</a>
|
|
52
|
-
{{#unless readonly}}
|
|
53
|
-
<a href="{{basePath}}/tables/{{table}}/rows/new" class="btn btn-primary btn-sm gap-1.5">
|
|
54
|
-
{{{icon "plus" class="h-4 w-4"}}}
|
|
55
|
-
New row
|
|
56
|
-
</a>
|
|
57
|
-
{{/unless}}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
<details class="app-card overflow-hidden" {{#if filterQS}}open{{/if}}>
|
|
61
|
-
<summary class="group flex cursor-pointer list-none items-center justify-between gap-2 px-4 py-3 transition hover:bg-base-200/40 [&::-webkit-details-marker]:hidden">
|
|
62
|
-
<span class="flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-base-content/45">
|
|
63
|
-
{{{icon "filter" class="h-4 w-4"}}}
|
|
64
|
-
Filter rows
|
|
65
|
-
{{#if filterQS}}<span class="chip chip-pk">active</span>{{/if}}
|
|
66
|
-
</span>
|
|
67
|
-
<span class="transition-transform group-open:rotate-180 text-base-content/40">
|
|
68
|
-
{{{icon "chevronDown" class="h-4 w-4"}}}
|
|
69
|
-
</span>
|
|
70
|
-
</summary>
|
|
71
|
-
<form id="filter-form" class="border-t border-base-200 p-4">
|
|
72
|
-
<p class="mb-3 text-xs text-base-content/40">Filters adapt to each column's type: foreign keys, booleans, dates and numbers get dedicated controls. Text columns keep the operator syntax — <code>=exact</code> · <code>>5</code> · <code>pre*</code> starts with · plain text = contains.</p>
|
|
73
|
-
<div id="filter-fields" class="grid grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-4"></div>
|
|
74
|
-
<div class="mt-3 flex flex-wrap items-center gap-2">
|
|
75
|
-
<button type="submit" class="btn btn-primary btn-sm gap-1.5">
|
|
76
|
-
{{{icon "search" class="h-4 w-4"}}}
|
|
77
|
-
Apply filter
|
|
78
|
-
</button>
|
|
79
|
-
<button type="button" id="clear-filter" class="btn btn-ghost btn-sm">Clear</button>
|
|
80
|
-
{{#if filterQS}}
|
|
81
|
-
<span class="text-xs text-base-content/50">Filtering <b class="tabular-nums">{{count}}</b> row(s).</span>
|
|
82
|
-
{{/if}}
|
|
83
|
-
</div>
|
|
84
|
-
</form>
|
|
85
|
-
</details>
|
|
86
|
-
<div id="import-panel" class="app-card hidden p-4">
|
|
87
|
-
<div class="mb-3 flex items-center justify-between">
|
|
88
|
-
<h2 class="text-sm font-semibold uppercase tracking-wide text-base-content/45">Import CSV into <span class="font-mono normal-case">{{table}}</span></h2>
|
|
89
|
-
<button id="import-csv-close" type="button" class="btn btn-ghost btn-xs" aria-label="Close">×</button>
|
|
90
|
-
</div>
|
|
91
|
-
<div class="space-y-3">
|
|
92
|
-
<textarea id="import-csv-text" rows="6" class="field-input w-full font-mono text-[13px]" placeholder="id,name,email 1,Ada,ada@example.com 2,Bob,bob@example.com" spellcheck="false"></textarea>
|
|
93
|
-
<div class="flex flex-wrap items-center gap-3">
|
|
94
|
-
<input type="file" id="import-csv-file" class="file-input file-input-sm w-full max-w-xs" accept=".csv,text/csv" />
|
|
95
|
-
<button id="import-csv-submit" type="button" class="btn btn-primary btn-sm gap-1.5">
|
|
96
|
-
{{{icon "import" class="h-4 w-4"}}}
|
|
97
|
-
Import
|
|
98
|
-
</button>
|
|
99
|
-
</div>
|
|
100
|
-
<p class="text-xs text-base-content/45">The first row must be column names matching this table. Empty cells are treated as “not set” so database defaults apply. The import is transactional — a failed row rolls back everything.</p>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
{{#if rows.length}}
|
|
104
|
-
<div class="app-card overflow-hidden">
|
|
105
|
-
<div class="flex flex-wrap items-center justify-between gap-2 border-b border-base-200 px-4 py-2.5">
|
|
106
|
-
<div class="flex flex-wrap items-center gap-3">
|
|
107
|
-
<span class="text-sm text-base-content/65">Showing <b class="tabular-nums font-bold text-base-content">{{firstRow}}</b>–<b class="tabular-nums font-bold text-base-content">{{lastRow}}</b> of <b class="tabular-nums font-bold text-base-content">{{count}}</b> rows</span>
|
|
108
|
-
<span class="hidden xl:inline-flex items-center gap-1.5 text-xs text-base-content/40 font-medium">
|
|
109
|
-
<kbd class="kbd kbd-xs font-mono">Dbl-click</kbd> edit · <kbd class="kbd kbd-xs font-mono">↑↓←→</kbd> move · <kbd class="kbd kbd-xs font-mono">Space</kbd> toggle · <kbd class="kbd kbd-xs font-mono">Esc</kbd> cancel
|
|
110
|
-
</span>
|
|
111
|
-
</div>
|
|
112
|
-
<div class="flex flex-wrap items-center gap-2.5">
|
|
113
|
-
<label class="flex items-center gap-2 text-xs text-base-content/55">
|
|
114
|
-
Per page
|
|
115
|
-
<select id="page-size" class="field-select !w-auto !py-1 !text-xs" data-table="{{table}}" data-orderby="{{orderBy}}" data-orderdir="{{orderDir}}" data-filters="{{filterQS}}">
|
|
116
|
-
{{#each sizes}}<option value="{{this}}" {{#if (eq this ../pageSize)}}selected{{/if}}>{{this}}</option>{{/each}}
|
|
117
|
-
</select>
|
|
118
|
-
</label>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
<div id="bulk-bar" class="hidden items-center justify-between gap-2 border-b border-base-200 bg-primary/[0.04] px-4 py-2">
|
|
122
|
-
<span class="text-sm font-medium text-base-content/80">
|
|
123
|
-
<b id="bulk-count" class="tabular-nums text-primary">0</b> row(s) selected
|
|
124
|
-
</span>
|
|
125
|
-
<div class="flex flex-wrap items-center gap-1.5">
|
|
126
|
-
<button id="bulk-export-csv" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
|
|
127
|
-
{{{icon "export" class="h-3.5 w-3.5"}}}
|
|
128
|
-
Export CSV
|
|
129
|
-
</button>
|
|
130
|
-
<button id="bulk-export-json" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
|
|
131
|
-
{{{icon "export" class="h-3.5 w-3.5"}}}
|
|
132
|
-
Export JSON
|
|
133
|
-
</button>
|
|
134
|
-
<button id="bulk-delete" type="button" class="btn btn-error btn-xs gap-1" {{#if readonly}}disabled{{/if}}>
|
|
135
|
-
{{{icon "trash" class="h-3.5 w-3.5"}}}
|
|
136
|
-
Delete
|
|
137
|
-
</button>
|
|
138
|
-
<button id="bulk-clear" type="button" class="btn btn-ghost btn-xs text-base-content/60">Clear</button>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
<div class="overflow-x-auto">
|
|
142
|
-
<table id="main-table" class="table table-compact table-pin-rows">
|
|
143
|
-
<thead>
|
|
144
|
-
<tr class="bg-base-200/70 text-xs uppercase tracking-wide text-base-content/50">
|
|
145
|
-
<th class="w-10 text-center font-semibold">
|
|
146
|
-
<input type="checkbox" id="select-all-rows" class="checkbox checkbox-primary checkbox-xs" title="Select all rows on this page" />
|
|
147
|
-
</th>
|
|
148
|
-
{{#each colHeaders}}
|
|
149
|
-
<th class="whitespace-nowrap font-semibold">
|
|
150
|
-
<a href="{{this.href}}" class="sort-link {{#if this.active}}active{{/if}}" title="Sort by {{this.name}}">
|
|
151
|
-
<span class="font-mono normal-case">{{this.name}}</span>
|
|
152
|
-
{{#if this.pk}}<span class="chip chip-pk">PK</span>{{/if}}
|
|
153
|
-
<span class="sort-arrow">{{#if (eq this.dir 'desc')}}↓{{else}}↑{{/if}}</span>
|
|
154
|
-
</a>
|
|
155
|
-
</th>
|
|
156
|
-
{{/each}}
|
|
157
|
-
<th class="text-right font-semibold sticky-col-actions">Actions</th>
|
|
158
|
-
{{#each refColumns}}
|
|
159
|
-
<th class="whitespace-nowrap text-right font-semibold" title="Rows in {{this.table}} where {{this.from}} references {{this.to}}">
|
|
160
|
-
<span class="font-mono normal-case">{{this.table}}</span>
|
|
161
|
-
</th>
|
|
162
|
-
{{/each}}
|
|
163
|
-
</tr>
|
|
164
|
-
</thead>
|
|
165
|
-
<tbody>
|
|
166
|
-
{{#each rows}}
|
|
167
|
-
<tr class="group hover:bg-primary/[0.04]" data-table="{{@root.table}}" data-pk="{{this.pkEncoded}}">
|
|
168
|
-
<td class="w-10 text-center align-middle">
|
|
169
|
-
{{#if this.pkEncoded}}
|
|
170
|
-
<input type="checkbox" class="row-checkbox checkbox checkbox-primary checkbox-xs" value="{{this.pkEncoded}}" aria-label="Select this row" />
|
|
171
|
-
{{/if}}
|
|
172
|
-
</td>
|
|
173
|
-
{{#each this.cells}}
|
|
174
|
-
<td class="max-w-xs align-middle cell-editable {{#unless @root.readonly}}js-inline-cell{{/unless}}"
|
|
175
|
-
data-col="{{this.name}}" data-null="{{this.isNull}}" data-value="{{this.display}}"
|
|
176
|
-
data-blob="{{this.isBlob}}" data-json="{{this.isJson}}" data-type="{{this.type}}">
|
|
177
|
-
{{#if this.isNull}}
|
|
178
|
-
<span class="chip chip-null js-cell-value">NULL</span>
|
|
179
|
-
{{else if this.isImage}}
|
|
180
|
-
<div class="flex items-center gap-2 js-cell-value">
|
|
181
|
-
<img src="{{this.blobUrl}}" alt="{{this.name}}" class="h-7 max-w-12 rounded object-cover border border-base-300 bg-base-200 shadow-xs cursor-pointer js-inspect-btn" title="Inspect Image" />
|
|
182
|
-
<span class="chip chip-type text-[9px] font-mono">{{this.blobExt}} · {{this.blobSize}}</span>
|
|
183
|
-
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Inspect BLOB">
|
|
184
|
-
{{{icon "inspect" class="h-3 w-3"}}}
|
|
185
|
-
</button>
|
|
186
|
-
</div>
|
|
187
|
-
{{else if this.isBlob}}
|
|
188
|
-
<div class="flex items-center gap-1.5 js-cell-value">
|
|
189
|
-
<span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary">🗃️ BLOB · {{this.blobSize}}</span>
|
|
190
|
-
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Inspect BLOB">
|
|
191
|
-
{{{icon "inspect" class="h-3 w-3"}}}
|
|
192
|
-
</button>
|
|
193
|
-
</div>
|
|
194
|
-
{{else if this.isJson}}
|
|
195
|
-
<div class="flex items-center gap-1.5 js-cell-value">
|
|
196
|
-
<span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info">{ } JSON</span>
|
|
197
|
-
<span class="block max-w-[140px] truncate font-mono text-[12px] text-base-content/70" title="{{this.display}}">{{this.display}}</span>
|
|
198
|
-
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Inspect JSON">
|
|
199
|
-
{{{icon "inspect" class="h-3 w-3"}}}
|
|
200
|
-
</button>
|
|
201
|
-
</div>
|
|
202
|
-
{{else if this.isColor}}
|
|
203
|
-
<div class="flex items-center gap-1.5 js-cell-value">
|
|
204
|
-
<span class="inline-block h-3.5 w-3.5 rounded-full border border-base-300 shadow-xs shrink-0" style="background-color: {{this.display}}"></span>
|
|
205
|
-
<span class="font-mono text-[13px]">{{this.display}}</span>
|
|
206
|
-
</div>
|
|
207
|
-
{{else if this.isUrl}}
|
|
208
|
-
<div class="flex items-center gap-1.5 js-cell-value">
|
|
209
|
-
<span class="block max-w-xs truncate font-mono text-[13px] text-primary underline" title="{{this.display}}">{{this.display}}</span>
|
|
210
|
-
<a href="{{this.display}}" target="_blank" rel="noopener" class="text-base-content/40 hover:text-primary" title="Open link">↗</a>
|
|
211
|
-
</div>
|
|
212
|
-
{{else}}
|
|
213
|
-
<div class="flex items-center justify-between gap-1 group/cell">
|
|
214
|
-
<span class="js-cell-value block max-w-xs truncate font-mono text-[13px]" title="{{this.display}}">{{truncate this.display 120}}</span>
|
|
215
|
-
{{#if (gt this.display.length 30)}}
|
|
216
|
-
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-inspect-btn shrink-0" title="Inspect text">
|
|
217
|
-
{{{icon "inspect" class="h-3 w-3"}}}
|
|
218
|
-
</button>
|
|
219
|
-
{{/if}}
|
|
220
|
-
</div>
|
|
221
|
-
{{/if}}
|
|
222
|
-
</td>
|
|
223
|
-
{{/each}}
|
|
224
|
-
<td class="whitespace-nowrap text-right align-middle sticky-col-actions">
|
|
225
|
-
{{#if this.pkEncoded}}
|
|
226
|
-
<div class="row-actions">
|
|
227
|
-
{{#unless @root.readonly}}
|
|
228
|
-
<a href="{{@root.basePath}}/tables/{{@root.table}}/rows/{{this.pkEncoded}}/edit" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary" title="Edit row">
|
|
229
|
-
{{{icon "edit" class="h-3.5 w-3.5"}}}
|
|
230
|
-
Edit
|
|
231
|
-
</a>
|
|
232
|
-
{{/unless}}
|
|
233
|
-
<div class="dropdown dropdown-end">
|
|
234
|
-
<label tabindex="0" role="button" class="btn btn-ghost btn-xs btn-square text-base-content/60 hover:text-base-content" title="Row actions">
|
|
235
|
-
{{{icon "dotsVertical" class="h-3.5 w-3.5"}}}
|
|
236
|
-
</label>
|
|
237
|
-
<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">
|
|
238
|
-
<li>
|
|
239
|
-
<button type="button" class="js-row-copy-json flex items-center gap-2" data-pk="{{this.pkEncoded}}">
|
|
240
|
-
{{{icon "copy" class="h-3.5 w-3.5 text-base-content/60"}}}
|
|
241
|
-
Copy as JSON
|
|
242
|
-
</button>
|
|
243
|
-
</li>
|
|
244
|
-
<li>
|
|
245
|
-
<button type="button" class="js-row-copy-sql flex items-center gap-2" data-pk="{{this.pkEncoded}}">
|
|
246
|
-
{{{icon "sql" class="h-3.5 w-3.5 text-base-content/60"}}}
|
|
247
|
-
Copy SQL INSERT
|
|
248
|
-
</button>
|
|
249
|
-
</li>
|
|
250
|
-
{{#unless @root.readonly}}
|
|
251
|
-
<li>
|
|
252
|
-
<button type="button" class="js-row-duplicate flex items-center gap-2" data-pk="{{this.pkEncoded}}">
|
|
253
|
-
{{{icon "duplicate" class="h-3.5 w-3.5 text-base-content/60"}}}
|
|
254
|
-
Duplicate Row
|
|
255
|
-
</button>
|
|
256
|
-
</li>
|
|
257
|
-
<div class="divider my-1"></div>
|
|
258
|
-
<li>
|
|
259
|
-
<button type="button" class="js-delete-row flex items-center gap-2 text-error hover:bg-error/10" data-table="{{@root.table}}" data-pk="{{this.pkEncoded}}">
|
|
260
|
-
{{{icon "trash" class="h-3.5 w-3.5"}}}
|
|
261
|
-
Delete Row
|
|
262
|
-
</button>
|
|
263
|
-
</li>
|
|
264
|
-
{{/unless}}
|
|
265
|
-
</ul>
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
{{else}}
|
|
269
|
-
<span class="text-xs text-base-content/35">No PK</span>
|
|
270
|
-
{{/if}}
|
|
271
|
-
</td>
|
|
272
|
-
{{#each this.refs}}
|
|
273
|
-
<td class="whitespace-nowrap text-right align-middle">
|
|
274
|
-
{{#if this.value}}
|
|
275
|
-
<button type="button" class="js-ref-toggle inline-flex items-center gap-1.5 rounded-lg border border-base-300 bg-base-100 px-2 py-1 text-xs font-medium text-base-content/70 shadow-soft transition hover:border-primary/50 hover:text-primary {{#unless this.count}}opacity-50{{/unless}}"
|
|
276
|
-
data-table="{{@root.table}}" data-pk="{{../pkEncoded}}" data-ref-table="{{this.table}}"
|
|
277
|
-
aria-expanded="false" title="View {{this.table}} rows where {{this.from}} = {{this.value}}">
|
|
278
|
-
<span class="font-mono normal-case">{{this.table}}</span>
|
|
279
|
-
<span class="rounded-md bg-primary/10 px-1.5 py-0.5 font-semibold tabular-nums text-primary">{{this.count}}</span>
|
|
280
|
-
<span class="js-ref-chevron transition-transform inline-block">
|
|
281
|
-
{{{icon "chevronDown" class="h-3 w-3"}}}
|
|
282
|
-
</span>
|
|
283
|
-
</button>
|
|
284
|
-
{{else}}
|
|
285
|
-
<span class="text-xs text-base-content/35">—</span>
|
|
286
|
-
{{/if}}
|
|
287
|
-
</td>
|
|
288
|
-
{{/each}}
|
|
289
|
-
</tr>
|
|
290
|
-
{{/each}}
|
|
291
|
-
</tbody>
|
|
292
|
-
</table>
|
|
293
|
-
</div>
|
|
294
|
-
{{#if (gt pages 1)}}
|
|
295
|
-
<div class="flex flex-wrap items-center justify-between gap-2 border-t border-base-200 px-4 py-3">
|
|
296
|
-
<span class="text-sm text-base-content/50">Page {{page}} of {{pages}}</span>
|
|
297
|
-
<div class="flex gap-1.5">
|
|
298
|
-
<a href="{{basePath}}/tables/{{table}}?page={{sub page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
|
|
299
|
-
class="btn btn-outline btn-sm {{#unless (gt page 1)}}btn-disabled{{/unless}}" aria-disabled="{{#if (gt page 1)}}false{{else}}true{{/if}}">
|
|
300
|
-
{{{icon "chevronLeft" class="h-4 w-4"}}}
|
|
301
|
-
Prev
|
|
302
|
-
</a>
|
|
303
|
-
<a href="{{basePath}}/tables/{{table}}?page={{add page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
|
|
304
|
-
class="btn btn-outline btn-sm {{#unless (lt page pages)}}btn-disabled{{/unless}}" aria-disabled="{{#if (lt page pages)}}false{{else}}true{{/if}}">
|
|
305
|
-
Next
|
|
306
|
-
{{{icon "chevronRight" class="h-4 w-4"}}}
|
|
307
|
-
</a>
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
{{/if}}
|
|
311
|
-
</div>
|
|
312
|
-
{{else}}
|
|
313
|
-
<div class="empty-state">
|
|
314
|
-
<span class="empty-icon">{{{icon "book" class="h-7 w-7"}}}</span>
|
|
315
|
-
<p class="text-sm font-medium text-base-content/70">No rows in <span class="font-mono">{{table}}</span></p>
|
|
316
|
-
<div class="mt-2 flex gap-2">
|
|
317
|
-
{{#unless readonly}}
|
|
318
|
-
<a href="{{basePath}}/tables/{{table}}/rows/new" class="btn btn-primary btn-sm">Insert a row</a>
|
|
319
|
-
<a href="{{basePath}}/tables/{{table}}/seed" class="btn btn-outline btn-sm">Generate sample data</a>
|
|
320
|
-
{{/unless}}
|
|
321
|
-
<a href="{{basePath}}/query" class="btn btn-outline btn-sm">Run a query</a>
|
|
322
|
-
</div>
|
|
323
|
-
</div>
|
|
324
|
-
{{/if}}
|
|
325
|
-
</div>
|
|
326
|
-
<script type="application/json" id="browse-config">{{{json browseConfig}}}</script>
|
|
327
|
-
<script src="{{basePath}}/js/browse.js" defer></script>
|
|
1
|
+
<div class="space-y-5">
|
|
328
2
|
<div class="page-header">
|
|
329
3
|
<div class="page-header-row flex-wrap lg:flex-nowrap">
|
|
330
4
|
<div class="flex items-center gap-3.5">
|
|
331
5
|
<span class="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-primary/10 text-primary border border-primary/20">
|
|
332
6
|
{{{icon "table" class="h-5 w-5"}}}
|
|
333
7
|
</span>
|
|
334
8
|
<div>
|
|
335
9
|
<div class="page-pretitle">Table Browser</div>
|
|
336
10
|
<div class="flex items-center gap-2">
|
|
337
11
|
<h1 class="page-title font-mono text-xl sm:text-2xl">{{table}}</h1>
|
|
338
12
|
{{#if isPostgres}}
|
|
339
13
|
<span class="rounded bg-info/15 px-2 py-0.5 text-[10px] font-bold font-sans text-info border border-info/20">PostgreSQL</span>
|
|
340
14
|
{{else}}
|
|
341
15
|
<span class="rounded bg-emerald-500/15 px-2 py-0.5 text-[10px] font-bold font-sans text-emerald-600 dark:text-emerald-400 border border-emerald-500/20">SQLite</span>
|
|
342
16
|
{{/if}}
|
|
343
17
|
</div>
|
|
344
18
|
<p class="text-xs text-base-content/55 mt-0.5">
|
|
345
19
|
{{count}} row(s) · {{info.columns.length}} column(s)
|
|
346
20
|
{{#if pkCols.length}}· PK <span class="font-mono font-semibold text-primary">{{join pkCols ", "}}</span>{{/if}}
|
|
347
21
|
</p>
|
|
348
22
|
</div>
|
|
349
23
|
</div>
|
|
350
24
|
<div class="page-actions flex-wrap">
|
|
351
25
|
<a href="{{basePath}}/tables/{{table}}/schema" class="btn btn-ghost btn-sm gap-1.5">
|
|
352
26
|
{{{icon "schema" class="h-4 w-4"}}}
|
|
353
27
|
Edit schema
|
|
354
28
|
</a>
|
|
355
29
|
<a href="{{basePath}}/export" class="btn btn-ghost btn-sm gap-1.5">
|
|
356
30
|
{{{icon "export" class="h-4 w-4"}}}
|
|
357
31
|
SQL dump
|
|
358
32
|
</a>
|
|
359
33
|
<div class="dropdown dropdown-end">
|
|
360
34
|
<div tabindex="0" role="button" class="btn btn-outline btn-sm gap-1.5">
|
|
361
35
|
{{{icon "export" class="h-4 w-4"}}}
|
|
362
36
|
Export
|
|
363
37
|
{{{icon "chevronDown" class="h-3.5 w-3.5"}}}
|
|
364
38
|
</div>
|
|
365
39
|
<ul tabindex="0" class="dropdown-content menu z-50 w-40 rounded-xl border border-base-300 bg-base-100 p-1.5 shadow-lift">
|
|
366
40
|
<li><a href="{{basePath}}/api/tables/{{table}}/export?format=csv">CSV</a></li>
|
|
367
41
|
<li><a href="{{basePath}}/api/tables/{{table}}/export?format=json">JSON</a></li>
|
|
368
42
|
</ul>
|
|
369
43
|
</div>
|
|
370
44
|
{{#unless readonly}}
|
|
371
45
|
<button id="import-csv" type="button" class="btn btn-outline btn-sm gap-1.5">
|
|
372
46
|
{{{icon "import" class="h-4 w-4"}}}
|
|
373
47
|
Import CSV
|
|
374
48
|
</button>
|
|
375
49
|
{{/unless}}
|
|
376
50
|
<a href="{{basePath}}/seed/{{table}}" class="btn btn-outline btn-sm gap-1.5">
|
|
377
51
|
{{{icon "seed" class="h-4 w-4"}}}
|
|
378
52
|
Seed data
|
|
379
53
|
</a>
|
|
380
54
|
{{#unless readonly}}
|
|
381
55
|
<a href="{{basePath}}/tables/{{table}}/rows/new" class="btn btn-primary btn-sm gap-1.5">
|
|
382
56
|
{{{icon "plus" class="h-4 w-4"}}}
|
|
383
57
|
New row
|
|
384
58
|
</a>
|
|
385
59
|
{{/unless}}
|
|
386
60
|
</div>
|
|
387
61
|
</div>
|
|
388
62
|
</div>
|
|
389
63
|
<details class="app-card overflow-hidden" {{#if filterQS}}open{{/if}}>
|
|
390
64
|
<summary class="group flex cursor-pointer list-none items-center justify-between gap-2 px-4 py-3 transition hover:bg-base-200/40 [&::-webkit-details-marker]:hidden">
|
|
391
65
|
<span class="flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-base-content/45">
|
|
392
66
|
{{{icon "filter" class="h-4 w-4"}}}
|
|
393
67
|
Filter rows
|
|
394
68
|
{{#if filterQS}}<span class="chip chip-pk">active</span>{{/if}}
|
|
395
69
|
</span>
|
|
396
70
|
<span class="transition-transform group-open:rotate-180 text-base-content/40">
|
|
397
71
|
{{{icon "chevronDown" class="h-4 w-4"}}}
|
|
398
72
|
</span>
|
|
399
73
|
</summary>
|
|
400
74
|
<form id="filter-form" class="border-t border-base-200 p-4">
|
|
401
75
|
<p class="mb-3 text-xs text-base-content/40">Filters adapt to each column's type: foreign keys, booleans, dates and numbers get dedicated controls. Text columns keep the operator syntax — <code>=exact</code> · <code>>5</code> · <code>pre*</code> starts with · plain text = contains.</p>
|
|
402
76
|
<div id="filter-fields" class="grid grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-4"></div>
|
|
403
77
|
<div class="mt-3 flex flex-wrap items-center gap-2">
|
|
404
78
|
<button type="submit" class="btn btn-primary btn-sm gap-1.5">
|
|
405
79
|
{{{icon "search" class="h-4 w-4"}}}
|
|
406
80
|
Apply filter
|
|
407
81
|
</button>
|
|
408
82
|
<button type="button" id="clear-filter" class="btn btn-ghost btn-sm">Clear</button>
|
|
409
83
|
{{#if filterQS}}
|
|
410
84
|
<span class="text-xs text-base-content/50">Filtering <b class="tabular-nums">{{count}}</b> row(s).</span>
|
|
411
85
|
{{/if}}
|
|
412
86
|
</div>
|
|
413
87
|
</form>
|
|
414
88
|
</details>
|
|
415
89
|
<div id="import-panel" class="app-card hidden p-4">
|
|
416
90
|
<div class="mb-3 flex items-center justify-between">
|
|
417
91
|
<h2 class="text-sm font-semibold uppercase tracking-wide text-base-content/45">Import CSV into <span class="font-mono normal-case">{{table}}</span></h2>
|
|
418
92
|
<button id="import-csv-close" type="button" class="btn btn-ghost btn-xs" aria-label="Close">×</button>
|
|
419
93
|
</div>
|
|
420
94
|
<div class="space-y-3">
|
|
421
95
|
<textarea id="import-csv-text" rows="6" class="field-input w-full font-mono text-[13px]" placeholder="id,name,email 1,Ada,ada@example.com 2,Bob,bob@example.com" spellcheck="false"></textarea>
|
|
422
96
|
<div class="flex flex-wrap items-center gap-3">
|
|
423
97
|
<input type="file" id="import-csv-file" class="file-input file-input-sm w-full max-w-xs" accept=".csv,text/csv" />
|
|
424
98
|
<button id="import-csv-submit" type="button" class="btn btn-primary btn-sm gap-1.5">
|
|
425
99
|
{{{icon "import" class="h-4 w-4"}}}
|
|
426
100
|
Import
|
|
427
101
|
</button>
|
|
428
102
|
</div>
|
|
429
103
|
<p class="text-xs text-base-content/45">The first row must be column names matching this table. Empty cells are treated as “not set” so database defaults apply. The import is transactional — a failed row rolls back everything.</p>
|
|
430
104
|
</div>
|
|
431
105
|
</div>
|
|
432
106
|
{{#if rows.length}}
|
|
433
107
|
<div class="app-card overflow-hidden">
|
|
434
108
|
<div class="flex flex-wrap items-center justify-between gap-2 border-b border-base-200 px-4 py-2.5">
|
|
435
109
|
<div class="flex flex-wrap items-center gap-3">
|
|
436
110
|
<span class="text-sm text-base-content/65">Showing <b class="tabular-nums font-bold text-base-content">{{firstRow}}</b>–<b class="tabular-nums font-bold text-base-content">{{lastRow}}</b> of <b class="tabular-nums font-bold text-base-content">{{count}}</b> rows</span>
|
|
437
111
|
<span class="hidden xl:inline-flex items-center gap-1.5 text-xs text-base-content/40 font-medium">
|
|
438
112
|
<kbd class="kbd kbd-xs font-mono">Dbl-click</kbd> edit · <kbd class="kbd kbd-xs font-mono">↑↓←→</kbd> move · <kbd class="kbd kbd-xs font-mono">Space</kbd> toggle · <kbd class="kbd kbd-xs font-mono">Esc</kbd> cancel
|
|
439
113
|
</span>
|
|
440
114
|
</div>
|
|
441
115
|
<div class="flex flex-wrap items-center gap-2.5">
|
|
442
116
|
<button id="btn-columns-manager" type="button" class="btn btn-outline btn-xs gap-1.5 font-medium text-xs" title="Manage column visibility, order, and pinned columns">
|
|
443
117
|
{{{icon "columns" class="h-3.5 w-3.5"}}}
|
|
444
118
|
Columns
|
|
445
119
|
<span id="col-count-badge" class="badge badge-sm badge-primary text-[10px] tabular-nums font-semibold">{{add info.columns.length refColumns.length}}/{{add info.columns.length refColumns.length}}</span>
|
|
446
120
|
</button>
|
|
447
121
|
<label class="flex items-center gap-2 text-xs text-base-content/55">
|
|
448
122
|
Per page
|
|
449
123
|
<select id="page-size" class="field-select !w-auto !py-1 !text-xs" data-table="{{table}}" data-orderby="{{orderBy}}" data-orderdir="{{orderDir}}" data-filters="{{filterQS}}">
|
|
450
124
|
{{#each sizes}}<option value="{{this}}" {{#if (eq this ../pageSize)}}selected{{/if}}>{{this}}</option>{{/each}}
|
|
451
125
|
</select>
|
|
452
126
|
</label>
|
|
453
127
|
</div>
|
|
454
128
|
</div>
|
|
455
129
|
<div id="bulk-bar" class="hidden items-center justify-between gap-2 border-b border-base-200 bg-primary/[0.04] px-4 py-2">
|
|
456
130
|
<span class="text-sm font-medium text-base-content/80">
|
|
457
131
|
<b id="bulk-count" class="tabular-nums text-primary">0</b> row(s) selected
|
|
458
132
|
</span>
|
|
459
133
|
<div class="flex flex-wrap items-center gap-1.5">
|
|
460
134
|
<button id="bulk-export-csv" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
|
|
461
135
|
{{{icon "export" class="h-3.5 w-3.5"}}}
|
|
462
136
|
Export CSV
|
|
463
137
|
</button>
|
|
464
138
|
<button id="bulk-export-json" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
|
|
465
139
|
{{{icon "export" class="h-3.5 w-3.5"}}}
|
|
466
140
|
Export JSON
|
|
467
141
|
</button>
|
|
468
142
|
<button id="bulk-delete" type="button" class="btn btn-error btn-xs gap-1" {{#if readonly}}disabled{{/if}}>
|
|
469
143
|
{{{icon "trash" class="h-3.5 w-3.5"}}}
|
|
470
144
|
Delete
|
|
471
145
|
</button>
|
|
472
146
|
<button id="bulk-clear" type="button" class="btn btn-ghost btn-xs text-base-content/60">Clear</button>
|
|
473
147
|
</div>
|
|
474
148
|
</div>
|
|
475
149
|
<div class="overflow-x-auto">
|
|
476
150
|
<table id="main-table" class="table table-compact table-pin-rows">
|
|
477
151
|
<thead>
|
|
478
152
|
<tr class="bg-base-200/70 text-xs uppercase tracking-wide text-base-content/50">
|
|
479
153
|
<th class="w-10 text-center font-semibold th-select-all">
|
|
480
154
|
<input type="checkbox" id="select-all-rows" class="checkbox checkbox-primary checkbox-xs" title="Select all rows on this page" />
|
|
481
155
|
</th>
|
|
482
156
|
{{#each colHeaders}}
|
|
483
157
|
<th class="whitespace-nowrap font-semibold" data-col="{{this.name}}">
|
|
484
158
|
<a href="{{this.href}}" class="sort-link {{#if this.active}}active{{/if}}" title="Sort by {{this.name}}">
|
|
485
159
|
<span class="font-mono normal-case">{{this.name}}</span>
|
|
486
160
|
{{#if this.pk}}<span class="chip chip-pk">PK</span>{{/if}}
|
|
487
161
|
<span class="sort-arrow">{{#if (eq this.dir 'desc')}}↓{{else}}↑{{/if}}</span>
|
|
488
162
|
</a>
|
|
489
163
|
</th>
|
|
490
164
|
{{/each}}
|
|
491
165
|
{{#each refColumns}}
|
|
492
166
|
<th class="whitespace-nowrap text-right font-semibold" data-col="ref:{{this.table}}" data-is-ref="true" data-ref-table="{{this.table}}" title="Rows in {{this.table}} where {{this.from}} references {{this.to}}">
|
|
493
167
|
<span class="font-mono normal-case">{{this.table}}</span>
|
|
494
168
|
<span class="chip chip-type text-[9px] ml-1 bg-secondary/15 text-secondary">REF</span>
|
|
495
169
|
</th>
|
|
496
170
|
{{/each}}
|
|
497
171
|
<th class="text-right font-semibold sticky-col-actions">Actions</th>
|
|
498
172
|
</tr>
|
|
499
173
|
</thead>
|
|
500
174
|
<tbody>
|
|
501
175
|
{{#each rows}}
|
|
502
176
|
<tr class="group hover:bg-primary/[0.04]" data-table="{{@root.table}}" data-pk="{{this.pkEncoded}}">
|
|
503
177
|
<td class="w-10 text-center align-middle">
|
|
504
178
|
{{#if this.pkEncoded}}
|
|
505
179
|
<input type="checkbox" class="row-checkbox checkbox checkbox-primary checkbox-xs" value="{{this.pkEncoded}}" aria-label="Select this row" />
|
|
506
180
|
{{/if}}
|
|
507
181
|
</td>
|
|
508
182
|
{{#each this.cells}}
|
|
509
183
|
<td class="max-w-xs align-middle cell-editable {{#unless @root.readonly}}js-inline-cell{{/unless}}"
|
|
510
184
|
data-col="{{this.name}}" data-null="{{this.isNull}}" data-value="{{this.display}}"
|
|
511
185
|
data-blob="{{this.isBlob}}" data-json="{{this.isJson}}" data-image="{{this.isImage}}"
|
|
512
186
|
data-audio="{{this.isAudio}}" data-video="{{this.isVideo}}" data-pdf="{{this.isPdf}}"
|
|
513
187
|
data-mime="{{this.blobMime}}" data-type="{{this.type}}">
|
|
514
188
|
{{#if this.isNull}}
|
|
515
189
|
<span class="chip chip-null js-cell-value">NULL</span>
|
|
516
190
|
{{else if this.isImage}}
|
|
517
191
|
<div class="flex items-center gap-2 js-cell-value">
|
|
518
192
|
<img src="{{this.blobUrl}}" alt="{{this.name}}" class="h-7 max-w-12 rounded object-cover border border-base-300 bg-base-200 shadow-xs cursor-pointer js-inspect-btn" title="Inspect Image" />
|
|
519
193
|
<span class="chip chip-type text-[9px] font-mono">{{this.blobExt}} · {{this.blobSize}}</span>
|
|
520
194
|
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Inspect Image">
|
|
521
195
|
{{{icon "inspect" class="h-3 w-3"}}}
|
|
522
196
|
</button>
|
|
523
197
|
</div>
|
|
524
198
|
{{else if this.isAudio}}
|
|
525
199
|
<div class="flex items-center gap-1.5 js-cell-value">
|
|
526
200
|
<span class="inline-flex items-center gap-1 rounded-md bg-warning/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-warning cursor-pointer js-inspect-btn">🎵 {{this.blobExt}} · {{this.blobSize}}</span>
|
|
527
201
|
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Listen & Inspect Audio">
|
|
528
202
|
{{{icon "inspect" class="h-3 w-3"}}}
|
|
529
203
|
</button>
|
|
530
204
|
</div>
|
|
531
205
|
{{else if this.isVideo}}
|
|
532
206
|
<div class="flex items-center gap-1.5 js-cell-value">
|
|
533
207
|
<span class="inline-flex items-center gap-1 rounded-md bg-accent/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-accent cursor-pointer js-inspect-btn">🎬 {{this.blobExt}} · {{this.blobSize}}</span>
|
|
534
208
|
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Watch & Inspect Video">
|
|
535
209
|
{{{icon "inspect" class="h-3 w-3"}}}
|
|
536
210
|
</button>
|
|
537
211
|
</div>
|
|
538
212
|
{{else if this.isPdf}}
|
|
539
213
|
<div class="flex items-center gap-1.5 js-cell-value">
|
|
540
214
|
<span class="inline-flex items-center gap-1 rounded-md bg-error/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-error cursor-pointer js-inspect-btn">📄 PDF · {{this.blobSize}}</span>
|
|
541
215
|
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="View PDF">
|
|
542
216
|
{{{icon "inspect" class="h-3 w-3"}}}
|
|
543
217
|
</button>
|
|
544
218
|
</div>
|
|
545
219
|
{{else if this.isBlob}}
|
|
546
220
|
<div class="flex items-center gap-1.5 js-cell-value">
|
|
547
221
|
<span class="inline-flex items-center gap-1 rounded-md bg-secondary/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-secondary cursor-pointer js-inspect-btn">🗃️ BLOB · {{this.blobSize}}</span>
|
|
548
222
|
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Inspect BLOB">
|
|
549
223
|
{{{icon "inspect" class="h-3 w-3"}}}
|
|
550
224
|
</button>
|
|
551
225
|
</div>
|
|
552
226
|
{{else if this.isJson}}
|
|
553
227
|
<div class="flex items-center gap-1.5 js-cell-value">
|
|
554
228
|
<span class="inline-flex items-center gap-1 rounded-md bg-info/10 px-1.5 py-0.5 text-[11px] font-mono font-medium text-info cursor-pointer js-inspect-btn">{ } JSON</span>
|
|
555
229
|
<span class="block max-w-[140px] truncate font-mono text-[12px] text-base-content/70" title="{{this.display}}">{{this.display}}</span>
|
|
556
230
|
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover:opacity-100 transition-opacity js-inspect-btn" title="Inspect JSON">
|
|
557
231
|
{{{icon "inspect" class="h-3 w-3"}}}
|
|
558
232
|
</button>
|
|
559
233
|
</div>
|
|
560
234
|
{{else if this.isColor}}
|
|
561
235
|
<div class="flex items-center gap-1.5 js-cell-value">
|
|
562
236
|
<span class="inline-block h-3.5 w-3.5 rounded-full border border-base-300 shadow-xs shrink-0" style="background-color: {{this.display}}"></span>
|
|
563
237
|
<span class="font-mono text-[13px]">{{this.display}}</span>
|
|
564
238
|
</div>
|
|
565
239
|
{{else if this.isUrl}}
|
|
566
240
|
<div class="flex items-center gap-1.5 js-cell-value">
|
|
567
241
|
<span class="block max-w-xs truncate font-mono text-[13px] text-primary underline" title="{{this.display}}">{{this.display}}</span>
|
|
568
242
|
<a href="{{this.display}}" target="_blank" rel="noopener" class="text-base-content/40 hover:text-primary" title="Open link">↗</a>
|
|
569
243
|
</div>
|
|
570
244
|
{{else}}
|
|
571
245
|
<div class="flex items-center justify-between gap-1 group/cell">
|
|
572
246
|
<span class="js-cell-value block max-w-xs truncate font-mono text-[13px]" title="{{this.display}}">{{truncate this.display 120}}</span>
|
|
573
247
|
{{#if (gt this.display.length 30)}}
|
|
574
248
|
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-0 group-hover/cell:opacity-100 transition-opacity js-inspect-btn shrink-0" title="Inspect text">
|
|
575
249
|
{{{icon "inspect" class="h-3 w-3"}}}
|
|
576
250
|
</button>
|
|
577
251
|
{{/if}}
|
|
578
252
|
</div>
|
|
579
253
|
{{/if}}
|
|
580
254
|
</td>
|
|
581
255
|
{{/each}}
|
|
582
256
|
{{#each this.refs}}
|
|
583
257
|
<td class="whitespace-nowrap text-right align-middle" data-col="ref:{{this.table}}" data-is-ref="true" data-ref-table="{{this.table}}">
|
|
584
258
|
{{#if this.value}}
|
|
585
259
|
<button type="button" class="js-ref-toggle inline-flex items-center gap-1.5 rounded-lg border border-base-300 bg-base-100 px-2 py-1 text-xs font-medium text-base-content/70 shadow-soft transition hover:border-primary/50 hover:text-primary {{#unless this.count}}opacity-50{{/unless}}"
|
|
586
260
|
data-table="{{@root.table}}" data-pk="{{../pkEncoded}}" data-ref-table="{{this.table}}"
|
|
587
261
|
aria-expanded="false" title="View {{this.table}} rows where {{this.from}} = {{this.value}}">
|
|
588
262
|
<span class="font-mono normal-case">{{this.table}}</span>
|
|
589
263
|
<span class="rounded-md bg-primary/10 px-1.5 py-0.5 font-semibold tabular-nums text-primary">{{this.count}}</span>
|
|
590
264
|
<span class="js-ref-chevron transition-transform inline-block">
|
|
591
265
|
{{{icon "chevronDown" class="h-3 w-3"}}}
|
|
592
266
|
</span>
|
|
593
267
|
</button>
|
|
594
268
|
{{else}}
|
|
595
269
|
<span class="text-xs text-base-content/35">—</span>
|
|
596
270
|
{{/if}}
|
|
597
271
|
</td>
|
|
598
272
|
{{/each}}
|
|
599
273
|
<td class="whitespace-nowrap text-right align-middle sticky-col-actions">
|
|
600
274
|
{{#if this.pkEncoded}}
|
|
601
275
|
<div class="row-actions">
|
|
602
276
|
{{#unless @root.readonly}}
|
|
603
277
|
<a href="{{@root.basePath}}/tables/{{@root.table}}/rows/{{this.pkEncoded}}/edit" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary" title="Edit row">
|
|
604
278
|
{{{icon "edit" class="h-3.5 w-3.5"}}}
|
|
605
279
|
Edit
|
|
606
280
|
</a>
|
|
607
281
|
{{/unless}}
|
|
608
282
|
<div class="dropdown dropdown-end">
|
|
609
283
|
<label tabindex="0" role="button" class="btn btn-ghost btn-xs btn-square text-base-content/60 hover:text-base-content" title="Row actions">
|
|
610
284
|
{{{icon "dotsVertical" class="h-3.5 w-3.5"}}}
|
|
611
285
|
</label>
|
|
612
286
|
<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">
|
|
613
287
|
<li>
|
|
614
288
|
<button type="button" class="js-row-copy-json flex items-center gap-2" data-pk="{{this.pkEncoded}}">
|
|
615
289
|
{{{icon "copy" class="h-3.5 w-3.5 text-base-content/60"}}}
|
|
616
290
|
Copy as JSON
|
|
617
291
|
</button>
|
|
618
292
|
</li>
|
|
619
293
|
<li>
|
|
620
294
|
<button type="button" class="js-row-copy-sql flex items-center gap-2" data-pk="{{this.pkEncoded}}">
|
|
621
295
|
{{{icon "sql" class="h-3.5 w-3.5 text-base-content/60"}}}
|
|
622
296
|
Copy SQL INSERT
|
|
623
297
|
</button>
|
|
624
298
|
</li>
|
|
625
299
|
{{#unless @root.readonly}}
|
|
626
300
|
<li>
|
|
627
301
|
<button type="button" class="js-row-duplicate flex items-center gap-2" data-pk="{{this.pkEncoded}}">
|
|
628
302
|
{{{icon "duplicate" class="h-3.5 w-3.5 text-base-content/60"}}}
|
|
629
303
|
Duplicate Row
|
|
630
304
|
</button>
|
|
631
305
|
</li>
|
|
632
306
|
<div class="divider my-1"></div>
|
|
633
307
|
<li>
|
|
634
308
|
<button type="button" class="js-delete-row flex items-center gap-2 text-error hover:bg-error/10" data-table="{{@root.table}}" data-pk="{{this.pkEncoded}}">
|
|
635
309
|
{{{icon "trash" class="h-3.5 w-3.5"}}}
|
|
636
310
|
Delete Row
|
|
637
311
|
</button>
|
|
638
312
|
</li>
|
|
639
313
|
{{/unless}}
|
|
640
314
|
</ul>
|
|
641
315
|
</div>
|
|
642
316
|
</div>
|
|
643
317
|
{{else}}
|
|
644
318
|
<span class="text-xs text-base-content/35">No PK</span>
|
|
645
319
|
{{/if}}
|
|
646
320
|
</td>
|
|
647
321
|
</tr>
|
|
648
322
|
{{/each}}
|
|
649
323
|
</tbody>
|
|
650
324
|
</table>
|
|
651
325
|
</div>
|
|
652
326
|
{{#if (gt pages 1)}}
|
|
653
327
|
<div class="flex flex-wrap items-center justify-between gap-2 border-t border-base-200 px-4 py-3">
|
|
654
328
|
<span class="text-sm text-base-content/50">Page {{page}} of {{pages}}</span>
|
|
655
329
|
<div class="flex gap-1.5">
|
|
656
330
|
<a href="{{basePath}}/tables/{{table}}?page={{sub page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
|
|
657
331
|
class="btn btn-outline btn-sm {{#unless (gt page 1)}}btn-disabled{{/unless}}" aria-disabled="{{#if (gt page 1)}}false{{else}}true{{/if}}">
|
|
658
332
|
{{{icon "chevronLeft" class="h-4 w-4"}}}
|
|
659
333
|
Prev
|
|
660
334
|
</a>
|
|
661
335
|
<a href="{{basePath}}/tables/{{table}}?page={{add page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
|
|
662
336
|
class="btn btn-outline btn-sm {{#unless (lt page pages)}}btn-disabled{{/unless}}" aria-disabled="{{#if (lt page pages)}}false{{else}}true{{/if}}">
|
|
663
337
|
Next
|
|
664
338
|
{{{icon "chevronRight" class="h-4 w-4"}}}
|
|
665
339
|
</a>
|
|
666
340
|
</div>
|
|
667
341
|
</div>
|
|
668
342
|
{{/if}}
|
|
669
343
|
</div>
|
|
670
344
|
{{else}}
|
|
671
345
|
<div class="empty-state">
|
|
672
346
|
<span class="empty-icon">{{{icon "book" class="h-7 w-7"}}}</span>
|
|
673
347
|
<p class="text-sm font-medium text-base-content/70">No rows in <span class="font-mono">{{table}}</span></p>
|
|
674
348
|
<div class="mt-2 flex gap-2">
|
|
675
349
|
{{#unless readonly}}
|
|
676
350
|
<a href="{{basePath}}/tables/{{table}}/rows/new" class="btn btn-primary btn-sm">Insert a row</a>
|
|
677
351
|
<a href="{{basePath}}/seed/{{table}}" class="btn btn-outline btn-sm">Generate sample data</a>
|
|
678
352
|
{{/unless}}
|
|
679
353
|
<a href="{{basePath}}/query" class="btn btn-outline btn-sm">Run a query</a>
|
|
680
354
|
</div>
|
|
681
355
|
</div>
|
|
682
356
|
{{/if}}
|
|
683
357
|
<div class="modal-box max-w-lg p-0 overflow-hidden shadow-lift border border-base-300 bg-base-100">
|
|
684
358
|
<div class="flex items-center justify-between border-b border-base-200 px-5 py-3.5 bg-base-200/50">
|
|
685
359
|
<div class="flex items-center gap-2.5">
|
|
686
360
|
<span class="grid h-8 w-8 place-items-center rounded-lg bg-primary/10 text-primary">
|
|
687
361
|
{{{icon "columns" class="h-4 w-4"}}}
|
|
688
362
|
</span>
|
|
689
363
|
<div>
|
|
690
364
|
<h3 class="font-bold text-sm text-base-content">Column Manager</h3>
|
|
691
365
|
<p class="text-[11px] text-base-content/50">Visibility, custom ordering, and pinned columns</p>
|
|
692
366
|
</div>
|
|
693
367
|
</div>
|
|
694
368
|
<form method="dialog">
|
|
695
369
|
<button class="btn btn-ghost btn-xs btn-circle text-base-content/60" aria-label="Close">✕</button>
|
|
696
370
|
</form>
|
|
697
371
|
</div>
|
|
698
372
|
<div class="p-4 space-y-3">
|
|
699
373
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
|
700
374
|
<div class="relative flex-1 min-w-[160px]">
|
|
701
375
|
<input type="text" id="col-search" class="field-input !py-1.5 !pl-8 !pr-3 !text-xs w-full" placeholder="Filter columns..." />
|
|
702
376
|
<span class="absolute left-2.5 top-1/2 -translate-y-1/2 text-base-content/40 pointer-events-none">
|
|
703
377
|
{{{icon "search" class="h-3.5 w-3.5"}}}
|
|
704
378
|
</span>
|
|
705
379
|
</div>
|
|
706
380
|
<div class="flex items-center gap-1">
|
|
707
381
|
<button type="button" id="col-btn-show-all" class="btn btn-ghost btn-xs text-xs font-medium">Show all</button>
|
|
708
382
|
<button type="button" id="col-btn-hide-non-pk" class="btn btn-ghost btn-xs text-xs font-medium">PKs only</button>
|
|
709
383
|
<button type="button" id="col-btn-reset" class="btn btn-ghost btn-xs text-xs text-warning hover:bg-warning/10">Reset</button>
|
|
710
384
|
</div>
|
|
711
385
|
</div>
|
|
712
386
|
<div class="flex items-center justify-between text-[11px] text-base-content/50 px-0.5">
|
|
713
387
|
<span>Drag ⠿ to reorder. Toggle checkbox to show/hide. Click 📌 to pin.</span>
|
|
714
388
|
<span id="col-manager-summary" class="font-mono text-primary font-semibold"></span>
|
|
715
389
|
</div>
|
|
716
390
|
<div id="col-manager-list" class="space-y-1 max-h-[360px] overflow-y-auto pr-1">
|
|
717
391
|
</div>
|
|
718
392
|
</div>
|
|
719
393
|
<div class="flex items-center justify-between border-t border-base-200 px-5 py-3 bg-base-200/40 text-xs">
|
|
720
394
|
<span class="text-base-content/50 text-[11px]">Saved in browser for this table</span>
|
|
721
395
|
<form method="dialog">
|
|
722
396
|
<button class="btn btn-primary btn-sm">Done</button>
|
|
723
397
|
</form>
|
|
724
398
|
</div>
|
|
725
399
|
</div>
|
|
726
400
|
<form method="dialog" class="modal-backdrop bg-black/40 backdrop-blur-xs">
|
|
727
401
|
<button>close</button>
|
|
728
402
|
</form>
|