@checkstack/incident-frontend 0.0.4 → 0.2.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 CHANGED
@@ -1,5 +1,170 @@
1
1
  # @checkstack/incident-frontend
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9faec1f: # Unified AccessRule Terminology Refactoring
8
+
9
+ This release completes a comprehensive terminology refactoring from "permission" to "accessRule" across the entire codebase, establishing a consistent and modern access control vocabulary.
10
+
11
+ ## Changes
12
+
13
+ ### Core Infrastructure (`@checkstack/common`)
14
+
15
+ - Introduced `AccessRule` interface as the primary access control type
16
+ - Added `accessPair()` helper for creating read/manage access rule pairs
17
+ - Added `access()` builder for individual access rules
18
+ - Replaced `Permission` type with `AccessRule` throughout
19
+
20
+ ### API Changes
21
+
22
+ - `env.registerPermissions()` → `env.registerAccessRules()`
23
+ - `meta.permissions` → `meta.access` in RPC contracts
24
+ - `usePermission()` → `useAccess()` in frontend hooks
25
+ - Route `permission:` field → `accessRule:` field
26
+
27
+ ### UI Changes
28
+
29
+ - "Roles & Permissions" tab → "Roles & Access Rules"
30
+ - "You don't have permission..." → "You don't have access..."
31
+ - All permission-related UI text updated
32
+
33
+ ### Documentation & Templates
34
+
35
+ - Updated 18 documentation files with AccessRule terminology
36
+ - Updated 7 scaffolding templates with `accessPair()` pattern
37
+ - All code examples use new AccessRule API
38
+
39
+ ## Migration Guide
40
+
41
+ ### Backend Plugins
42
+
43
+ ```diff
44
+ - import { permissionList } from "./permissions";
45
+ - env.registerPermissions(permissionList);
46
+ + import { accessRules } from "./access";
47
+ + env.registerAccessRules(accessRules);
48
+ ```
49
+
50
+ ### RPC Contracts
51
+
52
+ ```diff
53
+ - .meta({ userType: "user", permissions: [permissions.read.id] })
54
+ + .meta({ userType: "user", access: [access.read] })
55
+ ```
56
+
57
+ ### Frontend Hooks
58
+
59
+ ```diff
60
+ - const canRead = accessApi.usePermission(permissions.read.id);
61
+ + const canRead = accessApi.useAccess(access.read);
62
+ ```
63
+
64
+ ### Routes
65
+
66
+ ```diff
67
+ - permission: permissions.entityRead.id,
68
+ + accessRule: access.read,
69
+ ```
70
+
71
+ ### Patch Changes
72
+
73
+ - Updated dependencies [9faec1f]
74
+ - Updated dependencies [95eeec7]
75
+ - Updated dependencies [f533141]
76
+ - @checkstack/auth-frontend@0.2.0
77
+ - @checkstack/catalog-common@1.1.0
78
+ - @checkstack/common@0.2.0
79
+ - @checkstack/frontend-api@0.1.0
80
+ - @checkstack/incident-common@0.2.0
81
+ - @checkstack/ui@0.2.0
82
+ - @checkstack/signal-frontend@0.0.6
83
+
84
+ ## 0.1.0
85
+
86
+ ### Minor Changes
87
+
88
+ - 8e43507: # Teams and Resource-Level Access Control
89
+
90
+ This release introduces a comprehensive Teams system for organizing users and controlling access to resources at a granular level.
91
+
92
+ ## Features
93
+
94
+ ### Team Management
95
+
96
+ - Create, update, and delete teams with name and description
97
+ - Add/remove users from teams
98
+ - Designate team managers with elevated privileges
99
+ - View team membership and manager status
100
+
101
+ ### Resource-Level Access Control
102
+
103
+ - Grant teams access to specific resources (systems, health checks, incidents, maintenances)
104
+ - Configure read-only or manage permissions per team
105
+ - Resource-level "Team Only" mode that restricts access exclusively to team members
106
+ - Separate `resourceAccessSettings` table for resource-level settings (not per-grant)
107
+ - Automatic cleanup of grants when teams are deleted (database cascade)
108
+
109
+ ### Middleware Integration
110
+
111
+ - Extended `autoAuthMiddleware` to support resource access checks
112
+ - Single-resource pre-handler validation for detail endpoints
113
+ - Automatic list filtering for collection endpoints
114
+ - S2S endpoints for access verification
115
+
116
+ ### Frontend Components
117
+
118
+ - `TeamsTab` component for managing teams in Auth Settings
119
+ - `TeamAccessEditor` component for assigning team access to resources
120
+ - Resource-level "Team Only" toggle in `TeamAccessEditor`
121
+ - Integration into System, Health Check, Incident, and Maintenance editors
122
+
123
+ ## Breaking Changes
124
+
125
+ ### API Response Format Changes
126
+
127
+ List endpoints now return objects with named keys instead of arrays directly:
128
+
129
+ ```typescript
130
+ // Before
131
+ const systems = await catalogApi.getSystems();
132
+
133
+ // After
134
+ const { systems } = await catalogApi.getSystems();
135
+ ```
136
+
137
+ Affected endpoints:
138
+
139
+ - `catalog.getSystems` → `{ systems: [...] }`
140
+ - `healthcheck.getConfigurations` → `{ configurations: [...] }`
141
+ - `incident.listIncidents` → `{ incidents: [...] }`
142
+ - `maintenance.listMaintenances` → `{ maintenances: [...] }`
143
+
144
+ ### User Identity Enrichment
145
+
146
+ `RealUser` and `ApplicationUser` types now include `teamIds: string[]` field with team memberships.
147
+
148
+ ## Documentation
149
+
150
+ See `docs/backend/teams.md` for complete API reference and integration guide.
151
+
152
+ ### Patch Changes
153
+
154
+ - 97c5a6b: Fix Radix UI accessibility warning in dialog components by adding visually hidden DialogDescription components
155
+ - Updated dependencies [8e43507]
156
+ - Updated dependencies [97c5a6b]
157
+ - Updated dependencies [97c5a6b]
158
+ - Updated dependencies [8e43507]
159
+ - Updated dependencies [8e43507]
160
+ - @checkstack/ui@0.1.0
161
+ - @checkstack/auth-frontend@0.1.0
162
+ - @checkstack/catalog-common@1.0.0
163
+ - @checkstack/common@0.1.0
164
+ - @checkstack/incident-common@0.1.0
165
+ - @checkstack/frontend-api@0.0.4
166
+ - @checkstack/signal-frontend@0.0.5
167
+
3
168
  ## 0.0.4
