@abinnovision/payloadcms-mcpx 2.0.0-beta.13 → 2.0.0-beta.15
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 +50 -35
- package/dist/admin/capability-matrix.d.mts +4 -3
- package/dist/admin/capability-matrix.mjs +262 -111
- package/dist/admin/capability-toggles.mjs +84 -40
- package/dist/admin/confirmations-panel.mjs +242 -0
- package/dist/admin/confirmations.d.mts +15 -0
- package/dist/admin/confirmations.mjs +70 -0
- package/dist/admin/index.d.mts +2 -1
- package/dist/admin/index.mjs +2 -1
- package/dist/api-keys/capability-matrix.d.mts +5 -4
- package/dist/api-keys/capability-matrix.mjs +33 -12
- package/dist/api-keys/collection.mjs +3 -3
- package/dist/api-keys/confirmation-view.mjs +6 -0
- package/dist/api-keys/fields.mjs +20 -3
- package/dist/builtin-tool-names.mjs +3 -1
- package/dist/capabilities.mjs +9 -4
- package/dist/define-tool.d.mts +4 -4
- package/dist/define-tool.mjs +8 -1
- package/dist/endpoint/confirmations.mjs +124 -0
- package/dist/endpoint/handler.mjs +2 -2
- package/dist/endpoint/server.mjs +22 -18
- package/dist/endpoint/upload.mjs +5 -4
- package/dist/grants/confirmation.mjs +106 -0
- package/dist/{upload → grants}/grant.mjs +39 -16
- package/dist/options.mjs +32 -6
- package/dist/plugin.d.mts +2 -1
- package/dist/plugin.mjs +5 -2
- package/dist/tools/builtin.mjs +5 -1
- package/dist/tools/create-document.mjs +1 -1
- package/dist/tools/delete-document.mjs +157 -0
- package/dist/tools/find-versions.mjs +1 -1
- package/dist/tools/get-document.mjs +1 -1
- package/dist/tools/list-capabilities.mjs +2 -2
- package/dist/tools/patch-document.mjs +2 -2
- package/dist/tools/publish-document.mjs +1 -1
- package/dist/tools/run-confirmed.mjs +126 -0
- package/dist/tools/shared.mjs +4 -0
- package/dist/tools/validate-document.mjs +2 -2
- package/dist/types.d.mts +130 -19
- package/dist/upload/file.mjs +3 -3
- package/dist/write/draft-guard.mjs +11 -9
- package/dist/write/publish-intent.mjs +16 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ The plugin adds:
|
|
|
61
61
|
- An `mcpx-api-keys` collection in the admin group "MCP", holding the keys.
|
|
62
62
|
- A draft guard on every collection and global that turns MCP writes into draft saves.
|
|
63
63
|
|
|
64
|
-
The key form uses two admin components, so regenerate the import map:
|
|
64
|
+
The key form uses two admin components, three with `delete`, so regenerate the import map:
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
67
|
payload generate:importmap
|
|
@@ -86,24 +86,25 @@ Claude Desktop and the MCP Inspector are covered in
|
|
|
86
86
|
|
|
87
87
|
## Options
|
|
88
88
|
|
|
89
|
-
| Option | Default | Description
|
|
90
|
-
| ---------------------------- | ----------------------------------------- |
|
|
91
|
-
| `collections` | required | Collections to expose. `true` means `{}`, everything it supports.
|
|
92
|
-
| `collections.<slug>.read` | `true` | Expose the read tools for this collection.
|
|
93
|
-
| `collections.<slug>.write` | `true` | Expose the write tools. Without drafts, a write goes live.
|
|
94
|
-
| `collections.<slug>.publish` | `true` with drafts | Expose `publishDocument`. Needs `versions.drafts` and `write`.
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `apiKeys.
|
|
99
|
-
| `apiKeys.
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `limits.
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
89
|
+
| Option | Default | Description |
|
|
90
|
+
| ---------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `collections` | required | Collections to expose. `true` means `{}`, everything it supports. |
|
|
92
|
+
| `collections.<slug>.read` | `true` | Expose the read tools for this collection. |
|
|
93
|
+
| `collections.<slug>.write` | `true` | Expose the write tools. Needs `read`. Without drafts, a write goes live. |
|
|
94
|
+
| `collections.<slug>.publish` | `true` with drafts | Expose `publishDocument`. Needs `versions.drafts` and `write`. |
|
|
95
|
+
| `collections.<slug>.delete` | `false` | Expose `deleteDocument`. Needs `read`. Each delete is approved in the admin. `"unattended"` also offers trashing without approval; needs `trash`. |
|
|
96
|
+
| `globals` | `{}` | Globals to expose, with `read`, `write` and `publish`. |
|
|
97
|
+
| `userCollection` | `config.admin.user`, then `users` | Auth collection whose users the keys act as. |
|
|
98
|
+
| `apiKeys.slug` | `mcpx-api-keys` | Slug of the key collection. |
|
|
99
|
+
| `apiKeys.setupGuide` | `true` | Add the "Connect a client" tab to saved keys. |
|
|
100
|
+
| `apiKeys.overrideCollection` | none | Function that receives the key collection and returns it. |
|
|
101
|
+
| `endpoint.path` | `/mcpx` | Endpoint path below the API route. |
|
|
102
|
+
| `limits.maxLimit` | `25` | Highest `limit` a client may pass to a list tool. |
|
|
103
|
+
| `limits.maxDepth` | `1` | Highest `depth` a client may pass to a read tool. |
|
|
104
|
+
| `tools` | `[]` | Custom tools, see [`docs/custom-tools.md`](./docs/custom-tools.md). |
|
|
105
|
+
| `auth.resolve` | none | Replace or wrap the key lookup. |
|
|
106
|
+
| `serverInfo` | `payloadcms-mcpx` and the package version | `{ name, version }` reported to clients. |
|
|
107
|
+
| `diagnostics` | `true` | Expose the server version plus repository and issue links. |
|
|
107
108
|
|
|
108
109
|
The config only takes capabilities away: `true` exposes everything the entity supports, and a key's
|
|
109
110
|
checkboxes decide what each key may do. `publish: false` keeps MCP writes as drafts. On an entity
|
|
@@ -115,17 +116,19 @@ every check.
|
|
|
115
116
|
|
|
116
117
|
## Tools
|
|
117
118
|
|
|
118
|
-
| Tool | What it does | Needs
|
|
119
|
-
| ------------------ | ------------------------------------------------------------------ |
|
|
120
|
-
| `listCapabilities` | Lists what this key may do. Clients call it first. | any key
|
|
121
|
-
| `describeSchema` | Describes the fields at one schema path, with the paths below it. | `read`
|
|
122
|
-
| `findDocuments` | Queries a collection with a Payload `where`, `sort` and `select`. | `read`
|
|
123
|
-
| `getDocument` | Reads a document, a subtree of it, an old version or a diff. | `read`
|
|
124
|
-
| `findVersions` | Lists the version history of a document or global, without bodies. | `read`
|
|
125
|
-
| `patchDocument` | Applies JSON Patch operations to the current draft, or a new file. | `write`
|
|
126
|
-
| `createDocument` | Creates a draft from a seed, with a file in an upload collection. | `write`
|
|
127
|
-
| `validateDocument` | Lists what blocks publishing, without saving. | `write`
|
|
128
|
-
| `publishDocument` | Publishes the current draft. | `
|
|
119
|
+
| Tool | What it does | Needs |
|
|
120
|
+
| ------------------ | ------------------------------------------------------------------ | --------- |
|
|
121
|
+
| `listCapabilities` | Lists what this key may do. Clients call it first. | any key |
|
|
122
|
+
| `describeSchema` | Describes the fields at one schema path, with the paths below it. | `read` |
|
|
123
|
+
| `findDocuments` | Queries a collection with a Payload `where`, `sort` and `select`. | `read` |
|
|
124
|
+
| `getDocument` | Reads a document, a subtree of it, an old version or a diff. | `read` |
|
|
125
|
+
| `findVersions` | Lists the version history of a document or global, without bodies. | `read` |
|
|
126
|
+
| `patchDocument` | Applies JSON Patch operations to the current draft, or a new file. | `write` |
|
|
127
|
+
| `createDocument` | Creates a draft from a seed, with a file in an upload collection. | `write` |
|
|
128
|
+
| `validateDocument` | Lists what blocks publishing, without saving. | `write` |
|
|
129
|
+
| `publishDocument` | Publishes the current draft. | `publish` |
|
|
130
|
+
| `deleteDocument` | Asks to delete a document, which the key's user approves. | `delete` |
|
|
131
|
+
| `runConfirmed` | Runs the approved calls. | `delete` |
|
|
129
132
|
|
|
130
133
|
Collections and globals use the same tools. A tool that addresses one document takes either
|
|
131
134
|
`collection` and `id`, or `global` alone. [`docs/concepts.md`](./docs/concepts.md#tools) lists
|
|
@@ -138,11 +141,20 @@ A key can do something only when both the config and the key allow it.
|
|
|
138
141
|
The plugin config sets the upper bound: which collections and globals are exposed, and whether
|
|
139
142
|
each one is readable, writable and publishable. A key cannot go past it.
|
|
140
143
|
|
|
141
|
-
Each key carries one checkbox per exposed entity and operation (`read`, `write`, `publish
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
Each key carries one checkbox per exposed entity and operation (`read`, `write`, `publish`,
|
|
145
|
+
`delete`, `deleteUnattended`), and
|
|
146
|
+
one per custom tool. The admin panel shows them as a matrix with a row per entity, an Access
|
|
147
|
+
control (None, Read, Write, Publish) and a Delete control (Off, Approval, Trash). Each access level
|
|
148
|
+
includes the ones before it, and a level the config does not expose has no segment. `publish`
|
|
149
|
+
exists only where the config exposes `write` and `publish` on an entity with drafts, and counts
|
|
150
|
+
only when `write` is ticked as well. `write` counts only when `read` is ticked, so a key never
|
|
151
|
+
writes what it cannot read. An entity without drafts carries a "Live" badge, as its writes go live
|
|
152
|
+
immediately.
|
|
153
|
+
`delete` exists only where the config sets `delete: true` on a collection, and counts only when
|
|
154
|
+
`read` is ticked as well. Setting access to None clears it. A delete runs only after the key's user
|
|
155
|
+
approved it on the key's edit view, except where the config sets `delete: "unattended"` and the
|
|
156
|
+
key's Delete control is set to Trash: there the document moves to trash at once. See
|
|
157
|
+
[deleting](./docs/concepts.md#deleting).
|
|
146
158
|
|
|
147
159
|
`tools/list` follows the key. A tool the key cannot use is not listed, and each `collection` and
|
|
148
160
|
`global` argument lists only the slugs the key may use with that tool. A checkbox added by a later
|
|
@@ -167,6 +179,9 @@ To restrict that, use `apiKeys.overrideCollection` as shown in
|
|
|
167
179
|
- A file is sent in a second step, a `PUT`, and downloaded with a `GET`, each with a short-lived,
|
|
168
180
|
single-use grant. Only collections with `upload.mimeTypes` take files. Downloads go through the
|
|
169
181
|
collection's own file access. See [uploads](./docs/security.md#uploads).
|
|
182
|
+
- A delete waits for the key's user to approve it in the admin panel and is then run by the same
|
|
183
|
+
key. Collections with `trash` move the document to trash. See
|
|
184
|
+
[confirmations](./docs/security.md#confirmations).
|
|
170
185
|
- Custom tools are trusted code and must apply access control themselves.
|
|
171
186
|
|
|
172
187
|
Open limitations: a filter through a relation into a readable collection ignores that collection's
|
|
@@ -25,9 +25,10 @@ interface McpxCapabilityMatrixProps {
|
|
|
25
25
|
withinTab?: boolean;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* What an API key may do, as one table per namespace: a row per entity
|
|
29
|
-
*
|
|
30
|
-
* checkbox fields back every
|
|
28
|
+
* What an API key may do, as one table per namespace: a row per entity with
|
|
29
|
+
* segmented Access and Delete controls, and an "All" row that sets every row.
|
|
30
|
+
* The nested checkbox fields back every control, so the stored document is
|
|
31
|
+
* unaffected.
|
|
31
32
|
*/
|
|
32
33
|
export declare const McpxCapabilityMatrix: React.FC<McpxCapabilityMatrixProps>;
|
|
33
34
|
//#endregion
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { CAPABILITIES_FIELD } from "../capabilities.mjs";
|
|
3
|
-
import { CAPABILITY_OPERATIONS,
|
|
4
|
-
import {
|
|
3
|
+
import { CAPABILITY_OPERATIONS, STORED_OPERATIONS, capabilityPaths, toolPath } from "../api-keys/capability-matrix.mjs";
|
|
4
|
+
import { accessLevelOf, accessLevelsOf, allAccessLevel, allDeleteMode, buildToggleActions, deleteModeOf, toolsState } from "./capability-toggles.mjs";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { CheckboxInput, FieldDescription, FieldLabel, useForm, useFormFields } from "@payloadcms/ui";
|
|
6
|
+
import { Button, CheckboxInput, FieldDescription, FieldLabel, Pill, useForm, useFormFields } from "@payloadcms/ui";
|
|
7
7
|
import { useCallback, useMemo } from "react";
|
|
8
8
|
//#region src/admin/capability-matrix.tsx
|
|
9
|
-
const NOT_EXPOSED = "The plugin config does not expose this.";
|
|
10
9
|
const BASE_CLASS = "mcpx-capabilities";
|
|
10
|
+
const LIVE_WRITE = "Writes go live immediately.";
|
|
11
11
|
const SHEET = `
|
|
12
12
|
.${BASE_CLASS} tbody tr:hover { background: var(--theme-elevation-50); }
|
|
13
|
-
.${BASE_CLASS}__toggle:hover { text-decoration: underline; }
|
|
14
13
|
.${BASE_CLASS} section:last-of-type { margin-bottom: 0; }
|
|
14
|
+
.${BASE_CLASS}__segments {
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
border: 1px solid var(--theme-elevation-150);
|
|
17
|
+
border-radius: var(--style-radius-s);
|
|
18
|
+
background: var(--theme-input-bg);
|
|
19
|
+
}
|
|
20
|
+
.${BASE_CLASS}__segments .btn { border: 0; border-radius: 0; white-space: nowrap; }
|
|
21
|
+
.${BASE_CLASS}__segments .btn + .btn { border-inline-start: 1px solid var(--theme-elevation-150); }
|
|
22
|
+
.${BASE_CLASS}__segments .btn:first-child { border-start-start-radius: inherit; border-end-start-radius: inherit; }
|
|
23
|
+
.${BASE_CLASS}__segments .btn:last-child { border-start-end-radius: inherit; border-end-end-radius: inherit; }
|
|
24
|
+
.${BASE_CLASS}__segments .btn--style-transparent { --hover-bg: var(--theme-elevation-100); }
|
|
25
|
+
.${BASE_CLASS}__segments .${BASE_CLASS}__danger { --bg-color: var(--theme-error-500); --hover-bg: var(--theme-error-600); }
|
|
15
26
|
`;
|
|
16
27
|
const CELL_PADDING = "calc(var(--base) * 0.6)";
|
|
17
28
|
const EDGE_PADDING = "calc(var(--base) * 0.8)";
|
|
@@ -57,30 +68,35 @@ const styles = {
|
|
|
57
68
|
fontWeight: "normal",
|
|
58
69
|
wordBreak: "break-word"
|
|
59
70
|
},
|
|
60
|
-
rowButton: {
|
|
61
|
-
padding: 0,
|
|
62
|
-
border: 0,
|
|
63
|
-
background: "none",
|
|
64
|
-
color: "inherit",
|
|
65
|
-
font: "inherit",
|
|
66
|
-
cursor: "pointer",
|
|
67
|
-
textAlign: "start",
|
|
68
|
-
textDecoration: "none"
|
|
69
|
-
},
|
|
70
71
|
hint: {
|
|
71
72
|
display: "block",
|
|
72
73
|
color: "var(--theme-elevation-400)"
|
|
73
74
|
},
|
|
75
|
+
badge: {
|
|
76
|
+
display: "inline-flex",
|
|
77
|
+
marginInlineStart: "calc(var(--base) * 0.4)",
|
|
78
|
+
verticalAlign: "middle",
|
|
79
|
+
cursor: "help"
|
|
80
|
+
},
|
|
81
|
+
visuallyHidden: {
|
|
82
|
+
position: "absolute",
|
|
83
|
+
width: "1px",
|
|
84
|
+
height: "1px",
|
|
85
|
+
overflow: "hidden",
|
|
86
|
+
clip: "rect(0 0 0 0)",
|
|
87
|
+
whiteSpace: "nowrap"
|
|
88
|
+
},
|
|
74
89
|
cell: {
|
|
75
90
|
padding: CELL_PADDING,
|
|
76
91
|
verticalAlign: "middle",
|
|
77
92
|
textAlign: "center"
|
|
78
93
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
94
|
+
controlCell: {
|
|
95
|
+
padding: CELL_PADDING,
|
|
96
|
+
verticalAlign: "middle",
|
|
97
|
+
textAlign: "start"
|
|
98
|
+
},
|
|
99
|
+
lastCell: { paddingInlineEnd: EDGE_PADDING }
|
|
84
100
|
};
|
|
85
101
|
const Box = ({ label, onChange, readOnly, state }) => /*#__PURE__*/ jsx(CheckboxInput, {
|
|
86
102
|
checked: state === "on",
|
|
@@ -89,116 +105,250 @@ const Box = ({ label, onChange, readOnly, state }) => /*#__PURE__*/ jsx(Checkbox
|
|
|
89
105
|
partialChecked: state === "mixed",
|
|
90
106
|
readOnly
|
|
91
107
|
});
|
|
108
|
+
const descriptionOf = (operation) => STORED_OPERATIONS.find((candidate) => candidate.id === operation)?.description;
|
|
109
|
+
const ACCESS_OPTIONS = [{
|
|
110
|
+
id: "none",
|
|
111
|
+
label: "None",
|
|
112
|
+
title: "No access"
|
|
113
|
+
}, ...CAPABILITY_OPERATIONS.filter((operation) => operation.id !== "delete").map((operation) => ({
|
|
114
|
+
id: operation.id,
|
|
115
|
+
label: operation.label,
|
|
116
|
+
title: operation.description
|
|
117
|
+
}))];
|
|
118
|
+
const DELETE_OPTIONS = [
|
|
119
|
+
{
|
|
120
|
+
id: "off",
|
|
121
|
+
label: "Off",
|
|
122
|
+
title: "No delete"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "approve",
|
|
126
|
+
label: "Approval",
|
|
127
|
+
title: descriptionOf("delete")
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "trash",
|
|
131
|
+
label: "Trash",
|
|
132
|
+
title: descriptionOf("deleteUnattended")
|
|
133
|
+
}
|
|
134
|
+
];
|
|
135
|
+
const ARROW_STEPS = {
|
|
136
|
+
ArrowDown: 1,
|
|
137
|
+
ArrowLeft: -1,
|
|
138
|
+
ArrowRight: 1,
|
|
139
|
+
ArrowUp: -1
|
|
140
|
+
};
|
|
141
|
+
const Segments = ({ included = [], label, onSelect, options, readOnly, selected }) => {
|
|
142
|
+
const focusable = options.some((option) => option.id === selected) ? selected : options[0]?.id;
|
|
143
|
+
const onKeyDown = (event) => {
|
|
144
|
+
const step = ARROW_STEPS[event.key];
|
|
145
|
+
if (readOnly || step === void 0) return;
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
const radios = [...event.currentTarget.querySelectorAll("[role=\"radio\"]")];
|
|
148
|
+
const current = radios.findIndex((radio) => radio === document.activeElement);
|
|
149
|
+
const next = Math.min(Math.max(current + step, 0), radios.length - 1);
|
|
150
|
+
const option = options[next];
|
|
151
|
+
if (option) {
|
|
152
|
+
radios[next]?.focus();
|
|
153
|
+
onSelect(option.id);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return /*#__PURE__*/ jsx("div", {
|
|
157
|
+
"aria-label": label,
|
|
158
|
+
className: `${BASE_CLASS}__segments`,
|
|
159
|
+
onKeyDown,
|
|
160
|
+
role: "radiogroup",
|
|
161
|
+
children: options.map((option) => {
|
|
162
|
+
const checked = option.id === selected;
|
|
163
|
+
return /*#__PURE__*/ jsx(Button, {
|
|
164
|
+
buttonStyle: checked ? "primary" : included.includes(option.id) ? "pill" : "transparent",
|
|
165
|
+
className: checked && option.id === "trash" ? `${BASE_CLASS}__danger` : "",
|
|
166
|
+
disabled: readOnly,
|
|
167
|
+
extraButtonProps: {
|
|
168
|
+
"aria-checked": checked,
|
|
169
|
+
role: "radio",
|
|
170
|
+
tabIndex: option.id === focusable ? 0 : -1,
|
|
171
|
+
title: option.title
|
|
172
|
+
},
|
|
173
|
+
margin: false,
|
|
174
|
+
onClick: () => {
|
|
175
|
+
onSelect(option.id);
|
|
176
|
+
},
|
|
177
|
+
size: "small",
|
|
178
|
+
children: option.label
|
|
179
|
+
}, option.id);
|
|
180
|
+
})
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
const includedBelow = (options, level) => {
|
|
184
|
+
const selected = options.findIndex((option) => option.id === level);
|
|
185
|
+
return selected < 1 ? [] : options.slice(1, selected).map((option) => option.id);
|
|
186
|
+
};
|
|
187
|
+
const accessOptions = (rows) => ACCESS_OPTIONS.filter((option) => rows.some((row) => accessLevelsOf(row).includes(option.id)));
|
|
92
188
|
const LABEL_WIDTH = "calc(var(--base) * 9)";
|
|
93
189
|
const OPERATION_WIDTH = "calc(var(--base) * 3.5)";
|
|
94
|
-
const
|
|
95
|
-
const
|
|
190
|
+
const ACCESS_WIDTH = "calc(var(--base) * 13)";
|
|
191
|
+
const DELETE_WIDTH = "calc(var(--base) * 10)";
|
|
192
|
+
const Columns = ({ widths }) => /*#__PURE__*/ jsxs("colgroup", { children: [/*#__PURE__*/ jsx("col", { style: { width: LABEL_WIDTH } }), widths.map((width, index) => /*#__PURE__*/ jsx("col", { style: { width } }, index))] });
|
|
193
|
+
const lastCellStyle = {
|
|
96
194
|
...styles.cell,
|
|
97
195
|
...styles.lastCell
|
|
98
|
-
}
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
196
|
+
};
|
|
197
|
+
const controlStyle = (isLast) => isLast ? {
|
|
198
|
+
...styles.controlCell,
|
|
199
|
+
...styles.lastCell
|
|
200
|
+
} : styles.controlCell;
|
|
201
|
+
const LiveBadge = () => /*#__PURE__*/ jsx("span", {
|
|
202
|
+
style: styles.badge,
|
|
203
|
+
title: LIVE_WRITE,
|
|
204
|
+
children: /*#__PURE__*/ jsxs(Pill, {
|
|
205
|
+
pillStyle: "warning",
|
|
206
|
+
size: "small",
|
|
207
|
+
children: [/*#__PURE__*/ jsx("span", {
|
|
208
|
+
"aria-hidden": "true",
|
|
209
|
+
children: "Live"
|
|
210
|
+
}), /*#__PURE__*/ jsx("span", {
|
|
211
|
+
style: styles.visuallyHidden,
|
|
212
|
+
children: LIVE_WRITE
|
|
213
|
+
})]
|
|
214
|
+
})
|
|
104
215
|
});
|
|
105
216
|
const NamespaceTable = ({ matrix, namespace, path, readOnly, toggle, values }) => {
|
|
106
217
|
const rows = matrix[namespace.id];
|
|
107
|
-
const
|
|
218
|
+
const withDelete = rows.some((row) => row.delete);
|
|
219
|
+
const allOptions = accessOptions(rows);
|
|
220
|
+
const allLevel = allAccessLevel(matrix, path, namespace.id, values);
|
|
221
|
+
const entries = namespace.title.toLowerCase();
|
|
108
222
|
return /*#__PURE__*/ jsx("section", {
|
|
109
223
|
style: styles.section,
|
|
110
224
|
children: /*#__PURE__*/ jsxs("table", {
|
|
111
225
|
style: styles.table,
|
|
112
226
|
children: [
|
|
113
|
-
/*#__PURE__*/ jsx(Columns, {
|
|
227
|
+
/*#__PURE__*/ jsx(Columns, { widths: withDelete ? [ACCESS_WIDTH, DELETE_WIDTH] : [ACCESS_WIDTH] }),
|
|
114
228
|
/*#__PURE__*/ jsxs("thead", {
|
|
115
229
|
style: styles.head,
|
|
116
|
-
children: [/*#__PURE__*/ jsxs("tr", { children: [
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...styles.headTitle,
|
|
120
|
-
...styles.topStart
|
|
121
|
-
},
|
|
122
|
-
children: namespace.title
|
|
123
|
-
}), columns.map((operation, column) => /*#__PURE__*/ jsx("th", {
|
|
124
|
-
scope: "col",
|
|
125
|
-
style: column === columns.length - 1 ? {
|
|
126
|
-
...styles.headOperation,
|
|
127
|
-
...styles.topEnd
|
|
128
|
-
} : styles.headOperation,
|
|
129
|
-
title: operation.description,
|
|
130
|
-
children: operation.label
|
|
131
|
-
}, operation.id))] }), /*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
|
|
132
|
-
scope: "row",
|
|
133
|
-
style: {
|
|
134
|
-
...styles.headLabel,
|
|
135
|
-
...styles.rule
|
|
136
|
-
},
|
|
137
|
-
children: "All"
|
|
138
|
-
}), columns.map((operation, column) => {
|
|
139
|
-
const state = columnState(matrix, path, namespace.id, operation.id, values);
|
|
140
|
-
return /*#__PURE__*/ jsx("td", {
|
|
230
|
+
children: [/*#__PURE__*/ jsxs("tr", { children: [
|
|
231
|
+
/*#__PURE__*/ jsx("th", {
|
|
232
|
+
scope: "col",
|
|
141
233
|
style: {
|
|
142
|
-
...
|
|
234
|
+
...styles.headTitle,
|
|
235
|
+
...styles.topStart
|
|
236
|
+
},
|
|
237
|
+
children: namespace.title
|
|
238
|
+
}),
|
|
239
|
+
/*#__PURE__*/ jsx("th", {
|
|
240
|
+
scope: "col",
|
|
241
|
+
style: {
|
|
242
|
+
...styles.headOperation,
|
|
243
|
+
textAlign: "start",
|
|
244
|
+
...withDelete ? {} : styles.topEnd
|
|
245
|
+
},
|
|
246
|
+
children: "Access"
|
|
247
|
+
}),
|
|
248
|
+
withDelete ? /*#__PURE__*/ jsx("th", {
|
|
249
|
+
scope: "col",
|
|
250
|
+
style: {
|
|
251
|
+
...styles.headOperation,
|
|
252
|
+
textAlign: "start",
|
|
253
|
+
...styles.topEnd
|
|
254
|
+
},
|
|
255
|
+
children: "Delete"
|
|
256
|
+
}) : null
|
|
257
|
+
] }), /*#__PURE__*/ jsxs("tr", { children: [
|
|
258
|
+
/*#__PURE__*/ jsx("th", {
|
|
259
|
+
scope: "row",
|
|
260
|
+
style: {
|
|
261
|
+
...styles.headLabel,
|
|
143
262
|
...styles.rule
|
|
144
263
|
},
|
|
145
|
-
children:
|
|
146
|
-
|
|
147
|
-
|
|
264
|
+
children: "All"
|
|
265
|
+
}),
|
|
266
|
+
/*#__PURE__*/ jsx("td", {
|
|
267
|
+
style: {
|
|
268
|
+
...controlStyle(!withDelete),
|
|
269
|
+
...styles.rule
|
|
270
|
+
},
|
|
271
|
+
children: /*#__PURE__*/ jsx(Segments, {
|
|
272
|
+
included: includedBelow(allOptions, allLevel),
|
|
273
|
+
label: `Access for every ${entries} entry`,
|
|
274
|
+
onSelect: (level) => {
|
|
148
275
|
toggle({
|
|
149
|
-
kind: "
|
|
276
|
+
kind: "access",
|
|
150
277
|
namespace: namespace.id,
|
|
151
|
-
|
|
152
|
-
value: state !== "on"
|
|
278
|
+
level
|
|
153
279
|
});
|
|
154
280
|
},
|
|
281
|
+
options: allOptions,
|
|
155
282
|
readOnly,
|
|
156
|
-
|
|
283
|
+
selected: allLevel
|
|
157
284
|
})
|
|
158
|
-
},
|
|
159
|
-
|
|
285
|
+
}),
|
|
286
|
+
withDelete ? /*#__PURE__*/ jsx("td", {
|
|
287
|
+
style: {
|
|
288
|
+
...controlStyle(true),
|
|
289
|
+
...styles.rule
|
|
290
|
+
},
|
|
291
|
+
children: /*#__PURE__*/ jsx(Segments, {
|
|
292
|
+
label: `Delete for every ${entries} entry`,
|
|
293
|
+
onSelect: (mode) => {
|
|
294
|
+
toggle({
|
|
295
|
+
kind: "deleteMode",
|
|
296
|
+
namespace: namespace.id,
|
|
297
|
+
mode
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
options: DELETE_OPTIONS.filter((option) => option.id !== "trash"),
|
|
301
|
+
readOnly,
|
|
302
|
+
selected: allDeleteMode(matrix, path, namespace.id, values)
|
|
303
|
+
})
|
|
304
|
+
}) : null
|
|
305
|
+
] })]
|
|
160
306
|
}),
|
|
161
307
|
/*#__PURE__*/ jsx("tbody", { children: rows.map((row) => {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
308
|
+
const options = accessOptions([row]);
|
|
309
|
+
const level = accessLevelOf(path, namespace.id, row, values);
|
|
310
|
+
return /*#__PURE__*/ jsxs("tr", { children: [
|
|
311
|
+
/*#__PURE__*/ jsxs("th", {
|
|
312
|
+
scope: "row",
|
|
313
|
+
style: styles.rowHeader,
|
|
314
|
+
children: [row.label, row.live ? /*#__PURE__*/ jsx(LiveBadge, {}) : null]
|
|
315
|
+
}),
|
|
316
|
+
/*#__PURE__*/ jsx("td", {
|
|
317
|
+
style: controlStyle(!withDelete),
|
|
318
|
+
children: /*#__PURE__*/ jsx(Segments, {
|
|
319
|
+
included: includedBelow(options, level),
|
|
320
|
+
label: `Access for ${row.label}`,
|
|
321
|
+
onSelect: (next) => {
|
|
322
|
+
toggle({
|
|
323
|
+
kind: "access",
|
|
324
|
+
namespace: namespace.id,
|
|
325
|
+
fieldName: row.fieldName,
|
|
326
|
+
level: next
|
|
327
|
+
});
|
|
328
|
+
},
|
|
329
|
+
options,
|
|
330
|
+
readOnly,
|
|
331
|
+
selected: level
|
|
332
|
+
})
|
|
333
|
+
}),
|
|
334
|
+
withDelete ? /*#__PURE__*/ jsx("td", {
|
|
335
|
+
style: controlStyle(true),
|
|
336
|
+
children: row.delete ? /*#__PURE__*/ jsx(Segments, {
|
|
337
|
+
label: `Delete for ${row.label}`,
|
|
338
|
+
onSelect: (mode) => {
|
|
339
|
+
toggle({
|
|
340
|
+
kind: "deleteMode",
|
|
341
|
+
namespace: namespace.id,
|
|
342
|
+
fieldName: row.fieldName,
|
|
343
|
+
mode
|
|
344
|
+
});
|
|
345
|
+
},
|
|
346
|
+
options: DELETE_OPTIONS.filter((option) => option.id !== "trash" || row.deleteUnattended),
|
|
347
|
+
readOnly,
|
|
348
|
+
selected: deleteModeOf(path, namespace.id, row, values)
|
|
349
|
+
}) : null
|
|
350
|
+
}) : null
|
|
351
|
+
] }, row.fieldName);
|
|
202
352
|
}) })
|
|
203
353
|
]
|
|
204
354
|
})
|
|
@@ -211,7 +361,7 @@ const ToolsTable = ({ matrix, path, readOnly, toggle, values }) => {
|
|
|
211
361
|
children: /*#__PURE__*/ jsxs("table", {
|
|
212
362
|
style: styles.table,
|
|
213
363
|
children: [
|
|
214
|
-
/*#__PURE__*/ jsx(Columns, {
|
|
364
|
+
/*#__PURE__*/ jsx(Columns, { widths: [OPERATION_WIDTH] }),
|
|
215
365
|
/*#__PURE__*/ jsxs("thead", {
|
|
216
366
|
style: styles.head,
|
|
217
367
|
children: [/*#__PURE__*/ jsxs("tr", { children: [/*#__PURE__*/ jsx("th", {
|
|
@@ -237,7 +387,7 @@ const ToolsTable = ({ matrix, path, readOnly, toggle, values }) => {
|
|
|
237
387
|
children: "All"
|
|
238
388
|
}), /*#__PURE__*/ jsx("td", {
|
|
239
389
|
style: {
|
|
240
|
-
...
|
|
390
|
+
...lastCellStyle,
|
|
241
391
|
...styles.rule
|
|
242
392
|
},
|
|
243
393
|
children: /*#__PURE__*/ jsx(Box, {
|
|
@@ -261,7 +411,7 @@ const ToolsTable = ({ matrix, path, readOnly, toggle, values }) => {
|
|
|
261
411
|
children: tool.description
|
|
262
412
|
})]
|
|
263
413
|
}), /*#__PURE__*/ jsx("td", {
|
|
264
|
-
style:
|
|
414
|
+
style: lastCellStyle,
|
|
265
415
|
children: /*#__PURE__*/ jsx(Box, {
|
|
266
416
|
label: `Enable ${tool.name}`,
|
|
267
417
|
onChange: () => {
|
|
@@ -280,9 +430,10 @@ const ToolsTable = ({ matrix, path, readOnly, toggle, values }) => {
|
|
|
280
430
|
});
|
|
281
431
|
};
|
|
282
432
|
/**
|
|
283
|
-
* What an API key may do, as one table per namespace: a row per entity
|
|
284
|
-
*
|
|
285
|
-
* checkbox fields back every
|
|
433
|
+
* What an API key may do, as one table per namespace: a row per entity with
|
|
434
|
+
* segmented Access and Delete controls, and an "All" row that sets every row.
|
|
435
|
+
* The nested checkbox fields back every control, so the stored document is
|
|
436
|
+
* unaffected.
|
|
286
437
|
*/ const McpxCapabilityMatrix = ({ field, matrix, path = CAPABILITIES_FIELD, readOnly = false, withinTab = false }) => {
|
|
287
438
|
const { dispatchFields, setModified } = useForm();
|
|
288
439
|
const paths = useMemo(() => capabilityPaths(matrix, path), [matrix, path]);
|