From 2e106265f9021c7336fb944ce09f03b20bd16066 Mon Sep 17 00:00:00 2001 From: MystiPanda Date: Sun, 30 Jun 2024 12:46:31 +0800 Subject: [PATCH] feat(unfinished): rules editor --- package.json | 3 + pnpm-lock.yaml | 34 +++ src/components/profile/profile-item.tsx | 6 +- .../profile/rules-editor-viewer.tsx | 276 ++++++++++++++++++ src/services/types.d.ts | 6 + 5 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 src/components/profile/rules-editor-viewer.tsx diff --git a/package.json b/package.json index ff987548..05a39539 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dayjs": "1.11.5", "foxact": "^0.2.35", "i18next": "^23.11.5", + "js-yaml": "^4.1.0", "lodash-es": "^4.17.21", "meta-json-schema": "1.18.5-alpha6", "monaco-editor": "^0.49.0", @@ -46,6 +47,7 @@ "react-hook-form": "^7.52.0", "react-i18next": "^13.5.0", "react-markdown": "^9.0.1", + "react-monaco-editor": "^0.55.0", "react-router-dom": "^6.23.1", "react-transition-group": "^4.4.5", "react-virtuoso": "^4.7.11", @@ -59,6 +61,7 @@ "@tauri-apps/cli": "^1.5.14", "@types/fs-extra": "^9.0.13", "@types/js-cookie": "^3.0.6", + "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6269b33..1ff6d870 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,6 +58,9 @@ importers: i18next: specifier: ^23.11.5 version: 23.11.5 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -91,6 +94,9 @@ importers: react-markdown: specifier: ^9.0.1 version: 9.0.1(@types/react@18.3.3)(react@18.3.1) + react-monaco-editor: + specifier: ^0.55.0 + version: 0.55.0(@types/react@18.3.3)(monaco-editor@0.49.0)(react@18.3.1) react-router-dom: specifier: ^6.23.1 version: 6.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -125,6 +131,9 @@ importers: "@types/js-cookie": specifier: ^3.0.6 version: 3.0.6 + "@types/js-yaml": + specifier: ^4.0.9 + version: 4.0.9 "@types/lodash-es": specifier: ^4.17.12 version: 4.17.12 @@ -2210,6 +2219,12 @@ packages: integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==, } + "@types/js-yaml@4.0.9": + resolution: + { + integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==, + } + "@types/json-schema@7.0.15": resolution: { @@ -3830,6 +3845,16 @@ packages: "@types/react": ">=18" react: ">=18" + react-monaco-editor@0.55.0: + resolution: + { + integrity: sha512-GdEP0Q3Rn1dczfKEEyY08Nes5plWwIYU4sWRBQO0+jsQWQsKMHKCC6+hPRwR7G/4aA3V/iU9jSmWPzVJYMVFSQ==, + } + peerDependencies: + "@types/react": ">=16 <= 18" + monaco-editor: ^0.44.0 + react: ">=16 <= 18" + react-refresh@0.14.2: resolution: { @@ -5941,6 +5966,8 @@ snapshots: "@types/js-cookie@3.0.6": {} + "@types/js-yaml@4.0.9": {} + "@types/json-schema@7.0.15": {} "@types/lodash-es@4.17.12": @@ -6973,6 +7000,13 @@ snapshots: transitivePeerDependencies: - supports-color + react-monaco-editor@0.55.0(@types/react@18.3.3)(monaco-editor@0.49.0)(react@18.3.1): + dependencies: + "@types/react": 18.3.3 + monaco-editor: 0.49.0 + prop-types: 15.8.1 + react: 18.3.1 + react-refresh@0.14.2: {} react-router-dom@6.23.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index 7d659f85..34f82034 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -19,10 +19,11 @@ import { RefreshRounded, DragIndicator } from "@mui/icons-material"; import { useLoadingCache, useSetLoadingCache } from "@/services/states"; import { updateProfile, viewProfile } from "@/services/cmds"; import { Notice } from "@/components/base"; +import { RulesEditorViewer } from "@/components/profile/rules-editor-viewer"; import { EditorViewer } from "@/components/profile/editor-viewer"; import { ProfileBox } from "./profile-box"; import parseTraffic from "@/utils/parse-traffic"; -import { ConfirmViewer } from "./confirm-viewer"; +import { ConfirmViewer } from "@/components/profile/confirm-viewer"; import { open } from "@tauri-apps/api/shell"; const round = keyframes` from { transform: rotate(0deg); } @@ -481,8 +482,7 @@ export const ProfileItem = (props: Props) => { onChange={onChange} onClose={() => setFileOpen(false)} /> - void; + onChange?: (prev?: string, curr?: string) => void; +} + +const RuleTypeList = [ + "DOMAIN", + "DOMAIN-SUFFIX", + "DOMAIN-KEYWORD", + "DOMAIN-REGEX", + "GEOSITE", + "IP-CIDR", + "IP-SUFFIX", + "IP-ASN", + "GEOIP", + "SRC-GEOIP", + "SRC-IP-ASN", + "SRC-IP-CIDR", + "SRC-IP-SUFFIX", + "DST-PORT", + "SRC-PORT", + "IN-PORT", + "IN-TYPE", + "IN-USER", + "IN-NAME", + "PROCESS-PATH", + "PROCESS-PATH-REGEX", + "PROCESS-NAME", + "PROCESS-NAME-REGEX", + "UID", + "NETWORK", + "DSCP", + "RULE-SET", + "SUB-RULE", + "MATCH", +]; + +export const RulesEditorViewer = (props: Props) => { + const { title, property, open, onClose, onChange } = props; + const { t } = useTranslation(); + + const editorRef = useRef(); // 编辑器实例 + const monacoRef = useRef(); // monaco 实例 + const monacoHoverProviderRef = useRef(); // monaco 注册缓存 + const monacoCompletionItemProviderRef = useRef(); // monaco 注册缓存 + + // 获取编辑器实例 + const editorDidMountHandle = useCallback( + (editor: monaco.editor.IStandaloneCodeEditor, monacoIns: typeof monaco) => { + editorRef.current = editor; + monacoRef.current = monacoIns; + }, + [] + ); + + const themeMode = useThemeMode(); + const [prevData, setPrevData] = useState(""); + const [currData, setCurrData] = useState(""); + const [method, setMethod] = useState("append"); + const [ruleType, setRuleType] = useState("DOMAIN"); + const [ruleContent, setRuleContent] = useState(""); + const [proxyPolicy, setProxyPolicy] = useState(""); + + const uri = monaco.Uri.parse(`${nanoid()}`); + const model = monaco.editor.createModel(prevData, "yaml", uri); + + const fetchContent = async () => { + let data = await readProfileFile(property); + setCurrData(data); + setPrevData(data); + }; + + const addSeq = async () => { + let obj = yaml.load(currData) as ISeqProfileConfig; + if (!obj.prepend) { + obj = { prepend: [], append: [], delete: [] }; + } + switch (method) { + case "append": { + obj.append.push(`${ruleType},${ruleContent},${proxyPolicy}`); + break; + } + case "prepend": { + obj.prepend.push(`${ruleType},${ruleContent},${proxyPolicy}`); + break; + } + case "delete": { + obj.delete.push(`${ruleType},${ruleContent},${proxyPolicy}`); + break; + } + } + let raw = yaml.dump(obj); + + await saveProfileFile(property, raw); + setCurrData(raw); + }; + + useEffect(() => { + fetchContent(); + }, []); + + useEffect(() => { + return () => { + if (editorRef.current) { + editorRef.current.dispose(); + } + monacoCompletionItemProviderRef.current?.dispose(); + monacoHoverProviderRef.current?.dispose(); + }; + }, [open]); + + const onSave = useLockFn(async () => { + try { + await saveProfileFile(property, currData); + onChange?.(prevData, currData); + onClose(); + } catch (err: any) { + Notice.error(err.message || err.toString()); + } + }); + + return ( + + {title ?? t("Edit File")} + + +
+ + + + + + + + { + if (v) setRuleType(v); + }} + renderInput={(params) => } + /> + + + + { + setRuleContent(e.target.value); + }} + /> + + + + { + setProxyPolicy(e.target.value); + }} + /> + + + +
+
+ +
+
+ + + + + + +
+ ); +}; + +const Item = styled(ListItem)(() => ({ + padding: "5px 2px", +})); diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 9cdc4cb1..b5fe3615 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -198,6 +198,12 @@ interface IVergeTestItem { url: string; } +interface ISeqProfileConfig { + prepend: string[]; + append: string[]; + delete: string[]; +} + interface IVergeConfig { app_log_level?: "trace" | "debug" | "info" | "warn" | "error" | string; language?: string;