admindb 1.2.1 → 2.1.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 (208) hide show
  1. package/README.md +262 -329
  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 -146
  56. package/dist/db/database.js +208 -461
  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 +74 -11
  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 +55 -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 +185 -52
  96. package/dist/public/js/seed.js +865 -152
  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 +157 -64
  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 +7 -3
  131. package/dist/sql/generator.js +144 -7
  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 +40 -11
  159. package/dist/views/pages/databases.hbs +182 -74
  160. package/dist/views/pages/designer.hbs +29 -11
  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 +8 -6
  166. package/dist/views/pages/schema.hbs +41 -13
  167. package/dist/views/pages/seed.hbs +200 -54
  168. package/dist/views/pages/table.hbs +152 -72
  169. package/dist/views/partials/icon.hbs +2 -0
  170. package/dist/views/partials/navbar.hbs +69 -25
  171. package/dist/views/partials/sidebar.hbs +108 -94
  172. package/docs/API.md +144 -0
  173. package/docs/EXAMPLES.md +576 -0
  174. package/docs/SECURITY.md +265 -0
  175. package/package.json +15 -4
  176. package/dist/args.d.ts +0 -28
  177. package/dist/args.js +0 -187
  178. package/dist/args.js.map +0 -1
  179. package/dist/config.d.ts +0 -17
  180. package/dist/config.js +0 -23
  181. package/dist/config.js.map +0 -1
  182. package/dist/csv.js.map +0 -1
  183. package/dist/data/generator.d.ts +0 -86
  184. package/dist/data/generator.js +0 -1031
  185. package/dist/data/generator.js.map +0 -1
  186. package/dist/logger.js +0 -25
  187. package/dist/logger.js.map +0 -1
  188. package/dist/routes/api.d.ts +0 -9
  189. package/dist/routes/api.js +0 -848
  190. package/dist/routes/api.js.map +0 -1
  191. package/dist/test/classifier.test.d.ts +0 -1
  192. package/dist/test/classifier.test.js +0 -44
  193. package/dist/test/classifier.test.js.map +0 -1
  194. package/dist/test/csv.test.d.ts +0 -1
  195. package/dist/test/csv.test.js +0 -64
  196. package/dist/test/csv.test.js.map +0 -1
  197. package/dist/test/database.test.d.ts +0 -1
  198. package/dist/test/database.test.js +0 -402
  199. package/dist/test/database.test.js.map +0 -1
  200. package/dist/test/generator.test.d.ts +0 -1
  201. package/dist/test/generator.test.js +0 -132
  202. package/dist/test/generator.test.js.map +0 -1
  203. package/dist/test/manager.test.d.ts +0 -1
  204. package/dist/test/manager.test.js +0 -146
  205. package/dist/test/manager.test.js.map +0 -1
  206. package/dist/util.js.map +0 -1
  207. /package/dist/{csv.d.ts → utils/csv.d.ts} +0 -0
  208. /package/dist/{csv.js → utils/csv.js} +0 -0
@@ -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,43 +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
51
  <a href="{{basePath}}/tables/{{table}}/seed" 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="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" /></svg>
52
+ {{{icon "seed" class="h-4 w-4"}}}
44
53
  Seed data
45
54
  </a>
