pgkiln 0.83.0 → 0.85.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/154_notifications.sql +186 -0
- package/docs/apex-feature-parity.md +3 -2
- package/docs/guide/03-builder.md +16 -5
- package/docs/guide/04-pages-and-regions.md +42 -0
- package/docs/guide/06-processing.md +22 -1
- package/docs/guide/07-dynamic-actions.md +1 -0
- package/docs/guide/09-reference.md +15 -0
- package/docs/guide/11-from-apex.md +1 -0
- package/docs/guide/12-development.md +13 -1
- package/package.json +1 -1
- package/public/app.css +29 -0
- package/public/app.js +78 -0
- package/public/sw.js +5 -1
- package/src/app.ts +11 -0
- package/src/builder/ai-agents.ts +86 -85
- package/src/builder/ai-builder.ts +30 -30
- package/src/builder/ai.ts +93 -92
- package/src/builder/api.ts +47 -47
- package/src/builder/app-groups.ts +33 -32
- package/src/builder/appfromfile.ts +57 -58
- package/src/builder/appsheets.ts +33 -32
- package/src/builder/appwizard.ts +65 -65
- package/src/builder/arrange.ts +23 -22
- package/src/builder/assistant.ts +39 -38
- package/src/builder/automations.ts +32 -31
- package/src/builder/availability.ts +24 -24
- package/src/builder/backups.ts +30 -31
- package/src/builder/blueprints.ts +62 -61
- package/src/builder/code-editor.ts +1 -1
- package/src/builder/components.ts +10 -6
- package/src/builder/create-page.ts +52 -50
- package/src/builder/cross-app.ts +37 -35
- package/src/builder/dataload.ts +2 -2
- package/src/builder/datasets.ts +107 -106
- package/src/builder/dependencies.ts +20 -19
- package/src/builder/designer.ts +36 -36
- package/src/builder/diagnostics.ts +58 -57
- package/src/builder/features.ts +39 -35
- package/src/builder/feedback.ts +23 -20
- package/src/builder/find-workspace.ts +28 -24
- package/src/builder/form-designer.ts +36 -35
- package/src/builder/gallery.ts +25 -29
- package/src/builder/globalization.ts +50 -49
- package/src/builder/i18n/de.ts +3461 -0
- package/src/builder/i18n/en.ts +3461 -0
- package/src/builder/i18n/es.ts +3461 -0
- package/src/builder/i18n/fr.ts +3461 -0
- package/src/builder/i18n/it.ts +3461 -0
- package/src/builder/i18n/ja.ts +3461 -0
- package/src/builder/i18n/ko.ts +3461 -0
- package/src/builder/i18n/nl.ts +3461 -0
- package/src/builder/i18n/pt.ts +3461 -0
- package/src/builder/i18n/zh-cn.ts +3461 -0
- package/src/builder/i18n/zh-tw.ts +3461 -0
- package/src/builder/i18n.ts +17 -3
- package/src/builder/json-sources.ts +63 -62
- package/src/builder/layouts.ts +12 -11
- package/src/builder/locks.ts +29 -28
- package/src/builder/login-page.ts +26 -25
- package/src/builder/mail.ts +148 -147
- package/src/builder/newapp.ts +15 -12
- package/src/builder/notification-settings.ts +27 -0
- package/src/builder/plugins.ts +47 -44
- package/src/builder/process-jobs.ts +11 -10
- package/src/builder/pwa.ts +22 -21
- package/src/builder/remote-databases.ts +55 -56
- package/src/builder/report-settings.ts +1 -1
- package/src/builder/reporter.ts +43 -42
- package/src/builder/rest-catalogs.ts +87 -86
- package/src/builder/rest.ts +9 -8
- package/src/builder/routes.ts +30 -27
- package/src/builder/search.ts +14 -13
- package/src/builder/static-files.ts +31 -32
- package/src/builder/subscriptions.ts +47 -45
- package/src/builder/supporting.ts +23 -18
- package/src/builder/team.ts +179 -179
- package/src/builder/telemetry.ts +59 -62
- package/src/builder/templates.ts +66 -65
- package/src/builder/themeroller.ts +60 -63
- package/src/builder/ui-defaults.ts +21 -20
- package/src/builder/ui.ts +2 -1
- package/src/builder/upgrade.ts +25 -24
- package/src/builder/utilities.ts +48 -47
- package/src/builder/vector-providers.ts +67 -66
- package/src/builder/websources.ts +43 -42
- package/src/builder/wizards.ts +95 -89
- package/src/builder/workflow-migrate.ts +30 -31
- package/src/builder/workflows.ts +40 -39
- package/src/builder/workingcopies.ts +41 -49
- package/src/builder/workspace-backups.ts +21 -20
- package/src/builder/workspace-mail.ts +17 -16
- package/src/builder/workspace-resources.ts +104 -112
- package/src/builder/workspace-transfer.ts +34 -26
- package/src/cli/replace.ts +2 -0
- package/src/i18n/ar.ts +9 -0
- package/src/i18n/cs.ts +9 -0
- package/src/i18n/da.ts +9 -0
- package/src/i18n/de.ts +9 -0
- package/src/i18n/el.ts +9 -0
- package/src/i18n/es.ts +9 -0
- package/src/i18n/fi.ts +9 -0
- package/src/i18n/fr.ts +9 -0
- package/src/i18n/he.ts +9 -0
- package/src/i18n/it.ts +9 -0
- package/src/i18n/ja.ts +9 -0
- package/src/i18n/ko.ts +9 -0
- package/src/i18n/nb.ts +9 -0
- package/src/i18n/pl.ts +9 -0
- package/src/i18n/pt.ts +9 -0
- package/src/i18n/ru.ts +9 -0
- package/src/i18n/sv.ts +9 -0
- package/src/i18n/tr.ts +9 -0
- package/src/i18n/uk.ts +9 -0
- package/src/i18n/zh.ts +9 -0
- package/src/i18n.ts +18 -0
- package/src/metadata.ts +9 -3
- package/src/runtime/engine.ts +2 -1
- package/src/runtime/feedback.ts +1 -1
- package/src/runtime/lists.ts +43 -11
- package/src/runtime/notifications.ts +237 -0
- package/src/runtime/processes.ts +45 -1
- package/src/runtime/regions.ts +11 -15
- package/src/runtime/render.ts +12 -3
- package/src/runtime/routes.ts +12 -4
- package/src/runtime/signin.ts +2 -1
- package/src/session.ts +13 -4
|
@@ -11,6 +11,7 @@ import { buildRequest, call, clearResponseCache, clearTokens, credentialProblems
|
|
|
11
11
|
import type { ComponentSpec } from './components.ts';
|
|
12
12
|
import { back, BASE, csrf, developer, flash, type Req } from './ui.ts';
|
|
13
13
|
import { resolveSource } from '../remote-servers.ts';
|
|
14
|
+
import { scopedTx as tx } from './i18n.ts';
|
|
14
15
|
|
|
15
16
|
// Shared Components → Web credentials and REST data sources: their property
|
|
16
17
|
// specs, the secrets' status (never a secret), "Test" for a source (its
|
|
@@ -139,9 +140,9 @@ export async function designSql(appId: number, r: { source: string | null; rest_
|
|
|
139
140
|
export const needsSecret = (row: { type: string; grant_type?: string | null }) => row.type !== 'oauth2' || (row.grant_type ?? 'client_credentials') === 'client_credentials';
|
|
140
141
|
|
|
141
142
|
const CLEARABLE: Record<string, { column: string; label: string }> = {
|
|
142
|
-
secret: { column: 'secret_enc', label
|
|
143
|
-
password: { column: 'password_enc', label
|
|
144
|
-
refresh: { column: 'refresh_token_enc', label
|
|
143
|
+
secret: { column: 'secret_enc', get label() { return tx('wsrc.the_secret'); } },
|
|
144
|
+
password: { column: 'password_enc', get label() { return tx('wsrc.the_password'); } },
|
|
145
|
+
refresh: { column: 'refresh_token_enc', get label() { return tx('wsrc.the_refresh_token'); } },
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
export function credentialExtras(
|
|
@@ -150,20 +151,20 @@ export function credentialExtras(
|
|
|
150
151
|
s: Session,
|
|
151
152
|
) {
|
|
152
153
|
const clear = (what: string) =>
|
|
153
|
-
html`<form method="post" action="${BASE}/apps/${appId}/web-credentials/${row.id}/clear" class="u-inline">${csrf(s)}<input type="hidden" name="what" value="${what}"><button class="btn" data-confirm="
|
|
154
|
+
html`<form method="post" action="${BASE}/apps/${appId}/web-credentials/${row.id}/clear" class="u-inline">${csrf(s)}<input type="hidden" name="what" value="${what}"><button class="btn" data-confirm="${tx('wsrc.remove_confirm', { what: CLEARABLE[what].label })}">${tx('wsrc.remove', { what: CLEARABLE[what].label })}</button></form>`;
|
|
154
155
|
const oauth = row.type === 'oauth2';
|
|
155
156
|
const grant = row.grant_type ?? 'client_credentials';
|
|
156
|
-
return html`<fieldset class="prop-group u-mt125"><legend
|
|
157
|
+
return html`<fieldset class="prop-group u-mt125"><legend>${tx('wsrc.secrets')}</legend>
|
|
157
158
|
<p>${row.secret_enc
|
|
158
|
-
? html`<b
|
|
159
|
-
: needsSecret(row) ? html`<b
|
|
160
|
-
${oauth && grant === 'password' ? html`<p>${row.password_enc ?
|
|
159
|
+
? tx.rich('wsrc.secret_stored', { stored: html`<b>${tx('wsrc.a_secret_is_stored')}</b>` })
|
|
160
|
+
: needsSecret(row) ? tx.rich('wsrc.secret_missing', { none: html`<b>${tx('wsrc.no_secret_yet')}</b>` }) : tx('wsrc.no_client_secret')}</p>
|
|
161
|
+
${oauth && grant === 'password' ? html`<p>${row.password_enc ? tx.rich('wsrc.password_stored', { stored: html`<b>${tx('wsrc.a_password_is_stored')}</b>` }) : tx.rich('wsrc.password_missing', { none: html`<b>${tx('wsrc.no_password_yet')}</b>` })}</p>` : ''}
|
|
161
162
|
${oauth
|
|
162
163
|
? html`<p>${row.refresh_token_enc
|
|
163
|
-
?
|
|
164
|
-
: grant === 'refresh_token' ?
|
|
164
|
+
? (row.token_refreshed_at ? tx.rich('wsrc.refresh_stored_at', { stored: html`<b>${tx('wsrc.a_refresh_token_is_stored')}</b>`, at: new Date(row.token_refreshed_at).toISOString().slice(0, 16).replace('T', ' ') }) : tx.rich('wsrc.refresh_stored', { stored: html`<b>${tx('wsrc.a_refresh_token_is_stored')}</b>` }))
|
|
165
|
+
: grant === 'refresh_token' ? tx.rich('wsrc.refresh_missing', { none: html`<b>${tx('wsrc.no_refresh_token_yet')}</b>` }) : tx('wsrc.no_refresh')}</p>`
|
|
165
166
|
: ''}
|
|
166
|
-
${secretKeyConfigured() ? '' : html`<div class="alert alert-error" role="alert"
|
|
167
|
+
${secretKeyConfigured() ? '' : html`<div class="alert alert-error" role="alert">${tx.code('wsrc.no_secret_key')}</div>`}
|
|
167
168
|
<div class="buttons">${row.secret_enc ? clear('secret') : ''}${row.password_enc ? clear('password') : ''}${row.refresh_token_enc ? clear('refresh') : ''}</div>
|
|
168
169
|
</fieldset>`;
|
|
169
170
|
}
|
|
@@ -185,8 +186,8 @@ const when = (d: Date | string | null) => (d ? new Date(d).toISOString().slice(0
|
|
|
185
186
|
|
|
186
187
|
/** A source's synchronisation: next run, Synchronise now, the last runs. */
|
|
187
188
|
async function syncExtras(appId: number, row: RestSource & Record<string, any>, s: Session) {
|
|
188
|
-
if (!row.sync_table) return html`<fieldset class="prop-group u-mt125"><legend
|
|
189
|
-
<p class="muted"
|
|
189
|
+
if (!row.sync_table) return html`<fieldset class="prop-group u-mt125"><legend>${tx('wsrc.synchronisation')}</legend>
|
|
190
|
+
<p class="muted">${tx('wsrc.set_a_local_table_above')}</p></fieldset>`;
|
|
190
191
|
let next = 'not scheduled';
|
|
191
192
|
if (row.sync_enabled && row.sync_schedule)
|
|
192
193
|
try {
|
|
@@ -194,26 +195,26 @@ async function syncExtras(appId: number, row: RestSource & Record<string, any>,
|
|
|
194
195
|
} catch (e) {
|
|
195
196
|
next = `never: ${(e as Error).message}`;
|
|
196
197
|
}
|
|
197
|
-
const scheduler = process.env.AUTOMATIONS === 'off' ? html` <b
|
|
198
|
+
const scheduler = process.env.AUTOMATIONS === 'off' ? html` <b>${tx('wsrc.the_scheduler_is_off_on')}</b>` : '';
|
|
198
199
|
const log = await syncLog(row.id);
|
|
199
|
-
return html`<fieldset class="prop-group u-mt125"><legend
|
|
200
|
-
<p
|
|
201
|
-
<p class="muted"
|
|
202
|
-
<form method="post" action="${BASE}/apps/${appId}/rest-sources/${row.id}/sync">${csrf(s)}<button class="btn">${icon('play')}
|
|
200
|
+
return html`<fieldset class="prop-group u-mt125"><legend>${tx('wsrc.synchronisation')}</legend>
|
|
201
|
+
<p>${tx.rich('wsrc.sync_into', { table: html`<code>${row.sync_table}</code>`, mode: `${row.sync_mode}${row.sync_mode === 'merge' && row.sync_delete ? `, ${tx('wsrc.deleting_missing')}` : ''}`, next: html`<b>${next}</b>` })}${scheduler}</p>
|
|
202
|
+
<p class="muted">${tx.rich('wsrc.sync_from_code', { call: html`<code>select meta.request_rest_sync('${row.name}')</code>` })}</p>
|
|
203
|
+
<form method="post" action="${BASE}/apps/${appId}/rest-sources/${row.id}/sync">${csrf(s)}<button class="btn">${icon('play')} ${tx('wsrc.synchronise_now')}</button></form>
|
|
203
204
|
${log.length
|
|
204
|
-
? html`<div class="table-wrap u-mt075"><table class="report report-reflow"><thead><tr><th
|
|
205
|
+
? html`<div class="table-wrap u-mt075"><table class="report report-reflow"><thead><tr><th>${tx('wsrc.requested')}</th><th>${tx('wsrc.took')}</th><th>${tx('wsrc.by')}</th><th>${tx('wsrc.status')}</th><th class="num">${tx('wsrc.rows')}</th><th class="num">${tx('wsrc.inserted')}</th><th class="num">${tx('wsrc.updated')}</th><th class="num">${tx('wsrc.deleted')}</th><th>${tx('wsrc.message')}</th></tr></thead><tbody>
|
|
205
206
|
${log.map((l) => html`<tr>
|
|
206
|
-
<td data-label="
|
|
207
|
-
<td data-label="
|
|
208
|
-
<td data-label="
|
|
209
|
-
<td data-label="
|
|
210
|
-
<td class="num" data-label="
|
|
211
|
-
<td class="num" data-label="
|
|
212
|
-
<td class="num" data-label="
|
|
213
|
-
<td class="num" data-label="
|
|
214
|
-
<td data-label="
|
|
207
|
+
<td data-label="${tx('wsrc.requested')}">${when(l.requested_at)}</td>
|
|
208
|
+
<td data-label="${tx('wsrc.took')}">${l.finished_at && l.started_at ? `${((new Date(l.finished_at).getTime() - new Date(l.started_at).getTime()) / 1000).toFixed(1)} s` : '…'}</td>
|
|
209
|
+
<td data-label="${tx('wsrc.by')}">${l.trigger}${l.requested_by ? html` <span class="muted">(${l.requested_by})</span>` : ''}</td>
|
|
210
|
+
<td data-label="${tx('wsrc.status')}"><span class="tag${SYNC_TAG[l.status] ?? ''}">${l.status}</span></td>
|
|
211
|
+
<td class="num" data-label="${tx('wsrc.rows')}">${l.rows_fetched ?? ''}</td>
|
|
212
|
+
<td class="num" data-label="${tx('wsrc.inserted')}">${l.inserted ?? ''}</td>
|
|
213
|
+
<td class="num" data-label="${tx('wsrc.updated')}">${l.updated ?? ''}</td>
|
|
214
|
+
<td class="num" data-label="${tx('wsrc.deleted')}">${l.deleted ?? ''}</td>
|
|
215
|
+
<td data-label="${tx('wsrc.message')}">${l.message ?? ''}</td></tr>`)}
|
|
215
216
|
</tbody></table></div>`
|
|
216
|
-
: html`<p class="muted"
|
|
217
|
+
: html`<p class="muted">${tx('wsrc.no_runs_yet')}</p>`}
|
|
217
218
|
</fieldset>`;
|
|
218
219
|
}
|
|
219
220
|
|
|
@@ -231,13 +232,13 @@ function testExtras(appId: number, row: RestSource, s: Session) {
|
|
|
231
232
|
}
|
|
232
233
|
delete s.state[RESULT];
|
|
233
234
|
const params = Array.isArray(row.params) ? row.params : [];
|
|
234
|
-
return html`<fieldset class="prop-group u-mt125"><legend
|
|
235
|
+
return html`<fieldset class="prop-group u-mt125"><legend>${tx('wsrc.test')}</legend>
|
|
235
236
|
<form method="post" action="${BASE}/apps/${appId}/rest-sources/${row.id}/test">${csrf(s)}
|
|
236
237
|
${params.length
|
|
237
238
|
? html`<div class="form-grid">${params.map((p) => html`<div class="field"><label class="label" for="rt_${p.name}">${p.name} <span class="muted">(${p.in})</span></label>
|
|
238
239
|
<input id="rt_${p.name}" name="p_${p.name}" value="${p.default && !p.default.includes('&') ? p.default : ''}"></div>`)}</div>`
|
|
239
240
|
: ''}
|
|
240
|
-
<div class="buttons"><button class="btn"
|
|
241
|
+
<div class="buttons"><button class="btn">${tx('wsrc.test')}</button></div>
|
|
241
242
|
</form>
|
|
242
243
|
${result
|
|
243
244
|
? html`<div class="alert ${result.ok ? 'alert-success' : 'alert-error'}" role="status">${result.message}</div>
|
|
@@ -247,11 +248,11 @@ function testExtras(appId: number, row: RestSource, s: Session) {
|
|
|
247
248
|
: ''}
|
|
248
249
|
${result.suggested
|
|
249
250
|
? html`<form method="post" action="${BASE}/apps/${appId}/rest-sources/${row.id}/columns" class="u-mt1">${csrf(s)}
|
|
250
|
-
<label class="label" for="rt_cols"
|
|
251
|
+
<label class="label" for="rt_cols">${tx('wsrc.suggested_columns')}</label>
|
|
251
252
|
<textarea id="rt_cols" name="columns" class="code" rows="4" spellcheck="false">${result.suggested}</textarea>
|
|
252
|
-
<div class="buttons"><button class="btn"
|
|
253
|
+
<div class="buttons"><button class="btn">${tx('wsrc.use_these_columns')}</button></div></form>`
|
|
253
254
|
: ''}
|
|
254
|
-
${result.excerpt ? html`<details class="u-mt1"><summary
|
|
255
|
+
${result.excerpt ? html`<details class="u-mt1"><summary>${tx('wsrc.response')}</summary><pre class="source">${result.excerpt}</pre></details>` : ''}`
|
|
255
256
|
: ''}
|
|
256
257
|
</fieldset>`;
|
|
257
258
|
}
|
|
@@ -266,7 +267,7 @@ export async function webSourceRoutes(app: FastifyInstance) {
|
|
|
266
267
|
// the column comes from the fixed list above, never from the request
|
|
267
268
|
const r = await owner.query(`update meta.web_credential set ${what.column} = null${what.column === 'refresh_token_enc' ? ', token_refreshed_at = null' : ''} where id = $1 and app_id = $2`, [req.params.cid, req.params.id]);
|
|
268
269
|
clearTokens(Number(req.params.cid));
|
|
269
|
-
flash(s, r.rowCount ? `${what.label[0].toUpperCase()}${what.label.slice(1)} was removed.` : '
|
|
270
|
+
flash(s, r.rowCount ? `${what.label[0].toUpperCase()}${what.label.slice(1)} was removed.` : tx('wsrc.not_found'), r.rowCount ? 'ok' : 'error');
|
|
270
271
|
return back(reply, s, `${BASE}/apps/${req.params.id}/shared?c=web_credential-${req.params.cid}`);
|
|
271
272
|
});
|
|
272
273
|
|
|
@@ -288,8 +289,8 @@ export async function webSourceRoutes(app: FastifyInstance) {
|
|
|
288
289
|
const res = await call({ ...r, appId: src.app_id, what: `REST data source ${src.name} (builder test)`, credential, timeoutMs: src.timeout_s * 1000 });
|
|
289
290
|
const text = res.body.toString('utf8');
|
|
290
291
|
result.excerpt = text.length > 3000 ? `${text.slice(0, 3000)}…` : text;
|
|
291
|
-
if (res.status < 200 || res.status > 299) throw new Error(
|
|
292
|
-
const body = parseJson(res, '
|
|
292
|
+
if (res.status < 200 || res.status > 299) throw new Error(tx('wsrc.answered', { status: res.status }));
|
|
293
|
+
const body = parseJson(res, tx('wsrc.the_source'));
|
|
293
294
|
const { columns, rows, truncated } = toRows(body, { ...src, max_rows: Math.min(src.max_rows, 1000) });
|
|
294
295
|
const selected = select(body, src.row_selector);
|
|
295
296
|
const items = selected.length === 1 && Array.isArray(selected[0]) ? selected[0] : selected;
|
|
@@ -317,8 +318,8 @@ export async function webSourceRoutes(app: FastifyInstance) {
|
|
|
317
318
|
if (!src) return reply.code(404).send('Not found');
|
|
318
319
|
const r = await runSync(src.id, 'manual', { by: s.username ?? undefined });
|
|
319
320
|
if (r.status === 'ok')
|
|
320
|
-
flash(s,
|
|
321
|
-
else flash(s, r.message ?? '
|
|
321
|
+
flash(s, `${tx('wsrc.synced', { rows: r.rows, inserted: r.inserted, updated: r.updated, deleted: r.deleted })}${r.message ? ` ${r.message}` : ''}`);
|
|
322
|
+
else flash(s, r.message ?? tx('wsrc.the_synchronisation_failed'), 'error');
|
|
322
323
|
return back(reply, s, `${BASE}/apps/${req.params.id}/shared?c=rest_source-${src.id}`);
|
|
323
324
|
});
|
|
324
325
|
|
|
@@ -330,10 +331,10 @@ export async function webSourceRoutes(app: FastifyInstance) {
|
|
|
330
331
|
const problems = sourceProblems({ url: 'https://x', params: [], columns: cols });
|
|
331
332
|
if (problems.length) throw new Error(problems.join(' '));
|
|
332
333
|
const r = await owner.query('update meta.rest_source set columns = $3 where id = $1 and app_id = $2', [req.params.sid, req.params.id, JSON.stringify(cols)]);
|
|
333
|
-
if (!r.rowCount) throw new Error('
|
|
334
|
-
flash(s, '
|
|
334
|
+
if (!r.rowCount) throw new Error(tx('wsrc.not_found'));
|
|
335
|
+
flash(s, tx('wsrc.columns_saved'));
|
|
335
336
|
} catch (e) {
|
|
336
|
-
flash(s, e instanceof SyntaxError ? '
|
|
337
|
+
flash(s, e instanceof SyntaxError ? tx('wsrc.the_columns_are_not_valid') : (e as Error).message, 'error');
|
|
337
338
|
}
|
|
338
339
|
return back(reply, s, `${BASE}/apps/${req.params.id}/shared?c=rest_source-${req.params.sid}`);
|
|
339
340
|
});
|