@checkstack/healthcheck-frontend 0.2.0 → 0.3.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,109 @@
1
1
  # @checkstack/healthcheck-frontend
2
2
 
3
+ ## 0.3.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
+ - 827b286: Add array assertion operators for string array fields
72
+
73
+ New operators for asserting on array fields (e.g., playerNames in RCON collectors):
74
+
75
+ - **includes** - Check if array contains a specific value
76
+ - **notIncludes** - Check if array does NOT contain a specific value
77
+ - **lengthEquals** - Check if array length equals a value
78
+ - **lengthGreaterThan** - Check if array length is greater than a value
79
+ - **lengthLessThan** - Check if array length is less than a value
80
+ - **isEmpty** - Check if array is empty
81
+ - **isNotEmpty** - Check if array has at least one element
82
+
83
+ Also exports a new `arrayField()` schema factory for creating array assertion schemas.
84
+
85
+ ### Patch Changes
86
+
87
+ - f533141: Enforce health result factory function usage via branded types
88
+
89
+ - Added `healthResultSchema()` builder that enforces the use of factory functions at compile-time
90
+ - Added `healthResultArray()` factory for array fields (e.g., DNS resolved values)
91
+ - Added branded `HealthResultField<T>` type to mark schemas created by factory functions
92
+ - Consolidated `ChartType` and `HealthResultMeta` into `@checkstack/common` as single source of truth
93
+ - Updated all 12 health check strategies and 11 collectors to use `healthResultSchema()`
94
+ - Using raw `z.number()` etc. inside `healthResultSchema()` now causes a TypeScript error
95
+
96
+ - Updated dependencies [9faec1f]
97
+ - Updated dependencies [95eeec7]
98
+ - Updated dependencies [f533141]
99
+ - @checkstack/auth-frontend@0.2.0
100
+ - @checkstack/catalog-common@1.1.0
101
+ - @checkstack/common@0.2.0
102
+ - @checkstack/frontend-api@0.1.0
103
+ - @checkstack/healthcheck-common@0.3.0
104
+ - @checkstack/ui@0.2.0
105
+ - @checkstack/signal-frontend@0.0.6
106
+
3
107
  ## 0.2.0
4
108
 
