dsh-perm-gate 2.1.2 → 2.6.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/CHANGELOG.ja.md +108 -0
- package/CHANGELOG.ko.md +104 -0
- package/CHANGELOG.md +236 -0
- package/INSTALL.ja.md +41 -21
- package/INSTALL.ko.md +37 -19
- package/INSTALL.md +46 -23
- package/INSTALL.zh.md +33 -17
- package/README.ja.md +52 -12
- package/README.ko.md +53 -13
- package/README.md +68 -10
- package/README.zh.md +41 -10
- package/assets/allow1.png +0 -0
- package/assets/allow2.png +0 -0
- package/assets/gateoff.png +0 -0
- package/assets/llms.png +0 -0
- package/assets/permlist.png +0 -0
- package/assets/setting-head.png +0 -0
- package/assets/setting-rules.png +0 -0
- package/cordis.patch.yml +11 -8
- package/docs/baseline-guard.md +56 -0
- package/docs/plan-rules-to-settings.md +217 -0
- package/docs/rules-format.en.md +294 -0
- package/docs/rules-format.md +279 -0
- package/docs/ui-gap-analysis-and-branch-permissions.md +338 -0
- package/dsh.plugin.json +2 -2
- package/examples/permissions.example.yaml +16 -1
- package/lib/allowlist.d.ts +27 -0
- package/lib/allowlist.js +103 -1
- package/lib/cli.js +11 -14
- package/lib/client.js +484 -6
- package/lib/client.js.map +1 -1
- package/lib/command-dispatcher.d.ts +22 -0
- package/lib/command-dispatcher.js +97 -0
- package/lib/command-semantics.d.ts +62 -0
- package/lib/command-semantics.js +6 -0
- package/lib/compiler.d.ts +11 -0
- package/lib/compiler.js +16 -0
- package/lib/config.d.ts +73 -12
- package/lib/config.js +56 -14
- package/lib/dry-run.d.ts +87 -0
- package/lib/dry-run.js +110 -0
- package/lib/engine.js +52 -2
- package/lib/evaluate.js +76 -2
- package/lib/events.d.ts +56 -2
- package/lib/events.js +92 -0
- package/lib/index.js +106 -26
- package/lib/parsers/git.d.ts +43 -0
- package/lib/parsers/git.js +404 -0
- package/lib/parsers/shell-cmds.d.ts +32 -0
- package/lib/parsers/shell-cmds.js +293 -0
- package/lib/preset.d.ts +13 -0
- package/lib/preset.js +17 -0
- package/lib/risk.d.ts +17 -3
- package/lib/risk.js +17 -3
- package/lib/rule-chain.js +13 -22
- package/lib/rule-dims.d.ts +35 -0
- package/lib/rule-dims.js +36 -0
- package/lib/rule.d.ts +36 -1
- package/lib/rule.js +56 -8
- package/lib/rules-view.d.ts +52 -0
- package/lib/rules-view.js +126 -0
- package/lib/runtime.d.ts +121 -7
- package/lib/runtime.js +204 -25
- package/lib/shell.d.ts +14 -0
- package/lib/shell.js +17 -0
- package/package.json +19 -2
- package/scripts/patch-permission-glyph.mjs +225 -42
package/lib/client.js
CHANGED
|
@@ -16,10 +16,11 @@ window.__ModuleLoader__.load({
|
|
|
16
16
|
"card.description": "独立审批档位:与只读 / 完全权限 / 白名单平行,在会话权限下拉中显示为「自动审查」。前端只暴露一个开关;后台四个审批策略可组合、由此处设置决定,仍对 P0 硬拒绝保持 fail-closed。",
|
|
17
17
|
"card.permissive": "启用自动审查档位",
|
|
18
18
|
"card.permissiveHint": "关闭时门禁行为与此前完全一致。",
|
|
19
|
+
"card.scopeNote": "档位作用域:本门禁仅在会话权限档位为 %scope 时生效。其他档位下整个门禁停用(含 P0 硬拒绝),由所选档位自身的策略接管;停用时会写入一条 stand-down 事件,并在输入框上方常驻一条提示条。",
|
|
19
20
|
"card.strategies": "后台审批策略(至少一个生效;可组合)",
|
|
20
21
|
"card.strategy.trustAutoAllow": "trustAutoAllow — 作用域内安全操作自动放行,危险/未知转 ask(中间档基线)",
|
|
21
22
|
"card.strategy.alwaysConfirm": "alwaysConfirm — 一律逐次 ask;审批面板「允许控件」含两个扩展按钮:「本会话重复允许该类」(会话语限次 grant)与「允许所有类型」(命令词持久迁入 rulesFile 白名单)",
|
|
22
|
-
"card.strategy.llmAssist": "llmAssist — 自定义 LLM 按风险分级裁决:safe 自动放行;deletion/credential/remote/system/bulk
|
|
23
|
+
"card.strategy.llmAssist": "llmAssist — 自定义 LLM 按风险分级裁决:safe 自动放行;deletion/credential/remote/system/bulk 硬风险维持人工确认(分类器永不拒绝,只负责升级判断);neutral 进入确认学习;失败/超时回退人工",
|
|
23
24
|
"card.strategy.trustEscalation": "trustEscalation — 沙箱升级免确认:本门禁已放行的调用,其工具内部发起的沙箱提权(shell / pwsh / edit 的 sandbox_permissions)由门禁直接批准,不再弹窗",
|
|
24
25
|
"card.strategy.trustEscalationHint": "沙箱提权审批发生在 tools/pre-execute 之后、工具体内部,门禁原本看不到它,因此「LLM 判安全已自动放行」的调用仍会弹窗要求你批准提权。开启后,仅对门禁确实放行过的那一次调用(按 callId 精确匹配)免确认,其余一律照旧走人工;目标模式非 workspace-write / danger-full-access 或理由无法识别时同样回退人工。关闭后提权保持人工把关。",
|
|
25
26
|
"card.readonly": "只读",
|
|
@@ -102,6 +103,7 @@ window.__ModuleLoader__.load({
|
|
|
102
103
|
"history.tag.manualApproved": "人工通过",
|
|
103
104
|
"history.tag.manualRejected": "人工拒绝",
|
|
104
105
|
"history.tag.manualCancelled": "人工取消",
|
|
106
|
+
"history.tag.standDown": "门禁停用",
|
|
105
107
|
"history.learnedProgress": "学习 %n/%t",
|
|
106
108
|
"history.snapshots": "diff 快照",
|
|
107
109
|
"history.clearSession": "仅清本会话",
|
|
@@ -124,7 +126,41 @@ window.__ModuleLoader__.load({
|
|
|
124
126
|
"history.diffChipTitle": "查看该文件改动对比",
|
|
125
127
|
"history.diffStats": "+%a / -%r 行变更 · %c 行未变",
|
|
126
128
|
"history.diffGone": "(文件当前已不存在)",
|
|
127
|
-
"history.diffHidden": "%n 行未修改"
|
|
129
|
+
"history.diffHidden": "%n 行未修改",
|
|
130
|
+
"card.dryRun": "规则测试",
|
|
131
|
+
"card.dryRunHint": "用当前生效的规则判定一次调用,不执行任何工具、不写入任何规则。改规则前先在这里验一遍。",
|
|
132
|
+
"card.dryRunTool": "工具名",
|
|
133
|
+
"card.dryRunCommand": "命令 / 参数",
|
|
134
|
+
"card.dryRunCommandPlaceholder": "git push --force origin main",
|
|
135
|
+
"card.dryRunTest": "测试",
|
|
136
|
+
"card.dryRunRunning": "判定中…",
|
|
137
|
+
"card.dryRunEmpty": "尚未测试。填入工具名与命令,点「测试」查看会怎么裁决。",
|
|
138
|
+
"card.dryRunVerdict": "裁决",
|
|
139
|
+
"card.dryRunAllow": "放行",
|
|
140
|
+
"card.dryRunAsk": "人工确认",
|
|
141
|
+
"card.dryRunDeny": "拒绝",
|
|
142
|
+
"card.dryRunRule": "命中规则",
|
|
143
|
+
"card.dryRunRuleNone": "无规则命中,走 defaultAction:%d",
|
|
144
|
+
"card.dryRunDimensions": "约束维度",
|
|
145
|
+
"card.dryRunReason": "原因",
|
|
146
|
+
"card.dryRunNote": "「命中规则」来自规则层。若裁决由更早的硬拒绝或关键词黑名单产生,则不会有规则序号——那种情况下列出的维度仅供参考。",
|
|
147
|
+
"card.dryRunNoRules": "(当前加载了 0 条规则:检查 rulesFile 路径)",
|
|
148
|
+
"card.dryRunFail": "测试失败:",
|
|
149
|
+
"card.dryRunStale": "路由不存在(插件未重启?)",
|
|
150
|
+
"card.rules": "权限 YAML",
|
|
151
|
+
"card.rulesHint": "门禁此刻实际加载的规则文件原文。只读——这里改不了任何东西。要改规则,用下面的白名单,或直接编辑该文件。",
|
|
152
|
+
"card.rulesRefresh": "刷新",
|
|
153
|
+
"card.rulesLoading": "读取中…",
|
|
154
|
+
"card.rulesPath": "文件",
|
|
155
|
+
"card.rulesNone": "未配置规则文件",
|
|
156
|
+
"card.rulesCounts": "规则",
|
|
157
|
+
"card.rulesDefault": "defaultAction",
|
|
158
|
+
"card.rulesStats": "%b 字节 · %l 行",
|
|
159
|
+
"card.rulesMissing": "文件不存在。门禁会退到内置默认。",
|
|
160
|
+
"card.rulesTruncated": "文件过大,仅显示前 512 KB。",
|
|
161
|
+
"card.rulesError": "加载失败:",
|
|
162
|
+
"card.rulesStale": "路由不存在(插件未重启?)",
|
|
163
|
+
"card.rulesNote": "此处只读:面板无法修改该文件,白名单段是唯一会写入它的地方。"
|
|
128
164
|
};
|
|
129
165
|
/** English dictionary (keys mirror zh). */
|
|
130
166
|
const en = {
|
|
@@ -132,10 +168,11 @@ window.__ModuleLoader__.load({
|
|
|
132
168
|
"card.description": "Independent approval tier, parallel to read-only / full-access / whitelist, shown as 自动审查 in the session permission picker. The front-end exposes a single switch; the four backend approval strategies are combinable via this panel and still fail-closed against P0 hard-deny.",
|
|
133
169
|
"card.permissive": "Enable the 自动审查 tier",
|
|
134
170
|
"card.permissiveHint": "When off, the gate behaves exactly as before.",
|
|
171
|
+
"card.scopeNote": "Preset scope: this gate only acts while the session permission preset is %scope. Under any other preset the whole gate stands down — P0 hard-deny included — and the selected preset's own policy takes over. A stand-down records one event and leaves a sticky strip above the input.",
|
|
135
172
|
"card.strategies": "Backend approval strategies (at least one effective; combinable)",
|
|
136
173
|
"card.strategy.trustAutoAllow": "trustAutoAllow — safe in-scope ops auto-allow; dangerous/unknown ask (baseline middle tier)",
|
|
137
174
|
"card.strategy.alwaysConfirm": "alwaysConfirm — every crossing asks; the approval \"allow controls\" get two extended buttons: \"repeat-allow this type this session\" (bounded session grant) and \"allow every occurrence\" (persist the command word into the rulesFile whitelist)",
|
|
138
|
-
"card.strategy.llmAssist": "llmAssist — a custom LLM grades risk: safe auto-allows; deletion/credential/remote/system/bulk hard risks
|
|
175
|
+
"card.strategy.llmAssist": "llmAssist — a custom LLM grades risk: safe auto-allows; deletion/credential/remote/system/bulk hard risks keep the human ask (the classifier never denies — it only escalates); neutral enters confirm-learning; failure/timeout falls back to the human",
|
|
139
176
|
"card.strategy.trustEscalation": "trustEscalation — sandbox escalation without a prompt: for a call this gate already allowed, the sandbox widening a shell / pwsh / edit tool raises from inside itself is approved here instead of asking you",
|
|
140
177
|
"card.strategy.trustEscalationHint": "A sandbox escalation is asked from inside the tool body, after tools/pre-execute, so the gate never saw it — a call the LLM graded safe and the gate auto-allowed still prompted you to approve the widening. With this on, only the exact call the gate allowed (matched by callId) skips the prompt; everything else goes to the human as before, as does any unrecognized reason or a target other than workspace-write / danger-full-access. Turn it off to keep widening human-gated.",
|
|
141
178
|
"card.readonly": "Read-only",
|
|
@@ -218,6 +255,7 @@ window.__ModuleLoader__.load({
|
|
|
218
255
|
"history.tag.manualApproved": "Approved",
|
|
219
256
|
"history.tag.manualRejected": "Rejected",
|
|
220
257
|
"history.tag.manualCancelled": "Cancelled",
|
|
258
|
+
"history.tag.standDown": "Gate off",
|
|
221
259
|
"history.learnedProgress": "learned %n/%t",
|
|
222
260
|
"history.snapshots": "diff snapshots",
|
|
223
261
|
"history.clearSession": "Clear this session",
|
|
@@ -240,7 +278,41 @@ window.__ModuleLoader__.load({
|
|
|
240
278
|
"history.diffChipTitle": "View this file’s change comparison",
|
|
241
279
|
"history.diffStats": "+%a / -%r lines changed · %c unchanged",
|
|
242
280
|
"history.diffGone": " (file no longer exists)",
|
|
243
|
-
"history.diffHidden": "%n unmodified lines"
|
|
281
|
+
"history.diffHidden": "%n unmodified lines",
|
|
282
|
+
"card.dryRun": "Rule test",
|
|
283
|
+
"card.dryRunHint": "Judge one call against the rules in force. Runs no tool and writes no rule — check a rule here before you change it.",
|
|
284
|
+
"card.dryRunTool": "Tool",
|
|
285
|
+
"card.dryRunCommand": "Command / arguments",
|
|
286
|
+
"card.dryRunCommandPlaceholder": "git push --force origin main",
|
|
287
|
+
"card.dryRunTest": "Test",
|
|
288
|
+
"card.dryRunRunning": "Judging…",
|
|
289
|
+
"card.dryRunEmpty": "Nothing tested yet. Fill in a tool and command, then press Test to see how it would be judged.",
|
|
290
|
+
"card.dryRunVerdict": "Verdict",
|
|
291
|
+
"card.dryRunAllow": "allow",
|
|
292
|
+
"card.dryRunAsk": "ask",
|
|
293
|
+
"card.dryRunDeny": "deny",
|
|
294
|
+
"card.dryRunRule": "Matched rule",
|
|
295
|
+
"card.dryRunRuleNone": "no rule matched; defaultAction: %d",
|
|
296
|
+
"card.dryRunDimensions": "Dimensions",
|
|
297
|
+
"card.dryRunReason": "Reason",
|
|
298
|
+
"card.dryRunNote": "\"Matched rule\" comes from the rule layer. When the verdict was produced by an earlier hard-deny or by the keyword blacklist there is no rule index — the dimensions listed are then informational only.",
|
|
299
|
+
"card.dryRunNoRules": "(0 rules loaded — check the rulesFile path)",
|
|
300
|
+
"card.dryRunFail": "Test failed: ",
|
|
301
|
+
"card.dryRunStale": "route missing (plugin not restarted?)",
|
|
302
|
+
"card.rules": "Permissions YAML",
|
|
303
|
+
"card.rulesHint": "The rules file the gate is loading right now, verbatim. Read-only — nothing here can change it. To change rules, use the allowlist below or edit the file directly.",
|
|
304
|
+
"card.rulesRefresh": "Refresh",
|
|
305
|
+
"card.rulesLoading": "Reading…",
|
|
306
|
+
"card.rulesPath": "File",
|
|
307
|
+
"card.rulesNone": "no rules file configured",
|
|
308
|
+
"card.rulesCounts": "Rules",
|
|
309
|
+
"card.rulesDefault": "defaultAction",
|
|
310
|
+
"card.rulesStats": "%b bytes · %l lines",
|
|
311
|
+
"card.rulesMissing": "The file does not exist. The gate falls back to its built-in defaults.",
|
|
312
|
+
"card.rulesTruncated": "File is large; showing the first 512 KB only.",
|
|
313
|
+
"card.rulesError": "Load failed: ",
|
|
314
|
+
"card.rulesStale": "route missing (plugin not restarted?)",
|
|
315
|
+
"card.rulesNote": "Read-only: this panel cannot modify the file. The allowlist section is the only place that writes to it."
|
|
244
316
|
};
|
|
245
317
|
//#endregion
|
|
246
318
|
//#region src/deny-defaults.ts
|
|
@@ -309,6 +381,19 @@ window.__ModuleLoader__.load({
|
|
|
309
381
|
}
|
|
310
382
|
];
|
|
311
383
|
//#endregion
|
|
384
|
+
//#region src/preset.ts
|
|
385
|
+
/**
|
|
386
|
+
* Presets in which the gate is active. It owns both 自动审查 tiers — the plain
|
|
387
|
+
* one (file sandbox kept) and the full-access one (sandbox restriction lifted,
|
|
388
|
+
* same approval behaviour) — so a user can have the gate without inheriting a
|
|
389
|
+
* sandbox that breaks `git` / Cygwin tools. `'*'` makes it global (every
|
|
390
|
+
* preset, including the hard-deny layer).
|
|
391
|
+
*
|
|
392
|
+
* Lives in this dependency-free module (not `config.ts`) so the browser half can
|
|
393
|
+
* render the scope without pulling schemastery into the client bundle.
|
|
394
|
+
*/
|
|
395
|
+
const DEFAULT_GATE_PRESETS = ["permissive", "permissive-full"];
|
|
396
|
+
//#endregion
|
|
312
397
|
//#region src/client/sediment.tsx
|
|
313
398
|
/**
|
|
314
399
|
* Learning-sediment management section — the settings-card face of verdict
|
|
@@ -559,6 +644,67 @@ window.__ModuleLoader__.load({
|
|
|
559
644
|
const [bulkOpen, setBulkOpen] = (0, react.useState)(false);
|
|
560
645
|
const [healthBusy, setHealthBusy] = (0, react.useState)(false);
|
|
561
646
|
const [healthResult, setHealthResult] = (0, react.useState)(null);
|
|
647
|
+
const [dryTool, setDryTool] = (0, react.useState)("shell");
|
|
648
|
+
const [dryCommand, setDryCommand] = (0, react.useState)("");
|
|
649
|
+
const [dryBusy, setDryBusy] = (0, react.useState)(false);
|
|
650
|
+
const [dryReport, setDryReport] = (0, react.useState)(null);
|
|
651
|
+
const [dryError, setDryError] = (0, react.useState)(null);
|
|
652
|
+
const runDryRunTest = () => {
|
|
653
|
+
const tool = dryTool.trim() === "" ? "shell" : dryTool.trim();
|
|
654
|
+
const command = dryCommand.trim();
|
|
655
|
+
setDryBusy(true);
|
|
656
|
+
setDryError(null);
|
|
657
|
+
setDryReport(null);
|
|
658
|
+
fetch("/api/dsh-perm-gate/dry-run", {
|
|
659
|
+
method: "POST",
|
|
660
|
+
headers: { "content-type": "application/json" },
|
|
661
|
+
body: JSON.stringify({
|
|
662
|
+
tool,
|
|
663
|
+
args: command === "" ? {} : { command }
|
|
664
|
+
})
|
|
665
|
+
}).then(async (r) => {
|
|
666
|
+
const text = await r.text();
|
|
667
|
+
if (r.status === 404) throw new Error(t("card.dryRunStale"));
|
|
668
|
+
try {
|
|
669
|
+
return JSON.parse(text);
|
|
670
|
+
} catch {
|
|
671
|
+
throw new Error(text.trim().slice(0, 120) !== "" ? text.trim().slice(0, 120) : `HTTP ${r.status}`);
|
|
672
|
+
}
|
|
673
|
+
}).then((res) => {
|
|
674
|
+
if (res.ok === true && res.result !== void 0) setDryReport(res.result);
|
|
675
|
+
else setDryError(res.error ?? t("card.dryRunFail"));
|
|
676
|
+
}).catch((e) => {
|
|
677
|
+
setDryError(String(e?.message ?? e));
|
|
678
|
+
}).finally(() => {
|
|
679
|
+
setDryBusy(false);
|
|
680
|
+
});
|
|
681
|
+
};
|
|
682
|
+
const [rulesBody, setRulesBody] = (0, react.useState)(null);
|
|
683
|
+
const [rulesBusy, setRulesBusy] = (0, react.useState)(false);
|
|
684
|
+
const [rulesError, setRulesError] = (0, react.useState)(null);
|
|
685
|
+
const loadRules = () => {
|
|
686
|
+
setRulesBusy(true);
|
|
687
|
+
setRulesError(null);
|
|
688
|
+
fetch("/api/dsh-perm-gate/rules", { headers: { "cache-control": "no-cache" } }).then(async (r) => {
|
|
689
|
+
const text = await r.text();
|
|
690
|
+
if (r.status === 404) throw new Error(t("card.rulesStale"));
|
|
691
|
+
try {
|
|
692
|
+
return JSON.parse(text);
|
|
693
|
+
} catch {
|
|
694
|
+
throw new Error(text.trim().slice(0, 120) !== "" ? text.trim().slice(0, 120) : `HTTP ${r.status}`);
|
|
695
|
+
}
|
|
696
|
+
}).then((res) => {
|
|
697
|
+
if (res.ok === true && res.result !== void 0) setRulesBody(res.result);
|
|
698
|
+
else setRulesError(res.error ?? t("card.rulesError"));
|
|
699
|
+
}).catch((e) => {
|
|
700
|
+
setRulesError(String(e?.message ?? e));
|
|
701
|
+
}).finally(() => {
|
|
702
|
+
setRulesBusy(false);
|
|
703
|
+
});
|
|
704
|
+
};
|
|
705
|
+
(0, react.useEffect)(() => {
|
|
706
|
+
loadRules();
|
|
707
|
+
}, []);
|
|
562
708
|
const receiverSource = (value.classifierSource ?? "custom") === "host" ? "host" : "custom";
|
|
563
709
|
const [receiverInfo, setReceiverInfo] = (0, react.useState)(null);
|
|
564
710
|
const [receiverNonce, setReceiverNonce] = (0, react.useState)(0);
|
|
@@ -696,6 +842,10 @@ window.__ModuleLoader__.load({
|
|
|
696
842
|
style: hintStyle,
|
|
697
843
|
children: t("card.permissiveHint")
|
|
698
844
|
}),
|
|
845
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
846
|
+
style: hintStyle,
|
|
847
|
+
children: t("card.scopeNote").replace("%scope", ((value.gatePresets ?? []).length > 0 ? value.gatePresets : [...DEFAULT_GATE_PRESETS]).join(" / "))
|
|
848
|
+
}),
|
|
699
849
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
700
850
|
style: sectionStyle,
|
|
701
851
|
children: [
|
|
@@ -1115,6 +1265,325 @@ window.__ModuleLoader__.load({
|
|
|
1115
1265
|
}) : null
|
|
1116
1266
|
]
|
|
1117
1267
|
}),
|
|
1268
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1269
|
+
style: sectionStyle,
|
|
1270
|
+
children: [
|
|
1271
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1272
|
+
style: labelStyle,
|
|
1273
|
+
children: t("card.dryRun")
|
|
1274
|
+
}),
|
|
1275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1276
|
+
style: hintStyle,
|
|
1277
|
+
children: t("card.dryRunHint")
|
|
1278
|
+
}),
|
|
1279
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1280
|
+
style: {
|
|
1281
|
+
display: "flex",
|
|
1282
|
+
flexWrap: "wrap",
|
|
1283
|
+
gap: "8px",
|
|
1284
|
+
alignItems: "flex-end",
|
|
1285
|
+
margin: "6px 0"
|
|
1286
|
+
},
|
|
1287
|
+
children: [
|
|
1288
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1289
|
+
style: {
|
|
1290
|
+
display: "flex",
|
|
1291
|
+
flexDirection: "column",
|
|
1292
|
+
gap: "4px",
|
|
1293
|
+
flex: "0 0 120px"
|
|
1294
|
+
},
|
|
1295
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1296
|
+
style: fieldLabelStyle,
|
|
1297
|
+
children: t("card.dryRunTool")
|
|
1298
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1299
|
+
type: "text",
|
|
1300
|
+
value: dryTool,
|
|
1301
|
+
disabled: readonly,
|
|
1302
|
+
placeholder: "shell",
|
|
1303
|
+
onChange: (e) => {
|
|
1304
|
+
setDryTool(e.target.value);
|
|
1305
|
+
},
|
|
1306
|
+
onKeyDown: (e) => {
|
|
1307
|
+
if (e.key === "Enter") runDryRunTest();
|
|
1308
|
+
},
|
|
1309
|
+
style: controlStyle
|
|
1310
|
+
})]
|
|
1311
|
+
}),
|
|
1312
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1313
|
+
style: {
|
|
1314
|
+
display: "flex",
|
|
1315
|
+
flexDirection: "column",
|
|
1316
|
+
gap: "4px",
|
|
1317
|
+
flex: "1 1 260px",
|
|
1318
|
+
minWidth: 0
|
|
1319
|
+
},
|
|
1320
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1321
|
+
style: fieldLabelStyle,
|
|
1322
|
+
children: t("card.dryRunCommand")
|
|
1323
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1324
|
+
type: "text",
|
|
1325
|
+
value: dryCommand,
|
|
1326
|
+
disabled: readonly,
|
|
1327
|
+
placeholder: t("card.dryRunCommandPlaceholder"),
|
|
1328
|
+
onChange: (e) => {
|
|
1329
|
+
setDryCommand(e.target.value);
|
|
1330
|
+
},
|
|
1331
|
+
onKeyDown: (e) => {
|
|
1332
|
+
if (e.key === "Enter") runDryRunTest();
|
|
1333
|
+
},
|
|
1334
|
+
style: controlStyle
|
|
1335
|
+
})]
|
|
1336
|
+
}),
|
|
1337
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1338
|
+
type: "button",
|
|
1339
|
+
disabled: readonly || dryBusy,
|
|
1340
|
+
onClick: runDryRunTest,
|
|
1341
|
+
style: {
|
|
1342
|
+
...controlStyle,
|
|
1343
|
+
flex: "0 0 auto",
|
|
1344
|
+
cursor: readonly || dryBusy ? "default" : "pointer"
|
|
1345
|
+
},
|
|
1346
|
+
children: dryBusy ? t("card.dryRunRunning") : t("card.dryRunTest")
|
|
1347
|
+
})
|
|
1348
|
+
]
|
|
1349
|
+
}),
|
|
1350
|
+
dryError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1351
|
+
style: {
|
|
1352
|
+
...hintStyle,
|
|
1353
|
+
color: "var(--dsw-alias-label-error, #c0392b)"
|
|
1354
|
+
},
|
|
1355
|
+
children: [t("card.dryRunFail"), dryError]
|
|
1356
|
+
}),
|
|
1357
|
+
dryReport === null ? dryError === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1358
|
+
style: hintStyle,
|
|
1359
|
+
children: t("card.dryRunEmpty")
|
|
1360
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1361
|
+
style: {
|
|
1362
|
+
display: "flex",
|
|
1363
|
+
flexDirection: "column",
|
|
1364
|
+
gap: "4px",
|
|
1365
|
+
padding: "8px 10px",
|
|
1366
|
+
borderRadius: "6px",
|
|
1367
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1368
|
+
background: "var(--dsw-alias-bg-surface, transparent)",
|
|
1369
|
+
fontSize: "12px",
|
|
1370
|
+
lineHeight: "18px"
|
|
1371
|
+
},
|
|
1372
|
+
children: [
|
|
1373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1374
|
+
style: {
|
|
1375
|
+
display: "flex",
|
|
1376
|
+
alignItems: "center",
|
|
1377
|
+
gap: "8px"
|
|
1378
|
+
},
|
|
1379
|
+
children: [
|
|
1380
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1381
|
+
style: fieldLabelStyle,
|
|
1382
|
+
children: t("card.dryRunVerdict")
|
|
1383
|
+
}),
|
|
1384
|
+
(() => {
|
|
1385
|
+
const verdict = dryReport.verdict ?? "ask";
|
|
1386
|
+
const colour = verdict === "deny" ? "var(--dsw-alias-label-error, #c0392b)" : verdict === "allow" ? "var(--dsw-alias-label-success, #2e7d32)" : "var(--dsw-alias-label-warning, #b26a00)";
|
|
1387
|
+
const text = verdict === "deny" ? t("card.dryRunDeny") : verdict === "allow" ? t("card.dryRunAllow") : t("card.dryRunAsk");
|
|
1388
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1389
|
+
style: { color: colour },
|
|
1390
|
+
children: text
|
|
1391
|
+
});
|
|
1392
|
+
})(),
|
|
1393
|
+
(dryReport.ruleCount ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1394
|
+
style: { color: "var(--dsw-alias-label-secondary, inherit)" },
|
|
1395
|
+
children: t("card.dryRunNoRules")
|
|
1396
|
+
})
|
|
1397
|
+
]
|
|
1398
|
+
}),
|
|
1399
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1400
|
+
style: {
|
|
1401
|
+
display: "flex",
|
|
1402
|
+
gap: "8px",
|
|
1403
|
+
overflowWrap: "anywhere"
|
|
1404
|
+
},
|
|
1405
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1406
|
+
style: fieldLabelStyle,
|
|
1407
|
+
children: t("card.dryRunRule")
|
|
1408
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1409
|
+
style: {
|
|
1410
|
+
font: "500 12px/18px var(--ds-font-family-code, monospace)",
|
|
1411
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1412
|
+
},
|
|
1413
|
+
children: dryReport.ruleLayer?.ruleIndex === void 0 ? t("card.dryRunRuleNone").replace("%d", dryReport.defaultAction ?? "ask") : `#${dryReport.ruleLayer.ruleIndex} (${dryReport.ruleLayer.action ?? "?"})`
|
|
1414
|
+
})]
|
|
1415
|
+
}),
|
|
1416
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1417
|
+
style: {
|
|
1418
|
+
display: "flex",
|
|
1419
|
+
gap: "8px",
|
|
1420
|
+
overflowWrap: "anywhere"
|
|
1421
|
+
},
|
|
1422
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1423
|
+
style: fieldLabelStyle,
|
|
1424
|
+
children: t("card.dryRunDimensions")
|
|
1425
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1426
|
+
style: {
|
|
1427
|
+
font: "500 12px/18px var(--ds-font-family-code, monospace)",
|
|
1428
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1429
|
+
},
|
|
1430
|
+
children: (dryReport.ruleLayer?.matchedDimensions ?? []).length === 0 ? "—" : (dryReport.ruleLayer?.matchedDimensions ?? []).join(", ")
|
|
1431
|
+
})]
|
|
1432
|
+
}),
|
|
1433
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1434
|
+
style: {
|
|
1435
|
+
display: "flex",
|
|
1436
|
+
gap: "8px",
|
|
1437
|
+
overflowWrap: "anywhere"
|
|
1438
|
+
},
|
|
1439
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1440
|
+
style: fieldLabelStyle,
|
|
1441
|
+
children: t("card.dryRunReason")
|
|
1442
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1443
|
+
style: { color: "var(--dsw-alias-label-secondary, inherit)" },
|
|
1444
|
+
children: dryReport.reason ?? ""
|
|
1445
|
+
})]
|
|
1446
|
+
}),
|
|
1447
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1448
|
+
style: {
|
|
1449
|
+
...hintStyle,
|
|
1450
|
+
margin: 0
|
|
1451
|
+
},
|
|
1452
|
+
children: t("card.dryRunNote")
|
|
1453
|
+
})
|
|
1454
|
+
]
|
|
1455
|
+
})
|
|
1456
|
+
]
|
|
1457
|
+
}),
|
|
1458
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1459
|
+
style: sectionStyle,
|
|
1460
|
+
children: [
|
|
1461
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1462
|
+
style: {
|
|
1463
|
+
display: "flex",
|
|
1464
|
+
alignItems: "center",
|
|
1465
|
+
justifyContent: "space-between",
|
|
1466
|
+
gap: "8px"
|
|
1467
|
+
},
|
|
1468
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1469
|
+
style: labelStyle,
|
|
1470
|
+
children: t("card.rules")
|
|
1471
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1472
|
+
type: "button",
|
|
1473
|
+
disabled: rulesBusy,
|
|
1474
|
+
onClick: loadRules,
|
|
1475
|
+
style: {
|
|
1476
|
+
...controlStyle,
|
|
1477
|
+
flex: "0 0 auto",
|
|
1478
|
+
cursor: rulesBusy ? "default" : "pointer"
|
|
1479
|
+
},
|
|
1480
|
+
children: rulesBusy ? t("card.rulesLoading") : t("card.rulesRefresh")
|
|
1481
|
+
})]
|
|
1482
|
+
}),
|
|
1483
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1484
|
+
style: hintStyle,
|
|
1485
|
+
children: t("card.rulesHint")
|
|
1486
|
+
}),
|
|
1487
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1488
|
+
style: {
|
|
1489
|
+
display: "flex",
|
|
1490
|
+
gap: "8px",
|
|
1491
|
+
overflowWrap: "anywhere",
|
|
1492
|
+
marginTop: "6px"
|
|
1493
|
+
},
|
|
1494
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1495
|
+
style: fieldLabelStyle,
|
|
1496
|
+
children: t("card.rulesPath")
|
|
1497
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1498
|
+
style: {
|
|
1499
|
+
font: "500 12px/18px var(--ds-font-family-code, monospace)",
|
|
1500
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1501
|
+
},
|
|
1502
|
+
children: rulesBody?.path !== void 0 && rulesBody.path !== "" ? rulesBody.path : t("card.rulesNone")
|
|
1503
|
+
})]
|
|
1504
|
+
}),
|
|
1505
|
+
rulesError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1506
|
+
style: {
|
|
1507
|
+
...hintStyle,
|
|
1508
|
+
color: "var(--dsw-alias-label-error, #c0392b)"
|
|
1509
|
+
},
|
|
1510
|
+
children: [t("card.rulesError"), rulesError]
|
|
1511
|
+
}),
|
|
1512
|
+
rulesBody !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1514
|
+
style: {
|
|
1515
|
+
display: "flex",
|
|
1516
|
+
flexWrap: "wrap",
|
|
1517
|
+
gap: "12px",
|
|
1518
|
+
marginTop: "4px"
|
|
1519
|
+
},
|
|
1520
|
+
children: [
|
|
1521
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1522
|
+
style: fieldLabelStyle,
|
|
1523
|
+
children: [t("card.rulesCounts"), " "]
|
|
1524
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1525
|
+
style: {
|
|
1526
|
+
font: "500 12px/18px var(--ds-font-family-code, monospace)",
|
|
1527
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1528
|
+
},
|
|
1529
|
+
children: `deny ${rulesBody.counts?.deny ?? 0} · allow ${rulesBody.counts?.allow ?? 0} · ask ${rulesBody.counts?.ask ?? 0}`
|
|
1530
|
+
})] }),
|
|
1531
|
+
rulesBody.defaultAction !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1532
|
+
style: fieldLabelStyle,
|
|
1533
|
+
children: [t("card.rulesDefault"), " "]
|
|
1534
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1535
|
+
style: {
|
|
1536
|
+
font: "500 12px/18px var(--ds-font-family-code, monospace)",
|
|
1537
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1538
|
+
},
|
|
1539
|
+
children: rulesBody.defaultAction
|
|
1540
|
+
})] }),
|
|
1541
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1542
|
+
style: fieldLabelStyle,
|
|
1543
|
+
children: t("card.rulesStats").replace("%b", String(rulesBody.bytes ?? 0)).replace("%l", String(rulesBody.lines ?? 0))
|
|
1544
|
+
})
|
|
1545
|
+
]
|
|
1546
|
+
}),
|
|
1547
|
+
rulesBody.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1548
|
+
style: {
|
|
1549
|
+
...hintStyle,
|
|
1550
|
+
color: "var(--dsw-alias-label-error, #c0392b)"
|
|
1551
|
+
},
|
|
1552
|
+
children: rulesBody.error
|
|
1553
|
+
}),
|
|
1554
|
+
rulesBody.exists === false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1555
|
+
style: hintStyle,
|
|
1556
|
+
children: t("card.rulesMissing")
|
|
1557
|
+
}),
|
|
1558
|
+
rulesBody.truncated === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1559
|
+
style: hintStyle,
|
|
1560
|
+
children: t("card.rulesTruncated")
|
|
1561
|
+
}),
|
|
1562
|
+
(rulesBody.raw ?? "") !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1563
|
+
style: {
|
|
1564
|
+
margin: "6px 0 0",
|
|
1565
|
+
padding: "8px 10px",
|
|
1566
|
+
maxHeight: "260px",
|
|
1567
|
+
overflow: "auto",
|
|
1568
|
+
borderRadius: "6px",
|
|
1569
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1570
|
+
background: "var(--dsw-alias-bg-surface, transparent)",
|
|
1571
|
+
font: "500 12px/18px var(--ds-font-family-code, monospace)",
|
|
1572
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1573
|
+
whiteSpace: "pre"
|
|
1574
|
+
},
|
|
1575
|
+
children: rulesBody.raw
|
|
1576
|
+
}),
|
|
1577
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1578
|
+
style: {
|
|
1579
|
+
...hintStyle,
|
|
1580
|
+
margin: 0
|
|
1581
|
+
},
|
|
1582
|
+
children: t("card.rulesNote")
|
|
1583
|
+
})
|
|
1584
|
+
] })
|
|
1585
|
+
]
|
|
1586
|
+
}),
|
|
1118
1587
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1119
1588
|
style: sectionStyle,
|
|
1120
1589
|
children: [
|
|
@@ -1594,6 +2063,12 @@ window.__ModuleLoader__.load({
|
|
|
1594
2063
|
tag: "CANCELLED",
|
|
1595
2064
|
sticky: false
|
|
1596
2065
|
};
|
|
2066
|
+
case "stand-down": return {
|
|
2067
|
+
color: "var(--dsw-alias-state-warn-label, #b9770e)",
|
|
2068
|
+
bg: "var(--dsw-alias-state-warn-tertiary, rgba(185,119,14,0.14))",
|
|
2069
|
+
tag: "GATE OFF",
|
|
2070
|
+
sticky: true
|
|
2071
|
+
};
|
|
1597
2072
|
default: return {
|
|
1598
2073
|
color: "var(--dsw-alias-state-success-primary, #1e8449)",
|
|
1599
2074
|
bg: "var(--dsw-alias-state-success-tertiary, rgba(30,132,73,0.08))",
|
|
@@ -1623,7 +2098,9 @@ window.__ModuleLoader__.load({
|
|
|
1623
2098
|
"human-approved": "人工通过",
|
|
1624
2099
|
"human-rejected": "人工拒绝",
|
|
1625
2100
|
"human-cancelled": "人工取消",
|
|
1626
|
-
"no-approval-channel": "无审批通道"
|
|
2101
|
+
"no-approval-channel": "无审批通道",
|
|
2102
|
+
"preset-passthrough": "审批策略 never · 已放行",
|
|
2103
|
+
"stand-down": "门禁停用"
|
|
1627
2104
|
};
|
|
1628
2105
|
/** Compact wall-clock rendering of an ISO timestamp. */
|
|
1629
2106
|
function clockTime(iso) {
|
|
@@ -1879,7 +2356,8 @@ window.__ModuleLoader__.load({
|
|
|
1879
2356
|
learned: "history.tag.learned",
|
|
1880
2357
|
"manual-approved": "history.tag.manualApproved",
|
|
1881
2358
|
"manual-rejected": "history.tag.manualRejected",
|
|
1882
|
-
"manual-cancelled": "history.tag.manualCancelled"
|
|
2359
|
+
"manual-cancelled": "history.tag.manualCancelled",
|
|
2360
|
+
"stand-down": "history.tag.standDown"
|
|
1883
2361
|
};
|
|
1884
2362
|
/** The file name alone (chips show the basename, like the reference page). */
|
|
1885
2363
|
function basename(p) {
|