headroom-cms 0.1.9 → 0.1.11
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 +11 -6
- package/admin/assets/{AdminsPage-Bt_ekZen.js → AdminsPage-BnzH9TL3.js} +1 -1
- package/admin/assets/AllContentPage-BtObN6oy.js +1 -0
- package/admin/assets/{ApiKeysPage-BfWCxGhC.js → ApiKeysPage-DEAa8eyC.js} +1 -1
- package/admin/assets/AuditPage-BN9yNsxh.js +1 -0
- package/admin/assets/BlockEditor-3wnisTOZ.js +176 -0
- package/admin/assets/BlockEditor-CQpF8tYb.css +1 -0
- package/admin/assets/BlockTypeEditPage-C2evAESK.js +1 -0
- package/admin/assets/BlockTypesPage-Dhkho6T_.js +1 -0
- package/admin/assets/{BulkActionBar-TRiXXLQd.js → BulkActionBar-BxdfUSrN.js} +1 -1
- package/admin/assets/CollectionEditPage-lOb4hEZy.js +1 -0
- package/admin/assets/{CollectionsPage-ClplrxNn.js → CollectionsPage-CgtOloa1.js} +1 -1
- package/admin/assets/{ContentCreatePage-DfYcEH1u.js → ContentCreatePage-LeQjahp_.js} +1 -1
- package/admin/assets/ContentEditPage-xczr4d_h.js +1 -0
- package/admin/assets/ContentField-pilCbdnA.js +1 -0
- package/admin/assets/ContentListPage-BAKDn1Xy.js +1 -0
- package/admin/assets/CustomBlockPreview-DNnTFM0z.js +479 -0
- package/admin/assets/FieldRenderer-DiOKvkWV.js +2 -0
- package/admin/assets/FilterBar-BZoa63zh.js +1 -0
- package/admin/assets/FloatingComposerController-D4uLQfUX-BMIvFCoE.js +1 -0
- package/admin/assets/IconPicker-CpIgiQTC.js +3 -0
- package/admin/assets/{LoginPage-DutieANA.js → LoginPage-D9ZsGLIi.js} +1 -1
- package/admin/assets/MediaField-CxccCFGQ.js +1 -0
- package/admin/assets/MediaPage-QvMaH2YJ.js +1 -0
- package/admin/assets/Pagination-Df9nQ7Z0.js +1 -0
- package/admin/assets/RelationshipPicker-B3Ftmqxp.js +1 -0
- package/admin/assets/{SiteSettingsPage-BtCC3RKc.js → SiteSettingsPage-6NvH7CiQ.js} +1 -1
- package/admin/assets/{SiteUserEditPage-ClHmp0T-.js → SiteUserEditPage-D5VaQ1Xq.js} +1 -1
- package/admin/assets/SiteUsersPage-BYVduiqs.js +1 -0
- package/admin/assets/{SitesPage-Bw_WBN6v.js → SitesPage-rfWWE0yK.js} +1 -1
- package/admin/assets/{SubmissionDetailPage-DS08LGxd.js → SubmissionDetailPage-BSUR685F.js} +1 -1
- package/admin/assets/SubmissionEditPage-DjLXHjWU.js +1 -0
- package/admin/assets/SubmissionListPage-DBxNEvde.js +1 -0
- package/admin/assets/{TagInput-BILCaC9b.js → TagInput-57c4DG1w.js} +1 -1
- package/admin/assets/{TagsPage-DdeZokow.js → TagsPage-BEO5AwCv.js} +1 -1
- package/admin/assets/{UsersPage-B0vLxjrg.js → UsersPage-BpIRorJ1.js} +1 -1
- package/admin/assets/{WebhookEditPage-SlJE4d3z.js → WebhookEditPage-D5xgi56h.js} +1 -1
- package/admin/assets/{WebhooksPage-C6lGZLpr.js → WebhooksPage-BY7AaiGr.js} +1 -1
- package/admin/assets/{card-hXVtlM0q.js → card-C9hfyHXf.js} +1 -1
- package/admin/assets/checkbox-DVJcwUt1.js +1 -0
- package/admin/assets/{collapsible-B414SspL.js → collapsible-D3d29uJp.js} +1 -1
- package/admin/assets/{command-fvBFHye4.js → command-Bfmj0MEL.js} +1 -1
- package/admin/assets/contentStatus-CkPi9Dh6.js +1 -0
- package/admin/assets/{core.esm-B_kcYf6n.js → core.esm-DdQHdRkd.js} +2 -2
- package/admin/assets/index-BB9Syqw2.css +1 -0
- package/admin/assets/index-Ce5pmRMj.js +18 -0
- package/admin/assets/media-url-DdCoIedP.js +1 -0
- package/admin/assets/popover-CzaQYEEP.js +1 -0
- package/admin/assets/radix-C5ZmWuuL.js +51 -0
- package/admin/assets/select-CrRhFGIi.js +1 -0
- package/admin/assets/serializeToText-2VrsuRUh.js +2 -0
- package/admin/assets/{sortable.esm-QyXA6fio.js → sortable.esm-qVEMoaTg.js} +1 -1
- package/admin/assets/{table-DLoIbCQ5.js → table-_3bMY0_z.js} +1 -1
- package/admin/assets/{textarea-vSXNxwTe.js → textarea-6fq0R6VV.js} +1 -1
- package/admin/assets/useAdminResolver-BJNPz3OG.js +1 -0
- package/admin/assets/useContent-Bs7nel7C.js +1 -0
- package/admin/assets/useContentSearch-B3aTjuCu.js +1 -0
- package/admin/assets/{useMedia-e3sqWm_t.js → useMedia-ae3s_ajC.js} +1 -1
- package/admin/assets/usePageTitle-C1r1-C00.js +1 -0
- package/admin/assets/useSiteUsers-DIaqgNSp.js +1 -0
- package/admin/assets/{useTags-f7AVSLuj.js → useTags-B-HgMVwo.js} +1 -1
- package/admin/assets/{useWebhooks-BH_r8-Mo.js → useWebhooks-BvZjUJkJ.js} +1 -1
- package/admin/assets/yjs-tXBm_srz.js +5 -0
- package/admin/favicon-16x16.png +0 -0
- package/admin/favicon-32x32.png +0 -0
- package/admin/icons/icon-180x180.png +0 -0
- package/admin/icons/icon-192x192.png +0 -0
- package/admin/icons/icon-512x512.png +0 -0
- package/admin/icons/maskable-icon-512x512.png +0 -0
- package/admin/index.html +3 -3
- package/admin/sw.js +1 -1
- package/dist/admin-site.d.ts +16 -2
- package/dist/admin-site.d.ts.map +1 -1
- package/dist/admin-site.js +10 -3
- package/dist/admin-site.js.map +1 -1
- package/dist/api.d.ts +7 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +8 -1
- package/dist/api.js.map +1 -1
- package/dist/cdn-api.d.ts +25 -0
- package/dist/cdn-api.d.ts.map +1 -0
- package/dist/{cdn.js → cdn-api.js} +7 -139
- package/dist/cdn-api.js.map +1 -0
- package/dist/cdn-media.d.ts +26 -0
- package/dist/cdn-media.d.ts.map +1 -0
- package/dist/cdn-media.js +202 -0
- package/dist/cdn-media.js.map +1 -0
- package/dist/collaboration.d.ts +55 -0
- package/dist/collaboration.d.ts.map +1 -0
- package/dist/collaboration.js +141 -0
- package/dist/collaboration.js.map +1 -0
- package/dist/index.d.ts +27 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -12
- package/dist/index.js.map +1 -1
- package/dist/storage.d.ts +2 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +33 -0
- package/dist/storage.js.map +1 -1
- package/lambda/api/bootstrap +0 -0
- package/lambda/image-lambda/node_modules/.package-lock.json +3 -3
- package/lambda/image-lambda/node_modules/semver/README.md +19 -4
- package/lambda/image-lambda/node_modules/semver/bin/semver.js +14 -10
- package/lambda/image-lambda/node_modules/semver/functions/truncate.js +48 -0
- package/lambda/image-lambda/node_modules/semver/index.js +2 -0
- package/lambda/image-lambda/node_modules/semver/internal/re.js +1 -1
- package/lambda/image-lambda/node_modules/semver/package.json +3 -3
- package/lambda/image-lambda/node_modules/semver/range.bnf +5 -4
- package/lambda/webhook-worker/bootstrap +0 -0
- package/package.json +1 -1
- package/src/admin-site.ts +26 -5
- package/src/api.ts +15 -1
- package/src/{cdn.ts → cdn-api.ts} +8 -161
- package/src/cdn-media.ts +250 -0
- package/src/collaboration.ts +187 -0
- package/src/index.ts +77 -14
- package/src/sst-env.d.ts +28 -0
- package/src/storage.ts +35 -0
- package/admin/assets/AllContentPage-CFqEMAl9.js +0 -1
- package/admin/assets/AuditPage-BE0XIUl2.js +0 -1
- package/admin/assets/BlockEditor-6wqsThJ7.js +0 -179
- package/admin/assets/BlockEditor-Cp_wZ2xN.css +0 -1
- package/admin/assets/BlockTypeEditPage-CuNJfZw0.js +0 -1
- package/admin/assets/BlockTypesPage-BIMBVxBs.js +0 -1
- package/admin/assets/CollectionEditPage-BqX_0cC2.js +0 -1
- package/admin/assets/ContentEditPage-D3Rvlktk.js +0 -2
- package/admin/assets/ContentListPage-zmO8Is4d.js +0 -1
- package/admin/assets/CustomBlockPreview-C6HqS4xv.js +0 -479
- package/admin/assets/FieldBuilder-36tfpSyM.js +0 -3
- package/admin/assets/FilterBar-DhRwTqFv.js +0 -1
- package/admin/assets/MediaField-J2TLG_fu.js +0 -1
- package/admin/assets/MediaPage-DZZKMGF4.js +0 -1
- package/admin/assets/RelationshipPicker-CDFs4TMW.js +0 -1
- package/admin/assets/SiteUsersPage-AyJvcVM7.js +0 -1
- package/admin/assets/SubmissionEditPage-Brf-DK2X.js +0 -1
- package/admin/assets/SubmissionListPage-DNMzQZHS.js +0 -1
- package/admin/assets/checkbox-WGrS3sUr.js +0 -1
- package/admin/assets/contentStatus-BmaiYVOm.js +0 -1
- package/admin/assets/index-Cir9tY_P.js +0 -18
- package/admin/assets/index-DACBYsKM.css +0 -1
- package/admin/assets/media-url-DIg_vSyf.js +0 -1
- package/admin/assets/popover-D5_HjjUC.js +0 -1
- package/admin/assets/radix-C1kb_NqW.js +0 -51
- package/admin/assets/select-_uJYxzeZ.js +0 -1
- package/admin/assets/serializeToText-DR_WnxiI.js +0 -2
- package/admin/assets/useAdminResolver-D-LlmquD.js +0 -1
- package/admin/assets/useContent-e8beBIuq.js +0 -1
- package/admin/assets/useContentSearch-DOjveB9t.js +0 -1
- package/admin/assets/useDebouncedValue-C-cQUcLG.js +0 -1
- package/admin/assets/usePageTitle-BNSba9_L.js +0 -1
- package/admin/assets/useSiteUsers-BdnvuM2E.js +0 -1
- package/dist/cdn.d.ts +0 -27
- package/dist/cdn.d.ts.map +0 -1
- package/dist/cdn.js.map +0 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collaboration Infrastructure
|
|
3
|
+
*
|
|
4
|
+
* API Gateway WebSocket API + DynamoDB Collab table + Node.js Lambda handler.
|
|
5
|
+
* Deployed at all times but dormant for solo editing — no Lambda invocations
|
|
6
|
+
* and no Collab table writes happen until collaboration is opted into.
|
|
7
|
+
*
|
|
8
|
+
* Supports dev mode (Node.js source with live reload) and package mode
|
|
9
|
+
* (pre-bundled handler).
|
|
10
|
+
*
|
|
11
|
+
* Split into two helpers:
|
|
12
|
+
* - `createCollabTable` — provisions just the DynamoDB Collab table.
|
|
13
|
+
* Must be called BEFORE `createApi`, because the Go API links the
|
|
14
|
+
* Collab table for ticket reads/writes.
|
|
15
|
+
* - `createCollabHandler` — provisions the WebSocket API + Lambda and
|
|
16
|
+
* wires `API_URL` into the Lambda environment so `snapshotToDraft`
|
|
17
|
+
* can `fetch` back into the Go API on `$disconnect`. Must be called
|
|
18
|
+
* AFTER `createApi`, because it needs `api.api.url`.
|
|
19
|
+
*
|
|
20
|
+
* The two halves are merged into a single `CollaborationResources` value
|
|
21
|
+
* by `index.ts` so downstream consumers (admin-site, etc.) see one shape.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import path from "path";
|
|
25
|
+
import type { StorageResources } from "./storage.js";
|
|
26
|
+
import type { AuthResources } from "./auth.js";
|
|
27
|
+
import type { ApiResources } from "./api.js";
|
|
28
|
+
|
|
29
|
+
export interface CollabTableArgs {
|
|
30
|
+
// No external dependencies — the table is a leaf resource.
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createCollabTable(name: string, _args: CollabTableArgs = {}) {
|
|
34
|
+
// DynamoDB table for collaboration sessions + YJS state
|
|
35
|
+
const collabTable = new sst.aws.Dynamo(`${name}Collab`, {
|
|
36
|
+
fields: {
|
|
37
|
+
pk: "string",
|
|
38
|
+
sk: "string",
|
|
39
|
+
},
|
|
40
|
+
primaryIndex: { hashKey: "pk", rangeKey: "sk" },
|
|
41
|
+
// PascalCase to match the convention used throughout the collab TS
|
|
42
|
+
// package (RoomManager, YjsPersistence) and the Go ticket writer
|
|
43
|
+
// (repository/collab.go). DynamoDB attribute names are case-sensitive;
|
|
44
|
+
// the TTL attribute name must match what writers actually emit.
|
|
45
|
+
ttl: "ExpiresAt",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return { collabTable };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type CollabTableResources = ReturnType<typeof createCollabTable>;
|
|
52
|
+
|
|
53
|
+
export interface CollabHandlerArgs {
|
|
54
|
+
storage: StorageResources;
|
|
55
|
+
auth: AuthResources;
|
|
56
|
+
api: ApiResources;
|
|
57
|
+
collabTable: CollabTableResources["collabTable"];
|
|
58
|
+
pkgRoot: string;
|
|
59
|
+
dev?: {
|
|
60
|
+
/** SST handler string, e.g. "packages/collab/src/index.handler" */
|
|
61
|
+
handler: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createCollabHandler(name: string, args: CollabHandlerArgs) {
|
|
66
|
+
const { storage, auth, api, collabTable } = args;
|
|
67
|
+
|
|
68
|
+
// WebSocket API via raw Pulumi (SST v4 doesn't have a WebSocket component)
|
|
69
|
+
const wsApi = new aws.apigatewayv2.Api(`${name}WsApi`, {
|
|
70
|
+
protocolType: "WEBSOCKET",
|
|
71
|
+
routeSelectionExpression: "$request.body.action",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const handlerConfig = args.dev
|
|
75
|
+
? {
|
|
76
|
+
handler: args.dev.handler,
|
|
77
|
+
runtime: "nodejs22.x" as const,
|
|
78
|
+
}
|
|
79
|
+
: {
|
|
80
|
+
bundle: path.join(args.pkgRoot, "packages/collab"),
|
|
81
|
+
handler: "index.handler",
|
|
82
|
+
runtime: "nodejs22.x" as const,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const wsHandler = new sst.aws.Function(`${name}CollabHandler`, {
|
|
86
|
+
...handlerConfig,
|
|
87
|
+
// jsdom is pulled in transitively via @blocknote/server-util (used in
|
|
88
|
+
// src/yjs/converter.ts to convert YJS state to BlockNote blocks during
|
|
89
|
+
// the disconnect snapshot). esbuild does not auto-bundle jsdom's
|
|
90
|
+
// xhr-sync-worker.js because it's spawned dynamically at runtime
|
|
91
|
+
// (`new Worker(__dirname + "/xhr-sync-worker.js")`), so the worker file
|
|
92
|
+
// is missing on disk in the deployed Lambda and cold-starts fail with
|
|
93
|
+
// "Cannot find module './xhr-sync-worker.js'". Adding jsdom to
|
|
94
|
+
// `nodejs.install` excludes it from the esbuild bundle and instead
|
|
95
|
+
// installs it (with its full directory tree, including the worker file)
|
|
96
|
+
// into the function's node_modules/ at deploy time.
|
|
97
|
+
nodejs: {
|
|
98
|
+
install: ["jsdom"],
|
|
99
|
+
},
|
|
100
|
+
timeout: "30 seconds",
|
|
101
|
+
memory: "512 MB",
|
|
102
|
+
environment: {
|
|
103
|
+
COLLAB_TABLE: collabTable.name,
|
|
104
|
+
DRAFT_CONTENT_TABLE: storage.draftContent.name,
|
|
105
|
+
BLOCKS_TABLE: storage.blocks.name,
|
|
106
|
+
SITES_TABLE: storage.sites.name,
|
|
107
|
+
USER_POOL_ID: auth.userPool.id,
|
|
108
|
+
USER_POOL_CLIENT_ID: auth.userPoolClient.id,
|
|
109
|
+
WS_API_ENDPOINT: $interpolate`https://${wsApi.id}.execute-api.${aws.getRegionOutput().name}.amazonaws.com/${$app.stage}`,
|
|
110
|
+
// Required by `snapshotToDraft` (packages/collab/src/draft/snapshot.ts)
|
|
111
|
+
// which fetches `${apiUrl}/v1/admin/sites/{host}/content/{id}/draft`
|
|
112
|
+
// on `$disconnect` to persist the YJS room state back to DraftContent.
|
|
113
|
+
// Without this, the URL becomes `undefined/v1/admin/...` and the fetch
|
|
114
|
+
// throws "Failed to parse URL", crashing the disconnect Lambda.
|
|
115
|
+
API_URL: api.api.url,
|
|
116
|
+
// Shared with the Go API. Sent as `X-Headroom-Internal` on the
|
|
117
|
+
// disconnect snapshot PUT so the Go JWT middleware accepts the
|
|
118
|
+
// internal service-to-service call. See packages/api/internal/middleware/jwt.go.
|
|
119
|
+
INTERNAL_SECRET: storage.internalSecret.value,
|
|
120
|
+
// Phase 7.1 "Large documents": S3 bucket for YJS state that exceeds
|
|
121
|
+
// DynamoDB's 400KB per-item cap. See `YjsPersistence.saveState`.
|
|
122
|
+
COLLAB_STATE_BUCKET: storage.collabStateBucket.name,
|
|
123
|
+
},
|
|
124
|
+
link: [
|
|
125
|
+
collabTable,
|
|
126
|
+
storage.draftContent,
|
|
127
|
+
storage.blocks,
|
|
128
|
+
storage.sites,
|
|
129
|
+
storage.internalSecret,
|
|
130
|
+
// S3 link grants the Lambda IAM permissions for s3:GetObject /
|
|
131
|
+
// s3:PutObject on the bucket via SST's resource binding.
|
|
132
|
+
storage.collabStateBucket,
|
|
133
|
+
],
|
|
134
|
+
permissions: [
|
|
135
|
+
{
|
|
136
|
+
actions: ["execute-api:ManageConnections"],
|
|
137
|
+
resources: [$interpolate`arn:aws:execute-api:*:*:${wsApi.id}/*`],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Wire up $connect, $default, $disconnect routes
|
|
143
|
+
const integration = new aws.apigatewayv2.Integration(
|
|
144
|
+
`${name}WsIntegration`,
|
|
145
|
+
{
|
|
146
|
+
apiId: wsApi.id,
|
|
147
|
+
integrationType: "AWS_PROXY",
|
|
148
|
+
integrationUri: wsHandler.nodes.function.invokeArn,
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
for (const routeKey of ["$connect", "$default", "$disconnect"]) {
|
|
153
|
+
new aws.apigatewayv2.Route(`${name}WsRoute${routeKey}`, {
|
|
154
|
+
apiId: wsApi.id,
|
|
155
|
+
routeKey,
|
|
156
|
+
target: $interpolate`integrations/${integration.id}`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
new aws.apigatewayv2.Stage(`${name}WsStage`, {
|
|
161
|
+
apiId: wsApi.id,
|
|
162
|
+
name: $app.stage,
|
|
163
|
+
autoDeploy: true,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Grant API Gateway permission to invoke Lambda
|
|
167
|
+
new aws.lambda.Permission(`${name}WsPermission`, {
|
|
168
|
+
action: "lambda:InvokeFunction",
|
|
169
|
+
function: wsHandler.nodes.function.name,
|
|
170
|
+
principal: "apigateway.amazonaws.com",
|
|
171
|
+
sourceArn: $interpolate`${wsApi.executionArn}/*/*`,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const wsUrl =
|
|
175
|
+
$interpolate`wss://${wsApi.id}.execute-api.${aws.getRegionOutput().name}.amazonaws.com/${$app.stage}`;
|
|
176
|
+
|
|
177
|
+
return { wsApi, wsHandler, wsUrl };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type CollabHandlerResources = ReturnType<typeof createCollabHandler>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Combined collaboration resource shape — the union of the table half
|
|
184
|
+
* (created early) and the handler half (created late). Downstream consumers
|
|
185
|
+
* (admin-site, public outputs) see a single object.
|
|
186
|
+
*/
|
|
187
|
+
export type CollaborationResources = CollabTableResources & CollabHandlerResources;
|
package/src/index.ts
CHANGED
|
@@ -12,8 +12,10 @@ import { createAuth } from "./auth.js";
|
|
|
12
12
|
import { createWebhooks } from "./webhooks.js";
|
|
13
13
|
import { createImage } from "./image.js";
|
|
14
14
|
import { createApi } from "./api.js";
|
|
15
|
-
import {
|
|
15
|
+
import { createApiCdn } from "./cdn-api.js";
|
|
16
|
+
import { createMediaCdn } from "./cdn-media.js";
|
|
16
17
|
import { createScheduler } from "./scheduler.js";
|
|
18
|
+
import { createCollabTable, createCollabHandler } from "./collaboration.js";
|
|
17
19
|
import { createAdminSite } from "./admin-site.js";
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -48,8 +50,10 @@ export type { AuthResources } from "./auth.js";
|
|
|
48
50
|
export type { WebhookResources } from "./webhooks.js";
|
|
49
51
|
export type { ImageResources } from "./image.js";
|
|
50
52
|
export type { ApiResources } from "./api.js";
|
|
51
|
-
export type {
|
|
53
|
+
export type { ApiCdnResources } from "./cdn-api.js";
|
|
54
|
+
export type { MediaCdnResources } from "./cdn-media.js";
|
|
52
55
|
export type { SchedulerResources } from "./scheduler.js";
|
|
56
|
+
export type { CollaborationResources } from "./collaboration.js";
|
|
53
57
|
export type { AdminSiteResources } from "./admin-site.js";
|
|
54
58
|
|
|
55
59
|
export interface HeadroomCMSArgs {
|
|
@@ -61,7 +65,7 @@ export interface HeadroomCMSArgs {
|
|
|
61
65
|
senderEmail: string;
|
|
62
66
|
|
|
63
67
|
/**
|
|
64
|
-
* Custom domain for the CDN (
|
|
68
|
+
* Custom domain for the API CDN (`/v1/*` and `/health`).
|
|
65
69
|
* If not provided, uses the default CloudFront domain.
|
|
66
70
|
*/
|
|
67
71
|
domain?: {
|
|
@@ -70,6 +74,17 @@ export interface HeadroomCMSArgs {
|
|
|
70
74
|
certificateArn: string;
|
|
71
75
|
};
|
|
72
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Custom domain for the media CDN (S3 + image-Lambda origin, serves
|
|
79
|
+
* `/media/*` and `/img/*`). If not provided, uses the default CloudFront
|
|
80
|
+
* domain. The certificate must be in us-east-1, same as `domain`.
|
|
81
|
+
*/
|
|
82
|
+
mediaDomain?: {
|
|
83
|
+
name: string;
|
|
84
|
+
/** ACM certificate ARN (must be in us-east-1 for CloudFront) */
|
|
85
|
+
certificateArn: string;
|
|
86
|
+
};
|
|
87
|
+
|
|
73
88
|
/**
|
|
74
89
|
* Custom domain for the admin UI.
|
|
75
90
|
* If not provided, uses the default CloudFront domain.
|
|
@@ -120,15 +135,28 @@ export interface HeadroomCMSArgs {
|
|
|
120
135
|
adminPath: string;
|
|
121
136
|
/** Go source path for scheduler Lambda, e.g. "packages/scheduler" */
|
|
122
137
|
schedulerHandler?: string;
|
|
138
|
+
/** SST handler for collab Lambda, e.g. "packages/collab/src/index.handler" */
|
|
139
|
+
collabHandler?: string;
|
|
123
140
|
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build-time toggle for the collaboration feature in the admin UI.
|
|
144
|
+
* Defaults to `true`. Set `false` to keep the WebSocket / Collab table
|
|
145
|
+
* deployed but hide the "Collaborate" / "Request collaboration" buttons
|
|
146
|
+
* and refuse to open WebSockets — a kill-switch without teardown. Used
|
|
147
|
+
* to ship the feature to a stage before turning it on for editors.
|
|
148
|
+
*/
|
|
149
|
+
collabEnabled?: boolean;
|
|
124
150
|
}
|
|
125
151
|
|
|
126
152
|
export class HeadroomCMS {
|
|
127
153
|
public readonly apiUrl: $util.Output<string>;
|
|
128
|
-
public readonly
|
|
154
|
+
public readonly apiCdnUrl: $util.Output<string>;
|
|
155
|
+
public readonly mediaCdnUrl: $util.Output<string>;
|
|
129
156
|
public readonly adminUrl: $util.Output<string>;
|
|
130
157
|
public readonly userPoolId: $util.Output<string>;
|
|
131
158
|
public readonly userPoolClientId: $util.Output<string>;
|
|
159
|
+
public readonly collabWsUrl: $util.Output<string>;
|
|
132
160
|
|
|
133
161
|
public readonly outputs: Record<string, $util.Output<string>>;
|
|
134
162
|
|
|
@@ -167,12 +195,17 @@ export class HeadroomCMS {
|
|
|
167
195
|
: undefined,
|
|
168
196
|
});
|
|
169
197
|
|
|
170
|
-
//
|
|
198
|
+
// 5a. Collaboration table (must exist before the API Lambda, which
|
|
199
|
+
// links it for ticket reads/writes).
|
|
200
|
+
const collabTable = createCollabTable(name);
|
|
201
|
+
|
|
202
|
+
// 6. API Lambda (Go handler with access to all resources)
|
|
171
203
|
const api = createApi(name, {
|
|
172
204
|
storage,
|
|
173
205
|
auth,
|
|
174
206
|
webhooks,
|
|
175
207
|
image,
|
|
208
|
+
collab: collabTable,
|
|
176
209
|
senderEmail: args.senderEmail,
|
|
177
210
|
pkgRoot,
|
|
178
211
|
dev: args.dev
|
|
@@ -180,7 +213,24 @@ export class HeadroomCMS {
|
|
|
180
213
|
: undefined,
|
|
181
214
|
});
|
|
182
215
|
|
|
183
|
-
//
|
|
216
|
+
// 5b. Collaboration WebSocket handler (must be created AFTER the API
|
|
217
|
+
// because its Lambda environment includes API_URL = api.api.url, which
|
|
218
|
+
// `snapshotToDraft` uses to PUT the disconnect snapshot back to the
|
|
219
|
+
// Go draft endpoint).
|
|
220
|
+
const collabHandler = createCollabHandler(name, {
|
|
221
|
+
storage,
|
|
222
|
+
auth,
|
|
223
|
+
api,
|
|
224
|
+
collabTable: collabTable.collabTable,
|
|
225
|
+
pkgRoot,
|
|
226
|
+
dev: args.dev?.collabHandler
|
|
227
|
+
? { handler: args.dev.collabHandler }
|
|
228
|
+
: undefined,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const collab = { ...collabTable, ...collabHandler };
|
|
232
|
+
|
|
233
|
+
// 7. Scheduler (EventBridge cron + Go Lambda + lock table)
|
|
184
234
|
const schedulerResources = createScheduler(name, {
|
|
185
235
|
storage,
|
|
186
236
|
api,
|
|
@@ -190,42 +240,55 @@ export class HeadroomCMS {
|
|
|
190
240
|
: undefined,
|
|
191
241
|
});
|
|
192
242
|
|
|
193
|
-
//
|
|
194
|
-
const
|
|
243
|
+
// 8a. API CDN (CloudFront distribution + edge auth, /v1/* and /health)
|
|
244
|
+
const apiCdn = createApiCdn(name, {
|
|
195
245
|
api,
|
|
196
|
-
image,
|
|
197
|
-
contentBucket: storage.contentBucket,
|
|
198
246
|
kvs: storage.kvs,
|
|
199
247
|
priceClass: args.priceClass,
|
|
200
248
|
apiCacheTtl: args.apiCacheTtl,
|
|
201
249
|
domain: args.domain,
|
|
202
250
|
});
|
|
203
251
|
|
|
204
|
-
//
|
|
252
|
+
// 8b. Media CDN (CloudFront distribution, /media/* and /img/* only)
|
|
253
|
+
const mediaCdn = createMediaCdn(name, {
|
|
254
|
+
image,
|
|
255
|
+
contentBucket: storage.contentBucket,
|
|
256
|
+
priceClass: args.priceClass,
|
|
257
|
+
domain: args.mediaDomain,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// 9. Admin UI (static site)
|
|
205
261
|
const admin = createAdminSite(name, {
|
|
206
262
|
api,
|
|
207
|
-
|
|
263
|
+
apiCdn,
|
|
264
|
+
mediaCdn,
|
|
208
265
|
auth,
|
|
266
|
+
collab,
|
|
209
267
|
pkgRoot,
|
|
210
268
|
dev: args.dev
|
|
211
269
|
? { adminPath: args.dev.adminPath }
|
|
212
270
|
: undefined,
|
|
213
271
|
domain: args.adminDomain,
|
|
272
|
+
collabEnabled: args.collabEnabled,
|
|
214
273
|
});
|
|
215
274
|
|
|
216
275
|
// Expose outputs
|
|
217
276
|
this.apiUrl = api.api.url;
|
|
218
|
-
this.
|
|
277
|
+
this.apiCdnUrl = apiCdn.url;
|
|
278
|
+
this.mediaCdnUrl = mediaCdn.url;
|
|
219
279
|
this.adminUrl = admin.url;
|
|
220
280
|
this.userPoolId = auth.userPool.id;
|
|
221
281
|
this.userPoolClientId = auth.userPoolClient.id;
|
|
282
|
+
this.collabWsUrl = collab.wsUrl;
|
|
222
283
|
|
|
223
284
|
this.outputs = {
|
|
224
285
|
api: api.api.url,
|
|
225
|
-
|
|
286
|
+
apiCdn: apiCdn.url,
|
|
287
|
+
mediaCdn: mediaCdn.url,
|
|
226
288
|
admin: admin.url,
|
|
227
289
|
userPoolId: auth.userPool.id,
|
|
228
290
|
userPoolClientId: auth.userPoolClient.id,
|
|
291
|
+
collabWs: collab.wsUrl,
|
|
229
292
|
};
|
|
230
293
|
}
|
|
231
294
|
}
|
package/src/sst-env.d.ts
CHANGED
|
@@ -40,6 +40,13 @@ declare namespace sst {
|
|
|
40
40
|
url: PulumiOutput<string>;
|
|
41
41
|
name: PulumiOutput<string>;
|
|
42
42
|
arn: PulumiOutput<string>;
|
|
43
|
+
nodes: {
|
|
44
|
+
function: {
|
|
45
|
+
name: PulumiOutput<string>;
|
|
46
|
+
invokeArn: PulumiOutput<string>;
|
|
47
|
+
arn: PulumiOutput<string>;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
class CognitoUserPool {
|
|
@@ -111,6 +118,27 @@ declare namespace aws {
|
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
|
|
121
|
+
namespace apigatewayv2 {
|
|
122
|
+
class Api {
|
|
123
|
+
constructor(name: string, args?: any, opts?: any);
|
|
124
|
+
id: PulumiOutput<string>;
|
|
125
|
+
executionArn: PulumiOutput<string>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class Integration {
|
|
129
|
+
constructor(name: string, args?: any, opts?: any);
|
|
130
|
+
id: PulumiOutput<string>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class Route {
|
|
134
|
+
constructor(name: string, args?: any, opts?: any);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class Stage {
|
|
138
|
+
constructor(name: string, args?: any, opts?: any);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
114
142
|
namespace ses {
|
|
115
143
|
class EmailIdentity {
|
|
116
144
|
constructor(name: string, args?: any, opts?: any);
|
package/src/storage.ts
CHANGED
|
@@ -131,11 +131,44 @@ export function createStorage(name: string) {
|
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
// Phase 7.1 "Large documents": YJS room state that exceeds DynamoDB's 400KB
|
|
135
|
+
// per-item cap is uploaded here instead of being inlined on the Collab
|
|
136
|
+
// item. Dedicated bucket (vs reusing `contentBucket`) for two reasons:
|
|
137
|
+
// 1. `contentBucket` is `access: "cloudfront"` for media delivery; YJS
|
|
138
|
+
// state must NOT be CDN-fronted (it's per-collaboration-session
|
|
139
|
+
// transient state, never publicly addressable).
|
|
140
|
+
// 2. Lifecycle expiration is much shorter — these objects are recovery
|
|
141
|
+
// fallback for an active collaboration session; the matching Collab
|
|
142
|
+
// table item carries a 24h DynamoDB TTL, so the S3 lifecycle rule
|
|
143
|
+
// cleans up overflow objects on a similar timeline.
|
|
144
|
+
const collabStateBucket = new sst.aws.Bucket(`${name}CollabStateBucket`, {
|
|
145
|
+
transform: {
|
|
146
|
+
bucket: (args: any) => {
|
|
147
|
+
args.lifecycleRules = [
|
|
148
|
+
{
|
|
149
|
+
id: "expire-collab-state",
|
|
150
|
+
enabled: true,
|
|
151
|
+
expiration: { days: 7 },
|
|
152
|
+
// Keep the matching Collab DynamoDB TTL (24h) as the primary
|
|
153
|
+
// cleanup mechanism; this is a long-stop safety net so orphaned
|
|
154
|
+
// S3 objects don't accumulate forever.
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
134
161
|
const kvs = new aws.cloudfront.KeyValueStore(`${name}KVS`, {
|
|
135
162
|
name: $interpolate`${$app.name}-${$app.stage}-kvs`,
|
|
136
163
|
comment: "Headroom CMS edge data: API keys and site versions",
|
|
137
164
|
});
|
|
138
165
|
|
|
166
|
+
// Shared secret for internal service-to-service auth between the collab
|
|
167
|
+
// Lambda and the Go API (`X-Headroom-Internal` header). Both Lambdas read
|
|
168
|
+
// this from their `INTERNAL_SECRET` environment variable. Set the value via
|
|
169
|
+
// `sst secret set HeadroomInternalSecret <value>` (or the namespaced equivalent).
|
|
170
|
+
const internalSecret = new sst.Secret(`${name}InternalSecret`);
|
|
171
|
+
|
|
139
172
|
return {
|
|
140
173
|
sites,
|
|
141
174
|
content,
|
|
@@ -148,7 +181,9 @@ export function createStorage(name: string) {
|
|
|
148
181
|
relationships,
|
|
149
182
|
siteUsers,
|
|
150
183
|
contentBucket,
|
|
184
|
+
collabStateBucket,
|
|
151
185
|
kvs,
|
|
186
|
+
internalSecret,
|
|
152
187
|
};
|
|
153
188
|
}
|
|
154
189
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as s}from"./tanstack-Bs3zYPPV.js";import{u as U,f as D,r as w}from"./react-vendor-C2CvUxFh.js";import{u as M,a as R,b as L,c as z}from"./useContent-e8beBIuq.js";import{m as k}from"./media-url-DIg_vSyf.js";import{w as E,x as H,v as r,S as F,j as h,y as V,B as _}from"./index-Cir9tY_P.js";import{u as q}from"./usePageTitle-BNSba9_L.js";import{C as G,c as J}from"./card-hXVtlM0q.js";import{C as m}from"./checkbox-WGrS3sUr.js";import{T as K,a as O,b as S,c as i,d as Q,e as d}from"./table-DLoIbCQ5.js";import{f as y}from"./format-C88SDH8g.js";import{B as W}from"./BulkActionBar-TRiXXLQd.js";import"./radix-C1kb_NqW.js";function ie(){const{host:c}=U(),p=D(),{collections:A}=E(),f=H();q({title:"Recent"});const[$,P]=w.useState(),[n,a]=w.useState(new Set),{data:o,isLoading:I}=M(c,$),j=R(c),g=L(c),b=z(c),N=new Map(A.map(e=>[e.name,e.label])),x=o?.items.map(e=>e.contentId)??[],v=x.length>0&&x.every(e=>n.has(e));function C(e){a(t=>{const l=new Set(t);return l.has(e)?l.delete(e):l.add(e),l})}function B(){a(v?new Set:new Set(x))}const T=j.isPending||g.isPending||b.isPending;return s.jsxs("div",{children:[!f&&s.jsxs("div",{className:"mb-6",children:[s.jsx("h1",{className:"text-2xl font-semibold",children:"All Content"}),s.jsx("p",{className:"text-muted-foreground text-sm",children:"Recently updated content across all collections."})]}),n.size>0&&s.jsx(W,{selectedCount:n.size,onPublish:async()=>{const e=[...n],l=(await j.mutateAsync(e)).filter(u=>u.status==="rejected").length;l?r.error(`${l} of ${e.length} failed to publish`):r.success(`${e.length} item(s) published`),a(new Set)},onUnpublish:async()=>{const e=[...n],l=(await g.mutateAsync(e)).filter(u=>u.status==="rejected").length;l?r.error(`${l} of ${e.length} failed to unpublish`):r.success(`${e.length} item(s) unpublished`),a(new Set)},onDelete:async()=>{const e=[...n],l=(await b.mutateAsync(e)).filter(u=>u.status==="rejected").length;l?r.error(`${l} of ${e.length} failed to delete`):r.success(`${e.length} item(s) deleted`),a(new Set)},onClear:()=>a(new Set),isPending:T}),I?s.jsx("div",{className:"space-y-2",children:Array.from({length:5}).map((e,t)=>s.jsx(F,{className:"h-12 w-full"},t))}):o?.items.length?s.jsxs(s.Fragment,{children:[f?s.jsx("div",{className:"grid gap-3","data-testid":"all-content-card-view",children:o.items.map(e=>{const t=e.cover;return s.jsx(G,{className:V("cursor-pointer gap-0 overflow-hidden py-0 transition-shadow hover:shadow-md",n.has(e.contentId)&&"ring-2 ring-primary"),onClick:()=>p(`/sites/${c}/content/${e.collection}/${e.contentId}?from=recent`),children:s.jsx(J,{className:"p-3",children:s.jsxs("div",{className:"flex items-start gap-3 min-w-0",children:[t?.url&&s.jsx("img",{src:k(t.url),alt:t.alt??"",className:"h-12 w-12 rounded object-cover flex-shrink-0"}),s.jsxs("div",{className:"min-w-0 flex-1",children:[s.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[s.jsx("span",{className:"font-medium truncate",children:e.title||"Untitled"}),s.jsx(h,{variant:e.publishedAt?"default":"secondary",className:"shrink-0",children:e.publishedAt?"Published":"Draft"})]}),e.slug&&s.jsxs("p",{className:"text-xs text-muted-foreground truncate mt-0.5",children:["/",e.slug]}),s.jsxs("div",{className:"flex items-center gap-2 mt-1",children:[s.jsx(h,{variant:"outline",className:"text-[10px] px-1.5 py-0",children:N.get(e.collection)??e.collection}),s.jsx("span",{className:"text-xs text-muted-foreground",children:e.publishedAt?y(e.publishedAt):"—"})]})]}),s.jsx(m,{checked:n.has(e.contentId),onCheckedChange:()=>C(e.contentId),onClick:l=>l.stopPropagation(),className:"mt-1"})]})})},e.contentId)})}):s.jsxs(K,{children:[s.jsx(O,{children:s.jsxs(S,{children:[s.jsx(i,{className:"w-10",children:s.jsx(m,{checked:v,onCheckedChange:B,"aria-label":"Select all"})}),s.jsx(i,{children:"Title"}),s.jsx(i,{children:"Slug"}),s.jsx(i,{children:"Collection"}),s.jsx(i,{children:"Status"}),s.jsx(i,{children:"Updated"})]})}),s.jsx(Q,{children:o.items.map(e=>s.jsxs(S,{className:"cursor-pointer",onClick:()=>p(`/sites/${c}/content/${e.collection}/${e.contentId}?from=recent`),children:[s.jsx(d,{onClick:t=>t.stopPropagation(),children:s.jsx(m,{checked:n.has(e.contentId),onCheckedChange:()=>C(e.contentId),"aria-label":`Select ${e.title||"Untitled"}`})}),s.jsx(d,{className:"font-medium",children:s.jsxs("div",{className:"flex items-center gap-3",children:[(()=>{const t=e.cover;return t?.url?s.jsx("img",{src:k(t.url),alt:t.alt??"",className:"h-8 w-8 rounded object-cover flex-shrink-0"}):null})(),s.jsx("span",{children:e.title||"Untitled"})]})}),s.jsx(d,{className:"text-muted-foreground text-sm",children:e.slug||"—"}),s.jsx(d,{children:s.jsx(h,{variant:"outline",children:N.get(e.collection)??e.collection})}),s.jsx(d,{children:s.jsx(h,{variant:e.publishedAt?"default":"secondary",children:e.publishedAt?"Published":"Draft"})}),s.jsx(d,{className:"text-muted-foreground",children:e.publishedAt?y(e.publishedAt):"—"})]},e.contentId))})]}),o.hasMore&&s.jsx("div",{className:"mt-4 flex justify-center",children:s.jsx(_,{variant:"outline",onClick:()=>P(o.cursor??void 0),children:"Load More"})})]}):s.jsx("p",{className:"text-muted-foreground",children:"No content yet."})]})}export{ie as AllContentPage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as F,j as e}from"./tanstack-Bs3zYPPV.js";import{L,r as i,u as E}from"./react-vendor-C2CvUxFh.js";import{e as O,F as K,G as V,J as W,K as q,B as R,au as H,S as y,x as J,aK as z,aJ as v,aL as G,aM as Q,aN as X,aO as Y,aP as Z,aQ as ee,aR as te,aS as se,al as S,aT as ae,T as x,$ as m,aE as le,P as A,aU as U,ai as re,aV as de,aW as ne,aX as oe,a4 as ce,aY as ie,aZ as ue}from"./index-Cir9tY_P.js";import{u as me}from"./useAdminResolver-D-LlmquD.js";import{s as _,T as he}from"./serializeToText-DR_WnxiI.js";import{h as T}from"./useContent-e8beBIuq.js";import{f as P,a as xe}from"./format-C88SDH8g.js";import{S as be,a as pe,b as fe,c as ge,d as je}from"./select-_uJYxzeZ.js";import{T as ke,a as Ie,b as D,c as p,d as ye,e as f}from"./table-DLoIbCQ5.js";import{u as Ce}from"./usePageTitle-BNSba9_L.js";import"./radix-C1kb_NqW.js";function we(t,a){const l=O(),d=new URLSearchParams;a?.action&&d.set("action",a.action),a?.before&&d.set("before",String(a.before));const o=d.toString();return F({queryKey:["sites",t,"audit",a?.action??"",a?.before??""],queryFn:()=>l.apiFetch(`/v1/admin/sites/${t}/audit${o?`?${o}`:""}`),enabled:!!t})}const Ne={"content.create":"Content Created","content.update":"Content Updated","content.publish":"Content Published","content.unpublish":"Content Unpublished","content.delete":"Content Deleted","content.schedule":"Content Scheduled","content.unschedule":"Content Unscheduled","content.update_publish_date":"Publish Date Changed","content.discard":"Draft Discarded","media.create":"Media Uploaded","media.update":"Media Updated","media.delete":"Media Deleted","media.bulk":"Bulk Media Operation","media.folder.create":"Folder Created","media.folder.update":"Folder Renamed","media.folder.delete":"Folder Deleted","collection.create":"Collection Created","collection.update":"Collection Updated","collection.delete":"Collection Deleted","blocktype.create":"Block Type Created","blocktype.update":"Block Type Updated","blocktype.delete":"Block Type Deleted","webhook.create":"Webhook Created","webhook.update":"Webhook Updated","webhook.delete":"Webhook Deleted","webhook.rotate_secret":"Webhook Secret Rotated","webhook.retry_delivery":"Delivery Retried","apikey.create":"API Key Created","apikey.revoke":"API Key Revoked","site.archived":"Site Archived","site.unarchived":"Site Unarchived","site.purged":"Site Purged","site_user.create":"User Created","site_user.update":"User Updated","site_user.delete":"User Deleted","site_user.change_email":"User Email Changed","site_user.self_register":"User Self-Registered","site_user.self_update":"User Updated Profile"};function ve(t){return!!t?.blockId&&!!t?.prevBlockId&&!!t?.resourceId}function Se({host:t,details:a}){const l=a.resourceId,d=a.collection??"",{data:o}=H(t,d),{data:s,isLoading:b}=T(t,l,a.blockId??null),{data:n,isLoading:u}=T(t,l,a.prevBlockId??null),c=i.useMemo(()=>o?.fields??[],[o?.fields]),g=i.useMemo(()=>{if(!s)return[];const h={title:s.title,slug:s.slug,snippet:s.snippet,tags:s.tags};return _(c,s.body??{},h)},[s,c]),j=i.useMemo(()=>{if(!n)return[];const h={title:n.title,slug:n.slug,snippet:n.snippet,tags:n.tags};return _(c,n.body??{},h)},[n,c]);return b||u?e.jsxs("div",{className:"space-y-2","data-testid":"diff-loading",children:[e.jsx(y,{className:"h-6 w-24"}),e.jsx(y,{className:"h-40 w-full"})]}):!s||!n?e.jsx("p",{className:"text-muted-foreground text-sm","data-testid":"diff-unavailable",children:"Version data unavailable."}):e.jsxs("div",{"data-testid":"diff-view",children:[e.jsx("h3",{className:"mb-2 text-sm font-medium",children:"Changes"}),e.jsx(he,{left:j,right:g,leftLabel:"Before",rightLabel:"After"})]})}function Ae({open:t,onOpenChange:a,event:l,host:d,resolveAdmin:o}){if(!l)return null;const s=l.details,b=Ne[l.action]??l.action,n=ve(s),u=s?.resourceType==="content"&&s?.resourceId&&s?.collection?`/sites/${d}/content/${s.collection}/${s.resourceId}`:null;return e.jsx(K,{open:t,onOpenChange:a,children:e.jsxs(V,{className:"w-full overflow-y-auto px-6 sm:max-w-lg","data-testid":"audit-drawer",children:[e.jsx(W,{children:e.jsx(q,{children:b})}),e.jsxs("div",{className:"mt-4 space-y-4",children:[e.jsxs("dl",{className:"grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm",children:[s?.title&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-muted-foreground",children:"Resource"}),e.jsx("dd",{className:"font-medium","data-testid":"drawer-resource",children:s.title})]}),s?.collection&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-muted-foreground",children:"Collection"}),e.jsx("dd",{children:s.collection})]}),e.jsx("dt",{className:"text-muted-foreground",children:"Admin"}),e.jsx("dd",{children:o(l.adminId)}),e.jsx("dt",{className:"text-muted-foreground",children:"Time"}),e.jsx("dd",{children:P(l.createdAt)}),e.jsx("dt",{className:"text-muted-foreground",children:"Status"}),e.jsx("dd",{children:e.jsx("span",{className:l.status==="success"?"text-green-600":l.status==="failed"?"text-red-600":"text-yellow-600",children:l.status})}),s?.ipAddress&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-muted-foreground",children:"IP Address"}),e.jsx("dd",{className:"font-mono text-xs",children:s.ipAddress})]}),s?.extra&&e.jsxs(e.Fragment,{children:[e.jsx("dt",{className:"text-muted-foreground",children:"Details"}),e.jsx("dd",{className:"text-muted-foreground",children:s.extra})]})]}),s&&e.jsxs("details",{className:"text-sm","data-testid":"drawer-json",children:[e.jsx("summary",{className:"text-muted-foreground cursor-pointer select-none",children:"Raw details"}),e.jsx("pre",{className:"bg-muted mt-2 overflow-x-auto rounded p-3 text-xs",children:JSON.stringify(s,null,2)})]}),l.errorMsg&&e.jsx("div",{className:"rounded border border-red-200 bg-red-50 p-3 text-sm text-red-800 dark:border-red-900 dark:bg-red-950 dark:text-red-300","data-testid":"drawer-error",children:l.errorMsg}),u&&e.jsx(R,{variant:"outline",size:"sm",asChild:!0,children:e.jsx(L,{to:u,children:"View content"})}),n&&s&&e.jsx("div",{className:"border-t pt-4",children:e.jsx(Se,{host:d,details:s})}),!n&&s?.resourceType==="content"&&e.jsx("p",{className:"text-muted-foreground text-sm","data-testid":"no-changes",children:"No change comparison available for this event."})]})]})})}const Ue={"content.create":{label:"Created content",Icon:A},"content.update":{label:"Updated content",Icon:m},"content.publish":{label:"Published content",Icon:ue},"content.unpublish":{label:"Unpublished content",Icon:ie},"content.delete":{label:"Deleted content",Icon:x},"content.schedule":{label:"Scheduled content",Icon:ce},"content.unschedule":{label:"Unscheduled content",Icon:oe},"content.update_publish_date":{label:"Changed publish date",Icon:ne},"content.discard":{label:"Discarded draft",Icon:de},"media.create":{label:"Uploaded media",Icon:re},"media.update":{label:"Updated media",Icon:m},"media.delete":{label:"Deleted media",Icon:x},"media.bulk":{label:"Bulk media operation",Icon:S},"media.folder.create":{label:"Created folder",Icon:U},"media.folder.update":{label:"Renamed folder",Icon:m},"media.folder.delete":{label:"Deleted folder",Icon:x},"collection.create":{label:"Created collection",Icon:U},"collection.update":{label:"Updated collection",Icon:m},"collection.delete":{label:"Deleted collection",Icon:x},"blocktype.create":{label:"Created block type",Icon:A},"blocktype.update":{label:"Updated block type",Icon:m},"blocktype.delete":{label:"Deleted block type",Icon:x},"webhook.create":{label:"Created webhook",Icon:le},"webhook.update":{label:"Updated webhook",Icon:m},"webhook.delete":{label:"Deleted webhook",Icon:x},"webhook.rotate_secret":{label:"Rotated webhook secret",Icon:ae},"webhook.retry_delivery":{label:"Retried delivery",Icon:S},"apikey.create":{label:"Created API key",Icon:se},"apikey.revoke":{label:"Revoked API key",Icon:te},"site.archived":{label:"Archived site",Icon:ee},"site.unarchived":{label:"Unarchived site",Icon:Z},"site.purged":{label:"Purged site",Icon:Y},"site_user.create":{label:"Created user",Icon:v},"site_user.update":{label:"Updated user",Icon:X},"site_user.delete":{label:"Deleted user",Icon:Q},"site_user.change_email":{label:"Changed user email",Icon:G},"site_user.self_register":{label:"User registered",Icon:v},"site_user.self_update":{label:"User updated profile",Icon:z}},_e=[{label:"All",value:"all"},{label:"Content",value:"content."},{label:"Media",value:"media."},{label:"Collections",value:"collection."},{label:"Block Types",value:"blocktype."},{label:"Webhooks",value:"webhook."},{label:"API Keys",value:"apikey."},{label:"Sites",value:"site."},{label:"Site Users",value:"site_user."}];function Te(t){return Ue[t]??{label:t,Icon:m}}function De(t,a){const l=a.resourceType??a.details?.resourceType,d=a.resourceId??a.details?.resourceId;if(!l||!d)return null;switch(l){case"content":{const o=a.details?.collection;return o?`/sites/${t}/content/${o}/${d}`:`/sites/${t}/content`}case"media":return`/sites/${t}/media`;case"collection":return`/sites/${t}/settings/collections`;case"webhook":return`/sites/${t}/settings/webhooks`;case"site_user":return`/sites/${t}/settings/site-users/${d}`;default:return null}}function Le({status:t}){const a=t==="success"?"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400":t==="failed"?"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400":"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400";return e.jsx("span",{className:`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${a}`,children:t})}function qe(){const{host:t}=E(),[a,l]=i.useState(),[d,o]=i.useState("all"),[s,b]=i.useState(null),[n,u]=i.useState(!1),{data:c,isLoading:g}=we(t,{before:a}),j=c?.items.map(r=>r.adminId)??[],{resolve:h}=me(j),$=J(),B=i.useCallback(r=>{b(r),u(!0)},[]);Ce({title:"Audit Log"});const C=d!=="all"?c?.items.filter(r=>r.action.startsWith(d)):c?.items;return e.jsxs("div",{children:[e.jsxs("div",{className:"mb-6 flex items-center justify-between gap-4",children:[!$&&e.jsx("h1",{className:"text-2xl font-semibold",children:"Audit Log"}),e.jsxs(be,{value:d,onValueChange:o,children:[e.jsx(pe,{className:"w-[180px]","data-testid":"action-filter",children:e.jsx(fe,{placeholder:"Filter by type"})}),e.jsx(ge,{children:_e.map(r=>e.jsx(je,{value:r.value,children:r.label},r.value))})]})]}),g?e.jsx("div",{className:"space-y-2",children:Array.from({length:5}).map((r,k)=>e.jsx(y,{className:"h-12 w-full"},k))}):C?.length?e.jsxs(e.Fragment,{children:[e.jsxs(ke,{children:[e.jsx(Ie,{children:e.jsxs(D,{children:[e.jsx(p,{children:"Action"}),e.jsx(p,{children:"Resource"}),e.jsx(p,{children:"Admin"}),e.jsx(p,{children:"Status"}),e.jsx(p,{children:"Time"})]})}),e.jsx(ye,{children:C.map(r=>{const{label:k,Icon:M}=Te(r.action),I=r.details?.title,w=r.details?.collection,N=t?De(t,r):null;return e.jsxs(D,{"data-testid":"audit-row",className:"cursor-pointer",onClick:()=>B(r),children:[e.jsx(f,{children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(M,{className:"text-muted-foreground h-4 w-4 shrink-0"}),e.jsx("span",{className:"font-medium",children:k})]})}),e.jsx(f,{children:I?e.jsxs("div",{className:"flex items-center gap-2",children:[N?e.jsx(L,{to:N,className:"text-primary hover:underline",children:I}):e.jsx("span",{children:I}),w&&e.jsx("span",{className:"bg-muted text-muted-foreground rounded px-1.5 py-0.5 text-xs",children:w})]}):e.jsx("span",{className:"text-muted-foreground",children:"-"})}),e.jsx(f,{className:"text-sm",children:h(r.adminId)}),e.jsx(f,{children:e.jsx(Le,{status:r.status})}),e.jsx(f,{className:"text-muted-foreground",title:P(r.createdAt),children:xe(r.createdAt)})]},r.eventId)})})]}),c?.hasMore&&c.items.length>0&&e.jsx("div",{className:"mt-4 flex justify-center",children:e.jsx(R,{variant:"outline",onClick:()=>l(c.items[c.items.length-1].createdAt),children:"Load More"})})]}):e.jsx("p",{className:"text-muted-foreground",children:"No audit events."}),e.jsx(Ae,{open:n,onOpenChange:u,event:s,host:t,resolveAdmin:h})]})}export{qe as AuditPage};
|