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
|
@@ -4,6 +4,7 @@ import { APP_MFA, EMAIL_CODE_MINUTES, EMAIL_CODES_PER_HOUR, type AppMfa } from '
|
|
|
4
4
|
import { html, raw } from '../html.ts';
|
|
5
5
|
import { secretKeyConfigured } from '../secrets.ts';
|
|
6
6
|
import { back, BASE, developer, flash, type Req } from './ui.ts';
|
|
7
|
+
import { scopedTx as tx } from './i18n.ts';
|
|
7
8
|
|
|
8
9
|
// Application settings → Sign-in methods (migration 087): the application's
|
|
9
10
|
// own login page (APEX: the login page, usually 9999) and "Forgot password?".
|
|
@@ -33,47 +34,47 @@ export async function loginSettingsFields(a: AppRow) {
|
|
|
33
34
|
from meta.app_mail_service ams join meta.mail_service s on s.id = ams.service_id where ams.app_id = $1`, [a.id],
|
|
34
35
|
);
|
|
35
36
|
const mailOk = !!(mail?.enabled && mail.from_address?.trim());
|
|
36
|
-
const noMail = html`<strong
|
|
37
|
+
const noMail = html`<strong>${tx('lgp.not_offered_yet')}</strong> the application needs an enabled mail service with a From address (Workspace utilities → Mail services).`;
|
|
37
38
|
const opt = (v: string, label: string, cur: string) => html`<option value="${v}"${v === cur ? raw(' selected') : ''}>${label}</option>`;
|
|
38
39
|
const current = a.login_page ? String(a.login_page) : '';
|
|
39
40
|
const page = html`<div class="form-grid">
|
|
40
|
-
<div class="field"><label class="label" for="f_login_page"
|
|
41
|
-
<select id="f_login_page" name="login_page">${opt('', '
|
|
42
|
-
opt(String(p.page_no), `${p.page_no} ${p.name}
|
|
43
|
-
<small class="help"
|
|
44
|
-
<button class="link-button" formaction="${BASE}/apps/${a.id}/login-page"
|
|
41
|
+
<div class="field"><label class="label" for="f_login_page">${tx('lgp.login_page')}</label>
|
|
42
|
+
<select id="f_login_page" name="login_page">${opt('', tx('lgp.the_built_in_sign_in'), current)}${pages.map((p) =>
|
|
43
|
+
opt(String(p.page_no), p.requires_auth ? tx('lgp.requires_signin', { page: `${p.page_no} ${p.name}` }) : `${p.page_no} ${p.name}`, current))}</select>
|
|
44
|
+
<small class="help">${tx.rich('lgp.page_help', { login: html`<em>${tx('lgp.login')}</em>`, minimal: html`<em>${tx('lgp.minimal')}</em>` })}
|
|
45
|
+
<button class="link-button" formaction="${BASE}/apps/${a.id}/login-page">${tx('lgp.create_a_login_page')}</button></small></div>
|
|
45
46
|
</div>
|
|
46
|
-
<div class="field"><label class="check"><input type="checkbox" name="password_reset" value="true"${a.password_reset ? raw(' checked') : ''}>
|
|
47
|
-
<small class="help"
|
|
48
|
-
${mailOk ?
|
|
47
|
+
<div class="field"><label class="check"><input type="checkbox" name="password_reset" value="true"${a.password_reset ? raw(' checked') : ''}> ${tx('lgp.offer_forgot_password')}</label>
|
|
48
|
+
<small class="help">${tx('lgp.reset_help')}
|
|
49
|
+
${mailOk ? tx('lgp.sent_through', { service: mail!.service, from: mail!.from_address ?? '' }) : noMail}</small></div>`;
|
|
49
50
|
const twoFactor = html`<div class="form-grid">
|
|
50
|
-
<div class="field"><label class="label" for="f_mfa"
|
|
51
|
-
<select id="f_mfa" name="mfa" aria-describedby="f_mfa_help">${(APP_MFA as string[]).map((m) => opt(m, MFA_LABELS[m as AppMfa], a.mfa))}</select>
|
|
52
|
-
<small class="help" id="f_mfa_help"
|
|
53
|
-
${secretKeyConfigured() ? '' : html`<strong
|
|
54
|
-
<div class="field"><label class="label" for="f_mfa_roles"
|
|
55
|
-
<input id="f_mfa_roles" name="mfa_roles" value="${(a.mfa_roles ?? []).join(', ')}" placeholder="
|
|
56
|
-
<small class="help" id="f_mfa_roles_help"
|
|
51
|
+
<div class="field"><label class="label" for="f_mfa">${tx('lgp.second_step')}</label>
|
|
52
|
+
<select id="f_mfa" name="mfa" aria-describedby="f_mfa_help">${(APP_MFA as string[]).map((m) => opt(m, MFA_LABELS()[m as AppMfa], a.mfa))}</select>
|
|
53
|
+
<small class="help" id="f_mfa_help">${tx.rich('lgp.mfa_help', { account: html`<em>${tx('lgp.my_account')}</em>` })}
|
|
54
|
+
${secretKeyConfigured() ? '' : tx.rich('lgp.needs_key', { na: html`<strong>${tx('lgp.not_available_yet')}</strong>`, key: html`<code>PGKILN_SECRET_KEY</code>` })}</small></div>
|
|
55
|
+
<div class="field"><label class="label" for="f_mfa_roles">${tx('lgp.required_for_roles')}</label>
|
|
56
|
+
<input id="f_mfa_roles" name="mfa_roles" value="${(a.mfa_roles ?? []).join(', ')}" placeholder="${tx('lgp.admin_manager')}" aria-describedby="f_mfa_roles_help">
|
|
57
|
+
<small class="help" id="f_mfa_roles_help">${tx.rich('lgp.roles_help', { some: html`<em>${tx('lgp.required_for_some_roles')}</em>` })}</small></div>
|
|
57
58
|
</div>
|
|
58
|
-
<div class="field"><label class="check"><input type="checkbox" name="mfa_email" value="true"${a.mfa_email ? raw(' checked') : ''}>
|
|
59
|
-
<small class="help"
|
|
59
|
+
<div class="field"><label class="check"><input type="checkbox" name="mfa_email" value="true"${a.mfa_email ? raw(' checked') : ''}> ${tx('lgp.offer_a_code_by_e')}</label>
|
|
60
|
+
<small class="help">${tx('lgp.email_help', { minutes: EMAIL_CODE_MINUTES, per_hour: EMAIL_CODES_PER_HOUR })}
|
|
60
61
|
${mailOk ? '' : noMail}</small></div>
|
|
61
|
-
<p class="muted small"
|
|
62
|
+
<p class="muted small">${tx.rich('lgp.passkeys_note', { mfa: html`<em>${tx('lgp.multi_factor_sign_in')}</em>`, within: html`<em>${tx('lgp.second_step_within_minutes')}</em>` })}</p>`;
|
|
62
63
|
return { page, twoFactor };
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
const MFA_LABELS: Record<AppMfa, string>
|
|
66
|
+
const MFA_LABELS = (): Record<AppMfa, string> => ({ off: tx('lgp.off'), optional: tx('lgp.optional_users_may_add_an'), required: tx('lgp.required'), roles: tx('lgp.required_for_some_roles') });
|
|
66
67
|
|
|
67
68
|
/** Save them (with the rest of the settings form). */
|
|
68
69
|
export async function saveLoginSettings(appId: string, b: Record<string, string | undefined>) {
|
|
69
70
|
const page = b.login_page?.trim() ? Number(b.login_page) : null;
|
|
70
|
-
if (page !== null && !(await owner.one('select 1 from meta.page where app_id = $1 and page_no = $2', [appId, page]))) throw new Error(
|
|
71
|
+
if (page !== null && !(await owner.one('select 1 from meta.page where app_id = $1 and page_no = $2', [appId, page]))) throw new Error(tx('lgp.no_page', { n: String(b.login_page) }));
|
|
71
72
|
const mfa = (APP_MFA as string[]).includes(b.mfa ?? '') ? (b.mfa as AppMfa) : 'off';
|
|
72
73
|
const roles = [...new Set((b.mfa_roles ?? '').split(',').map((r) => r.trim().toLowerCase()).filter(Boolean))];
|
|
73
|
-
if (roles.some((r) => r.length > 100)) throw new Error('
|
|
74
|
-
if (mfa === 'roles' && !roles.length) throw new Error('
|
|
74
|
+
if (roles.some((r) => r.length > 100)) throw new Error(tx('lgp.two_factor_sign_in_a'));
|
|
75
|
+
if (mfa === 'roles' && !roles.length) throw new Error(tx('lgp.two_factor_sign_in_name'));
|
|
75
76
|
// without the instance key, users could not add an app: requiring one would lock them out
|
|
76
|
-
if ((mfa === 'required' || mfa === 'roles') && !secretKeyConfigured()) throw new Error('
|
|
77
|
+
if ((mfa === 'required' || mfa === 'roles') && !secretKeyConfigured()) throw new Error(tx('lgp.two_factor_sign_in_the'));
|
|
77
78
|
await owner.query('update meta.app set login_page = $2, password_reset = $3, mfa = $4, mfa_roles = $5, mfa_email = $6 where id = $1',
|
|
78
79
|
[appId, page, b.password_reset === 'true', mfa, roles, b.mfa_email === 'true']);
|
|
79
80
|
}
|
|
@@ -102,7 +103,7 @@ export async function loginPageRoutes(app: FastifyInstance) {
|
|
|
102
103
|
if (!a) return reply.code(404).send('Not found');
|
|
103
104
|
try {
|
|
104
105
|
const { page, no } = await owner.tx((c) => createLoginPage(c, a));
|
|
105
|
-
flash(s,
|
|
106
|
+
flash(s, tx('lgp.created', { n: no }));
|
|
106
107
|
return back(reply, s, `${BASE}/pages/${page}`);
|
|
107
108
|
} catch (e) {
|
|
108
109
|
flash(s, (e as Error).message, 'error');
|