@prenta/admin 1.17.13 → 1.17.16
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/CHANGELOG.md +20 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +6 -26
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/lib/pages-service-fetch.test.js +3 -0
- package/dist/__tests__/lib/pages-service-fetch.test.js.map +1 -1
- package/dist/__tests__/views/admin-lazy-route.render.test.d.ts +2 -0
- package/dist/__tests__/views/admin-lazy-route.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/admin-lazy-route.render.test.js +30 -0
- package/dist/__tests__/views/admin-lazy-route.render.test.js.map +1 -0
- package/dist/__tests__/views/admin-lazy-views.test.d.ts +2 -0
- package/dist/__tests__/views/admin-lazy-views.test.d.ts.map +1 -0
- package/dist/__tests__/views/admin-lazy-views.test.js +67 -0
- package/dist/__tests__/views/admin-lazy-views.test.js.map +1 -0
- package/dist/__tests__/views/audit-log-export.test.d.ts +2 -0
- package/dist/__tests__/views/audit-log-export.test.d.ts.map +1 -0
- package/dist/__tests__/views/audit-log-export.test.js +11 -0
- package/dist/__tests__/views/audit-log-export.test.js.map +1 -0
- package/dist/__tests__/views/trash.render.test.js +35 -0
- package/dist/__tests__/views/trash.render.test.js.map +1 -1
- package/dist/lib/pages-service.d.ts.map +1 -1
- package/dist/lib/pages-service.js +22 -2
- package/dist/lib/pages-service.js.map +1 -1
- package/dist/router/admin-routes.d.ts +2 -2
- package/dist/router/admin-routes.js +3 -3
- package/dist/router/admin-routes.js.map +1 -1
- package/dist/views/CollectionList.d.ts.map +1 -1
- package/dist/views/CollectionList.js +23 -2
- package/dist/views/CollectionList.js.map +1 -1
- package/dist/views/SEO.d.ts +1 -44
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +1 -46
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/Trash.d.ts.map +1 -1
- package/dist/views/Trash.js +21 -2
- package/dist/views/Trash.js.map +1 -1
- package/dist/views/lazy-views.d.ts +30 -0
- package/dist/views/lazy-views.d.ts.map +1 -0
- package/dist/views/lazy-views.js +37 -0
- package/dist/views/lazy-views.js.map +1 -0
- package/dist/views/seo/seo-routes.d.ts +16 -0
- package/dist/views/seo/seo-routes.d.ts.map +1 -0
- package/dist/views/seo/seo-routes.js +53 -0
- package/dist/views/seo/seo-routes.js.map +1 -0
- package/dist/views/settings/AuditLogExportCard.d.ts +9 -0
- package/dist/views/settings/AuditLogExportCard.d.ts.map +1 -0
- package/dist/views/settings/AuditLogExportCard.js +66 -0
- package/dist/views/settings/AuditLogExportCard.js.map +1 -0
- package/dist/views/settings/SecurityTab.d.ts.map +1 -1
- package/dist/views/settings/SecurityTab.js +2 -1
- package/dist/views/settings/SecurityTab.js.map +1 -1
- package/package.json +5 -5
- package/src/AdminRoot.tsx +33 -25
- package/src/__tests__/lib/pages-service-fetch.test.ts +5 -0
- package/src/__tests__/views/admin-lazy-route.render.test.tsx +39 -0
- package/src/__tests__/views/admin-lazy-views.test.ts +70 -0
- package/src/__tests__/views/audit-log-export.test.ts +17 -0
- package/src/__tests__/views/trash.render.test.tsx +40 -0
- package/src/lib/pages-service.ts +26 -2
- package/src/router/admin-routes.ts +3 -3
- package/src/views/CollectionList.tsx +21 -2
- package/src/views/SEO.tsx +1 -48
- package/src/views/Trash.tsx +68 -16
- package/src/views/lazy-views.tsx +79 -0
- package/src/views/seo/seo-routes.ts +58 -0
- package/src/views/settings/AuditLogExportCard.tsx +131 -0
- package/src/views/settings/SecurityTab.tsx +3 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { lazy } from 'react';
|
|
3
|
+
import { Skeleton } from '../components/ui/Skeleton.js';
|
|
4
|
+
/**
|
|
5
|
+
* Route-level splits. Each view is a dynamic import so the admin shell
|
|
6
|
+
* (login, layout, dashboard) does not download the editors up front.
|
|
7
|
+
* Named exports are adapted to the default export `React.lazy` expects.
|
|
8
|
+
*/
|
|
9
|
+
export const CollectionList = lazy(() => import('./CollectionList.js').then((m) => ({ default: m.CollectionList })));
|
|
10
|
+
export const DocumentEdit = lazy(() => import('./DocumentEdit.js').then((m) => ({ default: m.DocumentEdit })));
|
|
11
|
+
export const MediaBrowser = lazy(() => import('./MediaBrowser.js').then((m) => ({ default: m.MediaBrowser })));
|
|
12
|
+
export const Settings = lazy(() => import('./Settings.js').then((m) => ({ default: m.Settings })));
|
|
13
|
+
export const Profile = lazy(() => import('./Profile.js').then((m) => ({ default: m.Profile })));
|
|
14
|
+
export const Forms = lazy(() => import('./Forms.js').then((m) => ({ default: m.Forms })));
|
|
15
|
+
export const FormEditor = lazy(() => import('./FormEditor.js').then((m) => ({ default: m.FormEditor })));
|
|
16
|
+
export const FormSubmissions = lazy(() => import('./FormSubmissions.js').then((m) => ({ default: m.FormSubmissions })));
|
|
17
|
+
export const FormEntries = lazy(() => import('./FormEntries.js').then((m) => ({ default: m.FormEntries })));
|
|
18
|
+
export const FormDeliveries = lazy(() => import('./FormDeliveries.js').then((m) => ({ default: m.FormDeliveries })));
|
|
19
|
+
export const SEO = lazy(() => import('./SEO.js').then((m) => ({ default: m.SEO })));
|
|
20
|
+
export const ScriptTags = lazy(() => import('./ScriptTags.js').then((m) => ({ default: m.ScriptTags })));
|
|
21
|
+
export const PageSectionEditor = lazy(() => import('./page-editor/PageSectionEditor.js').then((m) => ({ default: m.PageSectionEditor })));
|
|
22
|
+
export const Trash = lazy(() => import('./Trash.js').then((m) => ({ default: m.Trash })));
|
|
23
|
+
export const PagesListView = lazy(() => import('./Pages/PagesListView.js').then((m) => ({ default: m.PagesListView })));
|
|
24
|
+
export const PageTagManager = lazy(() => import('./Pages/PageTagManager.js').then((m) => ({ default: m.PageTagManager })));
|
|
25
|
+
export const PostsListView = lazy(() => import('./Posts/PostsListView.js').then((m) => ({ default: m.PostsListView })));
|
|
26
|
+
export const PostTypesView = lazy(() => import('./Posts/PostTypesView.js').then((m) => ({ default: m.PostTypesView })));
|
|
27
|
+
export const PostTypeEditor = lazy(() => import('./Posts/PostTypeEditor.js').then((m) => ({ default: m.PostTypeEditor })));
|
|
28
|
+
export const NewPostPicker = lazy(() => import('./Posts/NewPostPicker.js').then((m) => ({ default: m.NewPostPicker })));
|
|
29
|
+
export const NewPostTypeInstructions = lazy(() => import('./Posts/NewPostTypeInstructions.js').then((m) => ({
|
|
30
|
+
default: m.NewPostTypeInstructions,
|
|
31
|
+
})));
|
|
32
|
+
export const PostSectionEditor = lazy(() => import('./post-editor/PostSectionEditor.js').then((m) => ({ default: m.PostSectionEditor })));
|
|
33
|
+
export const PostTemplateEditor = lazy(() => import('./post-editor/PostTemplateEditor.js').then((m) => ({ default: m.PostTemplateEditor })));
|
|
34
|
+
export function ViewFallback() {
|
|
35
|
+
return (_jsxs("div", { className: "p-6", "aria-busy": "true", "aria-live": "polite", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: "Loading" }), _jsx(Skeleton, { className: "mt-4" })] }));
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=lazy-views.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy-views.js","sourceRoot":"","sources":["../../src/views/lazy-views.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO deep-link table. Kept out of `SEO.tsx` so the admin shell can route
|
|
3
|
+
* these paths without loading the SEO view.
|
|
4
|
+
*/
|
|
5
|
+
declare const SEO_TAB_IDS: readonly ["overview", "content", "links", "technical", "ai-visibility", "redirects", "audit", "proposals"];
|
|
6
|
+
export type SeoTabId = (typeof SEO_TAB_IDS)[number];
|
|
7
|
+
/**
|
|
8
|
+
* SEO deep paths the admin router resolves onto the tabbed SEO view — including
|
|
9
|
+
* every path plugin-seo advertises in the sidebar. `matchRoute` is exact, so any
|
|
10
|
+
* of these NOT listed here falls through to the generic 404.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SEO_DEEP_PATHS: readonly ["/seo/link-health", "/seo/sitemap", "/seo/redirects", "/seo/content", "/seo/canonicals", "/seo/technical", "/seo/links", "/seo/audit", "/seo/proposals", "/seo/ai-visibility", "/seo"];
|
|
13
|
+
/** Map an SEO deep path to the tab it should open, or null if it isn't one. */
|
|
14
|
+
export declare function seoTabForPath(pathname: string): SeoTabId | null;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=seo-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-routes.d.ts","sourceRoot":"","sources":["../../../src/views/seo/seo-routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,QAAA,MAAM,WAAW,4GASP,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnD;;;;GAIG;AACH,eAAO,MAAM,cAAc,kMAYjB,CAAA;AAgBV,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAK/D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO deep-link table. Kept out of `SEO.tsx` so the admin shell can route
|
|
3
|
+
* these paths without loading the SEO view.
|
|
4
|
+
*/
|
|
5
|
+
const SEO_TAB_IDS = [
|
|
6
|
+
'overview',
|
|
7
|
+
'content',
|
|
8
|
+
'links',
|
|
9
|
+
'technical',
|
|
10
|
+
'ai-visibility',
|
|
11
|
+
'redirects',
|
|
12
|
+
'audit',
|
|
13
|
+
'proposals',
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* SEO deep paths the admin router resolves onto the tabbed SEO view — including
|
|
17
|
+
* every path plugin-seo advertises in the sidebar. `matchRoute` is exact, so any
|
|
18
|
+
* of these NOT listed here falls through to the generic 404.
|
|
19
|
+
*/
|
|
20
|
+
export const SEO_DEEP_PATHS = [
|
|
21
|
+
'/seo/link-health',
|
|
22
|
+
'/seo/sitemap',
|
|
23
|
+
'/seo/redirects',
|
|
24
|
+
'/seo/content',
|
|
25
|
+
'/seo/canonicals',
|
|
26
|
+
'/seo/technical',
|
|
27
|
+
'/seo/links',
|
|
28
|
+
'/seo/audit',
|
|
29
|
+
'/seo/proposals',
|
|
30
|
+
'/seo/ai-visibility',
|
|
31
|
+
'/seo',
|
|
32
|
+
];
|
|
33
|
+
const SEO_TAB_BY_PATH = {
|
|
34
|
+
'/seo/link-health': 'links',
|
|
35
|
+
'/seo/sitemap': 'technical',
|
|
36
|
+
'/seo/redirects': 'redirects',
|
|
37
|
+
'/seo/content': 'content',
|
|
38
|
+
'/seo/canonicals': 'content',
|
|
39
|
+
'/seo/technical': 'technical',
|
|
40
|
+
'/seo/links': 'technical',
|
|
41
|
+
'/seo/audit': 'audit',
|
|
42
|
+
'/seo/proposals': 'proposals',
|
|
43
|
+
'/seo/ai-visibility': 'ai-visibility',
|
|
44
|
+
'/seo': 'overview',
|
|
45
|
+
};
|
|
46
|
+
/** Map an SEO deep path to the tab it should open, or null if it isn't one. */
|
|
47
|
+
export function seoTabForPath(pathname) {
|
|
48
|
+
if (Object.prototype.hasOwnProperty.call(SEO_TAB_BY_PATH, pathname)) {
|
|
49
|
+
return SEO_TAB_BY_PATH[pathname];
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=seo-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-routes.js","sourceRoot":"","sources":["../../../src/views/seo/seo-routes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,GAAG;IAClB,UAAU;IACV,SAAS;IACT,OAAO;IACP,WAAW;IACX,eAAe;IACf,WAAW;IACX,OAAO;IACP,WAAW;CACH,CAAA;AAIV;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,kBAAkB;IAClB,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,iBAAiB;IACjB,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,gBAAgB;IAChB,oBAAoB;IACpB,MAAM;CACE,CAAA;AAEV,MAAM,eAAe,GAAsD;IACzE,kBAAkB,EAAE,OAAO;IAC3B,cAAc,EAAE,WAAW;IAC3B,gBAAgB,EAAE,WAAW;IAC7B,cAAc,EAAE,SAAS;IACzB,iBAAiB,EAAE,SAAS;IAC5B,gBAAgB,EAAE,WAAW;IAC7B,YAAY,EAAE,WAAW;IACzB,YAAY,EAAE,OAAO;IACrB,gBAAgB,EAAE,WAAW;IAC7B,oBAAoB,EAAE,eAAe;IACrC,MAAM,EAAE,UAAU;CACnB,CAAA;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,EAAE,CAAC;QACpE,OAAO,eAAe,CAAC,QAAwC,CAAC,CAAA;IAClE,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type AuditExportFormat = 'csv' | 'json';
|
|
2
|
+
export declare function auditExportPath(options: {
|
|
3
|
+
format: AuditExportFormat;
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
}): string;
|
|
7
|
+
/** Admin download of the audit log as CSV or JSON, optionally limited to a date range. */
|
|
8
|
+
export declare function AuditLogExportCard(): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=AuditLogExportCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditLogExportCard.d.ts","sourceRoot":"","sources":["../../../src/views/settings/AuditLogExportCard.tsx"],"names":[],"mappings":"AASA,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM,CAAA;AAE9C,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,MAAM,EAAE,iBAAiB,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX,GAAG,MAAM,CAKT;AAyBD,0FAA0F;AAC1F,wBAAgB,kBAAkB,4CAoFjC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Download } from 'lucide-react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { Button } from '../../components/ui/Button.js';
|
|
7
|
+
import { getApiBase } from '../../lib/api.js';
|
|
8
|
+
import { SecurityCard } from './securityPrimitives.js';
|
|
9
|
+
export function auditExportPath(options) {
|
|
10
|
+
const params = new URLSearchParams({ format: options.format });
|
|
11
|
+
if (options.from)
|
|
12
|
+
params.set('from', options.from);
|
|
13
|
+
if (options.to)
|
|
14
|
+
params.set('to', options.to);
|
|
15
|
+
return `/security/audit-log/export?${params.toString()}`;
|
|
16
|
+
}
|
|
17
|
+
async function downloadAuditExport(path) {
|
|
18
|
+
let res;
|
|
19
|
+
try {
|
|
20
|
+
res = await fetch(`${getApiBase()}${path}`, { credentials: 'include' });
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return { error: err instanceof Error ? err.message : 'Network error' };
|
|
24
|
+
}
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const body = (await res.json().catch(() => ({})));
|
|
27
|
+
return { error: body.error || `Export failed (${res.status})` };
|
|
28
|
+
}
|
|
29
|
+
const blob = await res.blob();
|
|
30
|
+
const disposition = res.headers.get('content-disposition') ?? '';
|
|
31
|
+
const filename = /filename="([^"]+)"/.exec(disposition)?.[1] ?? 'audit-log.csv';
|
|
32
|
+
const url = URL.createObjectURL(blob);
|
|
33
|
+
const link = document.createElement('a');
|
|
34
|
+
link.href = url;
|
|
35
|
+
link.download = filename;
|
|
36
|
+
link.click();
|
|
37
|
+
URL.revokeObjectURL(url);
|
|
38
|
+
return { truncated: res.headers.get('x-audit-export-truncated') === 'true' };
|
|
39
|
+
}
|
|
40
|
+
/** Admin download of the audit log as CSV or JSON, optionally limited to a date range. */
|
|
41
|
+
export function AuditLogExportCard() {
|
|
42
|
+
const [from, setFrom] = useState('');
|
|
43
|
+
const [to, setTo] = useState('');
|
|
44
|
+
const [pending, setPending] = useState(null);
|
|
45
|
+
const [error, setError] = useState(null);
|
|
46
|
+
async function exportAs(format) {
|
|
47
|
+
setPending(format);
|
|
48
|
+
setError(null);
|
|
49
|
+
const result = await downloadAuditExport(auditExportPath({ format, from, to }));
|
|
50
|
+
setPending(null);
|
|
51
|
+
if (result.error) {
|
|
52
|
+
setError(result.error);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (result.truncated) {
|
|
56
|
+
toast.success('Export downloaded. It was capped at the newest 10,000 events.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
toast.success('Audit log exported');
|
|
60
|
+
}
|
|
61
|
+
return (_jsx(SecurityCard, { title: "Audit log export", description: "Download recorded security and content events. A date range limits the file. The newest 10,000 matching events are included.", children: _jsxs("form", { className: "flex flex-col gap-4", onSubmit: (event) => {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
void exportAs('csv');
|
|
64
|
+
}, children: [_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row", children: [_jsxs("label", { className: "flex min-w-0 flex-1 flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "text-foreground font-medium", children: "From" }), _jsx("input", { type: "date", value: from, onChange: (event) => setFrom(event.target.value), className: "border-border bg-input-background text-foreground focus-visible:ring-ring min-h-11 rounded-md border px-3 text-sm focus-visible:ring-2 focus-visible:outline-none" })] }), _jsxs("label", { className: "flex min-w-0 flex-1 flex-col gap-1.5 text-sm", children: [_jsx("span", { className: "text-foreground font-medium", children: "To" }), _jsx("input", { type: "date", value: to, onChange: (event) => setTo(event.target.value), className: "border-border bg-input-background text-foreground focus-visible:ring-ring min-h-11 rounded-md border px-3 text-sm focus-visible:ring-2 focus-visible:outline-none" })] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { type: "submit", className: "min-h-11", loading: pending === 'csv', disabled: pending !== null, leftIcon: _jsx(Download, { size: 16, "aria-hidden": true }), children: "Export CSV" }), _jsx(Button, { type: "button", variant: "outline", className: "min-h-11", loading: pending === 'json', disabled: pending !== null, leftIcon: _jsx(Download, { size: 16, "aria-hidden": true }), onClick: () => void exportAs('json'), children: "Export JSON" })] }), error && (_jsx("p", { className: "text-destructive text-sm", role: "alert", children: error }))] }) }));
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=AuditLogExportCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditLogExportCard.js","sourceRoot":"","sources":["../../../src/views/settings/AuditLogExportCard.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityTab.d.ts","sourceRoot":"","sources":["../../../src/views/settings/SecurityTab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SecurityTab.d.ts","sourceRoot":"","sources":["../../../src/views/settings/SecurityTab.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,UAAU,GACX,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,2CAuJA"}
|
|
@@ -6,6 +6,7 @@ import { parseRouteQuery } from '../../router/index.js';
|
|
|
6
6
|
import { ConfirmDangerousSettingDialog, SettingsSaveBar, SettingsValidationSummary, } from './components.js';
|
|
7
7
|
import { AccessProtectionCard } from './AccessProtectionCard.js';
|
|
8
8
|
import { AuthMfaCard } from './AuthMfaCard.js';
|
|
9
|
+
import { AuditLogExportCard } from './AuditLogExportCard.js';
|
|
9
10
|
import { SecretsAuditCard } from './SecretsAuditCard.js';
|
|
10
11
|
import { SecurityStatusCard } from './SecurityStatusCard.js';
|
|
11
12
|
import { CaptchaStatusCard } from './CaptchaStatusCard.js';
|
|
@@ -38,6 +39,6 @@ export function SecurityTab({ role, onNavigate, }) {
|
|
|
38
39
|
? 'Ask an administrator to review the Security tab.'
|
|
39
40
|
: (loadError ?? 'The security service could not be reached. Try again shortly.') })] })] }));
|
|
40
41
|
}
|
|
41
|
-
return (_jsxs("div", { className: "space-y-4 pb-4", children: [_jsx(SecurityStatusCard, { status: data.status, onNavigate: onNavigate }), _jsx(CaptchaStatusCard, {}), issues.length > 0 && _jsx(SettingsValidationSummary, { issues: issues }), !canEdit && (_jsx("p", { className: "text-muted-foreground text-sm", children: "You have read-only access. Only administrators can change security settings." })), _jsx(AuthMfaCard, { form: form, setField: setField, canEdit: canEdit, viewerHasMfa: data.viewerHasMfa, withMfa: data.users.withMfa, totalAdmins: data.users.totalAdmins, onNavigate: onNavigate }), _jsx("div", { id: "security-sessions", children: _jsx(SessionPolicyCard, { form: form, setField: setField, canEdit: canEdit, activeSessions: data.activeSessions, revokeState: revokeState, onRevokeAll: () => void revokeAllOtherSessions() }) }), _jsx("div", { id: "security-ip-allowlist", children: _jsx(AccessProtectionCard, { form: form, setField: setField, canEdit: canEdit, ipAllowlist: data.ipAllowlist, secrets: data.secrets, csrfEnabled: data.csrfEnabled, uploadValidation: data.uploadValidation, sources: data.sources, onNavigate: onNavigate }) }),
|
|
42
|
+
return (_jsxs("div", { className: "space-y-4 pb-4", children: [_jsx(SecurityStatusCard, { status: data.status, onNavigate: onNavigate }), _jsx(CaptchaStatusCard, {}), issues.length > 0 && _jsx(SettingsValidationSummary, { issues: issues }), !canEdit && (_jsx("p", { className: "text-muted-foreground text-sm", children: "You have read-only access. Only administrators can change security settings." })), _jsx(AuthMfaCard, { form: form, setField: setField, canEdit: canEdit, viewerHasMfa: data.viewerHasMfa, withMfa: data.users.withMfa, totalAdmins: data.users.totalAdmins, onNavigate: onNavigate }), _jsx("div", { id: "security-sessions", children: _jsx(SessionPolicyCard, { form: form, setField: setField, canEdit: canEdit, activeSessions: data.activeSessions, revokeState: revokeState, onRevokeAll: () => void revokeAllOtherSessions() }) }), _jsx("div", { id: "security-ip-allowlist", children: _jsx(AccessProtectionCard, { form: form, setField: setField, canEdit: canEdit, ipAllowlist: data.ipAllowlist, secrets: data.secrets, csrfEnabled: data.csrfEnabled, uploadValidation: data.uploadValidation, sources: data.sources, onNavigate: onNavigate }) }), _jsxs("div", { id: "security-audit", className: "space-y-4", children: [canEdit && _jsx(AuditLogExportCard, {}), _jsx(SecretsAuditCard, { secrets: data.secrets, auditEnabled: data.auditEnabled, failedLogins24h: data.failedLogins24h, users: data.users, apiKeys: data.apiKeys, sources: data.sources, onNavigate: onNavigate })] }), saveState === 'error' && (_jsx("p", { className: "text-destructive text-sm", role: "alert", children: "Failed to save security settings. Please try again." })), _jsx(ConfirmDangerousSettingDialog, { open: lockoutPrompt !== null, title: "This allowlist may lock you out", description: lockoutPrompt ?? 'Your current IP is not covered by the allowlist you are about to save.', confirmLabel: "Save anyway", onConfirm: () => void save({ confirmLockout: true }), onCancel: dismissLockoutPrompt }), _jsx(SettingsSaveBar, { dirty: dirty, canEdit: canEdit, hasErrors: hasErrors, saveState: saveState, onSave: () => void save(), onReset: reset })] }));
|
|
42
43
|
}
|
|
43
44
|
//# sourceMappingURL=SecurityTab.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityTab.js","sourceRoot":"","sources":["../../../src/views/settings/SecurityTab.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"SecurityTab.js","sourceRoot":"","sources":["../../../src/views/settings/SecurityTab.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prenta/admin",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.16",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -99,16 +99,16 @@
|
|
|
99
99
|
"tailwindcss": "^4.0.0",
|
|
100
100
|
"typescript": "^5.7.0",
|
|
101
101
|
"vitest": "^4.1.0",
|
|
102
|
-
"@prenta/
|
|
102
|
+
"@prenta/component-blocks": "0.3.1",
|
|
103
|
+
"@prenta/core": "7.6.4",
|
|
103
104
|
"@prenta/plugin-ai": "3.0.1",
|
|
104
105
|
"@prenta/plugin-blocks": "0.2.1",
|
|
105
106
|
"@prenta/plugin-commerce": "0.2.3",
|
|
106
|
-
"@prenta/
|
|
107
|
+
"@prenta/plugin-forms": "0.1.1",
|
|
107
108
|
"@prenta/plugin-media": "0.2.1",
|
|
108
109
|
"@prenta/plugin-navigation": "0.3.1",
|
|
109
110
|
"@prenta/plugin-seo": "1.0.1",
|
|
110
|
-
"@prenta/
|
|
111
|
-
"@prenta/sections-react": "0.17.56"
|
|
111
|
+
"@prenta/sections-react": "0.17.61"
|
|
112
112
|
},
|
|
113
113
|
"scripts": {
|
|
114
114
|
"build": "tsc --project tsconfig.json && npx @tailwindcss/cli -i src/styles/build-input.css -o dist/prenta-admin.css --minify",
|
package/src/AdminRoot.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState, useMemo, useEffect, useRef, useCallback } from 'react'
|
|
3
|
+
import { useState, useMemo, useEffect, useRef, useCallback, Suspense } from 'react'
|
|
4
4
|
import * as Dialog from '@radix-ui/react-dialog'
|
|
5
5
|
import { toast } from 'sonner'
|
|
6
6
|
import { Layout } from './layout/Layout.js'
|
|
@@ -14,22 +14,38 @@ import {
|
|
|
14
14
|
resetCustomSectionTypes,
|
|
15
15
|
resolveCustomSectionTypes,
|
|
16
16
|
registerSectionTemplates,
|
|
17
|
+
registerSectionPathTemplates,
|
|
17
18
|
resetSectionTemplates,
|
|
18
19
|
type CustomSectionTypeInput,
|
|
19
20
|
} from '@prenta/core/page-sections'
|
|
20
21
|
import { Dashboard } from './views/Dashboard.js'
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
import { SEO_DEEP_PATHS, seoTabForPath } from './views/seo/seo-routes.js'
|
|
23
|
+
import {
|
|
24
|
+
CollectionList,
|
|
25
|
+
DocumentEdit,
|
|
26
|
+
FormDeliveries,
|
|
27
|
+
FormEditor,
|
|
28
|
+
FormEntries,
|
|
29
|
+
FormSubmissions,
|
|
30
|
+
Forms,
|
|
31
|
+
MediaBrowser,
|
|
32
|
+
NewPostPicker,
|
|
33
|
+
NewPostTypeInstructions,
|
|
34
|
+
PageSectionEditor,
|
|
35
|
+
PageTagManager,
|
|
36
|
+
PagesListView,
|
|
37
|
+
PostSectionEditor,
|
|
38
|
+
PostTemplateEditor,
|
|
39
|
+
PostTypeEditor,
|
|
40
|
+
PostTypesView,
|
|
41
|
+
PostsListView,
|
|
42
|
+
Profile,
|
|
43
|
+
SEO,
|
|
44
|
+
ScriptTags,
|
|
45
|
+
Settings,
|
|
46
|
+
Trash,
|
|
47
|
+
ViewFallback,
|
|
48
|
+
} from './views/lazy-views.js'
|
|
33
49
|
import { SetupWizard } from './views/SetupWizard.js'
|
|
34
50
|
import { Login } from './views/Login.js'
|
|
35
51
|
import { HiddenLoginNotFound } from './views/HiddenLoginNotFound.js'
|
|
@@ -44,23 +60,12 @@ import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts.js'
|
|
|
44
60
|
import { useAppearanceBootstrap } from './hooks/useAppearanceBootstrap.js'
|
|
45
61
|
import { useIdleLogout } from './hooks/useIdleLogout.js'
|
|
46
62
|
import { resolveLogoutRedirect } from './lib/logout-redirect.js'
|
|
47
|
-
import { PageSectionEditor } from './views/page-editor/PageSectionEditor.js'
|
|
48
63
|
import {
|
|
49
64
|
AdminSectionRenderersProvider,
|
|
50
65
|
type AdminSectionRendererMap,
|
|
51
66
|
} from './views/page-editor/sections/index.js'
|
|
52
67
|
import { ThemeChromeProvider } from './views/page-editor/theme-chrome/index.js'
|
|
53
68
|
import type { AdminThemeChrome } from './lib/theme-chrome.js'
|
|
54
|
-
import { Trash } from './views/Trash.js'
|
|
55
|
-
import { PagesListView } from './views/Pages/PagesListView.js'
|
|
56
|
-
import { PageTagManager } from './views/Pages/PageTagManager.js'
|
|
57
|
-
import { PostsListView } from './views/Posts/PostsListView.js'
|
|
58
|
-
import { PostTypesView } from './views/Posts/PostTypesView.js'
|
|
59
|
-
import { PostTypeEditor } from './views/Posts/PostTypeEditor.js'
|
|
60
|
-
import { NewPostPicker } from './views/Posts/NewPostPicker.js'
|
|
61
|
-
import { NewPostTypeInstructions } from './views/Posts/NewPostTypeInstructions.js'
|
|
62
|
-
import { PostSectionEditor } from './views/post-editor/PostSectionEditor.js'
|
|
63
|
-
import { PostTemplateEditor } from './views/post-editor/PostTemplateEditor.js'
|
|
64
69
|
|
|
65
70
|
export interface AdminRootProps {
|
|
66
71
|
config: any
|
|
@@ -272,6 +277,7 @@ function AdminShell({
|
|
|
272
277
|
registerSectionTypes(resolveCustomSectionTypes(config?.sections))
|
|
273
278
|
resetSectionTemplates()
|
|
274
279
|
registerSectionTemplates(config?.sections?.templates)
|
|
280
|
+
registerSectionPathTemplates(config?.sections?.pathTemplates)
|
|
275
281
|
|
|
276
282
|
let cancelled = false
|
|
277
283
|
void cmsApi<CustomSectionTypeInput[]>('/page-sections/types').then((res) => {
|
|
@@ -709,7 +715,9 @@ function AdminShell({
|
|
|
709
715
|
onNavigate={navigate}
|
|
710
716
|
onLogout={handleLogout}
|
|
711
717
|
>
|
|
712
|
-
<ErrorBoundary>
|
|
718
|
+
<ErrorBoundary>
|
|
719
|
+
<Suspense fallback={<ViewFallback />}>{renderView()}</Suspense>
|
|
720
|
+
</ErrorBoundary>
|
|
713
721
|
<ShortcutHelp open={shortcutHelpOpen} onClose={() => setShortcutHelpOpen(false)} />
|
|
714
722
|
</Layout>
|
|
715
723
|
)
|
|
@@ -69,6 +69,11 @@ describe('fetchPagesDataset', () => {
|
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
const ds = await fetchPagesDataset()
|
|
72
|
+
const pagesCall = cmsApi.mock.calls.find((call) =>
|
|
73
|
+
String(call[0]).startsWith('/collections/pages'),
|
|
74
|
+
)
|
|
75
|
+
expect(String(pagesCall?.[0])).toContain('pick=title')
|
|
76
|
+
expect(String(pagesCall?.[0])).not.toContain('sections')
|
|
72
77
|
expect(ds.pages).toHaveLength(3)
|
|
73
78
|
expect(ds.tags.find((t) => t.slug === 'a')?.pageCount).toBe(2)
|
|
74
79
|
expect(ds.tags.find((t) => t.slug === 'b')?.pageCount).toBe(1)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { cleanup, render, screen } from '@testing-library/react'
|
|
4
|
+
|
|
5
|
+
vi.mock('../../lib/api.js', () => ({
|
|
6
|
+
cmsApi: vi.fn(async () => ({ data: { docs: [] }, status: 200 })),
|
|
7
|
+
ensureCsrfToken: vi.fn(async () => undefined),
|
|
8
|
+
setApiBase: vi.fn(),
|
|
9
|
+
getApiBase: () => '',
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const storage = new Map<string, string>()
|
|
13
|
+
vi.stubGlobal('localStorage', {
|
|
14
|
+
getItem: (key: string) => storage.get(key) ?? null,
|
|
15
|
+
setItem: (key: string, value: string) => void storage.set(key, String(value)),
|
|
16
|
+
removeItem: (key: string) => void storage.delete(key),
|
|
17
|
+
clear: () => storage.clear(),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const { AdminRoot } = await import('../../AdminRoot.js')
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
cleanup()
|
|
24
|
+
window.history.replaceState({}, '', '/')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('AdminRoot lazy routes', () => {
|
|
28
|
+
it('loads the trash view through the route split', async () => {
|
|
29
|
+
window.history.replaceState({}, '', '/admin/trash')
|
|
30
|
+
render(
|
|
31
|
+
<AdminRoot
|
|
32
|
+
config={{}}
|
|
33
|
+
session={{ user: { id: 'u1', email: 'admin@example.com', role: 'ADMIN' } }}
|
|
34
|
+
initialPath="/trash"
|
|
35
|
+
/>,
|
|
36
|
+
)
|
|
37
|
+
expect(await screen.findByText('Trash is empty')).toBeTruthy()
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
const srcRoot = fileURLToPath(new URL('../../', import.meta.url))
|
|
7
|
+
|
|
8
|
+
const HEAVY_VIEWS = [
|
|
9
|
+
'views/page-editor/PageSectionEditor.tsx',
|
|
10
|
+
'views/post-editor/PostSectionEditor.tsx',
|
|
11
|
+
'views/post-editor/PostTemplateEditor.tsx',
|
|
12
|
+
'views/Settings.tsx',
|
|
13
|
+
'views/SEO.tsx',
|
|
14
|
+
'views/MediaBrowser.tsx',
|
|
15
|
+
'views/DocumentEdit.tsx',
|
|
16
|
+
'views/Forms.tsx',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
function resolveSpecifier(fromFile: string, specifier: string): string | undefined {
|
|
20
|
+
const base = resolve(dirname(fromFile), specifier)
|
|
21
|
+
const candidates = [
|
|
22
|
+
base,
|
|
23
|
+
base.replace(/\.js$/, '.tsx'),
|
|
24
|
+
base.replace(/\.js$/, '.ts'),
|
|
25
|
+
`${base}.tsx`,
|
|
26
|
+
`${base}.ts`,
|
|
27
|
+
join(base, 'index.tsx'),
|
|
28
|
+
join(base, 'index.ts'),
|
|
29
|
+
]
|
|
30
|
+
return candidates.find((candidate) => existsSync(candidate))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Static import graph. Dynamic `import()` edges are stripped first. */
|
|
34
|
+
function staticGraph(entry: string): Set<string> {
|
|
35
|
+
const seen = new Set<string>()
|
|
36
|
+
const stack = [entry]
|
|
37
|
+
while (stack.length > 0) {
|
|
38
|
+
const file = stack.pop()
|
|
39
|
+
if (!file || seen.has(file)) continue
|
|
40
|
+
seen.add(file)
|
|
41
|
+
const text = readFileSync(file, 'utf8').replace(/import\s*\(\s*['"][^'"]+['"]\s*\)/g, '')
|
|
42
|
+
for (const match of text.matchAll(/from ['"](\.[^'"]+)['"]/g)) {
|
|
43
|
+
const specifier = match[1]
|
|
44
|
+
if (!specifier) continue
|
|
45
|
+
const next = resolveSpecifier(file, specifier)
|
|
46
|
+
if (next) stack.push(next)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return seen
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('admin route-level code splitting', () => {
|
|
53
|
+
it('keeps heavy views out of the AdminRoot static import graph', () => {
|
|
54
|
+
const graph = staticGraph(join(srcRoot, 'AdminRoot.tsx'))
|
|
55
|
+
const present = HEAVY_VIEWS.filter((view) => graph.has(join(srcRoot, view)))
|
|
56
|
+
expect(present).toEqual([])
|
|
57
|
+
|
|
58
|
+
const bytes = HEAVY_VIEWS.reduce((total, view) => total + statSync(join(srcRoot, view)).size, 0)
|
|
59
|
+
// The shell no longer statically imports these modules. Their source is
|
|
60
|
+
// the chunk the app bundler can defer. This floor fails if the split is
|
|
61
|
+
// reverted and the files are pulled back into the entry graph above.
|
|
62
|
+
expect(bytes).toBeGreaterThan(40_000)
|
|
63
|
+
|
|
64
|
+
const lazySource = readFileSync(join(srcRoot, 'views/lazy-views.tsx'), 'utf8')
|
|
65
|
+
expect(lazySource).toContain("import('./page-editor/PageSectionEditor.js')")
|
|
66
|
+
expect(lazySource).toContain("import('./SEO.js')")
|
|
67
|
+
expect(lazySource).toContain("import('./Settings.js')")
|
|
68
|
+
expect(lazySource).toContain("import('./MediaBrowser.js')")
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { auditExportPath } from '../../views/settings/AuditLogExportCard.js'
|
|
4
|
+
|
|
5
|
+
describe('auditExportPath', () => {
|
|
6
|
+
it('omits empty dates and keeps the format', () => {
|
|
7
|
+
expect(auditExportPath({ format: 'csv', from: '', to: '' })).toBe(
|
|
8
|
+
'/security/audit-log/export?format=csv',
|
|
9
|
+
)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('includes a date range', () => {
|
|
13
|
+
expect(auditExportPath({ format: 'json', from: '2026-01-01', to: '2026-01-31' })).toBe(
|
|
14
|
+
'/security/audit-log/export?format=json&from=2026-01-01&to=2026-01-31',
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -96,4 +96,44 @@ describe('Trash', () => {
|
|
|
96
96
|
)
|
|
97
97
|
})
|
|
98
98
|
})
|
|
99
|
+
|
|
100
|
+
it('asks before permanently deleting a trashed document', async () => {
|
|
101
|
+
cmsApi.mockImplementation(async (endpoint: string, options?: { method?: string }) => {
|
|
102
|
+
if (options?.method === 'POST' && String(endpoint).includes('/purge')) {
|
|
103
|
+
return { data: { success: true }, status: 200 }
|
|
104
|
+
}
|
|
105
|
+
if (String(endpoint).includes('/collections/pages')) {
|
|
106
|
+
return {
|
|
107
|
+
data: {
|
|
108
|
+
docs: [
|
|
109
|
+
{
|
|
110
|
+
id: 'p1',
|
|
111
|
+
title: 'About',
|
|
112
|
+
slug: null,
|
|
113
|
+
deletedAt: '2026-08-01T00:00:00.000Z',
|
|
114
|
+
data: { slug: 'about' },
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
status: 200,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { data: { docs: [] }, status: 200 }
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
render(<Trash onNavigate={vi.fn()} />)
|
|
125
|
+
expect(await screen.findByText('About')).toBeTruthy()
|
|
126
|
+
fireEvent.click(screen.getByRole('button', { name: 'Delete About permanently' }))
|
|
127
|
+
expect(cmsApi).not.toHaveBeenCalledWith(expect.stringContaining('/purge'), expect.anything())
|
|
128
|
+
fireEvent.click(screen.getByRole('button', { name: 'Delete permanently' }))
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(cmsApi).toHaveBeenCalledWith(
|
|
131
|
+
'/collections/pages/p1/purge',
|
|
132
|
+
expect.objectContaining({ method: 'POST' }),
|
|
133
|
+
)
|
|
134
|
+
})
|
|
135
|
+
await waitFor(() => {
|
|
136
|
+
expect(screen.queryByText('About')).toBeNull()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
99
139
|
})
|