admindb 1.2.0 → 2.0.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.
Files changed (202) hide show
  1. package/README.md +250 -352
  2. package/dist/app.d.ts +25 -4
  3. package/dist/app.js +79 -15
  4. package/dist/app.js.map +1 -1
  5. package/dist/auth/config.d.ts +19 -0
  6. package/dist/auth/config.js +147 -0
  7. package/dist/auth/config.js.map +1 -0
  8. package/dist/auth/crypto.d.ts +27 -0
  9. package/dist/auth/crypto.js +133 -0
  10. package/dist/auth/crypto.js.map +1 -0
  11. package/dist/auth/index.d.ts +5 -0
  12. package/dist/auth/index.js +22 -0
  13. package/dist/auth/index.js.map +1 -0
  14. package/dist/auth/middleware.d.ts +14 -0
  15. package/dist/auth/middleware.js +100 -0
  16. package/dist/auth/middleware.js.map +1 -0
  17. package/dist/auth/routes.d.ts +7 -0
  18. package/dist/auth/routes.js +78 -0
  19. package/dist/auth/routes.js.map +1 -0
  20. package/dist/auth/types.d.ts +35 -0
  21. package/dist/auth/types.js +11 -0
  22. package/dist/auth/types.js.map +1 -0
  23. package/dist/cli/args.d.ts +45 -0
  24. package/dist/cli/args.js +328 -0
  25. package/dist/cli/args.js.map +1 -0
  26. package/dist/cli/config.d.ts +56 -0
  27. package/dist/cli/config.js +133 -0
  28. package/dist/cli/config.js.map +1 -0
  29. package/dist/cli/index.d.ts +4 -0
  30. package/dist/cli/index.js +21 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/runner.d.ts +1 -0
  33. package/dist/cli/runner.js +222 -0
  34. package/dist/cli/runner.js.map +1 -0
  35. package/dist/cli.js +2 -127
  36. package/dist/cli.js.map +1 -1
  37. package/dist/data/datasets.d.ts +17 -0
  38. package/dist/data/datasets.js +133 -0
  39. package/dist/data/datasets.js.map +1 -0
  40. package/dist/data/detector.d.ts +30 -0
  41. package/dist/data/detector.js +472 -0
  42. package/dist/data/detector.js.map +1 -0
  43. package/dist/data/engine.d.ts +10 -0
  44. package/dist/data/engine.js +211 -0
  45. package/dist/data/engine.js.map +1 -0
  46. package/dist/data/index.d.ts +5 -0
  47. package/dist/data/index.js +22 -0
  48. package/dist/data/index.js.map +1 -0
  49. package/dist/data/strategies.d.ts +30 -0
  50. package/dist/data/strategies.js +343 -0
  51. package/dist/data/strategies.js.map +1 -0
  52. package/dist/data/types.d.ts +73 -0
  53. package/dist/data/types.js +8 -0
  54. package/dist/data/types.js.map +1 -0
  55. package/dist/db/database.d.ts +14 -142
  56. package/dist/db/database.js +209 -435
  57. package/dist/db/database.js.map +1 -1
  58. package/dist/db/export.d.ts +2 -2
  59. package/dist/db/export.js +7 -2
  60. package/dist/db/export.js.map +1 -1
  61. package/dist/db/filters.d.ts +22 -0
  62. package/dist/db/filters.js +158 -0
  63. package/dist/db/filters.js.map +1 -0
  64. package/dist/db/index.d.ts +8 -0
  65. package/dist/db/index.js +25 -0
  66. package/dist/db/index.js.map +1 -0
  67. package/dist/db/introspection.d.ts +6 -0
  68. package/dist/db/introspection.js +172 -0
  69. package/dist/db/introspection.js.map +1 -0
  70. package/dist/db/manager.d.ts +32 -19
  71. package/dist/db/manager.js +203 -44
  72. package/dist/db/manager.js.map +1 -1
  73. package/dist/db/migrations.d.ts +5 -0
  74. package/dist/db/migrations.js +110 -0
  75. package/dist/db/migrations.js.map +1 -0
  76. package/dist/db/postgres.d.ts +85 -0
  77. package/dist/db/postgres.js +745 -0
  78. package/dist/db/postgres.js.map +1 -0
  79. package/dist/db/types.d.ts +206 -0
  80. package/dist/db/types.js +7 -0
  81. package/dist/db/types.js.map +1 -0
  82. package/dist/index.d.ts +9 -2
  83. package/dist/index.js +46 -4
  84. package/dist/index.js.map +1 -1
  85. package/dist/public/css/app.css +1 -1
  86. package/dist/public/css/input.css +83 -13
  87. package/dist/public/js/app.js +142 -0
  88. package/dist/public/js/browse.js +591 -104
  89. package/dist/public/js/databases.js +105 -13
  90. package/dist/public/js/designer.js +40 -2
  91. package/dist/public/js/forms.js +610 -28
  92. package/dist/public/js/icons.js +51 -0
  93. package/dist/public/js/inspector.js +638 -0
  94. package/dist/public/js/query.js +93 -3
  95. package/dist/public/js/schema.js +170 -52
  96. package/dist/public/js/seed.js +1090 -0
  97. package/dist/routes/api/helpers.d.ts +42 -0
  98. package/dist/routes/api/helpers.js +133 -0
  99. package/dist/routes/api/helpers.js.map +1 -0
  100. package/dist/routes/api/import-export.d.ts +3 -0
  101. package/dist/routes/api/import-export.js +85 -0
  102. package/dist/routes/api/import-export.js.map +1 -0
  103. package/dist/routes/api/index.d.ts +4 -0
  104. package/dist/routes/api/index.js +37 -0
  105. package/dist/routes/api/index.js.map +1 -0
  106. package/dist/routes/api/query.d.ts +3 -0
  107. package/dist/routes/api/query.js +76 -0
  108. package/dist/routes/api/query.js.map +1 -0
  109. package/dist/routes/api/rows.d.ts +3 -0
  110. package/dist/routes/api/rows.js +373 -0
  111. package/dist/routes/api/rows.js.map +1 -0
  112. package/dist/routes/api/seed.d.ts +3 -0
  113. package/dist/routes/api/seed.js +82 -0
  114. package/dist/routes/api/seed.js.map +1 -0
  115. package/dist/routes/api/tables.d.ts +3 -0
  116. package/dist/routes/api/tables.js +160 -0
  117. package/dist/routes/api/tables.js.map +1 -0
  118. package/dist/routes/databases.d.ts +8 -11
  119. package/dist/routes/databases.js +100 -66
  120. package/dist/routes/databases.js.map +1 -1
  121. package/dist/routes/index.d.ts +3 -0
  122. package/dist/routes/index.js +20 -0
  123. package/dist/routes/index.js.map +1 -0
  124. package/dist/routes/pages.d.ts +3 -3
  125. package/dist/routes/pages.js +163 -54
  126. package/dist/routes/pages.js.map +1 -1
  127. package/dist/serverless.d.ts +40 -0
  128. package/dist/serverless.js +210 -0
  129. package/dist/serverless.js.map +1 -0
  130. package/dist/sql/generator.d.ts +4 -3
  131. package/dist/sql/generator.js +103 -6
  132. package/dist/sql/generator.js.map +1 -1
  133. package/dist/sql/index.d.ts +2 -0
  134. package/dist/sql/index.js +19 -0
  135. package/dist/sql/index.js.map +1 -0
  136. package/dist/types/api.d.ts +412 -0
  137. package/dist/types/api.js +9 -0
  138. package/dist/types/api.js.map +1 -0
  139. package/dist/utils/colors.d.ts +40 -0
  140. package/dist/utils/colors.js +64 -0
  141. package/dist/utils/colors.js.map +1 -0
  142. package/dist/{util.d.ts → utils/common.d.ts} +10 -6
  143. package/dist/{util.js → utils/common.js} +80 -13
  144. package/dist/utils/common.js.map +1 -0
  145. package/dist/utils/csv.js.map +1 -0
  146. package/dist/utils/datatype.d.ts +49 -0
  147. package/dist/utils/datatype.js +222 -0
  148. package/dist/utils/datatype.js.map +1 -0
  149. package/dist/utils/icons.d.ts +11 -0
  150. package/dist/utils/icons.js +72 -0
  151. package/dist/utils/icons.js.map +1 -0
  152. package/dist/utils/index.d.ts +4 -0
  153. package/dist/utils/index.js +21 -0
  154. package/dist/utils/index.js.map +1 -0
  155. package/dist/{logger.d.ts → utils/logger.d.ts} +1 -1
  156. package/dist/utils/logger.js +40 -0
  157. package/dist/utils/logger.js.map +1 -0
  158. package/dist/views/layouts/main.hbs +33 -11
  159. package/dist/views/pages/databases.hbs +182 -74
  160. package/dist/views/pages/designer.hbs +9 -6
  161. package/dist/views/pages/error.hbs +3 -3
  162. package/dist/views/pages/form.hbs +5 -3
  163. package/dist/views/pages/home.hbs +22 -13
  164. package/dist/views/pages/login.hbs +111 -0
  165. package/dist/views/pages/query.hbs +10 -8
  166. package/dist/views/pages/schema.hbs +41 -13
  167. package/dist/views/pages/seed.hbs +232 -0
  168. package/dist/views/pages/table.hbs +155 -70
  169. package/dist/views/partials/icon.hbs +2 -0
  170. package/dist/views/partials/navbar.hbs +70 -31
  171. package/dist/views/partials/sidebar.hbs +108 -99
  172. package/package.json +12 -5
  173. package/dist/args.d.ts +0 -28
  174. package/dist/args.js +0 -187
  175. package/dist/args.js.map +0 -1
  176. package/dist/config.d.ts +0 -17
  177. package/dist/config.js +0 -23
  178. package/dist/config.js.map +0 -1
  179. package/dist/csv.js.map +0 -1
  180. package/dist/logger.js +0 -25
  181. package/dist/logger.js.map +0 -1
  182. package/dist/routes/api.d.ts +0 -9
  183. package/dist/routes/api.js +0 -786
  184. package/dist/routes/api.js.map +0 -1
  185. package/dist/test/classifier.test.d.ts +0 -1
  186. package/dist/test/classifier.test.js +0 -44
  187. package/dist/test/classifier.test.js.map +0 -1
  188. package/dist/test/csv.test.d.ts +0 -1
  189. package/dist/test/csv.test.js +0 -64
  190. package/dist/test/csv.test.js.map +0 -1
  191. package/dist/test/database.test.d.ts +0 -1
  192. package/dist/test/database.test.js +0 -402
  193. package/dist/test/database.test.js.map +0 -1
  194. package/dist/test/generator.test.d.ts +0 -1
  195. package/dist/test/generator.test.js +0 -132
  196. package/dist/test/generator.test.js.map +0 -1
  197. package/dist/test/manager.test.d.ts +0 -1
  198. package/dist/test/manager.test.js +0 -146
  199. package/dist/test/manager.test.js.map +0 -1
  200. package/dist/util.js.map +0 -1
  201. /package/dist/{csv.d.ts → utils/csv.d.ts} +0 -0
  202. /package/dist/{csv.js → utils/csv.js} +0 -0
