admindb 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +1 -69
  3. package/dist/app.js +1 -257
  4. package/dist/auth/config.d.ts +1 -19
  5. package/dist/auth/config.js +1 -147
  6. package/dist/auth/crypto.d.ts +1 -27
  7. package/dist/auth/crypto.js +1 -133
  8. package/dist/auth/index.d.ts +1 -5
  9. package/dist/auth/index.js +1 -22
  10. package/dist/auth/middleware.d.ts +1 -14
  11. package/dist/auth/middleware.js +1 -100
  12. package/dist/auth/routes.d.ts +1 -7
  13. package/dist/auth/routes.js +1 -78
  14. package/dist/auth/types.d.ts +1 -35
  15. package/dist/auth/types.js +1 -11
  16. package/dist/cli/args.d.ts +1 -45
  17. package/dist/cli/args.js +1 -328
  18. package/dist/cli/config.d.ts +1 -56
  19. package/dist/cli/config.js +1 -133
  20. package/dist/cli/index.d.ts +1 -4
  21. package/dist/cli/index.js +1 -21
  22. package/dist/cli/runner.d.ts +1 -1
  23. package/dist/cli/runner.js +1 -222
  24. package/dist/cli.d.ts +1 -2
  25. package/dist/cli.js +1 -5
  26. package/dist/data/datasets.d.ts +1 -17
  27. package/dist/data/datasets.js +1 -133
  28. package/dist/data/detector.d.ts +1 -30
  29. package/dist/data/detector.js +1 -472
  30. package/dist/data/engine.d.ts +1 -10
  31. package/dist/data/engine.js +1 -211
  32. package/dist/data/index.d.ts +1 -5
  33. package/dist/data/index.js +1 -22
  34. package/dist/data/strategies.d.ts +1 -30
  35. package/dist/data/strategies.js +1 -343
  36. package/dist/data/types.d.ts +1 -73
  37. package/dist/data/types.js +1 -8
  38. package/dist/db/database.d.ts +1 -119
  39. package/dist/db/database.js +1 -471
  40. package/dist/db/export.d.ts +1 -12
  41. package/dist/db/export.js +1 -42
  42. package/dist/db/filters.d.ts +1 -22
  43. package/dist/db/filters.js +1 -158
  44. package/dist/db/index.d.ts +1 -8
  45. package/dist/db/index.js +1 -25
  46. package/dist/db/introspection.d.ts +1 -6
  47. package/dist/db/introspection.js +1 -172
  48. package/dist/db/manager.d.ts +1 -81
  49. package/dist/db/manager.js +1 -397
  50. package/dist/db/migrations.d.ts +1 -5
  51. package/dist/db/migrations.js +1 -110
  52. package/dist/db/postgres.d.ts +1 -85
  53. package/dist/db/postgres.js +1 -745
  54. package/dist/db/types.d.ts +1 -206
  55. package/dist/db/types.js +1 -7
  56. package/dist/index.d.ts +1 -23
  57. package/dist/index.js +1 -66
  58. package/dist/public/js/api.js +1 -47
  59. package/dist/public/js/app.js +1 -262
  60. package/dist/public/js/browse.js +1 -1371
  61. package/dist/public/js/databases.js +1 -235
  62. package/dist/public/js/designer.js +1 -258
  63. package/dist/public/js/forms.js +1 -826
  64. package/dist/public/js/icons.js +1 -51
  65. package/dist/public/js/inspector.js +1 -638
  66. package/dist/public/js/query.js +1 -322
  67. package/dist/public/js/schema.js +1 -447
  68. package/dist/public/js/seed.js +1 -1090
  69. package/dist/routes/api/helpers.d.ts +1 -42
  70. package/dist/routes/api/helpers.js +1 -133
  71. package/dist/routes/api/import-export.d.ts +1 -3
  72. package/dist/routes/api/import-export.js +1 -85
  73. package/dist/routes/api/index.d.ts +1 -4
  74. package/dist/routes/api/index.js +1 -37
  75. package/dist/routes/api/query.d.ts +1 -3
  76. package/dist/routes/api/query.js +1 -76
  77. package/dist/routes/api/rows.d.ts +1 -3
  78. package/dist/routes/api/rows.js +1 -373
  79. package/dist/routes/api/seed.d.ts +1 -3
  80. package/dist/routes/api/seed.js +1 -82
  81. package/dist/routes/api/tables.d.ts +1 -3
  82. package/dist/routes/api/tables.js +1 -160
  83. package/dist/routes/databases.d.ts +1 -26
  84. package/dist/routes/databases.js +1 -230
  85. package/dist/routes/index.d.ts +1 -3
  86. package/dist/routes/index.js +1 -20
  87. package/dist/routes/pages.d.ts +1 -9
  88. package/dist/routes/pages.js +1 -390
  89. package/dist/serverless.d.ts +1 -40
  90. package/dist/serverless.js +1 -210
  91. package/dist/sql/classifier.d.ts +1 -19
  92. package/dist/sql/classifier.js +1 -37
  93. package/dist/sql/generator.d.ts +1 -81
  94. package/dist/sql/generator.js +1 -348
  95. package/dist/sql/index.d.ts +1 -2
  96. package/dist/sql/index.js +1 -19
  97. package/dist/types/api.d.ts +1 -412
  98. package/dist/types/api.js +1 -9
  99. package/dist/utils/colors.d.ts +1 -40
  100. package/dist/utils/colors.js +1 -64
  101. package/dist/utils/common.d.ts +1 -50
  102. package/dist/utils/common.js +1 -229
  103. package/dist/utils/csv.d.ts +1 -15
  104. package/dist/utils/csv.js +1 -80
  105. package/dist/utils/datatype.d.ts +1 -49
  106. package/dist/utils/datatype.js +1 -222
  107. package/dist/utils/icons.d.ts +1 -11
  108. package/dist/utils/icons.js +1 -72
  109. package/dist/utils/index.d.ts +1 -4
  110. package/dist/utils/index.js +1 -21
  111. package/dist/utils/logger.d.ts +1 -10
  112. package/dist/utils/logger.js +1 -40
  113. package/dist/views/layouts/main.hbs +0 -4
  114. package/dist/views/pages/databases.hbs +0 -18
  115. package/dist/views/pages/designer.hbs +0 -10
  116. package/dist/views/pages/error.hbs +0 -2
  117. package/dist/views/pages/form.hbs +0 -5
  118. package/dist/views/pages/home.hbs +0 -8
  119. package/dist/views/pages/login.hbs +0 -13
  120. package/dist/views/pages/query.hbs +0 -9
  121. package/dist/views/pages/schema.hbs +0 -20
  122. package/dist/views/pages/seed.hbs +0 -31
  123. package/dist/views/pages/table.hbs +0 -14
  124. package/dist/views/partials/icon.hbs +0 -1
  125. package/dist/views/partials/navbar.hbs +0 -6
  126. package/dist/views/partials/sidebar.hbs +0 -8
  127. package/package.json +3 -2
  128. package/dist/app.js.map +0 -1
  129. package/dist/auth/config.js.map +0 -1
  130. package/dist/auth/crypto.js.map +0 -1
  131. package/dist/auth/index.js.map +0 -1
  132. package/dist/auth/middleware.js.map +0 -1
  133. package/dist/auth/routes.js.map +0 -1
  134. package/dist/auth/types.js.map +0 -1
  135. package/dist/cli/args.js.map +0 -1
  136. package/dist/cli/config.js.map +0 -1
  137. package/dist/cli/index.js.map +0 -1
  138. package/dist/cli/runner.js.map +0 -1
  139. package/dist/cli.js.map +0 -1
  140. package/dist/data/datasets.js.map +0 -1
  141. package/dist/data/detector.js.map +0 -1
  142. package/dist/data/engine.js.map +0 -1
  143. package/dist/data/index.js.map +0 -1
  144. package/dist/data/strategies.js.map +0 -1
  145. package/dist/data/types.js.map +0 -1
  146. package/dist/db/database.js.map +0 -1
  147. package/dist/db/export.js.map +0 -1
  148. package/dist/db/filters.js.map +0 -1
  149. package/dist/db/index.js.map +0 -1
  150. package/dist/db/introspection.js.map +0 -1
  151. package/dist/db/manager.js.map +0 -1
  152. package/dist/db/migrations.js.map +0 -1
  153. package/dist/db/postgres.js.map +0 -1
  154. package/dist/db/types.js.map +0 -1
  155. package/dist/index.js.map +0 -1
  156. package/dist/public/css/input.css +0 -302
  157. package/dist/routes/api/helpers.js.map +0 -1
  158. package/dist/routes/api/import-export.js.map +0 -1
  159. package/dist/routes/api/index.js.map +0 -1
  160. package/dist/routes/api/query.js.map +0 -1
  161. package/dist/routes/api/rows.js.map +0 -1
  162. package/dist/routes/api/seed.js.map +0 -1
  163. package/dist/routes/api/tables.js.map +0 -1
  164. package/dist/routes/databases.js.map +0 -1
  165. package/dist/routes/index.js.map +0 -1
  166. package/dist/routes/pages.js.map +0 -1
  167. package/dist/serverless.js.map +0 -1
  168. package/dist/sql/classifier.js.map +0 -1
  169. package/dist/sql/generator.js.map +0 -1
  170. package/dist/sql/index.js.map +0 -1
  171. package/dist/types/api.js.map +0 -1
  172. package/dist/utils/colors.js.map +0 -1
  173. package/dist/utils/common.js.map +0 -1
  174. package/dist/utils/csv.js.map +0 -1
  175. package/dist/utils/datatype.js.map +0 -1
  176. package/dist/utils/icons.js.map +0 -1
  177. package/dist/utils/index.js.map +0 -1
  178. package/dist/utils/logger.js.map +0 -1
