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