46
- <a href="{{basePath}}/query" class="btn btn-outline btn-sm gap-1.5">
47
- <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>
48
- Query
49
- </a>
50
55
  {{#unless readonly}}
51
56
  <a href="{{basePath}}/tables/{{table}}/rows/new" class="btn btn-primary btn-sm gap-1.5">
52
- <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"}}}
53
58
  New row
54
59
  </a>
55
60
  {{/unless}}
56
61
  </div>
57
62
  </div>
58
63
 
59
- <!-- Row filters (collapsed by default; auto-expands when filters are active) -->
64
+ <!-- Row filters -->
60
65
  <details class="app-card overflow-hidden" {{#if filterQS}}open{{/if}}>
61
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">
62
67
  <span class="flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-base-content/45">
63
- <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"}}}
64
69
  Filter rows
65
70
  {{#if filterQS}}<span class="chip chip-pk">active</span>{{/if}}
66
71
  </span>
67
- <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>
68
75
  </summary>
69
76
  <form id="filter-form" class="border-t border-base-200 p-4">
70
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>
71
78
  <div id="filter-fields" class="grid grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-4"></div>
72
79
  <div class="mt-3 flex flex-wrap items-center gap-2">
73
80
  <button type="submit" class="btn btn-primary btn-sm gap-1.5">
74
- <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"}}}
75
82
  Apply filter
76
83
  </button>
77
84
  <button type="button" id="clear-filter" class="btn btn-ghost btn-sm">Clear</button>
@@ -93,7 +100,7 @@
93
100
  <div class="flex flex-wrap items-center gap-3">
94
101
  <input type="file" id="import-csv-file" class="file-input file-input-sm w-full max-w-xs" accept=".csv,text/csv" />
95
102
  <button id="import-csv-submit" type="button" class="btn btn-primary btn-sm gap-1.5">
96
- <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"}}}
97
104
  Import
98
105
  </button>
99
106
  </div>
@@ -104,32 +111,39 @@
104
111
  {{#if rows.length}}
105
112
  <div class="app-card overflow-hidden">
106
113
  <!-- Toolbar -->
107
- <div class="flex flex-wrap items-center justify-between gap-2 border-b border-base-200 px-4 py-3">
108
- <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>
109
- <label class="flex items-center gap-2 text-sm text-base-content/55">
110
- Rows per page
111
- <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}}">
112
- {{#each sizes}}<option value="{{this}}" {{#if (eq this ../pageSize)}}selected{{/if}}>{{this}}</option>{{/each}}
113
- </select>
114
- </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>
115
129
  </div>
116
130
 
117
131
  <!-- Bulk selection bar -->
118
- <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">
119
133
  <span class="text-sm font-medium text-base-content/80">
120
134
  <b id="bulk-count" class="tabular-nums text-primary">0</b> row(s) selected
121
135
  </span>
122
136
  <div class="flex flex-wrap items-center gap-1.5">
123
137
  <button id="bulk-export-csv" 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>
138
+ {{{icon "export" class="h-3.5 w-3.5"}}}
125
139
  Export CSV
126
140
  </button>
127
141
  <button id="bulk-export-json" type="button" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
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="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"}}}
129
143
  Export JSON
130
144
  </button>
131
145
  <button id="bulk-delete" type="button" class="btn btn-error btn-xs gap-1" {{#if readonly}}disabled{{/if}}>
132
- <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"}}}
133
147
  Delete
134
148
  </button>
135
149
  <button id="bulk-clear" type="button" class="btn btn-ghost btn-xs text-base-content/60">Clear</button>
@@ -137,14 +151,12 @@
137
151
  </div>
138
152
 
139
153
  <div class="overflow-x-auto">
140
- <table class="table table-sm table-pin-rows">
154
+ <table id="main-table" class="table table-compact table-pin-rows">
141
155
  <thead>
142
156
  <tr class="bg-base-200/70 text-xs uppercase tracking-wide text-base-content/50">
143
- {{#if hasPk}}
144
- <th class="w-10 text-center font-semibold">
145
- <input type="checkbox" id="select-all-rows" class="checkbox checkbox-primary checkbox-xs" title="Select all rows on this page" />
146
- </th>
147
- {{/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>
148
160
  {{#each colHeaders}}
149
161
  <th class="whitespace-nowrap font-semibold">
150
162
  <a href="{{this.href}}" class="sort-link {{#if this.active}}active{{/if}}" title="Sort by {{this.name}}">
@@ -154,7 +166,7 @@
154
166
  </a>
155
167
  </th>
156
168
  {{/each}}
157
- <th class="text-right font-semibold">Actions</th>
169
+ <th class="text-right font-semibold sticky-col-actions">Actions</th>
158
170
  {{#each refColumns}}
159
171
  <th class="whitespace-nowrap text-right font-semibold" title="Rows in {{this.table}} where {{this.from}} references {{this.to}}">
160
172
  <span class="font-mono normal-case">{{this.table}}</span>
@@ -164,52 +176,122 @@
164
176
  </thead>
165
177
  <tbody>
166
178
  {{#each rows}}
167
- <tr class="group hover:bg-primary/[0.04]" data-table="{{../table}}" data-pk="{{this.pkEncoded}}">
168
- {{#if ../hasPk}}
169
- <td class="w-10 text-center align-middle">
170
- {{#if this.pkEncoded}}
171
- <input type="checkbox" class="row-checkbox checkbox checkbox-primary checkbox-xs" value="{{this.pkEncoded}}" aria-label="Select this row" />
172
- {{/if}}
173
- </td>
174
- {{/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>
175
185
  {{#each this.cells}}
176
- <td class="max-w-xs align-middle cell-editable {{#unless ../../readonly}}js-inline-cell{{/unless}}"
177
- 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}}">
178
189
  {{#if this.isNull}}
179
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>
180
224
  {{else}}
181
- <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>
182
233
  {{/if}}
183
234
  </td>
184
235
  {{/each}}
185
- <td class="whitespace-nowrap text-right align-middle">
186
- {{#unless ../readonly}}
187
- {{#if this.pkEncoded}}
188
- <div class="row-actions">
189
- <a href="{{../basePath}}/tables/{{../table}}/rows/{{this.pkEncoded}}/edit" class="btn btn-ghost btn-xs gap-1 text-base-content/70 hover:text-primary">
190
- <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>
191
- Edit
192
- </a>
193
- <button type="button" class="btn btn-ghost btn-xs gap-1 text-error js-delete-row"
194
- data-table="{{../table}}" data-pk="{{this.pkEncoded}}">
195
- <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>
196
- Delete
197
- </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>
198
279
  </div>
199
- {{else}}
200
- <span class="text-xs text-base-content/35">No PK</span>
201
- {{/if}}
202
- {{/unless}}
280
+ {{else}}
281
+ <span class="text-xs text-base-content/35">No PK</span>
282
+ {{/if}}
203
283
  </td>
204
284
  {{#each this.refs}}
205
285
  <td class="whitespace-nowrap text-right align-middle">
206
286
  {{#if this.value}}
207
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}}"
208
- data-table="{{../../table}}" data-pk="{{../pkEncoded}}" data-ref-table="{{this.table}}"
288
+ data-table="{{@root.table}}" data-pk="{{../pkEncoded}}" data-ref-table="{{this.table}}"
209
289
  aria-expanded="false" title="View {{this.table}} rows where {{this.from}} = {{this.value}}">
210
290
  <span class="font-mono normal-case">{{this.table}}</span>
211
291
  <span class="rounded-md bg-primary/10 px-1.5 py-0.5 font-semibold tabular-nums text-primary">{{this.count}}</span>
212
- <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>
213
295
  </button>
214
296
  {{else}}
215
297
  <span class="text-xs text-base-content/35">&mdash;</span>
@@ -229,13 +311,13 @@
229
311
  <div class="flex gap-1.5">
230
312
  <a href="{{basePath}}/tables/{{table}}?page={{sub page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
231
313
  class="btn btn-outline btn-sm {{#unless (gt page 1)}}btn-disabled{{/unless}}" aria-disabled="{{#if (gt page 1)}}false{{else}}true{{/if}}">
232
- <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"}}}
233
315
  Prev
234
316
  </a>
235
317
  <a href="{{basePath}}/tables/{{table}}?page={{add page 1}}&size={{pageSize}}&orderBy={{orderBy}}&orderDir={{orderDir}}{{#if filterQS}}&{{filterQS}}{{/if}}"
236
318
  class="btn btn-outline btn-sm {{#unless (lt page pages)}}btn-disabled{{/unless}}" aria-disabled="{{#if (lt page pages)}}false{{else}}true{{/if}}">
237
319
  Next
238
- <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"}}}
239
321
  </a>
240
322
  </div>
241
323
  </div>
@@ -243,9 +325,7 @@
243
325
  </div>
244
326
  {{else}}
245
327
  <div class="empty-state">
246
- <span class="empty-icon">
247
- <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>
248
- </span>
328
+ <span class="empty-icon">{{{icon "book" class="h-7 w-7"}}}</span>
249
329
  <p class="text-sm font-medium text-base-content/70">No rows in <span class="font-mono">{{table}}</span></p>
250
330
  <div class="mt-2 flex gap-2">
251
331
  {{#unless readonly}}
@@ -0,0 +1,2 @@
1
+ {{!-- Partial: Dynamic SVG Icon Renderer · Helper: {{{icon "name" class="..."}}} --}}
2
+ {{{icon name class=class}}}
@@ -1,45 +1,89 @@
1
+ {{!-- Partial: Top Navigation Bar Header --}}
1
2
  <div class="sticky top-0 z-30 border-b border-base-300/80 bg-base-100/85 backdrop-blur-md">
3
+
2
4
  <div class="navbar mx-auto w-full max-w-[1500px] px-4 sm:px-6 lg:px-8">
3
5
  <div class="flex-none lg:hidden">
4
6
  <label for="sidebar-toggle" class="btn btn-square btn-ghost" aria-label="open sidebar">
5
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
6
- <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
7
- </svg>
7
+ {{{icon "menu" class="h-5 w-5"}}}
8
8
  </label>
9
9
  </div>
10
- <div class="flex-1">
10
+ <div class="flex flex-1 items-center gap-3">
11
11
  <a href="{{basePath}}/" class="flex items-center gap-2.5">
12
12
  <span class="text-lg font-bold tracking-tight">Admin<span class="text-primary">DB</span></span>
13
13
  </a>
14
+ {{#unless databasesMode}}
15
+ {{#if isPostgres}}
16
+ <span class="inline-flex items-center gap-1.5 rounded-lg border border-info/40 bg-info/10 px-2.5 py-0.5 text-xs font-semibold text-info" title="Engine: PostgreSQL ({{dbPath}})">
17
+ <span class="h-2 w-2 rounded-full bg-info animate-pulse"></span>
18
+ PostgreSQL
19
+ </span>
20
+ {{else}}
21
+ <span class="inline-flex items-center gap-1.5 rounded-lg border border-emerald-500/40 bg-emerald-500/10 px-2.5 py-0.5 text-xs font-semibold text-emerald-600 dark:text-emerald-400" title="Engine: SQLite ({{dbPath}})">
22
+ <span class="h-2 w-2 rounded-full bg-emerald-500 animate-pulse"></span>
23
+ SQLite
24
+ </span>
25
+ {{/if}}
26
+ {{/unless}}
14
27
  </div>
28
+
15
29
  <div class="flex items-center gap-1.5">
16
- {{#unless databasesMode}}
17
- {{#if databasesUrl}}
18
- <a href="{{databasesUrl}}" class="btn btn-ghost btn-sm hidden gap-1.5 sm:inline-flex">
19
- <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 7v10c0 1.1.9 2 2 2h12a2 2 0 002-2V7M4 7c0-1.1.9-2 2-2h12a2 2 0 012 2M4 7c0 1.1.9 2 2 2h12a2 2 0 002-2" /></svg>
20
- Databases
30
+ {{#unless noSidebar}}
31
+ {{#unless databasesMode}}
32
+ {{#if databasesUrl}}
33
+ <a href="{{databasesUrl}}" class="btn btn-ghost btn-sm hidden gap-1.5 sm:inline-flex">
34
+ {{{icon "database" class="h-4 w-4"}}}
35
+ Databases
36
+ </a>
37
+ {{/if}}
38
+ <a href="{{basePath}}/query" class="btn btn-ghost btn-sm hidden gap-1.5 sm:inline-flex">
39
+ {{{icon "query" class="h-4 w-4"}}}
40
+ Query
21
41
  </a>
22
- {{/if}}
23
- <a href="{{basePath}}/query" class="btn btn-ghost btn-sm hidden gap-1.5 sm:inline-flex">
24
- <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>
25
- Query
26
- </a>
27
- {{#unless readonly}}
28
- <a href="{{basePath}}/designer" class="btn btn-ghost btn-sm hidden gap-1.5 sm:inline-flex">
29
- <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>
30
- New table
42
+ {{#unless readonly}}
43
+ <a href="{{basePath}}/designer" class="btn btn-ghost btn-sm hidden gap-1.5 sm:inline-flex">
44
+ {{{icon "plus" class="h-4 w-4"}}}
45
+ New table
46
+ </a>
47
+ {{/unless}}
48
+ <a href="{{basePath}}/export" class="btn btn-ghost btn-sm hidden gap-1.5 md:inline-flex">
49
+ {{{icon "export" class="h-4 w-4"}}}
50
+ Export
31
51
  </a>
32
52
  {{/unless}}
33
- <a href="{{basePath}}/export" class="btn btn-ghost btn-sm hidden gap-1.5 md:inline-flex">
34
- <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>
35
- Export
36
- </a>
37
53
  {{/unless}}
54
+
55
+ {{#if authEnabled}}
56
+ {{#if authUser}}
57
+ {{#if isDefaultPassword}}
58
+ <span class="badge badge-warning badge-sm gap-1 hidden sm:inline-flex font-mono text-[11px]" title="Using default password (admin). Set ADMINDB_PASSWORD in production.">
59
+ {{{icon "warning" class="h-3.5 w-3.5"}}}
60
+ default pass
61
+ </span>
62
+ {{/if}}
63
+ <div class="dropdown dropdown-end">
64
+ <label tabindex="0" class="btn btn-ghost btn-sm gap-1.5 normal-case font-medium">
65
+ {{{icon "user" class="h-4 w-4 text-base-content/70"}}}
66
+ <span class="max-w-[120px] truncate text-xs">{{authUser}}</span>
67
+ </label>
68
+ <ul tabindex="0" class="dropdown-content menu p-2 shadow-soft bg-base-100 rounded-xl w-48 border border-base-300 z-50 mt-1 text-sm">
69
+ <li class="menu-title px-2 py-1 text-xs text-base-content/50">Signed in as <strong class="text-base-content">{{authUser}}</strong></li>
70
+ <li>
71
+ <form method="POST" action="{{basePath}}/logout" class="p-0">
72
+ <button type="submit" class="w-full text-left flex items-center gap-2 px-3 py-2 text-error hover:bg-error/10 rounded-lg">
73
+ {{{icon "logout" class="h-4 w-4"}}}
74
+ Sign out
75
+ </button>
76
+ </form>
77
+ </li>
78
+ </ul>
79
+ </div>
80
+ {{/if}}
81
+ {{/if}}
82
+
38
83
  <button id="theme-toggle" type="button" class="btn btn-square btn-ghost btn-sm" aria-label="Toggle dark mode" title="Toggle dark mode">
39
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
40
- <path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
41
- </svg>
84
+ {{{icon "moon" class="h-5 w-5"}}}
42
85
  </button>
86
+
43
87
  </div>
44
88
  </div>
45
89
  </div>