@datasynx/agentic-ai-cartography 0.1.9 → 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/dist/bookmarks-O7KNR7D3.js +8 -0
- package/dist/bookmarks-O7KNR7D3.js.map +1 -0
- package/dist/chunk-EVJP2FWQ.js +118 -0
- package/dist/chunk-EVJP2FWQ.js.map +1 -0
- package/dist/chunk-GUZXO6PM.js +647 -0
- package/dist/chunk-GUZXO6PM.js.map +1 -0
- package/dist/chunk-JAFRT2R6.js +97 -0
- package/dist/chunk-JAFRT2R6.js.map +1 -0
- package/dist/cli.js +650 -715
- package/dist/cli.js.map +1 -1
- package/dist/exporter-BDVDYA3K.js +24 -0
- package/dist/exporter-BDVDYA3K.js.map +1 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +342 -3
- package/dist/index.js.map +1 -1
- package/dist/types-NKF6BRMZ.js +26 -0
- package/dist/types-NKF6BRMZ.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/exporter.ts
|
|
4
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
function nodeLayer(type) {
|
|
7
|
+
if (type === "saas_tool") return "saas";
|
|
8
|
+
if (["web_service", "api_endpoint"].includes(type)) return "web";
|
|
9
|
+
if (["database_server", "database", "table", "cache_server"].includes(type)) return "data";
|
|
10
|
+
if (["message_broker", "queue", "topic"].includes(type)) return "messaging";
|
|
11
|
+
if (["host", "container", "pod", "k8s_cluster"].includes(type)) return "infra";
|
|
12
|
+
if (type === "config_file") return "config";
|
|
13
|
+
return "other";
|
|
14
|
+
}
|
|
15
|
+
var LAYER_LABELS = {
|
|
16
|
+
saas: "\u2601 SaaS Tools",
|
|
17
|
+
web: "\u{1F310} Web / API",
|
|
18
|
+
data: "\u{1F5C4} Data Layer",
|
|
19
|
+
messaging: "\u{1F4E8} Messaging",
|
|
20
|
+
infra: "\u{1F5A5} Infrastructure",
|
|
21
|
+
config: "\u{1F4C4} Config",
|
|
22
|
+
other: "\u2753 Sonstige"
|
|
23
|
+
};
|
|
24
|
+
var LAYER_ORDER = ["saas", "web", "data", "messaging", "infra", "config", "other"];
|
|
25
|
+
var MERMAID_ICONS = {
|
|
26
|
+
host: "\u{1F5A5}",
|
|
27
|
+
database_server: "\u{1F5C4}",
|
|
28
|
+
database: "\u{1F5C4}",
|
|
29
|
+
table: "\u{1F4CB}",
|
|
30
|
+
web_service: "\u{1F310}",
|
|
31
|
+
api_endpoint: "\u{1F50C}",
|
|
32
|
+
cache_server: "\u26A1",
|
|
33
|
+
message_broker: "\u{1F4E8}",
|
|
34
|
+
queue: "\u{1F4EC}",
|
|
35
|
+
topic: "\u{1F4E2}",
|
|
36
|
+
container: "\u{1F4E6}",
|
|
37
|
+
pod: "\u2638",
|
|
38
|
+
k8s_cluster: "\u2638",
|
|
39
|
+
config_file: "\u{1F4C4}",
|
|
40
|
+
saas_tool: "\u2601",
|
|
41
|
+
unknown: "\u2753"
|
|
42
|
+
};
|
|
43
|
+
var EDGE_LABELS = {
|
|
44
|
+
connects_to: "\u2192",
|
|
45
|
+
reads_from: "reads",
|
|
46
|
+
writes_to: "writes",
|
|
47
|
+
calls: "calls",
|
|
48
|
+
contains: "contains",
|
|
49
|
+
depends_on: "depends on"
|
|
50
|
+
};
|
|
51
|
+
var MERMAID_CLASSES = {
|
|
52
|
+
host: "fill:#1e3352,stroke:#4a82c4,color:#cce",
|
|
53
|
+
database_server: "fill:#1e3352,stroke:#4a82c4,color:#cce",
|
|
54
|
+
database: "fill:#163352,stroke:#3a8ad4,color:#bdf",
|
|
55
|
+
table: "fill:#0f2a40,stroke:#2a6090,color:#9bd",
|
|
56
|
+
web_service: "fill:#1a3a1a,stroke:#3a9a3a,color:#bfb",
|
|
57
|
+
api_endpoint: "fill:#0f2a0f,stroke:#2a7a2a,color:#9d9",
|
|
58
|
+
cache_server: "fill:#3a2a0a,stroke:#ca8a0a,color:#fda",
|
|
59
|
+
message_broker: "fill:#2a1a3a,stroke:#7a3aaa,color:#daf",
|
|
60
|
+
queue: "fill:#1f1030,stroke:#5a2a8a,color:#caf",
|
|
61
|
+
topic: "fill:#1f1030,stroke:#5a2a8a,color:#caf",
|
|
62
|
+
container: "fill:#1a2a3a,stroke:#3a6a9a,color:#acd",
|
|
63
|
+
pod: "fill:#0f1f2f,stroke:#2a5a8a,color:#8bc",
|
|
64
|
+
k8s_cluster: "fill:#0a1520,stroke:#1a4a7a,color:#7ab",
|
|
65
|
+
config_file: "fill:#2a2a1a,stroke:#7a7a2a,color:#ddc",
|
|
66
|
+
saas_tool: "fill:#2a1a2a,stroke:#9a3a9a,color:#daf",
|
|
67
|
+
unknown: "fill:#2a2a2a,stroke:#5a5a5a,color:#aaa"
|
|
68
|
+
};
|
|
69
|
+
function sanitize(id) {
|
|
70
|
+
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
71
|
+
}
|
|
72
|
+
function nodeLabel(node) {
|
|
73
|
+
const icon = MERMAID_ICONS[node.type] ?? "?";
|
|
74
|
+
const parts = node.id.split(":");
|
|
75
|
+
const location = parts.length >= 3 ? `${parts[1]}:${parts[2]}` : parts[1] ?? "";
|
|
76
|
+
const conf = `${Math.round(node.confidence * 100)}%`;
|
|
77
|
+
const meta = node.metadata;
|
|
78
|
+
const extras = [];
|
|
79
|
+
for (const key of ["category", "version", "description"]) {
|
|
80
|
+
const v = meta[key];
|
|
81
|
+
if (typeof v === "string" && v.length > 0) {
|
|
82
|
+
extras.push(v.substring(0, 28));
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const locLine = location ? `<br/><small>${location}</small>` : "";
|
|
87
|
+
const extraLine = extras.length ? `<br/><small>${extras[0]}</small>` : "";
|
|
88
|
+
return `["${icon} <b>${node.name}</b>${locLine}${extraLine}<br/><small>${node.type} \xB7 ${conf}</small>"]`;
|
|
89
|
+
}
|
|
90
|
+
function generateTopologyMermaid(nodes, edges) {
|
|
91
|
+
if (nodes.length === 0) return 'graph TB\n empty["No nodes discovered yet"]';
|
|
92
|
+
const lines = ["graph TB"];
|
|
93
|
+
const usedTypes = new Set(nodes.map((n) => n.type));
|
|
94
|
+
for (const type of usedTypes) {
|
|
95
|
+
const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES["unknown"];
|
|
96
|
+
lines.push(` classDef ${type.replace(/_/g, "")} ${style}`);
|
|
97
|
+
}
|
|
98
|
+
lines.push("");
|
|
99
|
+
const layerMap = /* @__PURE__ */ new Map();
|
|
100
|
+
for (const node of nodes) {
|
|
101
|
+
const layer = nodeLayer(node.type);
|
|
102
|
+
if (!layerMap.has(layer)) layerMap.set(layer, []);
|
|
103
|
+
layerMap.get(layer).push(node);
|
|
104
|
+
}
|
|
105
|
+
for (const layerKey of LAYER_ORDER) {
|
|
106
|
+
const layerNodes = layerMap.get(layerKey);
|
|
107
|
+
if (!layerNodes || layerNodes.length === 0) continue;
|
|
108
|
+
const label = LAYER_LABELS[layerKey] ?? layerKey;
|
|
109
|
+
lines.push(` subgraph ${layerKey}["${label}"]`);
|
|
110
|
+
for (const node of layerNodes) {
|
|
111
|
+
lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, "")}`);
|
|
112
|
+
}
|
|
113
|
+
lines.push(" end");
|
|
114
|
+
lines.push("");
|
|
115
|
+
}
|
|
116
|
+
for (const edge of edges) {
|
|
117
|
+
const src = sanitize(edge.sourceId);
|
|
118
|
+
const tgt = sanitize(edge.targetId);
|
|
119
|
+
const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;
|
|
120
|
+
const arrow = edge.confidence < 0.6 ? `-. "${label}" .->` : `-->|"${label}"|`;
|
|
121
|
+
lines.push(` ${src} ${arrow} ${tgt}`);
|
|
122
|
+
}
|
|
123
|
+
return lines.join("\n");
|
|
124
|
+
}
|
|
125
|
+
function generateDependencyMermaid(nodes, edges) {
|
|
126
|
+
const depEdges = edges.filter(
|
|
127
|
+
(e) => ["calls", "reads_from", "writes_to", "depends_on"].includes(e.relationship)
|
|
128
|
+
);
|
|
129
|
+
if (depEdges.length === 0) return 'graph LR\n empty["No dependency edges found"]';
|
|
130
|
+
const lines = ["graph LR"];
|
|
131
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
132
|
+
for (const edge of depEdges) {
|
|
133
|
+
usedIds.add(edge.sourceId);
|
|
134
|
+
usedIds.add(edge.targetId);
|
|
135
|
+
}
|
|
136
|
+
const usedNodes = nodes.filter((n) => usedIds.has(n.id));
|
|
137
|
+
const usedTypes = new Set(usedNodes.map((n) => n.type));
|
|
138
|
+
for (const type of usedTypes) {
|
|
139
|
+
const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES["unknown"];
|
|
140
|
+
lines.push(` classDef ${type.replace(/_/g, "")} ${style}`);
|
|
141
|
+
}
|
|
142
|
+
lines.push("");
|
|
143
|
+
for (const node of usedNodes) {
|
|
144
|
+
lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, "")}`);
|
|
145
|
+
}
|
|
146
|
+
lines.push("");
|
|
147
|
+
for (const edge of depEdges) {
|
|
148
|
+
const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;
|
|
149
|
+
lines.push(` ${sanitize(edge.sourceId)} -->|"${label}"| ${sanitize(edge.targetId)}`);
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
function generateWorkflowMermaid(sop) {
|
|
154
|
+
const lines = ["flowchart TD"];
|
|
155
|
+
for (const step of sop.steps) {
|
|
156
|
+
const nodeId = `S${step.order}`;
|
|
157
|
+
const label = `${step.order}. ${step.instruction.substring(0, 60)}`;
|
|
158
|
+
lines.push(` ${nodeId}["${label}"]`);
|
|
159
|
+
if (step.order > 1) {
|
|
160
|
+
lines.push(` S${step.order - 1} --> ${nodeId}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
|
165
|
+
function exportBackstageYAML(nodes, edges, org) {
|
|
166
|
+
const owner = org ?? "unknown";
|
|
167
|
+
const docs = [];
|
|
168
|
+
for (const node of nodes) {
|
|
169
|
+
const isComponent = ["web_service", "container", "pod"].includes(node.type);
|
|
170
|
+
const isAPI = node.type === "api_endpoint";
|
|
171
|
+
const kind = isComponent ? "Component" : isAPI ? "API" : "Resource";
|
|
172
|
+
const deps = edges.filter((e) => e.sourceId === node.id).map((e) => ` - resource:default/${sanitize(e.targetId)}`);
|
|
173
|
+
const doc = [
|
|
174
|
+
`apiVersion: backstage.io/v1alpha1`,
|
|
175
|
+
`kind: ${kind}`,
|
|
176
|
+
`metadata:`,
|
|
177
|
+
` name: ${sanitize(node.id)}`,
|
|
178
|
+
` annotations:`,
|
|
179
|
+
` cartography/discovered-at: "${node.discoveredAt}"`,
|
|
180
|
+
` cartography/confidence: "${node.confidence}"`,
|
|
181
|
+
`spec:`,
|
|
182
|
+
` type: ${node.type}`,
|
|
183
|
+
` lifecycle: production`,
|
|
184
|
+
` owner: ${owner}`,
|
|
185
|
+
...deps.length > 0 ? [" dependsOn:", ...deps] : []
|
|
186
|
+
].join("\n");
|
|
187
|
+
docs.push(doc);
|
|
188
|
+
}
|
|
189
|
+
return docs.join("\n---\n");
|
|
190
|
+
}
|
|
191
|
+
function exportJSON(db, sessionId) {
|
|
192
|
+
const nodes = db.getNodes(sessionId);
|
|
193
|
+
const edges = db.getEdges(sessionId);
|
|
194
|
+
const events = db.getEvents(sessionId);
|
|
195
|
+
const tasks = db.getTasks(sessionId);
|
|
196
|
+
const sops = db.getSOPs(sessionId);
|
|
197
|
+
const stats = db.getStats(sessionId);
|
|
198
|
+
return JSON.stringify({
|
|
199
|
+
sessionId,
|
|
200
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
201
|
+
stats,
|
|
202
|
+
nodes,
|
|
203
|
+
edges,
|
|
204
|
+
events,
|
|
205
|
+
tasks,
|
|
206
|
+
sops
|
|
207
|
+
}, null, 2);
|
|
208
|
+
}
|
|
209
|
+
function exportHTML(nodes, edges) {
|
|
210
|
+
const graphData = JSON.stringify({
|
|
211
|
+
nodes: nodes.map((n) => ({
|
|
212
|
+
id: n.id,
|
|
213
|
+
name: n.name,
|
|
214
|
+
type: n.type,
|
|
215
|
+
confidence: n.confidence,
|
|
216
|
+
discoveredVia: n.discoveredVia,
|
|
217
|
+
discoveredAt: n.discoveredAt,
|
|
218
|
+
tags: n.tags,
|
|
219
|
+
metadata: n.metadata
|
|
220
|
+
})),
|
|
221
|
+
links: edges.map((e) => ({
|
|
222
|
+
source: e.sourceId,
|
|
223
|
+
target: e.targetId,
|
|
224
|
+
relationship: e.relationship,
|
|
225
|
+
confidence: e.confidence,
|
|
226
|
+
evidence: e.evidence
|
|
227
|
+
}))
|
|
228
|
+
});
|
|
229
|
+
return `<!DOCTYPE html>
|
|
230
|
+
<html lang="de">
|
|
231
|
+
<head>
|
|
232
|
+
<meta charset="UTF-8">
|
|
233
|
+
<title>Cartography \u2014 Topology</title>
|
|
234
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
235
|
+
<style>
|
|
236
|
+
* { box-sizing: border-box; }
|
|
237
|
+
body { margin: 0; background: #0d1117; color: #e6edf3; font-family: 'SF Mono', 'Fira Code', monospace; display: flex; }
|
|
238
|
+
#graph { flex: 1; height: 100vh; }
|
|
239
|
+
svg { width: 100%; height: 100%; }
|
|
240
|
+
.link { stroke-opacity: 0.5; }
|
|
241
|
+
.link-label { font-size: 9px; fill: #8b949e; }
|
|
242
|
+
.node circle { stroke-width: 2px; cursor: pointer; transition: r 0.15s; }
|
|
243
|
+
.node circle:hover { r: 14; }
|
|
244
|
+
.node text { font-size: 11px; fill: #c9d1d9; pointer-events: none; }
|
|
245
|
+
/* \u2500\u2500 Sidebar \u2500\u2500 */
|
|
246
|
+
#sidebar {
|
|
247
|
+
width: 300px; min-width: 300px; height: 100vh; overflow-y: auto;
|
|
248
|
+
background: #161b22; border-left: 1px solid #30363d;
|
|
249
|
+
padding: 16px; font-size: 12px; line-height: 1.6;
|
|
250
|
+
}
|
|
251
|
+
#sidebar h2 { margin: 0 0 8px; font-size: 14px; color: #58a6ff; }
|
|
252
|
+
#sidebar .meta-table { width: 100%; border-collapse: collapse; }
|
|
253
|
+
#sidebar .meta-table td { padding: 3px 6px; border-bottom: 1px solid #21262d; vertical-align: top; }
|
|
254
|
+
#sidebar .meta-table td:first-child { color: #8b949e; white-space: nowrap; width: 90px; }
|
|
255
|
+
#sidebar .tag { display: inline-block; background: #21262d; border-radius: 3px; padding: 1px 5px; margin: 1px; }
|
|
256
|
+
#sidebar .conf-bar { height: 6px; border-radius: 3px; background: #21262d; margin-top: 3px; }
|
|
257
|
+
#sidebar .conf-fill { height: 100%; border-radius: 3px; }
|
|
258
|
+
#sidebar .edges-list { margin-top: 12px; }
|
|
259
|
+
#sidebar .edge-item { padding: 4px 0; border-bottom: 1px solid #21262d; color: #8b949e; }
|
|
260
|
+
#sidebar .edge-item span { color: #c9d1d9; }
|
|
261
|
+
.hint { color: #484f58; font-size: 11px; margin-top: 8px; }
|
|
262
|
+
#header { position: fixed; top: 10px; left: 10px; background: rgba(13,17,23,0.85);
|
|
263
|
+
padding: 8px 12px; border-radius: 6px; font-size: 12px; border: 1px solid #30363d; }
|
|
264
|
+
#header strong { color: #58a6ff; }
|
|
265
|
+
</style>
|
|
266
|
+
</head>
|
|
267
|
+
<body>
|
|
268
|
+
<div id="graph">
|
|
269
|
+
<div id="header">
|
|
270
|
+
<strong>Cartography</strong>
|
|
271
|
+
<span style="color:#8b949e">${nodes.length} Nodes \xB7 ${edges.length} Edges</span><br>
|
|
272
|
+
<span style="color:#484f58;font-size:10px">Scroll=zoom \xB7 Drag=pan \xB7 Click=details</span>
|
|
273
|
+
</div>
|
|
274
|
+
<svg></svg>
|
|
275
|
+
</div>
|
|
276
|
+
<div id="sidebar">
|
|
277
|
+
<h2>Infrastructure Map</h2>
|
|
278
|
+
<p class="hint">Klicke einen Node um Details anzuzeigen.</p>
|
|
279
|
+
</div>
|
|
280
|
+
<script>
|
|
281
|
+
const data = ${graphData};
|
|
282
|
+
|
|
283
|
+
const TYPE_COLORS = {
|
|
284
|
+
host: '#4a9eff', database_server: '#ff6b6b', database: '#ff8c42',
|
|
285
|
+
web_service: '#6bcb77', api_endpoint: '#4d96ff', cache_server: '#ffd93d',
|
|
286
|
+
message_broker: '#c77dff', queue: '#e0aaff', topic: '#9d4edd',
|
|
287
|
+
container: '#48cae4', pod: '#00b4d8', k8s_cluster: '#0077b6',
|
|
288
|
+
config_file: '#adb5bd', saas_tool: '#da8bff', unknown: '#6c757d',
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const NODE_RADIUS = { saas_tool: 10, host: 11, database_server: 11, k8s_cluster: 13, default: 8 };
|
|
292
|
+
const radius = d => NODE_RADIUS[d.type] || NODE_RADIUS.default;
|
|
293
|
+
|
|
294
|
+
const sidebar = document.getElementById('sidebar');
|
|
295
|
+
|
|
296
|
+
function showNode(d) {
|
|
297
|
+
const c = TYPE_COLORS[d.type] || '#aaa';
|
|
298
|
+
const confPct = Math.round(d.confidence * 100);
|
|
299
|
+
const tags = (d.tags || []).map(t => \`<span class="tag">\${t}</span>\`).join('');
|
|
300
|
+
const metaRows = Object.entries(d.metadata || {})
|
|
301
|
+
.filter(([,v]) => v !== null && v !== undefined && String(v).length > 0)
|
|
302
|
+
.map(([k,v]) => \`<tr><td>\${k}</td><td>\${JSON.stringify(v)}</td></tr>\`)
|
|
303
|
+
.join('');
|
|
304
|
+
const related = data.links.filter(l =>
|
|
305
|
+
(l.source.id||l.source) === d.id || (l.target.id||l.target) === d.id
|
|
306
|
+
);
|
|
307
|
+
const edgeItems = related.map(l => {
|
|
308
|
+
const isOut = (l.source.id||l.source) === d.id;
|
|
309
|
+
const other = isOut ? (l.target.id||l.target) : (l.source.id||l.source);
|
|
310
|
+
return \`<div class="edge-item">\${isOut ? '\u2192' : '\u2190'} <span>\${other}</span> <small>[\${l.relationship}]</small></div>\`;
|
|
311
|
+
}).join('');
|
|
312
|
+
|
|
313
|
+
sidebar.innerHTML = \`
|
|
314
|
+
<h2>\${d.name}</h2>
|
|
315
|
+
<table class="meta-table">
|
|
316
|
+
<tr><td>ID</td><td style="font-size:10px;word-break:break-all">\${d.id}</td></tr>
|
|
317
|
+
<tr><td>Typ</td><td><span style="color:\${c}">\${d.type}</span></td></tr>
|
|
318
|
+
<tr><td>Confidence</td><td>
|
|
319
|
+
\${confPct}%
|
|
320
|
+
<div class="conf-bar"><div class="conf-fill" style="width:\${confPct}%;background:\${c}"></div></div>
|
|
321
|
+
</td></tr>
|
|
322
|
+
<tr><td>Entdeckt via</td><td>\${d.discoveredVia || '\u2014'}</td></tr>
|
|
323
|
+
<tr><td>Zeitpunkt</td><td>\${d.discoveredAt ? d.discoveredAt.substring(0,19).replace('T',' ') : '\u2014'}</td></tr>
|
|
324
|
+
\${tags ? '<tr><td>Tags</td><td>'+tags+'</td></tr>' : ''}
|
|
325
|
+
\${metaRows}
|
|
326
|
+
</table>
|
|
327
|
+
\${related.length > 0 ? '<div class="edges-list"><strong>Verbindungen:</strong>'+edgeItems+'</div>' : ''}
|
|
328
|
+
\`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const svgEl = d3.select('svg');
|
|
332
|
+
const graphDiv = document.getElementById('graph');
|
|
333
|
+
const width = () => graphDiv.clientWidth;
|
|
334
|
+
const height = () => graphDiv.clientHeight;
|
|
335
|
+
const g = svgEl.append('g');
|
|
336
|
+
|
|
337
|
+
svgEl.call(d3.zoom().scaleExtent([0.1, 4]).on('zoom', e => g.attr('transform', e.transform)));
|
|
338
|
+
|
|
339
|
+
const sim = d3.forceSimulation(data.nodes)
|
|
340
|
+
.force('link', d3.forceLink(data.links).id(d => d.id).distance(d => d.relationship === 'contains' ? 60 : 120))
|
|
341
|
+
.force('charge', d3.forceManyBody().strength(-320))
|
|
342
|
+
.force('center', d3.forceCenter(width() / 2, height() / 2))
|
|
343
|
+
.force('collision', d3.forceCollide().radius(d => radius(d) + 20));
|
|
344
|
+
|
|
345
|
+
const link = g.append('g')
|
|
346
|
+
.selectAll('line').data(data.links).join('line')
|
|
347
|
+
.attr('class', 'link')
|
|
348
|
+
.attr('stroke', d => d.confidence < 0.6 ? '#444' : '#555')
|
|
349
|
+
.attr('stroke-dasharray', d => d.confidence < 0.6 ? '4 3' : null)
|
|
350
|
+
.attr('stroke-width', d => d.confidence < 0.6 ? 1 : 1.5);
|
|
351
|
+
|
|
352
|
+
link.append('title').text(d => \`\${d.relationship} (conf:\${d.confidence})
|
|
353
|
+
\${d.evidence||''}\`);
|
|
354
|
+
|
|
355
|
+
const node = g.append('g')
|
|
356
|
+
.selectAll('g').data(data.nodes).join('g').attr('class', 'node')
|
|
357
|
+
.call(d3.drag()
|
|
358
|
+
.on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
359
|
+
.on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
|
|
360
|
+
.on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
361
|
+
)
|
|
362
|
+
.on('click', (e, d) => { e.stopPropagation(); showNode(d); });
|
|
363
|
+
|
|
364
|
+
node.append('circle')
|
|
365
|
+
.attr('r', radius)
|
|
366
|
+
.attr('fill', d => TYPE_COLORS[d.type] || '#aaa')
|
|
367
|
+
.attr('stroke', d => d3.color(TYPE_COLORS[d.type] || '#aaa').brighter(1).formatHex())
|
|
368
|
+
.append('title').text(d => \`\${d.id}
|
|
369
|
+
conf:\${d.confidence}\`);
|
|
370
|
+
|
|
371
|
+
node.append('text').attr('dx', d => radius(d) + 4).attr('dy', '.35em').text(d => d.name);
|
|
372
|
+
|
|
373
|
+
sim.on('tick', () => {
|
|
374
|
+
link.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
|
|
375
|
+
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
376
|
+
node.attr('transform', d => \`translate(\${d.x},\${d.y})\`);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
svgEl.on('click', () => {
|
|
380
|
+
sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class="hint">Klicke einen Node um Details anzuzeigen.</p>';
|
|
381
|
+
});
|
|
382
|
+
</script>
|
|
383
|
+
</body>
|
|
384
|
+
</html>`;
|
|
385
|
+
}
|
|
386
|
+
function exportSOPMarkdown(sop) {
|
|
387
|
+
const lines = [
|
|
388
|
+
`# ${sop.title}`,
|
|
389
|
+
"",
|
|
390
|
+
`**Beschreibung:** ${sop.description}`,
|
|
391
|
+
`**Systeme:** ${sop.involvedSystems.join(", ")}`,
|
|
392
|
+
`**Dauer:** ${sop.estimatedDuration}`,
|
|
393
|
+
`**H\xE4ufigkeit:** ${sop.frequency}`,
|
|
394
|
+
`**Confidence:** ${sop.confidence.toFixed(2)}`,
|
|
395
|
+
"",
|
|
396
|
+
"## Schritte",
|
|
397
|
+
""
|
|
398
|
+
];
|
|
399
|
+
for (const step of sop.steps) {
|
|
400
|
+
lines.push(`${step.order}. **${step.tool}**${step.target ? ` \u2192 \`${step.target}\`` : ""}`);
|
|
401
|
+
lines.push(` ${step.instruction}`);
|
|
402
|
+
if (step.notes) lines.push(` _${step.notes}_`);
|
|
403
|
+
lines.push("");
|
|
404
|
+
}
|
|
405
|
+
return lines.join("\n");
|
|
406
|
+
}
|
|
407
|
+
function exportSOPDashboard(sops) {
|
|
408
|
+
const sopsJson = JSON.stringify(sops.map((s) => ({
|
|
409
|
+
id: s.id,
|
|
410
|
+
title: s.title,
|
|
411
|
+
description: s.description,
|
|
412
|
+
steps: s.steps,
|
|
413
|
+
systems: s.involvedSystems,
|
|
414
|
+
duration: s.estimatedDuration,
|
|
415
|
+
frequency: s.frequency,
|
|
416
|
+
confidence: s.confidence,
|
|
417
|
+
generatedAt: s.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
418
|
+
})));
|
|
419
|
+
const systemCount = {};
|
|
420
|
+
for (const sop of sops) {
|
|
421
|
+
for (const sys of sop.involvedSystems) {
|
|
422
|
+
systemCount[sys] = (systemCount[sys] ?? 0) + 1;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const systemsJson = JSON.stringify(
|
|
426
|
+
Object.entries(systemCount).sort((a, b) => b[1] - a[1])
|
|
427
|
+
);
|
|
428
|
+
return `<!DOCTYPE html>
|
|
429
|
+
<html lang="de">
|
|
430
|
+
<head>
|
|
431
|
+
<meta charset="UTF-8">
|
|
432
|
+
<title>Cartography \u2014 SOP Dashboard</title>
|
|
433
|
+
<style>
|
|
434
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
435
|
+
body {
|
|
436
|
+
background: #0d1117; color: #e6edf3;
|
|
437
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
|
|
438
|
+
padding: 0; line-height: 1.6;
|
|
439
|
+
}
|
|
440
|
+
.header {
|
|
441
|
+
background: linear-gradient(135deg, #161b22 0%, #1a1f2e 100%);
|
|
442
|
+
border-bottom: 1px solid #30363d; padding: 32px 40px;
|
|
443
|
+
}
|
|
444
|
+
.header h1 { font-size: 24px; color: #58a6ff; margin-bottom: 8px; }
|
|
445
|
+
.header .subtitle { color: #8b949e; font-size: 14px; }
|
|
446
|
+
.stats-row {
|
|
447
|
+
display: flex; gap: 24px; margin-top: 16px; flex-wrap: wrap;
|
|
448
|
+
}
|
|
449
|
+
.stat-card {
|
|
450
|
+
background: #21262d; border: 1px solid #30363d; border-radius: 8px;
|
|
451
|
+
padding: 12px 20px; min-width: 140px;
|
|
452
|
+
}
|
|
453
|
+
.stat-card .value { font-size: 28px; font-weight: 700; color: #58a6ff; }
|
|
454
|
+
.stat-card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
455
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 24px 40px; }
|
|
456
|
+
.section-title { font-size: 18px; color: #c9d1d9; margin: 32px 0 16px; border-bottom: 1px solid #21262d; padding-bottom: 8px; }
|
|
457
|
+
/* Systems bar chart */
|
|
458
|
+
.systems-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }
|
|
459
|
+
.sys-tag {
|
|
460
|
+
background: #21262d; border: 1px solid #30363d; border-radius: 6px;
|
|
461
|
+
padding: 6px 12px; font-size: 12px; cursor: default;
|
|
462
|
+
}
|
|
463
|
+
.sys-tag .count { color: #58a6ff; font-weight: 600; margin-left: 4px; }
|
|
464
|
+
/* SOP cards */
|
|
465
|
+
.sop-card {
|
|
466
|
+
background: #161b22; border: 1px solid #30363d; border-radius: 8px;
|
|
467
|
+
margin-bottom: 16px; overflow: hidden; transition: border-color 0.2s;
|
|
468
|
+
}
|
|
469
|
+
.sop-card:hover { border-color: #58a6ff; }
|
|
470
|
+
.sop-header {
|
|
471
|
+
padding: 16px 20px; cursor: pointer; display: flex;
|
|
472
|
+
justify-content: space-between; align-items: center;
|
|
473
|
+
}
|
|
474
|
+
.sop-header h3 { font-size: 16px; color: #e6edf3; }
|
|
475
|
+
.sop-meta { display: flex; gap: 16px; align-items: center; font-size: 12px; color: #8b949e; }
|
|
476
|
+
.sop-meta .freq { color: #3fb950; font-weight: 600; }
|
|
477
|
+
.sop-meta .dur { color: #d29922; }
|
|
478
|
+
.sop-meta .conf {
|
|
479
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
480
|
+
}
|
|
481
|
+
.conf-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
482
|
+
.sop-body { display: none; padding: 0 20px 20px; border-top: 1px solid #21262d; }
|
|
483
|
+
.sop-body.open { display: block; padding-top: 16px; }
|
|
484
|
+
.sop-desc { color: #8b949e; font-size: 13px; margin-bottom: 12px; }
|
|
485
|
+
.sop-systems { margin-bottom: 12px; }
|
|
486
|
+
.sop-systems span { background: #0d419d33; color: #58a6ff; border-radius: 4px; padding: 2px 8px; font-size: 11px; margin-right: 4px; }
|
|
487
|
+
.steps-list { list-style: none; counter-reset: step; }
|
|
488
|
+
.steps-list li {
|
|
489
|
+
counter-increment: step; position: relative;
|
|
490
|
+
padding: 10px 12px 10px 44px; border-left: 2px solid #30363d;
|
|
491
|
+
margin-left: 14px; font-size: 13px;
|
|
492
|
+
}
|
|
493
|
+
.steps-list li:last-child { border-left-color: transparent; }
|
|
494
|
+
.steps-list li::before {
|
|
495
|
+
content: counter(step);
|
|
496
|
+
position: absolute; left: -14px; top: 8px;
|
|
497
|
+
width: 26px; height: 26px; border-radius: 50%;
|
|
498
|
+
background: #21262d; border: 2px solid #30363d;
|
|
499
|
+
display: flex; align-items: center; justify-content: center;
|
|
500
|
+
font-size: 12px; font-weight: 600; color: #58a6ff;
|
|
501
|
+
}
|
|
502
|
+
.step-tool { color: #d2a8ff; font-weight: 600; }
|
|
503
|
+
.step-target { color: #7ee787; font-size: 12px; }
|
|
504
|
+
.step-notes { color: #8b949e; font-style: italic; font-size: 12px; margin-top: 2px; }
|
|
505
|
+
.step-instr { color: #c9d1d9; }
|
|
506
|
+
.toggle-icon { color: #8b949e; font-size: 18px; transition: transform 0.2s; }
|
|
507
|
+
.toggle-icon.open { transform: rotate(90deg); }
|
|
508
|
+
.empty { color: #484f58; font-size: 14px; padding: 40px; text-align: center; }
|
|
509
|
+
.gen-time { color: #484f58; font-size: 11px; margin-top: 8px; }
|
|
510
|
+
</style>
|
|
511
|
+
</head>
|
|
512
|
+
<body>
|
|
513
|
+
<div class="header">
|
|
514
|
+
<h1>SOP Dashboard</h1>
|
|
515
|
+
<div class="subtitle">Datasynx Cartography \u2014 Standard Operating Procedures</div>
|
|
516
|
+
<div class="stats-row">
|
|
517
|
+
<div class="stat-card"><div class="value" id="sop-count">0</div><div class="label">SOPs</div></div>
|
|
518
|
+
<div class="stat-card"><div class="value" id="step-count">0</div><div class="label">Total Steps</div></div>
|
|
519
|
+
<div class="stat-card"><div class="value" id="sys-count">0</div><div class="label">Systems</div></div>
|
|
520
|
+
<div class="stat-card"><div class="value" id="avg-conf">\u2014</div><div class="label">Avg Confidence</div></div>
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="container">
|
|
524
|
+
<h2 class="section-title">Beteiligte Systeme</h2>
|
|
525
|
+
<div class="systems-grid" id="systems"></div>
|
|
526
|
+
|
|
527
|
+
<h2 class="section-title">SOPs</h2>
|
|
528
|
+
<div id="sop-list"></div>
|
|
529
|
+
</div>
|
|
530
|
+
<script>
|
|
531
|
+
const sops = ${sopsJson};
|
|
532
|
+
const systems = ${systemsJson};
|
|
533
|
+
|
|
534
|
+
document.getElementById('sop-count').textContent = sops.length;
|
|
535
|
+
document.getElementById('step-count').textContent = sops.reduce((a, s) => a + s.steps.length, 0);
|
|
536
|
+
document.getElementById('sys-count').textContent = systems.length;
|
|
537
|
+
const avgConf = sops.length > 0
|
|
538
|
+
? (sops.reduce((a, s) => a + s.confidence, 0) / sops.length * 100).toFixed(0) + '%'
|
|
539
|
+
: '\u2014';
|
|
540
|
+
document.getElementById('avg-conf').textContent = avgConf;
|
|
541
|
+
|
|
542
|
+
const sysDiv = document.getElementById('systems');
|
|
543
|
+
systems.forEach(([name, count]) => {
|
|
544
|
+
const el = document.createElement('div');
|
|
545
|
+
el.className = 'sys-tag';
|
|
546
|
+
el.innerHTML = name + '<span class="count">x' + count + '</span>';
|
|
547
|
+
sysDiv.appendChild(el);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const listDiv = document.getElementById('sop-list');
|
|
551
|
+
if (sops.length === 0) {
|
|
552
|
+
listDiv.innerHTML = '<div class="empty">Keine SOPs vorhanden. Shadow-Daemon starten und Workflows beobachten.</div>';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
sops.forEach((sop, i) => {
|
|
556
|
+
const confColor = sop.confidence >= 0.8 ? '#3fb950' : sop.confidence >= 0.5 ? '#d29922' : '#f85149';
|
|
557
|
+
const card = document.createElement('div');
|
|
558
|
+
card.className = 'sop-card';
|
|
559
|
+
card.innerHTML = \`
|
|
560
|
+
<div class="sop-header" onclick="toggle(\${i})">
|
|
561
|
+
<h3>\${sop.title}</h3>
|
|
562
|
+
<div class="sop-meta">
|
|
563
|
+
<span class="freq">\${sop.frequency}</span>
|
|
564
|
+
<span class="dur">\${sop.duration}</span>
|
|
565
|
+
<span class="conf"><span class="conf-dot" style="background:\${confColor}"></span>\${Math.round(sop.confidence*100)}%</span>
|
|
566
|
+
<span class="toggle-icon" id="icon-\${i}">\u25B8</span>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
<div class="sop-body" id="body-\${i}">
|
|
570
|
+
<div class="sop-desc">\${sop.description}</div>
|
|
571
|
+
<div class="sop-systems">\${sop.systems.map(s => '<span>'+s+'</span>').join('')}</div>
|
|
572
|
+
<ol class="steps-list">
|
|
573
|
+
\${sop.steps.map(st => \`
|
|
574
|
+
<li>
|
|
575
|
+
<span class="step-tool">\${st.tool}</span>
|
|
576
|
+
\${st.target ? '<span class="step-target"> \u2192 '+st.target+'</span>' : ''}
|
|
577
|
+
<div class="step-instr">\${st.instruction}</div>
|
|
578
|
+
\${st.notes ? '<div class="step-notes">'+st.notes+'</div>' : ''}
|
|
579
|
+
</li>
|
|
580
|
+
\`).join('')}
|
|
581
|
+
</ol>
|
|
582
|
+
<div class="gen-time">Generiert: \${sop.generatedAt ? sop.generatedAt.substring(0,19).replace('T',' ') : '\u2014'}</div>
|
|
583
|
+
</div>
|
|
584
|
+
\`;
|
|
585
|
+
listDiv.appendChild(card);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
function toggle(i) {
|
|
589
|
+
const body = document.getElementById('body-'+i);
|
|
590
|
+
const icon = document.getElementById('icon-'+i);
|
|
591
|
+
body.classList.toggle('open');
|
|
592
|
+
icon.classList.toggle('open');
|
|
593
|
+
}
|
|
594
|
+
</script>
|
|
595
|
+
</body>
|
|
596
|
+
</html>`;
|
|
597
|
+
}
|
|
598
|
+
function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml", "html", "sops"]) {
|
|
599
|
+
mkdirSync(outputDir, { recursive: true });
|
|
600
|
+
mkdirSync(join(outputDir, "sops"), { recursive: true });
|
|
601
|
+
mkdirSync(join(outputDir, "workflows"), { recursive: true });
|
|
602
|
+
const nodes = db.getNodes(sessionId);
|
|
603
|
+
const edges = db.getEdges(sessionId);
|
|
604
|
+
if (formats.includes("mermaid")) {
|
|
605
|
+
writeFileSync(join(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
|
|
606
|
+
writeFileSync(join(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
|
|
607
|
+
process.stderr.write("\u2713 topology.mermaid, dependencies.mermaid\n");
|
|
608
|
+
}
|
|
609
|
+
if (formats.includes("json")) {
|
|
610
|
+
writeFileSync(join(outputDir, "catalog.json"), exportJSON(db, sessionId));
|
|
611
|
+
process.stderr.write("\u2713 catalog.json\n");
|
|
612
|
+
}
|
|
613
|
+
if (formats.includes("yaml")) {
|
|
614
|
+
writeFileSync(join(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
|
|
615
|
+
process.stderr.write("\u2713 catalog-info.yaml\n");
|
|
616
|
+
}
|
|
617
|
+
if (formats.includes("html")) {
|
|
618
|
+
writeFileSync(join(outputDir, "topology.html"), exportHTML(nodes, edges));
|
|
619
|
+
process.stderr.write("\u2713 topology.html\n");
|
|
620
|
+
}
|
|
621
|
+
if (formats.includes("sops")) {
|
|
622
|
+
const sops = db.getSOPs(sessionId);
|
|
623
|
+
for (const sop of sops) {
|
|
624
|
+
const filename = sop.title.toLowerCase().replace(/[^a-z0-9]+/g, "-") + ".md";
|
|
625
|
+
writeFileSync(join(outputDir, "sops", filename), exportSOPMarkdown(sop));
|
|
626
|
+
const wfFilename = `workflow-${sop.workflowId.substring(0, 8)}.mermaid`;
|
|
627
|
+
writeFileSync(join(outputDir, "workflows", wfFilename), generateWorkflowMermaid(sop));
|
|
628
|
+
}
|
|
629
|
+
if (sops.length > 0) {
|
|
630
|
+
process.stderr.write(`\u2713 ${sops.length} SOPs + workflow diagrams
|
|
631
|
+
`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export {
|
|
637
|
+
generateTopologyMermaid,
|
|
638
|
+
generateDependencyMermaid,
|
|
639
|
+
generateWorkflowMermaid,
|
|
640
|
+
exportBackstageYAML,
|
|
641
|
+
exportJSON,
|
|
642
|
+
exportHTML,
|
|
643
|
+
exportSOPMarkdown,
|
|
644
|
+
exportSOPDashboard,
|
|
645
|
+
exportAll
|
|
646
|
+
};
|
|
647
|
+
//# sourceMappingURL=chunk-GUZXO6PM.js.map
|