headroom-cms 0.1.8 → 0.1.10
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/admin/assets/{AdminsPage-Bt_ekZen.js → AdminsPage-BIWASote.js} +1 -1
- package/admin/assets/AllContentPage-1gXe2OC7.js +1 -0
- package/admin/assets/{ApiKeysPage-BfWCxGhC.js → ApiKeysPage-BBW4ATBx.js} +1 -1
- package/admin/assets/AuditPage-B5GGFWGG.js +1 -0
- package/admin/assets/BlockEditor-CQpF8tYb.css +1 -0
- package/admin/assets/BlockEditor-ClskiZoX.js +176 -0
- package/admin/assets/BlockTypeEditPage-CY0gCPei.js +1 -0
- package/admin/assets/BlockTypesPage-D8Me6OeX.js +1 -0
- package/admin/assets/{BulkActionBar-TRiXXLQd.js → BulkActionBar--35xjnOP.js} +1 -1
- package/admin/assets/CollectionEditPage-y8t0ZO89.js +1 -0
- package/admin/assets/{CollectionsPage-ClplrxNn.js → CollectionsPage-BQmGXpvW.js} +1 -1
- package/admin/assets/{ContentCreatePage-DfYcEH1u.js → ContentCreatePage-DlgxamOe.js} +1 -1
- package/admin/assets/ContentEditPage-WkSbCnnG.js +1 -0
- package/admin/assets/ContentField-D04Uo1Ov.js +1 -0
- package/admin/assets/ContentListPage-BDMx7pWb.js +1 -0
- package/admin/assets/CustomBlockPreview-Cs9bFDh4.js +479 -0
- package/admin/assets/FieldRenderer-wE-mtqZB.js +2 -0
- package/admin/assets/FilterBar-kFcOLffg.js +1 -0
- package/admin/assets/FloatingComposerController-D4uLQfUX-C0Lhbmda.js +1 -0
- package/admin/assets/IconPicker-BrgSAsa_.js +3 -0
- package/admin/assets/{LoginPage-DutieANA.js → LoginPage-Bi7TBzK4.js} +1 -1
- package/admin/assets/MediaField-B-Cz8TlK.js +1 -0
- package/admin/assets/MediaPage-C84p9d1U.js +1 -0
- package/admin/assets/Pagination-CuHwUPHi.js +1 -0
- package/admin/assets/RelationshipPicker-Dv7GaLcU.js +1 -0
- package/admin/assets/{SiteSettingsPage-BtCC3RKc.js → SiteSettingsPage-nBT7NzkA.js} +1 -1
- package/admin/assets/{SiteUserEditPage-ClHmp0T-.js → SiteUserEditPage-DroUTii9.js} +1 -1
- package/admin/assets/SiteUsersPage-iVXPCBPe.js +1 -0
- package/admin/assets/{SitesPage-Bw_WBN6v.js → SitesPage-BefZeWuJ.js} +1 -1
- package/admin/assets/{SubmissionDetailPage-DS08LGxd.js → SubmissionDetailPage-ktmzzOE1.js} +1 -1
- package/admin/assets/SubmissionEditPage-C-ykTI2t.js +1 -0
- package/admin/assets/SubmissionListPage-DA-8deUy.js +1 -0
- package/admin/assets/{TagInput-BILCaC9b.js → TagInput-d-Hw1fkL.js} +1 -1
- package/admin/assets/{TagsPage-DdeZokow.js → TagsPage-BZzDvcKa.js} +1 -1
- package/admin/assets/{UsersPage-B0vLxjrg.js → UsersPage-CnQAOOGF.js} +1 -1
- package/admin/assets/{WebhookEditPage-SlJE4d3z.js → WebhookEditPage-KeS8hmdW.js} +1 -1
- package/admin/assets/{WebhooksPage-C6lGZLpr.js → WebhooksPage-CASjmlPN.js} +1 -1
- package/admin/assets/{card-hXVtlM0q.js → card-CZTHR2Qa.js} +1 -1
- package/admin/assets/checkbox-DEgzM8H9.js +1 -0
- package/admin/assets/{collapsible-B414SspL.js → collapsible-D3d29uJp.js} +1 -1
- package/admin/assets/{command-fvBFHye4.js → command-CdzYw11U.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-BA3y7HJs.js +18 -0
- package/admin/assets/index-c7UygSvP.css +1 -0
- package/admin/assets/popover-BFw_h3j6.js +1 -0
- package/admin/assets/radix-C5ZmWuuL.js +51 -0
- package/admin/assets/select-dX9e6VDt.js +1 -0
- package/admin/assets/{serializeToText-DR_WnxiI.js → serializeToText-Zin3gYPm.js} +1 -1
- package/admin/assets/{sortable.esm-QyXA6fio.js → sortable.esm-qVEMoaTg.js} +1 -1
- package/admin/assets/{table-DLoIbCQ5.js → table-Dk7eeOt2.js} +1 -1
- package/admin/assets/{textarea-vSXNxwTe.js → textarea-CpDSUg2s.js} +1 -1
- package/admin/assets/useAdminResolver-Bljb4XGQ.js +1 -0
- package/admin/assets/useContent-CW0tm0FY.js +1 -0
- package/admin/assets/useContentSearch-_bwacEth.js +1 -0
- package/admin/assets/{useMedia-e3sqWm_t.js → useMedia-Cu5N4rY8.js} +1 -1
- package/admin/assets/usePageTitle-DYvuJQp6.js +1 -0
- package/admin/assets/useSiteUsers-CKtC_8Jc.js +1 -0
- package/admin/assets/{useTags-f7AVSLuj.js → useTags-ybsMbCst.js} +1 -1
- package/admin/assets/{useWebhooks-BH_r8-Mo.js → useWebhooks-BAB-3sLa.js} +1 -1
- package/admin/assets/yjs-tXBm_srz.js +5 -0
- package/admin/index.html +3 -3
- package/admin/sw.js +1 -1
- package/dist/admin-site.d.ts +12 -0
- package/dist/admin-site.d.ts.map +1 -1
- package/dist/admin-site.js +8 -1
- 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/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 +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -4
- 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/webhook-worker/bootstrap +0 -0
- package/package.json +1 -1
- package/src/admin-site.ts +20 -1
- package/src/api.ts +15 -1
- package/src/collaboration.ts +187 -0
- package/src/index.ts +44 -4
- 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/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/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
|
@@ -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
|
@@ -14,6 +14,7 @@ import { createImage } from "./image.js";
|
|
|
14
14
|
import { createApi } from "./api.js";
|
|
15
15
|
import { createCdn } from "./cdn.js";
|
|
16
16
|
import { createScheduler } from "./scheduler.js";
|
|
17
|
+
import { createCollabTable, createCollabHandler } from "./collaboration.js";
|
|
17
18
|
import { createAdminSite } from "./admin-site.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -50,6 +51,7 @@ export type { ImageResources } from "./image.js";
|
|
|
50
51
|
export type { ApiResources } from "./api.js";
|
|
51
52
|
export type { CdnResources } from "./cdn.js";
|
|
52
53
|
export type { SchedulerResources } from "./scheduler.js";
|
|
54
|
+
export type { CollaborationResources } from "./collaboration.js";
|
|
53
55
|
export type { AdminSiteResources } from "./admin-site.js";
|
|
54
56
|
|
|
55
57
|
export interface HeadroomCMSArgs {
|
|
@@ -120,7 +122,18 @@ export interface HeadroomCMSArgs {
|
|
|
120
122
|
adminPath: string;
|
|
121
123
|
/** Go source path for scheduler Lambda, e.g. "packages/scheduler" */
|
|
122
124
|
schedulerHandler?: string;
|
|
125
|
+
/** SST handler for collab Lambda, e.g. "packages/collab/src/index.handler" */
|
|
126
|
+
collabHandler?: string;
|
|
123
127
|
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build-time toggle for the collaboration feature in the admin UI.
|
|
131
|
+
* Defaults to `true`. Set `false` to keep the WebSocket / Collab table
|
|
132
|
+
* deployed but hide the "Collaborate" / "Request collaboration" buttons
|
|
133
|
+
* and refuse to open WebSockets — a kill-switch without teardown. Used
|
|
134
|
+
* to ship the feature to a stage before turning it on for editors.
|
|
135
|
+
*/
|
|
136
|
+
collabEnabled?: boolean;
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
export class HeadroomCMS {
|
|
@@ -129,6 +142,7 @@ export class HeadroomCMS {
|
|
|
129
142
|
public readonly adminUrl: $util.Output<string>;
|
|
130
143
|
public readonly userPoolId: $util.Output<string>;
|
|
131
144
|
public readonly userPoolClientId: $util.Output<string>;
|
|
145
|
+
public readonly collabWsUrl: $util.Output<string>;
|
|
132
146
|
|
|
133
147
|
public readonly outputs: Record<string, $util.Output<string>>;
|
|
134
148
|
|
|
@@ -167,12 +181,17 @@ export class HeadroomCMS {
|
|
|
167
181
|
: undefined,
|
|
168
182
|
});
|
|
169
183
|
|
|
170
|
-
//
|
|
184
|
+
// 5a. Collaboration table (must exist before the API Lambda, which
|
|
185
|
+
// links it for ticket reads/writes).
|
|
186
|
+
const collabTable = createCollabTable(name);
|
|
187
|
+
|
|
188
|
+
// 6. API Lambda (Go handler with access to all resources)
|
|
171
189
|
const api = createApi(name, {
|
|
172
190
|
storage,
|
|
173
191
|
auth,
|
|
174
192
|
webhooks,
|
|
175
193
|
image,
|
|
194
|
+
collab: collabTable,
|
|
176
195
|
senderEmail: args.senderEmail,
|
|
177
196
|
pkgRoot,
|
|
178
197
|
dev: args.dev
|
|
@@ -180,7 +199,24 @@ export class HeadroomCMS {
|
|
|
180
199
|
: undefined,
|
|
181
200
|
});
|
|
182
201
|
|
|
183
|
-
//
|
|
202
|
+
// 5b. Collaboration WebSocket handler (must be created AFTER the API
|
|
203
|
+
// because its Lambda environment includes API_URL = api.api.url, which
|
|
204
|
+
// `snapshotToDraft` uses to PUT the disconnect snapshot back to the
|
|
205
|
+
// Go draft endpoint).
|
|
206
|
+
const collabHandler = createCollabHandler(name, {
|
|
207
|
+
storage,
|
|
208
|
+
auth,
|
|
209
|
+
api,
|
|
210
|
+
collabTable: collabTable.collabTable,
|
|
211
|
+
pkgRoot,
|
|
212
|
+
dev: args.dev?.collabHandler
|
|
213
|
+
? { handler: args.dev.collabHandler }
|
|
214
|
+
: undefined,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const collab = { ...collabTable, ...collabHandler };
|
|
218
|
+
|
|
219
|
+
// 7. Scheduler (EventBridge cron + Go Lambda + lock table)
|
|
184
220
|
const schedulerResources = createScheduler(name, {
|
|
185
221
|
storage,
|
|
186
222
|
api,
|
|
@@ -190,7 +226,7 @@ export class HeadroomCMS {
|
|
|
190
226
|
: undefined,
|
|
191
227
|
});
|
|
192
228
|
|
|
193
|
-
//
|
|
229
|
+
// 8. CDN (CloudFront distribution + edge functions)
|
|
194
230
|
const cdn = createCdn(name, {
|
|
195
231
|
api,
|
|
196
232
|
image,
|
|
@@ -201,16 +237,18 @@ export class HeadroomCMS {
|
|
|
201
237
|
domain: args.domain,
|
|
202
238
|
});
|
|
203
239
|
|
|
204
|
-
//
|
|
240
|
+
// 9. Admin UI (static site)
|
|
205
241
|
const admin = createAdminSite(name, {
|
|
206
242
|
api,
|
|
207
243
|
cdn,
|
|
208
244
|
auth,
|
|
245
|
+
collab,
|
|
209
246
|
pkgRoot,
|
|
210
247
|
dev: args.dev
|
|
211
248
|
? { adminPath: args.dev.adminPath }
|
|
212
249
|
: undefined,
|
|
213
250
|
domain: args.adminDomain,
|
|
251
|
+
collabEnabled: args.collabEnabled,
|
|
214
252
|
});
|
|
215
253
|
|
|
216
254
|
// Expose outputs
|
|
@@ -219,6 +257,7 @@ export class HeadroomCMS {
|
|
|
219
257
|
this.adminUrl = admin.url;
|
|
220
258
|
this.userPoolId = auth.userPool.id;
|
|
221
259
|
this.userPoolClientId = auth.userPoolClient.id;
|
|
260
|
+
this.collabWsUrl = collab.wsUrl;
|
|
222
261
|
|
|
223
262
|
this.outputs = {
|
|
224
263
|
api: api.api.url,
|
|
@@ -226,6 +265,7 @@ export class HeadroomCMS {
|
|
|
226
265
|
admin: admin.url,
|
|
227
266
|
userPoolId: auth.userPool.id,
|
|
228
267
|
userPoolClientId: auth.userPoolClient.id,
|
|
268
|
+
collabWs: collab.wsUrl,
|
|
229
269
|
};
|
|
230
270
|
}
|
|
231
271
|
}
|
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};
|