@podosoft/podokit 1.5.0 → 1.6.1
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/README.md +2 -1
- package/dist/app-proxy-timeouts.d.ts +27 -0
- package/dist/app-proxy-timeouts.js +180 -0
- package/dist/assemble.d.ts +5 -2
- package/dist/assemble.js +14 -4
- package/dist/deploy-checks.d.ts +44 -0
- package/dist/deploy-checks.js +267 -0
- package/dist/deploy-compose-profile.d.ts +6 -2
- package/dist/deploy-compose-profile.js +15 -6
- package/dist/deploy-compose-render.d.ts +12 -4
- package/dist/deploy-compose-render.js +47 -18
- package/dist/deploy-compose.d.ts +2 -5
- package/dist/deploy-compose.js +7 -2
- package/dist/deploy-profile.d.ts +6 -2
- package/dist/deploy-profile.js +11 -7
- package/dist/deploy-render.d.ts +4 -1
- package/dist/deploy-render.js +27 -21
- package/dist/deploy-schema.d.ts +60 -2
- package/dist/deploy-schema.js +141 -14
- package/dist/deploy.d.ts +2 -5
- package/dist/deploy.js +5 -2
- package/dist/dev.d.ts +21 -0
- package/dist/dev.js +78 -3
- package/dist/eject.js +2 -0
- package/dist/index.js +85 -5
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +5 -2
- package/dist/lockfile.d.ts +24 -0
- package/dist/lockfile.js +74 -4
- package/dist/provider.js +10 -5
- package/dist/remove.js +1 -1
- package/dist/template-files.d.ts +42 -0
- package/dist/template-files.js +102 -0
- package/dist/templates/fullstack/AGENTS.md +30 -4
- package/dist/templates/fullstack/README.md +8 -7
- package/dist/templates/fullstack/apps/api/package.json +4 -4
- package/dist/templates/fullstack/apps/api/scripts/build.extensions.mjs +6 -0
- package/dist/templates/fullstack/apps/api/scripts/build.mjs +65 -16
- package/dist/templates/fullstack/apps/api/scripts/run-migrations.mjs +7 -3
- package/dist/templates/fullstack/apps/api/scripts/verify-api-contract.ts +7 -2
- package/dist/templates/fullstack/apps/api/src/app.spec.ts +22 -0
- package/dist/templates/fullstack/apps/api/src/app.ts +41 -25
- package/dist/templates/fullstack/apps/api/src/build.spec.ts +121 -0
- package/dist/templates/fullstack/apps/api/src/config/env.extensions.ts +20 -0
- package/dist/templates/fullstack/apps/api/src/config/env.validation.spec.ts +76 -0
- package/dist/templates/fullstack/apps/api/src/config/env.validation.ts +18 -3
- package/dist/templates/fullstack/apps/api/src/config/provider-selection.spec.ts +53 -0
- package/dist/templates/fullstack/apps/api/src/config/provider-selection.ts +79 -0
- package/dist/templates/fullstack/apps/api/src/config/providers.ts +7 -3
- package/dist/templates/fullstack/apps/api/src/core/api-contract.extensions.ts +6 -0
- package/dist/templates/fullstack/apps/api/src/core/api-contract.spec.ts +72 -4
- package/dist/templates/fullstack/apps/api/src/core/api-contract.ts +78 -6
- package/dist/templates/fullstack/apps/api/src/core/services.ts +13 -0
- package/dist/templates/fullstack/apps/api/src/database/data-source.spec.ts +27 -0
- package/dist/templates/fullstack/apps/api/src/database/data-source.ts +7 -10
- package/dist/templates/fullstack/apps/api/src/database/database.spec.ts +12 -0
- package/dist/templates/fullstack/apps/api/src/database/database.ts +2 -3
- package/dist/templates/fullstack/apps/api/src/database/entities.ts +12 -0
- package/dist/templates/fullstack/apps/api/src/database/migrate.ts +12 -2
- package/dist/templates/fullstack/apps/api/src/database/migration-files.spec.ts +116 -0
- package/dist/templates/fullstack/apps/api/src/database/migration-files.ts +134 -0
- package/dist/templates/fullstack/apps/api/src/database/migration-readiness.spec.ts +36 -0
- package/dist/templates/fullstack/apps/api/src/database/migration-readiness.ts +25 -0
- package/dist/templates/fullstack/apps/api/src/database/run-migrations.spec.ts +12 -1
- package/dist/templates/fullstack/apps/api/src/database/sqlite-migrator.ts +2 -36
- package/dist/templates/fullstack/apps/api/src/main.ts +2 -2
- package/dist/templates/fullstack/apps/api/tsconfig.json +2 -0
- package/dist/templates/fullstack/apps/web/package.json +1 -1
- package/dist/templates/fullstack/apps/web/src/app.css +3 -0
- package/dist/templates/fullstack/apps/web/src/app.extensions.css +11 -0
- package/dist/templates/fullstack/apps/web/src/lib/components/site-runtime.svelte +3 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/api.test.ts +19 -0
- package/dist/templates/fullstack/apps/web/src/lib/server/api.ts +4 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.test.ts +79 -0
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.ts +55 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/proxy-timeouts.ts +2 -1
- package/dist/templates/fullstack/apps/web/tsconfig.json +1 -1
- package/dist/templates/fullstack/apps/web/vite.config.test.ts +33 -0
- package/dist/templates/fullstack/apps/web/vite.config.ts +55 -13
- package/dist/templates/fullstack/apps/web/vite.extensions.ts +23 -0
- package/dist/templates/fullstack/compose.dev.yaml +7 -4
- package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/SKILL.md +2 -2
- package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/references/profile.md +13 -3
- package/dist/templates/fullstack/dot-claude/skills/podokit-update/SKILL.md +5 -2
- package/dist/templates/fullstack/dot-env.docker.example +5 -2
- package/dist/templates/fullstack/dot-env.example +4 -0
- package/dist/templates/fullstack/dot-podokit/dev.json +1 -1
- package/dist/templates/fullstack/infra/k3s/configmap.yaml +1 -0
- package/dist/templates/fullstack/tests/README.md +50 -1
- package/dist/templates/fullstack/tests/helpers/app-paths.ts +18 -0
- package/dist/templates/fullstack/tests/helpers/paths.ts +33 -0
- package/dist/templates/fullstack/tests/helpers/seeded-admin.ts +15 -0
- package/dist/templates/fullstack/tests/package.json +3 -3
- package/dist/templates/fullstack/tests/playwright.config.ts +10 -4
- package/dist/templates/fullstack/tests/playwright.extensions.ts +149 -1
- package/dist/templates/fullstack/tests/run-e2e.ts +28 -0
- package/dist/templates/fullstack/tests/ui/smoke.ui.spec.ts +5 -4
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.test.ts +37 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +15 -6
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.test.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flags.ts +12 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-nav.ts +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.test.ts +53 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/registry.svelte.ts +15 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/account-page.svelte +11 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/admin-sidebar.svelte +2 -23
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +7 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.test.ts +30 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/impersonation-banner.svelte +3 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/settings/app-flags-settings.svelte +65 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +32 -23
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.test.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-footer.svelte +4 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-runtime.svelte +3 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +1 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/en.json +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/ko.json +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/app-hooks.ts +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/non-indexable-paths.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.test.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.ts +5 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/settings/+page.svelte +1 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.server.ts +2 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.svelte +1 -5
- package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md +2 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +159 -170
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +72 -81
- package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +1 -6
- package/dist/templates/modules/admin-dashboard/files/tests/api/profile-image.api.spec.ts +1 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/site-settings.api.spec.ts +43 -53
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +3 -1
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +24 -6
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/app-accounts.ts +24 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/devices.ts +41 -11
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/disposable-users.ts +5 -3
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +13 -4
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +3 -9
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +2 -4
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +14 -6
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account-flows.ui.spec.ts +4 -5
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +55 -55
- package/dist/templates/modules/admin-dashboard/files/tests/ui/app-flags.ui.spec.ts +63 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/appearance.ui.spec.ts +12 -16
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +3 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +32 -35
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +4 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +1 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +1 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/general-effects.ui.spec.ts +82 -91
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +6 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +16 -20
- package/dist/templates/modules/admin-dashboard/files/tests/ui/impersonation.ui.spec.ts +6 -3
- package/dist/templates/modules/admin-dashboard/files/tests/ui/login-methods.ui.spec.ts +2 -3
- package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +2 -4
- package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +5 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +7 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/same-origin-requests.ui.spec.ts +89 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +5 -4
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings-dialogs.ui.spec.ts +67 -73
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +84 -93
- package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +8 -7
- package/dist/templates/modules/admin-dashboard/files/tests/ui/theme-mode.ui.spec.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/two-factor.ui.spec.ts +51 -54
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +10 -9
- package/dist/templates/modules/admin-dashboard/module.manifest.json +7 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +5 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.spec.ts +17 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +18 -0
- package/dist/templates/modules/audit-log/module.manifest.json +15 -3
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-app-name.spec.ts +21 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-config-store.ts +13 -1
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-environment.ts +9 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +19 -3
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.module.ts +2 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.runtime.spec.ts +10 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +7 -5
- package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.spec.ts +86 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.ts +44 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +2 -1
- package/dist/templates/modules/auth/files/apps/api/src/migrate.ts +3 -1
- package/dist/templates/modules/auth/files/apps/api/src/settings/app-flags.ts +9 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.spec.ts +43 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +30 -5
- package/dist/templates/modules/auth/module.manifest.json +8 -2
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.spec.ts +107 -1
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.ts +113 -9
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.spec.ts +40 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +52 -5
- package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +9 -7
- package/dist/templates/modules/bullmq/files/apps/api/src/worker.extensions.ts +13 -0
- package/dist/templates/modules/bullmq/module.manifest.json +5 -1
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.worker.spec.ts +2 -0
- package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.spec.ts +174 -1
- package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.ts +150 -3
- package/dist/templates/modules/jobs-local/module.manifest.json +3 -0
- package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.spec.ts +16 -0
- package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +15 -10
- package/dist/templates/modules/mailer/module.manifest.json +1 -1
- package/dist/templates/modules/object-storage-local/files/tests/api/storage-local.api.spec.ts +3 -6
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -4
- package/dist/templates/modules/object-storage-s3/files/tests/api/storage.api.spec.ts +3 -6
- package/dist/templates/modules/rate-limit/files/tests/api/rate-limit.api.spec.ts +45 -20
- package/dist/templates/modules/sse/files/tests/api/events.api.spec.ts +3 -6
- package/dist/templates/todo/AGENTS.md +5 -1
- package/dist/templates/todo/README.md +6 -4
- package/dist/text-diff.d.ts +12 -0
- package/dist/text-diff.js +111 -0
- package/dist/update.d.ts +61 -3
- package/dist/update.js +152 -27
- package/package.json +2 -2
- package/dist/templates/modules/admin-dashboard/files/tests/api/exclusive.api.spec.ts +0 -75
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/exclusive.ts +0 -80
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.test.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRawSnippet } from "svelte";
|
|
2
|
+
import { render } from "svelte/server";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import DataTable, { type DataTableColumn, type DataTableRowContext } from "./data-table.svelte";
|
|
5
|
+
|
|
6
|
+
type Row = { name: string };
|
|
7
|
+
|
|
8
|
+
const row = createRawSnippet<[Row, DataTableRowContext]>((item) => ({
|
|
9
|
+
render: () => `<td>${item().name}</td><td></td>`,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe("DataTable", () => {
|
|
13
|
+
it("renders a column's header snippet in place of its label and sort button", () => {
|
|
14
|
+
const head = createRawSnippet(() => ({ render: () => '<input type="checkbox" aria-label="Select all rows">' }));
|
|
15
|
+
const columns: DataTableColumn<Row>[] = [
|
|
16
|
+
{ key: "name", label: "Name", sortable: true },
|
|
17
|
+
{ key: "selected", label: "Selected", head, sortable: true },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const { body } = render(DataTable<Row>, { props: { columns, rows: [{ name: "Ada" }], row, perPage: 0 } });
|
|
21
|
+
|
|
22
|
+
const headers = body.match(/<th\b[\s\S]*?<\/th>/g) ?? [];
|
|
23
|
+
expect(headers).toHaveLength(2);
|
|
24
|
+
expect(headers[0]).toContain("Name");
|
|
25
|
+
expect(headers[0]).toContain("<button");
|
|
26
|
+
expect(headers[1]).toContain('aria-label="Select all rows"');
|
|
27
|
+
expect(headers[1]).not.toContain("Selected");
|
|
28
|
+
expect(headers[1]).not.toContain("<button");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import { api } from "#lib/api.js";
|
|
4
4
|
import { Button } from "#lib/components/ui/button/index.js";
|
|
5
5
|
import { getI18n } from "#lib/i18n/index.js";
|
|
6
|
+
import { cn } from "#lib/utils.js";
|
|
6
7
|
import { toast } from "svelte-sonner";
|
|
7
8
|
|
|
8
|
-
let { email }: { email: string } = $props();
|
|
9
|
+
let { email, class: className }: { email: string; class?: string } = $props();
|
|
9
10
|
const i18n = getI18n();
|
|
10
11
|
|
|
11
12
|
async function stopImpersonating(): Promise<void> {
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
}
|
|
19
20
|
</script>
|
|
20
21
|
|
|
21
|
-
<div class="bg-primary text-primary-foreground flex items-center justify-between gap-2 px-4 py-2 text-sm">
|
|
22
|
+
<div class={cn("bg-primary text-primary-foreground flex items-center justify-between gap-2 px-4 py-2 text-sm", className)}>
|
|
22
23
|
<span>{i18n.t.users.impersonatingAs.replace("{email}", email)}</span>
|
|
23
24
|
<Button variant="secondary" size="sm" onclick={stopImpersonating}>{i18n.t.users.stopImpersonating}</Button>
|
|
24
25
|
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { invalidateAll } from "$app/navigation";
|
|
3
|
+
import { page } from "$app/state";
|
|
4
|
+
import type { Capabilities } from "@podosoft/podokit-api-client";
|
|
5
|
+
import { toast } from "svelte-sonner";
|
|
6
|
+
import { appFlagCards } from "#lib/admin/app-flag-settings.js";
|
|
7
|
+
import { api } from "#lib/api.js";
|
|
8
|
+
import { Badge } from "#lib/components/ui/badge/index.js";
|
|
9
|
+
import * as Card from "#lib/components/ui/card/index.js";
|
|
10
|
+
import { Switch } from "#lib/components/ui/switch/index.js";
|
|
11
|
+
import { getI18n } from "#lib/i18n/index.js";
|
|
12
|
+
|
|
13
|
+
// The application's flags from app-flags.ts. They share the feature-flag
|
|
14
|
+
// endpoint, so saving needs an administrator and applies at once, like the
|
|
15
|
+
// switches on the Authentication tab.
|
|
16
|
+
const i18n = getI18n();
|
|
17
|
+
const cards = $derived(appFlagCards(i18n.t, page.data.capabilities as Capabilities | undefined));
|
|
18
|
+
let saving = $state(false);
|
|
19
|
+
|
|
20
|
+
async function toggle(flag: string, next: boolean): Promise<void> {
|
|
21
|
+
saving = true;
|
|
22
|
+
try {
|
|
23
|
+
await api.put("/account/settings", { [flag]: next });
|
|
24
|
+
await invalidateAll();
|
|
25
|
+
toast.success(i18n.t.settings.saved);
|
|
26
|
+
} catch {
|
|
27
|
+
toast.error(i18n.t.settings.saveFailed);
|
|
28
|
+
}
|
|
29
|
+
saving = false;
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<section class="flex flex-col gap-3">
|
|
34
|
+
<div>
|
|
35
|
+
<h2 class="text-lg font-semibold">{i18n.t.settings.appFlags.title}</h2>
|
|
36
|
+
<p class="text-muted-foreground text-sm">{i18n.t.settings.appFlags.subtitle}</p>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
39
|
+
{#each cards as card (card.flag)}
|
|
40
|
+
<Card.Root class="flex min-h-[8.5rem] flex-col gap-2 p-4" data-app-flag={card.flag}>
|
|
41
|
+
<div class="flex items-start justify-between gap-3">
|
|
42
|
+
<span class="font-medium break-words">{card.name}</span>
|
|
43
|
+
<Switch
|
|
44
|
+
aria-label={card.name}
|
|
45
|
+
checked={card.on}
|
|
46
|
+
disabled={saving || !card.available}
|
|
47
|
+
onCheckedChange={(value) => toggle(card.flag, value === true)}
|
|
48
|
+
class="shrink-0"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<p class="text-muted-foreground flex-1 text-sm">{card.description}</p>
|
|
52
|
+
<div class="mt-auto flex items-center gap-2 pt-1">
|
|
53
|
+
<Badge variant={card.on ? "secondary" : "outline"} class="max-w-full gap-1.5">
|
|
54
|
+
<span class={`size-1.5 shrink-0 rounded-full ${card.on ? "bg-green-500" : "bg-muted-foreground/40"}`}></span>
|
|
55
|
+
<span class="truncate">
|
|
56
|
+
{card.available
|
|
57
|
+
? card.on ? i18n.t.settings.enabled : i18n.t.settings.disabled
|
|
58
|
+
: i18n.t.settings.appFlags.unavailable}
|
|
59
|
+
</span>
|
|
60
|
+
</Badge>
|
|
61
|
+
</div>
|
|
62
|
+
</Card.Root>
|
|
63
|
+
{/each}
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { goto } from "$app/navigation";
|
|
3
3
|
import * as DropdownMenu from "#lib/components/ui/dropdown-menu/index.js";
|
|
4
4
|
import * as Sidebar from "#lib/components/ui/sidebar/index.js";
|
|
5
|
+
import AccountMenu from "./account-menu.svelte";
|
|
5
6
|
import UserAvatar from "./user-avatar.svelte";
|
|
6
7
|
import ChevronsUpDownIcon from "@lucide/svelte/icons/chevrons-up-down";
|
|
7
8
|
import LogOutIcon from "@lucide/svelte/icons/log-out";
|
|
@@ -12,6 +13,10 @@
|
|
|
12
13
|
|
|
13
14
|
let { user }: { user: SessionUser } = $props();
|
|
14
15
|
const i18n = getI18n();
|
|
16
|
+
// Outside a Sidebar.Provider, such as in an application's own shell, the
|
|
17
|
+
// sidebar menu parts have no state to read, so the same identity block and
|
|
18
|
+
// actions come from the shell-independent AccountMenu instead.
|
|
19
|
+
const inSidebar = (Sidebar.useSidebar() as ReturnType<typeof Sidebar.useSidebar> | undefined) !== undefined;
|
|
15
20
|
|
|
16
21
|
async function signOut(): Promise<void> {
|
|
17
22
|
await api.auth.signOut();
|
|
@@ -19,26 +24,30 @@
|
|
|
19
24
|
}
|
|
20
25
|
</script>
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
<Sidebar.
|
|
24
|
-
<
|
|
25
|
-
<DropdownMenu.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<DropdownMenu.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</Sidebar.
|
|
27
|
+
{#if inSidebar}
|
|
28
|
+
<Sidebar.Menu>
|
|
29
|
+
<Sidebar.MenuItem>
|
|
30
|
+
<DropdownMenu.Root>
|
|
31
|
+
<DropdownMenu.Trigger data-testid="sidebar-user-menu">
|
|
32
|
+
{#snippet child({ props })}
|
|
33
|
+
<Sidebar.MenuButton {...props} size="lg" class="data-[state=open]:bg-sidebar-accent">
|
|
34
|
+
<UserAvatar {user} class="size-8" />
|
|
35
|
+
<div class="flex flex-col text-left text-sm leading-tight">
|
|
36
|
+
<span class="truncate font-medium">{user.name}</span>
|
|
37
|
+
<span class="text-muted-foreground truncate text-xs">{user.email}</span>
|
|
38
|
+
</div>
|
|
39
|
+
<ChevronsUpDownIcon class="ml-auto size-4" />
|
|
40
|
+
</Sidebar.MenuButton>
|
|
41
|
+
{/snippet}
|
|
42
|
+
</DropdownMenu.Trigger>
|
|
43
|
+
<DropdownMenu.Content align="end" side="top" class="w-56">
|
|
44
|
+
<DropdownMenu.Item onSelect={() => goto("/account")}><UserIcon class="mr-2 size-4" /> {i18n.t.nav.account}</DropdownMenu.Item>
|
|
45
|
+
<DropdownMenu.Separator />
|
|
46
|
+
<DropdownMenu.Item onSelect={signOut}><LogOutIcon class="mr-2 size-4" /> {i18n.t.nav.signOut}</DropdownMenu.Item>
|
|
47
|
+
</DropdownMenu.Content>
|
|
48
|
+
</DropdownMenu.Root>
|
|
49
|
+
</Sidebar.MenuItem>
|
|
50
|
+
</Sidebar.Menu>
|
|
51
|
+
{:else}
|
|
52
|
+
<AccountMenu {user} variant="identity" side="top" />
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { render } from "svelte/server";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import SidebarUserMenu from "./sidebar-user-menu.svelte";
|
|
4
|
+
|
|
5
|
+
vi.mock("$app/state", () => ({
|
|
6
|
+
page: {
|
|
7
|
+
url: new URL("http://localhost/projects"),
|
|
8
|
+
data: {
|
|
9
|
+
locale: "en",
|
|
10
|
+
messages: { nav: { account: "Account", signOut: "Sign out" }, common: { appName: "Admin" } },
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
vi.mock("$app/navigation", () => ({ goto: vi.fn(), invalidateAll: vi.fn() }));
|
|
15
|
+
|
|
16
|
+
describe("SidebarUserMenu", () => {
|
|
17
|
+
it("renders the account menu in a shell without a sidebar", () => {
|
|
18
|
+
const user = { id: "user-1", name: "Ada Lovelace", email: "ada@example.com", role: "user" };
|
|
19
|
+
|
|
20
|
+
const { body } = render(SidebarUserMenu, { props: { user } });
|
|
21
|
+
|
|
22
|
+
expect(body).toContain('data-testid="account-menu"');
|
|
23
|
+
expect(body).not.toContain('data-testid="sidebar-user-menu"');
|
|
24
|
+
expect(body).toContain("Ada Lovelace");
|
|
25
|
+
expect(body).toContain("ada@example.com");
|
|
26
|
+
});
|
|
27
|
+
});
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-footer.svelte
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
{#if s.footerText || hasLinks}
|
|
14
|
-
<footer
|
|
14
|
+
<footer
|
|
15
|
+
class="text-muted-foreground flex flex-col items-center gap-1 px-4 py-6 text-center text-sm"
|
|
16
|
+
data-testid="site-footer"
|
|
17
|
+
>
|
|
15
18
|
{#if s.footerText}
|
|
16
19
|
<p>{s.footerText}</p>
|
|
17
20
|
{/if}
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-runtime.svelte
CHANGED
|
@@ -44,7 +44,9 @@
|
|
|
44
44
|
{/if}
|
|
45
45
|
</svelte:head>
|
|
46
46
|
|
|
47
|
-
<
|
|
47
|
+
<!-- `.light` on <html> in light mode lets component libraries that follow a `.dark`
|
|
48
|
+
or `.light` class, and fall back to the OS setting otherwise, match the theme. -->
|
|
49
|
+
<ModeWatcher lightClassNames={["light"]} />
|
|
48
50
|
<SessionIdleTimeout timeoutMinutes={sessionIdleTimeoutMinutes} />
|
|
49
51
|
<!-- podokit:begin:site-runtime-components -->
|
|
50
52
|
<!-- podokit:end:site-runtime-components -->
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<DropdownMenu.Root>
|
|
14
14
|
<DropdownMenu.Trigger>
|
|
15
15
|
{#snippet child({ props })}
|
|
16
|
-
<Button {...props} variant="ghost" size="icon" aria-label={i18n.t.theme.toggle}>
|
|
16
|
+
<Button {...props} variant="ghost" size="icon" aria-label={i18n.t.theme.toggle} data-testid="theme-toggle">
|
|
17
17
|
<SunIcon class="size-4 dark:hidden" />
|
|
18
18
|
<MoonIcon class="hidden size-4 dark:block" />
|
|
19
19
|
</Button>
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"manage": "Manage",
|
|
294
294
|
"saved": "Setting updated",
|
|
295
295
|
"saveFailed": "Failed to update setting",
|
|
296
|
+
"appFlags": {
|
|
297
|
+
"tab": "Application",
|
|
298
|
+
"title": "Application settings",
|
|
299
|
+
"subtitle": "Turn this application's own features on or off. Changes apply immediately.",
|
|
300
|
+
"unavailable": "Not available"
|
|
301
|
+
},
|
|
296
302
|
"emailPassword": "Email & password",
|
|
297
303
|
"emailPasswordDesc": "Sign in with an email address and password.",
|
|
298
304
|
"emailVerification": "Email verification",
|
|
@@ -293,6 +293,12 @@
|
|
|
293
293
|
"manage": "관리",
|
|
294
294
|
"saved": "설정을 변경했습니다",
|
|
295
295
|
"saveFailed": "설정 변경에 실패했습니다",
|
|
296
|
+
"appFlags": {
|
|
297
|
+
"tab": "애플리케이션",
|
|
298
|
+
"title": "애플리케이션 설정",
|
|
299
|
+
"subtitle": "이 애플리케이션의 기능을 켜거나 끕니다. 변경 사항은 바로 적용됩니다.",
|
|
300
|
+
"unavailable": "사용할 수 없음"
|
|
301
|
+
},
|
|
296
302
|
"emailPassword": "이메일·비밀번호",
|
|
297
303
|
"emailPasswordDesc": "이메일과 비밀번호로 로그인.",
|
|
298
304
|
"emailVerification": "이메일 인증",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Handle } from "@sveltejs/kit/hooks";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Application-owned request hook. The managed hooks.server.ts runs it for every
|
|
5
|
+
* request after PodoKit has loaded the runtime context, so `event.locals` already
|
|
6
|
+
* holds the site settings, the signed-in user, the session, and the locale. Change
|
|
7
|
+
* `event.locals`, add response headers, or answer a request yourself here instead
|
|
8
|
+
* of editing hooks.server.ts. Combine several handles with `sequence` from
|
|
9
|
+
* "@sveltejs/kit/hooks". PodoKit creates this file once and never overwrites it.
|
|
10
|
+
*/
|
|
11
|
+
export const appHandle: Handle = ({ event, resolve }) => resolve(event);
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/non-indexable-paths.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application-owned path prefixes whose responses carry
|
|
3
|
+
* `X-Robots-Tag: noindex, nofollow`, in addition to the admin, account, sign-in,
|
|
4
|
+
* and API paths PodoKit already lists in search-indexing.ts. A listed path also
|
|
5
|
+
* covers its subpaths ("/app" covers "/app/projects/1"); "/" covers only the
|
|
6
|
+
* landing page. PodoKit creates this file once and never overwrites it.
|
|
7
|
+
*/
|
|
8
|
+
export const APP_NON_INDEXABLE_PATHS: readonly string[] = [];
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.test.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { applySearchIndexingHeaders, shouldPreventSearchIndexing } from "./search-indexing";
|
|
3
|
+
|
|
4
|
+
// Stands in for the application's own list in the app-owned non-indexable-paths.ts.
|
|
5
|
+
vi.mock("./non-indexable-paths", () => ({ APP_NON_INDEXABLE_PATHS: ["/", "/projects"] }));
|
|
6
|
+
|
|
7
|
+
describe("search indexing", () => {
|
|
8
|
+
it("keeps PodoKit's private paths out of search results", () => {
|
|
9
|
+
expect(shouldPreventSearchIndexing("/admin/users")).toBe(true);
|
|
10
|
+
expect(shouldPreventSearchIndexing("/account")).toBe(true);
|
|
11
|
+
expect(shouldPreventSearchIndexing("/api/site/settings")).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("adds the application's prefixes and their subpaths", () => {
|
|
15
|
+
expect(shouldPreventSearchIndexing("/projects")).toBe(true);
|
|
16
|
+
expect(shouldPreventSearchIndexing("/projects/a/b")).toBe(true);
|
|
17
|
+
expect(shouldPreventSearchIndexing("/projects-archive")).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("treats a listed root as the landing page only", () => {
|
|
21
|
+
expect(shouldPreventSearchIndexing("/")).toBe(true);
|
|
22
|
+
expect(shouldPreventSearchIndexing("/pricing")).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("sets the header only on non-indexable responses", () => {
|
|
26
|
+
expect(applySearchIndexingHeaders(new Response("ok"), "/projects/1").headers.get("x-robots-tag")).toBe(
|
|
27
|
+
"noindex, nofollow",
|
|
28
|
+
);
|
|
29
|
+
expect(applySearchIndexingHeaders(new Response("ok"), "/pricing").headers.has("x-robots-tag")).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { APP_NON_INDEXABLE_PATHS } from "./non-indexable-paths";
|
|
2
|
+
|
|
1
3
|
const NON_INDEXABLE_PATH_PREFIXES = [
|
|
2
4
|
"/api",
|
|
3
5
|
"/admin",
|
|
@@ -12,7 +14,9 @@ const NON_INDEXABLE_PATH_PREFIXES = [
|
|
|
12
14
|
"/oauth2",
|
|
13
15
|
"/accept-invitation",
|
|
14
16
|
"/maintenance",
|
|
15
|
-
|
|
17
|
+
// Application prefixes from the app-owned non-indexable-paths.ts.
|
|
18
|
+
...APP_NON_INDEXABLE_PATHS,
|
|
19
|
+
];
|
|
16
20
|
|
|
17
21
|
function matchesPathPrefix(pathname: string, prefix: string): boolean {
|
|
18
22
|
return pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
</div>
|
|
295
295
|
|
|
296
296
|
<Tabs.Root value="general">
|
|
297
|
-
<Tabs.List>
|
|
297
|
+
<Tabs.List class="h-auto max-w-full flex-wrap justify-start">
|
|
298
298
|
<Tabs.Trigger value="general">{i18n.t.general.tab}</Tabs.Trigger>
|
|
299
299
|
<Tabs.Trigger value="appearance">{i18n.t.general.appearanceTitle}</Tabs.Trigger>
|
|
300
300
|
<Tabs.Trigger value="auth">{i18n.t.settings.tab}</Tabs.Trigger>
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.server.ts
CHANGED
|
@@ -6,6 +6,7 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
|
|
|
6
6
|
await loadProtectedLayout({ locals, fetch });
|
|
7
7
|
return {
|
|
8
8
|
...(await loadAccountData(locals, fetch)),
|
|
9
|
-
|
|
9
|
+
// Rendered by the managed AccountPage, so an owned page never has to change for it.
|
|
10
|
+
showImpersonationBanner: Boolean(locals.session?.impersonatedBy),
|
|
10
11
|
};
|
|
11
12
|
};
|
package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.svelte
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import AccountPage from "#lib/components/account-page.svelte";
|
|
3
|
-
import ImpersonationBanner from "#lib/components/impersonation-banner.svelte";
|
|
4
3
|
import LanguageSwitch from "#lib/components/language-switch.svelte";
|
|
5
4
|
import SiteFooter from "#lib/components/site-footer.svelte";
|
|
6
5
|
import ThemeToggle from "#lib/components/theme-toggle.svelte";
|
|
@@ -13,15 +12,12 @@
|
|
|
13
12
|
let {
|
|
14
13
|
data,
|
|
15
14
|
}: {
|
|
16
|
-
data: { user: SessionUser; currentSessionId: string | null; capabilities: Capabilities
|
|
15
|
+
data: { user: SessionUser; currentSessionId: string | null; capabilities: Capabilities };
|
|
17
16
|
} = $props();
|
|
18
17
|
const i18n = getI18n();
|
|
19
18
|
</script>
|
|
20
19
|
|
|
21
20
|
<div class="flex min-h-svh flex-col">
|
|
22
|
-
{#if data.impersonating}
|
|
23
|
-
<ImpersonationBanner email={data.user.email} />
|
|
24
|
-
{/if}
|
|
25
21
|
<header class="flex h-14 items-center gap-2 border-b px-4">
|
|
26
22
|
<Button href="/" variant="ghost">{i18n.t.nav.home}</Button>
|
|
27
23
|
<div class="flex-1"></div>
|
package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md
CHANGED
|
@@ -38,6 +38,8 @@ put pagination outside the table.
|
|
|
38
38
|
- Responsive columns: set `hideBelow` to `sm`, `md`, `lg`, or `xl`, then apply
|
|
39
39
|
`cellClass(columnKey)` to the matching `Table.Cell`. Never repeat the
|
|
40
40
|
responsive class by hand; the shared class keeps headers and cells aligned.
|
|
41
|
+
- Header controls such as a select-all checkbox: pass a `head` snippet on the
|
|
42
|
+
column; it replaces the label and sort button. Give the control an aria-label.
|
|
41
43
|
- Give each table an `ariaLabel` and scope Playwright locators to that table.
|
|
42
44
|
- No footer needed: `perPage={0}`.
|
|
43
45
|
- **Search & filters**: put the shared `table-toolbar.svelte` (`TableToolbar`)
|
|
@@ -128,7 +128,7 @@ test("the audit log rejects bad paging parameters", async ({ playwright }) => {
|
|
|
128
128
|
|
|
129
129
|
// Audit logging is a DB-backed on/off toggle (auth_config `server`, applied live).
|
|
130
130
|
// Runs after the recording tests and restores the flag so nothing else is affected.
|
|
131
|
-
test("audit logging honours the DB on/off toggle @smoke", async ({ playwright, disposableUsers }) => {
|
|
131
|
+
test("audit logging honours the DB on/off toggle @smoke", { tag: "@exclusive" }, async ({ playwright, disposableUsers }) => {
|
|
132
132
|
const admin = await signedIn(playwright, ADMIN);
|
|
133
133
|
test.skip((await admin.get("/api/audit-logs")).status() === 404, "audit-log module not installed");
|
|
134
134
|
const auditLog = async () => (await (await admin.get("/api/account/capabilities")).json()).auditLog as boolean;
|