@aisdlc/core 1.3.0 → 1.3.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.
- package/dist/index.cjs +172 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +172 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -722,6 +722,12 @@ function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
|
722
722
|
"node_modules",
|
|
723
723
|
".git",
|
|
724
724
|
"dist",
|
|
725
|
+
"bin",
|
|
726
|
+
"obj",
|
|
727
|
+
"build",
|
|
728
|
+
"out",
|
|
729
|
+
"target",
|
|
730
|
+
"coverage",
|
|
725
731
|
".changeset",
|
|
726
732
|
"scratch",
|
|
727
733
|
"test-scaffold",
|
|
@@ -733,7 +739,9 @@ function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
|
733
739
|
".claude",
|
|
734
740
|
".cursor",
|
|
735
741
|
".github",
|
|
736
|
-
".vscode"
|
|
742
|
+
".vscode",
|
|
743
|
+
".vs",
|
|
744
|
+
".idea"
|
|
737
745
|
]) {
|
|
738
746
|
try {
|
|
739
747
|
if (!fs6.existsSync(dir)) return fileList;
|
|
@@ -3284,10 +3292,18 @@ var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
|
3284
3292
|
"node_modules",
|
|
3285
3293
|
".git",
|
|
3286
3294
|
"dist",
|
|
3295
|
+
"bin",
|
|
3296
|
+
"obj",
|
|
3297
|
+
"build",
|
|
3298
|
+
"out",
|
|
3299
|
+
"target",
|
|
3300
|
+
"coverage",
|
|
3287
3301
|
"scratch",
|
|
3288
3302
|
"fixtures",
|
|
3289
3303
|
"templates",
|
|
3290
|
-
"examples"
|
|
3304
|
+
"examples",
|
|
3305
|
+
".vs",
|
|
3306
|
+
".idea"
|
|
3291
3307
|
]);
|
|
3292
3308
|
function shouldSkipDir(name) {
|
|
3293
3309
|
return SKIPPED_DIR_NAMES.has(name) || name.startsWith("temp-");
|
|
@@ -3390,7 +3406,12 @@ function verifyTraceability(options = {}) {
|
|
|
3390
3406
|
}
|
|
3391
3407
|
}
|
|
3392
3408
|
if (relFile.includes("architecture/") || relFile.includes("06_runtime_view") || relFile.includes("08_security_concept") || relFile.includes("09_decisions")) {
|
|
3393
|
-
architectureViews.push({
|
|
3409
|
+
architectureViews.push({
|
|
3410
|
+
file: relFile,
|
|
3411
|
+
id: frontmatter.id ? String(frontmatter.id).trim() : void 0,
|
|
3412
|
+
type: typeof frontmatter.type === "string" ? frontmatter.type.trim() : void 0,
|
|
3413
|
+
content
|
|
3414
|
+
});
|
|
3394
3415
|
}
|
|
3395
3416
|
} catch {
|
|
3396
3417
|
}
|
|
@@ -3493,8 +3514,9 @@ function verifyTraceability(options = {}) {
|
|
|
3493
3514
|
}
|
|
3494
3515
|
for (const view of architectureViews) {
|
|
3495
3516
|
if (view.content.includes(reqId)) {
|
|
3496
|
-
const
|
|
3497
|
-
|
|
3517
|
+
const isEntityWithId = view.type === "component" || view.type === "adr" || view.type === "architecture-decision-record" || view.type === "security-enclave" || view.id !== void 0 && (view.id.startsWith("CMP-") || view.id.startsWith("ADR-") || view.id.startsWith("SEC-ENC-"));
|
|
3518
|
+
const viewIdentifier = isEntityWithId && view.id ? view.id : path8.basename(view.file);
|
|
3519
|
+
if (!archTraces.includes(viewIdentifier)) archTraces.push(viewIdentifier);
|
|
3498
3520
|
}
|
|
3499
3521
|
}
|
|
3500
3522
|
const uniqueArchTraces = Array.from(new Set(archTraces));
|
|
@@ -3831,7 +3853,7 @@ function scanAllTestArtifacts(rootDir) {
|
|
|
3831
3853
|
for (const e of entries) {
|
|
3832
3854
|
const fullPath = path8.join(dir, e.name);
|
|
3833
3855
|
if (e.isDirectory()) {
|
|
3834
|
-
if (!["node_modules", ".git", "dist", ".changeset", "scratch", "fixtures", "templates", "examples"].includes(e.name)) {
|
|
3856
|
+
if (!["node_modules", ".git", "dist", "bin", "obj", "build", "out", "target", "coverage", ".changeset", "scratch", "fixtures", "templates", "examples", ".vs", ".idea"].includes(e.name)) {
|
|
3835
3857
|
walk(fullPath);
|
|
3836
3858
|
}
|
|
3837
3859
|
} else {
|
|
@@ -7136,7 +7158,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7136
7158
|
for (const n of pdac.drifts) {
|
|
7137
7159
|
driftTargets.add(n.targetId);
|
|
7138
7160
|
}
|
|
7161
|
+
const NON_GRAPH_TYPES = /* @__PURE__ */ new Set([
|
|
7162
|
+
"term",
|
|
7163
|
+
"handoff",
|
|
7164
|
+
"agent-handoff",
|
|
7165
|
+
"tasks",
|
|
7166
|
+
"task-plan",
|
|
7167
|
+
"spec-change-proposal",
|
|
7168
|
+
"proposal",
|
|
7169
|
+
"spec-design",
|
|
7170
|
+
"spec-change-design",
|
|
7171
|
+
"design",
|
|
7172
|
+
"delivery-spec",
|
|
7173
|
+
"spec",
|
|
7174
|
+
"pre-merge-audit-report",
|
|
7175
|
+
"user-design-feedback",
|
|
7176
|
+
"release-kpis",
|
|
7177
|
+
"session-telemetry",
|
|
7178
|
+
"license-manifest",
|
|
7179
|
+
"mcp-config",
|
|
7180
|
+
"user-manual",
|
|
7181
|
+
"production-manual",
|
|
7182
|
+
"architecture-glossary"
|
|
7183
|
+
]);
|
|
7139
7184
|
const scannedArtifactsMap = /* @__PURE__ */ new Map();
|
|
7185
|
+
const archViewByBasename = /* @__PURE__ */ new Map();
|
|
7140
7186
|
const artifactDirs = ["product", "architecture", "specs", "security"];
|
|
7141
7187
|
for (const dirName of artifactDirs) {
|
|
7142
7188
|
const fullDirPath = path8.join(rootDir, dirName);
|
|
@@ -7146,6 +7192,7 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7146
7192
|
try {
|
|
7147
7193
|
const fileContent = fs6.readFileSync(f, "utf-8");
|
|
7148
7194
|
const relPath = path8.relative(rootDir, f).replace(/\\/g, "/");
|
|
7195
|
+
if (relPath.includes("/changes/") || relPath.startsWith("changes/")) continue;
|
|
7149
7196
|
const match = fileContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
7150
7197
|
if (!match) continue;
|
|
7151
7198
|
let fm = {};
|
|
@@ -7157,6 +7204,9 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7157
7204
|
const id = typeof fm.id === "string" ? fm.id.trim() : void 0;
|
|
7158
7205
|
if (!id) continue;
|
|
7159
7206
|
let type = typeof fm.type === "string" ? fm.type.trim() : "";
|
|
7207
|
+
if (NON_GRAPH_TYPES.has(type) || id.startsWith("TERM-") || id.startsWith("HOF-") || id.startsWith("AHOF-") || id.startsWith("CHG-") || id.startsWith("DSG-") || id.startsWith("SPEC-") || id.startsWith("TSK-") || id.startsWith("UDF-") || id.startsWith("AUDIT-")) {
|
|
7208
|
+
continue;
|
|
7209
|
+
}
|
|
7160
7210
|
let layer = "product";
|
|
7161
7211
|
if (id.startsWith("JRN-") || type === "journey") {
|
|
7162
7212
|
type = "journey";
|
|
@@ -7191,16 +7241,19 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7191
7241
|
} else if (id.startsWith("SEC-REQ-") || type === "security-requirement") {
|
|
7192
7242
|
type = "security-requirement";
|
|
7193
7243
|
layer = "requirement";
|
|
7194
|
-
} else if (id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || type === "requirement") {
|
|
7244
|
+
} else if (id.startsWith("FR-") || id.startsWith("QR-") || id.startsWith("CON-") || type === "requirement" || type === "functional-requirement" || type === "quality-requirement") {
|
|
7195
7245
|
type = "requirement";
|
|
7196
7246
|
layer = "requirement";
|
|
7247
|
+
} else if (id.startsWith("ARCH-") || relPath.includes("architecture/")) {
|
|
7248
|
+
type = "architecture";
|
|
7249
|
+
layer = "architecture";
|
|
7197
7250
|
} else {
|
|
7198
7251
|
type = type || "product";
|
|
7199
7252
|
layer = "product";
|
|
7200
7253
|
}
|
|
7201
7254
|
const title = typeof fm.title === "string" ? fm.title.trim() : id;
|
|
7202
7255
|
const content = fileContent.replace(/^---\r?\n[\s\S]*?\r?\n---/, "").trim();
|
|
7203
|
-
|
|
7256
|
+
const scannedArt = {
|
|
7204
7257
|
id,
|
|
7205
7258
|
type,
|
|
7206
7259
|
title,
|
|
@@ -7208,7 +7261,12 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7208
7261
|
filePath: relPath,
|
|
7209
7262
|
content,
|
|
7210
7263
|
frontmatter: fm
|
|
7211
|
-
}
|
|
7264
|
+
};
|
|
7265
|
+
if (layer === "architecture" && type === "architecture") {
|
|
7266
|
+
archViewByBasename.set(path8.basename(relPath), scannedArt);
|
|
7267
|
+
} else {
|
|
7268
|
+
scannedArtifactsMap.set(id, scannedArt);
|
|
7269
|
+
}
|
|
7212
7270
|
} catch {
|
|
7213
7271
|
}
|
|
7214
7272
|
}
|
|
@@ -7217,7 +7275,7 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7217
7275
|
for (const r of trace.rows) {
|
|
7218
7276
|
const isOk = (r.productStatus === "COMPLIANT" || r.productStatus === "CONFORME") && (r.archStatus === "COMPLIANT" || r.archStatus === "CONFORME") && (r.testStatus === "COMPLIANT" || r.testStatus === "CONFORME");
|
|
7219
7277
|
const status = isOk ? "COMPLIANT" : "ORPHAN";
|
|
7220
|
-
const reqType = r.id.startsWith("SEC-REQ-") ? "security-requirement" :
|
|
7278
|
+
const reqType = r.id.startsWith("SEC-REQ-") ? "security-requirement" : "requirement";
|
|
7221
7279
|
nodeMap.set(r.id, {
|
|
7222
7280
|
id: r.id,
|
|
7223
7281
|
label: r.id,
|
|
@@ -7277,13 +7335,16 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7277
7335
|
});
|
|
7278
7336
|
if (!nodeMap.has(a)) {
|
|
7279
7337
|
const aType = a.startsWith("CMP-") ? "component" : a.startsWith("ADR-") ? "adr" : a.startsWith("SEC-ENC-") ? "security-enclave" : "architecture";
|
|
7338
|
+
const viewArt = archViewByBasename.get(a);
|
|
7280
7339
|
nodeMap.set(a, {
|
|
7281
7340
|
id: a,
|
|
7282
7341
|
label: a,
|
|
7283
|
-
title: a,
|
|
7342
|
+
title: viewArt?.title || a,
|
|
7284
7343
|
type: aType,
|
|
7285
7344
|
layer: "architecture",
|
|
7286
|
-
status: "COMPLIANT"
|
|
7345
|
+
status: "COMPLIANT",
|
|
7346
|
+
filePath: viewArt?.filePath,
|
|
7347
|
+
content: viewArt?.content
|
|
7287
7348
|
});
|
|
7288
7349
|
}
|
|
7289
7350
|
}
|
|
@@ -7415,7 +7476,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7415
7476
|
status: "COMPLIANT"
|
|
7416
7477
|
});
|
|
7417
7478
|
}
|
|
7418
|
-
const
|
|
7479
|
+
const supportingActors = Array.isArray(fm["supporting-actors"]) ? fm["supporting-actors"] : Array.isArray(fm["secondary-actors"]) ? fm["secondary-actors"] : [];
|
|
7480
|
+
for (const actorId of supportingActors) {
|
|
7481
|
+
if (typeof actorId === "string" && actorId.trim()) {
|
|
7482
|
+
const a = actorId.trim();
|
|
7483
|
+
if (!nodeMap.has(a)) {
|
|
7484
|
+
nodeMap.set(a, {
|
|
7485
|
+
id: a,
|
|
7486
|
+
label: a,
|
|
7487
|
+
title: a,
|
|
7488
|
+
type: "actor",
|
|
7489
|
+
layer: "product",
|
|
7490
|
+
status: "COMPLIANT"
|
|
7491
|
+
});
|
|
7492
|
+
}
|
|
7493
|
+
addEdge({
|
|
7494
|
+
id: `edge-${a}-${art.id}`,
|
|
7495
|
+
source: a,
|
|
7496
|
+
target: art.id,
|
|
7497
|
+
label: "initiates",
|
|
7498
|
+
relation: "initiates",
|
|
7499
|
+
status: "COMPLIANT"
|
|
7500
|
+
});
|
|
7501
|
+
}
|
|
7502
|
+
}
|
|
7503
|
+
const governedBy = Array.isArray(fm["governed-by"]) ? fm["governed-by"] : Array.isArray(fm["business-rules"]) ? fm["business-rules"] : [];
|
|
7419
7504
|
for (const ruleId of governedBy) {
|
|
7420
7505
|
if (typeof ruleId === "string" && ruleId.trim()) {
|
|
7421
7506
|
const r = ruleId.trim();
|
|
@@ -7476,7 +7561,31 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7476
7561
|
status: "COMPLIANT"
|
|
7477
7562
|
});
|
|
7478
7563
|
}
|
|
7479
|
-
const
|
|
7564
|
+
const secondaryThreatActors = Array.isArray(fm["secondary-actors"]) ? fm["secondary-actors"] : Array.isArray(fm["supporting-actors"]) ? fm["supporting-actors"] : [];
|
|
7565
|
+
for (const secActor of secondaryThreatActors) {
|
|
7566
|
+
if (typeof secActor === "string" && secActor.trim()) {
|
|
7567
|
+
const sa = secActor.trim();
|
|
7568
|
+
if (!nodeMap.has(sa)) {
|
|
7569
|
+
nodeMap.set(sa, {
|
|
7570
|
+
id: sa,
|
|
7571
|
+
label: sa,
|
|
7572
|
+
title: sa,
|
|
7573
|
+
type: sa.startsWith("ACT-THREAT-") ? "threat-actor" : "actor",
|
|
7574
|
+
layer: "product",
|
|
7575
|
+
status: "COMPLIANT"
|
|
7576
|
+
});
|
|
7577
|
+
}
|
|
7578
|
+
addEdge({
|
|
7579
|
+
id: `edge-${sa}-${art.id}`,
|
|
7580
|
+
source: sa,
|
|
7581
|
+
target: art.id,
|
|
7582
|
+
label: "threatens",
|
|
7583
|
+
relation: "threatens",
|
|
7584
|
+
status: "COMPLIANT"
|
|
7585
|
+
});
|
|
7586
|
+
}
|
|
7587
|
+
}
|
|
7588
|
+
const rawTargets = Array.isArray(fm["targets-use-case"]) ? fm["targets-use-case"] : typeof fm["targets-use-case"] === "string" ? [fm["targets-use-case"]] : Array.isArray(fm["targets-use-cases"]) ? fm["targets-use-cases"] : Array.isArray(fm["targets"]) ? fm["targets"] : [];
|
|
7480
7589
|
for (const tgt of rawTargets) {
|
|
7481
7590
|
if (typeof tgt === "string" && tgt.trim()) {
|
|
7482
7591
|
const t = tgt.trim();
|
|
@@ -7541,6 +7650,40 @@ function buildGraphElements(rootDir = process.cwd()) {
|
|
|
7541
7650
|
});
|
|
7542
7651
|
}
|
|
7543
7652
|
}
|
|
7653
|
+
} else if (art.type === "adr") {
|
|
7654
|
+
const affectedComponents = Array.isArray(fm["affects-components"]) ? fm["affects-components"] : typeof fm["affects-components"] === "string" ? [fm["affects-components"]] : [];
|
|
7655
|
+
for (const cmpId of affectedComponents) {
|
|
7656
|
+
if (typeof cmpId === "string" && cmpId.trim()) {
|
|
7657
|
+
const c = cmpId.trim();
|
|
7658
|
+
if (nodeMap.has(c)) {
|
|
7659
|
+
addEdge({
|
|
7660
|
+
id: `edge-${art.id}-${c}`,
|
|
7661
|
+
source: art.id,
|
|
7662
|
+
target: c,
|
|
7663
|
+
label: "governs",
|
|
7664
|
+
relation: "governs",
|
|
7665
|
+
status: "COMPLIANT"
|
|
7666
|
+
});
|
|
7667
|
+
}
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
} else if (art.type === "security-enclave") {
|
|
7671
|
+
const enforcedBy = Array.isArray(fm["enforced-by"]) ? fm["enforced-by"] : typeof fm["enforced-by"] === "string" ? [fm["enforced-by"]] : [];
|
|
7672
|
+
for (const secReq of enforcedBy) {
|
|
7673
|
+
if (typeof secReq === "string" && secReq.trim()) {
|
|
7674
|
+
const s = secReq.trim();
|
|
7675
|
+
if (nodeMap.has(s)) {
|
|
7676
|
+
addEdge({
|
|
7677
|
+
id: `edge-${s}-${art.id}`,
|
|
7678
|
+
source: s,
|
|
7679
|
+
target: art.id,
|
|
7680
|
+
label: "satisfies",
|
|
7681
|
+
relation: "satisfies",
|
|
7682
|
+
status: "COMPLIANT"
|
|
7683
|
+
});
|
|
7684
|
+
}
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7544
7687
|
}
|
|
7545
7688
|
}
|
|
7546
7689
|
for (const [id, node] of nodeMap.entries()) {
|
|
@@ -7690,6 +7833,9 @@ function collectDashboardMetrics(rootDir) {
|
|
|
7690
7833
|
function escapeHtml(str) {
|
|
7691
7834
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7692
7835
|
}
|
|
7836
|
+
function safeJsonForScript(data) {
|
|
7837
|
+
return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
7838
|
+
}
|
|
7693
7839
|
function renderAutonomyModeSection(mode, statusIcon, count, roleDescription, allTasks = []) {
|
|
7694
7840
|
const modeTasks = (allTasks || []).filter(
|
|
7695
7841
|
(t) => (t.task?.autonomyMode || "").toUpperCase() === mode
|
|
@@ -7801,14 +7947,14 @@ function renderAutonomyModeSection(mode, statusIcon, count, roleDescription, all
|
|
|
7801
7947
|
}
|
|
7802
7948
|
function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard Interactivo de Trazabilidad y Calidad") {
|
|
7803
7949
|
const cytoscapeScript = getCytoscapeScript();
|
|
7804
|
-
const elementsJson =
|
|
7950
|
+
const elementsJson = safeJsonForScript(graphData.elements);
|
|
7805
7951
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7806
7952
|
return `<!DOCTYPE html>
|
|
7807
7953
|
<html lang="es">
|
|
7808
7954
|
<head>
|
|
7809
7955
|
<meta charset="UTF-8">
|
|
7810
7956
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7811
|
-
<title>${title}</title>
|
|
7957
|
+
<title>${escapeHtml(title)}</title>
|
|
7812
7958
|
<style>
|
|
7813
7959
|
:root {
|
|
7814
7960
|
--bg-dark: #0f172a;
|
|
@@ -8183,12 +8329,12 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
8183
8329
|
${metricsData.trace.rows.map((r) => {
|
|
8184
8330
|
const isRowOk = (r.productStatus === "COMPLIANT" || r.productStatus === "CONFORME") && (r.archStatus === "COMPLIANT" || r.archStatus === "CONFORME") && (r.testStatus === "COMPLIANT" || r.testStatus === "CONFORME");
|
|
8185
8331
|
return `
|
|
8186
|
-
<tr onclick="focusNode('${r.id}')" onmouseenter="startRowTooltip(event, '${r.id}')" onmouseleave="cancelRowTooltip()" onmousemove="updateRowTooltipPos(event)" style="cursor: pointer;">
|
|
8187
|
-
<td><strong><code>${r.id}</code></strong></td>
|
|
8188
|
-
<td>${r.title}</td>
|
|
8189
|
-
<td><code>${r.productTraces}</code></td>
|
|
8190
|
-
<td><code>${r.archTraces}</code></td>
|
|
8191
|
-
<td><code>${r.testTraces}</code></td>
|
|
8332
|
+
<tr onclick="focusNode('${escapeHtml(String(r.id || ""))}')" onmouseenter="startRowTooltip(event, '${escapeHtml(String(r.id || ""))}')" onmouseleave="cancelRowTooltip()" onmousemove="updateRowTooltipPos(event)" style="cursor: pointer;">
|
|
8333
|
+
<td><strong><code>${escapeHtml(String(r.id || ""))}</code></strong></td>
|
|
8334
|
+
<td>${escapeHtml(String(r.title || ""))}</td>
|
|
8335
|
+
<td><code>${escapeHtml(String(r.productTraces || ""))}</code></td>
|
|
8336
|
+
<td><code>${escapeHtml(String(r.archTraces || ""))}</code></td>
|
|
8337
|
+
<td><code>${escapeHtml(String(r.testTraces || ""))}</code></td>
|
|
8192
8338
|
<td><span class="badge ${isRowOk ? "badge-green" : "badge-amber"}">${isRowOk ? "COMPLIANT" : "ORPHAN"}</span></td>
|
|
8193
8339
|
</tr>
|
|
8194
8340
|
`;
|
|
@@ -8401,7 +8547,7 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
8401
8547
|
</script>
|
|
8402
8548
|
<script>
|
|
8403
8549
|
var elements = ${elementsJson};
|
|
8404
|
-
var govTasks = ${
|
|
8550
|
+
var govTasks = ${safeJsonForScript(metricsData.gov.tasks || [])};
|
|
8405
8551
|
var cy = null;
|
|
8406
8552
|
|
|
8407
8553
|
var reqHoverTimer = null;
|
|
@@ -9034,9 +9180,10 @@ function renderDashboardHtml(graphData, metricsData, title = "AI-SDLC: Dashboard
|
|
|
9034
9180
|
|
|
9035
9181
|
function switchTab(name) {
|
|
9036
9182
|
hideTooltip();
|
|
9037
|
-
document.querySelectorAll('.tab-btn').forEach(function(b) {
|
|
9183
|
+
document.querySelectorAll('.tab-btn').forEach(function(b) {
|
|
9184
|
+
b.classList.toggle('active', b.getAttribute('onclick') === "switchTab('" + name + "')");
|
|
9185
|
+
});
|
|
9038
9186
|
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
|
|
9039
|
-
event.target?.classList?.add('active');
|
|
9040
9187
|
var p = document.getElementById('tab-' + name);
|
|
9041
9188
|
if (p) p.classList.add('active');
|
|
9042
9189
|
if (name === 'graph' && cy) {
|