@checkstack/dependency-frontend 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 +42 -0
- package/package.json +34 -0
- package/src/components/DependencyAlert.tsx +144 -0
- package/src/components/DependencyBadge.tsx +78 -0
- package/src/components/DependencyEdgeForm.tsx +84 -0
- package/src/components/DependencyEditor.tsx +561 -0
- package/src/components/DependencyMapPage.tsx +631 -0
- package/src/components/DependencyMenuItems.tsx +20 -0
- package/src/components/HealthCheckRulesEditor.tsx +194 -0
- package/src/components/canvas/DependencyEdge.tsx +89 -0
- package/src/components/canvas/SystemNode.tsx +142 -0
- package/src/index.tsx +51 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @checkstack/dependency-frontend
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3f36a64: Add System Dependencies plugin
|
|
8
|
+
|
|
9
|
+
Introduces the system dependencies feature with three new core plugins and
|
|
10
|
+
extends the catalog with a new SystemEditorSlot extension point.
|
|
11
|
+
|
|
12
|
+
**New plugins:**
|
|
13
|
+
|
|
14
|
+
- **dependency-common**: Shared Zod schemas, RPC contract with resource-level access control, signal definitions, and routes
|
|
15
|
+
- **dependency-backend**: Drizzle schema, DependencyService with cycle detection, WarningEvaluationService with transitive impact matrix, RPC router with signal broadcasting, and per-user canvas node position persistence
|
|
16
|
+
- **dependency-frontend**: DependencyBadge (dashboard), DependencyAlert (system details), DependencyEditor (system editor dialog), and interactive DependencyMapPage (React Flow canvas)
|
|
17
|
+
|
|
18
|
+
**Catalog extensions:**
|
|
19
|
+
|
|
20
|
+
- **catalog-common**: New `SystemEditorSlot` for plugin-injected sections in the system editor dialog
|
|
21
|
+
- **catalog-frontend**: `SystemEditor` renders the slot after TeamAccessEditor for existing systems
|
|
22
|
+
|
|
23
|
+
**Key capabilities:**
|
|
24
|
+
|
|
25
|
+
- Directional dependency edges between systems (source depends on target)
|
|
26
|
+
- Three impact types: informational, degraded, critical
|
|
27
|
+
- Transitive multi-hop warning propagation with toggle switch
|
|
28
|
+
- Cycle detection at creation time with graphical chain visualization
|
|
29
|
+
- Health check-level dependency rules
|
|
30
|
+
- Interactive dependency map with drag-to-connect, edge click editor, and auto-saving node positions
|
|
31
|
+
- Inline editing of dependencies in both the system editor and the map canvas
|
|
32
|
+
- Team-based resource-level access control on all mutation endpoints
|
|
33
|
+
- Realtime signal-driven UI updates
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [1f191cf]
|
|
38
|
+
- Updated dependencies [3f36a64]
|
|
39
|
+
- @checkstack/healthcheck-common@0.9.0
|
|
40
|
+
- @checkstack/dashboard-frontend@0.3.24
|
|
41
|
+
- @checkstack/dependency-common@0.2.0
|
|
42
|
+
- @checkstack/catalog-common@1.3.0
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/dependency-frontend",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"checkstack": {
|
|
7
|
+
"type": "frontend"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"lint": "bun run lint:code",
|
|
12
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@checkstack/catalog-common": "1.2.11",
|
|
16
|
+
"@checkstack/common": "0.6.4",
|
|
17
|
+
"@checkstack/dashboard-frontend": "0.3.23",
|
|
18
|
+
"@checkstack/dependency-common": "0.1.0",
|
|
19
|
+
"@checkstack/frontend-api": "0.3.8",
|
|
20
|
+
"@checkstack/healthcheck-common": "0.8.4",
|
|
21
|
+
"@checkstack/signal-frontend": "0.0.14",
|
|
22
|
+
"@checkstack/ui": "1.2.0",
|
|
23
|
+
"@xyflow/react": "^12.10.2",
|
|
24
|
+
"lucide-react": "^0.344.0",
|
|
25
|
+
"react": "^18.2.0",
|
|
26
|
+
"react-router-dom": "^6.20.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.0.0",
|
|
30
|
+
"@types/react": "^18.2.0",
|
|
31
|
+
"@checkstack/tsconfig": "0.0.4",
|
|
32
|
+
"@checkstack/scripts": "0.1.2"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { usePluginClient, type SlotContext } from "@checkstack/frontend-api";
|
|
3
|
+
import { useSignal } from "@checkstack/signal-frontend";
|
|
4
|
+
import { SystemDetailsTopSlot } from "@checkstack/catalog-common";
|
|
5
|
+
import {
|
|
6
|
+
DependencyApi,
|
|
7
|
+
DEPENDENCY_CHANGED,
|
|
8
|
+
DEPENDENCY_WARNINGS_CHANGED,
|
|
9
|
+
type DerivedState,
|
|
10
|
+
type AffectedUpstream,
|
|
11
|
+
} from "@checkstack/dependency-common";
|
|
12
|
+
import {
|
|
13
|
+
Card,
|
|
14
|
+
CardHeader,
|
|
15
|
+
CardTitle,
|
|
16
|
+
CardContent,
|
|
17
|
+
Badge,
|
|
18
|
+
} from "@checkstack/ui";
|
|
19
|
+
import { ArrowUpRight, AlertTriangle, Info } from "lucide-react";
|
|
20
|
+
|
|
21
|
+
type Props = SlotContext<typeof SystemDetailsTopSlot>;
|
|
22
|
+
|
|
23
|
+
function getAlertStyle(state: DerivedState): {
|
|
24
|
+
border: string;
|
|
25
|
+
bg: string;
|
|
26
|
+
headerBg: string;
|
|
27
|
+
icon: React.ReactNode;
|
|
28
|
+
label: string;
|
|
29
|
+
} {
|
|
30
|
+
switch (state) {
|
|
31
|
+
case "down": {
|
|
32
|
+
return {
|
|
33
|
+
border: "border-destructive/30",
|
|
34
|
+
bg: "bg-destructive/5",
|
|
35
|
+
headerBg: "bg-destructive/10",
|
|
36
|
+
icon: <AlertTriangle className="h-5 w-5 text-destructive" />,
|
|
37
|
+
label: "Critical Upstream Failure",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
case "degraded": {
|
|
41
|
+
return {
|
|
42
|
+
border: "border-warning/30",
|
|
43
|
+
bg: "bg-warning/5",
|
|
44
|
+
headerBg: "bg-warning/10",
|
|
45
|
+
icon: <AlertTriangle className="h-5 w-5 text-warning" />,
|
|
46
|
+
label: "Upstream Degradation",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
default: {
|
|
50
|
+
return {
|
|
51
|
+
border: "border-info/30",
|
|
52
|
+
bg: "bg-info/5",
|
|
53
|
+
headerBg: "bg-info/10",
|
|
54
|
+
icon: <Info className="h-5 w-5 text-info" />,
|
|
55
|
+
label: "Upstream Dependency Notice",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getImpactBadge(impactType: string): React.ReactNode {
|
|
62
|
+
switch (impactType) {
|
|
63
|
+
case "critical": {
|
|
64
|
+
return <Badge variant="destructive">Critical</Badge>;
|
|
65
|
+
}
|
|
66
|
+
case "degraded": {
|
|
67
|
+
return <Badge variant="warning">Degraded</Badge>;
|
|
68
|
+
}
|
|
69
|
+
default: {
|
|
70
|
+
return <Badge variant="secondary">Info</Badge>;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Alert banner component injected into the SystemDetailsTopSlot.
|
|
77
|
+
* Shows warnings when upstream dependencies are affected.
|
|
78
|
+
*/
|
|
79
|
+
export const DependencyAlert: React.FC<Props> = ({ system }) => {
|
|
80
|
+
const depClient = usePluginClient(DependencyApi);
|
|
81
|
+
|
|
82
|
+
const { data, refetch } = depClient.getWarningsForSystem.useQuery(
|
|
83
|
+
{ systemId: system?.id ?? "" },
|
|
84
|
+
{ enabled: !!system?.id },
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Listen for dependency graph changes
|
|
88
|
+
useSignal(DEPENDENCY_CHANGED, () => {
|
|
89
|
+
void refetch();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Listen for warning re-evaluations
|
|
93
|
+
useSignal(DEPENDENCY_WARNINGS_CHANGED, ({ affectedSystemIds }) => {
|
|
94
|
+
if (system?.id && affectedSystemIds.includes(system.id)) {
|
|
95
|
+
void refetch();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!data || data.affectedUpstreams.length === 0) return;
|
|
100
|
+
|
|
101
|
+
const style = getAlertStyle(data.derivedState);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Card className={`${style.border} ${style.bg}`}>
|
|
105
|
+
<CardHeader
|
|
106
|
+
className={`border-b border-border py-3 ${style.headerBg}`}
|
|
107
|
+
>
|
|
108
|
+
<div className="flex items-center gap-2">
|
|
109
|
+
{style.icon}
|
|
110
|
+
<CardTitle className="text-lg font-semibold">{style.label}</CardTitle>
|
|
111
|
+
</div>
|
|
112
|
+
</CardHeader>
|
|
113
|
+
<CardContent className="p-4 space-y-2">
|
|
114
|
+
<p className="text-sm text-muted-foreground mb-3">
|
|
115
|
+
This system is affected by upstream dependency issues:
|
|
116
|
+
</p>
|
|
117
|
+
{data.affectedUpstreams.map((upstream: AffectedUpstream) => (
|
|
118
|
+
<div
|
|
119
|
+
key={upstream.systemId}
|
|
120
|
+
className="flex items-center justify-between p-2 rounded border border-border bg-background"
|
|
121
|
+
>
|
|
122
|
+
<div className="flex items-center gap-2">
|
|
123
|
+
<ArrowUpRight className="h-4 w-4 text-muted-foreground" />
|
|
124
|
+
<span className="text-sm font-medium">
|
|
125
|
+
{upstream.systemName}
|
|
126
|
+
</span>
|
|
127
|
+
{upstream.dependencyLabel && (
|
|
128
|
+
<span className="text-xs text-muted-foreground">
|
|
129
|
+
({upstream.dependencyLabel})
|
|
130
|
+
</span>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
<div className="flex items-center gap-2">
|
|
134
|
+
<Badge variant="outline" className="text-xs">
|
|
135
|
+
{upstream.ownStatus}
|
|
136
|
+
</Badge>
|
|
137
|
+
{getImpactBadge(upstream.impactType)}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
))}
|
|
141
|
+
</CardContent>
|
|
142
|
+
</Card>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { usePluginClient, type SlotContext } from "@checkstack/frontend-api";
|
|
3
|
+
import { useSignal } from "@checkstack/signal-frontend";
|
|
4
|
+
import { SystemStateBadgesSlot } from "@checkstack/catalog-common";
|
|
5
|
+
import {
|
|
6
|
+
DependencyApi,
|
|
7
|
+
DEPENDENCY_CHANGED,
|
|
8
|
+
DEPENDENCY_WARNINGS_CHANGED,
|
|
9
|
+
type DerivedState,
|
|
10
|
+
} from "@checkstack/dependency-common";
|
|
11
|
+
import { Badge } from "@checkstack/ui";
|
|
12
|
+
|
|
13
|
+
type Props = SlotContext<typeof SystemStateBadgesSlot>;
|
|
14
|
+
|
|
15
|
+
function getBadgeVariant(
|
|
16
|
+
state: DerivedState,
|
|
17
|
+
): "info" | "warning" | "destructive" {
|
|
18
|
+
switch (state) {
|
|
19
|
+
case "down": {
|
|
20
|
+
return "destructive";
|
|
21
|
+
}
|
|
22
|
+
case "degraded": {
|
|
23
|
+
return "warning";
|
|
24
|
+
}
|
|
25
|
+
default: {
|
|
26
|
+
return "info";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getBadgeLabel(state: DerivedState): string {
|
|
32
|
+
switch (state) {
|
|
33
|
+
case "down": {
|
|
34
|
+
return "Upstream Down";
|
|
35
|
+
}
|
|
36
|
+
case "degraded": {
|
|
37
|
+
return "Upstream Degraded";
|
|
38
|
+
}
|
|
39
|
+
default: {
|
|
40
|
+
return "Dep. Info";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Displays a dependency warning badge for a system on the dashboard.
|
|
47
|
+
* Shows nothing if no upstream systems are affected.
|
|
48
|
+
*
|
|
49
|
+
* Listens for realtime updates via signals.
|
|
50
|
+
*/
|
|
51
|
+
export const DependencyBadge: React.FC<Props> = ({ system }) => {
|
|
52
|
+
const depClient = usePluginClient(DependencyApi);
|
|
53
|
+
|
|
54
|
+
const { data, refetch } = depClient.getWarningsForSystem.useQuery(
|
|
55
|
+
{ systemId: system?.id ?? "" },
|
|
56
|
+
{ enabled: !!system?.id },
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Listen for dependency changes
|
|
60
|
+
useSignal(DEPENDENCY_CHANGED, () => {
|
|
61
|
+
void refetch();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Listen for warning re-evaluations
|
|
65
|
+
useSignal(DEPENDENCY_WARNINGS_CHANGED, ({ affectedSystemIds }) => {
|
|
66
|
+
if (system?.id && affectedSystemIds.includes(system.id)) {
|
|
67
|
+
void refetch();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!data) return;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Badge variant={getBadgeVariant(data.derivedState)}>
|
|
75
|
+
{getBadgeLabel(data.derivedState)}
|
|
76
|
+
</Badge>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ImpactType } from "@checkstack/dependency-common";
|
|
3
|
+
import { Toggle } from "@checkstack/ui";
|
|
4
|
+
import { HealthCheckRulesEditor } from "./HealthCheckRulesEditor";
|
|
5
|
+
|
|
6
|
+
interface HealthCheckRule {
|
|
7
|
+
healthCheckId: string;
|
|
8
|
+
overrideImpactType: ImpactType;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/** Current impact type */
|
|
13
|
+
impactType: ImpactType;
|
|
14
|
+
/** Callback when impact type changes */
|
|
15
|
+
onImpactTypeChange: (impactType: ImpactType) => void;
|
|
16
|
+
/** Current transitive (multi-hop) setting */
|
|
17
|
+
transitive: boolean;
|
|
18
|
+
/** Callback when transitive setting changes */
|
|
19
|
+
onTransitiveChange: (transitive: boolean) => void;
|
|
20
|
+
/** The upstream system ID — needed for health check rules lookup */
|
|
21
|
+
targetSystemId: string;
|
|
22
|
+
/** Current health check rules */
|
|
23
|
+
healthCheckRules: HealthCheckRule[];
|
|
24
|
+
/** Callback when health check rules change */
|
|
25
|
+
onHealthCheckRulesChange: (rules: HealthCheckRule[]) => void;
|
|
26
|
+
/** Compact mode for tight layouts (e.g. map edge editor panel) */
|
|
27
|
+
compact?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Shared form fields for editing a dependency edge.
|
|
32
|
+
* Used by both the list editor (DependencyEditor) and the map edge editor panel.
|
|
33
|
+
* Renders: impact type select, multi-hop toggle, and health check rules editor.
|
|
34
|
+
*/
|
|
35
|
+
export const DependencyEdgeForm: React.FC<Props> = ({
|
|
36
|
+
impactType,
|
|
37
|
+
onImpactTypeChange,
|
|
38
|
+
transitive,
|
|
39
|
+
onTransitiveChange,
|
|
40
|
+
targetSystemId,
|
|
41
|
+
healthCheckRules,
|
|
42
|
+
onHealthCheckRulesChange,
|
|
43
|
+
compact = false,
|
|
44
|
+
}) => {
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<div className="space-y-2">
|
|
48
|
+
<label className="text-sm font-medium">Impact</label>
|
|
49
|
+
<select
|
|
50
|
+
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
|
|
51
|
+
value={impactType}
|
|
52
|
+
onChange={(e) => onImpactTypeChange(e.target.value as ImpactType)}
|
|
53
|
+
>
|
|
54
|
+
<option value="informational">Informational</option>
|
|
55
|
+
<option value="degraded">Degraded</option>
|
|
56
|
+
<option value="critical">Critical</option>
|
|
57
|
+
</select>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="flex items-center justify-between rounded-lg border border-border p-3">
|
|
60
|
+
<div className="space-y-0.5">
|
|
61
|
+
<label className="text-sm font-medium">
|
|
62
|
+
{compact ? "Multi-hop" : "Multi-hop propagation"}
|
|
63
|
+
</label>
|
|
64
|
+
<p className="text-xs text-muted-foreground">
|
|
65
|
+
{compact
|
|
66
|
+
? "Cascade failures transitively."
|
|
67
|
+
: "Propagate status warnings through transitive dependency chains."}
|
|
68
|
+
</p>
|
|
69
|
+
</div>
|
|
70
|
+
<Toggle
|
|
71
|
+
checked={transitive}
|
|
72
|
+
onCheckedChange={onTransitiveChange}
|
|
73
|
+
aria-label="Enable multi-hop propagation"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
<HealthCheckRulesEditor
|
|
77
|
+
targetSystemId={targetSystemId}
|
|
78
|
+
rules={healthCheckRules}
|
|
79
|
+
onChange={onHealthCheckRulesChange}
|
|
80
|
+
compact={compact}
|
|
81
|
+
/>
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
};
|