diff --git a/src/security/GuardTab.i18n.tsx b/src/security/GuardTab.i18n.tsx index bba1436..9794cad 100644 --- a/src/security/GuardTab.i18n.tsx +++ b/src/security/GuardTab.i18n.tsx @@ -34,8 +34,6 @@ export const guardTabText = messages( /** 组标题:「隐藏字符 · 内置 · 已启用 2 条,共 3 条」 */ builtin: "内置", groupCount: (on: number, all: number) => `已启用 ${on} 条,共 ${all} 条`, - /** 组标题的复选框 */ - groupToggle: (group: string) => `启用「${group}」中的全部规则`, /** 行内的动作选择,给读屏 */ actionFor: (name: string) => `「${name}」的动作`, /** 动作为「替换」时,行内选项写出换成什么 */ @@ -70,7 +68,6 @@ export const guardTabText = messages( action: "Action", builtin: "Built-in", groupCount: (on: number, all: number) => `${on} of ${all} on`, - groupToggle: (group: string) => `Turn on all rules in “${group}”`, actionFor: (name: string) => `Action for “${name}”`, replaceWith: (text: string) => `Replace with ${text}`, turnOn: "Turn on", diff --git a/src/security/GuardTab.tsx b/src/security/GuardTab.tsx index 079c84c..13be914 100644 --- a/src/security/GuardTab.tsx +++ b/src/security/GuardTab.tsx @@ -2,7 +2,6 @@ import { Fragment, useMemo, useState } from "react"; import { FlaskConicalIcon, PlusIcon, SearchIcon } from "lucide-react"; import { Badge } from "@/ui/badge"; import { Button } from "@/ui/button"; -import { Checkbox } from "@/ui/checkbox"; import { InputGroup, InputGroupAddon, InputGroupInput } from "@/ui/input-group"; import { rowMotion, usePresentList } from "@/ui/motion"; import { NativeSelect, NativeSelectOption } from "@/ui/native-select"; @@ -27,9 +26,7 @@ import { actionAllowed, actionOf, DEFAULT_REPLACEMENT, - groupChecked, groupRules, - groupTarget, ruleMatches, scopeChoice, scopeOf, @@ -43,8 +40,6 @@ const MODES: readonly GuardMode[] = ["enforce", "observe", "off"]; export interface RuleActions { mode: (mode: GuardMode) => void; toggle: (r: SecurityRuleView, enabled: boolean) => void; - /** 一组规则一起启用或停用(组标题的复选框) */ - bulk: (rules: SecurityRuleView[], enabled: boolean) => void; /** 行内改一条规则的动作 */ setAction: (r: SecurityRuleView, action: RuleAction) => void; /** 这条规则正在写(开关、动作) */ @@ -131,7 +126,7 @@ export function GuardSkeleton() { * 规则的实际处理按这个顺序定(规格 §4.3):防护关闭不检查;规则停用不检查;防护处于 * 「监控」时一律仅记录(动作列照写用户设的动作,后面标「监控中」);其余按规则的动作。 * - * 内置规则按类别分组,组标题上的复选框一次启用或停用这一组(部分启用时半选);自定义 + * 内置规则按类别分组,组标题只写名字和启用条数、没有开关;自定义 * 规则在最后一组。检索框按名字和写法筛。每一行的开关、动作就地改;点一行打开它的对话框。 */ export function GuardTab({ @@ -290,8 +285,6 @@ function RuleTable({ title={title} builtin={!g.custom && g.key !== "command"} rules={live} - pending={live.some(actions.pending)} - onToggle={(enabled) => actions.bulk(live, enabled)} /> {g.rows.map(({ item: r, key, presence }) => ( void; }) { const t = useText(guardTabText); const on = rules.filter((r) => r.enabled).length; return ( - - onToggle(groupTarget(rules))} - /> - - + {title} {builtin && ` · ${t.builtin}`} {` · ${t.groupCount(on, rules.length)}`} diff --git a/src/security/SecurityPage.i18n.tsx b/src/security/SecurityPage.i18n.tsx index 7b6d8b9..eb7c089 100644 --- a/src/security/SecurityPage.i18n.tsx +++ b/src/security/SecurityPage.i18n.tsx @@ -36,8 +36,6 @@ export const securityPageText = messages( /** 撤销提示 */ ruleOn: (name: string) => `已启用「${name}」`, ruleOff: (name: string) => `已停用「${name}」`, - groupOn: (n: number) => `已启用 ${n} 条规则`, - groupOff: (n: number) => `已停用 ${n} 条规则`, actionSet: (name: string, action: string) => `「${name}」的动作已改为「${action}」`, modeSet: (guard: string, mode: string) => `${guard}已切换为「${mode}」`, ruleCreated: (name: string) => `已创建规则「${name}」`, @@ -64,8 +62,6 @@ export const securityPageText = messages( loadFailed: "The security settings could not be loaded", ruleOn: (name: string) => `“${name}” turned on`, ruleOff: (name: string) => `“${name}” turned off`, - groupOn: (n: number) => (n === 1 ? "1 rule turned on" : `${n} rules turned on`), - groupOff: (n: number) => (n === 1 ? "1 rule turned off" : `${n} rules turned off`), actionSet: (name: string, action: string) => `Action for “${name}” set to ${action}`, modeSet: (guard: string, mode: string) => `${guard} set to ${mode}`, ruleCreated: (name: string) => `Rule “${name}” created`, diff --git a/src/security/SecurityPage.tsx b/src/security/SecurityPage.tsx index 793f4d3..50c752e 100644 --- a/src/security/SecurityPage.tsx +++ b/src/security/SecurityPage.tsx @@ -183,40 +183,6 @@ export default function SecurityPage({ }); } - /** - * 一组规则一起启用或停用(组标题的复选框)。只写状态要变的那几条,一次写配置;撤销把 - * 这几条拨回去 - */ - function bulk(guard: Guard, rules: SecurityRuleView[], enabled: boolean) { - const targets = rules.filter((r) => r.enabled !== enabled); - if (targets.length === 0) return; - const hit = (x: SecurityRuleView) => targets.some((r) => same(r, x)); - const send = (on: boolean) => - tracked( - targets.map((r) => ruleKey(guard, r)), - () => - write((base) => - api.setEnabled(guard, { - builtin: targets.filter((r) => !r.custom).map((r) => r.id), - custom: targets.filter((r) => r.custom).map((r) => r.id), - enabled: on, - base_version: base, - }), - ), - ); - void undoable({ - message: enabled ? t.groupOn(targets.length) : t.groupOff(targets.length), - apply: () => - detail.mutate((d) => ({ - ...d!, - [guard]: { ...d![guard], rules: d![guard].rules.map((x) => (hit(x) ? { ...x, enabled } : x)) }, - })), - do: () => send(enabled), - undo: () => send(!enabled), - after: settle, - }); - } - /** 行内改一条规则的动作。可撤销 */ function setAction(guard: Guard, r: SecurityRuleView, action: RuleAction) { const before = actionOf(guard, r); @@ -259,7 +225,6 @@ export default function SecurityPage({ const actions = (guard: Guard): RuleActions => ({ mode: (mode) => setMode(guard, mode), toggle: (r, enabled) => toggle(guard, r, enabled), - bulk: (rules, enabled) => bulk(guard, rules, enabled), setAction: (r, a) => setAction(guard, r, a), pending: (r) => busy(ruleKey(guard, r)), open: (r) => diff --git a/src/security/api.ts b/src/security/api.ts index e18d969..6288634 100644 --- a/src/security/api.ts +++ b/src/security/api.ts @@ -13,7 +13,6 @@ import type { Guard, GuardMode, RuleAction, - RulesToggle, SecurityEventsQuery, } from "@/types"; @@ -56,8 +55,6 @@ export const api = { /** 启用或停用一条内置规则 */ toggleBuiltin: (guard: Guard, id: string, enabled: boolean, baseVersion: string) => call("ToggleBuiltinRule", { enabled, base_version: baseVersion }, guard, id), - /** 一组规则一起启用或停用:一次写配置 */ - setEnabled: (guard: Guard, save: RulesToggle) => call("SetRulesEnabled", save, guard), /** 一条内置规则在「开启」档下做什么 */ setAction: (guard: Guard, id: string, action: RuleAction, baseVersion: string) => call("SetBuiltinRuleAction", { action, base_version: baseVersion }, guard, id), diff --git a/src/security/rules.test.ts b/src/security/rules.test.ts index 4e56b6d..8663835 100644 --- a/src/security/rules.test.ts +++ b/src/security/rules.test.ts @@ -4,9 +4,7 @@ import { actionAllowed, actionOf, adjustForScope, - groupChecked, groupRules, - groupTarget, matchAllowed, patternText, ruleMatches, @@ -44,20 +42,6 @@ describe("分组", () => { }); }); -describe("组标题的复选框", () => { - it("全部启用、部分启用(半选)、全部停用", () => { - expect(groupChecked([{ enabled: true }, { enabled: true }])).toBe(true); - expect(groupChecked([{ enabled: true }, { enabled: false }])).toBe("indeterminate"); - expect(groupChecked([{ enabled: false }])).toBe(false); - }); - - it("全部启用时点它全部停用,否则全部启用", () => { - expect(groupTarget([{ enabled: true }, { enabled: true }])).toBe(false); - expect(groupTarget([{ enabled: true }, { enabled: false }])).toBe(true); - expect(groupTarget([{ enabled: false }])).toBe(true); - }); -}); - describe("检索", () => { it("按界面上的名字、英文名、id 和写法找,不分大小写", () => { const r = rule({ diff --git a/src/security/rules.ts b/src/security/rules.ts index 7534bff..e081118 100644 --- a/src/security/rules.ts +++ b/src/security/rules.ts @@ -1,5 +1,5 @@ /** - * 规则表和规则对话框里不画东西的那部分:分组、检索、组标题复选框的状态、内容规则 + * 规则表和规则对话框里不画东西的那部分:分组、检索、内容规则 * 「检查对象」和「动作」「匹配方式」的联动。拆出来是为了能单独测。 */ import type { ContentMatch, ContentScope, Guard, OutcomeDetail, RuleAction, SecurityRuleView } from "@/types"; @@ -39,17 +39,6 @@ export function groupRules(rows: readonly T[], ruleOf: (row: T) => SecurityRu return out; } -/** 组标题复选框:全部启用、部分启用(半选)、全部停用 */ -export function groupChecked(rules: readonly Pick[]): boolean | "indeterminate" { - const on = rules.filter((r) => r.enabled).length; - return on === 0 ? false : on === rules.length ? true : "indeterminate"; -} - -/** 点组标题复选框之后要的状态:全部启用时全部停用,否则全部启用 */ -export function groupTarget(rules: readonly Pick[]): boolean { - return groupChecked(rules) !== true; -} - /** * 一条规则写的是什么,拿来检索:正则、要找的文字、码位、前缀、域名后缀。代码里做的检查 * 和没有写法的内置规则(PEM、JWT…)是空的,只按名字找得到 diff --git a/src/ui/native-select.tsx b/src/ui/native-select.tsx index 8855ec2..82de228 100644 --- a/src/ui/native-select.tsx +++ b/src/ui/native-select.tsx @@ -1,5 +1,6 @@ // 多了一档 `inline`,和 `Input`、`SelectTrigger` 的同名档配套:上游那张 -// 表一行要就地改六个字段,平时不显边框、鼠标移上去才显。 +// 表一行要就地改六个字段,平时不显边框、鼠标移上去才显。高 24px 不留上下内边距 +// (WebKit 会把 20px 的行切掉底),箭头贴右,不压最后一个字。 import * as React from "react" import { cn } from "@/lib/utils" @@ -26,10 +27,10 @@ function NativeSelect({