@@ -1,1090 +1 @@
1
- /* "Seed data" page — enhanced interactive data generator.
2
- *
3
- * Lets you generate realistic fake rows for a table with 20+ strategies,
4
- * live preview samples, sparse data simulation, sequential numbering,
5
- * pattern templates, dual Table/SQL previews, CSV/JSON export, and presets.
6
- */
7
- (function () {
8
- 'use strict';
9
-
10
- const cfgEl = document.getElementById('seed-config');
11
- if (!cfgEl) return;
12
-
13
- const cfg = JSON.parse(cfgEl.textContent);
14
- const t = encodeURIComponent(cfg.table);
15
- const columns = cfg.columns || [];
16
-
17
- // DOM Elements
18
- const countInput = document.getElementById('seed-count');
19
- const searchInput = document.getElementById('seed-search');
20
- const truncateCheckbox = document.getElementById('seed-truncate');
21
- const columnsBox = document.getElementById('seed-columns');
22
- const emptySearchBox = document.getElementById('seed-empty-search');
23
- const colCountBadge = document.getElementById('seed-col-count-badge');
24
- const feedback = document.getElementById('seed-feedback');
25
- const previewCard = document.getElementById('seed-preview-card');
26
- const previewSummaryBadge = document.getElementById('preview-summary-badge');
27
- const previewThead = document.getElementById('seed-preview-thead');
28
- const previewTbody = document.getElementById('seed-preview-tbody');
29
- const tableViewContainer = document.getElementById('seed-table-view-container');
30
- const sqlViewContainer = document.getElementById('seed-sql-view-container');
31
- const sqlCode = document.getElementById('seed-sql-code');
32
- const tabTableView = document.getElementById('tab-table-view');
33
- const tabSqlView = document.getElementById('tab-sql-view');
34
-
35
- // Action buttons
36
- const insertBtn = document.getElementById('seed-insert');
37
- const previewBtn = document.getElementById('seed-preview');
38
- const resetBtn = document.getElementById('seed-reset');
39
- const refreshSamplesBtn = document.getElementById('seed-refresh-samples');
40
- const applyAllBtn = document.getElementById('seed-apply-all');
41
- const copySqlBtn = document.getElementById('seed-copy-sql');
42
- const downloadSqlBtn = document.getElementById('seed-download-sql');
43
- const exportCsvBtn = document.getElementById('seed-export-csv');
44
- const exportJsonBtn = document.getElementById('seed-export-json');
45
- const exportConfigBtn = document.getElementById('seed-export-config');
46
- const importConfigBtn = document.getElementById('seed-import-config');
47
-
48
- // Modals
49
- const configDialog = document.getElementById('seed-config-dialog');
50
- const configDialogTitle = document.getElementById('seed-config-dialog-title');
51
- const configTextarea = document.getElementById('seed-config-textarea');
52
- const configDialogCancel = document.getElementById('seed-config-dialog-cancel');
53
- const configDialogApply = document.getElementById('seed-config-dialog-apply');
54
-
55
- const truncateDialog = document.getElementById('seed-truncate-dialog');
56
- const truncateCancel = document.getElementById('seed-truncate-cancel');
57
- const truncateConfirm = document.getElementById('seed-truncate-confirm');
58
-
59
- // State
60
- const state = {};
61
- let lastGeneratedResult = null;
62
-
63
- function escapeHtml(s) {
64
- return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
65
- }
66
-
67
- // ---- Client-side Sample Generator (Fast Live Previews) --------------------
68
-
69
- const SAMPLE_FIRST = ["Sombat", "Minh", "Hiroshi", "William", "Ravi", "Zara", "Elena", "Liam", "Mei", "Noah"];
70
- const SAMPLE_LAST = ["Chen", "Smith", "Thompson", "Shah", "Yamamoto", "Patel", "Garcia", "Kim", "Nguyen", "Davis"];
71
- const SAMPLE_DOMAINS = ["example.com", "acme.io", "tech.asia", "gmail.com", "startup.io"];
72
- const SAMPLE_CITIES = ["New York", "Tokyo", "London", "Singapore", "Paris", "Berlin", "Sydney", "Toronto"];
73
- const SAMPLE_COUNTRIES = ["United States", "Japan", "United Kingdom", "Singapore", "Germany", "France", "Canada", "Australia"];
74
- const SAMPLE_COMPANIES = ["Apex Solutions", "Vortex Digital", "Nexus Systems", "Horizon Labs", "Quantum Dynamics", "Zenith Global"];
75
- const SAMPLE_JOBS = ["Software Engineer", "Product Manager", "UX Designer", "DevOps Specialist", "Data Scientist", "Marketing Director"];
76
- const SAMPLE_STATUSES = ["active", "pending", "completed", "archived", "draft", "verified"];
77
- const SAMPLE_CURRENCIES = ["USD", "EUR", "GBP", "JPY", "CAD", "AUD"];
78
- const SAMPLE_STATES = ["California", "New York", "Texas", "Washington", "Florida", "Illinois"];
79
- const SAMPLE_WORDS = ["alpha", "beta", "stream", "cloud", "service", "cluster", "record", "node", "signal"];
80
-
81
- function samplePick(arr) {
82
- return arr[Math.floor(Math.random() * arr.length)];
83
- }
84
-
85
- function sampleInt(min, max) {
86
- return Math.floor(Math.random() * (max - min + 1)) + min;
87
- }
88
-
89
- function samplePattern(pattern) {
90
- if (!pattern) return 'INV-2026-' + sampleInt(1000, 9999);
91
- const now = new Date();
92
- let s = pattern
93
- .replace(/\{YYYY\}/g, String(now.getFullYear()))
94
- .replace(/\{YY\}/g, String(now.getFullYear()).slice(-2))
95
- .replace(/\{MM\}/g, String(sampleInt(1, 12)).padStart(2, '0'))
96
- .replace(/\{DD\}/g, String(sampleInt(1, 28)).padStart(2, '0'));
97
- let res = '';
98
- const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
99
- const lower = 'abcdefghijklmnopqrstuvwxyz';
100
- for (let i = 0; i < s.length; i++) {
101
- const ch = s[i];
102
- if (ch === '#') res += String(sampleInt(0, 9));
103
- else if (ch === 'A' || ch === '?') res += upper[sampleInt(0, upper.length - 1)];
104
- else if (ch === 'a') res += lower[sampleInt(0, lower.length - 1)];
105
- else res += ch;
106
- }
107
- return res;
108
- }
109
-
110
- function computeSampleValue(col, plan) {
111
- const st = plan.strategy;
112
- if (st === 'skip') return '(omitted / default)';
113
- if (st === 'null') return 'NULL';
114
- if (plan.nullPct && sampleInt(1, 100) <= plan.nullPct && !col.pk && !col.notnull) {
115
- return 'NULL (simulated)';
116
- }
117
-
118
- let val = '';
119
- switch (st) {
120
- case 'fixed':
121
- val = plan.value || '';
122
- break;
123
- case 'list': {
124
- const vals = (plan.values || []).filter((v) => String(v).trim() !== '');
125
- val = vals.length ? samplePick(vals) : '(empty list)';
126
- break;
127
- }
128
- case 'pattern':
129
- val = samplePattern(plan.pattern);
130
- break;
131
- case 'first':
132
- val = samplePick(SAMPLE_FIRST);
133
- break;
134
- case 'last':
135
- val = samplePick(SAMPLE_LAST);
136
- break;
137
- case 'fullname':
138
- val = samplePick(SAMPLE_FIRST) + ' ' + samplePick(SAMPLE_LAST);
139
- break;
140
- case 'username':
141
- val = samplePick(SAMPLE_FIRST).toLowerCase() + sampleInt(10, 999);
142
- break;
143
- case 'job':
144
- val = samplePick(SAMPLE_JOBS);
145
- break;
146
- case 'company':
147
- val = samplePick(SAMPLE_COMPANIES);
148
- break;
149
- case 'currency':
150
- val = samplePick(SAMPLE_CURRENCIES);
151
- break;
152
- case 'status':
153
- val = samplePick(SAMPLE_STATUSES);
154
- break;
155
- case 'creditCard':
156
- val = '4532-****-****-' + sampleInt(1000, 9999);
157
- break;
158
- case 'avatar':
159
- val = 'https://api.dicebear.com/7.x/avataaars/svg?seed=' + samplePick(SAMPLE_FIRST).toLowerCase();
160
- break;
161
- case 'color':
162
- val = plan.format === 'name' ? 'Emerald' : '#3b82f6';
163
- break;
164
- case 'email':
165
- val = samplePick(SAMPLE_FIRST).toLowerCase() + '.' + samplePick(SAMPLE_LAST).toLowerCase() + '@' + samplePick(SAMPLE_DOMAINS);
166
- break;
167
- case 'phone':
168
- val = '+1 (' + sampleInt(200, 999) + ') ' + sampleInt(200, 999) + '-' + sampleInt(1000, 9999);
169
- break;
170
- case 'city':
171
- val = samplePick(SAMPLE_CITIES);
172
- break;
173
- case 'state':
174
- val = samplePick(SAMPLE_STATES);
175
- break;
176
- case 'country':
177
- val = samplePick(SAMPLE_COUNTRIES);
178
- break;
179
- case 'countryCode':
180
- val = samplePick(['US', 'GB', 'CA', 'DE', 'JP', 'FR', 'AU', 'SG']);
181
- break;
182
- case 'postal':
183
- val = String(sampleInt(10000, 99999));
184
- break;
185
- case 'address':
186
- val = sampleInt(100, 9999) + ' ' + samplePick(SAMPLE_WORDS) + ' Ave, ' + samplePick(SAMPLE_CITIES);
187
- break;
188
- case 'url':
189
- val = 'https://www.' + samplePick(SAMPLE_DOMAINS) + '/' + samplePick(SAMPLE_WORDS);
190
- break;
191
- case 'domain':
192
- val = samplePick(SAMPLE_DOMAINS);
193
- break;
194
- case 'ip':
195
- val = sampleInt(1, 223) + '.' + sampleInt(0, 255) + '.' + sampleInt(0, 255) + '.' + sampleInt(1, 254);
196
- break;
197
- case 'mac':
198
- val = '00:1A:2B:' + sampleInt(10, 99) + ':' + sampleInt(10, 99) + ':' + sampleInt(10, 99);
199
- break;
200
- case 'uuid':
201
- val = '550e8400-e29b-41d4-a716-' + sampleInt(100000000000, 999999999999);
202
- break;
203
- case 'slug':
204
- val = samplePick(SAMPLE_WORDS) + '-' + samplePick(SAMPLE_WORDS) + '-' + sampleInt(100, 999);
205
- break;
206
- case 'words':
207
- val = samplePick(SAMPLE_WORDS) + ' ' + samplePick(SAMPLE_WORDS);
208
- break;
209
- case 'sentence':
210
- val = 'Lorem ipsum dolor sit ' + samplePick(SAMPLE_WORDS) + ' amet.';
211
- break;
212
- case 'paragraph':
213
- val = 'Lorem ipsum dolor sit amet. Consectetur adipiscing elit ' + samplePick(SAMPLE_WORDS) + ' dynamicus.';
214
- break;
215
- case 'latitude':
216
- val = (Math.random() * 180 - 90).toFixed(6);
217
- break;
218
- case 'longitude':
219
- val = (Math.random() * 360 - 180).toFixed(6);
220
- break;
221
- case 'sequence':
222
- val = String((plan.start || 1));
223
- break;
224
- case 'int':
225
- val = String(sampleInt(plan.min ?? 1, plan.max ?? 1000));
226
- break;
227
- case 'decimal': {
228
- const min = plan.min ?? 1;
229
- const max = plan.max ?? 1000;
230
- const prec = plan.precision ?? 2;
231
- val = (Math.random() * (max - min) + min).toFixed(prec);
232
- break;
233
- }
234
- case 'date':
235
- val = '2026-03-' + String(sampleInt(1, 28)).padStart(2, '0');
236
- break;
237
- case 'datetime':
238
- val = '2026-03-' + String(sampleInt(1, 28)).padStart(2, '0') + 'T14:30:00.000Z';
239
- break;
240
- case 'time':
241
- val = '14:' + String(sampleInt(10, 59)) + ':00';
242
- break;
243
- case 'timestampUnix':
244
- val = plan.format === 'ms' ? String(Date.now()) : String(Math.floor(Date.now() / 1000));
245
- break;
246
- case 'bool':
247
- val = samplePick(['1', '0']);
248
- break;
249
- case 'bytes':
250
- val = '0x' + sampleInt(100000, 999999).toString(16);
251
- break;
252
- case 'json':
253
- val = '{"scope":"read-only","enabled":true}';
254
- break;
255
- case 'hash':
256
- val = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'.slice(0, plan.maxLen || 32);
257
- break;
258
- case 'token':
259
- val = 'tk_' + sampleInt(100000, 999999);
260
- break;
261
- case 'fk':
262
- val = col.fk ? '[Sample from ' + col.fk.table + ']' : 'NULL';
263
- break;
264
- default:
265
- val = '(auto)';
266
- }
267
-
268
- if (plan.prefix || plan.suffix) {
269
- val = (plan.prefix || '') + val + (plan.suffix || '');
270
- }
271
- return val;
272
- }
273
-
274
- // ---- Field Definitions per Strategy -------------------------------------
275
-
276
- function field(key, label, type, attrs) {
277
- return Object.assign({ key, label, type }, attrs || {});
278
- }
279
-
280
- const FIELD_DEFS = {
281
- fixed: [field('value', 'Value', 'text')],
282
- list: [field('values', 'One value per line', 'textarea')],
283
- pattern: [field('pattern', 'Pattern (tokens: #=digit, ?=letter, {YYYY})', 'text', { placeholder: 'INV-{YYYY}-####' })],
284
- int: [field('min', 'Min', 'number'), field('max', 'Max', 'number')],
285
- decimal: [field('min', 'Min', 'number'), field('max', 'Max', 'number'), field('precision', 'Decimals', 'number', { step: 1, min: 0, max: 10 })],
286
- sequence: [field('start', 'Start from', 'number', { placeholder: '1' }), field('step', 'Step increment', 'number', { placeholder: '1' })],
287
- date: [field('from', 'From', 'date'), field('to', 'To', 'date')],
288
- datetime: [field('from', 'From', 'datetime-local'), field('to', 'To', 'datetime-local')],
289
- time: [],
290
- timestampUnix: [field('format', 'Format (sec / ms)', 'select', { options: [{ value: 'sec', label: 'Seconds (10-digit)' }, { value: 'ms', label: 'Milliseconds (13-digit)' }] })],
291
- color: [field('format', 'Color format', 'select', { options: [{ value: 'hex', label: 'Hex (#3b82f6)' }, { value: 'name', label: 'Color Name (Emerald)' }] })],
292
- latitude: [field('min', 'Min Lat', 'number', { step: 0.0001, placeholder: '-90' }), field('max', 'Max Lat', 'number', { step: 0.0001, placeholder: '90' })],
293
- longitude: [field('min', 'Min Lng', 'number', { step: 0.0001, placeholder: '-180' }), field('max', 'Max Lng', 'number', { step: 0.0001, placeholder: '180' })],
294
- words: [field('minLen', 'Min words', 'number'), field('maxLen', 'Max words', 'number')],
295
- sentence: [field('minLen', 'Min sentences', 'number'), field('maxLen', 'Max sentences', 'number')],
296
- paragraph: [],
297
- bytes: [field('minLen', 'Min bytes', 'number'), field('maxLen', 'Max bytes', 'number')],
298
- json: [field('jsonKeys', 'JSON keys (comma-separated)', 'text')],
299
- hash: [field('minLen', 'Min length', 'number'), field('maxLen', 'Max length', 'number')],
300
- token: [field('minLen', 'Min length', 'number'), field('maxLen', 'Max length', 'number')],
301
- };
302
-
303
- const CATEGORY_TITLES = {
304
- identity: '👤 Identity & People',
305
- contact_web: '🌐 Contact & Web',
306
- commerce: '🏢 Commerce & Business',
307
- location: '📍 Location',
308
- numeric: '🔢 Numbers & Math',
309
- datetime: '📅 Date & Time',
310
- system_crypto: '🔒 System & Crypto',
311
- custom_control: '⚙️ Custom & Control',
312
- relational: '🔗 Relational (Foreign Key)',
313
- };
314
-
315
- function strategyNote(col, strategy) {
316
- if (strategy === 'fk' && col.fk) return 'Sampled from foreign table ' + escapeHtml(col.fk.table) + (col.fk.to ? '.' + escapeHtml(col.fk.to) : '') + '.';
317
- if (strategy === 'skip') return 'Omitted from INSERT — DB default or NULL applies.';
318
- if (strategy === 'null') return 'Explicitly set to NULL for all rows.';
319
- if (strategy === 'pattern') return 'Replaces # with digits, ? with letters, and {YYYY} with year.';
320
- if (strategy === 'sequence') return 'Generates sequentially incrementing integers across rows.';
321
- if (strategy === 'json') return 'Generates valid JSON objects compliant with indexes and defaults.';
322
- if (strategy === 'hash') return 'Generates random cryptographic hex hashes.';
323
- if (strategy === 'token') return 'Generates short alphanumeric identifier tokens.';
324
- return '';
325
- }
326
-
327
- // ---- Card Building ------------------------------------------------------
328
-
329
- function updateCardSample(col, card) {
330
- const sampleEl = card.querySelector('.seed-sample-value');
331
- if (sampleEl) {
332
- const sample = computeSampleValue(col, state[col.name] || {});
333
- sampleEl.textContent = sample;
334
- }
335
- }
336
-
337
- function buildOptionInputs(container, col, plan, card) {
338
- container.innerHTML = '';
339
- const fields = FIELD_DEFS[plan.strategy] || [];
340
-
341
- const hasOptions = fields.length > 0;
342
- const isNullable = !col.pk && !col.notnull;
343
- const isTextish = !['int', 'decimal', 'bool', 'bytes', 'skip', 'null'].includes(plan.strategy);
344
-
345
- if (!hasOptions && !isNullable) return;
346
-
347
- const wrap = document.createElement('div');
348
- wrap.className = 'space-y-2 pt-2 border-t border-base-200/60';
349
-
350
- if (fields.length) {
351
- const grid = document.createElement('div');
352
- grid.className = 'grid grid-cols-2 gap-2';
353
- fields.forEach((f) => {
354
- const fieldWrap = document.createElement('label');
355
- fieldWrap.className = 'block' + (f.type === 'textarea' || f.key === 'pattern' || f.key === 'jsonKeys' ? ' col-span-2' : '');
356
- const label = document.createElement('span');
357
- label.className = 'field-label !mb-1 !text-[11px] font-medium text-base-content/70';
358
- label.textContent = f.label;
359
- fieldWrap.appendChild(label);
360
-
361
- let el;
362
- if (f.type === 'textarea') {
363
- el = document.createElement('textarea');
364
- el.rows = 3;
365
- el.className = 'field-input w-full font-mono !text-[11px] leading-tight';
366
- el.placeholder = 'one value per line';
367
- el.spellcheck = false;
368
- if (Array.isArray(plan.values)) el.value = plan.values.join('\n');
369
- } else if (f.type === 'select') {
370
- el = document.createElement('select');
371
- el.className = 'field-select !py-1 !text-[12px]';
372
- (f.options || []).forEach((opt) => {
373
- const o = document.createElement('option');
374
- o.value = opt.value;
375
- o.textContent = opt.label;
376
- if (plan[f.key] === opt.value) o.selected = true;
377
- el.appendChild(o);
378
- });
379
- } else {
380
- el = document.createElement('input');
381
- el.type = f.type;
382
- el.className = 'field-input !py-1 !text-[12px]';
383
- if (f.step != null) el.step = String(f.step);
384
- if (f.min != null) el.min = String(f.min);
385
- if (f.max != null) el.max = String(f.max);
386
- if (f.placeholder) el.placeholder = f.placeholder;
387
- const v = plan[f.key];
388
- if (f.key === 'jsonKeys' && Array.isArray(v)) {
389
- el.value = v.join(', ');
390
- } else if (v !== undefined && v !== null) {
391
- el.value = String(v);
392
- }
393
- }
394
-
395
- el.dataset.optionKey = f.key;
396
- el.addEventListener('input', () => {
397
- const key = el.dataset.optionKey;
398
- if (f.type === 'textarea') {
399
- state[col.name][key] = el.value.split('\n').map((s) => s.trim()).filter(Boolean);
400
- } else if (key === 'jsonKeys') {
401
- state[col.name][key] = el.value.split(/[,;]/).map((s) => s.trim()).filter(Boolean);
402
- } else if (key === 'value' || key === 'from' || key === 'to' || key === 'pattern' || key === 'format') {
403
- state[col.name][key] = el.value;
404
- } else {
405
- const n = Number(el.value);
406
- state[col.name][key] = el.value === '' || !Number.isFinite(n) ? undefined : n;
407
- }
408
- updateCardSample(col, card);
409
- });
410
-
411
- fieldWrap.appendChild(el);
412
- grid.appendChild(fieldWrap);
413
- });
414
- wrap.appendChild(grid);
415
- }
416
-
417
- // Null percentage simulation for nullable columns
418
- if (isNullable && plan.strategy !== 'skip' && plan.strategy !== 'null') {
419
- const nullRow = document.createElement('div');
420
- nullRow.className = 'flex items-center justify-between gap-2 pt-1.5 text-[11px]';
421
- nullRow.innerHTML =
422
- '<span class="text-base-content/60">Simulate Nulls:</span>' +
423
- '<div class="flex items-center gap-1.5">' +
424
- '<input type="range" min="0" max="100" step="5" value="' + (plan.nullPct || 0) + '" class="range range-xs range-primary w-24" />' +
425
- '<span class="font-mono text-[11px] w-8 text-right text-base-content/75">' + (plan.nullPct || 0) + '%</span>' +
426
- '</div>';
427
- const rangeInput = nullRow.querySelector('input[type="range"]');
428
- const valSpan = nullRow.querySelector('span:last-child');
429
- rangeInput.addEventListener('input', () => {
430
- const val = Number(rangeInput.value);
431
- valSpan.textContent = val + '%';
432
- state[col.name].nullPct = val > 0 ? val : undefined;
433
- updateCardSample(col, card);
434
- });
435
- wrap.appendChild(nullRow);
436
- }
437
-
438
- container.appendChild(wrap);
439
- }
440
-
441
- function buildCard(col) {
442
- const card = document.createElement('div');
443
- card.className = 'app-card p-4 flex flex-col justify-between transition-shadow hover:shadow-md';
444
- card.dataset.column = col.name;
445
- card.dataset.type = (col.type || '').toLowerCase();
446
-
447
- // Top Header
448
- const top = document.createElement('div');
449
- top.className = 'space-y-3';
450
-
451
- const head = document.createElement('div');
452
- head.className = 'flex items-start justify-between gap-2';
453
-
454
- const titleBox = document.createElement('div');
455
- titleBox.className = 'min-w-0';
456
- const nameRow = document.createElement('div');
457
- nameRow.className = 'flex flex-wrap items-center gap-1.5';
458
- const name = document.createElement('span');
459
- name.className = 'font-mono text-sm font-bold tracking-tight text-base-content';
460
- name.textContent = col.name;
461
- nameRow.appendChild(name);
462
-
463
- if (col.pk) {
464
- const chip = document.createElement('span');
465
- chip.className = 'badge badge-xs badge-warning font-mono font-bold';
466
- chip.textContent = 'PK';
467
- nameRow.appendChild(chip);
468
- }
469
- if (col.notnull) {
470
- const chip = document.createElement('span');
471
- chip.className = 'badge badge-xs badge-info font-mono';
472
- chip.textContent = 'NOT NULL';
473
- nameRow.appendChild(chip);
474
- } else {
475
- const chip = document.createElement('span');
476
- chip.className = 'badge badge-xs badge-ghost text-base-content/40 font-mono';
477
- chip.textContent = 'NULLABLE';
478
- nameRow.appendChild(chip);
479
- }
480
- if (col.unique) {
481
- const chip = document.createElement('span');
482
- chip.className = 'badge badge-xs badge-secondary font-mono';
483
- chip.textContent = 'UNIQUE';
484
- nameRow.appendChild(chip);
485
- }
486
- if (col.hasDefault) {
487
- const chip = document.createElement('span');
488
- chip.className = 'badge badge-xs badge-ghost font-mono';
489
- chip.textContent = 'DEFAULT';
490
- nameRow.appendChild(chip);
491
- }
492
- if (col.fk) {
493
- const chip = document.createElement('span');
494
- chip.className = 'badge badge-xs badge-accent font-mono';
495
- chip.textContent = 'FK → ' + col.fk.table;
496
- nameRow.appendChild(chip);
497
- }
498
-
499
- const typeDesc = document.createElement('p');
500
- typeDesc.className = 'mt-0.5 text-[11px] font-mono text-base-content/45';
501
- typeDesc.textContent = col.type || 'untyped';
502
-
503
- titleBox.appendChild(nameRow);
504
- titleBox.appendChild(typeDesc);
505
- head.appendChild(titleBox);
506
- top.appendChild(head);
507
-
508
- // Strategy Dropdown with Categorized <optgroup>s
509
- const select = document.createElement('select');
510
- select.className = 'field-select seed-strategy w-full !py-1.5 !text-xs font-medium';
511
- select.dataset.column = col.name;
512
-
513
- const grouped = {};
514
- (col.strategies || []).forEach((s) => {
515
- const cat = s.category || 'custom_control';
516
- if (!grouped[cat]) grouped[cat] = [];
517
- grouped[cat].push(s);
518
- });
519
-
520
- Object.keys(grouped).forEach((cat) => {
521
- const grp = document.createElement('optgroup');
522
- grp.label = CATEGORY_TITLES[cat] || cat;
523
- grouped[cat].forEach((s) => {
524
- const opt = document.createElement('option');
525
- opt.value = s.id;
526
- opt.textContent = s.label;
527
- grp.appendChild(opt);
528
- });
529
- select.appendChild(grp);
530
- });
531
-
532
- top.appendChild(select);
533
-
534
- // Live Sample Chip
535
- const sampleBox = document.createElement('div');
536
- sampleBox.className = 'flex items-center justify-between gap-2 px-2.5 py-1.5 rounded-lg bg-base-200/50 text-[11px]';
537
- sampleBox.innerHTML =
538
- '<div class="flex items-center gap-1.5 min-w-0 overflow-hidden text-ellipsis">' +
539
- '<span class="text-base-content/50 shrink-0 font-medium">Sample:</span>' +
540
- '<span class="seed-sample-value font-mono text-primary font-medium truncate">...</span>' +
541
- '</div>' +
542
- '<button type="button" class="seed-sample-refresh btn btn-ghost btn-xs btn-square h-5 w-5 shrink-0" title="Reroll sample">' +
543
- '🎲' +
544
- '</button>';
545
-
546
- const sampleRefreshBtn = sampleBox.querySelector('.seed-sample-refresh');
547
- sampleRefreshBtn.addEventListener('click', () => updateCardSample(col, card));
548
- top.appendChild(sampleBox);
549
-
550
- // Strategy-specific options area
551
- const options = document.createElement('div');
552
- options.className = 'seed-options';
553
- top.appendChild(options);
554
-
555
- card.appendChild(top);
556
-
557
- // Bottom notes & warnings
558
- const bottom = document.createElement('div');
559
- bottom.className = 'mt-3 pt-2';
560
-
561
- const note = document.createElement('p');
562
- note.className = 'seed-note hidden text-[11px] leading-relaxed text-base-content/45';
563
- bottom.appendChild(note);
564
-
565
- const warn = document.createElement('p');
566
- warn.className = 'seed-warn hidden text-[11px] font-medium text-warning flex items-center gap-1';
567
- bottom.appendChild(warn);
568
-
569
- card.appendChild(bottom);
570
-
571
- // Event listener for strategy selection change
572
- select.addEventListener('change', () => {
573
- const st = select.value;
574
- state[col.name] = Object.assign({}, state[col.name] || {}, { strategy: st });
575
- buildOptionInputs(options, col, state[col.name], card);
576
- note.textContent = strategyNote(col, st);
577
- note.classList.toggle('hidden', !note.textContent);
578
- updateWarn(col, warn);
579
- updateCardSample(col, card);
580
- });
581
-
582
- columnsBox.appendChild(card);
583
- return { select, options, note, warn };
584
- }
585
-
586
- function updateWarn(col, warnEl) {
587
- const st = state[col.name] ? state[col.name].strategy : null;
588
- const risky = (st === 'skip' || st === 'null') && !col.pk && col.notnull && !col.hasDefault;
589
- warnEl.textContent = risky
590
- ? '⚠️ Column is NOT NULL with no default — "' + st + '" rows may fail.'
591
- : '';
592
- warnEl.classList.toggle('hidden', !warnEl.textContent);
593
- }
594
-
595
- function resetCard(col) {
596
- const plan = Object.assign({}, col.defaultPlan || { strategy: 'skip' });
597
- state[col.name] = plan;
598
- const card = columnsBox.querySelector('.app-card[data-column="' + CSS.escape(col.name) + '"]');
599
- if (card) {
600
- const select = card.querySelector('.seed-strategy');
601
- const options = card.querySelector('.seed-options');
602
- const note = card.querySelector('.seed-note');
603
- const warn = card.querySelector('.seed-warn');
604
- if (select) select.value = plan.strategy;
605
- buildOptionInputs(options, col, plan, card);
606
- note.textContent = strategyNote(col, plan.strategy);
607
- note.classList.toggle('hidden', !note.textContent);
608
- updateWarn(col, warn);
609
- updateCardSample(col, card);
610
- }
611
- }
612
-
613
- // ---- Filtering / Searching ----------------------------------------------
614
-
615
- function filterColumns() {
616
- const q = (searchInput ? searchInput.value : '').toLowerCase().trim();
617
- let visible = 0;
618
- columns.forEach((col) => {
619
- const card = columnsBox.querySelector('.app-card[data-column="' + CSS.escape(col.name) + '"]');
620
- if (!card) return;
621
- const match = !q || col.name.toLowerCase().includes(q) || (col.type || '').toLowerCase().includes(q);
622
- card.classList.toggle('hidden', !match);
623
- if (match) visible += 1;
624
- });
625
-
626
- if (emptySearchBox) emptySearchBox.classList.toggle('hidden', visible > 0);
627
- if (colCountBadge) colCountBadge.textContent = visible === columns.length ? `${columns.length} columns` : `${visible} of ${columns.length} columns`;
628
- }
629
-
630
- // ---- Presets ------------------------------------------------------------
631
-
632
- function applyPreset(type) {
633
- columns.forEach((col) => {
634
- if (type === 'realistic') {
635
- resetCard(col);
636
- } else if (type === 'minimal') {
637
- if (!col.pk && !col.notnull && !col.hasDefault) {
638
- state[col.name] = { strategy: 'null' };
639
- } else {
640
- state[col.name] = Object.assign({}, col.defaultPlan || { strategy: 'skip' });
641
- }
642
- } else if (type === 'sparse') {
643
- state[col.name] = Object.assign({}, col.defaultPlan || { strategy: 'skip' });
644
- if (!col.pk && !col.notnull) {
645
- state[col.name].nullPct = 30;
646
- }
647
- } else if (type === 'numbered') {
648
- const t = (col.type || '').toUpperCase();
649
- if (t.startsWith('INTEGER')) {
650
- state[col.name] = { strategy: 'sequence', start: 1, step: 1 };
651
- } else if (t === 'TEXT' || t === '') {
652
- state[col.name] = { strategy: 'pattern', pattern: 'REC-####' };
653
- } else {
654
- state[col.name] = Object.assign({}, col.defaultPlan || { strategy: 'skip' });
655
- }
656
- }
657
-
658
- // Sync DOM
659
- const card = columnsBox.querySelector('.app-card[data-column="' + CSS.escape(col.name) + '"]');
660
- if (card) {
661
- const select = card.querySelector('.seed-strategy');
662
- const options = card.querySelector('.seed-options');
663
- const note = card.querySelector('.seed-note');
664
- const warn = card.querySelector('.seed-warn');
665
- if (select) select.value = state[col.name].strategy;
666
- buildOptionInputs(options, col, state[col.name], card);
667
- note.textContent = strategyNote(col, state[col.name].strategy);
668
- note.classList.toggle('hidden', !note.textContent);
669
- updateWarn(col, warn);
670
- updateCardSample(col, card);
671
- }
672
- });
673
-
674
- if (window.UI && UI.showToast) UI.showToast('Applied preset: ' + type, 'info');
675
- }
676
-
677
- // ---- Apply strategy to all ---------------------------------------------
678
-
679
- function unionStrategies() {
680
- const map = new Map();
681
- columns.forEach((col) => (col.strategies || []).forEach((s) => map.set(s.id, s.label)));
682
- return [...map.entries()].map(([id, label]) => ({ id, label }));
683
- }
684
-
685
- function buildApplyAllDialog() {
686
- const dialog = document.createElement('dialog');
687
- dialog.className = 'modal';
688
- dialog.id = 'seed-apply-all-dialog';
689
- const inner = document.createElement('div');
690
- inner.className = 'modal-box app-card max-w-md';
691
- inner.innerHTML =
692
- '<h3 class="text-base font-bold">Apply strategy to all columns</h3>' +
693
- '<p class="mt-1 text-xs text-base-content/60">Columns that do not support the chosen strategy are left untouched.</p>' +
694
- '<label class="field-label mt-4 text-xs font-medium">Strategy</label>' +
695
- '<select id="seed-apply-all-select" class="field-select w-full text-xs"></select>' +
696
- '<div class="mt-5 flex justify-end gap-2">' +
697
- '<button id="seed-apply-all-cancel" type="button" class="btn btn-ghost btn-sm">Cancel</button>' +
698
- '<button id="seed-apply-all-go" type="button" class="btn btn-primary btn-sm">Apply to All</button>' +
699
- '</div>';
700
- dialog.appendChild(inner);
701
- document.body.appendChild(dialog);
702
-
703
- const select = inner.querySelector('#seed-apply-all-select');
704
- unionStrategies().forEach((s) => {
705
- const opt = document.createElement('option');
706
- opt.value = s.id;
707
- opt.textContent = s.label;
708
- select.appendChild(opt);
709
- });
710
-
711
- const close = () => dialog.close();
712
- inner.querySelector('#seed-apply-all-cancel').addEventListener('click', close);
713
- inner.querySelector('#seed-apply-all-go').addEventListener('click', () => {
714
- const target = select.value;
715
- columns.forEach((col) => {
716
- const supported = (col.strategies || []).some((s) => s.id === target);
717
- if (!supported) return;
718
- state[col.name] = Object.assign({}, state[col.name] || {}, { strategy: target });
719
- const card = columnsBox.querySelector('.app-card[data-column="' + CSS.escape(col.name) + '"]');
720
- if (card) {
721
- const sel = card.querySelector('.seed-strategy');
722
- const options = card.querySelector('.seed-options');
723
- const note = card.querySelector('.seed-note');
724
- const warn = card.querySelector('.seed-warn');
725
- if (sel) sel.value = target;
726
- buildOptionInputs(options, col, state[col.name], card);
727
- note.textContent = strategyNote(col, target);
728
- note.classList.toggle('hidden', !note.textContent);
729
- updateWarn(col, warn);
730
- updateCardSample(col, card);
731
- }
732
- });
733
- close();
734
- if (window.UI && UI.showToast) UI.showToast('Strategy applied across columns.', 'success');
735
- });
736
- return dialog;
737
- }
738
-
739
- // ---- Dual-Mode Preview Rendering ---------------------------------------
740
-
741
- function showFeedback(warnings) {
742
- if (!feedback) return;
743
- if (!warnings || !warnings.length) {
744
- feedback.innerHTML = '';
745
- feedback.classList.add('hidden');
746
- return;
747
- }
748
- feedback.innerHTML =
749
- '<div class="alert alert-warning shadow-soft">' +
750
- '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>' +
751
- '<div class="text-xs space-y-1">' + warnings.map((w) => '<p>' + escapeHtml(w) + '</p>').join('') + '</div>' +
752
- '</div>';
753
- feedback.classList.remove('hidden');
754
- }
755
-
756
- function renderPreview(data) {
757
- lastGeneratedResult = data;
758
- if (!previewCard) return;
759
-
760
- previewCard.classList.remove('hidden');
761
- if (previewSummaryBadge) {
762
- previewSummaryBadge.textContent = `${data.count} row(s) generated`;
763
- }
764
-
765
- // 1. SQL View
766
- if (sqlCode) sqlCode.textContent = data.sql || '';
767
-
768
- // 2. Table View
769
- if (previewThead && previewTbody) {
770
- previewThead.innerHTML = '';
771
- previewTbody.innerHTML = '';
772
-
773
- const trHead = document.createElement('tr');
774
- const thIdx = document.createElement('th');
775
- thIdx.className = 'w-12 text-center text-base-content/40';
776
- thIdx.textContent = '#';
777
- trHead.appendChild(thIdx);
778
-
779
- columns.forEach((col) => {
780
- const th = document.createElement('th');
781
- th.className = 'whitespace-nowrap font-mono';
782
- th.innerHTML = escapeHtml(col.name) + ' <span class="text-[10px] text-base-content/40 font-normal">(' + escapeHtml(col.type || 'text') + ')</span>';
783
- trHead.appendChild(th);
784
- });
785
- previewThead.appendChild(trHead);
786
-
787
- const rows = data.previewRows || [];
788
- rows.forEach((row, i) => {
789
- const tr = document.createElement('tr');
790
- const tdIdx = document.createElement('td');
791
- tdIdx.className = 'text-center font-mono text-[10px] text-base-content/40';
792
- tdIdx.textContent = String(i + 1);
793
- tr.appendChild(tdIdx);
794
-
795
- columns.forEach((col) => {
796
- const td = document.createElement('td');
797
- td.className = 'font-mono whitespace-nowrap max-w-xs truncate';
798
- const val = row[col.name];
799
- if (val === null) {
800
- td.innerHTML = '<span class="badge badge-xs badge-ghost text-base-content/40">NULL</span>';
801
- } else if (val === '(default)') {
802
- td.innerHTML = '<span class="badge badge-xs badge-outline text-base-content/40">DEFAULT</span>';
803
- } else if (typeof val === 'boolean') {
804
- td.innerHTML = val ? '<span class="badge badge-xs badge-success">TRUE</span>' : '<span class="badge badge-xs badge-ghost">FALSE</span>';
805
- } else {
806
- td.textContent = String(val);
807
- }
808
- tr.appendChild(td);
809
- });
810
- previewTbody.appendChild(tr);
811
- });
812
- }
813
-
814
- previewCard.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
815
- }
816
-
817
- function setPreviewTab(tab) {
818
- if (tab === 'table') {
819
- tabTableView.classList.add('tab-active');
820
- tabSqlView.classList.remove('tab-active');
821
- tableViewContainer.classList.remove('hidden');
822
- sqlViewContainer.classList.add('hidden');
823
- } else {
824
- tabSqlView.classList.add('tab-active');
825
- tabTableView.classList.remove('tab-active');
826
- sqlViewContainer.classList.remove('hidden');
827
- tableViewContainer.classList.add('hidden');
828
- }
829
- }
830
-
831
- // ---- Export Handlers ----------------------------------------------------
832
-
833
- function downloadFile(content, filename, mime) {
834
- const blob = new Blob([content], { type: mime });
835
- const url = URL.createObjectURL(blob);
836
- const a = document.createElement('a');
837
- a.href = url;
838
- a.download = filename;
839
- document.body.appendChild(a);
840
- a.click();
841
- document.body.removeChild(a);
842
- URL.revokeObjectURL(url);
843
- }
844
-
845
- function exportCsv() {
846
- if (!lastGeneratedResult || !lastGeneratedResult.previewRows) {
847
- if (window.UI && UI.showError) UI.showError('Generate preview data first.');
848
- return;
849
- }
850
- const rows = lastGeneratedResult.previewRows;
851
- const colNames = columns.map((c) => c.name);
852
- let csv = colNames.map((c) => `"${c.replace(/"/g, '""')}"`).join(',') + '\n';
853
- rows.forEach((r) => {
854
- csv += colNames.map((c) => {
855
- const v = r[c];
856
- if (v == null) return '';
857
- return `"${String(v).replace(/"/g, '""')}"`;
858
- }).join(',') + '\n';
859
- });
860
- downloadFile(csv, `${cfg.table}_seed_data.csv`, 'text/csv;charset=utf-8;');
861
- }
862
-
863
- function exportJson() {
864
- if (!lastGeneratedResult || !lastGeneratedResult.previewRows) {
865
- if (window.UI && UI.showError) UI.showError('Generate preview data first.');
866
- return;
867
- }
868
- const jsonStr = JSON.stringify(lastGeneratedResult.previewRows, null, 2);
869
- downloadFile(jsonStr, `${cfg.table}_seed_data.json`, 'application/json;charset=utf-8;');
870
- }
871
-
872
- function downloadSql() {
873
- if (!lastGeneratedResult || !lastGeneratedResult.sql) {
874
- if (window.UI && UI.showError) UI.showError('Generate preview data first.');
875
- return;
876
- }
877
- downloadFile(lastGeneratedResult.sql, `${cfg.table}_seed.sql`, 'text/sql;charset=utf-8;');
878
- }
879
-
880
- // ---- Collect Plan + Submit ----------------------------------------------
881
-
882
- function collectPlan() {
883
- const plan = {};
884
- columns.forEach((col) => {
885
- const st = state[col.name];
886
- if (!st || !st.strategy) return;
887
- const out = { strategy: st.strategy };
888
- Object.keys(st).forEach((k) => {
889
- if (k === 'strategy' || st[k] === undefined) return;
890
- out[k] = st[k];
891
- });
892
- plan[col.name] = out;
893
- });
894
- return plan;
895
- }
896
-
897
- function currentCount() {
898
- const n = Number.parseInt(String(countInput ? countInput.value : '10'), 10);
899
- return Math.max(1, Math.min(cfg.maxRows || 5000, Number.isFinite(n) ? n : 10));
900
- }
901
-
902
- async function executeSeed(truncate = false) {
903
- const count = currentCount();
904
- const plan = collectPlan();
905
- const path = '/api/tables/' + t + '/seed';
906
- try {
907
- if (insertBtn) {
908
- insertBtn.disabled = true;
909
- insertBtn.classList.add('loading');
910
- }
911
- const data = await Api.post(path, { count, plan, truncate });
912
- if (window.UI && UI.showToast) {
913
- UI.showToast(data.message || `Inserted ${data.inserted} row(s) successfully.`, 'success');
914
- }
915
- showFeedback(data.warnings || []);
916
- } catch (e) {
917
- if (window.UI && UI.showError) UI.showError(e.message);
918
- } finally {
919
- if (insertBtn) {
920
- insertBtn.disabled = false;
921
- insertBtn.classList.remove('loading');
922
- }
923
- }
924
- }
925
-
926
- async function submitPreview() {
927
- const count = currentCount();
928
- const plan = collectPlan();
929
- const path = '/api/tables/' + t + '/seed/generate';
930
- try {
931
- if (previewBtn) previewBtn.disabled = true;
932
- const data = await Api.post(path, { count, plan });
933
- renderPreview(data);
934
- showFeedback(data.warnings || []);
935
- } catch (e) {
936
- if (window.UI && UI.showError) UI.showError(e.message);
937
- } finally {
938
- if (previewBtn) previewBtn.disabled = false;
939
- }
940
- }
941
-
942
- // ---- Init ---------------------------------------------------------------
943
-
944
- function init() {
945
- columns.forEach((col) => {
946
- buildCard(col);
947
- resetCard(col);
948
- });
949
-
950
- filterColumns();
951
-
952
- // Event Listeners
953
- if (searchInput) searchInput.addEventListener('input', filterColumns);
954
- if (previewBtn) previewBtn.addEventListener('click', submitPreview);
955
-
956
- if (insertBtn) {
957
- insertBtn.addEventListener('click', () => {
958
- if (truncateCheckbox && truncateCheckbox.checked) {
959
- if (truncateDialog) truncateDialog.showModal();
960
- } else {
961
- executeSeed(false);
962
- }
963
- });
964
- }
965
-
966
- if (truncateConfirm) {
967
- truncateConfirm.addEventListener('click', () => {
968
- if (truncateDialog) truncateDialog.close();
969
- executeSeed(true);
970
- });
971
- }
972
-
973
- if (truncateCancel) {
974
- truncateCancel.addEventListener('click', () => {
975
- if (truncateDialog) truncateDialog.close();
976
- });
977
- }
978
-
979
- if (resetBtn) resetBtn.addEventListener('click', () => columns.forEach(resetCard));
980
- if (refreshSamplesBtn) refreshSamplesBtn.addEventListener('click', () => {
981
- columns.forEach((col) => {
982
- const card = columnsBox.querySelector('.app-card[data-column="' + CSS.escape(col.name) + '"]');
983
- if (card) updateCardSample(col, card);
984
- });
985
- });
986
-
987
- if (applyAllBtn) {
988
- applyAllBtn.addEventListener('click', () => {
989
- const dialog = document.getElementById('seed-apply-all-dialog') || buildApplyAllDialog();
990
- dialog.showModal();
991
- });
992
- }
993
-
994
- // Presets
995
- const pReal = document.getElementById('preset-realistic');
996
- const pMin = document.getElementById('preset-minimal');
997
- const pSparse = document.getElementById('preset-sparse');
998
- const pNum = document.getElementById('preset-numbered');
999
- if (pReal) pReal.addEventListener('click', () => applyPreset('realistic'));
1000
- if (pMin) pMin.addEventListener('click', () => applyPreset('minimal'));
1001
- if (pSparse) pSparse.addEventListener('click', () => applyPreset('sparse'));
1002
- if (pNum) pNum.addEventListener('click', () => applyPreset('numbered'));
1003
-
1004
- // Config export / import
1005
- if (exportConfigBtn) {
1006
- exportConfigBtn.addEventListener('click', () => {
1007
- if (!configDialog || !configTextarea) return;
1008
- configDialogTitle.textContent = 'Export Seed Plan';
1009
- configTextarea.value = JSON.stringify(collectPlan(), null, 2);
1010
- configDialogApply.classList.add('hidden');
1011
- configDialog.showModal();
1012
- });
1013
- }
1014
-
1015
- if (importConfigBtn) {
1016
- importConfigBtn.addEventListener('click', () => {
1017
- if (!configDialog || !configTextarea) return;
1018
- configDialogTitle.textContent = 'Import Seed Plan';
1019
- configTextarea.value = '';
1020
- configTextarea.placeholder = 'Paste plan JSON here...';
1021
- configDialogApply.classList.remove('hidden');
1022
- configDialog.showModal();
1023
- });
1024
- }
1025
-
1026
- if (configDialogCancel) {
1027
- configDialogCancel.addEventListener('click', () => {
1028
- if (configDialog) configDialog.close();
1029
- });
1030
- }
1031
-
1032
- if (configDialogApply) {
1033
- configDialogApply.addEventListener('click', () => {
1034
- try {
1035
- const imported = JSON.parse(configTextarea.value);
1036
- if (typeof imported !== 'object' || !imported) throw new Error('Invalid JSON format');
1037
- columns.forEach((col) => {
1038
- if (imported[col.name]) {
1039
- state[col.name] = imported[col.name];
1040
- const card = columnsBox.querySelector('.app-card[data-column="' + CSS.escape(col.name) + '"]');
1041
- if (card) {
1042
- const select = card.querySelector('.seed-strategy');
1043
- const options = card.querySelector('.seed-options');
1044
- const note = card.querySelector('.seed-note');
1045
- const warn = card.querySelector('.seed-warn');
1046
- if (select) select.value = state[col.name].strategy;
1047
- buildOptionInputs(options, col, state[col.name], card);
1048
- note.textContent = strategyNote(col, state[col.name].strategy);
1049
- note.classList.toggle('hidden', !note.textContent);
1050
- updateWarn(col, warn);
1051
- updateCardSample(col, card);
1052
- }
1053
- }
1054
- });
1055
- if (configDialog) configDialog.close();
1056
- if (window.UI && UI.showToast) UI.showToast('Seed plan configuration applied!', 'success');
1057
- } catch (err) {
1058
- if (window.UI && UI.showError) UI.showError('Failed to parse JSON: ' + err.message);
1059
- }
1060
- });
1061
- }
1062
-
1063
- // Dual-mode tabs
1064
- if (tabTableView) tabTableView.addEventListener('click', () => setPreviewTab('table'));
1065
- if (tabSqlView) tabSqlView.addEventListener('click', () => setPreviewTab('sql'));
1066
-
1067
- // Export buttons
1068
- if (copySqlBtn) {
1069
- copySqlBtn.addEventListener('click', () => {
1070
- if (!sqlCode || !sqlCode.textContent) return;
1071
- navigator.clipboard.writeText(sqlCode.textContent)
1072
- .then(() => UI.showToast('Copied SQL to clipboard.', 'success'))
1073
- .catch(() => UI.showError('Could not copy SQL.'));
1074
- });
1075
- }
1076
-
1077
- if (downloadSqlBtn) downloadSqlBtn.addEventListener('click', downloadSql);
1078
- if (exportCsvBtn) exportCsvBtn.addEventListener('click', exportCsv);
1079
- if (exportJsonBtn) exportJsonBtn.addEventListener('click', exportJson);
1080
-
1081
- // Quick counts
1082
- document.querySelectorAll('.seed-quick').forEach((btn) => {
1083
- btn.addEventListener('click', () => {
1084
- if (countInput) countInput.value = btn.dataset.n;
1085
- });
1086
- });
1087
- }
1088
-
1089
- init();
1090
- })();
1
+ !function(){"use strict";const e=document.getElementById("seed-config");if(!e)return;const t=JSON.parse(e.textContent),n=encodeURIComponent(t.table),a=t.columns||[],s=document.getElementById("seed-count"),o=document.getElementById("seed-search"),l=document.getElementById("seed-truncate"),c=document.getElementById("seed-columns"),r=document.getElementById("seed-empty-search"),d=document.getElementById("seed-col-count-badge"),i=document.getElementById("seed-feedback"),m=document.getElementById("seed-preview-card"),p=document.getElementById("preview-summary-badge"),u=document.getElementById("seed-preview-thead"),g=document.getElementById("seed-preview-tbody"),b=document.getElementById("seed-table-view-container"),f=document.getElementById("seed-sql-view-container"),h=document.getElementById("seed-sql-code"),y=document.getElementById("tab-table-view"),x=document.getElementById("tab-sql-view"),E=document.getElementById("seed-insert"),w=document.getElementById("seed-preview"),v=document.getElementById("seed-reset"),k=document.getElementById("seed-refresh-samples"),L=document.getElementById("seed-apply-all"),C=document.getElementById("seed-copy-sql"),S=document.getElementById("seed-download-sql"),I=document.getElementById("seed-export-csv"),N=document.getElementById("seed-export-json"),U=document.getElementById("seed-export-config"),M=document.getElementById("seed-import-config"),B=document.getElementById("seed-config-dialog"),q=document.getElementById("seed-config-dialog-title"),T=document.getElementById("seed-config-textarea"),j=document.getElementById("seed-config-dialog-cancel"),O=document.getElementById("seed-config-dialog-apply"),A=document.getElementById("seed-truncate-dialog"),D=document.getElementById("seed-truncate-cancel"),Y=document.getElementById("seed-truncate-confirm"),P={};let R=null;function F(e){return window.UI&&UI.escapeHtml?UI.escapeHtml(e):String(null==e?"":e)}const H=["Sombat","Minh","Hiroshi","William","Ravi","Zara","Elena","Liam","Mei","Noah"],G=["Chen","Smith","Thompson","Shah","Yamamoto","Patel","Garcia","Kim","Nguyen","Davis"],J=["example.com","acme.io","tech.asia","gmail.com","startup.io"],K=["New York","Tokyo","London","Singapore","Paris","Berlin","Sydney","Toronto"],$=["United States","Japan","United Kingdom","Singapore","Germany","France","Canada","Australia"],_=["Apex Solutions","Vortex Digital","Nexus Systems","Horizon Labs","Quantum Dynamics","Zenith Global"],V=["Software Engineer","Product Manager","UX Designer","DevOps Specialist","Data Scientist","Marketing Director"],Q=["active","pending","completed","archived","draft","verified"],W=["USD","EUR","GBP","JPY","CAD","AUD"],Z=["California","New York","Texas","Washington","Florida","Illinois"],z=["alpha","beta","stream","cloud","service","cluster","record","node","signal"];function X(e){return e[Math.floor(Math.random()*e.length)]}function ee(e,t){return Math.floor(Math.random()*(t-e+1))+e}function te(e,t){const n=t.strategy;if("skip"===n)return"(omitted / default)";if("null"===n)return"NULL";if(t.nullPct&&ee(1,100)<=t.nullPct&&!e.pk&&!e.notnull)return"NULL (simulated)";let a="";switch(n){case"fixed":a=t.value||"";break;case"list":{const e=(t.values||[]).filter(e=>""!==String(e).trim());a=e.length?X(e):"(empty list)";break}case"pattern":a=function(e){if(!e)return"INV-2026-"+ee(1e3,9999);const t=new Date;let n=e.replace(/\{YYYY\}/g,String(t.getFullYear())).replace(/\{YY\}/g,String(t.getFullYear()).slice(-2)).replace(/\{MM\}/g,String(ee(1,12)).padStart(2,"0")).replace(/\{DD\}/g,String(ee(1,28)).padStart(2,"0")),a="";const s="ABCDEFGHIJKLMNOPQRSTUVWXYZ",o="abcdefghijklmnopqrstuvwxyz";for(let e=0;e<n.length;e++){const t=n[e];a+="#"===t?String(ee(0,9)):"A"===t||"?"===t?s[ee(0,25)]:"a"===t?o[ee(0,25)]:t}return a}(t.pattern);break;case"first":a=X(H);break;case"last":a=X(G);break;case"fullname":a=X(H)+" "+X(G);break;case"username":a=X(H).toLowerCase()+ee(10,999);break;case"job":a=X(V);break;case"company":a=X(_);break;case"currency":a=X(W);break;case"status":a=X(Q);break;case"creditCard":a="4532-****-****-"+ee(1e3,9999);break;case"avatar":a="https://api.dicebear.com/7.x/avataaars/svg?seed="+X(H).toLowerCase();break;case"color":a="name"===t.format?"Emerald":"#3b82f6";break;case"email":a=X(H).toLowerCase()+"."+X(G).toLowerCase()+"@"+X(J);break;case"phone":a="+1 ("+ee(200,999)+") "+ee(200,999)+"-"+ee(1e3,9999);break;case"city":a=X(K);break;case"state":a=X(Z);break;case"country":a=X($);break;case"countryCode":a=X(["US","GB","CA","DE","JP","FR","AU","SG"]);break;case"postal":a=String(ee(1e4,99999));break;case"address":a=ee(100,9999)+" "+X(z)+" Ave, "+X(K);break;case"url":a="https://www."+X(J)+"/"+X(z);break;case"domain":a=X(J);break;case"ip":a=ee(1,223)+"."+ee(0,255)+"."+ee(0,255)+"."+ee(1,254);break;case"mac":a="00:1A:2B:"+ee(10,99)+":"+ee(10,99)+":"+ee(10,99);break;case"uuid":a="550e8400-e29b-41d4-a716-"+ee(1e11,999999999999);break;case"slug":a=X(z)+"-"+X(z)+"-"+ee(100,999);break;case"words":a=X(z)+" "+X(z);break;case"sentence":a="Lorem ipsum dolor sit "+X(z)+" amet.";break;case"paragraph":a="Lorem ipsum dolor sit amet. Consectetur adipiscing elit "+X(z)+" dynamicus.";break;case"latitude":a=(180*Math.random()-90).toFixed(6);break;case"longitude":a=(360*Math.random()-180).toFixed(6);break;case"sequence":a=String(t.start||1);break;case"int":a=String(ee(t.min??1,t.max??1e3));break;case"decimal":{const e=t.min??1,n=t.max??1e3,s=t.precision??2;a=(Math.random()*(n-e)+e).toFixed(s);break}case"date":a="2026-03-"+String(ee(1,28)).padStart(2,"0");break;case"datetime":a="2026-03-"+String(ee(1,28)).padStart(2,"0")+"T14:30:00.000Z";break;case"time":a="14:"+String(ee(10,59))+":00";break;case"timestampUnix":a="ms"===t.format?String(Date.now()):String(Math.floor(Date.now()/1e3));break;case"bool":a=X(["1","0"]);break;case"bytes":a="0x"+ee(1e5,999999).toString(16);break;case"json":a='{"scope":"read-only","enabled":true}';break;case"hash":a="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".slice(0,t.maxLen||32);break;case"token":a="tk_"+ee(1e5,999999);break;case"fk":a=e.fk?"[Sample from "+e.fk.table+"]":"NULL";break;default:a="(auto)"}return(t.prefix||t.suffix)&&(a=(t.prefix||"")+a+(t.suffix||"")),a}function ne(e,t,n,a){return Object.assign({key:e,label:t,type:n},a||{})}const ae={fixed:[ne("value","Value","text")],list:[ne("values","One value per line","textarea")],pattern:[ne("pattern","Pattern (tokens: #=digit, ?=letter, {YYYY})","text",{placeholder:"INV-{YYYY}-####"})],int:[ne("min","Min","number"),ne("max","Max","number")],decimal:[ne("min","Min","number"),ne("max","Max","number"),ne("precision","Decimals","number",{step:1,min:0,max:10})],sequence:[ne("start","Start from","number",{placeholder:"1"}),ne("step","Step increment","number",{placeholder:"1"})],date:[ne("from","From","date"),ne("to","To","date")],datetime:[ne("from","From","datetime-local"),ne("to","To","datetime-local")],time:[],timestampUnix:[ne("format","Format (sec / ms)","select",{options:[{value:"sec",label:"Seconds (10-digit)"},{value:"ms",label:"Milliseconds (13-digit)"}]})],color:[ne("format","Color format","select",{options:[{value:"hex",label:"Hex (#3b82f6)"},{value:"name",label:"Color Name (Emerald)"}]})],latitude:[ne("min","Min Lat","number",{step:1e-4,placeholder:"-90"}),ne("max","Max Lat","number",{step:1e-4,placeholder:"90"})],longitude:[ne("min","Min Lng","number",{step:1e-4,placeholder:"-180"}),ne("max","Max Lng","number",{step:1e-4,placeholder:"180"})],words:[ne("minLen","Min words","number"),ne("maxLen","Max words","number")],sentence:[ne("minLen","Min sentences","number"),ne("maxLen","Max sentences","number")],paragraph:[],bytes:[ne("minLen","Min bytes","number"),ne("maxLen","Max bytes","number")],json:[ne("jsonKeys","JSON keys (comma-separated)","text")],hash:[ne("minLen","Min length","number"),ne("maxLen","Max length","number")],token:[ne("minLen","Min length","number"),ne("maxLen","Max length","number")]},se={identity:"👤 Identity & People",contact_web:"🌐 Contact & Web",commerce:"🏢 Commerce & Business",location:"📍 Location",numeric:"🔢 Numbers & Math",datetime:"📅 Date & Time",system_crypto:"🔒 System & Crypto",custom_control:"⚙️ Custom & Control",relational:"🔗 Relational (Foreign Key)"};function oe(e,t){return"fk"===t&&e.fk?"Sampled from foreign table "+F(e.fk.table)+(e.fk.to?"."+F(e.fk.to):"")+".":"skip"===t?"Omitted from INSERT — DB default or NULL applies.":"null"===t?"Explicitly set to NULL for all rows.":"pattern"===t?"Replaces # with digits, ? with letters, and {YYYY} with year.":"sequence"===t?"Generates sequentially incrementing integers across rows.":"json"===t?"Generates valid JSON objects compliant with indexes and defaults.":"hash"===t?"Generates random cryptographic hex hashes.":"token"===t?"Generates short alphanumeric identifier tokens.":""}function le(e,t){const n=t.querySelector(".seed-sample-value");if(n){const t=te(e,P[e.name]||{});n.textContent=t}}function ce(e,t,n,a){e.innerHTML="";const s=ae[n.strategy]||[],o=s.length>0,l=!t.pk&&!t.notnull;["int","decimal","bool","bytes","skip","null"].includes(n.strategy);if(!o&&!l)return;const c=document.createElement("div");if(c.className="space-y-2 pt-2 border-t border-base-200/60",s.length){const e=document.createElement("div");e.className="grid grid-cols-2 gap-2",s.forEach(s=>{const o=document.createElement("label");o.className="block"+("textarea"===s.type||"pattern"===s.key||"jsonKeys"===s.key?" col-span-2":"");const l=document.createElement("span");let c;if(l.className="field-label !mb-1 !text-[11px] font-medium text-base-content/70",l.textContent=s.label,o.appendChild(l),"textarea"===s.type)c=document.createElement("textarea"),c.rows=3,c.className="field-input w-full font-mono !text-[11px] leading-tight",c.placeholder="one value per line",c.spellcheck=!1,Array.isArray(n.values)&&(c.value=n.values.join("\n"));else if("select"===s.type)c=document.createElement("select"),c.className="field-select !py-1 !text-[12px]",(s.options||[]).forEach(e=>{const t=document.createElement("option");t.value=e.value,t.textContent=e.label,n[s.key]===e.value&&(t.selected=!0),c.appendChild(t)});else{c=document.createElement("input"),c.type=s.type,c.className="field-input !py-1 !text-[12px]",null!=s.step&&(c.step=String(s.step)),null!=s.min&&(c.min=String(s.min)),null!=s.max&&(c.max=String(s.max)),s.placeholder&&(c.placeholder=s.placeholder);const e=n[s.key];"jsonKeys"===s.key&&Array.isArray(e)?c.value=e.join(", "):null!=e&&(c.value=String(e))}c.dataset.optionKey=s.key,c.addEventListener("input",()=>{const e=c.dataset.optionKey;if("textarea"===s.type)P[t.name][e]=c.value.split("\n").map(e=>e.trim()).filter(Boolean);else if("jsonKeys"===e)P[t.name][e]=c.value.split(/[,;]/).map(e=>e.trim()).filter(Boolean);else if("value"===e||"from"===e||"to"===e||"pattern"===e||"format"===e)P[t.name][e]=c.value;else{const n=Number(c.value);P[t.name][e]=""!==c.value&&Number.isFinite(n)?n:void 0}le(t,a)}),o.appendChild(c),e.appendChild(o)}),c.appendChild(e)}if(l&&"skip"!==n.strategy&&"null"!==n.strategy){const e=document.createElement("div");e.className="flex items-center justify-between gap-2 pt-1.5 text-[11px]",e.innerHTML='<span class="text-base-content/60">Simulate Nulls:</span><div class="flex items-center gap-1.5"><input type="range" min="0" max="100" step="5" value="'+(n.nullPct||0)+'" class="range range-xs range-primary w-24" /><span class="font-mono text-[11px] w-8 text-right text-base-content/75">'+(n.nullPct||0)+"%</span></div>";const s=e.querySelector('input[type="range"]'),o=e.querySelector("span:last-child");s.addEventListener("input",()=>{const e=Number(s.value);o.textContent=e+"%",P[t.name].nullPct=e>0?e:void 0,le(t,a)}),c.appendChild(e)}e.appendChild(c)}function re(e,t){const n=P[e.name]?P[e.name].strategy:null,a=("skip"===n||"null"===n)&&!e.pk&&e.notnull&&!e.hasDefault;t.textContent=a?'⚠️ Column is NOT NULL with no default — "'+n+'" rows may fail.':"",t.classList.toggle("hidden",!t.textContent)}function de(e){const t=Object.assign({},e.defaultPlan||{strategy:"skip"});P[e.name]=t;const n=c.querySelector('.app-card[data-column="'+CSS.escape(e.name)+'"]');if(n){const a=n.querySelector(".seed-strategy"),s=n.querySelector(".seed-options"),o=n.querySelector(".seed-note"),l=n.querySelector(".seed-warn");a&&(a.value=t.strategy),ce(s,e,t,n),o.textContent=oe(e,t.strategy),o.classList.toggle("hidden",!o.textContent),re(e,l),le(e,n)}}function ie(){const e=(o?o.value:"").toLowerCase().trim();let t=0;a.forEach(n=>{const a=c.querySelector('.app-card[data-column="'+CSS.escape(n.name)+'"]');if(!a)return;const s=!e||n.name.toLowerCase().includes(e)||(n.type||"").toLowerCase().includes(e);a.classList.toggle("hidden",!s),s&&(t+=1)}),r&&r.classList.toggle("hidden",t>0),d&&(d.textContent=t===a.length?`${a.length} columns`:`${t} of ${a.length} columns`)}function me(e){a.forEach(t=>{if("realistic"===e)de(t);else if("minimal"===e)t.pk||t.notnull||t.hasDefault?P[t.name]=Object.assign({},t.defaultPlan||{strategy:"skip"}):P[t.name]={strategy:"null"};else if("sparse"===e)P[t.name]=Object.assign({},t.defaultPlan||{strategy:"skip"}),t.pk||t.notnull||(P[t.name].nullPct=30);else if("numbered"===e){const e=(t.type||"").toUpperCase();e.startsWith("INTEGER")?P[t.name]={strategy:"sequence",start:1,step:1}:P[t.name]="TEXT"===e||""===e?{strategy:"pattern",pattern:"REC-####"}:Object.assign({},t.defaultPlan||{strategy:"skip"})}const n=c.querySelector('.app-card[data-column="'+CSS.escape(t.name)+'"]');if(n){const e=n.querySelector(".seed-strategy"),a=n.querySelector(".seed-options"),s=n.querySelector(".seed-note"),o=n.querySelector(".seed-warn");e&&(e.value=P[t.name].strategy),ce(a,t,P[t.name],n),s.textContent=oe(t,P[t.name].strategy),s.classList.toggle("hidden",!s.textContent),re(t,o),le(t,n)}}),window.UI&&UI.showToast&&UI.showToast("Applied preset: "+e,"info")}function pe(){const e=document.createElement("dialog");e.className="modal",e.id="seed-apply-all-dialog";const t=document.createElement("div");t.className="modal-box app-card max-w-md",t.innerHTML='<h3 class="text-base font-bold">Apply strategy to all columns</h3><p class="mt-1 text-xs text-base-content/60">Columns that do not support the chosen strategy are left untouched.</p><label class="field-label mt-4 text-xs font-medium">Strategy</label><select id="seed-apply-all-select" class="field-select w-full text-xs"></select><div class="mt-5 flex justify-end gap-2"><button id="seed-apply-all-cancel" type="button" class="btn btn-ghost btn-sm">Cancel</button><button id="seed-apply-all-go" type="button" class="btn btn-primary btn-sm">Apply to All</button></div>',e.appendChild(t),document.body.appendChild(e);const n=t.querySelector("#seed-apply-all-select");(function(){const e=new Map;return a.forEach(t=>(t.strategies||[]).forEach(t=>e.set(t.id,t.label))),[...e.entries()].map(([e,t])=>({id:e,label:t}))})().forEach(e=>{const t=document.createElement("option");t.value=e.id,t.textContent=e.label,n.appendChild(t)});const s=()=>e.close();return t.querySelector("#seed-apply-all-cancel").addEventListener("click",s),t.querySelector("#seed-apply-all-go").addEventListener("click",()=>{const e=n.value;a.forEach(t=>{if(!(t.strategies||[]).some(t=>t.id===e))return;P[t.name]=Object.assign({},P[t.name]||{},{strategy:e});const n=c.querySelector('.app-card[data-column="'+CSS.escape(t.name)+'"]');if(n){const a=n.querySelector(".seed-strategy"),s=n.querySelector(".seed-options"),o=n.querySelector(".seed-note"),l=n.querySelector(".seed-warn");a&&(a.value=e),ce(s,t,P[t.name],n),o.textContent=oe(t,e),o.classList.toggle("hidden",!o.textContent),re(t,l),le(t,n)}}),s(),window.UI&&UI.showToast&&UI.showToast("Strategy applied across columns.","success")}),e}function ue(e){if(i){if(!e||!e.length)return i.innerHTML="",void i.classList.add("hidden");i.innerHTML='<div class="alert alert-warning shadow-soft"><svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg><div class="text-xs space-y-1">'+e.map(e=>"<p>"+F(e)+"</p>").join("")+"</div></div>",i.classList.remove("hidden")}}function ge(e){"table"===e?(y.classList.add("tab-active"),x.classList.remove("tab-active"),b.classList.remove("hidden"),f.classList.add("hidden")):(x.classList.add("tab-active"),y.classList.remove("tab-active"),f.classList.remove("hidden"),b.classList.add("hidden"))}function be(e,t,n){const a=new Blob([e],{type:n}),s=URL.createObjectURL(a),o=document.createElement("a");o.href=s,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(s)}function fe(){if(!R||!R.previewRows)return void(window.UI&&UI.showError&&UI.showError("Generate preview data first."));const e=R.previewRows,n=a.map(e=>e.name);let s=n.map(e=>`"${e.replace(/"/g,'""')}"`).join(",")+"\n";e.forEach(e=>{s+=n.map(t=>{const n=e[t];return null==n?"":`"${String(n).replace(/"/g,'""')}"`}).join(",")+"\n"}),be(s,`${t.table}_seed_data.csv`,"text/csv;charset=utf-8;")}function he(){if(!R||!R.previewRows)return void(window.UI&&UI.showError&&UI.showError("Generate preview data first."));be(JSON.stringify(R.previewRows,null,2),`${t.table}_seed_data.json`,"application/json;charset=utf-8;")}function ye(){R&&R.sql?be(R.sql,`${t.table}_seed.sql`,"text/sql;charset=utf-8;"):window.UI&&UI.showError&&UI.showError("Generate preview data first.")}function xe(){const e={};return a.forEach(t=>{const n=P[t.name];if(!n||!n.strategy)return;const a={strategy:n.strategy};Object.keys(n).forEach(e=>{"strategy"!==e&&void 0!==n[e]&&(a[e]=n[e])}),e[t.name]=a}),e}function Ee(){const e=Number.parseInt(String(s?s.value:"10"),10);return Math.max(1,Math.min(t.maxRows||5e3,Number.isFinite(e)?e:10))}async function we(e=!1){const t=Ee(),a=xe(),s="/api/tables/"+n+"/seed";try{E&&(E.disabled=!0,E.classList.add("loading"));const n=await Api.post(s,{count:t,plan:a,truncate:e});window.UI&&UI.showToast&&UI.showToast(n.message||`Inserted ${n.inserted} row(s) successfully.`,"success"),ue(n.warnings||[])}catch(e){window.UI&&UI.showError&&UI.showError(e.message)}finally{E&&(E.disabled=!1,E.classList.remove("loading"))}}async function ve(){const e=Ee(),t=xe(),s="/api/tables/"+n+"/seed/generate";try{w&&(w.disabled=!0);const n=await Api.post(s,{count:e,plan:t});!function(e){if(R=e,m){if(m.classList.remove("hidden"),p&&(p.textContent=`${e.count} row(s) generated`),h&&(h.textContent=e.sql||""),u&&g){u.innerHTML="",g.innerHTML="";const t=document.createElement("tr"),n=document.createElement("th");n.className="w-12 text-center text-base-content/40",n.textContent="#",t.appendChild(n),a.forEach(e=>{const n=document.createElement("th");n.className="whitespace-nowrap font-mono",n.innerHTML=F(e.name)+' <span class="text-[10px] text-base-content/40 font-normal">('+F(e.type||"text")+")</span>",t.appendChild(n)}),u.appendChild(t),(e.previewRows||[]).forEach((e,t)=>{const n=document.createElement("tr"),s=document.createElement("td");s.className="text-center font-mono text-[10px] text-base-content/40",s.textContent=String(t+1),n.appendChild(s),a.forEach(t=>{const a=document.createElement("td");a.className="font-mono whitespace-nowrap max-w-xs truncate";const s=e[t.name];null===s?a.innerHTML='<span class="badge badge-xs badge-ghost text-base-content/40">NULL</span>':"(default)"===s?a.innerHTML='<span class="badge badge-xs badge-outline text-base-content/40">DEFAULT</span>':"boolean"==typeof s?a.innerHTML=s?'<span class="badge badge-xs badge-success">TRUE</span>':'<span class="badge badge-xs badge-ghost">FALSE</span>':a.textContent=String(s),n.appendChild(a)}),g.appendChild(n)})}m.scrollIntoView({behavior:"smooth",block:"nearest"})}}(n),ue(n.warnings||[])}catch(e){window.UI&&UI.showError&&UI.showError(e.message)}finally{w&&(w.disabled=!1)}}!function(){a.forEach(e=>{!function(e){const t=document.createElement("div");t.className="app-card p-4 flex flex-col justify-between transition-shadow hover:shadow-md",t.dataset.column=e.name,t.dataset.type=(e.type||"").toLowerCase();const n=document.createElement("div");n.className="space-y-3";const a=document.createElement("div");a.className="flex items-start justify-between gap-2";const s=document.createElement("div");s.className="min-w-0";const o=document.createElement("div");o.className="flex flex-wrap items-center gap-1.5";const l=document.createElement("span");if(l.className="font-mono text-sm font-bold tracking-tight text-base-content",l.textContent=e.name,o.appendChild(l),e.pk){const e=document.createElement("span");e.className="badge badge-xs badge-warning font-mono font-bold",e.textContent="PK",o.appendChild(e)}if(e.notnull){const e=document.createElement("span");e.className="badge badge-xs badge-info font-mono",e.textContent="NOT NULL",o.appendChild(e)}else{const e=document.createElement("span");e.className="badge badge-xs badge-ghost text-base-content/40 font-mono",e.textContent="NULLABLE",o.appendChild(e)}if(e.unique){const e=document.createElement("span");e.className="badge badge-xs badge-secondary font-mono",e.textContent="UNIQUE",o.appendChild(e)}if(e.hasDefault){const e=document.createElement("span");e.className="badge badge-xs badge-ghost font-mono",e.textContent="DEFAULT",o.appendChild(e)}if(e.fk){const t=document.createElement("span");t.className="badge badge-xs badge-accent font-mono",t.textContent="FK → "+e.fk.table,o.appendChild(t)}const r=document.createElement("p");r.className="mt-0.5 text-[11px] font-mono text-base-content/45",r.textContent=e.type||"untyped",s.appendChild(o),s.appendChild(r),a.appendChild(s),n.appendChild(a);const d=document.createElement("select");d.className="field-select seed-strategy w-full !py-1.5 !text-xs font-medium",d.dataset.column=e.name;const i={};(e.strategies||[]).forEach(e=>{const t=e.category||"custom_control";i[t]||(i[t]=[]),i[t].push(e)}),Object.keys(i).forEach(e=>{const t=document.createElement("optgroup");t.label=se[e]||e,i[e].forEach(e=>{const n=document.createElement("option");n.value=e.id,n.textContent=e.label,t.appendChild(n)}),d.appendChild(t)}),n.appendChild(d);const m=document.createElement("div");m.className="flex items-center justify-between gap-2 px-2.5 py-1.5 rounded-lg bg-base-200/50 text-[11px]",m.innerHTML='<div class="flex items-center gap-1.5 min-w-0 overflow-hidden text-ellipsis"><span class="text-base-content/50 shrink-0 font-medium">Sample:</span><span class="seed-sample-value font-mono text-primary font-medium truncate">...</span></div><button type="button" class="seed-sample-refresh btn btn-ghost btn-xs btn-square h-5 w-5 shrink-0" title="Reroll sample">🎲</button>',m.querySelector(".seed-sample-refresh").addEventListener("click",()=>le(e,t)),n.appendChild(m);const p=document.createElement("div");p.className="seed-options",n.appendChild(p),t.appendChild(n);const u=document.createElement("div");u.className="mt-3 pt-2";const g=document.createElement("p");g.className="seed-note hidden text-[11px] leading-relaxed text-base-content/45",u.appendChild(g);const b=document.createElement("p");b.className="seed-warn hidden text-[11px] font-medium text-warning flex items-center gap-1",u.appendChild(b),t.appendChild(u),d.addEventListener("change",()=>{const n=d.value;P[e.name]=Object.assign({},P[e.name]||{},{strategy:n}),ce(p,e,P[e.name],t),g.textContent=oe(e,n),g.classList.toggle("hidden",!g.textContent),re(e,b),le(e,t)}),c.appendChild(t)}(e),de(e)}),ie(),o&&o.addEventListener("input",ie),w&&w.addEventListener("click",ve),E&&E.addEventListener("click",()=>{l&&l.checked?A&&A.showModal():we(!1)}),Y&&Y.addEventListener("click",()=>{A&&A.close(),we(!0)}),D&&D.addEventListener("click",()=>{A&&A.close()}),v&&v.addEventListener("click",()=>a.forEach(de)),k&&k.addEventListener("click",()=>{a.forEach(e=>{const t=c.querySelector('.app-card[data-column="'+CSS.escape(e.name)+'"]');t&&le(e,t)})}),L&&L.addEventListener("click",()=>{(document.getElementById("seed-apply-all-dialog")||pe()).showModal()});const e=document.getElementById("preset-realistic"),t=document.getElementById("preset-minimal"),n=document.getElementById("preset-sparse"),r=document.getElementById("preset-numbered");e&&e.addEventListener("click",()=>me("realistic")),t&&t.addEventListener("click",()=>me("minimal")),n&&n.addEventListener("click",()=>me("sparse")),r&&r.addEventListener("click",()=>me("numbered")),U&&U.addEventListener("click",()=>{B&&T&&(q.textContent="Export Seed Plan",T.value=JSON.stringify(xe(),null,2),O.classList.add("hidden"),B.showModal())}),M&&M.addEventListener("click",()=>{B&&T&&(q.textContent="Import Seed Plan",T.value="",T.placeholder="Paste plan JSON here...",O.classList.remove("hidden"),B.showModal())}),j&&j.addEventListener("click",()=>{B&&B.close()}),O&&O.addEventListener("click",()=>{try{const e=JSON.parse(T.value);if("object"!=typeof e||!e)throw new Error("Invalid JSON format");a.forEach(t=>{if(e[t.name]){P[t.name]=e[t.name];const n=c.querySelector('.app-card[data-column="'+CSS.escape(t.name)+'"]');if(n){const e=n.querySelector(".seed-strategy"),a=n.querySelector(".seed-options"),s=n.querySelector(".seed-note"),o=n.querySelector(".seed-warn");e&&(e.value=P[t.name].strategy),ce(a,t,P[t.name],n),s.textContent=oe(t,P[t.name].strategy),s.classList.toggle("hidden",!s.textContent),re(t,o),le(t,n)}}}),B&&B.close(),window.UI&&UI.showToast&&UI.showToast("Seed plan configuration applied!","success")}catch(e){window.UI&&UI.showError&&UI.showError("Failed to parse JSON: "+e.message)}}),y&&y.addEventListener("click",()=>ge("table")),x&&x.addEventListener("click",()=>ge("sql")),C&&C.addEventListener("click",()=>{h&&h.textContent&&navigator.clipboard.writeText(h.textContent).then(()=>UI.showToast("Copied SQL to clipboard.","success")).catch(()=>UI.showError("Could not copy SQL."))}),S&&S.addEventListener("click",ye),I&&I.addEventListener("click",fe),N&&N.addEventListener("click",he),document.querySelectorAll(".seed-quick").forEach(e=>{e.addEventListener("click",()=>{s&&(s.value=e.dataset.n)})})}()}();