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.
- package/README.md +262 -329
- package/dist/app.d.ts +25 -4
- package/dist/app.js +79 -15
- package/dist/app.js.map +1 -1
- package/dist/auth/config.d.ts +19 -0
- package/dist/auth/config.js +147 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/crypto.d.ts +27 -0
- package/dist/auth/crypto.js +133 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +22 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +14 -0
- package/dist/auth/middleware.js +100 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/routes.d.ts +7 -0
- package/dist/auth/routes.js +78 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/auth/types.d.ts +35 -0
- package/dist/auth/types.js +11 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/args.d.ts +45 -0
- package/dist/cli/args.js +328 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/config.d.ts +56 -0
- package/dist/cli/config.js +133 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +21 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/runner.d.ts +1 -0
- package/dist/cli/runner.js +222 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli.js +2 -127
- package/dist/cli.js.map +1 -1
- package/dist/data/datasets.d.ts +17 -0
- package/dist/data/datasets.js +133 -0
- package/dist/data/datasets.js.map +1 -0
- package/dist/data/detector.d.ts +30 -0
- package/dist/data/detector.js +472 -0
- package/dist/data/detector.js.map +1 -0
- package/dist/data/engine.d.ts +10 -0
- package/dist/data/engine.js +211 -0
- package/dist/data/engine.js.map +1 -0
- package/dist/data/index.d.ts +5 -0
- package/dist/data/index.js +22 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/strategies.d.ts +30 -0
- package/dist/data/strategies.js +343 -0
- package/dist/data/strategies.js.map +1 -0
- package/dist/data/types.d.ts +73 -0
- package/dist/data/types.js +8 -0
- package/dist/data/types.js.map +1 -0
- package/dist/db/database.d.ts +14 -146
- package/dist/db/database.js +208 -461
- package/dist/db/database.js.map +1 -1
- package/dist/db/export.d.ts +2 -2
- package/dist/db/export.js +7 -2
- package/dist/db/export.js.map +1 -1
- package/dist/db/filters.d.ts +22 -0
- package/dist/db/filters.js +158 -0
- package/dist/db/filters.js.map +1 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +25 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/introspection.d.ts +6 -0
- package/dist/db/introspection.js +172 -0
- package/dist/db/introspection.js.map +1 -0
- package/dist/db/manager.d.ts +32 -19
- package/dist/db/manager.js +203 -44
- package/dist/db/manager.js.map +1 -1
- package/dist/db/migrations.d.ts +5 -0
- package/dist/db/migrations.js +110 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/postgres.d.ts +85 -0
- package/dist/db/postgres.js +745 -0
- package/dist/db/postgres.js.map +1 -0
- package/dist/db/types.d.ts +206 -0
- package/dist/db/types.js +7 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +46 -4
- package/dist/index.js.map +1 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/css/input.css +74 -11
- package/dist/public/js/app.js +142 -0
- package/dist/public/js/browse.js +591 -104
- package/dist/public/js/databases.js +105 -13
- package/dist/public/js/designer.js +55 -2
- package/dist/public/js/forms.js +610 -28
- package/dist/public/js/icons.js +51 -0
- package/dist/public/js/inspector.js +638 -0
- package/dist/public/js/query.js +93 -3
- package/dist/public/js/schema.js +185 -52
- package/dist/public/js/seed.js +865 -152
- package/dist/routes/api/helpers.d.ts +42 -0
- package/dist/routes/api/helpers.js +133 -0
- package/dist/routes/api/helpers.js.map +1 -0
- package/dist/routes/api/import-export.d.ts +3 -0
- package/dist/routes/api/import-export.js +85 -0
- package/dist/routes/api/import-export.js.map +1 -0
- package/dist/routes/api/index.d.ts +4 -0
- package/dist/routes/api/index.js +37 -0
- package/dist/routes/api/index.js.map +1 -0
- package/dist/routes/api/query.d.ts +3 -0
- package/dist/routes/api/query.js +76 -0
- package/dist/routes/api/query.js.map +1 -0
- package/dist/routes/api/rows.d.ts +3 -0
- package/dist/routes/api/rows.js +373 -0
- package/dist/routes/api/rows.js.map +1 -0
- package/dist/routes/api/seed.d.ts +3 -0
- package/dist/routes/api/seed.js +82 -0
- package/dist/routes/api/seed.js.map +1 -0
- package/dist/routes/api/tables.d.ts +3 -0
- package/dist/routes/api/tables.js +160 -0
- package/dist/routes/api/tables.js.map +1 -0
- package/dist/routes/databases.d.ts +8 -11
- package/dist/routes/databases.js +100 -66
- package/dist/routes/databases.js.map +1 -1
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +20 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/pages.d.ts +3 -3
- package/dist/routes/pages.js +157 -64
- package/dist/routes/pages.js.map +1 -1
- package/dist/serverless.d.ts +40 -0
- package/dist/serverless.js +210 -0
- package/dist/serverless.js.map +1 -0
- package/dist/sql/generator.d.ts +7 -3
- package/dist/sql/generator.js +144 -7
- package/dist/sql/generator.js.map +1 -1
- package/dist/sql/index.d.ts +2 -0
- package/dist/sql/index.js +19 -0
- package/dist/sql/index.js.map +1 -0
- package/dist/types/api.d.ts +412 -0
- package/dist/types/api.js +9 -0
- package/dist/types/api.js.map +1 -0
- package/dist/utils/colors.d.ts +40 -0
- package/dist/utils/colors.js +64 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/{util.d.ts → utils/common.d.ts} +10 -6
- package/dist/{util.js → utils/common.js} +80 -13
- package/dist/utils/common.js.map +1 -0
- package/dist/utils/csv.js.map +1 -0
- package/dist/utils/datatype.d.ts +49 -0
- package/dist/utils/datatype.js +222 -0
- package/dist/utils/datatype.js.map +1 -0
- package/dist/utils/icons.d.ts +11 -0
- package/dist/utils/icons.js +72 -0
- package/dist/utils/icons.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/{logger.d.ts → utils/logger.d.ts} +1 -1
- package/dist/utils/logger.js +40 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/views/layouts/main.hbs +40 -11
- package/dist/views/pages/databases.hbs +182 -74
- package/dist/views/pages/designer.hbs +29 -11
- package/dist/views/pages/error.hbs +3 -3
- package/dist/views/pages/form.hbs +5 -3
- package/dist/views/pages/home.hbs +22 -13
- package/dist/views/pages/login.hbs +111 -0
- package/dist/views/pages/query.hbs +8 -6
- package/dist/views/pages/schema.hbs +41 -13
- package/dist/views/pages/seed.hbs +200 -54
- package/dist/views/pages/table.hbs +152 -72
- package/dist/views/partials/icon.hbs +2 -0
- package/dist/views/partials/navbar.hbs +69 -25
- package/dist/views/partials/sidebar.hbs +108 -94
- package/docs/API.md +144 -0
- package/docs/EXAMPLES.md +576 -0
- package/docs/SECURITY.md +265 -0
- package/package.json +15 -4
- package/dist/args.d.ts +0 -28
- package/dist/args.js +0 -187
- package/dist/args.js.map +0 -1
- package/dist/config.d.ts +0 -17
- package/dist/config.js +0 -23
- package/dist/config.js.map +0 -1
- package/dist/csv.js.map +0 -1
- package/dist/data/generator.d.ts +0 -86
- package/dist/data/generator.js +0 -1031
- package/dist/data/generator.js.map +0 -1
- package/dist/logger.js +0 -25
- package/dist/logger.js.map +0 -1
- package/dist/routes/api.d.ts +0 -9
- package/dist/routes/api.js +0 -848
- package/dist/routes/api.js.map +0 -1
- package/dist/test/classifier.test.d.ts +0 -1
- package/dist/test/classifier.test.js +0 -44
- package/dist/test/classifier.test.js.map +0 -1
- package/dist/test/csv.test.d.ts +0 -1
- package/dist/test/csv.test.js +0 -64
- package/dist/test/csv.test.js.map +0 -1
- package/dist/test/database.test.d.ts +0 -1
- package/dist/test/database.test.js +0 -402
- package/dist/test/database.test.js.map +0 -1
- package/dist/test/generator.test.d.ts +0 -1
- package/dist/test/generator.test.js +0 -132
- package/dist/test/generator.test.js.map +0 -1
- package/dist/test/manager.test.d.ts +0 -1
- package/dist/test/manager.test.js +0 -146
- package/dist/test/manager.test.js.map +0 -1
- package/dist/util.js.map +0 -1
- /package/dist/{csv.d.ts → utils/csv.d.ts} +0 -0
- /package/dist/{csv.js → utils/csv.js} +0 -0
package/dist/public/js/seed.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
/* "Seed data" page.
|
|
1
|
+
/* "Seed data" page — enhanced interactive data generator.
|
|
2
2
|
*
|
|
3
|
-
* Lets you generate
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* generators (first/last name, email, phone, UUID, random int/date/…), plus
|
|
7
|
-
* "random from referenced table" for foreign-key columns.
|
|
8
|
-
*
|
|
9
|
-
* "Preview SQL" generates the rows and shows the INSERT statements without
|
|
10
|
-
* executing them; "Insert rows" writes them inside a single transaction.
|
|
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.
|
|
11
6
|
*/
|
|
12
7
|
(function () {
|
|
13
8
|
'use strict';
|
|
@@ -19,180 +14,569 @@
|
|
|
19
14
|
const t = encodeURIComponent(cfg.table);
|
|
20
15
|
const columns = cfg.columns || [];
|
|
21
16
|
|
|
17
|
+
// DOM Elements
|
|
22
18
|
const countInput = document.getElementById('seed-count');
|
|
19
|
+
const searchInput = document.getElementById('seed-search');
|
|
20
|
+
const truncateCheckbox = document.getElementById('seed-truncate');
|
|
23
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
24
|
const feedback = document.getElementById('seed-feedback');
|
|
25
|
-
const
|
|
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');
|
|
26
31
|
const sqlCode = document.getElementById('seed-sql-code');
|
|
27
|
-
const
|
|
32
|
+
const tabTableView = document.getElementById('tab-table-view');
|
|
33
|
+
const tabSqlView = document.getElementById('tab-sql-view');
|
|
34
|
+
|
|
35
|
+
// Action buttons
|
|
28
36
|
const insertBtn = document.getElementById('seed-insert');
|
|
29
37
|
const previewBtn = document.getElementById('seed-preview');
|
|
30
38
|
const resetBtn = document.getElementById('seed-reset');
|
|
39
|
+
const refreshSamplesBtn = document.getElementById('seed-refresh-samples');
|
|
31
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;
|
|
32
62
|
|
|
33
63
|
function escapeHtml(s) {
|
|
34
64
|
return (window.UI && UI.escapeHtml) ? UI.escapeHtml(s) : String(s == null ? '' : s);
|
|
35
65
|
}
|
|
36
66
|
|
|
37
|
-
//
|
|
38
|
-
|
|
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
|
+
}
|
|
39
109
|
|
|
40
|
-
|
|
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 -------------------------------------
|
|
41
275
|
|
|
42
276
|
function field(key, label, type, attrs) {
|
|
43
277
|
return Object.assign({ key, label, type }, attrs || {});
|
|
44
278
|
}
|
|
279
|
+
|
|
45
280
|
const FIELD_DEFS = {
|
|
46
281
|
fixed: [field('value', 'Value', 'text')],
|
|
47
282
|
list: [field('values', 'One value per line', 'textarea')],
|
|
283
|
+
pattern: [field('pattern', 'Pattern (tokens: #=digit, ?=letter, {YYYY})', 'text', { placeholder: 'INV-{YYYY}-####' })],
|
|
48
284
|
int: [field('min', 'Min', 'number'), field('max', 'Max', 'number')],
|
|
49
|
-
decimal: [field('min', 'Min', 'number'), field('max', 'Max', 'number'), field('precision', 'Decimals', 'number', { step: 1 })],
|
|
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' })],
|
|
50
287
|
date: [field('from', 'From', 'date'), field('to', 'To', 'date')],
|
|
51
288
|
datetime: [field('from', 'From', 'datetime-local'), field('to', 'To', 'datetime-local')],
|
|
52
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' })],
|
|
53
294
|
words: [field('minLen', 'Min words', 'number'), field('maxLen', 'Max words', 'number')],
|
|
54
|
-
sentence: [field('minLen', 'Min', 'number'), field('maxLen', 'Max', 'number')],
|
|
295
|
+
sentence: [field('minLen', 'Min sentences', 'number'), field('maxLen', 'Max sentences', 'number')],
|
|
296
|
+
paragraph: [],
|
|
55
297
|
bytes: [field('minLen', 'Min bytes', 'number'), field('maxLen', 'Max bytes', 'number')],
|
|
56
298
|
json: [field('jsonKeys', 'JSON keys (comma-separated)', 'text')],
|
|
57
299
|
hash: [field('minLen', 'Min length', 'number'), field('maxLen', 'Max length', 'number')],
|
|
58
300
|
token: [field('minLen', 'Min length', 'number'), field('maxLen', 'Max length', 'number')],
|
|
59
301
|
};
|
|
60
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
|
+
|
|
61
315
|
function strategyNote(col, strategy) {
|
|
62
|
-
if (strategy === 'fk' && col.fk) return '
|
|
63
|
-
if (strategy === 'skip') return '
|
|
64
|
-
if (strategy === 'null') return '
|
|
65
|
-
if (strategy === '
|
|
66
|
-
if (strategy === '
|
|
67
|
-
if (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.';
|
|
68
324
|
return '';
|
|
69
325
|
}
|
|
70
326
|
|
|
71
|
-
// ---- Card
|
|
327
|
+
// ---- Card Building ------------------------------------------------------
|
|
72
328
|
|
|
73
|
-
function
|
|
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) {
|
|
74
338
|
container.innerHTML = '';
|
|
75
339
|
const fields = FIELD_DEFS[plan.strategy] || [];
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
el
|
|
98
|
-
if (f.step != null) el.step = String(f.step);
|
|
99
|
-
const v = plan[f.key];
|
|
100
|
-
if (f.key === 'jsonKeys' && Array.isArray(v)) {
|
|
101
|
-
el.value = v.join(', ');
|
|
102
|
-
} else if (v !== undefined && v !== null) {
|
|
103
|
-
el.value = String(v);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
el.dataset.optionKey = f.key;
|
|
107
|
-
el.addEventListener('input', () => {
|
|
108
|
-
const key = el.dataset.optionKey;
|
|
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;
|
|
109
362
|
if (f.type === 'textarea') {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
});
|
|
115
379
|
} else {
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
}
|
|
118
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);
|
|
119
413
|
});
|
|
120
|
-
wrap.appendChild(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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);
|
|
124
439
|
}
|
|
125
440
|
|
|
126
441
|
function buildCard(col) {
|
|
127
442
|
const card = document.createElement('div');
|
|
128
|
-
card.className = 'app-card p-4';
|
|
443
|
+
card.className = 'app-card p-4 flex flex-col justify-between transition-shadow hover:shadow-md';
|
|
129
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';
|
|
130
450
|
|
|
131
451
|
const head = document.createElement('div');
|
|
132
|
-
head.className = 'flex
|
|
452
|
+
head.className = 'flex items-start justify-between gap-2';
|
|
133
453
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
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';
|
|
138
460
|
name.textContent = col.name;
|
|
461
|
+
nameRow.appendChild(name);
|
|
462
|
+
|
|
139
463
|
if (col.pk) {
|
|
140
464
|
const chip = document.createElement('span');
|
|
141
|
-
chip.className = '
|
|
465
|
+
chip.className = 'badge badge-xs badge-warning font-mono font-bold';
|
|
142
466
|
chip.textContent = 'PK';
|
|
143
|
-
|
|
467
|
+
nameRow.appendChild(chip);
|
|
144
468
|
}
|
|
145
469
|
if (col.notnull) {
|
|
146
470
|
const chip = document.createElement('span');
|
|
147
|
-
chip.className = '
|
|
471
|
+
chip.className = 'badge badge-xs badge-info font-mono';
|
|
148
472
|
chip.textContent = 'NOT NULL';
|
|
149
|
-
|
|
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);
|
|
150
485
|
}
|
|
151
486
|
if (col.hasDefault) {
|
|
152
487
|
const chip = document.createElement('span');
|
|
153
|
-
chip.className = '
|
|
154
|
-
chip.textContent = '
|
|
155
|
-
|
|
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);
|
|
156
497
|
}
|
|
157
|
-
const type = document.createElement('p');
|
|
158
|
-
type.className = 'mt-0.5 text-xs text-base-content/45';
|
|
159
|
-
type.textContent = col.type || 'untyped';
|
|
160
|
-
title.appendChild(name);
|
|
161
|
-
title.appendChild(type);
|
|
162
|
-
head.appendChild(title);
|
|
163
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
|
|
164
509
|
const select = document.createElement('select');
|
|
165
|
-
select.className = 'field-select seed-strategy
|
|
510
|
+
select.className = 'field-select seed-strategy w-full !py-1.5 !text-xs font-medium';
|
|
166
511
|
select.dataset.column = col.name;
|
|
512
|
+
|
|
513
|
+
const grouped = {};
|
|
167
514
|
(col.strategies || []).forEach((s) => {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
select.appendChild(opt);
|
|
515
|
+
const cat = s.category || 'custom_control';
|
|
516
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
517
|
+
grouped[cat].push(s);
|
|
172
518
|
});
|
|
173
|
-
head.appendChild(select);
|
|
174
|
-
card.appendChild(head);
|
|
175
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
|
|
176
551
|
const options = document.createElement('div');
|
|
177
|
-
options.className = 'seed-options
|
|
178
|
-
|
|
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';
|
|
179
560
|
|
|
180
561
|
const note = document.createElement('p');
|
|
181
|
-
note.className = 'seed-note
|
|
182
|
-
|
|
562
|
+
note.className = 'seed-note hidden text-[11px] leading-relaxed text-base-content/45';
|
|
563
|
+
bottom.appendChild(note);
|
|
183
564
|
|
|
184
565
|
const warn = document.createElement('p');
|
|
185
|
-
warn.className = 'seed-warn
|
|
186
|
-
|
|
566
|
+
warn.className = 'seed-warn hidden text-[11px] font-medium text-warning flex items-center gap-1';
|
|
567
|
+
bottom.appendChild(warn);
|
|
187
568
|
|
|
188
|
-
|
|
569
|
+
card.appendChild(bottom);
|
|
570
|
+
|
|
571
|
+
// Event listener for strategy selection change
|
|
189
572
|
select.addEventListener('change', () => {
|
|
190
573
|
const st = select.value;
|
|
191
574
|
state[col.name] = Object.assign({}, state[col.name] || {}, { strategy: st });
|
|
192
|
-
buildOptionInputs(options, col, state[col.name]);
|
|
575
|
+
buildOptionInputs(options, col, state[col.name], card);
|
|
193
576
|
note.textContent = strategyNote(col, st);
|
|
194
577
|
note.classList.toggle('hidden', !note.textContent);
|
|
195
578
|
updateWarn(col, warn);
|
|
579
|
+
updateCardSample(col, card);
|
|
196
580
|
});
|
|
197
581
|
|
|
198
582
|
columnsBox.appendChild(card);
|
|
@@ -203,7 +587,7 @@
|
|
|
203
587
|
const st = state[col.name] ? state[col.name].strategy : null;
|
|
204
588
|
const risky = (st === 'skip' || st === 'null') && !col.pk && col.notnull && !col.hasDefault;
|
|
205
589
|
warnEl.textContent = risky
|
|
206
|
-
? '
|
|
590
|
+
? '⚠️ Column is NOT NULL with no default — "' + st + '" rows may fail.'
|
|
207
591
|
: '';
|
|
208
592
|
warnEl.classList.toggle('hidden', !warnEl.textContent);
|
|
209
593
|
}
|
|
@@ -211,19 +595,85 @@
|
|
|
211
595
|
function resetCard(col) {
|
|
212
596
|
const plan = Object.assign({}, col.defaultPlan || { strategy: 'skip' });
|
|
213
597
|
state[col.name] = plan;
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
select
|
|
217
|
-
const options =
|
|
218
|
-
const note =
|
|
219
|
-
const warn =
|
|
220
|
-
|
|
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);
|
|
221
606
|
note.textContent = strategyNote(col, plan.strategy);
|
|
222
607
|
note.classList.toggle('hidden', !note.textContent);
|
|
223
608
|
updateWarn(col, warn);
|
|
609
|
+
updateCardSample(col, card);
|
|
224
610
|
}
|
|
225
611
|
}
|
|
226
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
|
+
|
|
227
677
|
// ---- Apply strategy to all ---------------------------------------------
|
|
228
678
|
|
|
229
679
|
function unionStrategies() {
|
|
@@ -237,15 +687,15 @@
|
|
|
237
687
|
dialog.className = 'modal';
|
|
238
688
|
dialog.id = 'seed-apply-all-dialog';
|
|
239
689
|
const inner = document.createElement('div');
|
|
240
|
-
inner.className = 'modal-box app-card';
|
|
690
|
+
inner.className = 'modal-box app-card max-w-md';
|
|
241
691
|
inner.innerHTML =
|
|
242
|
-
'<h3 class="text-
|
|
243
|
-
'<p class="mt-1 text-
|
|
244
|
-
'<label class="field-label mt-4">Strategy</label>' +
|
|
245
|
-
'<select id="seed-apply-all-select" class="field-select w-full"></select>' +
|
|
246
|
-
'<div class="mt-
|
|
247
|
-
'<button id="seed-apply-all-cancel" type="button" class="btn btn-ghost">Cancel</button>' +
|
|
248
|
-
'<button id="seed-apply-all-go" type="button" class="btn btn-primary">Apply</button>' +
|
|
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>' +
|
|
249
699
|
'</div>';
|
|
250
700
|
dialog.appendChild(inner);
|
|
251
701
|
document.body.appendChild(dialog);
|
|
@@ -266,27 +716,30 @@
|
|
|
266
716
|
const supported = (col.strategies || []).some((s) => s.id === target);
|
|
267
717
|
if (!supported) return;
|
|
268
718
|
state[col.name] = Object.assign({}, state[col.name] || {}, { strategy: target });
|
|
269
|
-
const
|
|
270
|
-
if (
|
|
271
|
-
sel
|
|
272
|
-
const options =
|
|
273
|
-
const note =
|
|
274
|
-
const warn =
|
|
275
|
-
|
|
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);
|
|
276
727
|
note.textContent = strategyNote(col, target);
|
|
277
728
|
note.classList.toggle('hidden', !note.textContent);
|
|
278
729
|
updateWarn(col, warn);
|
|
730
|
+
updateCardSample(col, card);
|
|
279
731
|
}
|
|
280
732
|
});
|
|
281
733
|
close();
|
|
734
|
+
if (window.UI && UI.showToast) UI.showToast('Strategy applied across columns.', 'success');
|
|
282
735
|
});
|
|
283
736
|
return dialog;
|
|
284
737
|
}
|
|
285
738
|
|
|
286
|
-
// ----
|
|
739
|
+
// ---- Dual-Mode Preview Rendering ---------------------------------------
|
|
287
740
|
|
|
288
741
|
function showFeedback(warnings) {
|
|
289
|
-
feedback
|
|
742
|
+
if (!feedback) return;
|
|
290
743
|
if (!warnings || !warnings.length) {
|
|
291
744
|
feedback.innerHTML = '';
|
|
292
745
|
feedback.classList.add('hidden');
|
|
@@ -295,17 +748,136 @@
|
|
|
295
748
|
feedback.innerHTML =
|
|
296
749
|
'<div class="alert alert-warning shadow-soft">' +
|
|
297
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>' +
|
|
298
|
-
'<div class="text-
|
|
751
|
+
'<div class="text-xs space-y-1">' + warnings.map((w) => '<p>' + escapeHtml(w) + '</p>').join('') + '</div>' +
|
|
299
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' });
|
|
300
815
|
}
|
|
301
816
|
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
}
|
|
306
829
|
}
|
|
307
830
|
|
|
308
|
-
// ----
|
|
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 ----------------------------------------------
|
|
309
881
|
|
|
310
882
|
function collectPlan() {
|
|
311
883
|
const plan = {};
|
|
@@ -327,21 +899,43 @@
|
|
|
327
899
|
return Math.max(1, Math.min(cfg.maxRows || 5000, Number.isFinite(n) ? n : 10));
|
|
328
900
|
}
|
|
329
901
|
|
|
330
|
-
async function
|
|
902
|
+
async function executeSeed(truncate = false) {
|
|
331
903
|
const count = currentCount();
|
|
332
904
|
const plan = collectPlan();
|
|
333
|
-
const path =
|
|
905
|
+
const path = '/api/tables/' + t + '/seed';
|
|
334
906
|
try {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
showFeedback(data.warnings || []);
|
|
339
|
-
} else {
|
|
340
|
-
showSql(data.sql);
|
|
341
|
-
showFeedback(data.warnings || []);
|
|
907
|
+
if (insertBtn) {
|
|
908
|
+
insertBtn.disabled = true;
|
|
909
|
+
insertBtn.classList.add('loading');
|
|
342
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 || []);
|
|
343
916
|
} catch (e) {
|
|
344
|
-
UI.showError(e.message);
|
|
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;
|
|
345
939
|
}
|
|
346
940
|
}
|
|
347
941
|
|
|
@@ -352,20 +946,139 @@
|
|
|
352
946
|
buildCard(col);
|
|
353
947
|
resetCard(col);
|
|
354
948
|
});
|
|
355
|
-
|
|
356
|
-
|
|
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
|
+
|
|
357
979
|
if (resetBtn) resetBtn.addEventListener('click', () => columns.forEach(resetCard));
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
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
|
+
});
|
|
361
985
|
});
|
|
362
|
-
|
|
363
|
-
|
|
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;
|
|
364
1071
|
navigator.clipboard.writeText(sqlCode.textContent)
|
|
365
|
-
.then(() => UI.showToast('Copied to clipboard.', 'success'))
|
|
366
|
-
.catch(() => UI.showError('Could not copy.'));
|
|
1072
|
+
.then(() => UI.showToast('Copied SQL to clipboard.', 'success'))
|
|
1073
|
+
.catch(() => UI.showError('Could not copy SQL.'));
|
|
367
1074
|
});
|
|
368
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
|
|
369
1082
|
document.querySelectorAll('.seed-quick').forEach((btn) => {
|
|
370
1083
|
btn.addEventListener('click', () => {
|
|
371
1084
|
if (countInput) countInput.value = btn.dataset.n;
|