@@ -0,0 +1,232 @@
1
+ {{!-- Page: Mock & Seed Data Generator · URL: /tables/:table/seed --}}
2
+ <div class="space-y-6">
3
+
4
+ <!-- Header -->
5
+ <div class="flex flex-wrap items-center justify-between gap-4 border-b border-base-200 pb-5">
6
+ <div class="flex items-center gap-3">
7
+ <a href="{{basePath}}/tables/{{table}}" class="btn btn-circle btn-ghost btn-sm" aria-label="Back to table">
8
+ {{{icon "arrowLeft" class="h-5 w-5"}}}
9
+ </a>
10
+ <div>
11
+ <div class="flex flex-wrap items-center gap-2">
12
+ <h1 class="font-mono text-2xl font-bold tracking-tight">{{table}}</h1>
13
+ <span class="badge badge-sm badge-neutral font-mono">{{rowCount}} rows in DB</span>
14
+ <span class="badge badge-sm badge-ghost font-mono">{{colCount}} columns</span>
15
+ </div>
16
+ <p class="mt-0.5 text-xs text-base-content/55">Data generator — fill the table with realistic mock data, simulate sparse records, and preview in real time.</p>
17
+ </div>
18
+ </div>
19
+ <div class="flex flex-wrap items-center gap-2">
20
+ <!-- Presets dropdown -->
21
+ <div class="dropdown dropdown-end">
22
+ <button tabindex="0" type="button" class="btn btn-outline btn-sm gap-1.5">
23
+ {{{icon "seed" class="h-4 w-4"}}}
24
+ Presets
25
+ {{{icon "chevronDown" class="h-3.5 w-3.5 opacity-60"}}}
26
+ </button>
27
+ <ul tabindex="0" class="dropdown-content z-20 menu p-2 shadow-lg bg-base-100 rounded-xl w-56 border border-base-200 text-xs">
28
+ <li><button type="button" id="preset-realistic" class="py-2">✨ Realistic Mock Data</button></li>
29
+ <li><button type="button" id="preset-minimal" class="py-2">⚡ Minimal (Required Only)</button></li>
30
+ <li><button type="button" id="preset-sparse" class="py-2">🎲 Sparse (30% Nullables)</button></li>
31
+ <li><button type="button" id="preset-numbered" class="py-2">🔢 Sequential & Numbered</button></li>
32
+ <li class="border-t border-base-200 my-1"></li>
33
+ <li><button type="button" id="seed-export-config" class="py-2">💾 Export Plan as JSON</button></li>
34
+ <li><button type="button" id="seed-import-config" class="py-2">📥 Import Plan JSON…</button></li>
35
+ </ul>
36
+ </div>
37
+
38
+ <button id="seed-preview" type="button" class="btn btn-outline btn-sm gap-1.5">
39
+ {{{icon "inspect" class="h-4 w-4 text-primary"}}}
40
+ Preview Data
41
+ </button>
42
+
43
+ {{#unless readonly}}
44
+ <button id="seed-insert" type="button" class="btn btn-primary btn-sm gap-1.5 shadow-sm">
45
+ {{{icon "plus" class="h-4 w-4"}}}
46
+ Insert Rows
47
+ </button>
48
+ {{else}}
49
+ <span class="inline-flex items-center gap-1.5 text-xs font-medium text-warning bg-warning/10 px-2.5 py-1.5 rounded-lg">
50
+ {{{icon "lock" class="h-4 w-4"}}}
51
+ Read-only database
52
+ </span>
53
+ {{/unless}}
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Configuration Bar -->
58
+ <div class="app-card p-5 bg-base-100">
59
+ <div class="grid grid-cols-1 gap-5 lg:grid-cols-12 lg:items-end">
60
+ <!-- Row count control -->
61
+ <div class="lg:col-span-4 space-y-1.5">
62
+ <label class="field-label !mb-0 font-medium" for="seed-count">
63
+ <span>Rows to generate</span>
64
+ <span class="text-xs text-base-content/45 font-normal">(max {{maxRows}})</span>
65
+ </label>
66
+ <div class="flex items-center gap-2">
67
+ <input id="seed-count" type="number" min="1" max="{{maxRows}}" value="10" class="field-input w-28 font-mono font-medium" />
68
+ <div class="flex flex-wrap items-center gap-1">
69
+ {{#each quickCounts}}
70
+ <button type="button" class="seed-quick btn btn-outline btn-xs px-2.5" data-n="{{this}}">{{this}}</button>
71
+ {{/each}}
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <!-- Column Search Filter -->
77
+ <div class="lg:col-span-4 space-y-1.5">
78
+ <label class="field-label !mb-0 font-medium" for="seed-search">
79
+ <span>Filter columns</span>
80
+ </label>
81
+ <div class="relative">
82
+ <span class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-base-content/40">
83
+ {{{icon "search" class="h-4 w-4"}}}
84
+ </span>
85
+ <input id="seed-search" type="text" placeholder="Search column by name or type..." class="field-input pl-9 text-xs" />
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Seeding options (truncate / clear) -->
90
+ <div class="lg:col-span-4 flex flex-col justify-end space-y-2">
91
+ {{#unless readonly}}
92
+ <label class="cursor-pointer flex items-center gap-2 text-xs font-medium text-base-content/80 hover:text-base-content transition-colors">
93
+ <input id="seed-truncate" type="checkbox" class="checkbox checkbox-xs checkbox-error rounded" />
94
+ <span class="flex items-center gap-1 text-error/90">
95
+ {{{icon "trash" class="h-3.5 w-3.5"}}}
96
+ Clear table before inserting (DELETE all rows)
97
+ </span>
98
+ </label>
99
+ {{/unless}}
100
+ <p class="text-[11px] text-base-content/45 leading-tight">Rows are inserted in a single atomic transaction. Any constraint failure safely rolls back.</p>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <!-- Column strategies section -->
106
+ <div class="space-y-3">
107
+ <div class="flex flex-wrap items-center justify-between gap-2">
108
+ <div class="flex items-center gap-2">
109
+ <h2 class="text-sm font-semibold uppercase tracking-wider text-base-content/50">Column Strategies</h2>
110
+ <span id="seed-col-count-badge" class="badge badge-sm badge-ghost font-mono"></span>
111
+ </div>
112
+ <div class="flex flex-wrap items-center gap-1.5">
113
+ <button id="seed-refresh-samples" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/60" title="Refresh live sample previews">
114
+ {{{icon "refresh" class="h-3.5 w-3.5"}}}
115
+ Refresh Samples
116
+ </button>
117
+ <button id="seed-apply-all" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/60">
118
+ {{{icon "duplicate" class="h-3.5 w-3.5"}}}
119
+ Apply strategy to all…
120
+ </button>
121
+ <button id="seed-reset" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/60">
122
+ {{{icon "refresh" class="h-3.5 w-3.5"}}}
123
+ Reset Defaults
124
+ </button>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- Cards Grid -->
129
+ <div id="seed-columns" class="grid grid-cols-1 gap-3.5 md:grid-cols-2 2xl:grid-cols-3"></div>
130
+ <div id="seed-empty-search" class="hidden p-8 text-center app-card">
131
+ <p class="text-sm text-base-content/50">No columns match the current search query.</p>
132
+ </div>
133
+ </div>
134
+
135
+ <!-- Feedback / Warnings Alert -->
136
+ <div id="seed-feedback" class="hidden"></div>
137
+
138
+ <!-- Interactive Dual-Mode Preview Section -->
139
+ <div id="seed-preview-card" class="hidden app-card overflow-hidden transition-all duration-200">
140
+ <div class="flex flex-wrap items-center justify-between border-b border-base-200 bg-base-200/40 px-5 py-3 gap-3">
141
+ <div class="flex items-center gap-3">
142
+ <div class="tabs tabs-boxed bg-base-300/40 p-0.5 rounded-lg">
143
+ <button id="tab-table-view" type="button" class="tab tab-sm tab-active font-medium gap-1.5">
144
+ {{{icon "table" class="h-3.5 w-3.5"}}}
145
+ Table Preview
146
+ </button>
147
+ <button id="tab-sql-view" type="button" class="tab tab-sm font-medium gap-1.5">
148
+ {{{icon "sql" class="h-3.5 w-3.5"}}}
149
+ SQL Statements
150
+ </button>
151
+ </div>
152
+ <span id="preview-summary-badge" class="badge badge-sm badge-ghost font-mono"></span>
153
+ </div>
154
+
155
+ <div class="flex flex-wrap items-center gap-1.5">
156
+ <button id="seed-export-csv" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
157
+ {{{icon "export" class="h-3.5 w-3.5"}}}
158
+ Export CSV
159
+ </button>
160
+ <button id="seed-export-json" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
161
+ {{{icon "export" class="h-3.5 w-3.5"}}}
162
+ Export JSON
163
+ </button>
164
+ <button id="seed-copy-sql" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
165
+ {{{icon "copy" class="h-3.5 w-3.5"}}}
166
+ Copy SQL
167
+ </button>
168
+ <button id="seed-download-sql" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
169
+ {{{icon "export" class="h-3.5 w-3.5"}}}
170
+ Download .sql
171
+ </button>
172
+ </div>
173
+ </div>
174
+
175
+ <!-- View 1: Interactive Table View -->
176
+ <div id="seed-table-view-container" class="overflow-x-auto max-h-[32rem]">
177
+ <table class="table table-sm table-pin-rows table-zebra w-full text-xs">
178
+ <thead id="seed-preview-thead" class="bg-base-200 font-mono text-[11px] text-base-content/70"></thead>
179
+ <tbody id="seed-preview-tbody"></tbody>
180
+ </table>
181
+ </div>
182
+
183
+ <!-- View 2: SQL View -->
184
+ <div id="seed-sql-view-container" class="hidden p-4">
185
+ <pre class="code-panel max-h-[32rem] overflow-auto"><code id="seed-sql-code"></code></pre>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <!-- Config JSON Modal -->
191
+ <dialog id="seed-config-dialog" class="modal">
192
+ <div class="modal-box app-card max-w-lg">
193
+ <h3 id="seed-config-dialog-title" class="text-base font-bold">Seed Plan Configuration</h3>
194
+ <p class="mt-1 text-xs text-base-content/60">Export or paste a JSON configuration for column generator settings.</p>
195
+ <textarea id="seed-config-textarea" rows="12" class="field-input mt-3 w-full font-mono text-xs" spellcheck="false"></textarea>
196
+ <div class="mt-4 flex justify-end gap-2">
197
+ <button id="seed-config-dialog-cancel" type="button" class="btn btn-ghost btn-sm">Close</button>
198
+ <button id="seed-config-dialog-apply" type="button" class="btn btn-primary btn-sm">Apply Configuration</button>
199
+ </div>
200
+ </div>
201
+ <form method="dialog" class="modal-backdrop">
202
+ <button>close</button>
203
+ </form>
204
+ </dialog>
205
+
206
+ <!-- Truncate Confirmation Modal -->
207
+ <dialog id="seed-truncate-dialog" class="modal">
208
+ <div class="modal-box app-card max-w-md border border-error/30">
209
+ <div class="flex items-center gap-3 text-error">
210
+ <span class="grid h-10 w-10 shrink-0 place-items-center rounded-xl bg-error/10">
211
+ {{{icon "warning" class="h-6 w-6"}}}
212
+ </span>
213
+ <div>
214
+ <h3 class="text-base font-bold text-base-content">Clear table before inserting?</h3>
215
+ <p class="text-xs text-error font-medium">All existing rows in "{{table}}" will be permanently deleted.</p>
216
+ </div>
217
+ </div>
218
+ <p class="mt-3 text-xs text-base-content/60 leading-relaxed">
219
+ This will execute <code>DELETE FROM "{{table}}"</code> before inserting the newly generated seed rows. This action cannot be undone.
220
+ </p>
221
+ <div class="mt-5 flex justify-end gap-2">
222
+ <button id="seed-truncate-cancel" type="button" class="btn btn-ghost btn-sm">Cancel</button>
223
+ <button id="seed-truncate-confirm" type="button" class="btn btn-error btn-sm">Yes, Clear & Insert</button>
224
+ </div>
225
+ </div>
226
+ <form method="dialog" class="modal-backdrop">
227
+ <button>close</button>
228
+ </form>
229
+ </dialog>
230
+
231
+ <script id="seed-config" type="application/json">{{{json seedConfig}}}</script>
232
+ <script src="{{basePath}}/js/seed.js" defer></script>
@@ -1,12 +1,21 @@
1
+ {{!-- Page: Table Records Browser & Pagination · URL: /tables/:table (or /{basePath}/tables/:table) --}}
1
2
  <div class="space-y-5">
3
+
2
4
  <!-- Header -->
3
5
  <div class="flex flex-wrap items-center justify-between gap-3">
4
6
  <div class="flex items-center gap-3.5">
5
7
  <span class="grid h-12 w-12 place-items-center rounded-xl bg-primary/10 text-primary">
6
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
8
+ {{{icon "table" class="h-6 w-6"}}}
7
9
  </span>
8
10
  <div>
9
- <h1 class="font-mono text-2xl font-bold tracking-tight">{{table}}</h1>
11
+ <div class="flex items-center gap-2">
12
+ <h1 class="font-mono text-2xl font-bold tracking-tight">{{table}}</h1>
13
+ {{#if isPostgres}}
14
+ <span class="rounded bg-info/15 px-2 py-0.5 text-[11px] font-bold font-sans text-info">PostgreSQL</span>
15
+ {{else}}
16
+ <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>
17
+ {{/if}}
18
+ </div>
10
19
  <p class="text-sm text-base-content/55">
11
20
  {{count}} row(s) · {{info.columns.length}} column(s)
12
21
  {{#if pkCols.length}}· PK <span class="font-mono">{{join pkCols ", "}}</span>{{/if}}
@@ -15,18 +24,18 @@
15
24
  </div>
16
25
  <div class="flex flex-wrap gap-2">
17
26
  <a href="{{basePath}}/tables/{{table}}/schema" class="btn btn-ghost btn-sm gap-1.5">
18
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
27
+ {{{icon "schema" class="h-4 w-4"}}}
19
28
  Edit schema
20
29
  </a>
21
30
  <a href="{{basePath}}/export" class="btn btn-ghost btn-sm gap-1.5">
22
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
31
+ {{{icon "export" class="h-4 w-4"}}}
23
32
  SQL dump
24
33
  </a>
25
34
  <div class="dropdown dropdown-end">
26
35
  <div tabindex="0" role="button" class="btn btn-outline btn-sm gap-1.5">
27
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
36
+ {{{icon "export" class="h-4 w-4"}}}
28
37
  Export
29
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" /></svg>
38
+ {{{icon "chevronDown" class="h-3.5 w-3.5"}}}
30
39
  </div>
31
40
  <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">
32
41
  <li><a href="{{basePath}}/api/tables/{{table}}/export?format=csv">CSV</a></li>
@@ -35,39 +44,41 @@
35
44
  </div>
36
45
  {{#unless readonly}}
37
46
  <button id="import-csv" type="button" class="btn btn-outline btn-sm gap-1.5">
38
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" /></svg>
47
+ {{{icon "import" class="h-4 w-4"}}}
39
48
  Import CSV
40
49
  </button>
41
50
  {{/unless}}
42
- <a href="{{basePath}}/query" class="btn btn-outline btn-sm gap-1.5">
43
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
44
- Query
51
+ <a href="{{basePath}}/tables/{{table}}/seed" class="btn btn-outline btn-sm gap-1.5">
52
+ {{{icon "seed" class="h-4 w-4"}}}
53
+ Seed data
45
54
  </a>
46
55
  {{#unless readonly}}
47
56
  <a href="{{basePath}}/tables/{{table}}/rows/new" class="btn btn-primary btn-sm gap-1.5">
48
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
57
+ {{{icon "plus" class="h-4 w-4"}}}
49
58
  New row
50
59
  </a>
51
60
  {{/unless}}
52
61
  </div>
53
62
  </div>
54
63
 
55
- <!-- Row filters (collapsed by default; auto-expands when filters are active) -->
64
+ <!-- Row filters -->
56
65
  <details class="app-card overflow-hidden" {{#if filterQS}}open{{/if}}>
57
66
  <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">
58
67
  <span class="flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-base-content/45">
59
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414A1 1 0 0014 15.414V20a1 1 0 01-1 1h-2a1 1 0 01-1-1v-4.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" /></svg>
68
+ {{{icon "filter" class="h-4 w-4"}}}
60
69
  Filter rows
61
70
  {{#if filterQS}}<span class="chip chip-pk">active</span>{{/if}}
62
71
  </span>
63
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 text-base-content/40 transition-transform group-open:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" /></svg>
72
+ <span class="transition-transform group-open:rotate-180 text-base-content/40">
73
+ {{{icon "chevronDown" class="h-4 w-4"}}}
74
+ </span>
64
75
  </summary>
65
76
  <form id="filter-form" class="border-t border-base-200 p-4">
66
77
  <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>&gt;5</code> · <code>pre*</code> starts with · plain text = contains.</p>
67
78
  <div id="filter-fields" class="grid grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-4"></div>
68
79
  <div class="mt-3 flex flex-wrap items-center gap-2">
69
80
  <button type="submit" class="btn btn-primary btn-sm gap-1.5">
70
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
81
+ {{{icon "search" class="h-4 w-4"}}}
71
82
  Apply filter
72
83
  </button>
73
84
  <button type="button" id="clear-filter" class="btn btn-ghost btn-sm">Clear</button>
@@ -89,7 +100,7 @@
89
100
  <div class="flex flex-wrap items-center gap-3">
90
101
  <input type="file" id="import-csv-file" class="file-input file-input-sm w-full max-w-xs" accept=".csv,text/csv" />
91
102
  <button id="import-csv-submit" type="button" class="btn btn-primary btn-sm gap-1.5">
92
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" /></svg>
103
+ {{{icon "import" class="h-4 w-4"}}}
93
104
  Import
94
105
  </button>
95
106
  </div>
@@ -100,32 +111,39 @@
100
111
  {{#if rows.length}}
101
112
  <div class="app-card overflow-hidden">
102
113
  <!-- Toolbar -->
103
- <div class="flex flex-wrap items-center justify-between gap-2 border-b border-base-200 px-4 py-3">
104
- <span class="text-sm text-base-content/55">Showing <b class="tabular-nums">{{firstRow}}</b>–<b class="tabular-nums">{{lastRow}}</b> of <b class="tabular-nums">{{count}}</b> rows</span>
105
- <label class="flex items-center gap-2 text-sm text-base-content/55">
106
- Rows per page
107
- <select id="page-size" class="field-select !w-auto !py-1.5 !text-[13px]" data-table="{{table}}" data-orderby="{{orderBy}}" data-orderdir="{{orderDir}}" data-filters="{{filterQS}}">
108
- {{#each sizes}}<option value="{{this}}" {{#if (eq this ../pageSize)}}selected{{/if}}>{{this}}</option>{{/each}}
109
- </select>
110
- </label>
114
+ <div class="flex flex-wrap items-center justify-between gap-2 border-b border-base-200 px-4 py-2.5">
115
+ <div class="flex flex-wrap items-center gap-3">
116
+ <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>
117
+ <span class="hidden xl:inline-flex items-center gap-1.5 text-xs text-base-content/40 font-medium">
118
+ <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
119
+ </span>
120
+ </div>
121
+ <div class="flex flex-wrap items-center gap-2.5">
122
+ <label class="flex items-center gap-2 text-xs text-base-content/55">
123
+ Per page
124
+ <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}}">
125
+ {{#each sizes}}<option value="{{this}}" {{#if (eq this ../pageSize)}}selected{{/if}}>{{this}}</option>{{/each}}
126
+ </select>
127
+ </label>
128
+ </div>
111
129
  </div>
112
130
 
113
131
  <!-- Bulk selection bar -->
114
- <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.5">
132
+ <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">
115
133
  <span class="text-sm font-medium text-base-content/80">
116
134
  <b id="bulk-count" class="tabular-nums text-primary">0</b> row(s) selected
117
135
  </span>
118
136
  <div class="flex flex-wrap items-center gap-1.5">
119
137
  <button id="bulk-export-csv" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
120
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
138
+ {{{icon "export" class="h-3.5 w-3.5"}}}
121
139
  Export CSV
122
140
  </button>
123
141
  <button id="bulk-export-json" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
124
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
142
+ {{{icon "export" class="h-3.5 w-3.5"}}}
125
143
  Export JSON
126
144
  </button>
127
145
  <button id="bulk-delete" type="button" class="btn btn-error btn-xs gap-1" {{#if readonly}}disabled{{/if}}>
128
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
146
+ {{{icon "trash" class="h-3.5 w-3.5"}}}
129
147
  Delete
130
148
  </button>
131
149
  <button id="bulk-clear" type="button" class="btn btn-ghost btn-xs text-base-content/60">Clear</button>
@@ -133,14 +151,12 @@
133
151
  </div>
134
152
 
135
153
  <div class="overflow-x-auto">
136
- <table class="table table-sm table-pin-rows">
154
+ <table id="main-table" class="table table-compact table-pin-rows">
137
155
  <thead>
138
156
  <tr class="bg-base-200/70 text-xs uppercase tracking-wide text-base-content/50">
139
- {{#if hasPk}}
140
- <th class="w-10 text-center font-semibold">
141
- <input type="checkbox" id="select-all-rows" class="checkbox checkbox-primary checkbox-xs" title="Select all rows on this page" />
142
- </th>
143
- {{/if}}
157
+ <th class="w-10 text-center font-semibold">
158
+ <input type="checkbox" id="select-all-rows" class="checkbox checkbox-primary checkbox-xs" title="Select all rows on this page" />
159
+ </th>
144
160
  {{#each colHeaders}}
145
161
  <th class="whitespace-nowrap font-semibold">
146
162
  <a href="{{this.href}}" class="sort-link {{#if this.active}}active{{/if}}" title="Sort by {{this.name}}">
@@ -150,7 +166,7 @@
150
166
  </a>
151
167
  </th>
152
168
  {{/each}}
153
- <th class="text-right font-semibold">Actions</th>
169
+ <th class="text-right font-semibold sticky-col-actions">Actions</th>
154
170
  {{#each refColumns}}
155
171
  <th class="whitespace-nowrap text-right font-semibold" title="Rows in {{this.table}} where {{this.from}} references {{this.to}}">
156
172
  <span class="font-mono normal-case">{{this.table}}</span>
@@ -160,52 +176,122 @@
160
176
  </thead>
161
177
  <tbody>
162
178
  {{#each rows}}
163
- <tr class="group hover:bg-primary/[0.04]" data-table="{{../table}}" data-pk="{{this.pkEncoded}}">
164
- {{#if ../hasPk}}
165
- <td class="w-10 text-center align-middle">
166
- {{#if this.pkEncoded}}
167
- <input type="checkbox" class="row-checkbox checkbox checkbox-primary checkbox-xs" value="{{this.pkEncoded}}" aria-label="Select this row" />
168
- {{/if}}
169
- </td>
170
- {{/if}}
179
+ <tr class="group hover:bg-primary/[0.04]" data-table="{{@root.table}}" data-pk="{{this.pkEncoded}}">
180
+ <td class="w-10 text-center align-middle">
181
+ {{#if this.pkEncoded}}
182
+ <input type="checkbox" class="row-checkbox checkbox checkbox-primary checkbox-xs" value="{{this.pkEncoded}}" aria-label="Select this row" />
183
+ {{/if}}
184
+ </td>
171
185
  {{#each this.cells}}
172
- <td class="max-w-xs align-middle cell-editable {{#unless ../../readonly}}js-inline-cell{{/unless}}"
173
- data-col="{{this.name}}" data-null="{{this.isNull}}" data-value="{{this.display}}">
186
+ <td class="max-w-xs align-middle cell-editable {{#unless @root.readonly}}js-inline-cell{{/unless}}"
187
+ data-col="{{this.name}}" data-null="{{this.isNull}}" data-value="{{this.display}}"
188
+ data-blob="{{this.isBlob}}" data-json="{{this.isJson}}" data-type="{{this.type}}">
174
189
  {{#if this.isNull}}
175
190
  <span class="chip chip-null js-cell-value">NULL</span>
191
+ {{else if this.isImage}}
192
+ <div class="flex items-center gap-2 js-cell-value">
193
+ <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" />
194
+ <span class="chip chip-type text-[9px] font-mono">{{this.blobExt}} · {{this.blobSize}}</span>
195
+ <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">
196
+ {{{icon "inspect" class="h-3 w-3"}}}
197
+ </button>
198
+ </div>
199
+ {{else if this.isBlob}}
200
+ <div class="flex items-center gap-1.5 js-cell-value">
201
+ <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>
202
+ <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">
203
+ {{{icon "inspect" class="h-3 w-3"}}}
204
+ </button>
205
+ </div>
206
+ {{else if this.isJson}}
207
+ <div class="flex items-center gap-1.5 js-cell-value">
208
+ <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>
209
+ <span class="block max-w-[140px] truncate font-mono text-[12px] text-base-content/70" title="{{this.display}}">{{this.display}}</span>
210
+ <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">
211
+ {{{icon "inspect" class="h-3 w-3"}}}
212
+ </button>
213
+ </div>
214
+ {{else if this.isColor}}
215
+ <div class="flex items-center gap-1.5 js-cell-value">
216
+ <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>
217
+ <span class="font-mono text-[13px]">{{this.display}}</span>
218
+ </div>
219
+ {{else if this.isUrl}}
220
+ <div class="flex items-center gap-1.5 js-cell-value">
221
+ <span class="block max-w-xs truncate font-mono text-[13px] text-primary underline" title="{{this.display}}">{{this.display}}</span>
222
+ <a href="{{this.display}}" target="_blank" rel="noopener" class="text-base-content/40 hover:text-primary" title="Open link">↗</a>
223
+ </div>
176
224
  {{else}}
177
- <span class="js-cell-value block max-w-xs truncate font-mono text-[13px]" title="{{this.display}}">{{truncate this.display 120}}</span>
225
+ <div class="flex items-center justify-between gap-1 group/cell">
226
+ <span class="js-cell-value block max-w-xs truncate font-mono text-[13px]" title="{{this.display}}">{{truncate this.display 120}}</span>
227
+ {{#if (gt this.display.length 30)}}
228
+ <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">
229
+ {{{icon "inspect" class="h-3 w-3"}}}
230
+ </button>
231
+ {{/if}}
232
+ </div>
178
233
  {{/if}}
179
234
  </td>
180
235
  {{/each}}
181
- <td class="whitespace-nowrap text-right align-middle">
182
- {{#unless ../readonly}}
183
- {{#if this.pkEncoded}}
184
- <div class="row-actions">
185
- <a href="{{../basePath}}/tables/{{../table}}/rows/{{this.pkEncoded}}/edit" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
186
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
187
- Edit
188
- </a>
189
- <button type="button" class="btn btn-ghost btn-xs gap-1 text-error js-delete-row"
190
- data-table="{{../table}}" data-pk="{{this.pkEncoded}}">
191
- <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
192
- Delete
193
- </button>
236
+ <td class="whitespace-nowrap text-right align-middle sticky-col-actions">
237
+ {{#if this.pkEncoded}}
238
+ <div class="row-actions">
239
+ {{#unless @root.readonly}}
240
+ <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">
241
+ {{{icon "edit" class="h-3.5 w-3.5"}}}
242
+ Edit
243
+ </a>
244
+ {{/unless}}
245
+ <div class="dropdown dropdown-end">
246
+ <label tabindex="0" role="button" class="btn btn-ghost btn-xs btn-square text-base-content/60 hover:text-base-content" title="Row actions">
247
+ {{{icon "dotsVertical" class="h-3.5 w-3.5"}}}
248
+ </label>
249
+ <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">
250
+ <li>
251
+ <button type="button" class="js-row-copy-json flex items-center gap-2" data-pk="{{this.pkEncoded}}">
252
+ {{{icon "copy" class="h-3.5 w-3.5 text-base-content/60"}}}
253
+ Copy as JSON
254
+ </button>
255
+ </li>
256
+ <li>
257
+ <button type="button" class="js-row-copy-sql flex items-center gap-2" data-pk="{{this.pkEncoded}}">
258
+ {{{icon "sql" class="h-3.5 w-3.5 text-base-content/60"}}}
259
+ Copy SQL INSERT
260
+ </button>
261
+ </li>
262
+ {{#unless @root.readonly}}
263
+ <li>
264
+ <button type="button" class="js-row-duplicate flex items-center gap-2" data-pk="{{this.pkEncoded}}">
265
+ {{{icon "duplicate" class="h-3.5 w-3.5 text-base-content/60"}}}
266
+ Duplicate Row
267
+ </button>
268
+ </li>
269
+ <div class="divider my-1"></div>
270
+ <li>
271
+ <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}}">
272
+ {{{icon "trash" class="h-3.5 w-3.5"}}}
273
+ Delete Row
274
+ </button>
275
+ </li>
276
+ {{/unless}}
277
+ </ul>
278
+ </div>
194
279
  </div>
195
- {{else}}
196
- <span class="text-xs text-base-content/35">No PK</span>
197
- {{/if}}
198
- {{/unless}}
280
+ {{else}}
281
+ <span class="text-xs text-base-content/35">No PK</span>
282
+ {{/if}}
199
283
  </td>
200
284
  {{#each this.refs}}
201
285
  <td class="whitespace-nowrap text-right align-middle">
202
286
  {{#if this.value}}
203
287
  <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}}"
204
- data-table="{{../../table}}" data-pk="{{../pkEncoded}}" data-ref-table="{{this.table}}"
288
+ data-table="{{@root.table}}" data-pk="{{../pkEncoded}}" data-ref-table="{{this.table}}"
205
289
  aria-expanded="false" title="View {{this.table}} rows where {{this.from}} = {{this.value}}">
206
290
  <span class="font-mono normal-case">{{this.table}}</span>
207
291
  <span class="rounded-md bg-primary/10 px-1.5 py-0.5 font-semibold tabular-nums text-primary">{{this.count}}</span>
208
- <svg xmlns="http://www.w3.org/2000/svg" class="js-ref-chevron h-3 w-3 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" /></svg>
292
+ <span class="js-ref-chevron transition-transform inline-block">
293
+ {{{icon "chevronDown" class="h-3 w-3"}}}
294
+ </span>
209
295
  </button>
210
296
  {{else}}
211
297
  <span class="text-xs text-base-content/35">&mdash;</span>
@@ -225,13 +311,13 @@
225
311
  <div class="flex gap-1.5">
226
312
  <a href="{{basePath}}/tables/{{table}}?page={{sub page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
227
313
  class="btn btn-outline btn-sm {{#unless (gt page 1)}}btn-disabled{{/unless}}" aria-disabled="{{#if (gt page 1)}}false{{else}}true{{/if}}">
228
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" /></svg>
314
+ {{{icon "chevronLeft" class="h-4 w-4"}}}
229
315
  Prev
230
316
  </a>
231
317
  <a href="{{basePath}}/tables/{{table}}?page={{add page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
232
318
  class="btn btn-outline btn-sm {{#unless (lt page pages)}}btn-disabled{{/unless}}" aria-disabled="{{#if (lt page pages)}}false{{else}}true{{/if}}">
233
319
  Next
234
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
320
+ {{{icon "chevronRight" class="h-4 w-4"}}}
235
321
  </a>
236
322
  </div>
237
323
  </div>
@@ -239,13 +325,12 @@
239
325
  </div>
240
326
  {{else}}
241
327
  <div class="empty-state">
242
- <span class="empty-icon">
243
- <svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /></svg>
244
- </span>
328
+ <span class="empty-icon">{{{icon "book" class="h-7 w-7"}}}</span>
245
329
  <p class="text-sm font-medium text-base-content/70">No rows in <span class="font-mono">{{table}}</span></p>
246
330
  <div class="mt-2 flex gap-2">
247
331
  {{#unless readonly}}
248
332
  <a href="{{basePath}}/tables/{{table}}/rows/new" class="btn btn-primary btn-sm">Insert a row</a>
333
+ <a href="{{basePath}}/tables/{{table}}/seed" class="btn btn-outline btn-sm">Generate sample data</a>
249
334
  {{/unless}}
250
335
  <a href="{{basePath}}/query" class="btn btn-outline btn-sm">Run a query</a>
251
336
  </div>
@@ -0,0 +1,2 @@
1
+ {{!-- Partial: Dynamic SVG Icon Renderer · Helper: {{{icon "name" class="..."}}} --}}
2
+ {{{icon name class=class}}}