admindb 2.5.0 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/dist/app.js +13 -1
- package/dist/core/diff/data-differ.d.ts +1 -0
- package/dist/core/diff/data-differ.js +243 -0
- package/dist/core/diff/patch-generator.d.ts +1 -0
- package/dist/core/diff/patch-generator.js +77 -0
- package/dist/core/diff/schema-differ.d.ts +1 -0
- package/dist/core/diff/schema-differ.js +229 -0
- package/dist/core/router.d.ts +1 -1
- package/dist/core/transfer/dialect-mapper.d.ts +1 -0
- package/dist/core/transfer/dialect-mapper.js +303 -0
- package/dist/core/transfer/masking.d.ts +1 -0
- package/dist/core/transfer/masking.js +49 -0
- package/dist/core/transfer/sync-engine.d.ts +1 -0
- package/dist/core/transfer/sync-engine.js +234 -0
- package/dist/core/transfer/topological-sort.d.ts +1 -0
- package/dist/core/transfer/topological-sort.js +55 -0
- package/dist/db/dialects/postgres/introspector.js +71 -71
- package/dist/db/dialects/sqlite/introspector.js +6 -3
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +9 -1
- package/dist/modules/search/index.d.ts +1 -0
- package/dist/modules/search/index.js +19 -0
- package/dist/modules/search/search.routes.d.ts +1 -0
- package/dist/modules/search/search.routes.js +38 -0
- package/dist/modules/search/search.service.d.ts +1 -0
- package/dist/modules/search/search.service.js +458 -0
- package/dist/modules/search/search.types.d.ts +1 -0
- package/dist/modules/search/search.types.js +2 -0
- package/dist/modules/sync/index.d.ts +1 -0
- package/dist/modules/sync/index.js +18 -0
- package/dist/modules/sync/sync.routes.d.ts +1 -0
- package/dist/modules/sync/sync.routes.js +135 -0
- package/dist/modules/sync/sync.service.d.ts +1 -0
- package/dist/modules/sync/sync.service.js +41 -0
- package/dist/public/css/app.css +2 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/search.js +1 -0
- package/dist/public/js/sync.js +1 -0
- package/dist/utils/icons.js +3 -0
- package/dist/views/layouts/main.hbs +1 -1
- package/dist/views/pages/error.hbs +1 -10
- package/dist/views/pages/seed-select.hbs +252 -1
- package/dist/views/pages/seed.hbs +307 -1
- package/dist/views/pages/sync.hbs +1 -0
- package/dist/views/pages/table.hbs +1 -1
- package/dist/views/partials/navbar.hbs +1 -1
- package/dist/views/partials/sidebar.hbs +1 -1
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- package/package.json +14 -9
|
@@ -1,10 +1 @@
|
|
|
1
|
-
<div class="mx-auto mt-16 max-w-md space-y-6 text-center">
|
|
2
|
-
<div class="mx-auto grid h-20 w-20 place-items-center rounded-3xl bg-error/10 text-error">
|
|
3
|
-
{{{icon "warning" class="h-10 w-10"}}}
|
|
4
|
-
</div>
|
|
5
|
-
<div>
|
|
6
|
-
<h1 class="text-5xl font-extrabold tracking-tight text-error">{{status}}</h1>
|
|
7
|
-
<p class="mt-2 text-base-content/60">{{error}}</p>
|
|
8
|
-
</div>
|
|
9
|
-
<a href="{{basePath}}/" class="btn btn-primary">Back to home</a>
|
|
10
|
-
</div>
|
|
1
|
+
<div class="mx-auto mt-16 max-w-md space-y-6 text-center">
|
|
11
2
|
<div class="mx-auto grid h-20 w-20 place-items-center rounded-3xl bg-error/10 text-error">
|
|
12
3
|
{{{icon "warning" class="h-10 w-10"}}}
|
|
13
4
|
</div>
|
|
14
5
|
<div>
|
|
15
6
|
<h1 class="text-5xl font-extrabold tracking-tight text-error">{{status}}</h1>
|
|
16
7
|
<p class="mt-2 text-base-content/60">{{error}}</p>
|
|
17
8
|
</div>
|
|
18
9
|
<a href="{{basePath}}/" class="btn btn-primary">Back to home</a>
|
|
@@ -1 +1,252 @@
|
|
|
1
|
-
<div class="space-y-6 max-w-5xl mx-auto pb-16">
|
|
2
1
|
<div class="page-header">
|
|
3
2
|
<div class="page-header-row">
|
|
4
3
|
<div>
|
|
5
4
|
<div class="page-pretitle">Developer Tools</div>
|
|
6
5
|
<h1 class="page-title">Seed Data Generator</h1>
|
|
7
6
|
<p class="mt-0.5 text-xs text-base-content/60">
|
|
8
7
|
Generate realistic mock datasets with column heuristics or populate complete relational entity graphs.
|
|
9
8
|
</p>
|
|
10
9
|
</div>
|
|
11
10
|
</div>
|
|
12
11
|
</div>
|
|
13
12
|
{{#if hasTables}}
|
|
14
13
|
<div class="grid gap-6 lg:grid-cols-[1fr_380px]">
|
|
15
14
|
<div class="space-y-4">
|
|
16
15
|
<div class="app-card overflow-hidden">
|
|
17
16
|
<div class="p-4 sm:p-5 border-b border-base-200 bg-base-200/30 flex flex-wrap items-center justify-between gap-3">
|
|
18
17
|
<div>
|
|
19
18
|
<h2 class="text-sm font-bold text-base-content flex items-center gap-2">
|
|
20
19
|
<span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">1</span>
|
|
21
20
|
Choose Target Table
|
|
22
21
|
</h2>
|
|
23
22
|
<p class="text-[11px] text-base-content/50 mt-0.5">Select the root table to generate rows for.</p>
|
|
24
23
|
</div>
|
|
25
24
|
<div class="w-full sm:w-56">
|
|
26
25
|
<div class="relative">
|
|
27
26
|
<span class="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-base-content/40">
|
|
28
27
|
{{{icon "search" class="h-3.5 w-3.5"}}}
|
|
29
28
|
</span>
|
|
30
29
|
<input id="table-search-input" type="text" placeholder="Filter tables…" autocomplete="off"
|
|
31
30
|
class="field-input text-xs !py-1 !pl-8 w-full" />
|
|
32
31
|
</div>
|
|
33
32
|
</div>
|
|
34
33
|
</div>
|
|
35
34
|
<div id="table-cards-list" class="p-3 sm:p-4 space-y-2 max-h-[480px] overflow-y-auto">
|
|
36
35
|
{{#each (or seedTables tables)}}
|
|
37
36
|
<label class="table-option-card block cursor-pointer select-none rounded-xl border p-3 transition-all {{#if (eq this.name ../defaultTable)}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
|
|
38
37
|
data-table="{{this.name}}">
|
|
39
38
|
<div class="flex items-center justify-between gap-3">
|
|
40
39
|
<div class="flex items-center gap-3 min-w-0">
|
|
41
40
|
<input type="radio" name="selected_table" value="{{this.name}}" class="radio radio-primary radio-xs"
|
|
42
41
|
{{#if (eq this.name ../defaultTable)}}checked{{/if}} />
|
|
43
42
|
<div class="min-w-0">
|
|
44
43
|
<div class="font-mono font-bold text-sm text-base-content truncate flex items-center gap-2">
|
|
45
44
|
{{{icon "table" class="h-3.5 w-3.5 text-base-content/50 shrink-0"}}}
|
|
46
45
|
<span>{{this.name}}</span>
|
|
47
46
|
</div>
|
|
48
47
|
<div class="flex items-center gap-2 mt-0.5 text-[11px] text-base-content/50">
|
|
49
48
|
<span>{{this.cols}} cols</span>
|
|
50
49
|
<span>·</span>
|
|
51
50
|
<span>{{this.rowCount}} existing rows</span>
|
|
52
51
|
{{#if this.fks}}
|
|
53
52
|
<span>·</span>
|
|
54
53
|
<span class="text-amber-500 font-medium">{{this.fks}} FK refs</span>
|
|
55
54
|
{{/if}}
|
|
56
55
|
</div>
|
|
57
56
|
</div>
|
|
58
57
|
</div>
|
|
59
58
|
<div class="flex items-center gap-1 shrink-0">
|
|
60
59
|
<button type="button" class="btn btn-ghost btn-xs text-[11px] text-base-content/70 hover:text-primary quick-seed-btn"
|
|
61
60
|
data-table="{{this.name}}" data-mode="single" title="Seed this table directly">
|
|
62
61
|
Single
|
|
63
62
|
</button>
|
|
64
63
|
<button type="button" class="btn btn-ghost btn-xs text-[11px] text-emerald-600 hover:text-emerald-500 quick-seed-btn"
|
|
65
64
|
data-table="{{this.name}}" data-mode="chain" title="Seed relational ER chain directly">
|
|
66
65
|
ER Chain
|
|
67
66
|
</button>
|
|
68
67
|
</div>
|
|
69
68
|
</div>
|
|
70
69
|
</label>
|
|
71
70
|
{{/each}}
|
|
72
71
|
</div>
|
|
73
72
|
</div>
|
|
74
73
|
</div>
|
|
75
74
|
<div class="space-y-4 lg:sticky lg:top-20 self-start">
|
|
76
75
|
<div class="app-card overflow-hidden">
|
|
77
76
|
<div class="p-4 border-b border-base-200 bg-base-200/30">
|
|
78
77
|
<h2 class="text-sm font-bold text-base-content flex items-center gap-2">
|
|
79
78
|
<span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">2</span>
|
|
80
79
|
Choose Generation Mode
|
|
81
80
|
</h2>
|
|
82
81
|
<p class="text-[11px] text-base-content/50 mt-0.5">Select how rows should be populated.</p>
|
|
83
82
|
</div>
|
|
84
83
|
<div class="p-3.5 space-y-2.5">
|
|
85
84
|
<label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'single')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
|
|
86
85
|
data-mode="single">
|
|
87
86
|
<div class="flex items-start gap-3">
|
|
88
87
|
<input type="radio" name="selected_mode" value="single" class="radio radio-primary radio-xs mt-0.5"
|
|
89
88
|
{{#if (eq defaultMode 'single')}}checked{{/if}} />
|
|
90
89
|
<div class="space-y-1">
|
|
91
90
|
<div class="flex items-center gap-2">
|
|
92
91
|
<span class="font-bold text-xs text-base-content">Single Table Seeder</span>
|
|
93
92
|
<span class="badge badge-xs badge-ghost text-[10px] font-mono">Standalone</span>
|
|
94
93
|
</div>
|
|
95
94
|
<p class="text-[11px] text-base-content/60 leading-relaxed">
|
|
96
95
|
Generates realistic rows for the selected table with automated column heuristics (names, emails, dates, addresses, UUIDs) and live formula templates.
|
|
97
96
|
</p>
|
|
98
97
|
</div>
|
|
99
98
|
</div>
|
|
100
99
|
</label>
|
|
101
100
|
<label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'chain')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
|
|
102
101
|
data-mode="chain">
|
|
103
102
|
<div class="flex items-start gap-3">
|
|
104
103
|
<input type="radio" name="selected_mode" value="chain" class="radio radio-primary radio-xs mt-0.5"
|
|
105
104
|
{{#if (eq defaultMode 'chain')}}checked{{/if}} />
|
|
106
105
|
<div class="space-y-1">
|
|
107
106
|
<div class="flex items-center gap-2">
|
|
108
107
|
<span class="font-bold text-xs text-base-content">Relational ER Chain Seeder</span>
|
|
109
108
|
<span class="badge badge-xs badge-outline badge-success text-[10px] font-mono">Full Graph</span>
|
|
110
109
|
</div>
|
|
111
110
|
<p class="text-[11px] text-base-content/60 leading-relaxed">
|
|
112
111
|
Analyzes schema graph foreign keys and seeds related parent & child entities in topological order to guarantee full relational integrity.
|
|
113
112
|
</p>
|
|
114
113
|
</div>
|
|
115
114
|
</div>
|
|
116
115
|
</label>
|
|
117
116
|
</div>
|
|
118
117
|
<div class="p-3.5 border-t border-base-200 bg-base-200/20">
|
|
119
118
|
<button id="btn-launch-seed" type="button" class="btn btn-primary btn-sm w-full gap-2 font-bold shadow-sm">
|
|
120
119
|
{{{icon "seed" class="h-4 w-4"}}}
|
|
121
120
|
<span id="launch-btn-label">Open Seed Generator</span>
|
|
122
121
|
{{{icon "arrowRight" class="h-3.5 w-3.5"}}}
|
|
123
122
|
</button>
|
|
124
123
|
</div>
|
|
125
124
|
</div>
|
|
126
125
|
<div class="p-3.5 rounded-xl bg-base-200/50 border border-base-300 text-xs space-y-1 text-base-content/70">
|
|
127
126
|
<div class="font-semibold text-base-content flex items-center gap-1.5 text-[11px]">
|
|
128
127
|
{{{icon "info" class="h-3.5 w-3.5 text-primary"}}}
|
|
129
128
|
Deterministic Generation
|
|
130
129
|
</div>
|
|
131
130
|
<p class="text-[11px] leading-relaxed text-base-content/60">
|
|
132
131
|
You can specify an exact numeric seed for deterministic reproducible data, or import/export seed profiles.
|
|
133
132
|
</p>
|
|
134
133
|
</div>
|
|
135
134
|
</div>
|
|
136
135
|
</div>
|
|
137
136
|
{{else}}
|
|
138
137
|
<div class="empty-state">
|
|
139
138
|
<span class="empty-icon">{{{icon "table" class="h-7 w-7"}}}</span>
|
|
140
139
|
<p class="text-sm font-medium text-base-content/70">No tables available in the database</p>
|
|
141
140
|
<p class="text-xs text-base-content/50 mt-1 max-w-sm">Create a table using the visual designer or run SQL migrations before generating seed data.</p>
|
|
142
141
|
<div class="mt-4 flex gap-2">
|
|
143
142
|
<a href="{{basePath}}/designer/{{encodeUri dbId}}" class="btn btn-primary btn-sm gap-1.5">
|
|
144
143
|
{{{icon "plus" class="h-4 w-4"}}}
|
|
145
144
|
Create table
|
|
146
145
|
</a>
|
|
147
146
|
<a href="{{basePath}}/query/{{encodeUri dbId}}" class="btn btn-outline btn-sm gap-1.5">
|
|
148
147
|
{{{icon "query" class="h-4 w-4"}}}
|
|
149
148
|
Query editor
|
|
150
149
|
</a>
|
|
151
150
|
</div>
|
|
152
151
|
</div>
|
|
153
152
|
{{/if}}
|
|
154
153
|
const BASE = '{{basePath}}';
|
|
155
154
|
const DB_ID = '{{dbId}}';
|
|
156
155
|
let selectedTable = '{{defaultTable}}';
|
|
157
156
|
let selectedMode = '{{defaultMode}}' || 'single';
|
|
158
157
|
const tableCards = document.querySelectorAll('.table-option-card');
|
|
159
158
|
const modeCards = document.querySelectorAll('.mode-option-card');
|
|
160
159
|
const launchBtn = document.getElementById('btn-launch-seed');
|
|
161
160
|
const launchLabel = document.getElementById('launch-btn-label');
|
|
162
161
|
const searchInput = document.getElementById('table-search-input');
|
|
163
162
|
function updateLaunchLabel() {
|
|
164
163
|
if (launchLabel) {
|
|
165
164
|
launchLabel.textContent = selectedTable
|
|
166
165
|
? `Seed "${selectedTable}" (${selectedMode === 'chain' ? 'ER Chain' : 'Single Table'})`
|
|
167
166
|
: 'Open Seed Generator';
|
|
168
167
|
}
|
|
169
168
|
}
|
|
170
169
|
function setSelectedTable(tableName) {
|
|
171
170
|
selectedTable = tableName;
|
|
172
171
|
tableCards.forEach(card => {
|
|
173
172
|
const isMatch = card.dataset.table === tableName;
|
|
174
173
|
const radio = card.querySelector('input[type="radio"]');
|
|
175
174
|
if (radio) radio.checked = isMatch;
|
|
176
175
|
if (isMatch) {
|
|
177
176
|
card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
178
177
|
card.classList.remove('border-base-200', 'bg-base-100');
|
|
179
178
|
} else {
|
|
180
179
|
card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
181
180
|
card.classList.add('border-base-200', 'bg-base-100');
|
|
182
181
|
}
|
|
183
182
|
});
|
|
184
183
|
updateLaunchLabel();
|
|
185
184
|
}
|
|
186
185
|
function setSelectedMode(mode) {
|
|
187
186
|
selectedMode = mode;
|
|
188
187
|
modeCards.forEach(card => {
|
|
189
188
|
const isMatch = card.dataset.mode === mode;
|
|
190
189
|
const radio = card.querySelector('input[type="radio"]');
|
|
191
190
|
if (radio) radio.checked = isMatch;
|
|
192
191
|
if (isMatch) {
|
|
193
192
|
card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
194
193
|
card.classList.remove('border-base-200', 'bg-base-100');
|
|
195
194
|
} else {
|
|
196
195
|
card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
197
196
|
card.classList.add('border-base-200', 'bg-base-100');
|
|
198
197
|
}
|
|
199
198
|
});
|
|
200
199
|
updateLaunchLabel();
|
|
201
200
|
}
|
|
202
201
|
tableCards.forEach(card => {
|
|
203
202
|
card.addEventListener('click', (e) => {
|
|
204
203
|
if (e.target.closest('.quick-seed-btn')) return;
|
|
205
204
|
const table = card.dataset.table;
|
|
206
205
|
if (table) setSelectedTable(table);
|
|
207
206
|
});
|
|
208
207
|
});
|
|
209
208
|
modeCards.forEach(card => {
|
|
210
209
|
card.addEventListener('click', () => {
|
|
211
210
|
const mode = card.dataset.mode;
|
|
212
211
|
if (mode) setSelectedMode(mode);
|
|
213
212
|
});
|
|
214
213
|
});
|
|
215
214
|
// Direct quick-seed buttons
|
|
216
215
|
document.querySelectorAll('.quick-seed-btn').forEach(btn => {
|
|
217
216
|
btn.addEventListener('click', (e) => {
|
|
218
217
|
e.stopPropagation();
|
|
219
218
|
const table = btn.dataset.table;
|
|
220
219
|
const mode = btn.dataset.mode || 'single';
|
|
221
220
|
if (table) {
|
|
222
221
|
window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(table)}?mode=${encodeURIComponent(mode)}`;
|
|
223
222
|
}
|
|
224
223
|
});
|
|
225
224
|
});
|
|
226
225
|
// Launch button
|
|
227
226
|
if (launchBtn) {
|
|
228
227
|
launchBtn.addEventListener('click', () => {
|
|
229
228
|
if (!selectedTable) return;
|
|
230
229
|
window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(selectedTable)}?mode=${encodeURIComponent(selectedMode)}`;
|
|
231
230
|
});
|
|
232
231
|
}
|
|
233
232
|
// Real-time table search filter
|
|
234
233
|
if (searchInput) {
|
|
235
234
|
searchInput.addEventListener('input', () => {
|
|
236
235
|
const q = searchInput.value.trim().toLowerCase();
|
|
237
236
|
tableCards.forEach(card => {
|
|
238
237
|
const name = (card.dataset.table || '').toLowerCase();
|
|
239
238
|
if (!q || name.includes(q)) {
|
|
240
239
|
card.classList.remove('hidden');
|
|
241
240
|
} else {
|
|
242
241
|
card.classList.add('hidden');
|
|
243
242
|
}
|
|
244
243
|
});
|
|
245
244
|
});
|
|
246
245
|
}
|
|
247
246
|
updateLaunchLabel();
|
|
247
|
+
<div class="space-y-6 max-w-5xl mx-auto pb-16">
|
|
248
|
+
<div class="page-header">
|
|
249
|
+
<div class="page-header-row">
|
|
250
|
+
<div>
|
|
251
|
+
<div class="page-pretitle">Developer Tools</div>
|
|
252
|
+
<h1 class="page-title">Seed Data Generator</h1>
|
|
253
|
+
<p class="mt-0.5 text-xs text-base-content/60">
|
|
254
|
+
Generate realistic mock datasets with column heuristics or populate complete relational entity graphs.
|
|
255
|
+
</p>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
{{#if hasTables}}
|
|
260
|
+
<div class="grid gap-6 lg:grid-cols-[1fr_380px]">
|
|
261
|
+
<div class="space-y-4">
|
|
262
|
+
<div class="app-card overflow-hidden">
|
|
263
|
+
<div class="p-4 sm:p-5 border-b border-base-200 bg-base-200/30 flex flex-wrap items-center justify-between gap-3">
|
|
264
|
+
<div>
|
|
265
|
+
<h2 class="text-sm font-bold text-base-content flex items-center gap-2">
|
|
266
|
+
<span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">1</span>
|
|
267
|
+
Choose Target Table
|
|
268
|
+
</h2>
|
|
269
|
+
<p class="text-[11px] text-base-content/50 mt-0.5">Select the root table to generate rows for.</p>
|
|
270
|
+
</div>
|
|
271
|
+
<div class="w-full sm:w-56">
|
|
272
|
+
<div class="relative">
|
|
273
|
+
<span class="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-base-content/40">
|
|
274
|
+
{{{icon "search" class="h-3.5 w-3.5"}}}
|
|
275
|
+
</span>
|
|
276
|
+
<input id="table-search-input" type="text" placeholder="Filter tables…" autocomplete="off"
|
|
277
|
+
class="field-input text-xs !py-1 !pl-8 w-full" />
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
<div id="table-cards-list" class="p-3 sm:p-4 space-y-2 max-h-[480px] overflow-y-auto">
|
|
282
|
+
{{#each (or seedTables tables)}}
|
|
283
|
+
<label class="table-option-card block cursor-pointer select-none rounded-xl border p-3 transition-all {{#if (eq this.name ../defaultTable)}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
|
|
284
|
+
data-table="{{this.name}}">
|
|
285
|
+
<div class="flex items-center justify-between gap-3">
|
|
286
|
+
<div class="flex items-center gap-3 min-w-0">
|
|
287
|
+
<input type="radio" name="selected_table" value="{{this.name}}" class="radio radio-primary radio-xs"
|
|
288
|
+
{{#if (eq this.name ../defaultTable)}}checked{{/if}} />
|
|
289
|
+
<div class="min-w-0">
|
|
290
|
+
<div class="font-mono font-bold text-sm text-base-content truncate flex items-center gap-2">
|
|
291
|
+
{{{icon "table" class="h-3.5 w-3.5 text-base-content/50 shrink-0"}}}
|
|
292
|
+
<span>{{this.name}}</span>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="flex items-center gap-2 mt-0.5 text-[11px] text-base-content/50">
|
|
295
|
+
<span>{{this.cols}} cols</span>
|
|
296
|
+
<span>·</span>
|
|
297
|
+
<span>{{this.rowCount}} existing rows</span>
|
|
298
|
+
{{#if this.fks}}
|
|
299
|
+
<span>·</span>
|
|
300
|
+
<span class="text-amber-500 font-medium">{{this.fks}} FK refs</span>
|
|
301
|
+
{{/if}}
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
<div class="flex items-center gap-1 shrink-0">
|
|
306
|
+
<button type="button" class="btn btn-ghost btn-xs text-[11px] text-base-content/70 hover:text-primary quick-seed-btn"
|
|
307
|
+
data-table="{{this.name}}" data-mode="single" title="Seed this table directly">
|
|
308
|
+
Single
|
|
309
|
+
</button>
|
|
310
|
+
<button type="button" class="btn btn-ghost btn-xs text-[11px] text-emerald-600 hover:text-emerald-500 quick-seed-btn"
|
|
311
|
+
data-table="{{this.name}}" data-mode="chain" title="Seed relational ER chain directly">
|
|
312
|
+
ER Chain
|
|
313
|
+
</button>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</label>
|
|
317
|
+
{{/each}}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
<div class="space-y-4 lg:sticky lg:top-20 self-start">
|
|
322
|
+
<div class="app-card overflow-hidden">
|
|
323
|
+
<div class="p-4 border-b border-base-200 bg-base-200/30">
|
|
324
|
+
<h2 class="text-sm font-bold text-base-content flex items-center gap-2">
|
|
325
|
+
<span class="grid h-5 w-5 place-items-center rounded-md bg-primary/15 text-primary text-xs font-bold font-mono">2</span>
|
|
326
|
+
Choose Generation Mode
|
|
327
|
+
</h2>
|
|
328
|
+
<p class="text-[11px] text-base-content/50 mt-0.5">Select how rows should be populated.</p>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="p-3.5 space-y-2.5">
|
|
331
|
+
<label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'single')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
|
|
332
|
+
data-mode="single">
|
|
333
|
+
<div class="flex items-start gap-3">
|
|
334
|
+
<input type="radio" name="selected_mode" value="single" class="radio radio-primary radio-xs mt-0.5"
|
|
335
|
+
{{#if (eq defaultMode 'single')}}checked{{/if}} />
|
|
336
|
+
<div class="space-y-1">
|
|
337
|
+
<div class="flex items-center gap-2">
|
|
338
|
+
<span class="font-bold text-xs text-base-content">Single Table Seeder</span>
|
|
339
|
+
<span class="badge badge-xs badge-ghost text-[10px] font-mono">Standalone</span>
|
|
340
|
+
</div>
|
|
341
|
+
<p class="text-[11px] text-base-content/60 leading-relaxed">
|
|
342
|
+
Generates realistic rows for the selected table with automated column heuristics (names, emails, dates, addresses, UUIDs) and live formula templates.
|
|
343
|
+
</p>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
</label>
|
|
347
|
+
<label class="mode-option-card block cursor-pointer select-none rounded-xl border p-3.5 transition-all {{#if (eq defaultMode 'chain')}}border-primary bg-primary/[0.04] shadow-sm{{else}}border-base-200 bg-base-100 hover:border-base-300 hover:bg-base-200/40{{/if}}"
|
|
348
|
+
data-mode="chain">
|
|
349
|
+
<div class="flex items-start gap-3">
|
|
350
|
+
<input type="radio" name="selected_mode" value="chain" class="radio radio-primary radio-xs mt-0.5"
|
|
351
|
+
{{#if (eq defaultMode 'chain')}}checked{{/if}} />
|
|
352
|
+
<div class="space-y-1">
|
|
353
|
+
<div class="flex items-center gap-2">
|
|
354
|
+
<span class="font-bold text-xs text-base-content">Relational ER Chain Seeder</span>
|
|
355
|
+
<span class="badge badge-xs badge-outline badge-success text-[10px] font-mono">Full Graph</span>
|
|
356
|
+
</div>
|
|
357
|
+
<p class="text-[11px] text-base-content/60 leading-relaxed">
|
|
358
|
+
Analyzes schema graph foreign keys and seeds related parent & child entities in topological order to guarantee full relational integrity.
|
|
359
|
+
</p>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</label>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="p-3.5 border-t border-base-200 bg-base-200/20">
|
|
365
|
+
<button id="btn-launch-seed" type="button" class="btn btn-primary btn-sm w-full gap-2 font-bold shadow-sm">
|
|
366
|
+
{{{icon "seed" class="h-4 w-4"}}}
|
|
367
|
+
<span id="launch-btn-label">Open Seed Generator</span>
|
|
368
|
+
{{{icon "arrowRight" class="h-3.5 w-3.5"}}}
|
|
369
|
+
</button>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
<div class="p-3.5 rounded-xl bg-base-200/50 border border-base-300 text-xs space-y-1 text-base-content/70">
|
|
373
|
+
<div class="font-semibold text-base-content flex items-center gap-1.5 text-[11px]">
|
|
374
|
+
{{{icon "info" class="h-3.5 w-3.5 text-primary"}}}
|
|
375
|
+
Deterministic Generation
|
|
376
|
+
</div>
|
|
377
|
+
<p class="text-[11px] leading-relaxed text-base-content/60">
|
|
378
|
+
You can specify an exact numeric seed for deterministic reproducible data, or import/export seed profiles.
|
|
379
|
+
</p>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
{{else}}
|
|
384
|
+
<div class="empty-state">
|
|
385
|
+
<span class="empty-icon">{{{icon "table" class="h-7 w-7"}}}</span>
|
|
386
|
+
<p class="text-sm font-medium text-base-content/70">No tables available in the database</p>
|
|
387
|
+
<p class="text-xs text-base-content/50 mt-1 max-w-sm">Create a table using the visual designer or run SQL migrations before generating seed data.</p>
|
|
388
|
+
<div class="mt-4 flex gap-2">
|
|
389
|
+
<a href="{{basePath}}/designer/{{encodeUri dbId}}" class="btn btn-primary btn-sm gap-1.5">
|
|
390
|
+
{{{icon "plus" class="h-4 w-4"}}}
|
|
391
|
+
Create table
|
|
392
|
+
</a>
|
|
393
|
+
<a href="{{basePath}}/query/{{encodeUri dbId}}" class="btn btn-outline btn-sm gap-1.5">
|
|
394
|
+
{{{icon "query" class="h-4 w-4"}}}
|
|
395
|
+
Query editor
|
|
396
|
+
</a>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
{{/if}}
|
|
400
|
+
</div>
|
|
401
|
+
<script>
|
|
402
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
403
|
+
const BASE = '{{basePath}}';
|
|
404
|
+
const DB_ID = '{{dbId}}';
|
|
405
|
+
let selectedTable = '{{defaultTable}}';
|
|
406
|
+
let selectedMode = '{{defaultMode}}' || 'single';
|
|
407
|
+
const tableCards = document.querySelectorAll('.table-option-card');
|
|
408
|
+
const modeCards = document.querySelectorAll('.mode-option-card');
|
|
409
|
+
const launchBtn = document.getElementById('btn-launch-seed');
|
|
410
|
+
const launchLabel = document.getElementById('launch-btn-label');
|
|
411
|
+
const searchInput = document.getElementById('table-search-input');
|
|
412
|
+
function updateLaunchLabel() {
|
|
413
|
+
if (launchLabel) {
|
|
414
|
+
launchLabel.textContent = selectedTable
|
|
415
|
+
? `Seed "${selectedTable}" (${selectedMode === 'chain' ? 'ER Chain' : 'Single Table'})`
|
|
416
|
+
: 'Open Seed Generator';
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function setSelectedTable(tableName) {
|
|
420
|
+
selectedTable = tableName;
|
|
421
|
+
tableCards.forEach(card => {
|
|
422
|
+
const isMatch = card.dataset.table === tableName;
|
|
423
|
+
const radio = card.querySelector('input[type="radio"]');
|
|
424
|
+
if (radio) radio.checked = isMatch;
|
|
425
|
+
if (isMatch) {
|
|
426
|
+
card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
427
|
+
card.classList.remove('border-base-200', 'bg-base-100');
|
|
428
|
+
} else {
|
|
429
|
+
card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
430
|
+
card.classList.add('border-base-200', 'bg-base-100');
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
updateLaunchLabel();
|
|
434
|
+
}
|
|
435
|
+
function setSelectedMode(mode) {
|
|
436
|
+
selectedMode = mode;
|
|
437
|
+
modeCards.forEach(card => {
|
|
438
|
+
const isMatch = card.dataset.mode === mode;
|
|
439
|
+
const radio = card.querySelector('input[type="radio"]');
|
|
440
|
+
if (radio) radio.checked = isMatch;
|
|
441
|
+
if (isMatch) {
|
|
442
|
+
card.classList.add('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
443
|
+
card.classList.remove('border-base-200', 'bg-base-100');
|
|
444
|
+
} else {
|
|
445
|
+
card.classList.remove('border-primary', 'bg-primary/[0.04]', 'shadow-sm');
|
|
446
|
+
card.classList.add('border-base-200', 'bg-base-100');
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
updateLaunchLabel();
|
|
450
|
+
}
|
|
451
|
+
tableCards.forEach(card => {
|
|
452
|
+
card.addEventListener('click', (e) => {
|
|
453
|
+
if (e.target.closest('.quick-seed-btn')) return;
|
|
454
|
+
const table = card.dataset.table;
|
|
455
|
+
if (table) setSelectedTable(table);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
modeCards.forEach(card => {
|
|
459
|
+
card.addEventListener('click', () => {
|
|
460
|
+
const mode = card.dataset.mode;
|
|
461
|
+
if (mode) setSelectedMode(mode);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
// Direct quick-seed buttons
|
|
465
|
+
document.querySelectorAll('.quick-seed-btn').forEach(btn => {
|
|
466
|
+
btn.addEventListener('click', (e) => {
|
|
467
|
+
e.stopPropagation();
|
|
468
|
+
const table = btn.dataset.table;
|
|
469
|
+
const mode = btn.dataset.mode || 'single';
|
|
470
|
+
if (table) {
|
|
471
|
+
window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(table)}?mode=${encodeURIComponent(mode)}`;
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
// Launch button
|
|
476
|
+
if (launchBtn) {
|
|
477
|
+
launchBtn.addEventListener('click', () => {
|
|
478
|
+
if (!selectedTable) return;
|
|
479
|
+
window.location.href = `${BASE}/seed/${encodeURIComponent(DB_ID)}/${encodeURIComponent(selectedTable)}?mode=${encodeURIComponent(selectedMode)}`;
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
// Real-time table search filter
|
|
483
|
+
if (searchInput) {
|
|
484
|
+
searchInput.addEventListener('input', () => {
|
|
485
|
+
const q = searchInput.value.trim().toLowerCase();
|
|
486
|
+
tableCards.forEach(card => {
|
|
487
|
+
const name = (card.dataset.table || '').toLowerCase();
|
|
488
|
+
if (!q || name.includes(q)) {
|
|
489
|
+
card.classList.remove('hidden');
|
|
490
|
+
} else {
|
|
491
|
+
card.classList.add('hidden');
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
updateLaunchLabel();
|
|
497
|
+
});
|
|
498
|
+
</script>
|