4
169
 
5
170
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/incident-frontend",
3
- "version": "0.0.4",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "src/index.tsx",
6
6
  "scripts": {
@@ -9,6 +9,7 @@
9
9
  "lint:code": "eslint . --max-warnings 0"
10
10
  },
11
11
  "dependencies": {
12
+ "@checkstack/auth-frontend": "workspace:*",
12
13
  "@checkstack/catalog-common": "workspace:*",
13
14
  "@checkstack/common": "workspace:*",
14
15
  "@checkstack/frontend-api": "workspace:*",
@@ -10,6 +10,7 @@ import type { System } from "@checkstack/catalog-common";
10
10
  import {
11
11
  Dialog,
12
12
  DialogContent,
13
+ DialogDescription,
13
14
  DialogHeader,
14
15
  DialogTitle,
15
16
  DialogFooter,
@@ -29,6 +30,7 @@ import {
29
30
  import { Plus, MessageSquare, Loader2, AlertCircle } from "lucide-react";
30
31
  import { IncidentUpdateForm } from "./IncidentUpdateForm";
31
32
  import { getIncidentStatusBadge } from "../utils/badges";
33
+ import { TeamAccessEditor } from "@checkstack/auth-frontend";
32
34
 
33
35
  interface Props {
34
36
  open: boolean;
@@ -165,6 +167,11 @@ export const IncidentEditor: React.FC<Props> = ({
165
167
  <DialogTitle>
166
168
  {incident ? "Edit Incident" : "Create Incident"}
167
169
  </DialogTitle>
170
+ <DialogDescription className="sr-only">
171
+ {incident
172
+ ? "Modify the details for this incident report"
173
+ : "Report a new incident affecting your systems"}
174
+ </DialogDescription>
168
175
  </DialogHeader>
169
176
 
170
177
  <div className="grid gap-6 py-4 max-h-[70vh] overflow-y-auto">
@@ -295,6 +302,16 @@ export const IncidentEditor: React.FC<Props> = ({
295
302
  )}
296
303
  </div>
297
304
  )}
305
+
306
+ {/* Team Access Editor - only shown when editing existing incident */}
307
+ {incident?.id && (
308
+ <TeamAccessEditor
309
+ resourceType="incident.incident"
310
+ resourceId={incident.id}
311
+ compact
312
+ expanded
313
+ />
314
+ )}
298
315
  </div>
299
316
 
300
317
  <DialogFooter>
@@ -3,20 +3,17 @@ import { Link } from "react-router-dom";
3
3
  import { AlertTriangle } from "lucide-react";
4
4
  import type { UserMenuItemsContext } from "@checkstack/frontend-api";
5
5
  import { DropdownMenuItem } from "@checkstack/ui";
6
- import { qualifyPermissionId, resolveRoute } from "@checkstack/common";
6
+ import { resolveRoute } from "@checkstack/common";
7
7
  import {
8
8
  incidentRoutes,
9
- permissions,
9
+ incidentAccess,
10
10
  pluginMetadata,
11
11
  } from "@checkstack/incident-common";
12
12
 
13
13
  export const IncidentMenuItems = ({
14
- permissions: userPerms,
14
+ accessRules: userPerms,
15
15
  }: UserMenuItemsContext) => {
16
- const qualifiedId = qualifyPermissionId(
17
- pluginMetadata,
18
- permissions.incidentManage
19
- );
16
+ const qualifiedId = `${pluginMetadata.pluginId}.${incidentAccess.incident.manage.id}`;
20
17
  const canManage = userPerms.includes("*") || userPerms.includes(qualifiedId);
21
18
 
22
19
  if (!canManage) {
package/src/index.tsx CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  incidentRoutes,
11
11
  IncidentApi,
12
12
  pluginMetadata,
13
- permissions,
13
+ incidentAccess,
14
14
  } from "@checkstack/incident-common";
15
15
  import {
16
16
  SystemDetailsTopSlot,
@@ -30,7 +30,7 @@ export default createFrontendPlugin({
30
30
  route: incidentRoutes.routes.config,
31
31
  element: <IncidentConfigPage />,
32
32
  title: "Incidents",
33
- permission: permissions.incidentManage,
33
+ accessRule: incidentAccess.incident.manage,
34
34
  },
35
35
  {
36
36
  route: incidentRoutes.routes.detail,
@@ -3,7 +3,7 @@ import { useSearchParams } from "react-router-dom";
3
3
  import {
4
4
  useApi,
5
5
  rpcApiRef,
6
- permissionApiRef,
6
+ accessApiRef,
7
7
  wrapInSuspense,
8
8
  } from "@checkstack/frontend-api";
9
9
  import { incidentApiRef } from "../api";
@@ -11,6 +11,7 @@ import type {
11
11
  IncidentWithSystems,
12
12
  IncidentStatus,
13
13
  } from "@checkstack/incident-common";
14
+ import { incidentAccess } from "@checkstack/incident-common";
14
15
  import { CatalogApi, type System } from "@checkstack/catalog-common";
15
16
  import {
16
17
  Card,
@@ -50,14 +51,14 @@ import { IncidentEditor } from "../components/IncidentEditor";
50
51
  const IncidentConfigPageContent: React.FC = () => {
51
52
  const api = useApi(incidentApiRef);
52
53
  const rpcApi = useApi(rpcApiRef);
53
- const permissionApi = useApi(permissionApiRef);
54
+ const accessApi = useApi(accessApiRef);
54
55
  const [searchParams, setSearchParams] = useSearchParams();
55
56
 
56
57
  const catalogApi = useMemo(() => rpcApi.forPlugin(CatalogApi), [rpcApi]);
57
58
  const toast = useToast();
58
59
 
59
- const { allowed: canManage, loading: permissionLoading } =
60
- permissionApi.useResourcePermission("incident", "manage");
60
+ const { allowed: canManage, loading: accessLoading } =
61
+ accessApi.useAccess(incidentAccess.incident.manage);
61
62
 
62
63
  const [incidents, setIncidents] = useState<IncidentWithSystems[]>([]);
63
64
  const [systems, setSystems] = useState<System[]>([]);
@@ -84,14 +85,15 @@ const IncidentConfigPageContent: React.FC = () => {
84
85
  const loadData = async () => {
85
86
  setLoading(true);
86
87
  try {
87
- const [incidentList, systemList] = await Promise.all([
88
- api.listIncidents(
89
- statusFilter === "all"
90
- ? { includeResolved: showResolved }
91
- : { status: statusFilter, includeResolved: showResolved }
92
- ),
93
- catalogApi.getSystems(),
94
- ]);
88
+ const [{ incidents: incidentList }, { systems: systemList }] =
89
+ await Promise.all([
90
+ api.listIncidents(
91
+ statusFilter === "all"
92
+ ? { includeResolved: showResolved }
93
+ : { status: statusFilter, includeResolved: showResolved }
94
+ ),
95
+ catalogApi.getSystems(),
96
+ ]);
95
97
  setIncidents(incidentList);
96
98
  setSystems(systemList);
97
99
  } catch (error) {
@@ -219,7 +221,7 @@ const IncidentConfigPageContent: React.FC = () => {
219
221
  <PageLayout
220
222
  title="Incident Management"
221
223
  subtitle="Track and manage incidents affecting your systems"
222
- loading={permissionLoading}
224
+ loading={accessLoading}
223
225
  allowed={canManage}
224
226
  actions={
225
227
  <Button onClick={handleCreate}>
@@ -3,7 +3,7 @@ import { useParams, useNavigate, useSearchParams } from "react-router-dom";
3
3
  import {
4
4
  useApi,
5
5
  rpcApiRef,
6
- permissionApiRef,
6
+ accessApiRef,
7
7
  wrapInSuspense,
8
8
  } from "@checkstack/frontend-api";
9
9
  import { useSignal } from "@checkstack/signal-frontend";
@@ -13,6 +13,7 @@ import {
13
13
  incidentRoutes,
14
14
  INCIDENT_UPDATED,
15
15
  type IncidentDetail,
16
+ incidentAccess,
16
17
  } from "@checkstack/incident-common";
17
18
  import { CatalogApi, type System } from "@checkstack/catalog-common";
18
19
  import {
@@ -50,14 +51,13 @@ const IncidentDetailPageContent: React.FC = () => {
50
51
  const [searchParams] = useSearchParams();
51
52
  const api = useApi(incidentApiRef);
52
53
  const rpcApi = useApi(rpcApiRef);
53
- const permissionApi = useApi(permissionApiRef);
54
+ const accessApi = useApi(accessApiRef);
54
55
  const toast = useToast();
55
56
 
56
57
  const catalogApi = useMemo(() => rpcApi.forPlugin(CatalogApi), [rpcApi]);
57
58
 
58
- const { allowed: canManage } = permissionApi.useResourcePermission(
59
- "incident",
60
- "manage"
59
+ const { allowed: canManage } = accessApi.useAccess(
60
+ incidentAccess.incident.manage
61
61
  );
62
62
 
63
63
  const [incident, setIncident] = useState<IncidentDetail | undefined>();
@@ -69,7 +69,7 @@ const IncidentDetailPageContent: React.FC = () => {
69
69
  if (!incidentId) return;
70
70
 
71
71
  try {
72
- const [incidentData, systemList] = await Promise.all([
72
+ const [incidentData, { systems: systemList }] = await Promise.all([
73
73
  api.getIncident({ id: incidentId }),
74
74
  catalogApi.getSystems(),
75
75
  ]);
@@ -1,10 +1,6 @@
1
1
  import React, { useEffect, useState, useMemo } from "react";
2
2
  import { useParams, Link } from "react-router-dom";
3
- import {
4
- useApi,
5
- rpcApiRef,
6
- wrapInSuspense,
7
- } from "@checkstack/frontend-api";
3
+ import { useApi, rpcApiRef, wrapInSuspense } from "@checkstack/frontend-api";
8
4
  import { useSignal } from "@checkstack/signal-frontend";
9
5
  import { resolveRoute } from "@checkstack/common";
10
6
  import { incidentApiRef } from "../api";
@@ -48,10 +44,11 @@ const SystemIncidentHistoryPageContent: React.FC = () => {
48
44
 
49
45
  setLoading(true);
50
46
  try {
51
- const [incidentList, systemList] = await Promise.all([
52
- api.listIncidents({ systemId, includeResolved: true }),
53
- catalogApi.getSystems(),
54
- ]);
47
+ const [{ incidents: incidentList }, { systems: systemList }] =
48
+ await Promise.all([
49
+ api.listIncidents({ systemId, includeResolved: true }),
50
+ catalogApi.getSystems(),
51
+ ]);
55
52
  const systemData = systemList.find((s) => s.id === systemId);
56
53
  setIncidents(incidentList);
57
54
  setSystem(systemData);