@checkstack/automation-frontend 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/package.json +16 -16
- package/src/index.tsx +5 -0
- package/src/pages/AutomationEditPage.tsx +25 -6
- package/src/pages/AutomationListPage.tsx +22 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,133 @@
|
|
|
1
1
|
# @checkstack/automation-frontend
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0d912a3: Make the frontend fully RLAC-aware so team-scoped users see and can use exactly
|
|
8
|
+
what the backend already authorises - no more, no less. Previously every nav
|
|
9
|
+
entry, route, management page, create button, per-row action, and resource
|
|
10
|
+
picker gated purely on a user's GLOBAL access rule, so a user whose team manages
|
|
11
|
+
a system saw none of the surfaces the backend would happily let them use, and
|
|
12
|
+
(where a page did render) could select systems they don't manage and only fail
|
|
13
|
+
after submit.
|
|
14
|
+
|
|
15
|
+
Platform primitives (on `AccessApi`, from `@checkstack/frontend-api`, implemented
|
|
16
|
+
in `@checkstack/auth-frontend`). Each ORs the global RBAC rule with team-derived
|
|
17
|
+
(ReBAC) grants, so a global-rule holder always sees everything:
|
|
18
|
+
|
|
19
|
+
- `useCanCreate({ accessRule, objectType, parentType? })` - may the user create
|
|
20
|
+
this type (global rule, a team `creator` grant, or managing a parent resource).
|
|
21
|
+
- `useCanAccessType({ accessRule, objectType, parentType? })` - may the user
|
|
22
|
+
reach a management SURFACE for this type at all (create capability OR managing
|
|
23
|
+
any existing object of the type / its parent). Powers route guards, sidebar
|
|
24
|
+
entries, and a management page's top-level `allowed`.
|
|
25
|
+
- `useResourceAccess({ accessRule, objectType, resourceIds })` - a `canAccess(id)`
|
|
26
|
+
predicate for per-row controls and for filtering resource pickers.
|
|
27
|
+
|
|
28
|
+
Backed by three authenticated `auth` RPC procedures - `canCreate`,
|
|
29
|
+
`myManageableTypes`, and `listMyAccessibleResources` - the frontend-facing
|
|
30
|
+
mirrors of the existing S2S authorization endpoints, resolved against the
|
|
31
|
+
caller's own team grants.
|
|
32
|
+
|
|
33
|
+
Route/nav gating is now capability-aware: a route may declare
|
|
34
|
+
`manageCapability: { objectType, parentType? }`; the route guard and sidebar then
|
|
35
|
+
show/allow it for team-scoped users via `myManageableTypes`. Applied to the
|
|
36
|
+
catalog, incident, maintenance, SLO, healthcheck, automation, and status-page
|
|
37
|
+
management routes. The route guard resolves this through a single
|
|
38
|
+
`useRouteAccess` hook with a constant hook count, since the guard is reconciled
|
|
39
|
+
in place as the URL changes (a conditional hook there would trip the rules of
|
|
40
|
+
hooks).
|
|
41
|
+
|
|
42
|
+
Resource types are now typed, plugin-qualified constants. A new
|
|
43
|
+
`resourceType(pluginMetadata, localType)` factory in `@checkstack/common` mints a
|
|
44
|
+
nominal `ResourceType`, and each `*-common` package exports its constants (e.g.
|
|
45
|
+
`catalogResourceTypes.system`, `incidentResourceTypes.incident`). The capability
|
|
46
|
+
APIs accept `ResourceType`, so a mistyped `"catalog.system"` string now fails
|
|
47
|
+
typecheck instead of silently breaking a gate.
|
|
48
|
+
|
|
49
|
+
Resource pickers now offer only what the backend will accept:
|
|
50
|
+
|
|
51
|
+
- Incident and maintenance "Affected Systems" pickers show only systems the user
|
|
52
|
+
manages (or all with the global rule), matching the backend's requirement of
|
|
53
|
+
MANAGE on every referenced system.
|
|
54
|
+
- SLO creation is now system-scoped end to end: `createObjective` gains a
|
|
55
|
+
`catalog.system` parent gate (managing the target system authorises creating an
|
|
56
|
+
SLO for it, like incident/maintenance), and the SLO editor's system picker is
|
|
57
|
+
filtered to manageable systems.
|
|
58
|
+
- Catalog group and environment membership (add-to-group / add-to-environment,
|
|
59
|
+
per-row and bulk) is gated on managing the system being (re)assigned.
|
|
60
|
+
- The health-check assignment surface (Assignment IDE + the system-detail
|
|
61
|
+
"Health Checks" action) requires MANAGE on the target system.
|
|
62
|
+
|
|
63
|
+
Catalog membership chips only render a removable "x" for systems the user
|
|
64
|
+
manages (removing a group/environment membership requires managing the system),
|
|
65
|
+
and the Dependency Map only lets a user originate an edge from a system they
|
|
66
|
+
manage (the source is access-checked; the target is not).
|
|
67
|
+
|
|
68
|
+
Owning-team correctness: a parent-gated creator (team member, no global rule)
|
|
69
|
+
who left the owning team unset previously created an object with no team grant -
|
|
70
|
+
which they then could not edit. The `authorizeCreate` parent-gate path now
|
|
71
|
+
resolves an owning team instead of silently orphaning the object (auto-assigns
|
|
72
|
+
when the caller belongs to exactly one team, requires an explicit choice when
|
|
73
|
+
several), and the `TeamOwnershipPicker` marks the field required and
|
|
74
|
+
auto-selects the sole eligible team.
|
|
75
|
+
|
|
76
|
+
Dependency writes are fixed to authorize on the SOURCE system. `createDependency`
|
|
77
|
+
/ `updateDependency` / `deleteDependency` previously used `instanceAccess:
|
|
78
|
+
{ idParam: "systemId" }`, which made the middleware look for a `dependency` grant
|
|
79
|
+
keyed by the system id - a grant that never exists - so every team-scoped source
|
|
80
|
+
manager was denied ("Access denied to resource dependency:<systemId>"). They now
|
|
81
|
+
`parentScope` on `catalog.system` manage, so managing the source system
|
|
82
|
+
authorises editing its dependencies (the target is not access-checked), matching
|
|
83
|
+
health-check assignment.
|
|
84
|
+
|
|
85
|
+
The backend authorization changes are limited to: the new read-only capability
|
|
86
|
+
procedures (`canCreate` / `myManageableTypes` / `listMyAccessibleResources`), the
|
|
87
|
+
SLO create parent gate, the `authorizeCreate` owning-team resolution, and the
|
|
88
|
+
dependency source-scope fix. Everything else only aligns the UI with
|
|
89
|
+
authorization the backend already enforced.
|
|
90
|
+
|
|
91
|
+
### Patch Changes
|
|
92
|
+
|
|
93
|
+
- Updated dependencies [0d912a3]
|
|
94
|
+
- Updated dependencies [0d912a3]
|
|
95
|
+
- Updated dependencies [d9f4654]
|
|
96
|
+
- Updated dependencies [0d912a3]
|
|
97
|
+
- Updated dependencies [a07b375]
|
|
98
|
+
- Updated dependencies [d9f4654]
|
|
99
|
+
- Updated dependencies [d9f4654]
|
|
100
|
+
- Updated dependencies [e430fbe]
|
|
101
|
+
- Updated dependencies [eab80e3]
|
|
102
|
+
- Updated dependencies [259b93c]
|
|
103
|
+
- Updated dependencies [0d912a3]
|
|
104
|
+
- Updated dependencies [692fa18]
|
|
105
|
+
- @checkstack/auth-frontend@0.11.0
|
|
106
|
+
- @checkstack/ui@1.22.0
|
|
107
|
+
- @checkstack/frontend-api@0.13.0
|
|
108
|
+
- @checkstack/common@0.19.0
|
|
109
|
+
- @checkstack/auth-common@0.12.0
|
|
110
|
+
- @checkstack/catalog-common@2.6.0
|
|
111
|
+
- @checkstack/automation-common@0.9.0
|
|
112
|
+
- @checkstack/gitops-frontend@0.6.5
|
|
113
|
+
- @checkstack/script-packages-frontend@0.4.5
|
|
114
|
+
- @checkstack/secrets-frontend@0.3.5
|
|
115
|
+
- @checkstack/ai-common@0.6.3
|
|
116
|
+
- @checkstack/integration-common@0.9.5
|
|
117
|
+
- @checkstack/signal-frontend@0.3.2
|
|
118
|
+
- @checkstack/template-engine@0.4.8
|
|
119
|
+
|
|
120
|
+
## 0.9.3
|
|
121
|
+
|
|
122
|
+
### Patch Changes
|
|
123
|
+
|
|
124
|
+
- Updated dependencies [baf9b6e]
|
|
125
|
+
- @checkstack/ui@1.21.0
|
|
126
|
+
- @checkstack/auth-frontend@0.10.2
|
|
127
|
+
- @checkstack/gitops-frontend@0.6.4
|
|
128
|
+
- @checkstack/script-packages-frontend@0.4.4
|
|
129
|
+
- @checkstack/secrets-frontend@0.3.4
|
|
130
|
+
|
|
3
131
|
## 0.9.2
|
|
4
132
|
|
|
5
133
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/automation-frontend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -16,20 +16,20 @@
|
|
|
16
16
|
"lint:code": "eslint . --max-warnings 0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@checkstack/auth-common": "0.
|
|
20
|
-
"@checkstack/auth-frontend": "0.
|
|
21
|
-
"@checkstack/automation-common": "0.
|
|
22
|
-
"@checkstack/ai-common": "0.6.
|
|
23
|
-
"@checkstack/catalog-common": "2.
|
|
24
|
-
"@checkstack/common": "0.
|
|
25
|
-
"@checkstack/frontend-api": "0.
|
|
26
|
-
"@checkstack/gitops-frontend": "0.6.
|
|
27
|
-
"@checkstack/integration-common": "0.9.
|
|
28
|
-
"@checkstack/script-packages-frontend": "0.4.
|
|
29
|
-
"@checkstack/secrets-frontend": "0.3.
|
|
30
|
-
"@checkstack/signal-frontend": "0.3.
|
|
31
|
-
"@checkstack/template-engine": "0.4.
|
|
32
|
-
"@checkstack/ui": "1.
|
|
19
|
+
"@checkstack/auth-common": "0.12.0",
|
|
20
|
+
"@checkstack/auth-frontend": "0.11.0",
|
|
21
|
+
"@checkstack/automation-common": "0.9.0",
|
|
22
|
+
"@checkstack/ai-common": "0.6.3",
|
|
23
|
+
"@checkstack/catalog-common": "2.6.0",
|
|
24
|
+
"@checkstack/common": "0.19.0",
|
|
25
|
+
"@checkstack/frontend-api": "0.13.0",
|
|
26
|
+
"@checkstack/gitops-frontend": "0.6.5",
|
|
27
|
+
"@checkstack/integration-common": "0.9.5",
|
|
28
|
+
"@checkstack/script-packages-frontend": "0.4.5",
|
|
29
|
+
"@checkstack/secrets-frontend": "0.3.5",
|
|
30
|
+
"@checkstack/signal-frontend": "0.3.2",
|
|
31
|
+
"@checkstack/template-engine": "0.4.8",
|
|
32
|
+
"@checkstack/ui": "1.22.0",
|
|
33
33
|
"@dnd-kit/core": "^6.3.1",
|
|
34
34
|
"@dnd-kit/sortable": "^8.0.0",
|
|
35
35
|
"@dnd-kit/utilities": "^3.2.2",
|
|
@@ -43,6 +43,6 @@
|
|
|
43
43
|
"typescript": "^5.0.0",
|
|
44
44
|
"@types/react": "^19.0.0",
|
|
45
45
|
"@checkstack/tsconfig": "0.0.7",
|
|
46
|
-
"@checkstack/scripts": "0.
|
|
46
|
+
"@checkstack/scripts": "0.7.0"
|
|
47
47
|
}
|
|
48
48
|
}
|
package/src/index.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
automationRoutes,
|
|
4
4
|
pluginMetadata,
|
|
5
5
|
automationAccess,
|
|
6
|
+
automationResourceTypes,
|
|
6
7
|
} from "@checkstack/automation-common";
|
|
7
8
|
import { Workflow } from "lucide-react";
|
|
8
9
|
|
|
@@ -60,6 +61,8 @@ export default createFrontendPlugin({
|
|
|
60
61
|
})),
|
|
61
62
|
title: "New automation",
|
|
62
63
|
accessRule: automationAccess.manage,
|
|
64
|
+
// Team-scoped: a team with an automation creator grant may reach create.
|
|
65
|
+
manageCapability: { objectType: automationResourceTypes.automation },
|
|
63
66
|
},
|
|
64
67
|
{
|
|
65
68
|
route: automationRoutes.routes.createBlank,
|
|
@@ -69,6 +72,8 @@ export default createFrontendPlugin({
|
|
|
69
72
|
})),
|
|
70
73
|
title: "New automation",
|
|
71
74
|
accessRule: automationAccess.manage,
|
|
75
|
+
// Team-scoped: a team with an automation creator grant may reach create.
|
|
76
|
+
manageCapability: { objectType: automationResourceTypes.automation },
|
|
72
77
|
},
|
|
73
78
|
{
|
|
74
79
|
route: automationRoutes.routes.edit,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
AutomationApi,
|
|
18
18
|
automationAccess,
|
|
19
19
|
automationRoutes,
|
|
20
|
+
automationResourceTypes,
|
|
20
21
|
type AutomationDefinition,
|
|
21
22
|
} from "@checkstack/automation-common";
|
|
22
23
|
import {
|
|
@@ -118,12 +119,28 @@ const AutomationEditContent: React.FC = () => {
|
|
|
118
119
|
const { allowed: canRead, loading: accessLoading } = accessApi.useAccess(
|
|
119
120
|
automationAccess.read,
|
|
120
121
|
);
|
|
121
|
-
|
|
122
|
+
// allowGlobal: the GLOBAL manage rule means the caller may create a resource
|
|
123
|
+
// not scoped to any team. This feeds the TeamOwnershipPicker only.
|
|
124
|
+
const { allowed: hasGlobalManageAccess } = accessApi.useAccess(
|
|
122
125
|
automationAccess.manage,
|
|
123
126
|
);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
const allowGlobal = hasGlobalManageAccess;
|
|
128
|
+
|
|
129
|
+
// Create/page gate (new automations): global manage rule OR team-derived
|
|
130
|
+
// create capability.
|
|
131
|
+
const { allowed: canCreate } = accessApi.useCanCreate({
|
|
132
|
+
accessRule: automationAccess.manage,
|
|
133
|
+
objectType: automationResourceTypes.automation,
|
|
134
|
+
});
|
|
135
|
+
// Per-resource manage gate (existing automations): grants to global-manage
|
|
136
|
+
// holders AND to team-scoped users with a per-object grant on THIS id.
|
|
137
|
+
const { canAccess } = accessApi.useResourceAccess({
|
|
138
|
+
accessRule: automationAccess.manage,
|
|
139
|
+
objectType: automationResourceTypes.automation,
|
|
140
|
+
resourceIds: !isNew && automationId ? [automationId] : [],
|
|
141
|
+
});
|
|
142
|
+
const canManageThisAutomation =
|
|
143
|
+
!isNew && automationId ? canAccess(automationId) : false;
|
|
127
144
|
|
|
128
145
|
// GitOps provenance lock: when this automation is declaratively managed,
|
|
129
146
|
// disable manual edits + show a banner. `entityId` is the automation id
|
|
@@ -134,8 +151,10 @@ const AutomationEditContent: React.FC = () => {
|
|
|
134
151
|
entityId: isNew ? undefined : automationId,
|
|
135
152
|
});
|
|
136
153
|
|
|
137
|
-
// Effective edit permission: manage
|
|
138
|
-
|
|
154
|
+
// Effective edit permission: RLAC manage (create capability for new, per-
|
|
155
|
+
// resource access for existing) AND not GitOps-locked.
|
|
156
|
+
const canManage =
|
|
157
|
+
(isNew ? canCreate : canManageThisAutomation) && !isLocked;
|
|
139
158
|
|
|
140
159
|
const loadQuery = client.getAutomation.useQuery(
|
|
141
160
|
{ id: automationId ?? "" },
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
AutomationApi,
|
|
13
13
|
automationAccess,
|
|
14
14
|
automationRoutes,
|
|
15
|
+
automationResourceTypes,
|
|
15
16
|
type Automation,
|
|
16
17
|
} from "@checkstack/automation-common";
|
|
17
18
|
import {
|
|
@@ -84,7 +85,12 @@ const AutomationListContent: React.FC = () => {
|
|
|
84
85
|
const { allowed: canRead, loading: accessLoading } = accessApi.useAccess(
|
|
85
86
|
automationAccess.read,
|
|
86
87
|
);
|
|
87
|
-
|
|
88
|
+
// Create/page gate: whether the user may create a new automation at all
|
|
89
|
+
// (global manage rule OR team-derived create capability).
|
|
90
|
+
const { allowed: canCreate } = accessApi.useCanCreate({
|
|
91
|
+
accessRule: automationAccess.manage,
|
|
92
|
+
objectType: automationResourceTypes.automation,
|
|
93
|
+
});
|
|
88
94
|
|
|
89
95
|
const [statusFilter, setStatusFilter] = React.useState<
|
|
90
96
|
"all" | "enabled" | "disabled"
|
|
@@ -163,6 +169,15 @@ const AutomationListContent: React.FC = () => {
|
|
|
163
169
|
const automations = React.useMemo(() => items ?? [], [items]);
|
|
164
170
|
const isEmpty = !query.isLoading && automations.length === 0;
|
|
165
171
|
|
|
172
|
+
// Per-resource manage gate: grants ALL rows to global-manage holders, and
|
|
173
|
+
// per-object grants to team-scoped users. Gate each row's toggle/delete on
|
|
174
|
+
// `canAccess(automation.id)` instead of a single global bool.
|
|
175
|
+
const { canAccess } = accessApi.useResourceAccess({
|
|
176
|
+
accessRule: automationAccess.manage,
|
|
177
|
+
objectType: automationResourceTypes.automation,
|
|
178
|
+
resourceIds: automations.map((a) => a.id),
|
|
179
|
+
});
|
|
180
|
+
|
|
166
181
|
// Collapsible sections, sorted alphabetically with "Ungrouped" last.
|
|
167
182
|
const groups = React.useMemo(
|
|
168
183
|
() => groupAutomations({ automations }),
|
|
@@ -195,7 +210,7 @@ const AutomationListContent: React.FC = () => {
|
|
|
195
210
|
)}
|
|
196
211
|
aria-hidden
|
|
197
212
|
/>
|
|
198
|
-
{
|
|
213
|
+
{canAccess(automation.id) ? (
|
|
199
214
|
<Toggle
|
|
200
215
|
checked={automation.status === "enabled"}
|
|
201
216
|
onCheckedChange={(enabled) =>
|
|
@@ -276,7 +291,7 @@ const AutomationListContent: React.FC = () => {
|
|
|
276
291
|
Runs
|
|
277
292
|
</Button>
|
|
278
293
|
</Link>
|
|
279
|
-
{
|
|
294
|
+
{canAccess(automation.id) && (
|
|
280
295
|
<Button
|
|
281
296
|
variant="ghost"
|
|
282
297
|
size="icon"
|
|
@@ -324,7 +339,7 @@ const AutomationListContent: React.FC = () => {
|
|
|
324
339
|
)}
|
|
325
340
|
</div>
|
|
326
341
|
<div onClick={(e) => e.stopPropagation()}>
|
|
327
|
-
{
|
|
342
|
+
{canAccess(automation.id) ? (
|
|
328
343
|
<Toggle
|
|
329
344
|
checked={automation.status === "enabled"}
|
|
330
345
|
onCheckedChange={(enabled) =>
|
|
@@ -389,7 +404,7 @@ const AutomationListContent: React.FC = () => {
|
|
|
389
404
|
Runs
|
|
390
405
|
</Button>
|
|
391
406
|
</Link>
|
|
392
|
-
{
|
|
407
|
+
{canAccess(automation.id) && (
|
|
393
408
|
<Button
|
|
394
409
|
variant="ghost"
|
|
395
410
|
size="icon"
|
|
@@ -419,7 +434,7 @@ const AutomationListContent: React.FC = () => {
|
|
|
419
434
|
Playground
|
|
420
435
|
</Button>
|
|
421
436
|
</Link>
|
|
422
|
-
{
|
|
437
|
+
{canCreate && (
|
|
423
438
|
<Link to={resolveRoute(automationRoutes.routes.create)}>
|
|
424
439
|
<Button size="sm">
|
|
425
440
|
<Plus className="mr-1 h-4 w-4" />
|
|
@@ -464,7 +479,7 @@ const AutomationListContent: React.FC = () => {
|
|
|
464
479
|
icon={<Workflow className="h-8 w-8 text-muted-foreground" />}
|
|
465
480
|
title="No automations yet"
|
|
466
481
|
description={
|
|
467
|
-
|
|
482
|
+
canCreate
|
|
468
483
|
? 'Click "New automation" to wire your first trigger.'
|
|
469
484
|
: "Once an admin creates an automation it will appear here."
|
|
470
485
|
}
|