@dsh-fly/dsh-plugin-fly-engagement 0.1.1 → 0.1.3
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/lib/client.js +400 -0
- package/package.json +3 -2
package/lib/client.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/* @dsh-fly/dsh-plugin-fly-engagement — browser half, built by scripts/build-plugin-client.mjs.
|
|
2
|
+
* src-hash: dfc489bb58d29d86b67f417b96d83c86f598b665a6426ab841f69745bd106120
|
|
3
|
+
* Factory-form CJS: the loader calls the factory with its platform `require`
|
|
4
|
+
* and memoizes the returned exports. Keep the banner so the loader id and the
|
|
5
|
+
* package that produced the bundle stay greppable in a live page. */
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: "@dsh-fly/dsh-plugin-fly-engagement",
|
|
8
|
+
factory: (require) => {
|
|
9
|
+
var module = { exports: {} };
|
|
10
|
+
var exports = module.exports;
|
|
11
|
+
"use strict";
|
|
12
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
13
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
14
|
+
const react = require("react");
|
|
15
|
+
const name = "fly-engagement-panel";
|
|
16
|
+
const inject = ["slots", "sidebarRightTabs"];
|
|
17
|
+
const PACKAGE = "@dsh-fly/dsh-plugin-fly-engagement";
|
|
18
|
+
const KIND = "fly.engagement";
|
|
19
|
+
const TAB_TITLE = "Engagement";
|
|
20
|
+
const TAB_DESCRIPTION = "目标 / 资产 / 漏洞 / 证据图";
|
|
21
|
+
const GUIDE_ORDER = 12;
|
|
22
|
+
const BASE = "/fly/engagement";
|
|
23
|
+
const STORE_KEY = "fly.engagement.workspace";
|
|
24
|
+
const PANEL_CSS = `/*
|
|
25
|
+
* fly-engagement 抽屉面板 —— **目标效果图的样式表**(2026-10-18,等用户判方向)
|
|
26
|
+
*
|
|
27
|
+
* 这张表就是实现时的对照物:类名 / token / 尺寸都按真面板的约定写('.fly-eg-*' 前缀,
|
|
28
|
+
* 与 shell 的 '.fly-*' 一族同形),所以"照图实现"不会漂移成另一套。
|
|
29
|
+
*
|
|
30
|
+
* token 纪律:这里因为要**独立渲染**(没有宿主 skin),把深色底值写进 ':root' 兜底;
|
|
31
|
+
* 实现时一律 'var(--dsw-alias-*, 兜底)' —— 真宿主会给出 token,兜底只在 mock/降级时生效。
|
|
32
|
+
* 面板框(rail 8px / desk 12px / 3px 亮缘)照 '.fly-panel' 的结构,不再发明第二套。
|
|
33
|
+
*/
|
|
34
|
+
:root {
|
|
35
|
+
--dsw-alias-bg-base: #070b12;
|
|
36
|
+
--dsw-alias-bg-layer-1: rgba(255, 255, 255, 0.045);
|
|
37
|
+
--dsw-alias-bg-layer-2: rgba(255, 255, 255, 0.07);
|
|
38
|
+
--dsw-alias-fg-primary: #e8eef8;
|
|
39
|
+
--dsw-alias-fg-secondary: #9fb0c6;
|
|
40
|
+
--dsw-alias-fg-muted: #6b7d95;
|
|
41
|
+
--dsw-alias-border-l1: rgba(255, 255, 255, 0.10);
|
|
42
|
+
--fly-accent: #f0a55a;
|
|
43
|
+
--fly-accent-dim: rgba(240, 165, 90, 0.16);
|
|
44
|
+
--fly-critical: #ff5f6d;
|
|
45
|
+
--fly-high: #ff9b52;
|
|
46
|
+
--fly-medium: #e8c54a;
|
|
47
|
+
--fly-low: #6fc2ff;
|
|
48
|
+
--fly-info: #8fa3bd;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
* { box-sizing: border-box; }
|
|
52
|
+
body {
|
|
53
|
+
margin: 0;
|
|
54
|
+
padding: var(--fly-desk, 12px);
|
|
55
|
+
background: #05080d;
|
|
56
|
+
font: 13px/1.5 -apple-system, "SF Pro Text", "PingFang SC", system-ui, sans-serif;
|
|
57
|
+
color: var(--dsw-alias-fg-primary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ── 面板框:照 .fly-panel 的结构(rail 8px / 3px 亮缘 / 底部关系型亮缘) ── */
|
|
61
|
+
.fly-eg-panel {
|
|
62
|
+
--fly-rail: 8px;
|
|
63
|
+
width: 760px;
|
|
64
|
+
border-radius: 12px;
|
|
65
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
66
|
+
background:
|
|
67
|
+
radial-gradient(900px 320px at 12% -12%, rgba(240, 165, 90, 0.10), transparent 70%),
|
|
68
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0) 42%),
|
|
69
|
+
var(--dsw-alias-bg-base);
|
|
70
|
+
padding: var(--fly-rail) calc(var(--fly-rail) + 6px) calc(var(--fly-rail) + 8px);
|
|
71
|
+
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ── 头部:工作区 + goal + authorization ── */
|
|
75
|
+
.fly-eg-head { display: flex; flex-direction: column; gap: 6px; padding-bottom: 10px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
76
|
+
.fly-eg-title { display: flex; align-items: baseline; gap: 8px; }
|
|
77
|
+
.fly-eg-title h1 { margin: 0; font-size: 15px; font-weight: 600; letter-spacing: 0.2px; }
|
|
78
|
+
.fly-eg-badge { font-size: 11px; padding: 1px 7px; border-radius: 999px; background: var(--fly-accent-dim); color: var(--fly-accent); border: 1px solid rgba(240, 165, 90, 0.35); }
|
|
79
|
+
.fly-eg-meta { font-size: 11.5px; color: var(--dsw-alias-fg-secondary); display: flex; flex-wrap: wrap; gap: 10px; }
|
|
80
|
+
.fly-eg-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--dsw-alias-fg-primary); }
|
|
81
|
+
.fly-eg-auth { font-size: 11.5px; color: var(--dsw-alias-fg-muted); }
|
|
82
|
+
.fly-eg-auth b { color: var(--fly-accent); font-weight: 600; }
|
|
83
|
+
|
|
84
|
+
/* ── 计数读数:counts + dropped/refused/truncated(截断显眼) ── */
|
|
85
|
+
.fly-eg-counters { display: flex; gap: 4px; margin: 8px 0; flex-wrap: wrap; align-items: center; }
|
|
86
|
+
.fly-eg-counter { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); border-radius: 6px; padding: 2px 6px; font-size: 10.5px; line-height: 1.5; color: var(--dsw-alias-fg-muted); }
|
|
87
|
+
.fly-eg-counter b { margin-left: 3px; font-size: 11px; }
|
|
88
|
+
.fly-eg-counter b { color: var(--dsw-alias-fg-primary); font-weight: 600; margin-left: 5px; }
|
|
89
|
+
.fly-eg-counter.is-warn { border-color: rgba(255, 95, 109, 0.45); background: rgba(255, 95, 109, 0.10); color: #ffb8be; }
|
|
90
|
+
.fly-eg-counter.is-warn b { color: #ffd7da; }
|
|
91
|
+
|
|
92
|
+
/* ── 表/图 切换(借 dsh-pentest AssetsView 的两模式) ── */
|
|
93
|
+
.fly-eg-tabs { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); margin-bottom: 10px; }
|
|
94
|
+
.fly-eg-tab { font-size: 11.5px; padding: 3px 10px; border-radius: 6px; color: var(--dsw-alias-fg-secondary); }
|
|
95
|
+
.fly-eg-tab.is-active { background: rgba(240, 165, 90, 0.18); color: var(--fly-accent); }
|
|
96
|
+
|
|
97
|
+
/* ── 分区标题 ── */
|
|
98
|
+
.fly-eg-section { margin-top: 12px; }
|
|
99
|
+
.fly-eg-section > h2 { margin: 0 0 6px; font-size: 12px; font-weight: 600; color: var(--dsw-alias-fg-secondary); letter-spacing: 0.4px; text-transform: uppercase; }
|
|
100
|
+
|
|
101
|
+
/* ── 表 ── */
|
|
102
|
+
.fly-eg-table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
103
|
+
.fly-eg-table th { text-align: left; font-weight: 500; color: var(--dsw-alias-fg-muted); font-size: 11px; padding: 4px 6px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
104
|
+
.fly-eg-table td { padding: 5px 6px; border-bottom: 1px solid rgba(255, 255, 255, 0.05); vertical-align: top; }
|
|
105
|
+
.fly-eg-id { font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: var(--dsw-alias-fg-muted); }
|
|
106
|
+
.fly-eg-src { font-size: 11px; color: var(--dsw-alias-fg-muted); }
|
|
107
|
+
.fly-eg-src b { color: var(--dsw-alias-fg-secondary); font-weight: 500; }
|
|
108
|
+
.fly-eg-sev { font-size: 11px; font-weight: 600; padding: 1px 7px; border-radius: 999px; display: inline-block; }
|
|
109
|
+
.fly-eg-sev.critical { color: #fff; background: var(--fly-critical); }
|
|
110
|
+
.fly-eg-sev.high { color: #1a0f06; background: var(--fly-high); }
|
|
111
|
+
.fly-eg-sev.medium { color: #1a1506; background: var(--fly-medium); }
|
|
112
|
+
.fly-eg-sev.low { color: #061019; background: var(--fly-low); }
|
|
113
|
+
.fly-eg-sev.info { color: #0b1017; background: var(--fly-info); }
|
|
114
|
+
.fly-eg-steps { margin: 4px 0 0 0; padding-left: 18px; color: var(--dsw-alias-fg-secondary); font-size: 11.5px; }
|
|
115
|
+
.fly-eg-steps code, .fly-eg-cmd { font-family: ui-monospace, Menlo, monospace; font-size: 11px; background: rgba(255, 255, 255, 0.06); padding: 0 4px; border-radius: 4px; }
|
|
116
|
+
|
|
117
|
+
/* ── 图区:静态分层 + 边胶囊 + 只读(借 xyflow 的形状,不引入拖拽) ── */
|
|
118
|
+
.fly-eg-graph { position: relative; height: 300px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; background: radial-gradient(600px 240px at 50% 0%, rgba(255,255,255,0.035), transparent 70%), var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
119
|
+
.fly-eg-graph-hint { position: absolute; left: 8px; bottom: 6px; font-size: 10.5px; color: var(--dsw-alias-fg-muted); }
|
|
120
|
+
.fly-eg-graph-badge { position: absolute; right: 8px; top: 8px; font-size: 10.5px; padding: 2px 7px; border-radius: 999px; border: 1px solid rgba(255, 95, 109, 0.45); background: rgba(255, 95, 109, 0.12); color: #ffb8be; }
|
|
121
|
+
.fly-eg-node { position: absolute; transform: translate(-50%, -50%); border-radius: 8px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); padding: 5px 9px; font-size: 11.5px; white-space: nowrap; }
|
|
122
|
+
.fly-eg-node.is-finding { border-color: rgba(255, 155, 82, 0.55); background: rgba(255, 155, 82, 0.14); color: #ffd7b0; }
|
|
123
|
+
.fly-eg-node.is-asset { border-color: rgba(111, 194, 255, 0.45); background: rgba(111, 194, 255, 0.12); color: #cbe8ff; }
|
|
124
|
+
.fly-eg-edge { position: absolute; height: 1.5px; background: rgba(240, 165, 90, 0.55); transform-origin: 0 50%; }
|
|
125
|
+
.fly-eg-edge-cap { position: absolute; font-size: 10px; color: var(--fly-accent); background: rgba(7, 11, 18, 0.85); padding: 0 4px; border-radius: 4px; border: 1px solid rgba(240, 165, 90, 0.3); }
|
|
126
|
+
|
|
127
|
+
/* ── 报告区 ── */
|
|
128
|
+
.fly-eg-report { border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--dsw-alias-bg-layer-1); padding: 8px 10px; font-size: 11.5px; color: var(--dsw-alias-fg-secondary); }
|
|
129
|
+
.fly-eg-report-actions { display: flex; gap: 6px; margin-top: 8px; }
|
|
130
|
+
.fly-eg-btn { font-size: 11.5px; padding: 3px 10px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-fg-primary); }
|
|
131
|
+
|
|
132
|
+
/* 图区的线走 SVG(按节点实测中心算),不用手摆坐标 —— 实现时同样按数据算 */
|
|
133
|
+
.fly-eg-wires { position: absolute; inset: 0; pointer-events: none; }
|
|
134
|
+
|
|
135
|
+
/* 连线走**折线**(elbow):横向只占层间那一段,纵向在间隙里走 —— 比直线省横向空间。
|
|
136
|
+
实现时同样用 SVG path(M/H/V 折角 + stroke-linejoin:round)。 */
|
|
137
|
+
.fly-eg-wire { fill: none; stroke: rgba(240, 165, 90, 0.55); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
|
|
138
|
+
|
|
139
|
+
/* ── 事实按来源工具折叠(<details>,缺省收起:长会话省地方) ── */
|
|
140
|
+
.fly-eg-group { border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); margin-bottom: 4px; }
|
|
141
|
+
.fly-eg-group > summary { cursor: default; list-style: none; padding: 4px 8px; font-size: 11.5px; color: var(--dsw-alias-fg-secondary); display: flex; align-items: center; gap: 6px; }
|
|
142
|
+
.fly-eg-group > summary::-webkit-details-marker { display: none; }
|
|
143
|
+
.fly-eg-group > summary::before { content: '▸'; color: var(--dsw-alias-fg-muted); font-size: 10px; }
|
|
144
|
+
.fly-eg-group[open] > summary::before { content: '▾'; }
|
|
145
|
+
.fly-eg-group > summary b { color: var(--dsw-alias-fg-primary); font-weight: 600; }
|
|
146
|
+
.fly-eg-group > summary .fly-eg-count { margin-left: auto; font-size: 10.5px; color: var(--dsw-alias-fg-muted); }
|
|
147
|
+
.fly-eg-group table { margin: 0 0 2px; }
|
|
148
|
+
|
|
149
|
+
.fly-eg-input { width: 100%; font: inherit; font-size: 11.5px; padding: 3px 8px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-fg-primary); }
|
|
150
|
+
.fly-eg-error { margin: 8px 0; padding: 6px 9px; border-radius: 8px; border: 1px solid rgba(255,95,109,0.45); background: rgba(255,95,109,0.10); color: #ffb8be; font-size: 11.5px; }
|
|
151
|
+
.fly-eg-empty { margin: 14px 0; color: var(--dsw-alias-fg-muted); font-size: 11.5px; }
|
|
152
|
+
`;
|
|
153
|
+
async function getJson(path) {
|
|
154
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
155
|
+
if (!response.ok) throw new Error(path + " → HTTP " + response.status);
|
|
156
|
+
return await response.json();
|
|
157
|
+
}
|
|
158
|
+
function Graph({ nodes, edges, kind }) {
|
|
159
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
160
|
+
for (const node of nodes) {
|
|
161
|
+
const list = byLayer.get(node.layer) ?? [];
|
|
162
|
+
list.push(node);
|
|
163
|
+
byLayer.set(node.layer, list);
|
|
164
|
+
}
|
|
165
|
+
const layers = [...byLayer.keys()].sort((a, b) => a - b);
|
|
166
|
+
const widest = Math.max(120, ...nodes.map((node) => node.label.length * 6.6 + 20));
|
|
167
|
+
const gap = 78;
|
|
168
|
+
const xOf = /* @__PURE__ */ new Map();
|
|
169
|
+
let cursor = 60;
|
|
170
|
+
for (const layer of layers) {
|
|
171
|
+
for (const node of byLayer.get(layer) ?? []) xOf.set(node.id, cursor + widest / 2);
|
|
172
|
+
cursor += widest + gap;
|
|
173
|
+
}
|
|
174
|
+
const height = Math.max(180, ...layers.map((layer) => (byLayer.get(layer) ?? []).length * 54));
|
|
175
|
+
const yOf = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const layer of layers) {
|
|
177
|
+
const list = byLayer.get(layer) ?? [];
|
|
178
|
+
list.forEach((node, index) => yOf.set(node.id, height / (list.length + 1) * (index + 1)));
|
|
179
|
+
}
|
|
180
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-graph", style: { height: height + 60 }, children: [
|
|
181
|
+
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "fly-eg-wires", width: "100%", height: "100%", children: edges.filter((edge) => xOf.has(edge.from) && xOf.has(edge.to)).map((edge) => {
|
|
182
|
+
const x1 = (xOf.get(edge.from) ?? 0) + widest / 2;
|
|
183
|
+
const x2 = (xOf.get(edge.to) ?? 0) - widest / 2;
|
|
184
|
+
const midX = (x1 + x2) / 2;
|
|
185
|
+
return /* @__PURE__ */ jsxRuntime.jsx("path", { className: "fly-eg-wire", d: "M " + x1 + " " + yOf.get(edge.from) + " H " + midX + " V " + yOf.get(edge.to) + " H " + x2 }, kind + edge.from + edge.to);
|
|
186
|
+
}) }),
|
|
187
|
+
nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
188
|
+
"div",
|
|
189
|
+
{
|
|
190
|
+
className: "fly-eg-node" + (node.tone === "finding" ? " is-finding" : node.tone === "asset" ? " is-asset" : ""),
|
|
191
|
+
style: { left: xOf.get(node.id), top: yOf.get(node.id) },
|
|
192
|
+
children: node.label
|
|
193
|
+
},
|
|
194
|
+
node.id
|
|
195
|
+
)),
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-graph-hint", children: "折线走层间间隙 · 静态分层 · 只读" })
|
|
197
|
+
] });
|
|
198
|
+
}
|
|
199
|
+
function Panel() {
|
|
200
|
+
const [workspace, setWorkspace] = react.useState(() => localStorage.getItem(STORE_KEY) ?? "");
|
|
201
|
+
const [state, setState] = react.useState(null);
|
|
202
|
+
const [error, setError] = react.useState("");
|
|
203
|
+
const [view, setView] = react.useState("table");
|
|
204
|
+
const [report, setReport] = react.useState("");
|
|
205
|
+
const load = react.useCallback(async (target) => {
|
|
206
|
+
if (target.trim() === "") return;
|
|
207
|
+
setError("");
|
|
208
|
+
try {
|
|
209
|
+
const [next, rendered] = await Promise.all([
|
|
210
|
+
getJson(BASE + "/state?workspace=" + encodeURIComponent(target)),
|
|
211
|
+
getJson(BASE + "/report?workspace=" + encodeURIComponent(target)).catch(() => ({ markdown: "" }))
|
|
212
|
+
]);
|
|
213
|
+
setState(next);
|
|
214
|
+
setReport(rendered.markdown);
|
|
215
|
+
localStorage.setItem(STORE_KEY, target);
|
|
216
|
+
} catch (failure) {
|
|
217
|
+
setError(String(failure.message ?? failure));
|
|
218
|
+
setState(null);
|
|
219
|
+
}
|
|
220
|
+
}, []);
|
|
221
|
+
react.useEffect(() => {
|
|
222
|
+
void load(workspace);
|
|
223
|
+
}, [load, workspace]);
|
|
224
|
+
const projection = state?.projection;
|
|
225
|
+
const groups = react.useMemo(() => {
|
|
226
|
+
const map = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const fact of projection?.facts ?? []) {
|
|
228
|
+
const tool = fact.source?.tool ?? "(unknown tool)";
|
|
229
|
+
map.set(tool, [...map.get(tool) ?? [], fact]);
|
|
230
|
+
}
|
|
231
|
+
return [...map.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
232
|
+
}, [projection]);
|
|
233
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "fly-eg-panel", children: [
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-head", children: [
|
|
235
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-title", children: [
|
|
236
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { children: "Engagement" }),
|
|
237
|
+
projection?.goal === null || projection === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-badge", children: projection.goal.target })
|
|
238
|
+
] }),
|
|
239
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-meta", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
240
|
+
"input",
|
|
241
|
+
{
|
|
242
|
+
className: "fly-eg-input",
|
|
243
|
+
value: workspace,
|
|
244
|
+
placeholder: "工作区绝对路径(记录在 <workspace>/.fly-engagement/events.jsonl)",
|
|
245
|
+
onChange: (event) => setWorkspace(event.target.value),
|
|
246
|
+
onKeyDown: (event) => {
|
|
247
|
+
if (event.key === "Enter") void load(workspace);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
) }),
|
|
251
|
+
projection?.goal === null || projection === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
252
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-meta", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
253
|
+
"objective: ",
|
|
254
|
+
projection.goal.objective
|
|
255
|
+
] }) }),
|
|
256
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-auth", children: [
|
|
257
|
+
"authorization: ",
|
|
258
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.goal.authorization === void 0 || projection.goal.authorization === "" ? "(未记录)" : projection.goal.authorization }),
|
|
259
|
+
" · 审计事实,不是门禁"
|
|
260
|
+
] })
|
|
261
|
+
] })
|
|
262
|
+
] }),
|
|
263
|
+
error !== "" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-error", children: error }) : null,
|
|
264
|
+
projection === void 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-empty", children: "还没读取记录(填工作区后回车)" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-counters", children: [
|
|
266
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
|
|
267
|
+
"facts",
|
|
268
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.facts })
|
|
269
|
+
] }),
|
|
270
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
|
|
271
|
+
"findings",
|
|
272
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.findings })
|
|
273
|
+
] }),
|
|
274
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
|
|
275
|
+
"assets",
|
|
276
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.assets })
|
|
277
|
+
] }),
|
|
278
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter", children: [
|
|
279
|
+
"edges",
|
|
280
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.counts.edges })
|
|
281
|
+
] }),
|
|
282
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter" + (projection.dropped > 0 ? " is-warn" : ""), children: [
|
|
283
|
+
"dropped",
|
|
284
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.dropped })
|
|
285
|
+
] }),
|
|
286
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-counter" + (projection.refused > 0 ? " is-warn" : ""), children: [
|
|
287
|
+
"refused",
|
|
288
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: projection.refused })
|
|
289
|
+
] })
|
|
290
|
+
] }),
|
|
291
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-tabs", children: [
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-tab" + (view === "table" ? " is-active" : ""), onClick: () => setView("table"), children: "表" }),
|
|
293
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-tab" + (view === "graph" ? " is-active" : ""), onClick: () => setView("graph"), children: "图" })
|
|
294
|
+
] }),
|
|
295
|
+
view === "table" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
296
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
|
|
297
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Assets" }),
|
|
298
|
+
/* @__PURE__ */ jsxRuntime.jsx("table", { className: "fly-eg-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: projection.assets.map((asset) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
299
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-id", children: asset.id }),
|
|
300
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: asset.type }),
|
|
301
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: asset.value }),
|
|
302
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-src", children: asset.parentId ?? "—" })
|
|
303
|
+
] }, asset.id)) }) })
|
|
304
|
+
] }),
|
|
305
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
|
|
306
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Findings" }),
|
|
307
|
+
/* @__PURE__ */ jsxRuntime.jsx("table", { className: "fly-eg-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: projection.findings.map((finding) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
308
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-id", children: finding.id }),
|
|
309
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-eg-sev " + (finding.severity ?? "info"), children: finding.severity ?? "info" }) }),
|
|
310
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: finding.title }),
|
|
311
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "fly-eg-steps", children: finding.reproducibleSteps.map((step, index) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: step }) }, index)) }) })
|
|
312
|
+
] }, finding.id)) }) })
|
|
313
|
+
] }),
|
|
314
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
|
|
315
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Facts · by source tool" }),
|
|
316
|
+
groups.map(([tool, facts], index) => /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "fly-eg-group", open: index === 0, children: [
|
|
317
|
+
/* @__PURE__ */ jsxRuntime.jsxs("summary", { children: [
|
|
318
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: tool }),
|
|
319
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fly-eg-count", children: [
|
|
320
|
+
facts.length,
|
|
321
|
+
" 条"
|
|
322
|
+
] })
|
|
323
|
+
] }),
|
|
324
|
+
/* @__PURE__ */ jsxRuntime.jsx("table", { className: "fly-eg-table", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: facts.map((fact) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
325
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "fly-eg-id", children: fact.id }),
|
|
326
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { children: fact.detail })
|
|
327
|
+
] }, fact.id)) }) })
|
|
328
|
+
] }, tool))
|
|
329
|
+
] }),
|
|
330
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
|
|
331
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Report" }),
|
|
332
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-eg-report", children: report === "" ? "(还没有可渲染的报告)" : report.slice(0, 2e3) })
|
|
333
|
+
] })
|
|
334
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
335
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
|
|
336
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Evidence graph" }),
|
|
337
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
338
|
+
Graph,
|
|
339
|
+
{
|
|
340
|
+
kind: "proves",
|
|
341
|
+
nodes: [
|
|
342
|
+
...projection.facts.map((fact, index) => ({ id: fact.id, label: fact.id + " " + (fact.kind ?? "info"), layer: 0, tone: void 0 })),
|
|
343
|
+
...projection.findings.map((finding) => ({ id: finding.id, label: finding.id + " " + finding.title, layer: 1, tone: "finding" }))
|
|
344
|
+
],
|
|
345
|
+
edges: projection.edges.filter((edge) => edge.kind === "proves" || edge.kind === "derived_from")
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
] }),
|
|
349
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-eg-section", children: [
|
|
350
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Asset tree" }),
|
|
351
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
352
|
+
Graph,
|
|
353
|
+
{
|
|
354
|
+
kind: "parent",
|
|
355
|
+
nodes: projection.assets.map((asset) => ({ id: asset.id, label: asset.value + " (" + asset.type + ")", layer: asset.parentId === void 0 ? 0 : 1, tone: "asset" })),
|
|
356
|
+
edges: projection.edges.filter((edge) => edge.kind === "parent")
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
] })
|
|
360
|
+
] })
|
|
361
|
+
] })
|
|
362
|
+
] });
|
|
363
|
+
}
|
|
364
|
+
function EngagementMark({ size = 18, active = false }) {
|
|
365
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "fly-mark", "data-active": active ? "true" : "false", width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
|
|
366
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6", cy: "6", r: "2.2", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
367
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "18", cy: "12", r: "2.2", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
368
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6", cy: "18", r: "2.2", stroke: "currentColor", strokeWidth: "1.6" }),
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 7l8 4M8 17l8-4", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })
|
|
370
|
+
] });
|
|
371
|
+
}
|
|
372
|
+
function apply(ctx) {
|
|
373
|
+
ctx.effect(() => ctx.sidebarRightTabs.register({
|
|
374
|
+
id: PACKAGE,
|
|
375
|
+
kind: KIND,
|
|
376
|
+
title: () => TAB_TITLE,
|
|
377
|
+
guide: [{ id: "open", order: GUIDE_ORDER, title: () => TAB_TITLE, description: () => TAB_DESCRIPTION, icon: EngagementMark }]
|
|
378
|
+
}), "fly-engagement-panel: drawer type");
|
|
379
|
+
ctx.effect(() => {
|
|
380
|
+
const tag = document.createElement("style");
|
|
381
|
+
tag.setAttribute("data-plugin", PACKAGE);
|
|
382
|
+
tag.textContent = PANEL_CSS;
|
|
383
|
+
document.head.appendChild(tag);
|
|
384
|
+
return () => {
|
|
385
|
+
tag.remove();
|
|
386
|
+
};
|
|
387
|
+
}, "fly-engagement-panel: panel stylesheet");
|
|
388
|
+
ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register(
|
|
389
|
+
{ name: "sidebar.right.pane.tab", key: PACKAGE, inject: () => ({}) },
|
|
390
|
+
Panel
|
|
391
|
+
)), "fly-engagement-panel: drawer body");
|
|
392
|
+
}
|
|
393
|
+
exports.EngagementMark = EngagementMark;
|
|
394
|
+
exports.apply = apply;
|
|
395
|
+
exports.inject = inject;
|
|
396
|
+
exports.name = name;
|
|
397
|
+
|
|
398
|
+
return module.exports;
|
|
399
|
+
},
|
|
400
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-fly/dsh-plugin-fly-engagement",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"scripts": {
|
|
46
46
|
"verify": "node scripts/verify.mjs",
|
|
47
47
|
"test": "node --test tests/*.spec.mjs",
|
|
48
|
-
"build:client": "node scripts/build-client.mjs"
|
|
48
|
+
"build:client": "node scripts/build-client.mjs",
|
|
49
|
+
"prepare": "node scripts/build-client.mjs"
|
|
49
50
|
},
|
|
50
51
|
"description": "DSH Fly engagement 记录面:eng_record/eng_state/eng_report + 抽屉面板(工作区 JSONL + 投影)",
|
|
51
52
|
"homepage": "https://github.com/ethanchen669/dsh-plugin-fly-engagement#readme",
|