@podosoft/podokit 1.1.6 → 1.3.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/dist/add.d.ts +3 -1
- package/dist/add.js +116 -14
- package/dist/deploy-render.js +5 -0
- package/dist/index.js +8 -0
- package/dist/lib.d.ts +2 -2
- package/dist/lib.js +3 -2
- package/dist/lockfile.d.ts +3 -1
- package/dist/lockfile.js +17 -6
- package/dist/templates/fullstack/apps/api/package.json +2 -2
- package/dist/templates/fullstack/apps/api/scripts/build.mjs +1 -0
- package/dist/templates/fullstack/apps/api/src/app.spec.ts +81 -0
- package/dist/templates/fullstack/apps/api/src/app.ts +53 -5
- package/dist/templates/fullstack/apps/api/src/config/env.validation.ts +6 -0
- package/dist/templates/fullstack/apps/api/src/core/api-contract.ts +10 -1
- package/dist/templates/fullstack/apps/api/src/core/services.spec.ts +30 -0
- package/dist/templates/fullstack/apps/api/src/core/services.ts +19 -3
- package/dist/templates/fullstack/apps/api/src/database/data-source.spec.ts +15 -0
- package/dist/templates/fullstack/apps/api/src/database/data-source.ts +3 -0
- package/dist/templates/fullstack/apps/api/src/database/database.spec.ts +149 -4
- package/dist/templates/fullstack/apps/api/src/database/database.ts +66 -13
- package/dist/templates/fullstack/apps/api/src/database/migrate.ts +1 -1
- package/dist/templates/fullstack/apps/api/src/health/health.plugin.spec.ts +86 -0
- package/dist/templates/fullstack/apps/api/src/health/health.plugin.ts +4 -9
- package/dist/templates/fullstack/apps/api/src/health/readiness.service.spec.ts +141 -1
- package/dist/templates/fullstack/apps/api/src/health/readiness.service.ts +86 -12
- package/dist/templates/fullstack/apps/web/package.json +1 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.test.ts +188 -1
- package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.ts +30 -0
- package/dist/templates/fullstack/apps/web/src/routes/api/[...path]/+server.ts +3 -3
- package/dist/templates/fullstack/apps/web/src/routes/api/auth/[...all]/+server.ts +3 -3
- package/dist/templates/fullstack/compose.dev.yaml +21 -0
- package/dist/templates/fullstack/dot-claude/skills/podokit-sveltekit-route/SKILL.md +8 -1
- package/dist/templates/fullstack/dot-env.docker +3 -0
- package/dist/templates/fullstack/dot-env.example +14 -0
- package/dist/templates/fullstack/infra/k3s/api-deployment.yaml +2 -0
- package/dist/templates/fullstack/tests/playwright.extensions.ts +2 -2
- package/dist/templates/modules/admin-dashboard/files/apps/api/scripts/bootstrap-admin.mjs +21 -26
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.migration.spec.ts +74 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.revoker.spec.ts +25 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.revoker.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.routes.spec.ts +122 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.routes.ts +46 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.service.spec.ts +187 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.service.ts +160 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.routes.spec.ts +76 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.routes.ts +41 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.service.spec.ts +124 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-users/admin-users.service.ts +153 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin.module.ts +22 -28
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/bootstrap-admin-runtime.ts +60 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/migrations/1792060000000-IndexSessionCreatedAt.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/profile-image/profile-image.service.spec.ts +3 -5
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/profile-image/profile-image.service.ts +6 -17
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-assets/site-assets.routes.spec.ts +167 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-assets/site-assets.routes.ts +50 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.service.spec.ts +158 -0
- package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.service.ts +78 -28
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +3 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.test.ts +124 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +19 -11
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/account-data.server.ts +6 -32
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/sessions.test.ts +74 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/sessions.ts +73 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/users.test.ts +102 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/users.ts +141 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/audit-log.test.ts +68 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/audit-log.ts +108 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/account-page.svelte +14 -8
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/session-idle-timeout.svelte +74 -49
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/en.json +6 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/ko.json +6 -1
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.test.ts +29 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +23 -6
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/oauth-query.test.ts +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/oauth-query.ts +15 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/capabilities.test.ts +98 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/capabilities.ts +54 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.test.ts +23 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +2 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/protected-layout.ts +7 -50
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/public-paths.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/audit/+page.svelte +88 -45
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/sessions/+page.svelte +76 -75
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/settings/+page.server.ts +15 -14
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/settings/+page.svelte +2 -2
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/users/+page.svelte +79 -37
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +10 -17
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.ts +10 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/oauth2/consent/+page.svelte +4 -3
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/setup-2fa/+page.svelte +14 -8
- package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md +2 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/admin-sessions.api.spec.ts +124 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/admin-users.api.spec.ts +86 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +75 -12
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +1 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +35 -1
- package/dist/templates/modules/admin-dashboard/files/tests/api/profile-image.api.spec.ts +7 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/site-settings.api.spec.ts +72 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +36 -2
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/disposable-users.ts +5 -2
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +41 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +32 -13
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +0 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +1 -3
- package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +16 -5
- package/dist/templates/modules/admin-dashboard/files/tests/ui/request-budget.ui.spec.ts +45 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +64 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +29 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +8 -1
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +32 -1
- package/dist/templates/modules/admin-dashboard/module.manifest.json +3 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +46 -5
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +19 -7
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +2 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.migration.spec.ts +126 -3
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +13 -11
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.routes.spec.ts +115 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.routes.ts +44 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.spec.ts +421 -43
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +382 -35
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1792000000000-IndexAuditLogFilters.ts +57 -0
- package/dist/templates/modules/audit-log/module.manifest.json +7 -4
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-config-store.ts +20 -5
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +24 -29
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.module.ts +28 -13
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.openapi.spec.ts +56 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.openapi.ts +22 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.service.spec.ts +81 -5
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.service.ts +26 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +15 -9
- package/dist/templates/modules/auth/files/apps/api/src/auth/config-events.spec.ts +38 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/config-events.ts +43 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/db.spec.ts +24 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +31 -2
- package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.spec.ts +58 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +46 -57
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.rows.spec.ts +53 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.rows.ts +65 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +21 -37
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/config-param.spec.ts +23 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/config-param.ts +10 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/session-expiry.spec.ts +33 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/session-expiry.ts +29 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +10 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.spec.ts +81 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.ts +37 -34
- package/dist/templates/modules/auth/files/dot-claude/skills/podokit-configure-auth/references/bootstrap-admin.md +2 -0
- package/dist/templates/modules/auth/module.manifest.json +2 -2
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.spec.ts +136 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.ts +34 -13
- package/dist/templates/modules/file-upload/files/apps/api/src/files/upload-keys.spec.ts +60 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/upload-keys.ts +51 -0
- package/dist/templates/modules/file-upload/files/tests/api/files.api.spec.ts +70 -9
- package/dist/templates/modules/file-upload/module.manifest.json +9 -3
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/job-progress.module.ts +26 -32
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.bridge.spec.ts +16 -1
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.processor.ts +31 -4
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.worker.spec.ts +145 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.worker.ts +37 -0
- package/dist/templates/modules/job-progress/files/tests/api/progress.api.spec.ts +94 -4
- package/dist/templates/modules/job-progress/module.manifest.json +3 -3
- package/dist/templates/modules/mailer/files/apps/api/src/mail/mailer.spec.ts +54 -0
- package/dist/templates/modules/mailer/files/apps/api/src/mail/mailer.ts +47 -25
- package/dist/templates/modules/mailer/module.manifest.json +1 -1
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/download-url.spec.ts +86 -0
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/download-url.ts +159 -0
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/storage-demo.ts +38 -0
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/storage-local.module.spec.ts +172 -4
- package/dist/templates/modules/object-storage-local/files/apps/api/src/storage-local/storage-local.module.ts +110 -33
- package/dist/templates/modules/object-storage-local/files/tests/api/storage-local.api.spec.ts +88 -0
- package/dist/templates/modules/object-storage-local/module.manifest.json +6 -1
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-object.store.spec.ts +80 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/s3-object.store.ts +13 -7
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage-demo.spec.ts +156 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage-demo.ts +38 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.module.ts +39 -21
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts +101 -2
- package/dist/templates/modules/object-storage-s3/files/tests/api/storage.api.spec.ts +84 -9
- package/dist/templates/modules/object-storage-s3/module.manifest.json +8 -3
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.config.spec.ts +9 -0
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.config.ts +7 -0
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.identity.spec.ts +139 -21
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.identity.ts +80 -11
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.spec.ts +12 -6
- package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +15 -2
- package/dist/templates/modules/rate-limit/files/tests/api/rate-limit.api.spec.ts +46 -0
- package/dist/templates/modules/rate-limit/module.manifest.json +4 -3
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis-cache.store.spec.ts +125 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis-cache.store.ts +27 -13
- package/dist/templates/modules/sse/files/apps/api/src/events/event-envelope.ts +123 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/event-stream.ts +191 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/event-topics.ts +64 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.module.spec.ts +250 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.module.ts +121 -15
- package/dist/templates/modules/sse/files/apps/api/src/events/events.service.spec.ts +407 -18
- package/dist/templates/modules/sse/files/apps/api/src/events/events.service.ts +153 -40
- package/dist/templates/modules/sse/files/apps/web/src/lib/events.test.ts +216 -0
- package/dist/templates/modules/sse/files/apps/web/src/lib/events.ts +207 -0
- package/dist/templates/modules/sse/files/tests/api/events.api.spec.ts +227 -70
- package/dist/templates/modules/sse/module.manifest.json +20 -5
- package/dist/update.d.ts +17 -0
- package/dist/update.js +33 -8
- package/package.json +2 -2
|
@@ -1,47 +1,54 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
2
3
|
import { Badge } from "#lib/components/ui/badge/index.js";
|
|
3
4
|
import * as Table from "#lib/components/ui/table/index.js";
|
|
4
5
|
import DataTable, { type DataTableColumn, type SortState, DEFAULT_PAGE_SIZE } from "#lib/components/data-table.svelte";
|
|
5
6
|
import TableToolbar, { type ToolbarFilter, type ToolbarSearchField } from "#lib/components/table-toolbar.svelte";
|
|
6
7
|
import { toast } from "svelte-sonner";
|
|
7
8
|
import { getI18n, fmt, formatDateTime } from "#lib/i18n/index.js";
|
|
9
|
+
import {
|
|
10
|
+
cursorForPage,
|
|
11
|
+
emptyTrail,
|
|
12
|
+
fetchAuditActions,
|
|
13
|
+
fetchAuditLogPage,
|
|
14
|
+
recordPage,
|
|
15
|
+
trailCount,
|
|
16
|
+
type AuditLogEntry,
|
|
17
|
+
type AuditLogFilters,
|
|
18
|
+
type AuditLogOrder,
|
|
19
|
+
} from "#lib/audit-log.js";
|
|
8
20
|
|
|
9
21
|
const i18n = getI18n();
|
|
10
|
-
|
|
11
|
-
id: string;
|
|
12
|
-
action: string;
|
|
13
|
-
actorId: string | null;
|
|
14
|
-
actorName: string | null;
|
|
15
|
-
actorEmail: string | null;
|
|
16
|
-
targetLabel: string | null;
|
|
17
|
-
ip: string | null;
|
|
18
|
-
createdAt: string;
|
|
19
|
-
};
|
|
22
|
+
const PAGE_SIZE = DEFAULT_PAGE_SIZE;
|
|
20
23
|
|
|
21
|
-
let entries = $state<
|
|
24
|
+
let entries = $state<AuditLogEntry[]>([]);
|
|
25
|
+
let actions = $state<string[]>([]);
|
|
22
26
|
let page = $state(1);
|
|
27
|
+
let shownPage = $state(1);
|
|
28
|
+
let trail = $state(emptyTrail());
|
|
23
29
|
let sort = $state<SortState | null>({ key: "createdAt", dir: "desc" });
|
|
24
30
|
let search = $state("");
|
|
25
|
-
let appliedSearch = $state("");
|
|
26
|
-
let filterValues = $state<Record<string, string>>({ action: "" });
|
|
27
|
-
let appliedFilters = $state<Record<string, string>>({ action: "" });
|
|
28
31
|
let searchField = $state("actor");
|
|
29
|
-
let
|
|
32
|
+
let filterValues = $state<Record<string, string>>({ action: "" });
|
|
33
|
+
let applied = $state<AuditLogFilters>({ action: "", actor: "", target: "" });
|
|
34
|
+
let trailOrder: AuditLogOrder = "desc";
|
|
35
|
+
let latestRequest = 0;
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
// The server pages by time with a cursor, so time is the only sortable column.
|
|
38
|
+
const columns = $derived<DataTableColumn<AuditLogEntry>[]>([
|
|
32
39
|
{ key: "createdAt", label: i18n.t.audit.when, sortable: true, class: "whitespace-nowrap" },
|
|
33
|
-
{ key: "actor", label: i18n.t.audit.actor
|
|
34
|
-
{ key: "action", label: i18n.t.audit.action
|
|
35
|
-
{ key: "target", label: i18n.t.audit.target
|
|
36
|
-
{ key: "ip", label: i18n.t.audit.ip
|
|
40
|
+
{ key: "actor", label: i18n.t.audit.actor },
|
|
41
|
+
{ key: "action", label: i18n.t.audit.action },
|
|
42
|
+
{ key: "target", label: i18n.t.audit.target },
|
|
43
|
+
{ key: "ip", label: i18n.t.audit.ip },
|
|
37
44
|
]);
|
|
38
45
|
|
|
39
|
-
const
|
|
46
|
+
const actionOptions = $derived([...new Set([...actions, ...(applied.action ? [applied.action] : [])])].sort());
|
|
40
47
|
const filters = $derived<ToolbarFilter[]>([
|
|
41
48
|
{
|
|
42
49
|
key: "action",
|
|
43
50
|
label: i18n.t.audit.action,
|
|
44
|
-
options: [{ value: "", label: i18n.t.toolbar.all }, ...
|
|
51
|
+
options: [{ value: "", label: i18n.t.toolbar.all }, ...actionOptions.map((a) => ({ value: a, label: a }))],
|
|
45
52
|
},
|
|
46
53
|
]);
|
|
47
54
|
|
|
@@ -50,33 +57,63 @@
|
|
|
50
57
|
{ value: "target", label: i18n.t.audit.target },
|
|
51
58
|
]);
|
|
52
59
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
? (e.targetLabel ?? "")
|
|
60
|
-
: `${e.actorName ?? ""} ${e.actorEmail ?? ""}`
|
|
61
|
-
).toLowerCase();
|
|
62
|
-
if (!hay.includes(appliedSearch.toLowerCase())) return false;
|
|
63
|
-
}
|
|
64
|
-
return true;
|
|
65
|
-
}),
|
|
60
|
+
const count = $derived(trailCount(trail, PAGE_SIZE));
|
|
61
|
+
const first = $derived((shownPage - 1) * PAGE_SIZE + 1);
|
|
62
|
+
const label = $derived(
|
|
63
|
+
trail.end || entries.length === 0
|
|
64
|
+
? fmt(i18n.t.audit.total, { count })
|
|
65
|
+
: fmt(i18n.t.audit.range, { from: first, to: first + entries.length - 1 }),
|
|
66
66
|
);
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
function currentOrder(): AuditLogOrder {
|
|
69
|
+
return sort?.dir === "asc" ? "asc" : "desc";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function load(target: number): Promise<void> {
|
|
73
|
+
const cursor = cursorForPage(trail, target);
|
|
74
|
+
if (cursor === undefined) {
|
|
75
|
+
page = shownPage;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const request = ++latestRequest;
|
|
69
79
|
try {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
entries =
|
|
80
|
+
const result = await fetchAuditLogPage(fetch, applied, trailOrder, cursor, PAGE_SIZE);
|
|
81
|
+
if (request !== latestRequest) return;
|
|
82
|
+
entries = result.entries;
|
|
83
|
+
page = target;
|
|
84
|
+
shownPage = target;
|
|
85
|
+
trail = recordPage(trail, target, result.entries.length, result.nextCursor);
|
|
73
86
|
} catch {
|
|
87
|
+
if (request !== latestRequest) return;
|
|
88
|
+
page = shownPage;
|
|
74
89
|
toast.error(i18n.t.audit.loadFailed);
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
function restart(): void {
|
|
94
|
+
trailOrder = currentOrder();
|
|
95
|
+
trail = emptyTrail();
|
|
96
|
+
void load(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function applySearch(): void {
|
|
100
|
+
const value = search.trim();
|
|
101
|
+
applied = {
|
|
102
|
+
action: filterValues.action ?? "",
|
|
103
|
+
actor: searchField === "actor" ? value : "",
|
|
104
|
+
target: searchField === "target" ? value : "",
|
|
105
|
+
};
|
|
106
|
+
restart();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function onTableChange(state: { page: number }): void {
|
|
110
|
+
if (currentOrder() !== trailOrder) restart();
|
|
111
|
+
else void load(state.page);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onMount(() => {
|
|
115
|
+
restart();
|
|
116
|
+
void fetchAuditActions(fetch).then((codes) => { actions = codes; }, () => undefined);
|
|
80
117
|
});
|
|
81
118
|
</script>
|
|
82
119
|
|
|
@@ -89,21 +126,27 @@
|
|
|
89
126
|
{searchFields}
|
|
90
127
|
bind:searchField
|
|
91
128
|
bind:search
|
|
129
|
+
searchPlaceholder={searchField === "target" ? i18n.t.audit.targetHint : i18n.t.audit.actorHint}
|
|
92
130
|
filterHeading={i18n.t.toolbar.filter}
|
|
93
131
|
searchHeading={i18n.t.toolbar.search}
|
|
94
132
|
searchButton={i18n.t.toolbar.searchButton}
|
|
95
|
-
onSearch={
|
|
133
|
+
onSearch={applySearch}
|
|
96
134
|
/>
|
|
97
135
|
|
|
98
136
|
<DataTable
|
|
99
137
|
{columns}
|
|
100
|
-
rows={
|
|
138
|
+
rows={entries}
|
|
101
139
|
getKey={(e) => e.id}
|
|
102
140
|
empty={i18n.t.audit.empty}
|
|
141
|
+
ariaLabel={i18n.t.audit.title}
|
|
103
142
|
bind:sort
|
|
143
|
+
manualSort
|
|
104
144
|
bind:page
|
|
105
|
-
perPage={
|
|
106
|
-
|
|
145
|
+
perPage={PAGE_SIZE}
|
|
146
|
+
total={count}
|
|
147
|
+
manualPagination
|
|
148
|
+
{label}
|
|
149
|
+
onChange={onTableChange}
|
|
107
150
|
>
|
|
108
151
|
{#snippet row(e)}
|
|
109
152
|
<Table.Cell class="text-muted-foreground whitespace-nowrap">{formatDateTime(e.createdAt)}</Table.Cell>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from "svelte";
|
|
2
3
|
import { Button } from "#lib/components/ui/button/index.js";
|
|
3
4
|
import { Badge } from "#lib/components/ui/badge/index.js";
|
|
4
5
|
import * as Table from "#lib/components/ui/table/index.js";
|
|
@@ -8,100 +9,94 @@
|
|
|
8
9
|
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
|
9
10
|
import { toast } from "svelte-sonner";
|
|
10
11
|
import { api } from "#lib/api.js";
|
|
12
|
+
import { KeysetPages, listAdminSessions, revokeAdminSession, type AdminSession } from "#lib/admin/sessions.js";
|
|
11
13
|
import { getI18n, fmt, formatDateTime } from "#lib/i18n/index.js";
|
|
12
14
|
import { deviceLabel } from "#lib/device-label.js";
|
|
13
15
|
|
|
14
16
|
const i18n = getI18n();
|
|
17
|
+
const PAGE_SIZE = DEFAULT_PAGE_SIZE;
|
|
18
|
+
// The API pages newest first by keyset, so the order is fixed rather than sortable.
|
|
19
|
+
const sort: SortState = { key: "createdAt", dir: "desc" };
|
|
20
|
+
const pages = new KeysetPages();
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const USER_LIMIT = 50;
|
|
20
|
-
type Row = {
|
|
21
|
-
id: string;
|
|
22
|
-
token: string;
|
|
23
|
-
userId: string;
|
|
24
|
-
userName: string;
|
|
25
|
-
userEmail: string;
|
|
26
|
-
userAgent?: string | null;
|
|
27
|
-
ipAddress?: string | null;
|
|
28
|
-
createdAt: string | Date;
|
|
29
|
-
expiresAt?: string | Date | null;
|
|
30
|
-
impersonatedBy?: string | null;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
let rows = $state<Row[]>([]);
|
|
22
|
+
let rows = $state<AdminSession[]>([]);
|
|
23
|
+
let total = $state(0);
|
|
24
|
+
let pagerTotal = $state(0);
|
|
34
25
|
let page = $state(1);
|
|
35
|
-
// No default sort: keep the fan-out order so the newest session isn't required
|
|
36
|
-
// to sit on page 1. Sorting is available by clicking a column header.
|
|
37
|
-
let sort = $state<SortState | null>(null);
|
|
38
26
|
let loading = $state(false);
|
|
39
27
|
let busy = $state(false);
|
|
40
28
|
let search = $state("");
|
|
41
29
|
let appliedSearch = $state("");
|
|
42
|
-
let searchField = $state("
|
|
43
|
-
let
|
|
30
|
+
let searchField = $state("user");
|
|
31
|
+
let latestRequest = 0;
|
|
32
|
+
let shownPage = 1;
|
|
44
33
|
|
|
45
|
-
const columns = $derived<DataTableColumn<
|
|
46
|
-
{ key: "user", label: i18n.t.adminSessions.user
|
|
47
|
-
{ key: "device", label: i18n.t.adminSessions.device
|
|
48
|
-
{ key: "ip", label: i18n.t.adminSessions.ip
|
|
49
|
-
{ key: "createdAt", label: i18n.t.adminSessions.since
|
|
50
|
-
{ key: "expiresAt", label: i18n.t.adminSessions.expires
|
|
34
|
+
const columns = $derived<DataTableColumn<AdminSession>[]>([
|
|
35
|
+
{ key: "user", label: i18n.t.adminSessions.user },
|
|
36
|
+
{ key: "device", label: i18n.t.adminSessions.device },
|
|
37
|
+
{ key: "ip", label: i18n.t.adminSessions.ip },
|
|
38
|
+
{ key: "createdAt", label: i18n.t.adminSessions.since },
|
|
39
|
+
{ key: "expiresAt", label: i18n.t.adminSessions.expires },
|
|
51
40
|
{ key: "actions", label: "", class: "w-10" },
|
|
52
41
|
]);
|
|
53
42
|
|
|
54
|
-
const searchFields = $derived<ToolbarSearchField[]>([
|
|
55
|
-
{ value: "email", label: i18n.t.users.email },
|
|
56
|
-
{ value: "name", label: i18n.t.users.name },
|
|
57
|
-
]);
|
|
58
|
-
|
|
59
|
-
const filtered = $derived(
|
|
60
|
-
rows.filter((r) => {
|
|
61
|
-
if (!appliedSearch) return true;
|
|
62
|
-
const hay = (appliedSearchField === "name" ? r.userName : r.userEmail).toLowerCase();
|
|
63
|
-
return hay.includes(appliedSearch.toLowerCase());
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
43
|
+
const searchFields = $derived<ToolbarSearchField[]>([{ value: "user", label: i18n.t.adminSessions.user }]);
|
|
66
44
|
|
|
67
|
-
|
|
45
|
+
// Loads one page; a newer request supersedes any that is still in flight.
|
|
46
|
+
async function load(target: number): Promise<void> {
|
|
47
|
+
const cursor = pages.cursorFor(target);
|
|
48
|
+
if (cursor === undefined) return;
|
|
49
|
+
const request = ++latestRequest;
|
|
68
50
|
loading = true;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return;
|
|
51
|
+
try {
|
|
52
|
+
const result = await listAdminSessions(api, { limit: PAGE_SIZE, cursor, search: appliedSearch });
|
|
53
|
+
if (request !== latestRequest) return;
|
|
54
|
+
// A revoke can empty the last page; show the one before it instead.
|
|
55
|
+
if (result.items.length === 0 && target > 1) return load(target - 1);
|
|
56
|
+
pages.record(target, result.nextCursor);
|
|
57
|
+
rows = result.items;
|
|
58
|
+
total = result.total;
|
|
59
|
+
pagerTotal = pages.pagerTotal(result.total, PAGE_SIZE);
|
|
60
|
+
page = shownPage = target;
|
|
61
|
+
} catch {
|
|
62
|
+
if (request !== latestRequest) return;
|
|
63
|
+
page = shownPage;
|
|
64
|
+
toast.error(i18n.t.adminSessions.loadFailed);
|
|
65
|
+
} finally {
|
|
66
|
+
if (request === latestRequest) loading = false;
|
|
74
67
|
}
|
|
75
|
-
const users = (data?.users ?? []) as Array<{ id: string; name: string; email: string }>;
|
|
76
|
-
const perUser = await Promise.all(
|
|
77
|
-
users.map(async (u) => {
|
|
78
|
-
const { data: res } = await api.auth.admin.listUserSessions({ userId: u.id });
|
|
79
|
-
const sessions = (res?.sessions ?? []) as Array<Omit<Row, "userId" | "userName" | "userEmail">>;
|
|
80
|
-
return sessions.map((s) => ({ ...s, userId: u.id, userName: u.name, userEmail: u.email }));
|
|
81
|
-
}),
|
|
82
|
-
);
|
|
83
|
-
rows = perUser.flat();
|
|
84
|
-
loading = false;
|
|
85
68
|
}
|
|
86
69
|
|
|
87
|
-
|
|
70
|
+
function applySearch(): void {
|
|
71
|
+
appliedSearch = search;
|
|
72
|
+
pages.reset();
|
|
73
|
+
void load(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function revoke(row: AdminSession): Promise<void> {
|
|
88
77
|
busy = true;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
try {
|
|
79
|
+
await revokeAdminSession(api, row.id);
|
|
80
|
+
toast.success(i18n.t.adminSessions.revoked);
|
|
81
|
+
await load(page);
|
|
82
|
+
} catch {
|
|
83
|
+
toast.error(i18n.t.adminSessions.revokeFailed);
|
|
84
|
+
} finally {
|
|
85
|
+
busy = false;
|
|
95
86
|
}
|
|
96
87
|
}
|
|
97
88
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
89
|
+
async function revokeAll(row: AdminSession): Promise<void> {
|
|
90
|
+
busy = true;
|
|
91
|
+
const { error } = await api.auth.admin.revokeUserSessions({ userId: row.userId });
|
|
92
|
+
busy = false;
|
|
93
|
+
if (error) return void toast.error(i18n.t.adminSessions.revokeFailed);
|
|
94
|
+
toast.success(i18n.t.adminSessions.allRevoked);
|
|
95
|
+
await load(page);
|
|
96
|
+
}
|
|
102
97
|
|
|
103
98
|
$effect(() => {
|
|
104
|
-
void load();
|
|
99
|
+
untrack(() => void load(1));
|
|
105
100
|
});
|
|
106
101
|
</script>
|
|
107
102
|
|
|
@@ -112,20 +107,26 @@
|
|
|
112
107
|
{searchFields}
|
|
113
108
|
bind:searchField
|
|
114
109
|
bind:search
|
|
110
|
+
searchPlaceholder={i18n.t.adminSessions.searchPlaceholder}
|
|
115
111
|
searchHeading={i18n.t.toolbar.search}
|
|
116
112
|
searchButton={i18n.t.toolbar.searchButton}
|
|
117
|
-
onSearch={
|
|
113
|
+
onSearch={applySearch}
|
|
118
114
|
/>
|
|
119
115
|
|
|
120
116
|
<DataTable
|
|
121
117
|
{columns}
|
|
122
|
-
rows
|
|
118
|
+
{rows}
|
|
123
119
|
getKey={(r) => r.id}
|
|
124
120
|
empty={loading ? "…" : i18n.t.adminSessions.empty}
|
|
125
|
-
|
|
121
|
+
ariaLabel={i18n.t.adminSessions.title}
|
|
122
|
+
{sort}
|
|
123
|
+
manualSort
|
|
124
|
+
manualPagination
|
|
126
125
|
bind:page
|
|
127
|
-
perPage={
|
|
128
|
-
|
|
126
|
+
perPage={PAGE_SIZE}
|
|
127
|
+
total={pagerTotal}
|
|
128
|
+
label={fmt(i18n.t.adminSessions.total, { count: total })}
|
|
129
|
+
onChange={(state) => void load(state.page)}
|
|
129
130
|
>
|
|
130
131
|
{#snippet row(r)}
|
|
131
132
|
<Table.Cell>
|
|
@@ -138,7 +139,7 @@
|
|
|
138
139
|
<Table.Cell class="max-w-xs truncate" title={r.userAgent ?? ""}>{deviceLabel(r.userAgent) ?? r.userAgent ?? i18n.t.adminSessions.unknown}</Table.Cell>
|
|
139
140
|
<Table.Cell class="text-muted-foreground">{r.ipAddress ?? "—"}</Table.Cell>
|
|
140
141
|
<Table.Cell class="text-muted-foreground">{formatDateTime(r.createdAt)}</Table.Cell>
|
|
141
|
-
<Table.Cell class="text-muted-foreground">{
|
|
142
|
+
<Table.Cell class="text-muted-foreground">{formatDateTime(r.expiresAt)}</Table.Cell>
|
|
142
143
|
<Table.Cell>
|
|
143
144
|
<DropdownMenu.Root>
|
|
144
145
|
<DropdownMenu.Trigger>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PageServerLoad } from "./$types";
|
|
2
|
+
import { getCapabilities, type CapabilitiesEvent } from "#lib/server/capabilities.js";
|
|
2
3
|
import { requireAdmin } from "#lib/server/guards.js";
|
|
3
4
|
|
|
4
5
|
// Non-secret view of the DB-backed auth config (OAuth providers, SMTP, server
|
|
@@ -20,23 +21,23 @@ export type AuthConfigView = {
|
|
|
20
21
|
};
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
requireAdmin(locals.user, locals);
|
|
25
|
-
let authConfig: AuthConfigView | null = null;
|
|
24
|
+
async function loadAuthConfig(fetch: CapabilitiesEvent["fetch"]): Promise<AuthConfigView | null> {
|
|
26
25
|
try {
|
|
27
26
|
const res = await fetch("/api/account/auth-config");
|
|
28
|
-
if (res.ok)
|
|
27
|
+
if (res.ok) return (await res.json()) as AuthConfigView;
|
|
29
28
|
} catch {
|
|
30
29
|
/* leave null — the page shows the DB-config sections as unavailable */
|
|
31
30
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const load: PageServerLoad = async ({ locals, fetch }) => {
|
|
35
|
+
requireAdmin(locals.user, locals);
|
|
36
|
+
// The admin layout loads the same capabilities in this request, so the
|
|
37
|
+
// require-two-factor policy comes from that shared response.
|
|
38
|
+
const [authConfig, capabilities] = await Promise.all([
|
|
39
|
+
loadAuthConfig(fetch),
|
|
40
|
+
getCapabilities({ locals, fetch }),
|
|
41
|
+
]);
|
|
42
|
+
return { authConfig, require2fa: capabilities.require2fa };
|
|
42
43
|
};
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
const ac = $derived(data.authConfig);
|
|
25
25
|
|
|
26
26
|
type EditableFlag = "twoFactor" | "magicLink" | "emailOtp" | "username" | "multiSession" | "phoneNumber" | "apiKey" | "passkey" | "organization" | "oidcProvider";
|
|
27
|
-
// require2fa is
|
|
28
|
-
//
|
|
27
|
+
// require2fa is an enrolment policy rather than a feature, so its card reads
|
|
28
|
+
// `data.require2fa` instead of the feature flags in `caps`.
|
|
29
29
|
type ToggleFlag = EditableFlag | "require2fa";
|
|
30
30
|
type ServerKey = "requireEmailVerification" | "requireSignupApproval" | "hibp" | "allowDelete" | "auditLog";
|
|
31
31
|
|