@cyvest/cyvest-vis 4.4.0 → 5.0.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/README.md +3 -3
- package/dist/{index.mjs → index.cjs} +384 -355
- package/dist/{index.d.mts → index.d.cts} +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +372 -367
- package/package.json +16 -3
- package/src/components/Icons.tsx +3 -3
- package/src/components/InvestigationGraph.tsx +79 -57
- package/src/components/InvestigationNode.tsx +2 -2
- package/src/types.ts +4 -4
package/package.json
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyvest/cyvest-vis",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"main": "dist/index.cjs",
|
|
5
|
-
"module": "dist/index.
|
|
6
|
+
"module": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
7
20
|
"sideEffects": false,
|
|
8
21
|
"peerDependencies": {
|
|
9
22
|
"react": "^19.0.0",
|
|
@@ -13,7 +26,7 @@
|
|
|
13
26
|
"@dagrejs/dagre": "^1.1.8",
|
|
14
27
|
"@xyflow/react": "^12.10.0",
|
|
15
28
|
"d3-force": "^3.0.0",
|
|
16
|
-
"@cyvest/cyvest-js": "
|
|
29
|
+
"@cyvest/cyvest-js": "5.0.0"
|
|
17
30
|
},
|
|
18
31
|
"devDependencies": {
|
|
19
32
|
"@types/d3-force": "^3.0.10",
|
package/src/components/Icons.tsx
CHANGED
|
@@ -594,9 +594,9 @@ export const CheckIcon: React.FC<IconProps> = ({
|
|
|
594
594
|
);
|
|
595
595
|
|
|
596
596
|
/**
|
|
597
|
-
*
|
|
597
|
+
* Tag icon for tags
|
|
598
598
|
*/
|
|
599
|
-
export const
|
|
599
|
+
export const TagIcon: React.FC<IconProps> = ({
|
|
600
600
|
size = defaultSize,
|
|
601
601
|
color = defaultColor,
|
|
602
602
|
className,
|
|
@@ -706,7 +706,7 @@ export const INVESTIGATION_ICON_MAP: Record<
|
|
|
706
706
|
> = {
|
|
707
707
|
root: CrosshairIcon,
|
|
708
708
|
check: CheckIcon,
|
|
709
|
-
|
|
709
|
+
tag: TagIcon,
|
|
710
710
|
};
|
|
711
711
|
|
|
712
712
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* InvestigationGraph component - displays investigation structure with Dagre layout.
|
|
3
|
-
* Shows root observable, checks, and
|
|
3
|
+
* Shows root observable, checks, and tags in a hierarchical view.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo, useCallback } from "react";
|
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
} from "@xyflow/react";
|
|
20
20
|
import "@xyflow/react/dist/style.css";
|
|
21
21
|
|
|
22
|
-
import type { CyvestInvestigation, Check,
|
|
22
|
+
import type { CyvestInvestigation, Check, Tag } from "@cyvest/cyvest-js";
|
|
23
|
+
import { getTagAncestors } from "@cyvest/cyvest-js";
|
|
23
24
|
|
|
24
25
|
import type {
|
|
25
26
|
InvestigationGraphProps,
|
|
@@ -55,21 +56,10 @@ const defaultEdgeOptions = {
|
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
*
|
|
59
|
+
* Get all tags as an array.
|
|
59
60
|
*/
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
): Container[] {
|
|
63
|
-
const result: Container[] = [];
|
|
64
|
-
|
|
65
|
-
for (const container of Object.values(containers)) {
|
|
66
|
-
result.push(container);
|
|
67
|
-
if (container.sub_containers) {
|
|
68
|
-
result.push(...flattenContainers(container.sub_containers));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return result;
|
|
61
|
+
function getAllTags(tags: Record<string, Tag>): Tag[] {
|
|
62
|
+
return Object.values(tags);
|
|
73
63
|
}
|
|
74
64
|
|
|
75
65
|
/**
|
|
@@ -115,31 +105,27 @@ function createInvestigationGraph(
|
|
|
115
105
|
draggable: true,
|
|
116
106
|
});
|
|
117
107
|
|
|
118
|
-
// Collect all check keys that belong to
|
|
108
|
+
// Collect all check keys that belong to tags
|
|
119
109
|
// These checks should NOT have a direct link to the root node
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
for (const
|
|
123
|
-
for (const checkKey of
|
|
124
|
-
|
|
110
|
+
const allTags = getAllTags(investigation.tags);
|
|
111
|
+
const checksInTags = new Set<string>();
|
|
112
|
+
for (const tag of allTags) {
|
|
113
|
+
for (const checkKey of tag.checks) {
|
|
114
|
+
checksInTags.add(checkKey);
|
|
125
115
|
}
|
|
126
116
|
}
|
|
127
117
|
|
|
128
118
|
// Add check nodes
|
|
129
|
-
|
|
130
|
-
const allChecks: Check[] = [];
|
|
131
|
-
for (const checksForKey of Object.values(investigation.checks)) {
|
|
132
|
-
allChecks.push(...checksForKey);
|
|
133
|
-
}
|
|
119
|
+
const allChecks = Object.values(investigation.checks);
|
|
134
120
|
|
|
135
|
-
// Create unique check nodes (by
|
|
121
|
+
// Create unique check nodes (by key to avoid duplicates)
|
|
136
122
|
const seenCheckIds = new Set<string>();
|
|
137
123
|
for (const check of allChecks) {
|
|
138
124
|
if (seenCheckIds.has(check.key)) continue;
|
|
139
125
|
seenCheckIds.add(check.key);
|
|
140
126
|
|
|
141
127
|
const checkNodeData: InvestigationNodeData = {
|
|
142
|
-
label: truncateLabel(check.
|
|
128
|
+
label: truncateLabel(check.check_name, 20),
|
|
143
129
|
nodeType: "check",
|
|
144
130
|
level: check.level,
|
|
145
131
|
score: check.score,
|
|
@@ -155,9 +141,9 @@ function createInvestigationGraph(
|
|
|
155
141
|
draggable: true,
|
|
156
142
|
});
|
|
157
143
|
|
|
158
|
-
// Only create edge from root to check if check is NOT in a
|
|
159
|
-
// Checks in
|
|
160
|
-
if (!
|
|
144
|
+
// Only create edge from root to check if check is NOT in a tag
|
|
145
|
+
// Checks in tags will be linked through their tag instead
|
|
146
|
+
if (!checksInTags.has(check.key)) {
|
|
161
147
|
edges.push({
|
|
162
148
|
id: `edge-root-${check.key}`,
|
|
163
149
|
source: rootKey,
|
|
@@ -168,43 +154,79 @@ function createInvestigationGraph(
|
|
|
168
154
|
}
|
|
169
155
|
}
|
|
170
156
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
157
|
+
// Build hierarchical tag structure
|
|
158
|
+
// First, collect all tag names and find/create ancestor tags
|
|
159
|
+
const tagByName = new Map<string, Tag>();
|
|
160
|
+
for (const tag of allTags) {
|
|
161
|
+
tagByName.set(tag.name, tag);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Collect all unique tag names including synthetic ancestors
|
|
165
|
+
const allTagNames = new Set<string>();
|
|
166
|
+
for (const tag of allTags) {
|
|
167
|
+
allTagNames.add(tag.name);
|
|
168
|
+
// Add ancestors (they may not exist as actual tags)
|
|
169
|
+
for (const ancestor of getTagAncestors(tag.name)) {
|
|
170
|
+
allTagNames.add(ancestor);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create tag nodes (real and synthetic)
|
|
175
|
+
for (const tagName of allTagNames) {
|
|
176
|
+
const realTag = tagByName.get(tagName);
|
|
177
|
+
|
|
178
|
+
const tagNodeData: InvestigationNodeData = {
|
|
179
|
+
label: truncateLabel(tagName.split(":").pop() ?? tagName, 20),
|
|
180
|
+
nodeType: "tag",
|
|
181
|
+
level: realTag?.direct_level ?? "INFO",
|
|
182
|
+
score: realTag?.direct_score ?? 0,
|
|
183
|
+
name: tagName,
|
|
182
184
|
};
|
|
183
185
|
|
|
184
186
|
nodes.push({
|
|
185
|
-
id: `
|
|
187
|
+
id: `tag-${tagName}`,
|
|
186
188
|
type: "investigation",
|
|
187
189
|
position: { x: 0, y: 0 },
|
|
188
|
-
data:
|
|
190
|
+
data: tagNodeData,
|
|
189
191
|
selectable: true,
|
|
190
192
|
draggable: true,
|
|
191
193
|
});
|
|
194
|
+
}
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
// Create edges based on tag hierarchy
|
|
197
|
+
for (const tagName of allTagNames) {
|
|
198
|
+
const nodeId = `tag-${tagName}`;
|
|
199
|
+
const parts = tagName.split(":");
|
|
200
|
+
|
|
201
|
+
if (parts.length === 1) {
|
|
202
|
+
// Top-level tag, connect to root
|
|
203
|
+
edges.push({
|
|
204
|
+
id: `edge-root-tag-${tagName}`,
|
|
205
|
+
source: rootKey,
|
|
206
|
+
target: nodeId,
|
|
207
|
+
type: "smoothstep",
|
|
208
|
+
animated: false,
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
// Has a parent tag, connect to parent
|
|
212
|
+
const parentName = parts.slice(0, -1).join(":");
|
|
213
|
+
edges.push({
|
|
214
|
+
id: `edge-tag-${parentName}-${tagName}`,
|
|
215
|
+
source: `tag-${parentName}`,
|
|
216
|
+
target: nodeId,
|
|
217
|
+
type: "smoothstep",
|
|
218
|
+
animated: false,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
201
222
|
|
|
202
|
-
|
|
203
|
-
|
|
223
|
+
// Create edges from tags to their checks (only for real tags with checks)
|
|
224
|
+
for (const tag of allTags) {
|
|
225
|
+
for (const checkKey of tag.checks) {
|
|
204
226
|
if (seenCheckIds.has(checkKey)) {
|
|
205
227
|
edges.push({
|
|
206
|
-
id: `edge-
|
|
207
|
-
source: `
|
|
228
|
+
id: `edge-tag-check-${tag.name}-${checkKey}`,
|
|
229
|
+
source: `tag-${tag.name}`,
|
|
208
230
|
target: `check-${checkKey}`,
|
|
209
231
|
type: "smoothstep",
|
|
210
232
|
animated: false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Custom node component for the Investigation Graph (Dagre layout).
|
|
3
|
-
* Professional design with SVG icons for root, check, and
|
|
3
|
+
* Professional design with SVG icons for root, check, and tag nodes.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { memo, useMemo } from "react";
|
|
@@ -33,7 +33,7 @@ const NODE_CONFIG = {
|
|
|
33
33
|
showIcon: false, // No icon for checks
|
|
34
34
|
alignCenter: false, // Left-aligned
|
|
35
35
|
},
|
|
36
|
-
|
|
36
|
+
tag: {
|
|
37
37
|
minWidth: 120,
|
|
38
38
|
padding: "8px 14px",
|
|
39
39
|
borderRadius: 16,
|
package/src/types.ts
CHANGED
|
@@ -66,7 +66,7 @@ export type ObservableEdge = Edge<ObservableEdgeData>;
|
|
|
66
66
|
/**
|
|
67
67
|
* Node types for the investigation graph view.
|
|
68
68
|
*/
|
|
69
|
-
export type InvestigationNodeType = "root" | "check" | "
|
|
69
|
+
export type InvestigationNodeType = "root" | "check" | "tag";
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Data attached to investigation graph nodes.
|
|
@@ -74,7 +74,7 @@ export type InvestigationNodeType = "root" | "check" | "container";
|
|
|
74
74
|
export interface InvestigationNodeData extends Record<string, unknown> {
|
|
75
75
|
/** Display label */
|
|
76
76
|
label: string;
|
|
77
|
-
/** Node type (root, check, or
|
|
77
|
+
/** Node type (root, check, or tag) */
|
|
78
78
|
nodeType: InvestigationNodeType;
|
|
79
79
|
/** Security level */
|
|
80
80
|
level: Level;
|
|
@@ -82,8 +82,8 @@ export interface InvestigationNodeData extends Record<string, unknown> {
|
|
|
82
82
|
score: number;
|
|
83
83
|
/** Description (for checks) */
|
|
84
84
|
description?: string;
|
|
85
|
-
/**
|
|
86
|
-
|
|
85
|
+
/** Name (for tags) */
|
|
86
|
+
name?: string;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|