pgkiln 0.82.0 → 0.83.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/db/migrations/153_component_events.sql +21 -0
- package/docs/apex-feature-parity.md +3 -3
- package/docs/guide/03-builder.md +8 -1
- package/docs/guide/04-pages-and-regions.md +2 -1
- package/docs/guide/07-dynamic-actions.md +27 -0
- package/docs/guide/11-from-apex.md +2 -1
- package/docs/guide/12-development.md +9 -1
- package/package.json +1 -1
- package/public/app.css +2 -0
- package/public/app.js +85 -4
- package/public/builder.css +25 -2
- package/public/data-modeler.js +8 -5
- package/public/sql-monitor.js +5 -2
- package/src/builder/ai-builder.ts +50 -46
- package/src/builder/api.ts +49 -49
- package/src/builder/autorest.ts +92 -87
- package/src/builder/changelog.ts +39 -37
- package/src/builder/components.ts +5 -4
- package/src/builder/data-modeler.ts +89 -84
- package/src/builder/data-security.ts +39 -37
- package/src/builder/dataload.ts +69 -65
- package/src/builder/datapump.ts +108 -97
- package/src/builder/ddl-tools.ts +38 -34
- package/src/builder/document-view.ts +11 -11
- package/src/builder/i18n/de.ts +1627 -0
- package/src/builder/i18n/en.ts +1627 -0
- package/src/builder/i18n/es.ts +1627 -0
- package/src/builder/i18n/fr.ts +1627 -0
- package/src/builder/i18n/it.ts +1627 -0
- package/src/builder/i18n/ja.ts +1627 -0
- package/src/builder/i18n/ko.ts +1627 -0
- package/src/builder/i18n/nl.ts +1627 -0
- package/src/builder/i18n/pt.ts +1627 -0
- package/src/builder/i18n/zh-cn.ts +1627 -0
- package/src/builder/i18n/zh-tw.ts +1627 -0
- package/src/builder/i18n.ts +18 -0
- package/src/builder/object-actions.ts +18 -16
- package/src/builder/object-browser.ts +2 -2
- package/src/builder/querybuilder.ts +42 -36
- package/src/builder/quicksql.ts +16 -18
- package/src/builder/region-settings.ts +201 -199
- package/src/builder/report-settings.ts +64 -60
- package/src/builder/rest-modules.ts +145 -143
- package/src/builder/sampledata.ts +59 -52
- package/src/builder/scheduling.ts +180 -159
- package/src/builder/scripts.ts +71 -65
- package/src/builder/sql-charts.ts +36 -30
- package/src/builder/sql-monitor.ts +95 -89
- package/src/builder/sql-utilities.ts +47 -40
- package/src/builder/top-sql.ts +21 -17
- package/src/builder/unload.ts +48 -44
- package/src/builder/worksheet.ts +3 -3
- package/src/cron-schedule.ts +47 -17
- package/src/metadata.ts +3 -1
- package/src/runtime/calendar.ts +3 -2
- package/src/runtime/context.ts +2 -0
- package/src/runtime/grid.ts +1 -0
- package/src/runtime/maps.ts +7 -4
- package/src/runtime/render.ts +6 -1
- package/src/runtime/routes.ts +4 -0
- package/src/runtime/tree.ts +4 -3
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import { clientIp, logActivity, type Session } from '../session.ts';
|
|
33
33
|
import { back, BASE, csrf, developer, flash, region, send, shell, workshopTabs, type Body, type Req } from './ui.ts';
|
|
34
34
|
import { currentWorkspace, workshopDb } from './workspaces.ts';
|
|
35
|
+
import { tr, type BuilderTranslate } from './i18n.ts';
|
|
35
36
|
|
|
36
37
|
// SQL Workshop → Sample Data (APEX 26.1: Data Generator): generate realistic
|
|
37
38
|
// rows for one or more tables of a schema (src/sampledata.ts). Step 1 picks a
|
|
@@ -92,28 +93,31 @@ function notes(c: ColumnInfo) {
|
|
|
92
93
|
return n.join(' · ');
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
/** (sprint 162) a generator's name and its options' help in the builder's language (sd.gen.<kind>, sd.opt.<kind>) */
|
|
97
|
+
const genLabel = (tx: BuilderTranslate, k: string, label: string) => tx.dyn(`sd.gen.${k}`, label);
|
|
98
|
+
const generatorHelp = (tx: BuilderTranslate) => html`<details class="sd-help u-mt1"><summary>${tx('sd.help')}</summary>
|
|
96
99
|
<dl class="sd-help-list">${Object.entries(GENERATORS).map(
|
|
97
|
-
([k, label]) => html`<dt>${label}</dt><dd>${OPTION_HELP[k as keyof typeof GENERATORS] ?? (k === 'foreign_key' ? 'a random existing parent row; parents generated in the same run are inserted first' : k === 'skip' ? 'not inserted: the column default, identity or null' : 'no options')}</dd>`,
|
|
100
|
+
([k, label]) => html`<dt>${genLabel(tx, k, label)}</dt><dd>${tx.dyn(`sd.opt.${k}`, OPTION_HELP[k as keyof typeof GENERATORS] ?? (k === 'foreign_key' ? 'a random existing parent row; parents generated in the same run are inserted first' : k === 'skip' ? 'not inserted: the column default, identity or null' : 'no options'))}</dd>`,
|
|
98
101
|
)}</dl></details>`;
|
|
99
102
|
|
|
100
103
|
export async function sampleDataRoutes(app: FastifyInstance) {
|
|
101
104
|
const page = (s: Session, reply: FastifyReply, title: string, main: Raw, code = 200) => {
|
|
102
105
|
reply.code(code);
|
|
106
|
+
const tx = tr(s);
|
|
107
|
+
const home = tx('sql.tool.sample');
|
|
103
108
|
return send(
|
|
104
109
|
reply,
|
|
105
110
|
s,
|
|
106
|
-
shell(s, title, [['
|
|
111
|
+
shell(s, title, [[tx('rail.sql_workshop'), `${BASE}/sql`], [home, title === home ? undefined : sdUrl], ...(title === home ? [] : ([[title]] as [string][]))], html`<h1 class="u-mb1">${tx('rail.sql_workshop')}</h1>${workshopTabs('sample', s)}${main}`, 'sql'),
|
|
107
112
|
);
|
|
108
113
|
};
|
|
109
114
|
|
|
110
|
-
const
|
|
111
|
-
foreign keys that pick existing parent rows. Preview them, insert them in one transaction (parents first) or download them as SQL or CSV.
|
|
112
|
-
Runs as the builder's owner connection (an isolated workspace's developer login for its developers), like the <a href="${BASE}/sql/worksheet">SQL Worksheet</a>.</p>`;
|
|
115
|
+
const introOf = (tx: BuilderTranslate) => html`<p class="muted">${tx.rich('sd.intro', { worksheet: html`<a href="${BASE}/sql/worksheet">${tx('sql.tool.worksheet')}</a>` })}</p>`;
|
|
113
116
|
|
|
114
117
|
// ------------------------------------------------------------ step 1 and 2: schema and tables
|
|
115
118
|
const start = async (s: Session, reply: FastifyReply, schema: string | undefined) => {
|
|
116
119
|
const db = await workshopDb(s);
|
|
120
|
+
const tx = tr(s);
|
|
117
121
|
const all = (await schemas(db.pool)).filter((x) => db === owner || x.usable);
|
|
118
122
|
const gens = (await owner.query<Saved & { updated_at: string; updated_by: string }>('select id, name, description, schema_name, tables, updated_at, updated_by from meta.data_generator where workspace_id = $1 order by lower(name)', [currentWorkspace(s)])).rows;
|
|
119
123
|
const savedList = gens.length
|
|
@@ -121,28 +125,28 @@ export async function sampleDataRoutes(app: FastifyInstance) {
|
|
|
121
125
|
(g) => html`<li><a href="${sdUrl}/${g.id}">${g.name}</a> <span class="muted small">${g.schema_name}: ${g.tables.map((t) => `${t.table} (${t.rows})`).join(', ')}</span>
|
|
122
126
|
${g.description ? html`<br><span class="muted small">${g.description}</span>` : ''}</li>`,
|
|
123
127
|
)}</ul>`
|
|
124
|
-
: html`<p class="muted"
|
|
128
|
+
: html`<p class="muted">${tx('sd.none_saved')}</p>`;
|
|
125
129
|
const chosen = schema && allowedSchema(schema) && all.some((x) => x.name === schema) ? schema : '';
|
|
126
130
|
const schemaForm = html`<form method="get" action="${sdUrl}" class="unload-pick">
|
|
127
|
-
<label class="label" for="f_schema"
|
|
128
|
-
<select id="f_schema" name="schema" required><option value=""
|
|
129
|
-
(x) => html`<option value="${x.name}"${x.name === chosen ? raw(' selected') : ''}>${x.name} (${x.tables
|
|
131
|
+
<label class="label" for="f_schema">${tx('sm.schema')}</label>
|
|
132
|
+
<select id="f_schema" name="schema" required><option value="">${tx('ddl.choose_schema')}</option>${all.map(
|
|
133
|
+
(x) => html`<option value="${x.name}"${x.name === chosen ? raw(' selected') : ''}>${x.name} (${x.tables === 1 ? tx('sd.table_one') : tx('sd.tables_n', { n: x.tables })})</option>`,
|
|
130
134
|
)}</select>
|
|
131
|
-
<button class="btn"
|
|
135
|
+
<button class="btn">${tx('ul.choose')}</button></form>`;
|
|
132
136
|
let tablesForm: Raw | '' = '';
|
|
133
137
|
if (chosen) {
|
|
134
138
|
const tables = await schemaTables(db.pool, chosen);
|
|
135
139
|
tablesForm = html`<form method="get" action="${sdUrl}" class="u-mt1"><input type="hidden" name="schema" value="${chosen}">
|
|
136
|
-
<fieldset class="prop-group"><legend
|
|
137
|
-
(t) => html`<label class="check"><input type="checkbox" name="t" value="${t.name}"> ${t.name} <span class="muted small">${t.estimate >= 0 ?
|
|
140
|
+
<fieldset class="prop-group"><legend>${tx('sd.tables_of', { schema: chosen })}</legend><div class="qb-pick">${tables.map(
|
|
141
|
+
(t) => html`<label class="check"><input type="checkbox" name="t" value="${t.name}"> ${t.name} <span class="muted small">${t.estimate >= 0 ? tx('sd.about_rows', { n: t.estimate.toLocaleString('en') }) : ''}</span></label>`,
|
|
138
142
|
)}</div></fieldset>
|
|
139
|
-
<div class="buttons"><button class="btn btn-hot">${icon('layers')}
|
|
140
|
-
} else if (schema) tablesForm = html`<div class="alert alert-error" role="alert"
|
|
143
|
+
<div class="buttons"><button class="btn btn-hot">${icon('layers')} ${tx('sd.propose')}</button></div></form>`;
|
|
144
|
+
} else if (schema) tablesForm = html`<div class="alert alert-error" role="alert">${tx('sd.own_schema')}</div>`;
|
|
141
145
|
return page(
|
|
142
146
|
s,
|
|
143
147
|
reply,
|
|
144
|
-
'
|
|
145
|
-
html`${
|
|
148
|
+
tx('sql.tool.sample'),
|
|
149
|
+
html`${introOf(tx)}${region(tx('sd.new'), html`${schemaForm}${tablesForm}`)}<div class="u-spacer"></div>${region(tx('sd.saved_title'), savedList, html`<span class="count">${gens.length}</span>`)}`,
|
|
146
150
|
);
|
|
147
151
|
};
|
|
148
152
|
|
|
@@ -175,23 +179,24 @@ export async function sampleDataRoutes(app: FastifyInstance) {
|
|
|
175
179
|
|
|
176
180
|
const editor = async (s: Session, reply: FastifyReply, st: FormState, code = 200) => {
|
|
177
181
|
const { tables, missing } = await describeAll(s, st.def);
|
|
178
|
-
const
|
|
182
|
+
const tx = tr(s);
|
|
183
|
+
const title = st.id ? (st.name ?? tx('sd.generator')) : tx('sd.new');
|
|
179
184
|
const columnRow = (i: number, j: number, c: ColumnInfo, spec: TableSpec) => {
|
|
180
185
|
const cs = spec.columns.find((x) => x.column === c.name)!;
|
|
181
186
|
const id = `${i}_${j}`;
|
|
182
187
|
return html`<div class="sd-col">
|
|
183
188
|
<div class="sd-col-name"><strong>${c.name}</strong><span class="muted small">${notes(c)}</span><input type="hidden" name="c_${id}" value="${c.name}"></div>
|
|
184
|
-
<div class="sd-col-gen"><select name="g_${id}" aria-label="${c.name}
|
|
185
|
-
([k, label]) => html`<option value="${k}"${cs.generator === k ? raw(' selected') : ''}>${label}</option>`,
|
|
189
|
+
<div class="sd-col-gen"><select name="g_${id}" aria-label="${tx('sd.col_generator', { column: c.name })}">${Object.entries(GENERATORS).map(
|
|
190
|
+
([k, label]) => html`<option value="${k}"${cs.generator === k ? raw(' selected') : ''}>${genLabel(tx, k, label)}</option>`,
|
|
186
191
|
)}</select></div>
|
|
187
|
-
<div class="sd-col-opt"><input name="o_${id}" value="${cs.options}" maxlength="4000" aria-label="${c.name}
|
|
188
|
-
<div class="sd-col-nulls"><input type="number" name="n_${id}" value="${cs.nulls}" min="0" max="100" step="1" aria-label="${c.name}
|
|
192
|
+
<div class="sd-col-opt"><input name="o_${id}" value="${cs.options}" maxlength="4000" aria-label="${tx('sd.col_options', { column: c.name })}" placeholder="${tx('sd.options')}" spellcheck="false"></div>
|
|
193
|
+
<div class="sd-col-nulls"><input type="number" name="n_${id}" value="${cs.nulls}" min="0" max="100" step="1" aria-label="${tx('sd.col_nulls', { column: c.name })}"><span class="muted small" aria-hidden="true">${tx('sd.pct_null')}</span></div>
|
|
189
194
|
</div>`;
|
|
190
195
|
};
|
|
191
196
|
const tableRegions = tables.map(
|
|
192
197
|
({ info, spec }, i) => html`<fieldset class="prop-group sd-table"><legend>${info.schema}.${info.name}</legend>
|
|
193
198
|
<input type="hidden" name="t" value="${info.name}">
|
|
194
|
-
<div class="field sd-rows"><label class="label" for="f_rows_${i}"
|
|
199
|
+
<div class="field sd-rows"><label class="label" for="f_rows_${i}">${tx('sm.col.rows')}</label>
|
|
195
200
|
<input type="number" id="f_rows_${i}" name="rows_${i}" value="${spec.rows}" min="0" max="${MAX_ROWS()}" step="1" required></div>
|
|
196
201
|
<div class="sd-cols">${info.columns.map((c, j) => columnRow(i, j, c, spec))}</div></fieldset>`,
|
|
197
202
|
);
|
|
@@ -199,42 +204,44 @@ export async function sampleDataRoutes(app: FastifyInstance) {
|
|
|
199
204
|
const form = html`<form method="post" action="${sdUrl}">${csrf(s)}
|
|
200
205
|
<input type="hidden" name="schema" value="${st.def.schema}">${st.id ? html`<input type="hidden" name="id" value="${st.id}">` : ''}
|
|
201
206
|
${st.error ? html`<div class="alert alert-error" role="alert">${st.error}</div>` : ''}
|
|
202
|
-
${missing.length ? html`<div class="alert alert-note"
|
|
207
|
+
${missing.length ? html`<div class="alert alert-note">${tx('sd.missing', { tables: missing.join(', ') })}</div>` : ''}
|
|
203
208
|
<div class="form-grid">
|
|
204
|
-
<div class="field"><label class="label" for="f_seed"
|
|
209
|
+
<div class="field"><label class="label" for="f_seed">${tx('sd.seed')}</label>
|
|
205
210
|
<input type="number" id="f_seed" name="seed" value="${st.def.seed ?? ''}" min="0" max="4294967295" step="1">
|
|
206
|
-
<small class="help"
|
|
207
|
-
<div class="field"><span class="label"
|
|
211
|
+
<small class="help">${tx('sd.seed_help')}</small></div>
|
|
212
|
+
<div class="field"><span class="label">${tx('sm.schema')}</span><p>${st.def.schema} <a class="small" href="${tablesLink}">${tx('sd.change_tables')}</a></p></div>
|
|
208
213
|
</div>
|
|
209
214
|
${tableRegions}
|
|
210
|
-
${generatorHelp}
|
|
215
|
+
${generatorHelp(tx)}
|
|
211
216
|
<div class="buttons u-mt1">
|
|
212
|
-
<button class="btn" name="action" value="preview">${icon('search')}
|
|
213
|
-
<button class="btn btn-hot" name="action" value="insert">${icon('play')}
|
|
214
|
-
<button class="btn" name="action" value="sql">${icon('download')}
|
|
215
|
-
<button class="btn" name="action" value="csv">${icon('download')}
|
|
216
|
-
<fieldset class="prop-group u-mt1"><legend
|
|
217
|
-
<div class="field"><label class="label" for="f_name"
|
|
218
|
-
<div class="field"><label class="label" for="f_description"
|
|
219
|
-
</div><div class="buttons"><button class="btn" name="action" value="save">${icon('check')} ${st.id ? '
|
|
217
|
+
<button class="btn" name="action" value="preview">${icon('search')} ${tx('sd.preview')}</button>
|
|
218
|
+
<button class="btn btn-hot" name="action" value="insert">${icon('play')} ${tx('sd.insert')}</button>
|
|
219
|
+
<button class="btn" name="action" value="sql">${icon('download')} ${tx('sd.download_sql')}</button>
|
|
220
|
+
<button class="btn" name="action" value="csv">${icon('download')} ${tx('sd.download_csv')}</button></div>
|
|
221
|
+
<fieldset class="prop-group u-mt1"><legend>${tx('sd.save_def')}</legend><div class="form-grid">
|
|
222
|
+
<div class="field"><label class="label" for="f_name">${tx('scr.name')}</label><input id="f_name" name="name" value="${st.name ?? ''}" maxlength="100"></div>
|
|
223
|
+
<div class="field"><label class="label" for="f_description">${tx('scr.description')}</label><input id="f_description" name="description" value="${st.description ?? ''}" maxlength="1000"></div>
|
|
224
|
+
</div><div class="buttons"><button class="btn" name="action" value="save">${icon('check')} ${st.id ? tx('scr.save') : tx('sd.save_new')}</button></div></fieldset>
|
|
220
225
|
</form>`;
|
|
221
226
|
const del = st.id
|
|
222
|
-
? html`<form method="post" action="${sdUrl}/${st.id}/delete" class="danger-zone">${csrf(s)}<button class="btn btn-danger" data-confirm="
|
|
227
|
+
? html`<form method="post" action="${sdUrl}/${st.id}/delete" class="danger-zone">${csrf(s)}<button class="btn btn-danger" data-confirm="${tx('sd.delete_confirm')}">${tx('sd.delete')}</button></form>`
|
|
223
228
|
: '';
|
|
224
|
-
return page(s, reply, title, html`${
|
|
229
|
+
return page(s, reply, title, html`${introOf(tx)}${st.result ? html`${st.result}<div class="u-spacer"></div>` : ''}${region(title, html`${form}${del}`)}`, code);
|
|
225
230
|
};
|
|
226
231
|
|
|
227
232
|
const sampleTable = (columns: string[], rows: (string | null)[][]) =>
|
|
228
233
|
html`<div class="table-wrap"><table class="report"><thead><tr>${columns.map((c) => html`<th>${c}</th>`)}</tr></thead>
|
|
229
234
|
<tbody>${rows.map((r) => html`<tr>${r.map((v) => html`<td>${v === null ? html`<span class="null">null</span>` : v.length > 200 ? `${v.slice(0, 200)}…` : v}</td>`)}</tr>`)}</tbody></table></div>`;
|
|
230
235
|
|
|
231
|
-
const resultHtml = (r: RunResult, committed: boolean) =>
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
const resultHtml = (tx: BuilderTranslate, r: RunResult, committed: boolean) => {
|
|
237
|
+
const counts = r.tables.map((t) => `${t.table}: ${tx('scr.rows', { n: t.rows })}`).join(', ');
|
|
238
|
+
return region(
|
|
239
|
+
committed ? tx('sd.inserted') : tx('sd.preview'),
|
|
234
240
|
html`<div class="alert ${committed ? 'alert-success' : 'alert-note'}" role="status">
|
|
235
|
-
${
|
|
236
|
-
${r.tables.map((t) => html`<h3 class="u-mt1">${t.table} <span class="muted small">${t.rows > PREVIEW_ROWS ?
|
|
241
|
+
${committed ? tx('sd.inserted_counts', { counts, seed: r.seed }) : html`${counts}. <strong>${tx('sd.rolled_back')}</strong> (${tx('sd.seed_is', { seed: r.seed })}).`}</div>
|
|
242
|
+
${r.tables.map((t) => html`<h3 class="u-mt1">${t.table} <span class="muted small">${t.rows > PREVIEW_ROWS ? tx('sd.first_of', { shown: PREVIEW_ROWS, n: t.rows }) : ''}</span></h3>${t.rows ? sampleTable(t.columns, t.sample) : html`<p class="muted">${tx('sd.no_rows')}</p>`}`)}`,
|
|
237
243
|
);
|
|
244
|
+
};
|
|
238
245
|
|
|
239
246
|
// ------------------------------------------------------------ routes
|
|
240
247
|
app.get(sdUrl, async (req: Req, reply) => {
|
|
@@ -266,24 +273,24 @@ export async function sampleDataRoutes(app: FastifyInstance) {
|
|
|
266
273
|
const action = b.action ?? 'preview';
|
|
267
274
|
|
|
268
275
|
if (action === 'save') {
|
|
269
|
-
if (!st.name) return refuse('
|
|
270
|
-
if (st.name.length > 100 || (st.description ?? '').length > 1000) return refuse('
|
|
271
|
-
if (!allowedSchema(def.schema)) return refuse('
|
|
276
|
+
if (!st.name) return refuse(tr(s)('sd.err.name'));
|
|
277
|
+
if (st.name.length > 100 || (st.description ?? '').length > 1000) return refuse(tr(s)('sd.err.length'));
|
|
278
|
+
if (!allowedSchema(def.schema)) return refuse(tr(s)('sd.own_schema'));
|
|
272
279
|
const { tables } = await describeAll(s, def);
|
|
273
|
-
if (!tables.length) return refuse('
|
|
280
|
+
if (!tables.length) return refuse(tr(s)('sd.err.table'));
|
|
274
281
|
const json = JSON.stringify(tables.map(({ spec }) => spec));
|
|
275
282
|
try {
|
|
276
283
|
const id = g
|
|
277
284
|
? (await owner.one('update meta.data_generator set name = $2, description = $3, schema_name = $4, seed = $5, tables = $6, updated_by = $7, updated_at = now() where id = $1 returning id', [g.id, st.name, st.description || null, def.schema, def.seed, json, s.username])).id
|
|
278
285
|
: (await owner.one('insert into meta.data_generator (name, description, schema_name, seed, tables, created_by, updated_by, workspace_id) values ($1, $2, $3, $4, $5, $6, $6, $7) returning id', [st.name, st.description || null, def.schema, def.seed, json, s.username, currentWorkspace(s)])).id;
|
|
279
|
-
flash(s,
|
|
286
|
+
flash(s, tr(s)('sd.saved', { name: st.name }));
|
|
280
287
|
return back(reply, s, `${sdUrl}/${id}`);
|
|
281
288
|
} catch (e) {
|
|
282
289
|
if ((e as { code?: string }).code === '23505') return refuse(`A generator named ${st.name} exists already.`);
|
|
283
290
|
throw e;
|
|
284
291
|
}
|
|
285
292
|
}
|
|
286
|
-
if (!['preview', 'insert', 'sql', 'csv'].includes(action)) return refuse('
|
|
293
|
+
if (!['preview', 'insert', 'sql', 'csv'].includes(action)) return refuse(tr(s)('sd.err.action'));
|
|
287
294
|
|
|
288
295
|
const db = await workshopDb(s);
|
|
289
296
|
const p = await plan(db.pool, def);
|
|
@@ -324,7 +331,7 @@ export async function sampleDataRoutes(app: FastifyInstance) {
|
|
|
324
331
|
await c.query('rollback').catch(() => {});
|
|
325
332
|
const err = e as { message: string; detail?: string; table?: string };
|
|
326
333
|
return refuse(
|
|
327
|
-
`${action === 'insert' ? '
|
|
334
|
+
`${action === 'insert' ? `${tr(s)('sd.nothing_inserted')} ` : ''}${err.table ? `${err.table}: ` : ''}${err.message}${err.detail ? ` (${err.detail})` : ''}`,
|
|
328
335
|
);
|
|
329
336
|
} finally {
|
|
330
337
|
// a connection of its own, closed: triggers may have changed settings
|
|
@@ -338,7 +345,7 @@ export async function sampleDataRoutes(app: FastifyInstance) {
|
|
|
338
345
|
elapsedMs: Math.round(performance.now() - started),
|
|
339
346
|
detail: `${def.schema}, seed ${seed}: ${result.tables.map((t) => `${t.table} ${t.rows}`).join(', ')}`.slice(0, 2000),
|
|
340
347
|
});
|
|
341
|
-
return editor(s, reply, { ...st, result: resultHtml(result, action === 'insert') });
|
|
348
|
+
return editor(s, reply, { ...st, result: resultHtml(tr(s), result, action === 'insert') });
|
|
342
349
|
});
|
|
343
350
|
|
|
344
351
|
app.post(`${sdUrl}/:id/delete`, async (req: Req, reply) => {
|
|
@@ -347,7 +354,7 @@ export async function sampleDataRoutes(app: FastifyInstance) {
|
|
|
347
354
|
const g = await saved(s, req.params.id);
|
|
348
355
|
if (!g) return reply.code(404).send('Not found');
|
|
349
356
|
await owner.query('delete from meta.data_generator where id = $1', [g.id]);
|
|
350
|
-
flash(s,
|
|
357
|
+
flash(s, tr(s)('sd.deleted', { name: g.name }));
|
|
351
358
|
return back(reply, s, sdUrl);
|
|
352
359
|
});
|
|
353
360
|
}
|