pgkiln 0.84.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/docs/apex-feature-parity.md +2 -2
- package/docs/guide/03-builder.md +14 -5
- package/docs/guide/12-development.md +6 -1
- package/package.json +1 -1
- package/src/app.ts +9 -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/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 +3453 -0
- package/src/builder/i18n/en.ts +3453 -0
- package/src/builder/i18n/es.ts +3453 -0
- package/src/builder/i18n/fr.ts +3453 -0
- package/src/builder/i18n/it.ts +3453 -0
- package/src/builder/i18n/ja.ts +3453 -0
- package/src/builder/i18n/ko.ts +3453 -0
- package/src/builder/i18n/nl.ts +3453 -0
- package/src/builder/i18n/pt.ts +3453 -0
- package/src/builder/i18n/zh-cn.ts +3453 -0
- package/src/builder/i18n/zh-tw.ts +3453 -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/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 +27 -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
|
@@ -7,9 +7,10 @@ import { html } from '../html.ts';
|
|
|
7
7
|
import { icon } from '../icons.ts';
|
|
8
8
|
import { readMultipart } from '../runtime/files.ts';
|
|
9
9
|
import { clientIp, logActivity, type Session } from '../session.ts';
|
|
10
|
-
import { describeWorkspaceFile, exportWorkspace, importWorkspace,
|
|
10
|
+
import { describeWorkspaceFile, exportWorkspace, importWorkspace, PARTS, type Part } from '../workspace-export.ts';
|
|
11
11
|
import { back, BASE, csrf, developer, flash, region, send, shell, type Body, type Req } from './ui.ts';
|
|
12
12
|
import { currentWorkspace, workspaceAdmin } from './workspaces.ts';
|
|
13
|
+
import { scopedTx as tx, type BuilderKey } from './i18n.ts';
|
|
13
14
|
|
|
14
15
|
// Workspace utilities → Export / import (APEX: Workspace Utilities → Export):
|
|
15
16
|
// the current workspace's components as one file, and such a file imported
|
|
@@ -19,7 +20,14 @@ import { currentWorkspace, workspaceAdmin } from './workspaces.ts';
|
|
|
19
20
|
|
|
20
21
|
const RESULT = '__WS_IMPORT_RESULT';
|
|
21
22
|
const page = `${BASE}/workspace-export`;
|
|
22
|
-
const crumbs: [string, string?][]
|
|
23
|
+
const crumbs = (): [string, string?][] => [[tx('rail.app_builder'), BASE], [tx('wu.title'), `${BASE}/utilities`], [tx('wu.export')]];
|
|
24
|
+
/** The kinds of component in the developer's language (PART_LABELS in English: src/workspace-export.ts). */
|
|
25
|
+
const PART_KEYS: Record<Part, BuilderKey> = {
|
|
26
|
+
settings: 'wtr.part.settings', developers: 'wtr.part.developers', app_groups: 'wtr.part.app_groups', extension_links: 'wtr.part.extension_links',
|
|
27
|
+
rest_catalogs: 'wtr.part.rest_catalogs', sql_scripts: 'wtr.part.sql_scripts', blueprints: 'wtr.part.blueprints', data_generators: 'wtr.part.data_generators',
|
|
28
|
+
datasets: 'wtr.part.datasets', team: 'wtr.part.team', workspace_files: 'wtr.part.workspace_files', remote_servers: 'wtr.part.remote_servers',
|
|
29
|
+
web_credentials: 'wtr.part.web_credentials',
|
|
30
|
+
};
|
|
23
31
|
const MAX_MB = 20;
|
|
24
32
|
|
|
25
33
|
let version: string | null | undefined;
|
|
@@ -41,16 +49,16 @@ const chosen = (b: Body | undefined): Part[] => {
|
|
|
41
49
|
|
|
42
50
|
async function adminOr403(s: Session, reply: FastifyReply) {
|
|
43
51
|
if (s.workspace && (await workspaceAdmin(s.username, s.workspace.id))) return true;
|
|
44
|
-
await send(reply.code(403), s, shell(s, '
|
|
45
|
-
<div class="alert alert-error" role="alert"
|
|
52
|
+
await send(reply.code(403), s, shell(s, tx('wu.export'), crumbs(), html`<h1 class="u-mb1">${tx('wu.export')}</h1>
|
|
53
|
+
<div class="alert alert-error" role="alert">${tx('wtr.admins_only')}</div>`));
|
|
46
54
|
return false;
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
const partBoxes = (counts?: Partial<Record<Part, number>>) =>
|
|
50
|
-
html`<fieldset class="field" data-wide><legend class="label"
|
|
58
|
+
html`<fieldset class="field" data-wide><legend class="label">${tx('wtr.components')}</legend>${PARTS.map((p) =>
|
|
51
59
|
counts && counts[p] === undefined
|
|
52
60
|
? ''
|
|
53
|
-
: html`<label class="check"><input type="checkbox" name="part" value="${p}" checked> ${
|
|
61
|
+
: html`<label class="check"><input type="checkbox" name="part" value="${p}" checked> ${tx(PART_KEYS[p])}${counts ? html` <span class="muted">(${p === 'settings' ? tx('wtr.in_file') : p === 'team' ? tx('wtr.issues', { n: counts[p] }) : counts[p]})</span>` : ''}</label>`)}</fieldset>`;
|
|
54
62
|
|
|
55
63
|
export async function workspaceTransferRoutes(app: FastifyInstance) {
|
|
56
64
|
app.get(page, async (req: Req, reply) => {
|
|
@@ -59,22 +67,22 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
|
|
|
59
67
|
const result = s.state[RESULT] ? (JSON.parse(s.state[RESULT]!) as string[]) : null;
|
|
60
68
|
delete s.state[RESULT];
|
|
61
69
|
const name = s.workspace!.name;
|
|
62
|
-
const main = html`<div class="title-row"><h1
|
|
63
|
-
<p class="muted u-mt0"
|
|
64
|
-
${result ? html`<div class="alert alert-success" role="status"><b
|
|
65
|
-
${region('
|
|
70
|
+
const main = html`<div class="title-row"><h1>${tx('wu.export')}</h1></div>
|
|
71
|
+
<p class="muted u-mt0">${tx('wtr.intro', { name })}</p>
|
|
72
|
+
${result ? html`<div class="alert alert-success" role="status"><b>${tx('wtr.imported_into', { name })}</b><ul>${result.map((l) => html`<li>${l}</li>`)}</ul></div>` : ''}
|
|
73
|
+
${region(tx('wtr.export'), html`<form method="post" action="${page}">${csrf(s)}
|
|
66
74
|
${partBoxes()}
|
|
67
|
-
<p class="muted"
|
|
68
|
-
<div class="buttons"><button class="btn btn-hot">${icon('download')}
|
|
69
|
-
${region('
|
|
70
|
-
<p class="muted u-mt0"
|
|
75
|
+
<p class="muted">${tx('wtr.not_in_file')}</p>
|
|
76
|
+
<div class="buttons"><button class="btn btn-hot">${icon('download')} ${tx('wtr.export')}</button></div></form>`)}
|
|
77
|
+
${region(tx('wtr.import'), html`<form method="post" action="${BASE}/workspace-import" enctype="multipart/form-data">${csrf(s)}
|
|
78
|
+
<p class="muted u-mt0">${tx.rich('wtr.import_intro', { name: html`<b>${name}</b>` })}</p>
|
|
71
79
|
<div class="form-grid">
|
|
72
|
-
<div class="field" data-wide><label class="label" for="f_file"
|
|
80
|
+
<div class="field" data-wide><label class="label" for="f_file">${tx('wtr.file')}</label><input id="f_file" type="file" name="file" accept=".json,application/json" required></div>
|
|
73
81
|
${partBoxes()}
|
|
74
|
-
<div class="field" data-wide><label class="check"><input type="checkbox" name="replace" value="true">
|
|
82
|
+
<div class="field" data-wide><label class="check"><input type="checkbox" name="replace" value="true"> ${tx('wtr.replace')}</label></div>
|
|
75
83
|
</div>
|
|
76
|
-
<div class="buttons"><button class="btn">${icon('upload')}
|
|
77
|
-
return send(reply, s, shell(s, '
|
|
84
|
+
<div class="buttons"><button class="btn">${icon('upload')} ${tx('wtr.import')}</button></div></form>`)}`;
|
|
85
|
+
return send(reply, s, shell(s, tx('wu.export'), crumbs(), main));
|
|
78
86
|
});
|
|
79
87
|
|
|
80
88
|
app.post(page, async (req: Req, reply) => {
|
|
@@ -82,7 +90,7 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
|
|
|
82
90
|
if (!s || !(await adminOr403(s, reply))) return;
|
|
83
91
|
const parts = chosen(req.body);
|
|
84
92
|
if (!parts.length) {
|
|
85
|
-
flash(s, '
|
|
93
|
+
flash(s, tx('wtr.choose_part'), 'error');
|
|
86
94
|
return back(reply, s, page);
|
|
87
95
|
}
|
|
88
96
|
const doc = await exportWorkspace(currentWorkspace(s), parts, { version: pgkilnVersion() ?? undefined, by: s.username });
|
|
@@ -104,23 +112,23 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
|
|
|
104
112
|
const s = await developer(req, reply);
|
|
105
113
|
if (!s || !(await adminOr403(s, reply))) return;
|
|
106
114
|
try {
|
|
107
|
-
if (tooBig) throw new Error(
|
|
108
|
-
if (!text.trim()) throw new Error('
|
|
115
|
+
if (tooBig) throw new Error(tx('wtr.too_big', { n: MAX_MB }));
|
|
116
|
+
if (!text.trim()) throw new Error(tx('wtr.choose_file'));
|
|
109
117
|
let doc: unknown;
|
|
110
118
|
try {
|
|
111
119
|
doc = JSON.parse(text);
|
|
112
120
|
} catch {
|
|
113
|
-
throw new Error('
|
|
121
|
+
throw new Error(tx('wtr.not_json'));
|
|
114
122
|
}
|
|
115
123
|
describeWorkspaceFile(doc);
|
|
116
124
|
const parts = chosen(req.body);
|
|
117
|
-
if (!parts.length) throw new Error('
|
|
125
|
+
if (!parts.length) throw new Error(tx('wtr.choose_part'));
|
|
118
126
|
const ws = currentWorkspace(s);
|
|
119
127
|
const result = await owner.tx((c) => importWorkspace(c, ws, doc, parts, { replace: req.body?.replace === 'true', by: s.username }));
|
|
120
128
|
await logActivity({ appId: null, username: s.username, event: 'workspace', ip: clientIp(req), detail: `imported into workspace ${ws}: ${result.added} added, ${result.replaced} replaced, ${result.skipped} kept` });
|
|
121
129
|
s.state[RESULT] = JSON.stringify(result.lines);
|
|
122
130
|
} catch (e) {
|
|
123
|
-
flash(s,
|
|
131
|
+
flash(s, tx('wtr.failed', { reason: friendly(e) }), 'error');
|
|
124
132
|
}
|
|
125
133
|
return back(reply, s, page);
|
|
126
134
|
});
|
|
@@ -129,8 +137,8 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
|
|
|
129
137
|
/** A database refusal in words (the transaction rolled back). */
|
|
130
138
|
function friendly(e: unknown) {
|
|
131
139
|
const err = e as { message: string; constraint?: string; table?: string; code?: string };
|
|
132
|
-
if (err.code === '23514' || err.code === '23502' || err.code === '22P02') return
|
|
133
|
-
if (err.code === '23505') return
|
|
140
|
+
if (err.code === '23514' || err.code === '23502' || err.code === '22P02') return tx('wtr.refused_value', { detail: `${err.table ?? ''}${err.constraint ? `: ${err.constraint}` : ''}` });
|
|
141
|
+
if (err.code === '23505') return tx('wtr.duplicate', { detail: err.constraint ?? err.table ?? '' });
|
|
134
142
|
return err.message;
|
|
135
143
|
}
|
|
136
144
|
|