@cyvest/cyvest-vis 2.0.1

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.
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Utility functions for observable visualization.
3
+ */
4
+
5
+ import type { ObservableShape } from "../types";
6
+
7
+ /**
8
+ * Map observable types to emojis.
9
+ */
10
+ const OBSERVABLE_EMOJI_MAP: Record<string, string> = {
11
+ // Network
12
+ "ipv4-addr": "🌐",
13
+ "ipv6-addr": "🌐",
14
+ "domain-name": "🏠",
15
+ url: "πŸ”—",
16
+ "autonomous-system": "🌍",
17
+ "mac-addr": "πŸ“Ά",
18
+
19
+ // Email
20
+ "email-addr": "πŸ“§",
21
+ "email-message": "βœ‰οΈ",
22
+
23
+ // File
24
+ file: "πŸ“„",
25
+ "file-hash": "πŸ”",
26
+ "file:hash:md5": "πŸ”",
27
+ "file:hash:sha1": "πŸ”",
28
+ "file:hash:sha256": "πŸ”",
29
+
30
+ // User/Identity
31
+ user: "πŸ‘€",
32
+ "user-account": "πŸ‘€",
33
+ identity: "πŸͺͺ",
34
+
35
+ // Process/System
36
+ process: "βš™οΈ",
37
+ software: "πŸ’Ώ",
38
+ "windows-registry-key": "πŸ“",
39
+
40
+ // Threat Intelligence
41
+ "threat-actor": "πŸ‘Ή",
42
+ malware: "🦠",
43
+ "attack-pattern": "βš”οΈ",
44
+ campaign: "🎯",
45
+ indicator: "🚨",
46
+
47
+ // Artifacts
48
+ artifact: "πŸ§ͺ",
49
+ certificate: "πŸ“œ",
50
+ "x509-certificate": "πŸ“œ",
51
+
52
+ // Default
53
+ unknown: "❓",
54
+ };
55
+
56
+ /**
57
+ * Get the emoji for an observable type.
58
+ * Falls back to a generic icon if type is unknown.
59
+ */
60
+ export function getObservableEmoji(observableType: string): string {
61
+ const normalized = observableType.toLowerCase().trim();
62
+ return OBSERVABLE_EMOJI_MAP[normalized] ?? OBSERVABLE_EMOJI_MAP.unknown;
63
+ }
64
+
65
+ /**
66
+ * Map observable types to shapes.
67
+ */
68
+ const OBSERVABLE_SHAPE_MAP: Record<string, ObservableShape> = {
69
+ // Domains get squares
70
+ "domain-name": "square",
71
+
72
+ // URLs get circles
73
+ url: "circle",
74
+
75
+ // IPs get triangles
76
+ "ipv4-addr": "triangle",
77
+ "ipv6-addr": "triangle",
78
+
79
+ // Root/files get rectangles (default for root)
80
+ file: "rectangle",
81
+ "email-message": "rectangle",
82
+ };
83
+
84
+ /**
85
+ * Get the shape for an observable type.
86
+ */
87
+ export function getObservableShape(
88
+ observableType: string,
89
+ isRoot: boolean
90
+ ): ObservableShape {
91
+ if (isRoot) {
92
+ return "rectangle";
93
+ }
94
+ const normalized = observableType.toLowerCase().trim();
95
+ return OBSERVABLE_SHAPE_MAP[normalized] ?? "circle";
96
+ }
97
+
98
+ /**
99
+ * Truncate a label, optionally in the middle for long strings.
100
+ */
101
+ export function truncateLabel(
102
+ value: string,
103
+ maxLength: number = 20,
104
+ truncateMiddle: boolean = true
105
+ ): string {
106
+ if (value.length <= maxLength) {
107
+ return value;
108
+ }
109
+
110
+ if (truncateMiddle) {
111
+ const halfLen = Math.floor((maxLength - 3) / 2);
112
+ return `${value.slice(0, halfLen)}…${value.slice(-halfLen)}`;
113
+ }
114
+
115
+ return `${value.slice(0, maxLength - 1)}…`;
116
+ }
117
+
118
+ /**
119
+ * Get color for security level.
120
+ */
121
+ export function getLevelColor(level: string): string {
122
+ const colors: Record<string, string> = {
123
+ NONE: "#6b7280", // gray-500
124
+ TRUSTED: "#22c55e", // green-500
125
+ INFO: "#3b82f6", // blue-500
126
+ SAFE: "#22c55e", // green-500
127
+ NOTABLE: "#eab308", // yellow-500
128
+ SUSPICIOUS: "#f97316", // orange-500
129
+ MALICIOUS: "#ef4444", // red-500
130
+ };
131
+ return colors[level] ?? colors.NONE;
132
+ }
133
+
134
+ /**
135
+ * Get background color for security level (lighter version).
136
+ */
137
+ export function getLevelBackgroundColor(level: string): string {
138
+ const colors: Record<string, string> = {
139
+ NONE: "#f3f4f6", // gray-100
140
+ TRUSTED: "#dcfce7", // green-100
141
+ INFO: "#dbeafe", // blue-100
142
+ SAFE: "#dcfce7", // green-100
143
+ NOTABLE: "#fef9c3", // yellow-100
144
+ SUSPICIOUS: "#ffedd5", // orange-100
145
+ MALICIOUS: "#fee2e2", // red-100
146
+ };
147
+ return colors[level] ?? colors.NONE;
148
+ }
149
+
150
+ /**
151
+ * Emoji map for investigation node types.
152
+ */
153
+ const INVESTIGATION_NODE_EMOJI: Record<string, string> = {
154
+ root: "🎯",
155
+ check: "βœ…",
156
+ container: "πŸ“¦",
157
+ };
158
+
159
+ /**
160
+ * Get emoji for investigation node type.
161
+ */
162
+ export function getInvestigationNodeEmoji(nodeType: string): string {
163
+ return INVESTIGATION_NODE_EMOJI[nodeType] ?? "❓";
164
+ }
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ getInvestigationNodeEmoji,
5
+ getLevelBackgroundColor,
6
+ getLevelColor,
7
+ getObservableEmoji,
8
+ getObservableShape,
9
+ truncateLabel,
10
+ } from "../src/utils/observables";
11
+
12
+ describe("observables utils", () => {
13
+ it("returns emojis for known and unknown observable types", () => {
14
+ expect(getObservableEmoji("domain-name")).toBe("🏠");
15
+ expect(getObservableEmoji("IPv4-Addr")).toBe("🌐");
16
+ expect(getObservableEmoji("unmapped")).toBe("❓");
17
+ });
18
+
19
+ it("returns shapes based on type and root flag", () => {
20
+ expect(getObservableShape("domain-name", false)).toBe("square");
21
+ expect(getObservableShape("ipv6-addr", false)).toBe("triangle");
22
+ expect(getObservableShape("anything-else", false)).toBe("circle");
23
+ expect(getObservableShape("anything-else", true)).toBe("rectangle");
24
+ });
25
+
26
+ it("truncates long labels in the middle by default", () => {
27
+ expect(truncateLabel("short", 10)).toBe("short");
28
+ expect(truncateLabel("averyverylongvalue", 10)).toBe("ave…lue");
29
+ expect(truncateLabel("averyverylongvalue", 10, false)).toBe("averyvery…");
30
+ });
31
+
32
+ it("maps levels to colors and falls back to NONE", () => {
33
+ expect(getLevelColor("SUSPICIOUS")).toBe("#f97316");
34
+ expect(getLevelColor("UNKNOWN")).toBe("#6b7280");
35
+ expect(getLevelBackgroundColor("SUSPICIOUS")).toBe("#ffedd5");
36
+ expect(getLevelBackgroundColor("UNKNOWN")).toBe("#f3f4f6");
37
+ });
38
+
39
+ it("returns investigation node emoji with fallback", () => {
40
+ expect(getInvestigationNodeEmoji("root")).toBe("🎯");
41
+ expect(getInvestigationNodeEmoji("missing")).toBe("❓");
42
+ });
43
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["src"]
4
+ }