headroom-cms 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -0
- package/admin/assets/AdminsPage-CnrQqwKA.js +1 -0
- package/admin/assets/AllContentPage-ByN1h3PP.js +1 -0
- package/admin/assets/ApiKeysPage-FgNHZPBS.js +1 -0
- package/admin/assets/AuditPage-DAPpo-sj.js +1 -0
- package/admin/assets/BlockEditor-CZTwex-o.js +179 -0
- package/admin/assets/BlockEditor-Cp_wZ2xN.css +1 -0
- package/admin/assets/BlockTypeEditPage-Buuwbx1P.js +1 -0
- package/admin/assets/BlockTypesPage-Dj0qmsqX.js +1 -0
- package/admin/assets/BulkActionBar-BMcUBJSH.js +1 -0
- package/admin/assets/CollectionEditPage-CLgQu2HS.js +1 -0
- package/admin/assets/CollectionsPage-BnCaxALz.js +1 -0
- package/admin/assets/ContentCreatePage-CJI326o-.js +1 -0
- package/admin/assets/ContentEditPage-A4i8P2Jd.js +3 -0
- package/admin/assets/ContentListPage-Bc4mBIkB.js +1 -0
- package/admin/assets/CustomBlockPreview-CCssn6vF.js +479 -0
- package/admin/assets/FieldBuilder-YJGSk0nY.js +3 -0
- package/admin/assets/LoginPage-Jrne8-Wr.js +1 -0
- package/admin/assets/MediaPage-DfPQBmNf.css +1 -0
- package/admin/assets/MediaPage-_qNXqsZg.js +1 -0
- package/admin/assets/SiteSettingsPage-CoZnavij.js +1 -0
- package/admin/assets/SitesPage-ETqFT3nO.js +1 -0
- package/admin/assets/TagsPage-BGpp0XZM.js +1 -0
- package/admin/assets/UsersPage-CKRJpAb6.js +1 -0
- package/admin/assets/WebhookEditPage-BOcLe5OJ.js +1 -0
- package/admin/assets/WebhooksPage-Czco583Y.js +1 -0
- package/admin/assets/badge-0Z1nL6DI.js +1 -0
- package/admin/assets/card-D1-S-QZ6.js +1 -0
- package/admin/assets/check-BGA0ADyt.js +1 -0
- package/admin/assets/checkbox-BPqrj_XS.js +1 -0
- package/admin/assets/command-ChD319uJ.js +1 -0
- package/admin/assets/contentStatus-DfWHjFVB.js +1 -0
- package/admin/assets/copy-BqH9rXYM.js +1 -0
- package/admin/assets/core.esm-Csvubn5Q.js +5 -0
- package/admin/assets/format-CZ9bpk32.js +1 -0
- package/admin/assets/index-DOqKbrpW.css +1 -0
- package/admin/assets/index-Ds50UTAc.js +18 -0
- package/admin/assets/lib-BrI1UB_t.js +38 -0
- package/admin/assets/media-url-DIg_vSyf.js +1 -0
- package/admin/assets/module-RjUF93sV.js +716 -0
- package/admin/assets/native-48B9X9Wg.js +1 -0
- package/admin/assets/plus-BgHSYWJN.js +1 -0
- package/admin/assets/radix-DQ3amgxj.js +51 -0
- package/admin/assets/react-vendor-DNVhVxD7.js +4 -0
- package/admin/assets/search-DIzcfCVh.js +1 -0
- package/admin/assets/select-CJXZv4wv.js +1 -0
- package/admin/assets/sortable.esm-Zh-9QRSf.js +1 -0
- package/admin/assets/table-B3EHrN_H.js +1 -0
- package/admin/assets/tanstack-BO6c-AOu.js +1 -0
- package/admin/assets/trash-2-Gny2Upn-.js +1 -0
- package/admin/assets/useAdminResolver-BsQc_N4z.js +1 -0
- package/admin/assets/useContent-CSobIico.js +1 -0
- package/admin/assets/useDebouncedValue-Bf8UizjU.js +1 -0
- package/admin/assets/useMedia-CQnmMz4N.js +1 -0
- package/admin/assets/useTags-CYqbj5cK.js +1 -0
- package/admin/assets/useWebhooks-DXgtQ3aU.js +1 -0
- package/admin/index.html +21 -0
- package/admin/vite.svg +1 -0
- package/dist/admin-site.d.ts +30 -0
- package/dist/admin-site.d.ts.map +1 -0
- package/dist/admin-site.js +80 -0
- package/dist/admin-site.js.map +1 -0
- package/dist/api.d.ts +26 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +91 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +27 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +86 -0
- package/dist/auth.js.map +1 -0
- package/dist/cdn.d.ts +27 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +382 -0
- package/dist/cdn.js.map +1 -0
- package/dist/image.d.ts +21 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +48 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +124 -0
- package/dist/index.js.map +1 -0
- package/dist/storage.d.ts +21 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +125 -0
- package/dist/storage.js.map +1 -0
- package/dist/webhooks.d.ts +23 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +91 -0
- package/dist/webhooks.js.map +1 -0
- package/lambda/api/bootstrap +0 -0
- package/lambda/api/resource.enc +0 -0
- package/lambda/functions/custom-message/index.mjs +112 -0
- package/lambda/functions/custom-message/resource.enc +1 -0
- package/lambda/image-lambda/index.mjs +188 -0
- package/lambda/image-lambda/node_modules/.package-lock.json +160 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/README.md +46 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/lib/glib-2.0/include/glibconfig.h +220 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/lib/index.js +1 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/lib/libvips-cpp.so.42 +0 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/package.json +42 -0
- package/lambda/image-lambda/node_modules/@img/sharp-libvips-linux-arm64/versions.json +30 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/LICENSE +191 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/README.md +18 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/lib/sharp-linux-arm64.node +0 -0
- package/lambda/image-lambda/node_modules/@img/sharp-linux-arm64/package.json +46 -0
- package/lambda/image-lambda/node_modules/color/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/color/README.md +123 -0
- package/lambda/image-lambda/node_modules/color/index.js +496 -0
- package/lambda/image-lambda/node_modules/color/package.json +47 -0
- package/lambda/image-lambda/node_modules/color-convert/CHANGELOG.md +54 -0
- package/lambda/image-lambda/node_modules/color-convert/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/color-convert/README.md +68 -0
- package/lambda/image-lambda/node_modules/color-convert/conversions.js +839 -0
- package/lambda/image-lambda/node_modules/color-convert/index.js +81 -0
- package/lambda/image-lambda/node_modules/color-convert/package.json +48 -0
- package/lambda/image-lambda/node_modules/color-convert/route.js +97 -0
- package/lambda/image-lambda/node_modules/color-name/LICENSE +8 -0
- package/lambda/image-lambda/node_modules/color-name/README.md +11 -0
- package/lambda/image-lambda/node_modules/color-name/index.js +152 -0
- package/lambda/image-lambda/node_modules/color-name/package.json +28 -0
- package/lambda/image-lambda/node_modules/color-string/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/color-string/README.md +62 -0
- package/lambda/image-lambda/node_modules/color-string/index.js +242 -0
- package/lambda/image-lambda/node_modules/color-string/package.json +39 -0
- package/lambda/image-lambda/node_modules/detect-libc/LICENSE +201 -0
- package/lambda/image-lambda/node_modules/detect-libc/README.md +163 -0
- package/lambda/image-lambda/node_modules/detect-libc/index.d.ts +14 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/detect-libc.js +313 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/elf.js +39 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/filesystem.js +51 -0
- package/lambda/image-lambda/node_modules/detect-libc/lib/process.js +24 -0
- package/lambda/image-lambda/node_modules/detect-libc/package.json +44 -0
- package/lambda/image-lambda/node_modules/is-arrayish/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/is-arrayish/README.md +16 -0
- package/lambda/image-lambda/node_modules/is-arrayish/index.js +9 -0
- package/lambda/image-lambda/node_modules/is-arrayish/package.json +45 -0
- package/lambda/image-lambda/node_modules/semver/LICENSE +15 -0
- package/lambda/image-lambda/node_modules/semver/README.md +665 -0
- package/lambda/image-lambda/node_modules/semver/bin/semver.js +191 -0
- package/lambda/image-lambda/node_modules/semver/classes/comparator.js +143 -0
- package/lambda/image-lambda/node_modules/semver/classes/index.js +7 -0
- package/lambda/image-lambda/node_modules/semver/classes/range.js +557 -0
- package/lambda/image-lambda/node_modules/semver/classes/semver.js +333 -0
- package/lambda/image-lambda/node_modules/semver/functions/clean.js +8 -0
- package/lambda/image-lambda/node_modules/semver/functions/cmp.js +54 -0
- package/lambda/image-lambda/node_modules/semver/functions/coerce.js +62 -0
- package/lambda/image-lambda/node_modules/semver/functions/compare-build.js +9 -0
- package/lambda/image-lambda/node_modules/semver/functions/compare-loose.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/compare.js +7 -0
- package/lambda/image-lambda/node_modules/semver/functions/diff.js +60 -0
- package/lambda/image-lambda/node_modules/semver/functions/eq.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/gt.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/gte.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/inc.js +21 -0
- package/lambda/image-lambda/node_modules/semver/functions/lt.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/lte.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/major.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/minor.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/neq.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/parse.js +18 -0
- package/lambda/image-lambda/node_modules/semver/functions/patch.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/prerelease.js +8 -0
- package/lambda/image-lambda/node_modules/semver/functions/rcompare.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/rsort.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/satisfies.js +12 -0
- package/lambda/image-lambda/node_modules/semver/functions/sort.js +5 -0
- package/lambda/image-lambda/node_modules/semver/functions/valid.js +8 -0
- package/lambda/image-lambda/node_modules/semver/index.js +91 -0
- package/lambda/image-lambda/node_modules/semver/internal/constants.js +37 -0
- package/lambda/image-lambda/node_modules/semver/internal/debug.js +11 -0
- package/lambda/image-lambda/node_modules/semver/internal/identifiers.js +29 -0
- package/lambda/image-lambda/node_modules/semver/internal/lrucache.js +42 -0
- package/lambda/image-lambda/node_modules/semver/internal/parse-options.js +17 -0
- package/lambda/image-lambda/node_modules/semver/internal/re.js +223 -0
- package/lambda/image-lambda/node_modules/semver/package.json +78 -0
- package/lambda/image-lambda/node_modules/semver/preload.js +4 -0
- package/lambda/image-lambda/node_modules/semver/range.bnf +16 -0
- package/lambda/image-lambda/node_modules/semver/ranges/gtr.js +6 -0
- package/lambda/image-lambda/node_modules/semver/ranges/intersects.js +9 -0
- package/lambda/image-lambda/node_modules/semver/ranges/ltr.js +6 -0
- package/lambda/image-lambda/node_modules/semver/ranges/max-satisfying.js +27 -0
- package/lambda/image-lambda/node_modules/semver/ranges/min-satisfying.js +26 -0
- package/lambda/image-lambda/node_modules/semver/ranges/min-version.js +63 -0
- package/lambda/image-lambda/node_modules/semver/ranges/outside.js +82 -0
- package/lambda/image-lambda/node_modules/semver/ranges/simplify.js +49 -0
- package/lambda/image-lambda/node_modules/semver/ranges/subset.js +249 -0
- package/lambda/image-lambda/node_modules/semver/ranges/to-comparators.js +10 -0
- package/lambda/image-lambda/node_modules/semver/ranges/valid.js +13 -0
- package/lambda/image-lambda/node_modules/sharp/LICENSE +191 -0
- package/lambda/image-lambda/node_modules/sharp/README.md +118 -0
- package/lambda/image-lambda/node_modules/sharp/install/check.js +41 -0
- package/lambda/image-lambda/node_modules/sharp/lib/channel.js +174 -0
- package/lambda/image-lambda/node_modules/sharp/lib/colour.js +180 -0
- package/lambda/image-lambda/node_modules/sharp/lib/composite.js +210 -0
- package/lambda/image-lambda/node_modules/sharp/lib/constructor.js +452 -0
- package/lambda/image-lambda/node_modules/sharp/lib/index.d.ts +1754 -0
- package/lambda/image-lambda/node_modules/sharp/lib/index.js +16 -0
- package/lambda/image-lambda/node_modules/sharp/lib/input.js +658 -0
- package/lambda/image-lambda/node_modules/sharp/lib/is.js +169 -0
- package/lambda/image-lambda/node_modules/sharp/lib/libvips.js +203 -0
- package/lambda/image-lambda/node_modules/sharp/lib/operation.js +958 -0
- package/lambda/image-lambda/node_modules/sharp/lib/output.js +1587 -0
- package/lambda/image-lambda/node_modules/sharp/lib/resize.js +587 -0
- package/lambda/image-lambda/node_modules/sharp/lib/sharp.js +114 -0
- package/lambda/image-lambda/node_modules/sharp/lib/utility.js +296 -0
- package/lambda/image-lambda/node_modules/sharp/package.json +222 -0
- package/lambda/image-lambda/node_modules/sharp/src/binding.gyp +280 -0
- package/lambda/image-lambda/node_modules/sharp/src/common.cc +1091 -0
- package/lambda/image-lambda/node_modules/sharp/src/common.h +393 -0
- package/lambda/image-lambda/node_modules/sharp/src/metadata.cc +320 -0
- package/lambda/image-lambda/node_modules/sharp/src/metadata.h +85 -0
- package/lambda/image-lambda/node_modules/sharp/src/operations.cc +475 -0
- package/lambda/image-lambda/node_modules/sharp/src/operations.h +125 -0
- package/lambda/image-lambda/node_modules/sharp/src/pipeline.cc +1758 -0
- package/lambda/image-lambda/node_modules/sharp/src/pipeline.h +393 -0
- package/lambda/image-lambda/node_modules/sharp/src/sharp.cc +40 -0
- package/lambda/image-lambda/node_modules/sharp/src/stats.cc +183 -0
- package/lambda/image-lambda/node_modules/sharp/src/stats.h +59 -0
- package/lambda/image-lambda/node_modules/sharp/src/utilities.cc +269 -0
- package/lambda/image-lambda/node_modules/sharp/src/utilities.h +19 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/LICENSE +21 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/README.md +43 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/index.js +29 -0
- package/lambda/image-lambda/node_modules/simple-swizzle/package.json +36 -0
- package/lambda/webhook-worker/bootstrap +0 -0
- package/lambda/webhook-worker/resource.enc +0 -0
- package/package.json +50 -0
- package/src/admin-site.ts +108 -0
- package/src/api.ts +113 -0
- package/src/auth.ts +110 -0
- package/src/cdn.ts +449 -0
- package/src/image.ts +62 -0
- package/src/index.ts +216 -0
- package/src/sst-env.d.ts +143 -0
- package/src/storage.ts +138 -0
- package/src/webhooks.ts +114 -0
package/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Headroom CMS
|
|
2
|
+
|
|
3
|
+
Serverless headless CMS for AWS, deployed with [SST](https://sst.dev).
|
|
4
|
+
|
|
5
|
+
Headroom provisions a complete CMS stack — DynamoDB, S3, Lambda, CloudFront, Cognito, and an admin UI — from a single SST component. No Go toolchain or build step required; pre-compiled binaries and a pre-built admin UI are included in the package.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm create headroom my-cms
|
|
11
|
+
cd my-cms
|
|
12
|
+
npx sst deploy --stage production
|
|
13
|
+
./scripts/create-admin.sh admin@example.com 'YourPassword123!'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
After deploying, the CLI prints three URLs:
|
|
17
|
+
|
|
18
|
+
- **API** — the Lambda function URL for the content API
|
|
19
|
+
- **CDN** — the CloudFront distribution (cached, edge-authenticated)
|
|
20
|
+
- **Admin** — the admin UI
|
|
21
|
+
|
|
22
|
+
Open the Admin URL and sign in with the credentials you just created.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- **Node.js 22+** and **pnpm** (or npm)
|
|
27
|
+
- **AWS credentials** configured (`aws configure` or environment variables)
|
|
28
|
+
- **SST v3** (installed automatically as a dev dependency)
|
|
29
|
+
- **AWS CLI** (for the `create-admin.sh` and `get-token.sh` helper scripts)
|
|
30
|
+
- **jq** (used by helper scripts to read SST outputs)
|
|
31
|
+
|
|
32
|
+
## Configuration Reference
|
|
33
|
+
|
|
34
|
+
Your `sst.config.ts` imports `HeadroomCMS` and passes a configuration object:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
/// <reference path="./.sst/platform/config.d.ts" />
|
|
38
|
+
import { HeadroomCMS } from "headroom-cms";
|
|
39
|
+
|
|
40
|
+
export default $config({
|
|
41
|
+
app(input) {
|
|
42
|
+
return {
|
|
43
|
+
name: "my-cms",
|
|
44
|
+
removal: input?.stage === "production" ? "retain" : "remove",
|
|
45
|
+
protect: input?.stage === "production",
|
|
46
|
+
home: "aws",
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
async run() {
|
|
50
|
+
const cms = new HeadroomCMS("CMS", {
|
|
51
|
+
senderEmail: "cms@mycompany.com",
|
|
52
|
+
|
|
53
|
+
// All options below are optional
|
|
54
|
+
domain: {
|
|
55
|
+
name: "api.mycompany.com",
|
|
56
|
+
certificateArn: "arn:aws:acm:us-east-1:...:certificate/...",
|
|
57
|
+
},
|
|
58
|
+
adminDomain: {
|
|
59
|
+
name: "cms.mycompany.com",
|
|
60
|
+
certificateArn: "arn:aws:acm:us-east-1:...:certificate/...",
|
|
61
|
+
},
|
|
62
|
+
priceClass: "PriceClass_100",
|
|
63
|
+
apiCacheTtl: 3600,
|
|
64
|
+
passwordPolicy: {
|
|
65
|
+
minimumLength: 12,
|
|
66
|
+
requireLowercase: true,
|
|
67
|
+
requireUppercase: true,
|
|
68
|
+
requireNumbers: true,
|
|
69
|
+
requireSymbols: true,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return cms.outputs;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Options
|
|
79
|
+
|
|
80
|
+
| Option | Type | Default | Description |
|
|
81
|
+
|--------|------|---------|-------------|
|
|
82
|
+
| `senderEmail` | `string` | **(required)** | Email for admin invitations and password resets. Must be verified in SES (see [Troubleshooting](#troubleshooting)). |
|
|
83
|
+
| `domain` | `{ name, certificateArn }` | CloudFront default | Custom domain for the CDN / API endpoint. |
|
|
84
|
+
| `adminDomain` | `{ name, certificateArn }` | CloudFront default | Custom domain for the admin UI. |
|
|
85
|
+
| `priceClass` | `"PriceClass_100"` \| `"PriceClass_200"` \| `"PriceClass_All"` | `"PriceClass_100"` | CloudFront price class. `100` = US/Canada/Europe, `200` adds Asia/Africa, `All` = global. |
|
|
86
|
+
| `apiCacheTtl` | `number` | `3600` | API response cache TTL in seconds at the CloudFront edge. |
|
|
87
|
+
| `passwordPolicy` | `object` | See below | Cognito password policy overrides. |
|
|
88
|
+
|
|
89
|
+
#### Password Policy Defaults
|
|
90
|
+
|
|
91
|
+
| Field | Default |
|
|
92
|
+
|-------|---------|
|
|
93
|
+
| `minimumLength` | `12` |
|
|
94
|
+
| `requireLowercase` | `true` |
|
|
95
|
+
| `requireUppercase` | `true` |
|
|
96
|
+
| `requireNumbers` | `true` |
|
|
97
|
+
| `requireSymbols` | `true` |
|
|
98
|
+
|
|
99
|
+
### Outputs
|
|
100
|
+
|
|
101
|
+
The component returns these outputs (available in `.sst/outputs.json` after deploy):
|
|
102
|
+
|
|
103
|
+
| Output | Description |
|
|
104
|
+
|--------|-------------|
|
|
105
|
+
| `api` | Lambda function URL for the Go API |
|
|
106
|
+
| `cdn` | CloudFront distribution URL (cached, edge-authenticated) |
|
|
107
|
+
| `admin` | Admin UI URL |
|
|
108
|
+
| `userPoolId` | Cognito User Pool ID |
|
|
109
|
+
| `userPoolClientId` | Cognito User Pool Client ID |
|
|
110
|
+
|
|
111
|
+
## Custom Domains
|
|
112
|
+
|
|
113
|
+
To use custom domains for the CDN or admin UI:
|
|
114
|
+
|
|
115
|
+
1. **Request an ACM certificate** in **us-east-1** (required for CloudFront):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
aws acm request-certificate \
|
|
119
|
+
--domain-name api.mycompany.com \
|
|
120
|
+
--validation-method DNS \
|
|
121
|
+
--region us-east-1
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
2. **Validate the certificate** by adding the DNS records ACM provides (check the AWS Console or use `aws acm describe-certificate`).
|
|
125
|
+
|
|
126
|
+
3. **Add the domain config** to your `sst.config.ts`:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const cms = new HeadroomCMS("CMS", {
|
|
130
|
+
senderEmail: "cms@mycompany.com",
|
|
131
|
+
domain: {
|
|
132
|
+
name: "api.mycompany.com",
|
|
133
|
+
certificateArn: "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
4. **Deploy**, then create a **CNAME** (or alias) DNS record pointing your domain to the CloudFront distribution domain shown in the deploy output.
|
|
139
|
+
|
|
140
|
+
Repeat for `adminDomain` if you want a custom domain for the admin UI.
|
|
141
|
+
|
|
142
|
+
## Updating
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pnpm update headroom-cms
|
|
146
|
+
npx sst deploy --stage production
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
SST detects changes in the component and updates Lambda code, admin UI assets, and any infrastructure changes automatically.
|
|
150
|
+
|
|
151
|
+
### Version Policy
|
|
152
|
+
|
|
153
|
+
Headroom follows semantic versioning:
|
|
154
|
+
|
|
155
|
+
- **Patch** (0.1.x) — Bug fixes, no infrastructure changes
|
|
156
|
+
- **Minor** (0.x.0) — New features, backward-compatible infrastructure additions
|
|
157
|
+
- **Major** (x.0.0) — Breaking changes to config interface or infrastructure that may require manual steps
|
|
158
|
+
|
|
159
|
+
## Troubleshooting
|
|
160
|
+
|
|
161
|
+
### SES Email Verification
|
|
162
|
+
|
|
163
|
+
Headroom creates an SES email identity for your `senderEmail` automatically, but AWS requires you to verify it. Check the inbox for that address and click the verification link. Until verified, admin invitation emails won't send.
|
|
164
|
+
|
|
165
|
+
If your AWS account is still in the SES sandbox, you can only send to verified email addresses. Request production access in the AWS Console under SES > Account dashboard.
|
|
166
|
+
|
|
167
|
+
### IAM Permissions
|
|
168
|
+
|
|
169
|
+
The deploying user/role needs broad permissions to create DynamoDB tables, S3 buckets, Lambda functions, CloudFront distributions, Cognito user pools, SQS queues, and IAM roles. If you get `AccessDenied` errors during deploy, ensure your credentials have `AdministratorAccess` or an equivalent policy.
|
|
170
|
+
|
|
171
|
+
### Deploy Errors
|
|
172
|
+
|
|
173
|
+
- **"Resource already exists"** — You may have resources from a previous deployment in the same AWS account/region. Use a different SST stage name or clean up the old resources.
|
|
174
|
+
- **"CREATE_FAILED ... custom-message Lambda"** — This usually means the Node.js runtime version isn't available in your region. Ensure you're deploying to a standard region.
|
|
175
|
+
- **Timeout during deploy** — CloudFront distribution creation can take 5-15 minutes on first deploy. This is normal.
|
|
176
|
+
|
|
177
|
+
### Helper Scripts
|
|
178
|
+
|
|
179
|
+
The scaffolded project includes two helper scripts in `scripts/`:
|
|
180
|
+
|
|
181
|
+
- **`create-admin.sh <email> <password>`** — Creates a Cognito admin user. Password must be 12+ characters with uppercase, lowercase, numbers, and symbols.
|
|
182
|
+
- **`get-token.sh <email> <password>`** — Returns a JWT access token for testing admin API endpoints.
|
|
183
|
+
|
|
184
|
+
Both scripts read from `.sst/outputs.json`, so you must deploy before running them.
|
|
185
|
+
|
|
186
|
+
## What Gets Deployed
|
|
187
|
+
|
|
188
|
+
A single `HeadroomCMS` component creates:
|
|
189
|
+
|
|
190
|
+
- **11 DynamoDB tables** (sites, content, collections, block types, media, API keys, tags, audit, relationships, webhooks, webhook deliveries)
|
|
191
|
+
- **1 S3 bucket** (media files and content bodies)
|
|
192
|
+
- **1 CloudFront KVS** (edge auth cache and site version tracking)
|
|
193
|
+
- **1 Cognito User Pool + Client** (admin authentication)
|
|
194
|
+
- **1 SES Email Identity** (admin invitations)
|
|
195
|
+
- **4 Lambda functions** (API, webhook worker, image transform, custom message)
|
|
196
|
+
- **1 CloudFront Distribution** with 2 CloudFront Functions (edge auth, media URL rewrite)
|
|
197
|
+
- **1 Admin UI** (static site on CloudFront)
|
|
198
|
+
- **2 SQS queues** (webhook delivery + dead letter queue)
|
|
199
|
+
|
|
200
|
+
## Advanced: Forking
|
|
201
|
+
|
|
202
|
+
If you need to modify Go API handlers, add custom admin UI pages, change DynamoDB schemas, or make other changes that the configuration options don't cover, you can eject to a full source checkout.
|
|
203
|
+
|
|
204
|
+
This is a one-way migration: once ejected, updates come from merging upstream changes rather than `pnpm update`.
|
|
205
|
+
|
|
206
|
+
### When to Eject
|
|
207
|
+
|
|
208
|
+
The packaged component covers most use cases. Eject only when you need to:
|
|
209
|
+
|
|
210
|
+
- Add custom API endpoints or middleware in the Go Lambda
|
|
211
|
+
- Add custom admin UI pages or block editor plugins
|
|
212
|
+
- Change DynamoDB table schemas
|
|
213
|
+
- Wire additional Lambda functions into the same infrastructure
|
|
214
|
+
- Replace a subsystem entirely (e.g., swap Cognito for a different auth provider)
|
|
215
|
+
|
|
216
|
+
### Ejection Steps
|
|
217
|
+
|
|
218
|
+
1. **Clone at your current version:**
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
git clone --branch v0.1.0 https://github.com/cykod/headroom.git my-cms-custom
|
|
222
|
+
cd my-cms-custom
|
|
223
|
+
pnpm install
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
2. **Copy your SST state** so SST recognizes existing AWS resources:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
cp -r ../my-cms/.sst ./
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
3. **Update `sst.config.ts`** to use local source with the `dev` option:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
/// <reference path="./.sst/platform/config.d.ts" />
|
|
236
|
+
import { HeadroomCMS } from "./packages/headroom-cms/src/index.js";
|
|
237
|
+
|
|
238
|
+
export default $config({
|
|
239
|
+
app(input) {
|
|
240
|
+
return {
|
|
241
|
+
name: "my-cms", // must match your previous app name
|
|
242
|
+
removal: input?.stage === "production" ? "retain" : "remove",
|
|
243
|
+
protect: input?.stage === "production",
|
|
244
|
+
home: "aws",
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
async run() {
|
|
248
|
+
const cms = new HeadroomCMS("CMS", {
|
|
249
|
+
senderEmail: "cms@mycompany.com",
|
|
250
|
+
dev: {
|
|
251
|
+
apiHandler: "packages/api",
|
|
252
|
+
webhookWorkerHandler: "packages/webhook-worker",
|
|
253
|
+
customMessageHandler: "packages/functions/custom-message.handler",
|
|
254
|
+
imageLambdaHandler: "packages/image-lambda/index.handler",
|
|
255
|
+
adminPath: "packages/admin",
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return cms.outputs;
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
4. **Deploy to verify** the state transfer (should be a no-op if config matches):
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
npx sst deploy --stage production
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
5. **Start customizing.** Key directories:
|
|
271
|
+
|
|
272
|
+
| Directory | What it is |
|
|
273
|
+
|-----------|-----------|
|
|
274
|
+
| `packages/api/` | Go Lambda API — add routes in `main.go`, handlers in `internal/handler/` |
|
|
275
|
+
| `packages/admin/` | React admin UI — add pages in `src/pages/`, components in `src/components/` |
|
|
276
|
+
| `packages/headroom-cms/src/` | SST infrastructure component — modify DynamoDB schemas, add resources |
|
|
277
|
+
| `packages/image-lambda/` | Sharp image transform Lambda |
|
|
278
|
+
| `packages/webhook-worker/` | Webhook delivery worker |
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
PolyForm Noncommercial 1.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as e}from"./tanstack-BO6c-AOu.js";import{u as H,r as x}from"./react-vendor-DNVhVxD7.js";import{n as L,u as q,a2 as B,a3 as _,o as z,a4 as M,a5 as V,S as y,D as G,h as $,B as l,i as J,j as K,k as Q,l as W,I as X,m as Y,s as i}from"./index-Ds50UTAc.js";import{u as Z}from"./useAdminResolver-BsQc_N4z.js";import{T as ee,a as se,b,c as p,d as ae,e as j}from"./table-B3EHrN_H.js";import{P as ie,a as te,b as ne,C as re,c as oe,d as le,e as de,f as ce,g as me}from"./command-ChD319uJ.js";import{S as ue,a as he,b as xe,c as pe,d as C}from"./select-CJXZv4wv.js";import{P as je}from"./plus-BgHSYWJN.js";import{T as ve}from"./trash-2-Gny2Upn-.js";import"./radix-DQ3amgxj.js";import"./search-DIzcfCVh.js";import"./check-BGA0ADyt.js";const fe=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}],["line",{x1:"19",x2:"19",y1:"8",y2:"14",key:"1bvyxn"}],["line",{x1:"22",x2:"16",y1:"11",y2:"11",key:"1shjgl"}]],ge=L("user-plus",fe);function Re(){const{host:d}=H(),{user:N}=q(),S=B(),{data:A,isLoading:I}=_(d),{data:T}=z(),c=M(d),m=V(d),n=A?.admins??[],E=T?.items??[],r=S==="admin",v=n.map(s=>s.id),{resolve:w}=Z(v),[D,f]=x.useState(!1),[P,g]=x.useState(!1),[u,h]=x.useState("");if(I)return e.jsxs("div",{className:"space-y-2",children:[e.jsx(y,{className:"h-8 w-64"}),Array.from({length:3}).map((s,a)=>e.jsx(y,{className:"h-12 w-full"},a))]});const R=E.filter(s=>!v.includes(s.sub));async function k(s){try{await c.mutateAsync({admins:[...n,{id:s,role:"editor"}]}),i.success("Admin added"),f(!1)}catch(a){i.error("Failed to add admin",{description:a instanceof Error?a.message:void 0})}}async function U(s){if(s.preventDefault(),!!u.trim())try{const a=await m.mutateAsync({email:u.trim()});a.existed?i.success("Existing user added to site"):i.success("Invite email sent",{description:`An invite has been sent to ${a.email}`}),h(""),g(!1)}catch(a){i.error("Failed to invite user",{description:a instanceof Error?a.message:void 0})}}async function O(s){try{await c.mutateAsync({admins:n.filter(a=>a.id!==s)}),i.success("Admin removed")}catch(a){i.error("Failed to remove admin",{description:a instanceof Error?a.message:void 0})}}async function F(s,a){const o=n.map(t=>t.id===s?{...t,role:a}:t);if(!o.some(t=>t.role==="admin")){i.error("At least one admin role is required");return}try{await c.mutateAsync({admins:o}),i.success("Role updated")}catch(t){i.error("Failed to update role",{description:t instanceof Error?t.message:void 0})}}return e.jsxs("div",{children:[e.jsx("h1",{className:"mb-6 text-2xl font-semibold",children:"Admins"}),r&&e.jsxs("div",{className:"mb-6 flex gap-2",children:[e.jsxs(G,{open:P,onOpenChange:s=>{g(s),s||h("")},children:[e.jsx($,{asChild:!0,children:e.jsxs(l,{children:[e.jsx(ge,{className:"mr-2 h-4 w-4"}),"Invite by email"]})}),e.jsxs(J,{children:[e.jsxs(K,{children:[e.jsx(Q,{children:"Invite User to Site"}),e.jsx(W,{className:"sr-only",children:"Send an invite email to add a user to this site"})]}),e.jsxs("form",{onSubmit:U,className:"space-y-4",children:[e.jsx("p",{className:"text-muted-foreground text-sm",children:"Enter an email address. If the user doesn't have an account yet, they'll receive an invite email."}),e.jsx(X,{type:"email",value:u,onChange:s=>h(s.target.value),placeholder:"email@example.com",required:!0}),e.jsx(Y,{children:e.jsx(l,{type:"submit",disabled:m.isPending,children:m.isPending?"Inviting...":"Send Invite"})})]})]})]}),e.jsxs(ie,{open:D,onOpenChange:f,children:[e.jsx(te,{asChild:!0,children:e.jsxs(l,{variant:"outline",children:[e.jsx(je,{className:"mr-2 h-4 w-4"}),"Add existing user"]})}),e.jsx(ne,{className:"w-72 p-0",align:"start",children:e.jsxs(re,{children:[e.jsx(oe,{placeholder:"Search users..."}),e.jsxs(le,{children:[e.jsx(de,{children:"No users found."}),e.jsx(ce,{children:R.map(s=>e.jsx(me,{value:s.email,onSelect:()=>k(s.sub),children:s.email},s.sub))})]})]})})]})]}),n.length===0?e.jsx("p",{className:"text-muted-foreground",children:"No admins configured."}):e.jsxs(ee,{children:[e.jsx(se,{children:e.jsxs(b,{children:[e.jsx(p,{children:"Email"}),e.jsx(p,{children:"Role"}),r&&e.jsx(p,{})]})}),e.jsx(ae,{children:n.map(s=>{const a=s.id===N?.sub;return e.jsxs(b,{children:[e.jsxs(j,{className:"text-sm",children:[w(s.id),a&&e.jsx("span",{className:"ml-2 text-muted-foreground",children:"(you)"})]}),e.jsx(j,{children:r&&!a?e.jsxs(ue,{value:s.role,onValueChange:o=>F(s.id,o),children:[e.jsx(he,{className:"w-28",children:e.jsx(xe,{})}),e.jsxs(pe,{children:[e.jsx(C,{value:"admin",children:"Admin"}),e.jsx(C,{value:"editor",children:"Editor"})]})]}):e.jsx("span",{className:"text-sm capitalize",children:s.role})}),r&&e.jsx(j,{className:"text-right",children:!a&&e.jsx(l,{variant:"ghost",size:"sm",onClick:()=>O(s.id),title:"Remove from site",children:e.jsx(ve,{className:"h-4 w-4"})})})]},s.id)})})]})]})}export{Re as AdminsPage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as s}from"./tanstack-BO6c-AOu.js";import{u as P,e as T,r as p}from"./react-vendor-DNVhVxD7.js";import{u as U,a as I,b as D,c as L}from"./useContent-CSobIico.js";import{m as M}from"./media-url-DIg_vSyf.js";import{t as R,s as c,S as z,B as E}from"./index-Ds50UTAc.js";import{B as g}from"./badge-0Z1nL6DI.js";import{C as b}from"./checkbox-BPqrj_XS.js";import{T as H,a as F,b as S,c as o,d as V,e as r}from"./table-B3EHrN_H.js";import{f as _}from"./format-CZ9bpk32.js";import{B as q}from"./BulkActionBar-BMcUBJSH.js";import"./radix-DQ3amgxj.js";import"./check-BGA0ADyt.js";function le(){const{host:i}=P(),k=T(),{collections:C}=R(),[w,N]=p.useState(),[n,a]=p.useState(new Set),{data:d,isLoading:v}=U(i,w),m=I(i),x=D(i),f=L(i),y=new Map(C.map(e=>[e.name,e.label])),h=d?.items.map(e=>e.contentId)??[],j=h.length>0&&h.every(e=>n.has(e));function A(e){a(l=>{const t=new Set(l);return t.has(e)?t.delete(e):t.add(e),t})}function $(){a(j?new Set:new Set(h))}const B=m.isPending||x.isPending||f.isPending;return s.jsxs("div",{children:[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(q,{selectedCount:n.size,onPublish:async()=>{const e=[...n],t=(await m.mutateAsync(e)).filter(u=>u.status==="rejected").length;t?c.error(`${t} of ${e.length} failed to publish`):c.success(`${e.length} item(s) published`),a(new Set)},onUnpublish:async()=>{const e=[...n],t=(await x.mutateAsync(e)).filter(u=>u.status==="rejected").length;t?c.error(`${t} of ${e.length} failed to unpublish`):c.success(`${e.length} item(s) unpublished`),a(new Set)},onDelete:async()=>{const e=[...n],t=(await f.mutateAsync(e)).filter(u=>u.status==="rejected").length;t?c.error(`${t} of ${e.length} failed to delete`):c.success(`${e.length} item(s) deleted`),a(new Set)},onClear:()=>a(new Set),isPending:B}),v?s.jsx("div",{className:"space-y-2",children:Array.from({length:5}).map((e,l)=>s.jsx(z,{className:"h-12 w-full"},l))}):d?.items.length?s.jsxs(s.Fragment,{children:[s.jsxs(H,{children:[s.jsx(F,{children:s.jsxs(S,{children:[s.jsx(o,{className:"w-10",children:s.jsx(b,{checked:j,onCheckedChange:$,"aria-label":"Select all"})}),s.jsx(o,{children:"Title"}),s.jsx(o,{children:"Slug"}),s.jsx(o,{children:"Collection"}),s.jsx(o,{children:"Status"}),s.jsx(o,{children:"Updated"})]})}),s.jsx(V,{children:d.items.map(e=>s.jsxs(S,{className:"cursor-pointer",onClick:()=>k(`/sites/${i}/content/${e.collection}/${e.contentId}`),children:[s.jsx(r,{onClick:l=>l.stopPropagation(),children:s.jsx(b,{checked:n.has(e.contentId),onCheckedChange:()=>A(e.contentId),"aria-label":`Select ${e.title||"Untitled"}`})}),s.jsx(r,{className:"font-medium",children:s.jsxs("div",{className:"flex items-center gap-3",children:[!!e.coverUrl&&s.jsx("img",{src:M(e.coverUrl),alt:"",className:"h-8 w-8 rounded object-cover flex-shrink-0"}),s.jsx("span",{children:e.title||"Untitled"})]})}),s.jsx(r,{className:"text-muted-foreground text-sm",children:e.slug||"—"}),s.jsx(r,{children:s.jsx(g,{variant:"outline",children:y.get(e.collection)??e.collection})}),s.jsx(r,{children:s.jsx(g,{variant:e.publishedAt?"default":"secondary",children:e.publishedAt?"Published":"Draft"})}),s.jsx(r,{className:"text-muted-foreground",children:e.publishedAt?_(e.publishedAt):"—"})]},e.contentId))})]}),d.hasMore&&s.jsx("div",{className:"mt-4 flex justify-center",children:s.jsx(E,{variant:"outline",onClick:()=>N(d.cursor??void 0),children:"Load More"})})]}):s.jsx("p",{className:"text-muted-foreground",children:"No content yet."})]})}export{le as AllContentPage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as L,a as N,b as A,j as e}from"./tanstack-BO6c-AOu.js";import{u as E,r as m}from"./react-vendor-DNVhVxD7.js";import{E as f,B as r,S as q,D as K,i as D,j as w,k as I,l as T,I as g,m as p,L as O,s as x}from"./index-Ds50UTAc.js";import{T as G,a as Q,b as k,c as d,d as R,e as u}from"./table-B3EHrN_H.js";import{f as C}from"./format-CZ9bpk32.js";import{P as $}from"./plus-BgHSYWJN.js";import{T as B}from"./trash-2-Gny2Upn-.js";import{C as H}from"./copy-BqH9rXYM.js";import"./radix-DQ3amgxj.js";function U(s){const t=f();return L({queryKey:["sites",s,"api-keys"],queryFn:async()=>({items:(await t.apiFetch(`/v1/admin/sites/${s}/api-keys`)).keys}),enabled:!!s})}function z(s){const t=f(),n=N();return A({mutationFn:i=>t.apiFetch(`/v1/admin/sites/${s}/api-keys`,{method:"POST",body:JSON.stringify(i)}),onSuccess:()=>n.invalidateQueries({queryKey:["sites",s,"api-keys"]})})}function J(s,t){const n=f(),i=N();return A({mutationFn:()=>n.apiFetch(`/v1/admin/sites/${s}/api-keys/${t}`,{method:"DELETE"}),onSuccess:()=>i.invalidateQueries({queryKey:["sites",s,"api-keys"]})})}function te(){const{host:s}=E(),{data:t,isLoading:n}=U(s),i=z(s),[h,l]=m.useState(!1),[b,y]=m.useState(""),[o,j]=m.useState(null),[P,v]=m.useState(null);async function F(a){a.preventDefault();try{const c=await i.mutateAsync({label:b});j(c.key),y("")}catch(c){x.error("Failed to create API key",{description:c instanceof Error?c.message:void 0})}}function S(){o&&(navigator.clipboard.writeText(o),x.success("Copied to clipboard"))}return e.jsxs("div",{children:[e.jsxs("div",{className:"mb-6 flex items-center justify-between",children:[e.jsx("h1",{className:"text-2xl font-semibold",children:"API Keys"}),e.jsxs(r,{onClick:()=>l(!0),children:[e.jsx($,{className:"mr-2 h-4 w-4"}),"Generate Key"]})]}),n?e.jsx("div",{className:"space-y-2",children:Array.from({length:3}).map((a,c)=>e.jsx(q,{className:"h-12 w-full"},c))}):t?.items.length?e.jsxs(G,{children:[e.jsx(Q,{children:e.jsxs(k,{children:[e.jsx(d,{children:"Label"}),e.jsx(d,{children:"Key ID"}),e.jsx(d,{children:"Created"}),e.jsx(d,{children:"Last Used"}),e.jsx(d,{})]})}),e.jsx(R,{children:t.items.map(a=>e.jsxs(k,{children:[e.jsx(u,{className:"font-medium",children:a.label}),e.jsx(u,{className:"font-mono text-xs",children:a.keyId}),e.jsx(u,{className:"text-muted-foreground",children:C(a.createdAt)}),e.jsx(u,{className:"text-muted-foreground",children:a.lastUsedAt?C(a.lastUsedAt):"Never"}),e.jsx(u,{children:e.jsx(r,{variant:"ghost",size:"sm",onClick:()=>v(a.keyId),children:e.jsx(B,{className:"h-4 w-4"})})})]},a.keyId))})]}):e.jsx("p",{className:"text-muted-foreground",children:"No API keys."}),e.jsx(K,{open:h,onOpenChange:a=>{l(a),a||(j(null),y(""))},children:e.jsxs(D,{children:[e.jsxs(w,{children:[e.jsx(I,{children:o?"API Key Generated":"Generate API Key"}),e.jsx(T,{className:"sr-only",children:"Create a new API key for site access"})]}),o?e.jsxs("div",{className:"space-y-4",children:[e.jsx("p",{className:"text-sm text-muted-foreground",children:"Copy this key now. You won't be able to see it again."}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx(g,{value:o,readOnly:!0,className:"font-mono text-xs"}),e.jsx(r,{variant:"outline",size:"icon",onClick:S,children:e.jsx(H,{className:"h-4 w-4"})})]}),e.jsx(p,{children:e.jsx(r,{onClick:()=>{l(!1),j(null)},children:"Done"})})]}):e.jsxs("form",{onSubmit:F,className:"space-y-4",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(O,{htmlFor:"key-label",children:"Label"}),e.jsx(g,{id:"key-label",value:b,onChange:a=>y(a.target.value),required:!0,autoFocus:!0})]}),e.jsxs(p,{children:[e.jsx(r,{variant:"outline",onClick:()=>l(!1),children:"Cancel"}),e.jsx(r,{type:"submit",disabled:i.isPending,children:"Generate"})]})]})]})}),e.jsx(M,{host:s,keyId:P,onClose:()=>v(null)})]})}function M({host:s,keyId:t,onClose:n}){const i=J(s,t??"");async function h(){try{await i.mutateAsync(),x.success("API key revoked"),n()}catch(l){x.error("Failed to revoke",{description:l instanceof Error?l.message:void 0})}}return e.jsx(K,{open:!!t,onOpenChange:()=>n(),children:e.jsxs(D,{children:[e.jsxs(w,{children:[e.jsx(I,{children:"Revoke API key?"}),e.jsx(T,{children:"This action cannot be undone. Any applications using this key will lose access."})]}),e.jsxs(p,{children:[e.jsx(r,{variant:"outline",onClick:n,children:"Cancel"}),e.jsx(r,{variant:"destructive",onClick:h,children:"Revoke"})]})]})})}export{te as ApiKeysPage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as x,j as e}from"./tanstack-BO6c-AOu.js";import{u as h,r as f}from"./react-vendor-DNVhVxD7.js";import{E as j,S as b,B as g}from"./index-Ds50UTAc.js";import{u as p}from"./useAdminResolver-BsQc_N4z.js";import{T as A,a as N,b as c,c as n,d as T,e as o}from"./table-B3EHrN_H.js";import{f as y}from"./format-CZ9bpk32.js";import"./radix-DQ3amgxj.js";function S(a,t){const d=j(),s=new URLSearchParams;t?.action&&s.set("action",t.action),t?.before&&s.set("before",String(t.before));const r=s.toString();return x({queryKey:["sites",a,"audit",t?.action??"",t?.before??""],queryFn:()=>d.apiFetch(`/v1/admin/sites/${a}/audit${r?`?${r}`:""}`),enabled:!!a})}function C(){const{host:a}=h(),[t,d]=f.useState(),{data:s,isLoading:r}=S(a,{before:t}),l=s?.items.map(i=>i.adminId)??[],{resolve:m}=p(l);return e.jsxs("div",{children:[e.jsx("h1",{className:"mb-6 text-2xl font-semibold",children:"Audit Log"}),r?e.jsx("div",{className:"space-y-2",children:Array.from({length:5}).map((i,u)=>e.jsx(b,{className:"h-12 w-full"},u))}):s?.items.length?e.jsxs(e.Fragment,{children:[e.jsxs(A,{children:[e.jsx(N,{children:e.jsxs(c,{children:[e.jsx(n,{children:"Action"}),e.jsx(n,{children:"Admin"}),e.jsx(n,{children:"Status"}),e.jsx(n,{children:"Time"})]})}),e.jsx(T,{children:s.items.map(i=>e.jsxs(c,{children:[e.jsx(o,{className:"font-medium",children:i.action}),e.jsx(o,{className:"text-sm",children:m(i.adminId)}),e.jsx(o,{children:e.jsx("span",{className:"text-muted-foreground",children:i.status})}),e.jsx(o,{className:"text-muted-foreground",children:y(i.createdAt)})]},i.eventId))})]}),s.hasMore&&s.items.length>0&&e.jsx("div",{className:"mt-4 flex justify-center",children:e.jsx(g,{variant:"outline",onClick:()=>d(s.items[s.items.length-1].createdAt),children:"Load More"})})]}):e.jsx("p",{className:"text-muted-foreground",children:"No audit events."})]})}export{C as AuditPage};
|