5
109
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/healthcheck-frontend",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "src/index.tsx",
6
6
  "scripts": {
@@ -7,8 +7,8 @@
7
7
  * Supports nested schemas under `collectors.*` for per-collector metrics.
8
8
  */
9
9
 
10
- import type { ChartType } from "@checkstack/healthcheck-common";
11
10
  import type {
11
+ ChartType,
12
12
  JsonSchemaPropertyCore,
13
13
  JsonSchemaBase,
14
14
  } from "@checkstack/common";
@@ -81,6 +81,13 @@ const OPERATORS: Record<FieldType, { value: string; label: string }[]> = {
81
81
  ],
82
82
  enum: [{ value: "equals", label: "Equals" }],
83
83
  array: [
84
+ { value: "includes", label: "Includes" },
85
+ { value: "notIncludes", label: "Not Includes" },
86
+ { value: "lengthEquals", label: "Length Equals" },
87
+ { value: "lengthGreaterThan", label: "Length Greater Than" },
88
+ { value: "lengthLessThan", label: "Length Less Than" },
89
+ { value: "isEmpty", label: "Is Empty" },
90
+ { value: "isNotEmpty", label: "Is Not Empty" },
84
91
  { value: "exists", label: "Exists" },
85
92
  { value: "notExists", label: "Not Exists" },
86
93
  ],
@@ -98,6 +105,7 @@ const OPERATORS: Record<FieldType, { value: string; label: string }[]> = {
98
105
  // Operators that don't need a value input
99
106
  const VALUE_LESS_OPERATORS = new Set([
100
107
  "isEmpty",
108
+ "isNotEmpty",
101
109
  "isTrue",
102
110
  "isFalse",
103
111
  "exists",
@@ -44,20 +44,20 @@ export function HealthCheckDiagram({
44
44
  }
45
45
 
46
46
  /**
47
- * Wrapper that shows permission message when user lacks access.
47
+ * Wrapper that shows access message when user lacks access.
48
48
  */
49
- export function HealthCheckDiagramPermissionGate({
50
- hasPermission,
49
+ export function HealthCheckDiagramAccessGate({
50
+ hasAccess,
51
51
  children,
52
52
  }: {
53
- hasPermission: boolean;
53
+ hasAccess: boolean;
54
54
  children: React.ReactNode;
55
55
  }) {
56
- if (!hasPermission) {
56
+ if (!hasAccess) {
57
57
  return (
58
58
  <InfoBanner variant="info">
59
59
  Additional strategy-specific visualizations are available with the
60
- &quot;Read Health Check Details&quot; permission.
60
+ &quot;Read Health Check Details&quot; access rule.
61
61
  </InfoBanner>
62
62
  );
63
63
  }
@@ -3,20 +3,17 @@ import { Link } from "react-router-dom";
3
3
  import { Activity } 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
  healthcheckRoutes,
9
- permissions,
9
+ healthCheckAccess,
10
10
  pluginMetadata,
11
11
  } from "@checkstack/healthcheck-common";
12
12
 
13
13
  export const HealthCheckMenuItems = ({
14
- permissions: userPerms,
14
+ accessRules: userPerms,
15
15
  }: UserMenuItemsContext) => {
16
- const qualifiedId = qualifyPermissionId(
17
- pluginMetadata,
18
- permissions.healthCheckRead
19
- );
16
+ const qualifiedId = `${pluginMetadata.pluginId}.${healthCheckAccess.configuration.read.id}`;
20
17
  const canRead = userPerms.includes("*") || userPerms.includes(qualifiedId);
21
18
 
22
19
  if (!canRead) {
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
2
2
  import {
3
3
  useApi,
4
4
  type SlotContext,
5
- permissionApiRef,
5
+ accessApiRef,
6
6
  } from "@checkstack/frontend-api";
7
7
  import { healthCheckApiRef, HealthCheckConfiguration } from "../api";
8
8
  import {
@@ -32,6 +32,7 @@ import type { StateThresholds } from "@checkstack/healthcheck-common";
32
32
  import {
33
33
  DEFAULT_STATE_THRESHOLDS,
34
34
  healthcheckRoutes,
35
+ healthCheckAccess,
35
36
  } from "@checkstack/healthcheck-common";
36
37
  import { resolveRoute } from "@checkstack/common";
37
38
  import { DEFAULT_RETENTION_CONFIG } from "@checkstack/healthcheck-common";
@@ -52,10 +53,9 @@ export const SystemHealthCheckAssignment: React.FC<Props> = ({
52
53
  systemName: _systemName,
53
54
  }) => {
54
55
  const api = useApi(healthCheckApiRef);
55
- const permissionApi = useApi(permissionApiRef);
56
- const { allowed: canManage } = permissionApi.useResourcePermission(
57
- "healthcheck",
58
- "manage"
56
+ const accessApi = useApi(accessApiRef);
57
+ const { allowed: canManage } = accessApi.useAccess(
58
+ healthCheckAccess.configuration.manage
59
59
  );
60
60
  const [configs, setConfigs] = useState<HealthCheckConfiguration[]>([]);
61
61
  const [associations, setAssociations] = useState<AssociationState[]>([]);
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useState, useMemo, useCallback } from "react";
2
- import { useApi, permissionApiRef } from "@checkstack/frontend-api";
2
+ import { useApi, accessApiRef } from "@checkstack/frontend-api";
3
3
  import { healthCheckApiRef } from "../api";
4
4
  import {
5
- permissions,
5
+ healthCheckAccess,
6
6
  DEFAULT_RETENTION_CONFIG,
7
7
  type RetentionConfig,
8
8
  HEALTH_CHECK_RUN_COMPLETED,
@@ -36,10 +36,10 @@ interface UseHealthCheckDataResult {
36
36
  isAggregated: boolean;
37
37
  /** The resolved retention config */
38
38
  retentionConfig: RetentionConfig;
39
- /** Whether user has permission to view detailed data */
40
- hasPermission: boolean;
41
- /** Whether permission is still loading */
42
- permissionLoading: boolean;
39
+ /** Whether user has access to view detailed data */
40
+ hasAccess: boolean;
41
+ /** Whether access is still loading */
42
+ accessLoading: boolean;
43
43
  }
44
44
 
45
45
  /**
@@ -52,14 +52,14 @@ interface UseHealthCheckDataResult {
52
52
  *
53
53
  * @example
54
54
  * ```tsx
55
- * const { context, loading, hasPermission } = useHealthCheckData({
55
+ * const { context, loading, hasAccess } = useHealthCheckData({
56
56
  * systemId,
57
57
  * configurationId,
58
58
  * strategyId,
59
59
  * dateRange: { startDate, endDate },
60
60
  * });
61
61
  *
62
- * if (!hasPermission) return <NoPermissionMessage />;
62
+ * if (!hasAccess) return <NoAccessMessage />;
63
63
  * if (loading) return <LoadingSpinner />;
64
64
  * if (!context) return null;
65
65
  *
@@ -75,11 +75,11 @@ export function useHealthCheckData({
75
75
  offset = 0,
76
76
  }: UseHealthCheckDataProps): UseHealthCheckDataResult {
77
77
  const api = useApi(healthCheckApiRef);
78
- const permissionApi = useApi(permissionApiRef);
78
+ const accessApi = useApi(accessApiRef);
79
79
 
80
- // Permission state
81
- const { allowed: hasPermission, loading: permissionLoading } =
82
- permissionApi.usePermission(permissions.healthCheckDetailsRead.id);
80
+ // Access state
81
+ const { allowed: hasAccess, loading: accessLoading } =
82
+ accessApi.useAccess(healthCheckAccess.details);
83
83
 
84
84
  // Retention config state
85
85
  const [retentionConfig, setRetentionConfig] = useState<RetentionConfig>(
@@ -160,24 +160,24 @@ export function useHealthCheckData({
160
160
 
161
161
  // Fetch raw data when in raw mode
162
162
  useEffect(() => {
163
- if (!hasPermission || permissionLoading || retentionLoading || isAggregated)
163
+ if (!hasAccess || accessLoading || retentionLoading || isAggregated)
164
164
  return;
165
165
  fetchRawData(true);
166
166
  }, [
167
167
  fetchRawData,
168
- hasPermission,
169
- permissionLoading,
168
+ hasAccess,
169
+ accessLoading,
170
170
  retentionLoading,
171
171
  isAggregated,
172
172
  ]);
173
173
 
174
174
  // Listen for realtime health check updates to refresh data silently
175
175
  useSignal(HEALTH_CHECK_RUN_COMPLETED, ({ systemId: changedId }) => {
176
- // Only refresh if we're in raw mode (not aggregated) and have permission
176
+ // Only refresh if we're in raw mode (not aggregated) and have access
177
177
  if (
178
178
  changedId === systemId &&
179
- hasPermission &&
180
- !permissionLoading &&
179
+ hasAccess &&
180
+ !accessLoading &&
181
181
  !retentionLoading &&
182
182
  !isAggregated
183
183
  ) {
@@ -188,8 +188,8 @@ export function useHealthCheckData({
188
188
  // Fetch aggregated data when in aggregated mode
189
189
  useEffect(() => {
190
190
  if (
191
- !hasPermission ||
192
- permissionLoading ||
191
+ !hasAccess ||
192
+ accessLoading ||
193
193
  retentionLoading ||
194
194
  !isAggregated
195
195
  )
@@ -198,7 +198,7 @@ export function useHealthCheckData({
198
198
  setAggregatedLoading(true);
199
199
  // Use daily buckets for ranges > 30 days, hourly otherwise
200
200
  const bucketSize = dateRangeDays > 30 ? "daily" : "hourly";
201
- // Use detailed endpoint to get aggregatedResult since we have permission
201
+ // Use detailed endpoint to get aggregatedResult since we have access
202
202
  api
203
203
  .getDetailedAggregatedHistory({
204
204
  systemId,
@@ -217,8 +217,8 @@ export function useHealthCheckData({
217
217
  api,
218
218
  systemId,
219
219
  configurationId,
220
- hasPermission,
221
- permissionLoading,
220
+ hasAccess,
221
+ accessLoading,
222
222
  retentionLoading,
223
223
  isAggregated,
224
224
  dateRangeDays,
@@ -227,7 +227,7 @@ export function useHealthCheckData({
227
227
  ]);
228
228
 
229
229
  const context = useMemo((): HealthCheckDiagramSlotContext | undefined => {
230
- if (!hasPermission || permissionLoading || retentionLoading) {
230
+ if (!hasAccess || accessLoading || retentionLoading) {
231
231
  return undefined;
232
232
  }
233
233
 
@@ -249,8 +249,8 @@ export function useHealthCheckData({
249
249
  runs: rawRuns,
250
250
  };
251
251
  }, [
252
- hasPermission,
253
- permissionLoading,
252
+ hasAccess,
253
+ accessLoading,
254
254
  retentionLoading,
255
255
  isAggregated,
256
256
  systemId,
@@ -261,7 +261,7 @@ export function useHealthCheckData({
261
261
  ]);
262
262
 
263
263
  const loading =
264
- permissionLoading ||
264
+ accessLoading ||
265
265
  retentionLoading ||
266
266
  (isAggregated ? aggregatedLoading : rawLoading);
267
267
 
@@ -270,7 +270,7 @@ export function useHealthCheckData({
270
270
  loading,
271
271
  isAggregated,
272
272
  retentionConfig,
273
- hasPermission,
274
- permissionLoading,
273
+ hasAccess,
274
+ accessLoading,
275
275
  };
276
276
  }
package/src/index.tsx CHANGED
@@ -13,7 +13,7 @@ import { HealthCheckMenuItems } from "./components/HealthCheckMenuItems";
13
13
  import { HealthCheckSystemOverview } from "./components/HealthCheckSystemOverview";
14
14
  import { SystemHealthCheckAssignment } from "./components/SystemHealthCheckAssignment";
15
15
  import { SystemHealthBadge } from "./components/SystemHealthBadge";
16
- import { permissions } from "@checkstack/healthcheck-common";
16
+ import { healthCheckAccess } from "@checkstack/healthcheck-common";
17
17
  import { autoChartExtension } from "./auto-charts";
18
18
 
19
19
  import {
@@ -49,19 +49,19 @@ export default createFrontendPlugin({
49
49
  route: healthcheckRoutes.routes.config,
50
50
  element: <HealthCheckConfigPage />,
51
51
  title: "Health Checks",
52
- permission: permissions.healthCheckManage,
52
+ accessRule: healthCheckAccess.configuration.manage,
53
53
  },
54
54
  {
55
55
  route: healthcheckRoutes.routes.history,
56
56
  element: <HealthCheckHistoryPage />,
57
57
  title: "Health Check History",
58
- permission: permissions.healthCheckRead,
58
+ accessRule: healthCheckAccess.configuration.read,
59
59
  },
60
60
  {
61
61
  route: healthcheckRoutes.routes.historyDetail,
62
62
  element: <HealthCheckHistoryDetailPage />,
63
63
  title: "Health Check Detail",
64
- permission: permissions.healthCheckDetailsRead,
64
+ accessRule: healthCheckAccess.details,
65
65
  },
66
66
  ],
67
67
  apis: [
@@ -3,7 +3,7 @@ import { useSearchParams } from "react-router-dom";
3
3
  import {
4
4
  useApi,
5
5
  wrapInSuspense,
6
- permissionApiRef,
6
+ accessApiRef,
7
7
  } from "@checkstack/frontend-api";
8
8
  import { healthCheckApiRef } from "../api";
9
9
  import {
@@ -11,6 +11,7 @@ import {
11
11
  HealthCheckStrategyDto,
12
12
  CreateHealthCheckConfiguration,
13
13
  healthcheckRoutes,
14
+ healthCheckAccess,
14
15
  } from "@checkstack/healthcheck-common";
15
16
  import { HealthCheckList } from "../components/HealthCheckList";
16
17
  import { HealthCheckEditor } from "../components/HealthCheckEditor";
@@ -21,13 +22,12 @@ import { resolveRoute } from "@checkstack/common";
21
22
 
22
23
  const HealthCheckConfigPageContent = () => {
23
24
  const api = useApi(healthCheckApiRef);
24
- const permissionApi = useApi(permissionApiRef);
25
+ const accessApi = useApi(accessApiRef);
25
26
  const [searchParams, setSearchParams] = useSearchParams();
26
- const { allowed: canRead, loading: permissionLoading } =
27
- permissionApi.useResourcePermission("healthcheck", "read");
28
- const { allowed: canManage } = permissionApi.useResourcePermission(
29
- "healthcheck",
30
- "manage"
27
+ const { allowed: canRead, loading: accessLoading } =
28
+ accessApi.useAccess(healthCheckAccess.configuration.read);
29
+ const { allowed: canManage } = accessApi.useAccess(
30
+ healthCheckAccess.configuration.manage
31
31
  );
32
32
 
33
33
  const [configurations, setConfigurations] = useState<
@@ -113,7 +113,7 @@ const HealthCheckConfigPageContent = () => {
113
113
  <PageLayout
114
114
  title="Health Checks"
115
115
  subtitle="Manage health check configurations"
116
- loading={permissionLoading}
116
+ loading={accessLoading}
117
117
  allowed={canRead}
118
118
  actions={
119
119
  <div className="flex gap-2">
@@ -2,10 +2,13 @@ import { useState } from "react";
2
2
  import {
3
3
  useApi,
4
4
  wrapInSuspense,
5
- permissionApiRef,
5
+ accessApiRef,
6
6
  } from "@checkstack/frontend-api";
7
7
  import { healthCheckApiRef } from "../api";
8
- import { healthcheckRoutes } from "@checkstack/healthcheck-common";
8
+ import {
9
+ healthcheckRoutes,
10
+ healthCheckAccess,
11
+ } from "@checkstack/healthcheck-common";
9
12
  import { resolveRoute } from "@checkstack/common";
10
13
  import {
11
14
  PageLayout,
@@ -32,9 +35,9 @@ const HealthCheckHistoryDetailPageContent = () => {
32
35
  }>();
33
36
 
34
37
  const api = useApi(healthCheckApiRef);
35
- const permissionApi = useApi(permissionApiRef);
36
- const { allowed: canManage, loading: permissionLoading } =
37
- permissionApi.useResourcePermission("healthcheck", "manage");
38
+ const accessApi = useApi(accessApiRef);
39
+ const { allowed: canManage, loading: accessLoading } =
40
+ accessApi.useAccess(healthCheckAccess.configuration.manage);
38
41
 
39
42
  const [dateRange, setDateRange] = useState<DateRange>(getDefaultDateRange);
40
43
 
@@ -65,7 +68,7 @@ const HealthCheckHistoryDetailPageContent = () => {
65
68
  0,
66
69
  8
67
70
  )}...`}
68
- loading={permissionLoading}
71
+ loading={accessLoading}
69
72
  allowed={canManage}
70
73
  actions={
71
74
  <BackLink to={resolveRoute(healthcheckRoutes.routes.history)}>
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  useApi,
3
3
  wrapInSuspense,
4
- permissionApiRef,
4
+ accessApiRef,
5
5
  } from "@checkstack/frontend-api";
6
6
  import { healthCheckApiRef } from "../api";
7
7
  import {
@@ -16,12 +16,13 @@ import {
16
16
  HealthCheckRunsTable,
17
17
  type HealthCheckRunDetailed,
18
18
  } from "../components/HealthCheckRunsTable";
19
+ import { healthCheckAccess } from "@checkstack/healthcheck-common";
19
20
 
20
21
  const HealthCheckHistoryPageContent = () => {
21
22
  const api = useApi(healthCheckApiRef);
22
- const permissionApi = useApi(permissionApiRef);
23
- const { allowed: canManage, loading: permissionLoading } =
24
- permissionApi.useResourcePermission("healthcheck", "manage");
23
+ const accessApi = useApi(accessApiRef);
24
+ const { allowed: canManage, loading: accessLoading } =
25
+ accessApi.useAccess(healthCheckAccess.configuration.manage);
25
26
 
26
27
  const {
27
28
  items: runs,
@@ -42,7 +43,7 @@ const HealthCheckHistoryPageContent = () => {
42
43
  <PageLayout
43
44
  title="Health Check History"
44
45
  subtitle="Detailed run history with full result data"
45
- loading={permissionLoading}
46
+ loading={accessLoading}
46
47
  allowed={canManage}
47
48
  >
48
49
  <Card>