diff --git a/.github/update.log b/.github/update.log index 7802b35a31..aadda73115 100644 --- a/.github/update.log +++ b/.github/update.log @@ -755,3 +755,4 @@ Update On Mon Sep 2 20:34:00 CEST 2024 Update On Tue Sep 3 20:32:43 CEST 2024 Update On Wed Sep 4 20:31:01 CEST 2024 Update On Thu Sep 5 20:35:23 CEST 2024 +Update On Fri Sep 6 20:34:58 CEST 2024 diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index 9e290f95ab..904b0a1fe4 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -1284,6 +1284,7 @@ dependencies = [ "parking_lot", "percent-encoding", "port_scanner", + "pretty_assertions", "rand 0.8.5", "redb", "regex", @@ -1963,6 +1964,12 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -5125,9 +5132,9 @@ checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" [[package]] name = "oxc_allocator" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b70e00e3c62b26ccefc5c5942b365091ebc398b4493625d34d54fabe9106cb" +checksum = "8f922944b51ca85c0acf47c37726a1e9475e5dd9f36c2ea89d1057f5c68f91ff" dependencies = [ "allocator-api2", "bumpalo", @@ -5135,23 +5142,24 @@ dependencies = [ [[package]] name = "oxc_ast" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65eda6aacccb2922fe506bff183930df82bf7a7217ef76480f82c35813a37a91" +checksum = "4385ef64890edde1135e5431fbe397cdc8f38bf7341d7e23429b5de09dd03897" dependencies = [ "bitflags 2.6.0", "num-bigint", "oxc_allocator", "oxc_ast_macros", + "oxc_regular_expression", "oxc_span", "oxc_syntax", ] [[package]] name = "oxc_ast_macros" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590d563fddfb8f75bcc2e8c34a6fb6d96bcce30e716b82ccb46c0a8600bc5cd2" +checksum = "807868208f9a594a88f6714dae60bc8bed4ccb87a5d3fd33ed946f6fc8a216de" dependencies = [ "proc-macro2", "quote", @@ -5160,9 +5168,9 @@ dependencies = [ [[package]] name = "oxc_diagnostics" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b23332f6e1781ec5d17c8c76756564331acf45bbed9a80cc07797ab6b29d24" +checksum = "bb283f8d9f7926c5ec4db85a65908ad72a3783110cc14771dbdec5ac81ad5d79" dependencies = [ "miette", "owo-colors", @@ -5172,15 +5180,15 @@ dependencies = [ [[package]] name = "oxc_index" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "582f984479fb15e680df6f42499e0c5d1aee8f7e38fd3716851a882357c4fce0" +checksum = "b15c56c7fe9c3d99df968c5d0b3129eb373228c09915e22fc91d24e80c262e0d" [[package]] name = "oxc_parser" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc8a633c442f6f828159e9bb326927ee8b13bbcf871c1bdd45223cea5d901d" +checksum = "e1ac96c09e7d0a33f25bfac70632eb3d8196e52b8e276ba5661c71da36f3d1ee" dependencies = [ "assert-unchecked", "bitflags 2.6.0", @@ -5199,11 +5207,12 @@ dependencies = [ [[package]] name = "oxc_regular_expression" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d74222e38ec5fa7b4065b899282ece8689fb804ce0daeb0a2e064b3215bebbc4" +checksum = "0d98c72fa996ba40322be6bd6c00e427036d9ffacbafad9347f08401e3553266" dependencies = [ "oxc_allocator", + "oxc_ast_macros", "oxc_diagnostics", "oxc_span", "phf 0.11.2", @@ -5213,9 +5222,9 @@ dependencies = [ [[package]] name = "oxc_span" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d6f98552a864d9c68173842bdffbd30e4ca0746778c308ebd839e70a1571f5" +checksum = "984cf0c05a0da6c557d7c7a600120841910d382008b92ac0ff44af400ba79e29" dependencies = [ "compact_str", "miette", @@ -5225,10 +5234,11 @@ dependencies = [ [[package]] name = "oxc_syntax" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce885718dd2744dd4e6500e765ea7fe4281d17ac0ec23c499a2ad747dbe1699" +checksum = "0343ef487214dbf9f296e155caeaab0d20a5da577ebd54a80f4fa4ce6a462f5a" dependencies = [ + "assert-unchecked", "bitflags 2.6.0", "dashmap 6.0.1", "nonmax", @@ -5680,6 +5690,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.22" @@ -9612,6 +9632,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yoke" version = "0.7.4" diff --git a/clash-nyanpasu/backend/tauri/Cargo.toml b/clash-nyanpasu/backend/tauri/Cargo.toml index 982ade489b..366198af9c 100644 --- a/clash-nyanpasu/backend/tauri/Cargo.toml +++ b/clash-nyanpasu/backend/tauri/Cargo.toml @@ -23,6 +23,7 @@ nyanpasu-ipc = { git = "https://github.com/LibNyanpasu/nyanpasu-service.git", fe ] } nyanpasu-utils = { git = "https://github.com/LibNyanpasu/nyanpasu-utils.git" } boa_utils = { path = "../boa_utils" } # should be removed when boa support console customize +pretty_assertions = "1.4.0" which = "6" anyhow = "1.0" dirs = "5.0.1" @@ -123,11 +124,11 @@ os_pipe = "1.2.0" whoami = "1.5.1" atomic_enum = "0.3.0" boa_engine.workspace = true -oxc_parser = "0.26" -oxc_allocator = "0.26" -oxc_span = "0.26" -oxc_ast = "0.26" -oxc_syntax = "0.26" +oxc_parser = "0.27" +oxc_allocator = "0.27" +oxc_span = "0.27" +oxc_ast = "0.27" +oxc_syntax = "0.27" mlua = { version = "0.9", features = [ "lua54", "async", diff --git a/clash-nyanpasu/backend/tauri/src/enhance/merge.rs b/clash-nyanpasu/backend/tauri/src/enhance/merge.rs index 85a69f7076..df370a5c46 100644 --- a/clash-nyanpasu/backend/tauri/src/enhance/merge.rs +++ b/clash-nyanpasu/backend/tauri/src/enhance/merge.rs @@ -1,5 +1,6 @@ use super::{runner::ProcessOutput, Logs, LogsExt}; use mlua::LuaSerdeExt; +use serde::de::DeserializeOwned; use serde_yaml::{Mapping, Value}; use tracing_attributes::instrument; @@ -58,6 +59,161 @@ fn merge_sequence(target: &mut Value, to_merge: &Value, append: bool) { } } +fn run_expr(logs: &mut Logs, item: &Value, expr: &str) -> Option { + let lua_runtime = match super::script::create_lua_context() { + Ok(lua) => lua, + Err(e) => { + logs.error(e.to_string()); + return None; + } + }; + let item = match lua_runtime.to_value(item) { + Ok(v) => v, + Err(e) => { + logs.error(format!("failed to convert item to lua value: {:#?}", e)); + return None; + } + }; + + if let Err(e) = lua_runtime.globals().set("item", item) { + logs.error(e.to_string()); + return None; + } + let res = lua_runtime.load(expr).eval::(); + match res { + Ok(v) => { + if let Ok(v) = lua_runtime.from_value(v) { + Some(v) + } else { + logs.error("failed to convert lua value to serde value"); + None + } + } + Err(e) => { + logs.error(format!("failed to run expr: {:#?}", e)); + None + } + } +} + +fn do_filter(logs: &mut Logs, config: &mut Value, field_str: &str, filter: &Value) { + let field = match find_field(config, field_str) { + Some(field) if !field.is_sequence() => { + logs.warn(format!("field is not sequence: {:#?}", field_str)); + return; + } + Some(field) => field, + None => { + logs.warn(format!("field not found: {:#?}", field_str)); + return; + } + }; + match filter { + Value::Sequence(filters) => { + for filter in filters { + do_filter(logs, config, field_str, filter); + } + } + Value::String(filter) => { + let list = field.as_sequence_mut().unwrap(); + list.retain(|item| run_expr(logs, item, filter).unwrap_or(false)); + } + Value::Mapping(filter) + if filter.get("when").is_some_and(|v| v.is_string()) + && filter.get("expr").is_some_and(|v| v.is_string()) => + { + let when = filter.get("when").unwrap().as_str().unwrap(); + let expr = filter.get("expr").unwrap().as_str().unwrap(); + let list = field.as_sequence_mut().unwrap(); + list.iter_mut().for_each(|item| { + let r#match = run_expr(logs, item, when); + if r#match.unwrap_or(false) { + let res: Option = run_expr(logs, item, expr); + if let Some(res) = res { + *item = res; + } + } + }); + } + Value::Mapping(filter) + if filter.get("when").is_some_and(|v| v.is_string()) + && filter.contains_key("override") => + { + let when = filter.get("when").unwrap().as_str().unwrap(); + let r#override = filter.get("override").unwrap(); + let list = field.as_sequence_mut().unwrap(); + list.iter_mut().for_each(|item| { + let r#match = run_expr(logs, item, when); + if r#match.unwrap_or(false) { + *item = r#override.clone(); + } + }); + } + Value::Mapping(filter) + if filter.get("when").is_some_and(|v| v.is_string()) + && filter.get("merge").is_some_and(|v| v.is_mapping()) => + { + let when = filter.get("when").unwrap().as_str().unwrap(); + let merge = filter.get("merge").unwrap().as_mapping().unwrap(); + let list = field.as_sequence_mut().unwrap(); + list.iter_mut().for_each(|item| { + let r#match = run_expr(logs, item, when); + if r#match.unwrap_or(false) { + for (key, value) in merge.iter() { + override_recursive(item.as_mapping_mut().unwrap(), key, value.clone()); + } + } + }); + } + + Value::Mapping(filter) + if filter.get("when").is_some_and(|v| v.is_string()) + && filter.get("remove").is_some_and(|v| v.is_sequence()) => + { + let when = filter.get("when").unwrap().as_str().unwrap(); + let remove = filter.get("remove").unwrap().as_sequence().unwrap(); + let list = field.as_sequence_mut().unwrap(); + list.iter_mut().for_each(|item| { + let r#match = run_expr(logs, item, when); + if r#match.unwrap_or(false) { + remove.iter().for_each(|key| { + if key.is_string() && item.is_mapping() { + let key_str = key.as_str().unwrap(); + // 对 key_str 做一下处理,跳过最后一个元素 + let mut keys = key_str.split('.').collect::>(); + let last_key = if keys.len() > 1 { + keys.pop().unwrap() + } else { + key_str + }; + let key_str = keys.join("."); + if let Some(field) = find_field(item, &key_str) { + field.as_mapping_mut().unwrap().remove(last_key); + } + } else { + match item { + Value::Sequence(list) if key.is_i64() => { + let index = key.as_i64().unwrap() as usize; + if index < list.len() { + list.remove(index); + } + } + _ => { + logs.warn(format!("invalid key: {:#?}", key)); + } + } + } + }); + } + }); + } + + _ => { + logs.warn(format!("invalid filter: {:#?}", filter)); + } + } +} + #[instrument(skip(merge, config))] pub fn use_merge(merge: Mapping, mut config: Mapping) -> ProcessOutput { tracing::trace!("original config: {:#?}", config); @@ -124,41 +280,7 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> ProcessOutput { } key_str if key_str.starts_with("filter__") => { let key_str = key_str.replace("filter__", ""); - if !value.is_string() { - logs.warn(format!("filter value is not string: {:#?}", key_str)); - continue; - } - let field = find_field(&mut map, &key_str); - match field { - Some(field) => { - if !field.is_sequence() { - logs.warn(format!("field is not sequence: {:#?}", key_str)); - continue; - } - let filter = value.as_str().unwrap_or_default(); - let lua = match super::script::create_lua_context() { - Ok(lua) => lua, - Err(e) => { - logs.error(e.to_string()); - continue; - } - }; - - let list = field.as_sequence_mut().unwrap(); - // apply filter to each item - list.retain(|item| { - let item = lua.to_value(item).unwrap(); - if let Err(e) = lua.globals().set("item", item) { - logs.error(e.to_string()); - return false; - } - lua.load(filter).eval::().unwrap_or(false) - }); - } - None => { - logs.warn(format!("field not found: {:#?}", key_str)); - } - } + do_filter(&mut logs, &mut map, &key_str, value); continue; } _ => { @@ -172,6 +294,7 @@ pub fn use_merge(merge: Mapping, mut config: Mapping) -> ProcessOutput { } mod tests { + use pretty_assertions::{assert_eq, assert_ne}; #[test] fn test_find_field() { let config = r" @@ -327,10 +450,10 @@ mod tests { } #[test] - fn test_filter() { + fn test_filter_string() { let merge = r" filter__proxies: | - item.type == 'ss' or item.type == 'hysteria2' + type(item) == 'table' and (item.type == 'ss' or item.type == 'hysteria2') filter__wow: | item == 'wow' "; @@ -423,6 +546,156 @@ mod tests { assert_eq!(result.unwrap(), expected); } + #[test] + fn test_filter_when_and_expr() { + let merge = r" + filter__proxies: + - when: | + type(item) == 'table' and (item.type == 'ss' or item.type == 'hysteria2') + expr: | + item + filter__proxy-groups: + - when: | + item.name == 'Spotify' + expr: | + item.icon = 'https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Spotify.png' + return item + "; + let config = r#"proxy-groups: +- name: Spotify + type: select + proxies: + - Proxies + - DIRECT + - HK + - JP + - SG + - TW + - US +- name: Steam + type: select + proxies: + - Proxies + - DIRECT + - HK + - JP + - SG + - TW + - US +- name: Telegram + type: select + proxies: + - Proxies + - HK + - JP + - SG + - TW + - US"#; + let expected = r#"proxy-groups: +- name: Spotify + icon: https://raw.githubusercontent.com/Koolson/Qure/master/IconSet/Color/Spotify.png + type: select + proxies: + - Proxies + - DIRECT + - HK + - JP + - SG + - TW + - US +- name: Steam + type: select + proxies: + - Proxies + - DIRECT + - HK + - JP + - SG + - TW + - US +- name: Telegram + type: select + proxies: + - Proxies + - HK + - JP + - SG + - TW + - US"#; + let merge = serde_yaml::from_str::(merge).unwrap(); + let config = serde_yaml::from_str::(config).unwrap(); + let (result, logs) = super::use_merge(merge, config); + eprintln!("{:#?}\n\n{:#?}", logs, result); + assert_eq!(logs.len(), 1); + let expected = serde_yaml::from_str::(expected).unwrap(); + assert_eq!(result.unwrap(), expected); + } + + #[test] + fn test_filter_when_and_override() { + let merge = r" + filter__proxies: + - when: | + type(item) == 'table' and (item.type == 'ss' or item.type == 'hysteria2') + override: OVERRIDDEN + "; + let config = r#" + proxies: + - 123 + - 555 + - name: "hysteria2" + type: hysteria2 + server: server.com + port: 443 + ports: 443-8443 + password: yourpassword + up: "30 Mbps" + down: "200 Mbps" + obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander + obfs-password: yourpassword + + sni: server.com + skip-cert-verify: false + fingerprint: xxxx + alpn: + - h3 + ca: "./my.ca" + ca-str: "xyz" + - name: "hysteria2" + type: ss + server: server.com + port: 443 + ports: 443-8443 + password: yourpassword + up: "30 Mbps" + down: "200 Mbps" + obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander + obfs-password: yourpassword + + sni: server.com + skip-cert-verify: false + fingerprint: xxxx + alpn: + - h3 + ca: "./my.ca" + ca-str: "xyz" + "#; + let expected = r#" + proxies: + - 123 + - 555 + - OVERRIDDEN + - OVERRIDDEN + "#; + let merge = serde_yaml::from_str::(merge).unwrap(); + let config = serde_yaml::from_str::(config).unwrap(); + let (result, logs) = super::use_merge(merge, config); + eprintln!("{:#?}\n\n{:#?}", logs, result); + assert_eq!(logs.len(), 0); + let expected = serde_yaml::from_str::(expected).unwrap(); + assert_eq!(result.unwrap(), expected); + } + #[test] fn test_override_recursive() { let merge = r" diff --git a/clash-nyanpasu/backend/tauri/src/enhance/script/js.rs b/clash-nyanpasu/backend/tauri/src/enhance/script/js.rs index 6bf0a1e0cb..1b0b7e2fa8 100644 --- a/clash-nyanpasu/backend/tauri/src/enhance/script/js.rs +++ b/clash-nyanpasu/backend/tauri/src/enhance/script/js.rs @@ -182,13 +182,14 @@ impl Runner for JSRunner { let boa_runner = wrap_result!(BoaRunner::try_new(), take_logs(logs)); wrap_result!(boa_runner.setup_console(logger), take_logs(logs)); let config = wrap_result!( - simd_json::serde::to_string_pretty(&mapping) + simd_json::serde::to_string(&mapping) .map_err(|e| { std::io::Error::new(std::io::ErrorKind::InvalidData, e) }), take_logs(logs) ); + let config = simd_json::to_string(&config).unwrap(); // escape the string let execute_module = format!( r#"import process from "./{hash}.mjs"; - let config = JSON.parse(`{config}`); + let config = JSON.parse({config}); export let result = JSON.stringify(await process(config)); "# ); diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index b72c6731b3..d0420d3286 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -26,7 +26,7 @@ "allotment": "1.20.2", "country-code-emoji": "2.3.0", "dayjs": "1.11.13", - "framer-motion": "12.0.0-alpha.0", + "framer-motion": "12.0.0-alpha.1", "i18next": "23.14.0", "jotai": "2.9.3", "material-react-table": "2.13.3", @@ -49,12 +49,15 @@ "@csstools/normalize.css": "12.1.1", "@emotion/babel-plugin": "11.12.0", "@emotion/react": "11.13.3", - "@iconify/json": "2.2.244", + "@iconify/json": "2.2.245", "@types/react": "18.3.5", "@types/react-dom": "18.3.0", "@vitejs/plugin-react": "4.3.1", "@vitejs/plugin-react-swc": "3.7.0", "clsx": "2.1.1", + "meta-json-schema": "github:libnyanpasu/meta-json-schema#main", + "monaco-yaml": "5.2.2", + "nanoid": "5.0.7", "sass": "1.78.0", "shiki": "1.16.2", "tailwindcss-textshadow": "2.1.3", diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/layout/animated-logo.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/layout/animated-logo.tsx index 231882d7b2..67fe7dbf5a 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/layout/animated-logo.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/layout/animated-logo.tsx @@ -5,6 +5,7 @@ import { classNames } from "@/utils"; import { useNyanpasu } from "@nyanpasu/interface"; import styles from "./animated-logo.module.scss"; +// @ts-expect-error framer-motion types is wrong const Logo = motion(LogoSvg); const transition = { @@ -45,21 +46,21 @@ const motionVariants: { [name: string]: Variants } = { export default function AnimatedLogo({ className, style, - disbaleMotion, + disableMotion, }: { className?: string; style?: CSSProperties; - disbaleMotion?: boolean; + disableMotion?: boolean; }) { const { nyanpasuConfig } = useNyanpasu(); - const disbale = disbaleMotion ?? nyanpasuConfig?.lighten_animation_effects; + const disable = disableMotion ?? nyanpasuConfig?.lighten_animation_effects; return ( (null); - const monacoeditorRef = useRef(null); + const monacoEditorRef = useRef(null); const instanceRef = useRef(null); useEffect(() => { const run = async () => { const { monaco } = await import("@/services/monaco"); - monacoeditorRef.current = monaco; + monacoEditorRef.current = monaco; if (!monacoRef.current) { return; } instanceRef.current = monaco.editor.create(monacoRef.current, { - value, - language, + readOnly: readonly, + renderValidationDecorations: "on", theme: mode === "light" ? "vs" : "vs-dark", + tabSize: language === "yaml" ? 2 : 4, minimap: { enabled: false }, automaticLayout: true, + fontLigatures: true, + smoothScrolling: true, + fontFamily: `'Cascadia Code NF', 'Cascadia Code', Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${ + OS === "windows" ? ", twemoji mozilla" : "" + }`, + quickSuggestions: { + strings: true, + comments: true, + other: true, + }, }); + const uri = monaco.Uri.parse( + `${nanoid()}.${!!schemaType ? `${schemaType}.` : ""}.${language}`, + ); + const model = monaco.editor.createModel(value || "", language, uri); + instanceRef.current.setModel(model); }; if (open) { run().catch(console.error); @@ -50,7 +77,7 @@ export const ProfileMonacoView = forwardRef(function ProfileMonacoView( return () => { instanceRef.current?.dispose(); }; - }, [language, mode, open, value]); + }, [language, mode, open, readonly, schemaType, value]); useImperativeHandle(ref, () => ({ getValue: () => instanceRef.current?.getValue(), @@ -63,7 +90,7 @@ export const ProfileMonacoView = forwardRef(function ProfileMonacoView( return; } - monacoeditorRef.current?.editor.setModelLanguage(model, language); + monacoEditorRef.current?.editor.setModelLanguage(model, language); }, [language]); useUpdateEffect(() => { diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx index e5cf929bb0..8e63543605 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/profiles/script-dialog.tsx @@ -222,6 +222,9 @@ export const ScriptDialog = ({ open={openMonaco} value={editor.value} language={editor.language} + schemaType={ + editor.rawType === Profile.Type.Merge ? "merge" : undefined + } /> diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/service-manual-prompt-dialog.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/service-manual-prompt-dialog.tsx index 1ee6c9e167..7389c092d4 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/service-manual-prompt-dialog.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/service-manual-prompt-dialog.tsx @@ -1,27 +1,31 @@ import { useAsyncEffect } from "ahooks"; -import { useAtom } from "jotai"; +import { useAtom, useSetAtom } from "jotai"; import { useState } from "react"; import useSWR from "swr"; +import { OS } from "@/consts"; import { serviceManualPromptDialogAtom } from "@/store/service"; import { getShikiSingleton } from "@/utils/shiki"; import { getServiceInstallPrompt } from "@nyanpasu/interface"; import { BaseDialog, BaseDialogProps } from "@nyanpasu/ui"; -export type ServerManualPromptDialogProps = Omit; +export type ServerManualPromptDialogProps = Omit & { + operation: "uninstall" | "install" | "start" | "stop" | null; +}; // TODO: maybe support more commands prompt? export default function ServerManualPromptDialog({ open, onClose, + operation, ...props }: ServerManualPromptDialogProps) { - const { data: serviceInstallPrompt } = useSWR( - "/service_install_prompt", + const { data: serviceInstallPrompt, error } = useSWR( + operation === "install" ? "/service_install_prompt" : null, getServiceInstallPrompt, ); const [codes, setCodes] = useState(null); useAsyncEffect(async () => { - if (serviceInstallPrompt) { + if (operation === "install" && serviceInstallPrompt) { const shiki = await getShikiSingleton(); const code = await shiki.codeToHtml(serviceInstallPrompt, { lang: "shell", @@ -31,17 +35,36 @@ export default function ServerManualPromptDialog({ }, }); setCodes(code); + } else if (!!operation) { + const shiki = await getShikiSingleton(); + const code = await shiki.codeToHtml( + `${OS !== "windows" ? "sudo " : ""}./nyanpasu-service ${operation}`, + { + lang: "shell", + themes: { + dark: "nord", + light: "min-light", + }, + }, + ); + setCodes(code); } - }, [serviceInstallPrompt]); + }, [serviceInstallPrompt, operation, setCodes]); return ( - +

Unable to install service automatically. Please open a PowerShell(as administrator) in Windows or a terminal emulator in macOS, Linux and run the following commands:

+ {error &&

{error.message}

} {!!codes && (
setOpen(false)} /> + setPrompt(null)} + operation={prompt} + /> ); } export function useServerManualPromptDialog() { - const [, setOpen] = useAtom(serviceManualPromptDialogAtom); + const setPrompt = useSetAtom(serviceManualPromptDialogAtom); return { - show: () => setOpen(true), - close: () => setOpen(false), + show: (prompt: "install" | "uninstall" | "stop" | "start") => + setPrompt(prompt), + close: () => setPrompt(null), }; } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-system-service.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-system-service.tsx index c09bd534e2..4124d64f4f 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-system-service.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/setting-system-service.tsx @@ -67,19 +67,20 @@ export const SettingSystemService = () => { } await restartSidecar(); } catch (e) { - const errorMessage = + const errorMessage = `${ getServiceStatus.data === "not_installed" ? "Install failed" - : "Uninstall failed"; + : "Uninstall failed" + }: ${formatError(e)}`; message(errorMessage, { type: "error", title: t("Error"), }); // If install failed show a prompt to user to install the service manually - if (getServiceStatus.data === "not_installed") { - promptDialog.show(); - } + promptDialog.show( + getServiceStatus.data === "not_installed" ? "install" : "uninstall", + ); } }); }); @@ -111,6 +112,10 @@ export const SettingSystemService = () => { type: "error", title: t("Error"), }); + // If start failed show a prompt to user to start the service manually + promptDialog.show( + getServiceStatus.data === "running" ? "stop" : "start", + ); } }); }); diff --git a/clash-nyanpasu/frontend/nyanpasu/src/services/monaco.ts b/clash-nyanpasu/frontend/nyanpasu/src/services/monaco.ts index 1249f011f8..64e183d0fb 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/services/monaco.ts +++ b/clash-nyanpasu/frontend/nyanpasu/src/services/monaco.ts @@ -1,3 +1,6 @@ +import nyanpasuMergeSchema from "meta-json-schema/schemas/clash-nyanpasu-merge-json-schema.json"; +import clashMetaSchema from "meta-json-schema/schemas/meta-json-schema.json"; +import { configureMonacoYaml } from "monaco-yaml"; // features // langs import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js"; @@ -14,4 +17,21 @@ monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ allowJs: true, }); +configureMonacoYaml(monaco, { + validate: true, + enableSchemaRequest: true, + schemas: [ + { + fileMatch: ["**/*.clash.yaml"], + // @ts-expect-error monaco-yaml parse issue + schema: clashMetaSchema, + }, + { + fileMatch: ["**/*.merge.yaml"], + // @ts-expect-error monaco-yaml parse issue + schema: nyanpasuMergeSchema, + }, + ], +}); + export { monaco }; diff --git a/clash-nyanpasu/frontend/nyanpasu/src/store/service.ts b/clash-nyanpasu/frontend/nyanpasu/src/store/service.ts index 68a9aae06f..6f0befd8cf 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/store/service.ts +++ b/clash-nyanpasu/frontend/nyanpasu/src/store/service.ts @@ -1,3 +1,5 @@ import { atom } from "jotai"; -export const serviceManualPromptDialogAtom = atom(false); +export const serviceManualPromptDialogAtom = atom< + "install" | "uninstall" | "start" | "stop" | null +>(null); diff --git a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts index 4871951ef9..f184835405 100644 --- a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts +++ b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts @@ -68,7 +68,15 @@ export default defineConfig(({ command }) => { }), generouted(), sassDts({ esmExport: true }), - monaco({ languageWorkers: ["editorWorkerService", "typescript"] }), + monaco({ + languageWorkers: ["editorWorkerService", "typescript"], + customWorkers: [ + { + label: "yaml", + entry: "monaco-yaml/yaml.worker", + }, + ], + }), isDev && devtools(), ], resolve: { diff --git a/clash-nyanpasu/frontend/ui/package.json b/clash-nyanpasu/frontend/ui/package.json index 372c657fd7..f88fed5072 100644 --- a/clash-nyanpasu/frontend/ui/package.json +++ b/clash-nyanpasu/frontend/ui/package.json @@ -28,7 +28,7 @@ "@vitejs/plugin-react": "4.3.1", "ahooks": "3.8.1", "d3": "7.9.0", - "framer-motion": "12.0.0-alpha.0", + "framer-motion": "12.0.0-alpha.1", "react": "18.3.1", "react-error-boundary": "4.0.13", "react-i18next": "15.0.1", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 5f6ef7530b..1c68a73973 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -3,7 +3,7 @@ "latest": { "mihomo": "v1.18.8", "mihomo_alpha": "alpha-faaa90f", - "clash_rs": "v0.3.0", + "clash_rs": "v0.3.1", "clash_premium": "2023-09-05-gdcc8d87" }, "arch_template": { @@ -36,5 +36,5 @@ "darwin-x64": "clash-darwin-amd64-n{}.gz" } }, - "updated_at": "2024-09-03T22:20:35.876Z" + "updated_at": "2024-09-05T22:20:27.332Z" } diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 3ad94f779d..fe2fb4954d 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -237,8 +237,8 @@ importers: specifier: 1.11.13 version: 1.11.13 framer-motion: - specifier: 12.0.0-alpha.0 - version: 12.0.0-alpha.0(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807) + specifier: 12.0.0-alpha.1 + version: 12.0.0-alpha.1(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807) i18next: specifier: 23.14.0 version: 23.14.0 @@ -301,8 +301,8 @@ importers: specifier: 11.13.3 version: 11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1) '@iconify/json': - specifier: 2.2.244 - version: 2.2.244 + specifier: 2.2.245 + version: 2.2.245 '@types/react': specifier: npm:types-react@rc version: types-react@19.0.0-rc.1 @@ -318,6 +318,15 @@ importers: clsx: specifier: 2.1.1 version: 2.1.1 + meta-json-schema: + specifier: github:libnyanpasu/meta-json-schema#main + version: https://codeload.github.com/libnyanpasu/meta-json-schema/tar.gz/b4b29ee93facde4ca7da6e9c4e0cba50c3c1e074 + monaco-yaml: + specifier: 5.2.2 + version: 5.2.2(monaco-editor@0.51.0) + nanoid: + specifier: 5.0.7 + version: 5.0.7 sass: specifier: 1.78.0 version: 1.78.0 @@ -388,8 +397,8 @@ importers: specifier: 7.9.0 version: 7.9.0 framer-motion: - specifier: 12.0.0-alpha.0 - version: 12.0.0-alpha.0(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807) + specifier: 12.0.0-alpha.1 + version: 12.0.0-alpha.1(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807) react: specifier: npm:react@rc version: 19.0.0-rc-e948a5ac-20240807 @@ -526,20 +535,24 @@ packages: resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.25.4': + resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} + engines: {node: '>=6.9.0'} + '@babel/core@7.24.5': resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} engines: {node: '>=6.9.0'} + '@babel/core@7.25.2': + resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.17.7': resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} engines: {node: '>=6.9.0'} - '@babel/generator@7.24.5': - resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.25.0': - resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} + '@babel/generator@7.25.6': + resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.24.7': @@ -550,8 +563,12 @@ packages: resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.25.0': - resolution: {integrity: sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==} + '@babel/helper-compilation-targets@7.25.2': + resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.25.4': + resolution: {integrity: sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -560,12 +577,16 @@ packages: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} engines: {node: '>=6.9.0'} - '@babel/helper-function-name@7.23.0': - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} engines: {node: '>=6.9.0'} - '@babel/helper-hoist-variables@7.22.5': - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + '@babel/helper-function-name@7.24.7': + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.24.7': + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} engines: {node: '>=6.9.0'} '@babel/helper-member-expression-to-functions@7.24.8': @@ -576,12 +597,22 @@ packages: resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.24.7': + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.24.5': resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.25.2': + resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.24.7': resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} engines: {node: '>=6.9.0'} @@ -590,6 +621,10 @@ packages: resolution: {integrity: sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.24.8': + resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} + engines: {node: '>=6.9.0'} + '@babel/helper-replace-supers@7.25.0': resolution: {integrity: sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==} engines: {node: '>=6.9.0'} @@ -600,6 +635,10 @@ packages: resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==} engines: {node: '>=6.9.0'} + '@babel/helper-simple-access@7.24.7': + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} + '@babel/helper-skip-transparent-expression-wrappers@7.24.7': resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} engines: {node: '>=6.9.0'} @@ -608,18 +647,14 @@ packages: resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.1': - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.24.8': resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.5': - resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} @@ -628,10 +663,18 @@ packages: resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.8': + resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.24.5': resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.25.6': + resolution: {integrity: sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.24.5': resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} engines: {node: '>=6.9.0'} @@ -640,13 +683,8 @@ packages: resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.24.5': - resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/parser@7.25.3': - resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} + '@babel/parser@7.25.6': + resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} engines: {node: '>=6.0.0'} hasBin: true @@ -677,6 +715,10 @@ packages: resolution: {integrity: sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.25.6': + resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} + engines: {node: '>=6.9.0'} + '@babel/template@7.24.0': resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} engines: {node: '>=6.9.0'} @@ -689,24 +731,16 @@ packages: resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.24.5': - resolution: {integrity: sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.25.3': - resolution: {integrity: sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==} + '@babel/traverse@7.25.6': + resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} engines: {node: '>=6.9.0'} '@babel/types@7.17.0': resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} engines: {node: '>=6.9.0'} - '@babel/types@7.24.5': - resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.25.2': - resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} + '@babel/types@7.25.6': + resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} '@commitlint/cli@19.4.1': @@ -912,8 +946,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.0': - resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -930,8 +964,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.0': - resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -948,8 +982,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.0': - resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -966,8 +1000,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.0': - resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -984,8 +1018,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.0': - resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -1002,8 +1036,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.0': - resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -1020,8 +1054,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.0': - resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -1038,8 +1072,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.0': - resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -1056,8 +1090,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.0': - resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -1074,8 +1108,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.0': - resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -1092,8 +1126,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.0': - resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -1110,8 +1144,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.0': - resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -1128,8 +1162,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.0': - resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -1146,8 +1180,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.0': - resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -1164,8 +1198,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.0': - resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1182,8 +1216,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.0': - resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1200,8 +1234,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.0': - resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -1218,14 +1252,14 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.0': - resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.23.0': - resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1242,8 +1276,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.0': - resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -1260,8 +1294,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.0': - resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1278,8 +1312,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.0': - resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1296,8 +1330,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.0': - resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1314,8 +1348,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.0': - resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1326,8 +1360,8 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.10.0': - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + '@eslint-community/regexpp@4.11.0': + resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/eslintrc@2.1.4': @@ -1389,8 +1423,8 @@ packages: '@vue/compiler-sfc': optional: true - '@iconify/json@2.2.244': - resolution: {integrity: sha512-l5wLZMzsyOo1M7aptt5m6Oyh3FMyQvmsEXmca+iojhEt/9V9DkH4rLWsinCqMC+QU401YlwXJx4mesK4QDncNQ==} + '@iconify/json@2.2.245': + resolution: {integrity: sha512-JbruddbGKghBe6fE1mzuo5hhUkisIW4mAdQGAyx0Q6sI52ukeQJHakolc2RQD/yWC3xp7rARNXMzWSXJynJ1vw==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -2476,8 +2510,8 @@ packages: '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} - '@types/lodash@4.17.1': - resolution: {integrity: sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==} + '@types/lodash@4.17.7': + resolution: {integrity: sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==} '@types/mdast@4.0.3': resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} @@ -2485,8 +2519,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@16.18.97': - resolution: {integrity: sha512-4muilE1Lbfn57unR+/nT9AFjWk0MtWi5muwCEJqnOvfRQDbSfLCUdN7vCIg8TYuaANfhLOV85ve+FNpiUsbSRg==} + '@types/node@16.18.108': + resolution: {integrity: sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==} '@types/node@22.5.0': resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==} @@ -2509,6 +2543,9 @@ packages: '@types/react-transition-group@4.4.10': resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==} + '@types/react-transition-group@4.4.11': + resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} + '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -2645,11 +2682,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.12.1: resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} engines: {node: '>=0.4.0'} @@ -2702,6 +2734,9 @@ packages: ajv@8.13.0: resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + allotment@1.20.2: resolution: {integrity: sha512-TaCuHfYNcsJS9EPk04M7TlG5Rl3vbAdHeAyrTE9D5vbpzV+wxnRoUrulDbfnzaQcPIZKpHJNixDOoZNuzliKEA==} peerDependencies: @@ -2918,11 +2953,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001616: - resolution: {integrity: sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==} - - caniuse-lite@1.0.30001646: - resolution: {integrity: sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==} + caniuse-lite@1.0.30001658: + resolution: {integrity: sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==} capture-stack-trace@1.0.2: resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==} @@ -3382,6 +3414,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} @@ -3521,16 +3562,16 @@ packages: electron-to-chromium@1.4.758: resolution: {integrity: sha512-/o9x6TCdrYZBMdGeTifAP3wlF/gVT+TtWJe3BSmtNh92Mw81U9hrYwW9OAGUh+sEOX/yz5e34sksqRruZbjYrw==} - electron-to-chromium@1.5.4: - resolution: {integrity: sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==} + electron-to-chromium@1.5.16: + resolution: {integrity: sha512-2gQpi2WYobXmz2q23FrOBYTLcI1O/P4heW3eqX+ldmPVDQELRqhiebV380EhlGG12NtnX1qbK/FHpN0ba+7bLA==} electron@23.3.13: resolution: {integrity: sha512-BaXtHEb+KYKLouUXlUVDa/lj9pj4F5kiE0kwFdJV84Y2EU7euIDgPthfKtchhr5MVHmjtavRMIV/zAwEiSQ9rQ==} engines: {node: '>= 12.20.55'} hasBin: true - emoji-regex@10.3.0: - resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3625,13 +3666,13 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.23.0: - resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} hasBin: true - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-string-regexp@1.0.5: @@ -3646,8 +3687,8 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-compat-utils@0.5.0: - resolution: {integrity: sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==} + eslint-compat-utils@0.5.1: + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} engines: {node: '>=12'} peerDependencies: eslint: '>=6.0.0' @@ -3676,8 +3717,8 @@ packages: eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - eslint-module-utils@2.8.1: - resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} + eslint-module-utils@2.11.0: + resolution: {integrity: sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -3697,8 +3738,8 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-es-x@7.6.0: - resolution: {integrity: sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==} + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '>=8' @@ -3782,8 +3823,8 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -3855,6 +3896,9 @@ packages: fast-shallow-equal@1.0.0: resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} + fast-uri@3.0.1: + resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} + fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -3881,8 +3925,8 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} - file-entry-cache@9.0.0: - resolution: {integrity: sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==} + file-entry-cache@9.1.0: + resolution: {integrity: sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==} engines: {node: '>=18'} fill-range@7.1.1: @@ -3914,8 +3958,8 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} formdata-polyfill@4.0.10: @@ -3925,8 +3969,8 @@ packages: fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - framer-motion@12.0.0-alpha.0: - resolution: {integrity: sha512-rw0+O0kqsSFPrYrSnHXrZTGAKteCj9D9HzDdHjJR8GyPWADLoQstgUYeSvLxrO3P+HlBfQtmu/SOsM9eLhKXtQ==} + framer-motion@12.0.0-alpha.1: + resolution: {integrity: sha512-WpMrDfk6I5Q4T/7+LEjQOVbAD5Yb/cGbbV+LLllFEg+dHi8XZ7QecJ9aYS9bn12cWuF7gGy+uqskyAkGTWHs3w==} peerDependencies: '@emotion/is-prop-valid': '*' react: npm:react@rc @@ -4010,8 +4054,8 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + get-tsconfig@4.8.0: + resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==} git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} @@ -4026,9 +4070,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.3.12: - resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} - engines: {node: '>=16 || 14 >=14.17'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true glob@7.2.3: @@ -4213,10 +4256,6 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4342,8 +4381,9 @@ packages: resolution: {integrity: sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==} hasBin: true - is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} is-data-view@1.0.1: resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} @@ -4516,9 +4556,8 @@ packages: iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} - jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} javascript-natural-sort@0.7.1: resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} @@ -4598,6 +4637,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -4749,6 +4791,9 @@ packages: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -4838,6 +4883,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + meta-json-schema@https://codeload.github.com/libnyanpasu/meta-json-schema/tar.gz/b4b29ee93facde4ca7da6e9c4e0cba50c3c1e074: + resolution: {tarball: https://codeload.github.com/libnyanpasu/meta-json-schema/tar.gz/b4b29ee93facde4ca7da6e9c4e0cba50c3c1e074} + version: 1.18.7 + micromark-core-commonmark@2.0.1: resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} @@ -4901,10 +4950,6 @@ packages: micromark@4.0.0: resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} - engines: {node: '>=8.6'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -4945,10 +4990,6 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -4975,6 +5016,25 @@ packages: monaco-editor@0.51.0: resolution: {integrity: sha512-xaGwVV1fq343cM7aOYB6lVE4Ugf0UyimdD/x5PWcWBMKENwectaEu77FAN7c5sFiyumqeJdX1RPTh1ocioyDjw==} + monaco-languageserver-types@0.4.0: + resolution: {integrity: sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==} + + monaco-marker-data-provider@1.2.3: + resolution: {integrity: sha512-BOiQs9UNEwVrF1rwYI32HUP8D7JTuHlJRlykx83e4+jfh1ceBWIBfB5ENDVSFUz651d95kxjKj36vV2JO3zr9w==} + + monaco-types@0.1.0: + resolution: {integrity: sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ==} + + monaco-worker-manager@2.0.1: + resolution: {integrity: sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==} + peerDependencies: + monaco-editor: '>=0.30.0' + + monaco-yaml@5.2.2: + resolution: {integrity: sha512-NWO/UhtJATlIsqwWPzK7YfbcIvPo3riFGsUkaGxNJoGiNPOvHD8vZ83ecqMQGkHPOpgHtSbe94uokE1AJvpbyQ==} + peerDependencies: + monaco-editor: '>=0.36' + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -5014,6 +5074,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.0.7: + resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==} + engines: {node: ^18 || >=20} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -5107,8 +5172,9 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -5196,6 +5262,9 @@ packages: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json@4.0.1: resolution: {integrity: sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==} engines: {node: '>=4'} @@ -5258,9 +5327,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.10.2: - resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} - engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -5390,8 +5459,8 @@ packages: postcss-nested@4.2.3: resolution: {integrity: sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==} - postcss-nested@6.0.1: - resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 @@ -5421,14 +5490,6 @@ packages: resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} engines: {node: '>=4'} - postcss-selector-parser@6.1.0: - resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} - engines: {node: '>=4'} - - postcss-selector-parser@6.1.1: - resolution: {integrity: sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==} - engines: {node: '>=4'} - postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} @@ -5541,6 +5602,11 @@ packages: peerDependencies: prettier: ^3.0.3 + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + prettier@3.3.3: resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} engines: {node: '>=14'} @@ -5885,11 +5951,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.6.1: - resolution: {integrity: sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==} - engines: {node: '>=10'} - hasBin: true - semver@7.6.3: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} @@ -6048,8 +6109,8 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string-width@7.1.0: - resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} string.prototype.matchall@4.0.11: @@ -6342,6 +6403,9 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tsx@4.19.0: resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} engines: {node: '>=18.0.0'} @@ -6412,8 +6476,8 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - undici-types@6.19.6: - resolution: {integrity: sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -6651,6 +6715,19 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} @@ -6681,8 +6758,8 @@ packages: which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - which-builtin-type@1.1.3: - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + which-builtin-type@1.1.4: + resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} engines: {node: '>= 0.4'} which-collection@1.0.2: @@ -6735,8 +6812,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@7.5.9: - resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} engines: {node: '>=8.3.0'} peerDependencies: bufferutil: ^4.0.1 @@ -6785,6 +6862,11 @@ packages: engines: {node: '>= 14'} hasBin: true + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + engines: {node: '>= 14'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -6800,8 +6882,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + yocto-queue@1.1.1: + resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} zod-validation-error@3.3.1: @@ -6863,20 +6945,42 @@ snapshots: '@babel/compat-data@7.24.4': {} + '@babel/compat-data@7.25.4': {} + '@babel/core@7.24.5': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.5 + '@babel/generator': 7.25.6 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) '@babel/helpers': 7.24.5 - '@babel/parser': 7.24.5 + '@babel/parser': 7.25.6 '@babel/template': 7.24.0 - '@babel/traverse': 7.24.5 - '@babel/types': 7.24.5 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 convert-source-map: 2.0.0 - debug: 4.3.6 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.25.2': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.25.6 + '@babel/helper-compilation-targets': 7.25.2 + '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) + '@babel/helpers': 7.25.6 + '@babel/parser': 7.25.6 + '@babel/template': 7.25.0 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 + convert-source-map: 2.0.0 + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6885,28 +6989,21 @@ snapshots: '@babel/generator@7.17.7': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 jsesc: 2.5.2 source-map: 0.5.7 optional: true - '@babel/generator@7.24.5': + '@babel/generator@7.25.6': dependencies: - '@babel/types': 7.24.5 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - - '@babel/generator@7.25.0': - dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 '@babel/helper-annotate-as-pure@7.24.7': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 '@babel/helper-compilation-targets@7.23.6': dependencies: @@ -6916,40 +7013,62 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.25.0(@babel/core@7.24.5)': + '@babel/helper-compilation-targets@7.25.2': dependencies: - '@babel/core': 7.24.5 + '@babel/compat-data': 7.25.4 + '@babel/helper-validator-option': 7.24.8 + browserslist: 4.23.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.25.4(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.24.7 '@babel/helper-member-expression-to-functions': 7.24.8 '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.25.0(@babel/core@7.24.5) + '@babel/helper-replace-supers': 7.25.0(@babel/core@7.25.2) '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/traverse': 7.25.3 + '@babel/traverse': 7.25.6 semver: 6.3.1 transitivePeerDependencies: - supports-color '@babel/helper-environment-visitor@7.22.20': {} - '@babel/helper-function-name@7.23.0': + '@babel/helper-environment-visitor@7.24.7': dependencies: - '@babel/template': 7.24.0 - '@babel/types': 7.24.5 + '@babel/types': 7.25.6 + optional: true - '@babel/helper-hoist-variables@7.22.5': + '@babel/helper-function-name@7.24.7': dependencies: - '@babel/types': 7.24.5 + '@babel/template': 7.25.0 + '@babel/types': 7.25.6 + optional: true + + '@babel/helper-hoist-variables@7.24.7': + dependencies: + '@babel/types': 7.25.6 + optional: true '@babel/helper-member-expression-to-functions@7.24.8': dependencies: - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.2 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.24.3': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 + transitivePeerDependencies: + - supports-color '@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5)': dependencies: @@ -6958,59 +7077,86 @@ snapshots: '@babel/helper-module-imports': 7.24.3 '@babel/helper-simple-access': 7.24.5 '@babel/helper-split-export-declaration': 7.24.5 - '@babel/helper-validator-identifier': 7.24.5 + '@babel/helper-validator-identifier': 7.24.7 + + '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + '@babel/traverse': 7.25.6 + transitivePeerDependencies: + - supports-color '@babel/helper-optimise-call-expression@7.24.7': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 '@babel/helper-plugin-utils@7.24.5': {} - '@babel/helper-replace-supers@7.25.0(@babel/core@7.24.5)': + '@babel/helper-plugin-utils@7.24.8': {} + + '@babel/helper-replace-supers@7.25.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 '@babel/helper-member-expression-to-functions': 7.24.8 '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/traverse': 7.25.3 + '@babel/traverse': 7.25.6 transitivePeerDependencies: - supports-color '@babel/helper-simple-access@7.24.5': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 + transitivePeerDependencies: + - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.24.7': dependencies: - '@babel/traverse': 7.25.3 - '@babel/types': 7.25.2 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 transitivePeerDependencies: - supports-color '@babel/helper-split-export-declaration@7.24.5': dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.25.6 - '@babel/helper-string-parser@7.24.1': {} + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.25.6 + optional: true '@babel/helper-string-parser@7.24.8': {} - '@babel/helper-validator-identifier@7.24.5': {} - '@babel/helper-validator-identifier@7.24.7': {} '@babel/helper-validator-option@7.23.5': {} + '@babel/helper-validator-option@7.24.8': {} + '@babel/helpers@7.24.5': dependencies: '@babel/template': 7.24.0 - '@babel/traverse': 7.24.5 - '@babel/types': 7.24.5 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 transitivePeerDependencies: - supports-color + '@babel/helpers@7.25.6': + dependencies: + '@babel/template': 7.25.0 + '@babel/types': 7.25.6 + '@babel/highlight@7.24.5': dependencies: - '@babel/helper-validator-identifier': 7.24.5 + '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.1.0 @@ -7022,19 +7168,15 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.0 - '@babel/parser@7.24.5': + '@babel/parser@7.25.6': dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.25.6 - '@babel/parser@7.25.3': + '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.25.2)': dependencies: - '@babel/types': 7.25.2 - - '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.24.5)': - dependencies: - '@babel/core': 7.24.5 - '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.24.5) - '@babel/helper-plugin-utils': 7.24.5 + '@babel/core': 7.25.2 + '@babel/helper-create-class-features-plugin': 7.25.4(@babel/core@7.25.2) + '@babel/helper-plugin-utils': 7.24.8 transitivePeerDependencies: - supports-color @@ -7056,57 +7198,46 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.25.6': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.24.0': dependencies: '@babel/code-frame': 7.24.2 - '@babel/parser': 7.25.3 - '@babel/types': 7.24.5 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 '@babel/template@7.25.0': dependencies: '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 '@babel/traverse@7.23.2': dependencies: '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.0 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.24.5 - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 - debug: 4.3.6 + '@babel/generator': 7.25.6 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color optional: true - '@babel/traverse@7.24.5': - dependencies: - '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.24.5 - '@babel/parser': 7.24.5 - '@babel/types': 7.24.5 - debug: 4.3.6 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/traverse@7.25.3': + '@babel/traverse@7.25.6': dependencies: '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.0 - '@babel/parser': 7.25.3 + '@babel/generator': 7.25.6 + '@babel/parser': 7.25.6 '@babel/template': 7.25.0 - '@babel/types': 7.25.2 - debug: 4.3.6 + '@babel/types': 7.25.6 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7117,13 +7248,7 @@ snapshots: to-fast-properties: 2.0.0 optional: true - '@babel/types@7.24.5': - dependencies: - '@babel/helper-string-parser': 7.24.1 - '@babel/helper-validator-identifier': 7.24.5 - to-fast-properties: 2.0.0 - - '@babel/types@7.25.2': + '@babel/types@7.25.6': dependencies: '@babel/helper-string-parser': 7.24.8 '@babel/helper-validator-identifier': 7.24.7 @@ -7150,7 +7275,7 @@ snapshots: '@commitlint/config-validator@19.0.3': dependencies: '@commitlint/types': 19.0.3 - ajv: 8.13.0 + ajv: 8.17.1 '@commitlint/ensure@19.0.3': dependencies: @@ -7290,7 +7415,7 @@ snapshots: '@electron/get@2.0.3': dependencies: - debug: 4.3.6 + debug: 4.3.7 env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -7395,7 +7520,7 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.23.0': + '@esbuild/aix-ppc64@0.23.1': optional: true '@esbuild/android-arm64@0.19.12': @@ -7404,7 +7529,7 @@ snapshots: '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.23.0': + '@esbuild/android-arm64@0.23.1': optional: true '@esbuild/android-arm@0.19.12': @@ -7413,7 +7538,7 @@ snapshots: '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.23.0': + '@esbuild/android-arm@0.23.1': optional: true '@esbuild/android-x64@0.19.12': @@ -7422,7 +7547,7 @@ snapshots: '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.23.0': + '@esbuild/android-x64@0.23.1': optional: true '@esbuild/darwin-arm64@0.19.12': @@ -7431,7 +7556,7 @@ snapshots: '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.23.0': + '@esbuild/darwin-arm64@0.23.1': optional: true '@esbuild/darwin-x64@0.19.12': @@ -7440,7 +7565,7 @@ snapshots: '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.23.0': + '@esbuild/darwin-x64@0.23.1': optional: true '@esbuild/freebsd-arm64@0.19.12': @@ -7449,7 +7574,7 @@ snapshots: '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.23.0': + '@esbuild/freebsd-arm64@0.23.1': optional: true '@esbuild/freebsd-x64@0.19.12': @@ -7458,7 +7583,7 @@ snapshots: '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.23.0': + '@esbuild/freebsd-x64@0.23.1': optional: true '@esbuild/linux-arm64@0.19.12': @@ -7467,7 +7592,7 @@ snapshots: '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.23.0': + '@esbuild/linux-arm64@0.23.1': optional: true '@esbuild/linux-arm@0.19.12': @@ -7476,7 +7601,7 @@ snapshots: '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.23.0': + '@esbuild/linux-arm@0.23.1': optional: true '@esbuild/linux-ia32@0.19.12': @@ -7485,7 +7610,7 @@ snapshots: '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.23.0': + '@esbuild/linux-ia32@0.23.1': optional: true '@esbuild/linux-loong64@0.19.12': @@ -7494,7 +7619,7 @@ snapshots: '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.23.0': + '@esbuild/linux-loong64@0.23.1': optional: true '@esbuild/linux-mips64el@0.19.12': @@ -7503,7 +7628,7 @@ snapshots: '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.23.0': + '@esbuild/linux-mips64el@0.23.1': optional: true '@esbuild/linux-ppc64@0.19.12': @@ -7512,7 +7637,7 @@ snapshots: '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.23.0': + '@esbuild/linux-ppc64@0.23.1': optional: true '@esbuild/linux-riscv64@0.19.12': @@ -7521,7 +7646,7 @@ snapshots: '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.23.0': + '@esbuild/linux-riscv64@0.23.1': optional: true '@esbuild/linux-s390x@0.19.12': @@ -7530,7 +7655,7 @@ snapshots: '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.23.0': + '@esbuild/linux-s390x@0.23.1': optional: true '@esbuild/linux-x64@0.19.12': @@ -7539,7 +7664,7 @@ snapshots: '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.23.0': + '@esbuild/linux-x64@0.23.1': optional: true '@esbuild/netbsd-x64@0.19.12': @@ -7548,10 +7673,10 @@ snapshots: '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.23.0': + '@esbuild/netbsd-x64@0.23.1': optional: true - '@esbuild/openbsd-arm64@0.23.0': + '@esbuild/openbsd-arm64@0.23.1': optional: true '@esbuild/openbsd-x64@0.19.12': @@ -7560,7 +7685,7 @@ snapshots: '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.23.0': + '@esbuild/openbsd-x64@0.23.1': optional: true '@esbuild/sunos-x64@0.19.12': @@ -7569,7 +7694,7 @@ snapshots: '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.23.0': + '@esbuild/sunos-x64@0.23.1': optional: true '@esbuild/win32-arm64@0.19.12': @@ -7578,7 +7703,7 @@ snapshots: '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.23.0': + '@esbuild/win32-arm64@0.23.1': optional: true '@esbuild/win32-ia32@0.19.12': @@ -7587,7 +7712,7 @@ snapshots: '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.23.0': + '@esbuild/win32-ia32@0.23.1': optional: true '@esbuild/win32-x64@0.19.12': @@ -7596,7 +7721,7 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.23.0': + '@esbuild/win32-x64@0.23.1': optional: true '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': @@ -7604,15 +7729,15 @@ snapshots: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.10.0': {} + '@eslint-community/regexpp@4.11.0': {} '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.6 + debug: 4.3.7 espree: 9.6.1 globals: 13.24.0 - ignore: 5.3.1 + ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -7657,7 +7782,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.6 + debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -7668,17 +7793,17 @@ snapshots: '@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)': dependencies: - '@babel/core': 7.24.5 - '@babel/generator': 7.24.5 - '@babel/parser': 7.24.5 - '@babel/traverse': 7.24.5 - '@babel/types': 7.24.5 + '@babel/core': 7.25.2 + '@babel/generator': 7.25.6 + '@babel/parser': 7.25.6 + '@babel/traverse': 7.25.6 + '@babel/types': 7.25.6 prettier: 3.3.3 - semver: 7.6.1 + semver: 7.6.3 transitivePeerDependencies: - supports-color - '@iconify/json@2.2.244': + '@iconify/json@2.2.245': dependencies: '@iconify/types': 2.0.0 pathe: 1.1.2 @@ -7930,12 +8055,12 @@ snapshots: '@mui/x-date-pickers@7.9.0(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(@mui/material@5.16.7(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(dayjs@1.11.13)(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1)': dependencies: - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.25.6 '@mui/base': 5.0.0-beta.40(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1) '@mui/material': 5.16.7(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1) '@mui/system': 5.16.7(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1))(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1) '@mui/utils': 5.16.6(react@19.0.0-rc-e948a5ac-20240807)(types-react@19.0.0-rc.1) - '@types/react-transition-group': 4.4.10 + '@types/react-transition-group': 4.4.11 clsx: 2.1.1 prop-types: 15.8.1 react: 19.0.0-rc-e948a5ac-20240807 @@ -8352,54 +8477,54 @@ snapshots: ignore: 5.3.2 p-map: 4.0.0 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.24.5)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.25.2 - '@svgr/babel-preset@8.1.0(@babel/core@7.24.5)': + '@svgr/babel-preset@8.1.0(@babel/core@7.25.2)': dependencies: - '@babel/core': 7.24.5 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.24.5) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.24.5) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.24.5) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.24.5) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.24.5) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.24.5) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.24.5) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.5) + '@babel/core': 7.25.2 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.25.2) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.25.2) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.25.2) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.25.2) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.25.2) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.25.2) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.25.2) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.25.2) '@svgr/core@8.1.0(typescript@5.5.4)': dependencies: - '@babel/core': 7.24.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.24.5) + '@babel/core': 7.25.2 + '@svgr/babel-preset': 8.1.0(@babel/core@7.25.2) camelcase: 6.3.0 cosmiconfig: 8.3.6(typescript@5.5.4) snake-case: 3.0.4 @@ -8409,13 +8534,13 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 entities: 4.5.0 '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.5.4))': dependencies: - '@babel/core': 7.24.5 - '@svgr/babel-preset': 8.1.0(@babel/core@7.24.5) + '@babel/core': 7.25.2 + '@svgr/babel-preset': 8.1.0(@babel/core@7.25.2) '@svgr/core': 8.1.0(typescript@5.5.4) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 @@ -8552,7 +8677,7 @@ snapshots: '@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.3.3)': dependencies: '@babel/generator': 7.17.7 - '@babel/parser': 7.25.3 + '@babel/parser': 7.25.6 '@babel/traverse': 7.23.2 '@babel/types': 7.17.0 javascript-natural-sort: 0.7.1 @@ -8572,24 +8697,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.25.3 - '@babel/types': 7.25.2 + '@babel/parser': 7.25.6 + '@babel/types': 7.25.6 '@types/babel__traverse@7.20.5': dependencies: - '@babel/types': 7.25.2 + '@babel/types': 7.25.6 '@types/cacheable-request@6.0.3': dependencies: @@ -8760,9 +8885,9 @@ snapshots: '@types/lodash-es@4.17.12': dependencies: - '@types/lodash': 4.17.1 + '@types/lodash': 4.17.7 - '@types/lodash@4.17.1': {} + '@types/lodash@4.17.7': {} '@types/mdast@4.0.3': dependencies: @@ -8770,15 +8895,15 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@16.18.97': {} + '@types/node@16.18.108': {} '@types/node@22.5.0': dependencies: - undici-types: 6.19.6 + undici-types: 6.19.8 '@types/node@22.5.4': dependencies: - undici-types: 6.19.6 + undici-types: 6.19.8 '@types/parse-json@4.0.2': {} @@ -8796,6 +8921,10 @@ snapshots: dependencies: '@types/react': types-react@19.0.0-rc.1 + '@types/react-transition-group@4.4.11': + dependencies: + '@types/react': types-react@19.0.0-rc.1 + '@types/responselike@1.0.3': dependencies: '@types/node': 22.5.4 @@ -8811,7 +8940,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.3.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)': dependencies: - '@eslint-community/regexpp': 4.10.0 + '@eslint-community/regexpp': 4.11.0 '@typescript-eslint/parser': 8.3.0(eslint@8.57.0)(typescript@5.5.4) '@typescript-eslint/scope-manager': 8.3.0 '@typescript-eslint/type-utils': 8.3.0(eslint@8.57.0)(typescript@5.5.4) @@ -8833,7 +8962,7 @@ snapshots: '@typescript-eslint/types': 8.3.0 '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) '@typescript-eslint/visitor-keys': 8.3.0 - debug: 4.3.6 + debug: 4.3.7 eslint: 8.57.0 optionalDependencies: typescript: 5.5.4 @@ -8849,7 +8978,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.5.4) '@typescript-eslint/utils': 8.3.0(eslint@8.57.0)(typescript@5.5.4) - debug: 4.3.6 + debug: 4.3.7 ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 @@ -8863,7 +8992,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.3.0 '@typescript-eslint/visitor-keys': 8.3.0 - debug: 4.3.6 + debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 @@ -8924,7 +9053,7 @@ snapshots: '@vue/compiler-core@3.4.38': dependencies: - '@babel/parser': 7.25.3 + '@babel/parser': 7.25.6 '@vue/shared': 3.4.38 entities: 4.5.0 estree-walker: 2.0.2 @@ -8962,9 +9091,9 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 - acorn-jsx@5.3.2(acorn@8.11.3): + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: - acorn: 8.11.3 + acorn: 8.12.1 acorn-node@1.8.2: dependencies: @@ -8976,15 +9105,13 @@ snapshots: acorn@7.4.1: {} - acorn@8.11.3: {} - acorn@8.12.1: {} adm-zip@0.5.16: {} agent-base@7.1.1: dependencies: - debug: 4.3.6 + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -9040,6 +9167,13 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.1 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + allotment@1.20.2(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807): dependencies: classnames: 2.5.1 @@ -9168,7 +9302,7 @@ snapshots: autoprefixer@10.4.20(postcss@8.4.45): dependencies: browserslist: 4.23.3 - caniuse-lite: 1.0.30001646 + caniuse-lite: 1.0.30001658 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.0 @@ -9178,7 +9312,7 @@ snapshots: autoprefixer@9.8.8: dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001616 + caniuse-lite: 1.0.30001658 normalize-range: 0.1.2 num2fraction: 1.2.2 picocolors: 0.2.1 @@ -9241,15 +9375,15 @@ snapshots: browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001616 + caniuse-lite: 1.0.30001658 electron-to-chromium: 1.4.758 node-releases: 2.0.14 update-browserslist-db: 1.0.15(browserslist@4.23.0) browserslist@4.23.3: dependencies: - caniuse-lite: 1.0.30001646 - electron-to-chromium: 1.5.4 + caniuse-lite: 1.0.30001658 + electron-to-chromium: 1.5.16 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) @@ -9294,9 +9428,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001616: {} - - caniuse-lite@1.0.30001646: {} + caniuse-lite@1.0.30001658: {} capture-stack-trace@1.0.2: {} @@ -9356,7 +9488,7 @@ snapshots: cli-truncate@4.0.0: dependencies: slice-ansi: 5.0.0 - string-width: 7.1.0 + string-width: 7.2.0 client-only@0.0.1: {} @@ -9750,6 +9882,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 @@ -9872,7 +10008,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.7.0 dot-prop@4.2.1: dependencies: @@ -9896,17 +10032,17 @@ snapshots: electron-to-chromium@1.4.758: {} - electron-to-chromium@1.5.4: {} + electron-to-chromium@1.5.16: {} electron@23.3.13: dependencies: '@electron/get': 2.0.3 - '@types/node': 16.18.97 + '@types/node': 16.18.108 extract-zip: 2.0.1 transitivePeerDependencies: - supports-color - emoji-regex@10.3.0: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -9975,7 +10111,7 @@ snapshots: is-string: 1.0.7 is-typed-array: 1.1.13 is-weakref: 1.0.2 - object-inspect: 1.13.1 + object-inspect: 1.13.2 object-keys: 1.1.1 object.assign: 4.1.5 regexp.prototype.flags: 1.5.2 @@ -10107,34 +10243,34 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.23.0: + esbuild@0.23.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.23.0 - '@esbuild/android-arm': 0.23.0 - '@esbuild/android-arm64': 0.23.0 - '@esbuild/android-x64': 0.23.0 - '@esbuild/darwin-arm64': 0.23.0 - '@esbuild/darwin-x64': 0.23.0 - '@esbuild/freebsd-arm64': 0.23.0 - '@esbuild/freebsd-x64': 0.23.0 - '@esbuild/linux-arm': 0.23.0 - '@esbuild/linux-arm64': 0.23.0 - '@esbuild/linux-ia32': 0.23.0 - '@esbuild/linux-loong64': 0.23.0 - '@esbuild/linux-mips64el': 0.23.0 - '@esbuild/linux-ppc64': 0.23.0 - '@esbuild/linux-riscv64': 0.23.0 - '@esbuild/linux-s390x': 0.23.0 - '@esbuild/linux-x64': 0.23.0 - '@esbuild/netbsd-x64': 0.23.0 - '@esbuild/openbsd-arm64': 0.23.0 - '@esbuild/openbsd-x64': 0.23.0 - '@esbuild/sunos-x64': 0.23.0 - '@esbuild/win32-arm64': 0.23.0 - '@esbuild/win32-ia32': 0.23.0 - '@esbuild/win32-x64': 0.23.0 + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 - escalade@3.1.2: {} + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} @@ -10142,7 +10278,7 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-compat-utils@0.5.0(eslint@8.57.0): + eslint-compat-utils@0.5.1(eslint@8.57.0): dependencies: eslint: 8.57.0 semver: 7.6.3 @@ -10165,12 +10301,12 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 - is-core-module: 2.13.1 + is-core-module: 2.15.1 resolve: 1.22.8 transitivePeerDependencies: - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -10180,12 +10316,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-es-x@7.6.0(eslint@8.57.0): + eslint-plugin-es-x@7.8.0(eslint@8.57.0): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.10.0 + '@eslint-community/regexpp': 4.11.0 eslint: 8.57.0 - eslint-compat-utils: 0.5.0(eslint@8.57.0) + eslint-compat-utils: 0.5.1(eslint@8.57.0) eslint-plugin-html@8.1.1: dependencies: @@ -10201,9 +10337,9 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.3.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) hasown: 2.0.2 - is-core-module: 2.13.1 + is-core-module: 2.15.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 @@ -10223,8 +10359,8 @@ snapshots: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-plugin-es-x: 7.6.0(eslint@8.57.0) - get-tsconfig: 4.7.5 + eslint-plugin-es-x: 7.8.0(eslint@8.57.0) + get-tsconfig: 4.8.0 globals: 15.9.0 ignore: 5.3.2 minimatch: 9.0.5 @@ -10245,9 +10381,9 @@ snapshots: eslint-plugin-react-compiler@0.0.0-experimental-f8a5409-20240829(eslint@8.57.0): dependencies: - '@babel/core': 7.24.5 - '@babel/parser': 7.25.3 - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.24.5) + '@babel/core': 7.25.2 + '@babel/parser': 7.25.6 + '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.25.2) eslint: 8.57.0 hermes-parser: 0.20.1 zod: 3.23.8 @@ -10291,7 +10427,7 @@ snapshots: eslint@8.57.0: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.10.0 + '@eslint-community/regexpp': 4.11.0 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.0 '@humanwhocodes/config-array': 0.11.14 @@ -10301,13 +10437,13 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.3.7 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - esquery: 1.5.0 + esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 @@ -10315,7 +10451,7 @@ snapshots: glob-parent: 6.0.2 globals: 13.24.0 graphemer: 1.4.0 - ignore: 5.3.1 + ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -10340,11 +10476,11 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) + acorn: 8.12.1 + acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 3.4.3 - esquery@1.5.0: + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -10413,7 +10549,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.3.6 + debug: 4.3.7 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -10431,7 +10567,7 @@ snapshots: '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} @@ -10439,6 +10575,8 @@ snapshots: fast-shallow-equal@1.0.0: {} + fast-uri@3.0.1: {} + fastest-levenshtein@1.0.16: {} fastest-stable-stringify@2.0.2: {} @@ -10462,7 +10600,7 @@ snapshots: dependencies: flat-cache: 3.2.0 - file-entry-cache@9.0.0: + file-entry-cache@9.1.0: dependencies: flat-cache: 5.0.0 @@ -10500,7 +10638,7 @@ snapshots: dependencies: is-callable: 1.2.7 - foreground-child@3.1.1: + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 @@ -10511,7 +10649,7 @@ snapshots: fraction.js@4.3.7: {} - framer-motion@12.0.0-alpha.0(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807): + framer-motion@12.0.0-alpha.1(@emotion/is-prop-valid@1.3.0)(react-dom@19.0.0-rc-e948a5ac-20240807(react@19.0.0-rc-e948a5ac-20240807))(react@19.0.0-rc-e948a5ac-20240807): dependencies: tslib: 2.6.2 optionalDependencies: @@ -10587,7 +10725,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 - get-tsconfig@4.7.5: + get-tsconfig@4.8.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -10605,13 +10743,14 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.3.12: + glob@10.4.5: dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 + foreground-child: 3.3.0 + jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 - path-scurry: 1.10.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 glob@7.2.3: dependencies: @@ -10839,8 +10978,6 @@ snapshots: ieee754@1.2.1: {} - ignore@5.3.1: {} - ignore@5.3.2: {} image-size@0.5.5: @@ -10947,7 +11084,7 @@ snapshots: dependencies: ci-info: 1.6.0 - is-core-module@2.13.1: + is-core-module@2.15.1: dependencies: hasown: 2.0.2 @@ -11086,7 +11223,7 @@ snapshots: reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 - jackspeak@2.3.6: + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -11141,6 +11278,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -11224,14 +11363,14 @@ snapshots: dependencies: chalk: 5.3.0 commander: 12.1.0 - debug: 4.3.6 + debug: 4.3.7 execa: 8.0.1 lilconfig: 3.1.2 listr2: 8.2.4 - micromatch: 4.0.7 + micromatch: 4.0.8 pidtree: 0.6.0 string-argv: 0.3.2 - yaml: 2.5.0 + yaml: 2.5.1 transitivePeerDependencies: - supports-color @@ -11303,7 +11442,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.6.2 + tslib: 2.7.0 lowercase-keys@1.0.1: {} @@ -11311,6 +11450,8 @@ snapshots: lru-cache@10.2.2: {} + lru-cache@10.4.3: {} + lru-cache@4.1.5: dependencies: pseudomap: 1.0.2 @@ -11462,6 +11603,8 @@ snapshots: merge2@1.4.1: {} + meta-json-schema@https://codeload.github.com/libnyanpasu/meta-json-schema/tar.gz/b4b29ee93facde4ca7da6e9c4e0cba50c3c1e074: {} + micromark-core-commonmark@2.0.1: dependencies: decode-named-character-reference: 1.0.2 @@ -11576,7 +11719,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.6 + debug: 4.3.7 decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -11595,11 +11738,6 @@ snapshots: transitivePeerDependencies: - supports-color - micromatch@4.0.7: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -11628,10 +11766,6 @@ snapshots: dependencies: brace-expansion: 1.1.11 - minimatch@9.0.4: - dependencies: - brace-expansion: 2.0.1 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -11656,6 +11790,37 @@ snapshots: monaco-editor@0.51.0: {} + monaco-languageserver-types@0.4.0: + dependencies: + monaco-types: 0.1.0 + vscode-languageserver-protocol: 3.17.5 + vscode-uri: 3.0.8 + + monaco-marker-data-provider@1.2.3: + dependencies: + monaco-types: 0.1.0 + + monaco-types@0.1.0: {} + + monaco-worker-manager@2.0.1(monaco-editor@0.51.0): + dependencies: + monaco-editor: 0.51.0 + + monaco-yaml@5.2.2(monaco-editor@0.51.0): + dependencies: + jsonc-parser: 3.3.1 + monaco-editor: 0.51.0 + monaco-languageserver-types: 0.4.0 + monaco-marker-data-provider: 1.2.3 + monaco-types: 0.1.0 + monaco-worker-manager: 2.0.1(monaco-editor@0.51.0) + path-browserify: 1.0.1 + prettier: 2.8.8 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.0.8 + yaml: 2.5.0 + ms@2.0.0: {} ms@2.1.2: {} @@ -11696,6 +11861,8 @@ snapshots: nanoid@3.3.7: {} + nanoid@5.0.7: {} + natural-compare@1.4.0: {} needle@3.3.1: @@ -11709,7 +11876,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.2 + tslib: 2.7.0 node-domexception@1.0.0: {} @@ -11750,7 +11917,7 @@ snapshots: ansi-styles: 6.2.1 cross-spawn: 7.0.3 memorystream: 0.3.1 - minimatch: 9.0.4 + minimatch: 9.0.5 pidtree: 0.6.0 read-package-json-fast: 3.0.2 shell-quote: 1.8.1 @@ -11775,7 +11942,7 @@ snapshots: object-hash@3.0.0: {} - object-inspect@1.13.1: {} + object-inspect@1.13.2: {} object-keys@1.1.1: {} @@ -11869,7 +12036,7 @@ snapshots: p-limit@4.0.0: dependencies: - yocto-queue: 1.0.0 + yocto-queue: 1.1.1 p-locate@5.0.0: dependencies: @@ -11887,6 +12054,8 @@ snapshots: dependencies: p-finally: 1.0.0 + package-json-from-dist@1.0.0: {} + package-json@4.0.1: dependencies: got: 6.7.1 @@ -11942,9 +12111,9 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.10.2: + path-scurry@1.11.1: dependencies: - lru-cache: 10.2.2 + lru-cache: 10.4.3 minipass: 7.1.2 path-type@4.0.0: {} @@ -12028,7 +12197,7 @@ snapshots: postcss-load-config@4.0.2(postcss@8.4.45): dependencies: lilconfig: 3.1.2 - yaml: 2.5.0 + yaml: 2.5.1 optionalDependencies: postcss: 8.4.45 @@ -12055,7 +12224,7 @@ snapshots: postcss: 7.0.39 postcss-selector-parser: 6.1.2 - postcss-nested@6.0.1(postcss@8.4.45): + postcss-nested@6.2.0(postcss@8.4.45): dependencies: postcss: 8.4.45 postcss-selector-parser: 6.1.2 @@ -12079,16 +12248,6 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@6.1.0: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-selector-parser@6.1.1: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 @@ -12157,6 +12316,8 @@ snapshots: '@taplo/lib': 0.4.0-alpha.2 prettier: 3.3.3 + prettier@2.8.8: {} + prettier@3.3.3: {} pretty-hrtime@1.0.3: {} @@ -12208,7 +12369,7 @@ snapshots: react-devtools-core@5.3.1(bufferutil@4.0.8)(utf-8-validate@5.0.10): dependencies: shell-quote: 1.8.1 - ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -12369,7 +12530,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 globalthis: 1.0.4 - which-builtin-type: 1.1.3 + which-builtin-type: 1.1.4 regenerator-runtime@0.14.1: {} @@ -12426,13 +12587,13 @@ snapshots: resolve@1.22.8: dependencies: - is-core-module: 2.13.1 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 resolve@2.0.0-next.5: dependencies: - is-core-module: 2.13.1 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -12455,7 +12616,7 @@ snapshots: rimraf@5.0.9: dependencies: - glob: 10.3.12 + glob: 10.4.5 roarr@2.15.4: dependencies: @@ -12547,8 +12708,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.6.1: {} - semver@7.6.3: {} serialize-error@7.0.1: @@ -12599,7 +12758,7 @@ snapshots: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - object-inspect: 1.13.1 + object-inspect: 1.13.2 signal-exit@3.0.7: {} @@ -12636,7 +12795,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.2 + tslib: 2.7.0 socks@2.8.3: dependencies: @@ -12701,9 +12860,9 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string-width@7.1.0: + string-width@7.2.0: dependencies: - emoji-regex: 10.3.0 + emoji-regex: 10.4.0 get-east-asian-width: 1.2.0 strip-ansi: 7.1.0 @@ -12837,10 +12996,10 @@ snapshots: cosmiconfig: 9.0.0(typescript@5.5.4) css-functions-list: 3.2.2 css-tree: 2.3.1 - debug: 4.3.6 + debug: 4.3.7 fast-glob: 3.3.2 fastest-levenshtein: 1.0.16 - file-entry-cache: 9.0.0 + file-entry-cache: 9.1.0 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 @@ -12877,7 +13036,7 @@ snapshots: stylus@0.62.0: dependencies: '@adobe/css-tools': 4.3.3 - debug: 4.3.6 + debug: 4.3.7 glob: 7.2.3 sax: 1.3.0 source-map: 0.7.4 @@ -12888,7 +13047,7 @@ snapshots: dependencies: '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 - glob: 10.3.12 + glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.6 @@ -12896,7 +13055,7 @@ snapshots: sumchecker@3.0.1: dependencies: - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -12938,11 +13097,11 @@ snapshots: synckit@0.9.1: dependencies: '@pkgr/core': 0.1.1 - tslib: 2.6.2 + tslib: 2.7.0 table@6.8.2: dependencies: - ajv: 8.13.0 + ajv: 8.17.1 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 @@ -12973,7 +13132,7 @@ snapshots: postcss-functions: 3.0.0 postcss-js: 2.0.3 postcss-nested: 4.2.3 - postcss-selector-parser: 6.1.0 + postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 pretty-hrtime: 1.0.3 reduce-css-calc: 2.1.8 @@ -12991,7 +13150,7 @@ snapshots: is-glob: 4.0.3 jiti: 1.21.6 lilconfig: 2.1.0 - micromatch: 4.0.7 + micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.0 @@ -12999,8 +13158,8 @@ snapshots: postcss-import: 15.1.0(postcss@8.4.45) postcss-js: 4.0.1(postcss@8.4.45) postcss-load-config: 4.0.2(postcss@8.4.45) - postcss-nested: 6.0.1(postcss@8.4.45) - postcss-selector-parser: 6.1.1 + postcss-nested: 6.2.0(postcss@8.4.45) + postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.0 transitivePeerDependencies: @@ -13104,10 +13263,12 @@ snapshots: tslib@2.6.2: {} + tslib@2.7.0: {} + tsx@4.19.0: dependencies: - esbuild: 0.23.0 - get-tsconfig: 4.7.5 + esbuild: 0.23.1 + get-tsconfig: 4.8.0 optionalDependencies: fsevents: 2.3.3 @@ -13204,7 +13365,7 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - undici-types@6.19.6: {} + undici-types@6.19.8: {} undici@5.28.4: dependencies: @@ -13323,13 +13484,13 @@ snapshots: update-browserslist-db@1.0.15(browserslist@4.23.0): dependencies: browserslist: 4.23.0 - escalade: 3.1.2 + escalade: 3.2.0 picocolors: 1.1.0 update-browserslist-db@1.1.0(browserslist@4.23.3): dependencies: browserslist: 4.23.3 - escalade: 3.1.2 + escalade: 3.2.0 picocolors: 1.1.0 update-notifier@2.5.0: @@ -13454,6 +13615,17 @@ snapshots: void-elements@3.1.0: {} + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + vscode-uri@3.0.8: {} vue-tsc@2.0.29(typescript@5.5.4): @@ -13493,7 +13665,7 @@ snapshots: is-string: 1.0.7 is-symbol: 1.0.4 - which-builtin-type@1.1.3: + which-builtin-type@1.1.4: dependencies: function.prototype.name: 1.1.6 has-tostringtag: 1.0.2 @@ -13552,7 +13724,7 @@ snapshots: wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 - string-width: 7.1.0 + string-width: 7.2.0 strip-ansi: 7.1.0 wrappy@1.0.2: {} @@ -13574,7 +13746,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@7.5.9(bufferutil@4.0.8)(utf-8-validate@5.0.10): + ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10): optionalDependencies: bufferutil: 4.0.8 utf-8-validate: 5.0.10 @@ -13599,12 +13771,14 @@ snapshots: yaml@2.5.0: {} + yaml@2.5.1: {} + yargs-parser@21.1.1: {} yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 @@ -13618,7 +13792,7 @@ snapshots: yocto-queue@0.1.0: {} - yocto-queue@1.0.0: {} + yocto-queue@1.1.1: {} zod-validation-error@3.3.1(zod@3.23.8): dependencies: diff --git a/echo/internal/cmgr/cmgr.go b/echo/internal/cmgr/cmgr.go index b81a3510d4..b4fd10d82a 100644 --- a/echo/internal/cmgr/cmgr.go +++ b/echo/internal/cmgr/cmgr.go @@ -39,7 +39,8 @@ type Cmgr interface { Start(ctx context.Context, errCH chan error) // Metrics related - QueryNodeMetrics(ctx context.Context, req *ms.QueryNodeMetricsReq) (*ms.QueryNodeMetricsResp, error) + QueryNodeMetrics(ctx context.Context, req *ms.QueryNodeMetricsReq, refresh bool) (*ms.QueryNodeMetricsResp, error) + QueryRuleMetrics(ctx context.Context, req *ms.QueryRuleMetricsReq, refresh bool) (*ms.QueryRuleMetricsResp, error) } type cmgrImpl struct { @@ -201,20 +202,30 @@ func (cm *cmgrImpl) Start(ctx context.Context, errCH chan error) { } } -func (cm *cmgrImpl) QueryNodeMetrics(ctx context.Context, req *ms.QueryNodeMetricsReq) (*ms.QueryNodeMetricsResp, error) { - num := -1 // default to return all metrics - if req.Latest { - m, err := cm.mr.ReadOnce(ctx) +func (cm *cmgrImpl) QueryNodeMetrics(ctx context.Context, req *ms.QueryNodeMetricsReq, refresh bool) (*ms.QueryNodeMetricsResp, error) { + if refresh { + nm, _, err := cm.mr.ReadOnce(ctx) if err != nil { return nil, err } - if err := cm.ms.AddNodeMetric(m); err != nil { + if err := cm.ms.AddNodeMetric(ctx, nm); err != nil { return nil, err } - num = 1 } - - startTime := time.Unix(req.StartTimestamp, 0) - endTime := time.Unix(req.EndTimestamp, 0) - return cm.ms.QueryNodeMetric(startTime, endTime, num) + return cm.ms.QueryNodeMetric(ctx, req) +} + +func (cm *cmgrImpl) QueryRuleMetrics(ctx context.Context, req *ms.QueryRuleMetricsReq, refresh bool) (*ms.QueryRuleMetricsResp, error) { + if refresh { + _, rm, err := cm.mr.ReadOnce(ctx) + if err != nil { + return nil, err + } + for _, m := range rm { + if err := cm.ms.AddRuleMetric(ctx, m); err != nil { + return nil, err + } + } + } + return cm.ms.QueryRuleMetric(ctx, req) } diff --git a/echo/internal/cmgr/ms/handler.go b/echo/internal/cmgr/ms/handler.go new file mode 100644 index 0000000000..f591ef8197 --- /dev/null +++ b/echo/internal/cmgr/ms/handler.go @@ -0,0 +1,163 @@ +package ms + +import ( + "context" + + "github.com/Ehco1996/ehco/pkg/metric_reader" +) + +type NodeMetrics struct { + Timestamp int64 `json:"timestamp"` + + CPUUsage float64 `json:"cpu_usage"` + MemoryUsage float64 `json:"memory_usage"` + DiskUsage float64 `json:"disk_usage"` + NetworkIn float64 `json:"network_in"` // bytes per second + NetworkOut float64 `json:"network_out"` // bytes per second +} + +type QueryNodeMetricsReq struct { + StartTimestamp int64 + EndTimestamp int64 + Num int64 +} + +type QueryNodeMetricsResp struct { + TOTAL int `json:"total"` + Data []NodeMetrics `json:"data"` +} + +type RuleMetricsData struct { + Timestamp int64 `json:"timestamp"` + Label string `json:"label"` + Remote string `json:"remote"` + PingLatency int64 `json:"ping_latency"` + TCPConnectionCount int64 `json:"tcp_connection_count"` + TCPHandshakeDuration int64 `json:"tcp_handshake_duration"` + TCPNetworkTransmitBytes int64 `json:"tcp_network_transmit_bytes"` + UDPConnectionCount int64 `json:"udp_connection_count"` + UDPHandshakeDuration int64 `json:"udp_handshake_duration"` + UDPNetworkTransmitBytes int64 `json:"udp_network_transmit_bytes"` +} + +type QueryRuleMetricsReq struct { + RuleLabel string + Remote string + + StartTimestamp int64 + EndTimestamp int64 + Num int64 +} + +type QueryRuleMetricsResp struct { + TOTAL int `json:"total"` + Data []RuleMetricsData `json:"data"` +} + +func (ms *MetricsStore) AddNodeMetric(ctx context.Context, m *metric_reader.NodeMetrics) error { + _, err := ms.db.ExecContext(ctx, ` + INSERT OR REPLACE INTO node_metrics (timestamp, cpu_usage, memory_usage, disk_usage, network_in, network_out) + VALUES (?, ?, ?, ?, ?, ?) +`, m.SyncTime.Unix(), m.CpuUsagePercent, m.MemoryUsagePercent, m.DiskUsagePercent, m.NetworkReceiveBytesRate, m.NetworkTransmitBytesRate) + return err +} + +func (ms *MetricsStore) AddRuleMetric(ctx context.Context, rm *metric_reader.RuleMetrics) error { + tx, err := ms.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() //nolint:errcheck + + stmt, err := tx.PrepareContext(ctx, ` + INSERT OR REPLACE INTO rule_metrics + (timestamp, label, remote, ping_latency, + tcp_connection_count, tcp_handshake_duration, tcp_network_transmit_bytes, + udp_connection_count, udp_handshake_duration, udp_network_transmit_bytes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `) + if err != nil { + return err + } + defer stmt.Close() //nolint:errcheck + + for remote, pingMetric := range rm.PingMetrics { + _, err := stmt.ExecContext(ctx, rm.SyncTime.Unix(), rm.Label, remote, pingMetric.Latency, + rm.TCPConnectionCount[remote], rm.TCPHandShakeDuration[remote], rm.TCPNetworkTransmitBytes[remote], + rm.UDPConnectionCount[remote], rm.UDPHandShakeDuration[remote], rm.UDPNetworkTransmitBytes[remote]) + if err != nil { + return err + } + } + + return tx.Commit() +} + +func (ms *MetricsStore) QueryNodeMetric(ctx context.Context, req *QueryNodeMetricsReq) (*QueryNodeMetricsResp, error) { + rows, err := ms.db.QueryContext(ctx, ` + SELECT timestamp, cpu_usage, memory_usage, disk_usage, network_in, network_out + FROM node_metrics + WHERE timestamp >= ? AND timestamp <= ? + ORDER BY timestamp DESC + LIMIT ? +`, req.StartTimestamp, req.EndTimestamp, req.Num) + if err != nil { + return nil, err + } + defer rows.Close() //nolint:errcheck + + var resp QueryNodeMetricsResp + for rows.Next() { + var m NodeMetrics + if err := rows.Scan(&m.Timestamp, &m.CPUUsage, &m.MemoryUsage, &m.DiskUsage, &m.NetworkIn, &m.NetworkOut); err != nil { + return nil, err + } + resp.Data = append(resp.Data, m) + } + resp.TOTAL = len(resp.Data) + return &resp, nil +} + +func (ms *MetricsStore) QueryRuleMetric(ctx context.Context, req *QueryRuleMetricsReq) (*QueryRuleMetricsResp, error) { + query := ` + SELECT timestamp, label, remote, ping_latency, + tcp_connection_count, tcp_handshake_duration, tcp_network_transmit_bytes, + udp_connection_count, udp_handshake_duration, udp_network_transmit_bytes + FROM rule_metrics + WHERE timestamp >= ? AND timestamp <= ? + ` + args := []interface{}{req.StartTimestamp, req.EndTimestamp} + + if req.RuleLabel != "" { + query += " AND label = ?" + args = append(args, req.RuleLabel) + } + if req.Remote != "" { + query += " AND remote = ?" + args = append(args, req.Remote) + } + + query += ` + ORDER BY timestamp DESC + LIMIT ? + ` + args = append(args, req.Num) + + rows, err := ms.db.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() //nolint:errcheck + var resp QueryRuleMetricsResp + for rows.Next() { + var m RuleMetricsData + if err := rows.Scan(&m.Timestamp, &m.Label, &m.Remote, &m.PingLatency, + &m.TCPConnectionCount, &m.TCPHandshakeDuration, &m.TCPNetworkTransmitBytes, + &m.UDPConnectionCount, &m.UDPHandshakeDuration, &m.UDPNetworkTransmitBytes); err != nil { + return nil, err + } + resp.Data = append(resp.Data, m) + } + resp.TOTAL = len(resp.Data) + return &resp, nil +} diff --git a/echo/internal/cmgr/ms/ms.go b/echo/internal/cmgr/ms/ms.go index e57a405320..fd7b38ad16 100644 --- a/echo/internal/cmgr/ms/ms.go +++ b/echo/internal/cmgr/ms/ms.go @@ -8,31 +8,8 @@ import ( "go.uber.org/zap" _ "modernc.org/sqlite" - - "github.com/Ehco1996/ehco/pkg/metric_reader" ) -type NodeMetrics struct { - Timestamp int64 `json:"timestamp"` - - CPUUsage float64 `json:"cpu_usage"` - MemoryUsage float64 `json:"memory_usage"` - DiskUsage float64 `json:"disk_usage"` - NetworkIn float64 `json:"network_in"` - NetworkOut float64 `json:"network_out"` -} - -type QueryNodeMetricsReq struct { - StartTimestamp int64 `json:"start_ts"` - EndTimestamp int64 `json:"end_ts"` - - Latest bool `json:"latest"` // whether to refresh the cache and get the latest data -} -type QueryNodeMetricsResp struct { - TOTAL int `json:"total"` - Data []NodeMetrics `json:"data"` -} - type MetricsStore struct { db *sql.DB dbPath string @@ -65,12 +42,34 @@ func NewMetricsStore(dbPath string) (*MetricsStore, error) { if err := ms.initDB(); err != nil { return nil, err } + if err := ms.cleanOldData(); err != nil { + return nil, err + } return ms, nil } +func (ms *MetricsStore) cleanOldData() error { + thirtyDaysAgo := time.Now().AddDate(0, 0, -30).Unix() + + // 清理 node_metrics 表 + _, err := ms.db.Exec("DELETE FROM node_metrics WHERE timestamp < ?", thirtyDaysAgo) + if err != nil { + return err + } + + // 清理 rule_metrics 表 + _, err = ms.db.Exec("DELETE FROM rule_metrics WHERE timestamp < ?", thirtyDaysAgo) + if err != nil { + return err + } + + ms.l.Infof("Cleaned data older than 30 days") + return nil +} + func (ms *MetricsStore) initDB() error { // init NodeMetrics table - _, err := ms.db.Exec(` + if _, err := ms.db.Exec(` CREATE TABLE IF NOT EXISTS node_metrics ( timestamp INTEGER, cpu_usage REAL, @@ -80,39 +79,27 @@ func (ms *MetricsStore) initDB() error { network_out REAL, PRIMARY KEY (timestamp) ) - `) - return err -} - -func (ms *MetricsStore) AddNodeMetric(m *metric_reader.NodeMetrics) error { - _, err := ms.db.Exec(` - INSERT OR REPLACE INTO node_metrics (timestamp, cpu_usage, memory_usage, disk_usage, network_in, network_out) - VALUES (?, ?, ?, ?, ?, ?) -`, m.SyncTime.Unix(), m.CpuUsagePercent, m.MemoryUsagePercent, m.DiskUsagePercent, m.NetworkReceiveBytesRate, m.NetworkTransmitBytesRate) - return err -} - -func (ms *MetricsStore) QueryNodeMetric(startTime, endTime time.Time, num int) (*QueryNodeMetricsResp, error) { - rows, err := ms.db.Query(` - SELECT timestamp, cpu_usage, memory_usage, disk_usage, network_in, network_out - FROM node_metrics - WHERE timestamp >= ? AND timestamp <= ? - ORDER BY timestamp DESC - LIMIT ? -`, startTime.Unix(), endTime.Unix(), num) - if err != nil { - return nil, err + `); err != nil { + return err } - defer rows.Close() //nolint:errcheck - var resp QueryNodeMetricsResp - for rows.Next() { - var m NodeMetrics - if err := rows.Scan(&m.Timestamp, &m.CPUUsage, &m.MemoryUsage, &m.DiskUsage, &m.NetworkIn, &m.NetworkOut); err != nil { - return nil, err - } - resp.Data = append(resp.Data, m) + // init rule_metrics + if _, err := ms.db.Exec(` + CREATE TABLE IF NOT EXISTS rule_metrics ( + timestamp INTEGER, + label TEXT, + remote TEXT, + ping_latency INTEGER, + tcp_connection_count INTEGER, + tcp_handshake_duration INTEGER, + tcp_network_transmit_bytes INTEGER, + udp_connection_count INTEGER, + udp_handshake_duration INTEGER, + udp_network_transmit_bytes INTEGER, + PRIMARY KEY (timestamp, label, remote) + ) + `); err != nil { + return err } - resp.TOTAL = len(resp.Data) - return &resp, nil + return nil } diff --git a/echo/internal/cmgr/syncer.go b/echo/internal/cmgr/syncer.go index 067a6337f5..a82987f291 100644 --- a/echo/internal/cmgr/syncer.go +++ b/echo/internal/cmgr/syncer.go @@ -45,14 +45,19 @@ func (cm *cmgrImpl) syncOnce(ctx context.Context) error { } if cm.cfg.NeedMetrics() { - metrics, err := cm.mr.ReadOnce(ctx) + nm, rmm, err := cm.mr.ReadOnce(ctx) if err != nil { cm.l.Errorf("read metrics failed: %v", err) } else { - req.Node = *metrics - if err := cm.ms.AddNodeMetric(metrics); err != nil { + req.Node = *nm + if err := cm.ms.AddNodeMetric(ctx, nm); err != nil { cm.l.Errorf("add metrics to store failed: %v", err) } + for _, rm := range rmm { + if err := cm.ms.AddRuleMetric(ctx, rm); err != nil { + cm.l.Errorf("add rule metrics to store failed: %v", err) + } + } } } diff --git a/echo/internal/conn/relay_conn.go b/echo/internal/conn/relay_conn.go index f0e110377b..f6fa62724a 100644 --- a/echo/internal/conn/relay_conn.go +++ b/echo/internal/conn/relay_conn.go @@ -209,14 +209,12 @@ func (c *innerConn) recordStats(n int, isRead bool) { return } if isRead { - metrics.NetWorkTransmitBytes.WithLabelValues( - c.rc.remote.Label, metrics.METRIC_CONN_TYPE_TCP, metrics.METRIC_CONN_FLOW_READ, - ).Add(float64(n)) + labels := []string{c.rc.RelayLabel, c.rc.ConnType, metrics.METRIC_FLOW_READ, c.rc.remote.Address} + metrics.NetWorkTransmitBytes.WithLabelValues(labels...).Add(float64(n)) c.rc.Stats.Record(0, int64(n)) } else { - metrics.NetWorkTransmitBytes.WithLabelValues( - c.rc.remote.Label, metrics.METRIC_CONN_TYPE_TCP, metrics.METRIC_CONN_FLOW_WRITE, - ).Add(float64(n)) + labels := []string{c.rc.RelayLabel, c.rc.ConnType, metrics.METRIC_FLOW_WRITE, c.rc.remote.Address} + metrics.NetWorkTransmitBytes.WithLabelValues(labels...).Add(float64(n)) c.rc.Stats.Record(int64(n), 0) } } @@ -236,7 +234,7 @@ func (c *innerConn) Read(p []byte) (n int, err error) { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { since := time.Since(c.lastActive) if since > c.rc.Options.IdleTimeout { - c.l.Debugf("Read idle, close remote: %s", c.rc.remote.Label) + c.l.Debugf("Read idle, close remote: %s", c.rc.remote.Address) return 0, ErrIdleTimeout } continue diff --git a/echo/internal/conn/relay_conn_test.go b/echo/internal/conn/relay_conn_test.go index 22f3d683f7..1c7acecc96 100644 --- a/echo/internal/conn/relay_conn_test.go +++ b/echo/internal/conn/relay_conn_test.go @@ -24,7 +24,7 @@ func TestInnerConn_ReadWrite(t *testing.T) { serverConn.SetDeadline(time.Now().Add(1 * time.Second)) defer clientConn.Close() defer serverConn.Close() - rc := relayConnImpl{Stats: &Stats{}, remote: &lb.Node{Label: "client"}, Options: &testOptions} + rc := relayConnImpl{Stats: &Stats{}, remote: &lb.Node{}, Options: &testOptions} innerC := newInnerConn(clientConn, &rc) errChan := make(chan error, 1) go func() { @@ -100,7 +100,7 @@ func TestCopyTCPConn(t *testing.T) { assert.NoError(t, err) defer remoteConn.Close() testOptions := conf.Options{IdleTimeout: time.Second, ReadTimeout: time.Second} - rc := relayConnImpl{Stats: &Stats{}, remote: &lb.Node{Label: "client"}, Options: &testOptions} + rc := relayConnImpl{Stats: &Stats{}, remote: &lb.Node{}, Options: &testOptions} c1 := newInnerConn(clientConn, &rc) c2 := newInnerConn(remoteConn, &rc) @@ -161,7 +161,7 @@ func TestCopyUDPConn(t *testing.T) { defer remoteConn.Close() testOptions := conf.Options{IdleTimeout: time.Second, ReadTimeout: time.Second} - rc := relayConnImpl{Stats: &Stats{}, remote: &lb.Node{Label: "client"}, Options: &testOptions} + rc := relayConnImpl{Stats: &Stats{}, remote: &lb.Node{}, Options: &testOptions} c1 := newInnerConn(clientConn, &rc) c2 := newInnerConn(remoteConn, &rc) diff --git a/echo/internal/lb/round_robin.go b/echo/internal/lb/round_robin.go index 080cbcef33..5cfd5ee07a 100644 --- a/echo/internal/lb/round_robin.go +++ b/echo/internal/lb/round_robin.go @@ -1,6 +1,8 @@ package lb import ( + "net/url" + "strings" "time" "go.uber.org/atomic" @@ -8,21 +10,38 @@ import ( type Node struct { Address string - Label string HandShakeDuration time.Duration } func (n *Node) Clone() *Node { return &Node{ Address: n.Address, - Label: n.Label, HandShakeDuration: n.HandShakeDuration, } } +func extractHost(input string) (string, error) { + // Check if the input string has a scheme, if not, add "http://" + if !strings.Contains(input, "://") { + input = "http://" + input + } + // Parse the URL + u, err := url.Parse(input) + if err != nil { + return "", err + } + return u.Hostname(), nil +} + +// NOTE for (https/ws/wss)://xxx.com -> xxx.com +func (n *Node) GetAddrHost() (string, error) { + return extractHost(n.Address) +} + // RoundRobin is an interface for representing round-robin balancing. type RoundRobin interface { Next() *Node + GetAll() []*Node } type roundrobin struct { @@ -42,3 +61,7 @@ func (r *roundrobin) Next() *Node { next := r.nodeList[(int(n)-1)%r.len] return next } + +func (r *roundrobin) GetAll() []*Node { + return r.nodeList +} diff --git a/echo/internal/metrics/metrics.go b/echo/internal/metrics/metrics.go index 4d155afb1a..083120c379 100644 --- a/echo/internal/metrics/metrics.go +++ b/echo/internal/metrics/metrics.go @@ -13,53 +13,43 @@ const ( METRIC_SUBSYSTEM_TRAFFIC = "traffic" METRIC_SUBSYSTEM_PING = "ping" - METRIC_LABEL_REMOTE = "remote" - - METRIC_LABEL_CONN_FLOW = "flow" - METRIC_CONN_FLOW_WRITE = "write" - METRIC_CONN_FLOW_READ = "read" - - METRIC_LABEL_CONN_TYPE = "type" - METRIC_CONN_TYPE_TCP = "tcp" - METRIC_CONN_TYPE_UDP = "udp" + METRIC_CONN_TYPE_TCP = "tcp" + METRIC_CONN_TYPE_UDP = "udp" + METRIC_FLOW_READ = "read" + METRIC_FLOW_WRITE = "write" EhcoAliveStateInit = 0 EhcoAliveStateRunning = 1 ) +var ( + Hostname, _ = os.Hostname() + ConstLabels = map[string]string{ + "ehco_runner_hostname": Hostname, + } + + // 1ms ~ 5s (1ms 到 437ms ) + msBuckets = prometheus.ExponentialBuckets(1, 1.5, 16) +) + // ping metrics var ( - pingLabelNames = []string{"ip", "host", "label"} - pingBuckets = prometheus.ExponentialBuckets(0.001, 2, 12) // 1ms ~ 4s - pingInterval = time.Second * 30 - - PingResponseDurationSeconds = prometheus.NewHistogramVec( + pingInterval = time.Second * 30 + PingResponseDurationMilliseconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: METRIC_NS, Subsystem: METRIC_SUBSYSTEM_PING, - Name: "response_duration_seconds", + Name: "response_duration_milliseconds", Help: "A histogram of latencies for ping responses.", - Buckets: pingBuckets, + Buckets: msBuckets, ConstLabels: ConstLabels, }, - pingLabelNames, - ) - PingRequestTotal = prometheus.NewDesc( - prometheus.BuildFQName(METRIC_NS, METRIC_SUBSYSTEM_PING, "requests_total"), - "Number of ping requests sent", - pingLabelNames, - ConstLabels, + []string{"label", "remote", "ip"}, ) ) // traffic metrics var ( - Hostname, _ = os.Hostname() - - ConstLabels = map[string]string{ - "ehco_runner_hostname": Hostname, - } - EhcoAlive = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: METRIC_NS, Subsystem: "", @@ -74,7 +64,15 @@ var ( Name: "current_connection_count", Help: "当前链接数", ConstLabels: ConstLabels, - }, []string{METRIC_LABEL_REMOTE, METRIC_LABEL_CONN_TYPE}) + }, []string{"label", "conn_type", "remote"}) + + HandShakeDurationMilliseconds = prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Subsystem: METRIC_SUBSYSTEM_TRAFFIC, + Namespace: METRIC_NS, + Name: "handshake_duration_milliseconds", + Help: "握手时间ms", + ConstLabels: ConstLabels, + }, []string{"label", "conn_type", "remote"}) NetWorkTransmitBytes = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: METRIC_NS, @@ -82,15 +80,7 @@ var ( Name: "network_transmit_bytes", Help: "传输流量总量bytes", ConstLabels: ConstLabels, - }, []string{METRIC_LABEL_REMOTE, METRIC_LABEL_CONN_TYPE, METRIC_LABEL_CONN_FLOW}) - - HandShakeDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ - Subsystem: METRIC_SUBSYSTEM_TRAFFIC, - Namespace: METRIC_NS, - Name: "handshake_duration", - Help: "握手时间ms", - ConstLabels: ConstLabels, - }, []string{METRIC_LABEL_REMOTE}) + }, []string{"label", "conn_type", "flow", "remote"}) ) func RegisterEhcoMetrics(cfg *config.Config) error { @@ -98,15 +88,14 @@ func RegisterEhcoMetrics(cfg *config.Config) error { prometheus.MustRegister(EhcoAlive) prometheus.MustRegister(CurConnectionCount) prometheus.MustRegister(NetWorkTransmitBytes) - prometheus.MustRegister(HandShakeDuration) + prometheus.MustRegister(HandShakeDurationMilliseconds) EhcoAlive.Set(EhcoAliveStateInit) // ping if cfg.EnablePing { pg := NewPingGroup(cfg) - prometheus.MustRegister(PingResponseDurationSeconds) - prometheus.MustRegister(pg) + prometheus.MustRegister(PingResponseDurationMilliseconds) go pg.Run() } return nil diff --git a/echo/internal/metrics/ping.go b/echo/internal/metrics/ping.go index 5a37ff6b2e..68d42ec3a4 100644 --- a/echo/internal/metrics/ping.go +++ b/echo/internal/metrics/ping.go @@ -1,20 +1,16 @@ package metrics import ( - "fmt" "math" - "net/url" "runtime" - "strings" "time" "github.com/Ehco1996/ehco/internal/config" "github.com/go-ping/ping" - "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" ) -func (pg *PingGroup) newPinger(addr string) (*ping.Pinger, error) { +func (pg *PingGroup) newPinger(ruleLabel string, remote string, addr string) (*ping.Pinger, error) { pinger := ping.New(addr) if err := pinger.Resolve(); err != nil { pg.logger.Error("failed to resolve pinger", zap.String("addr", addr), zap.Error(err)) @@ -26,6 +22,13 @@ func (pg *PingGroup) newPinger(addr string) (*ping.Pinger, error) { if runtime.GOOS != "darwin" { pinger.SetPrivileged(true) } + pinger.OnRecv = func(pkt *ping.Packet) { + ip := pkt.IPAddr.String() + PingResponseDurationMilliseconds.WithLabelValues( + ruleLabel, remote, ip).Observe(float64(pkt.Rtt.Milliseconds())) + pg.logger.Sugar().Infof("%d bytes from %s icmp_seq=%d time=%v ttl=%v", + pkt.Nbytes, pkt.Addr, pkt.Seq, pkt.Rtt, pkt.Ttl) + } return pinger, nil } @@ -34,89 +37,29 @@ type PingGroup struct { // k: addr Pingers map[string]*ping.Pinger - - // k: addr v:relay rule label joined by "," - PingerLabels map[string]string -} - -func extractHost(input string) (string, error) { - // Check if the input string has a scheme, if not, add "http://" - if !strings.Contains(input, "://") { - input = "http://" + input - } - // Parse the URL - u, err := url.Parse(input) - if err != nil { - return "", err - } - return u.Hostname(), nil } func NewPingGroup(cfg *config.Config) *PingGroup { - logger := zap.L().Named("pinger") - pg := &PingGroup{ - logger: logger, - Pingers: make(map[string]*ping.Pinger), - PingerLabels: map[string]string{}, + logger: zap.L().Named("pinger"), + Pingers: make(map[string]*ping.Pinger), } - - // parse addr from rule for _, relayCfg := range cfg.RelayConfigs { - // NOTE for (https/ws/wss)://xxx.com -> xxx.com - for _, remote := range relayCfg.Remotes { - addr, err := extractHost(remote) + for _, remote := range relayCfg.GetAllRemotes() { + addr, err := remote.GetAddrHost() if err != nil { pg.logger.Error("try parse host error", zap.Error(err)) } - if _, ok := pg.Pingers[addr]; ok { - // append rule label when remote host is same - pg.PingerLabels[addr] += fmt.Sprintf(",%s", relayCfg.Label) - continue - } - if pinger, err := pg.newPinger(addr); err != nil { + if pinger, err := pg.newPinger(relayCfg.Label, remote.Address, addr); err != nil { pg.logger.Error("new pinger meet error", zap.Error(err)) } else { - pg.Pingers[pinger.Addr()] = pinger - pg.PingerLabels[addr] = relayCfg.Label + pg.Pingers[addr] = pinger } } } - - // update metrics - for addr, pinger := range pg.Pingers { - pinger.OnRecv = func(pkt *ping.Packet) { - PingResponseDurationSeconds.WithLabelValues( - pkt.IPAddr.String(), pkt.Addr, pg.PingerLabels[addr]).Observe(pkt.Rtt.Seconds()) - pg.logger.Sugar().Infof("%d bytes from %s icmp_seq=%d time=%v ttl=%v", - pkt.Nbytes, pkt.Addr, pkt.Seq, pkt.Rtt, pkt.Ttl) - } - pinger.OnDuplicateRecv = func(pkt *ping.Packet) { - pg.logger.Sugar().Infof("%d bytes from %s icmp_seq=%d time=%v ttl=%v (DUP!)", - pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl) - } - } return pg } -func (pg *PingGroup) Describe(ch chan<- *prometheus.Desc) { - ch <- PingRequestTotal -} - -func (pg *PingGroup) Collect(ch chan<- prometheus.Metric) { - for addr, pinger := range pg.Pingers { - stats := pinger.Statistics() - ch <- prometheus.MustNewConstMetric( - PingRequestTotal, - prometheus.CounterValue, - float64(stats.PacketsSent), - stats.IPAddr.String(), - stats.Addr, - pg.PingerLabels[addr], - ) - } -} - func (pg *PingGroup) Run() { if len(pg.Pingers) <= 0 { return diff --git a/echo/internal/relay/conf/cfg.go b/echo/internal/relay/conf/cfg.go index 862898ff50..8b9b866b14 100644 --- a/echo/internal/relay/conf/cfg.go +++ b/echo/internal/relay/conf/cfg.go @@ -179,14 +179,16 @@ func (r *Config) DefaultLabel() string { func (r *Config) ToRemotesLB() lb.RoundRobin { tcpNodeList := make([]*lb.Node, len(r.Remotes)) for idx, addr := range r.Remotes { - tcpNodeList[idx] = &lb.Node{ - Address: addr, - Label: fmt.Sprintf("%s-%s", r.Label, addr), - } + tcpNodeList[idx] = &lb.Node{Address: addr} } return lb.NewRoundRobin(tcpNodeList) } +func (r *Config) GetAllRemotes() []*lb.Node { + lb := r.ToRemotesLB() + return lb.GetAll() +} + func (r *Config) GetLoggerName() string { return fmt.Sprintf("%s(%s<->%s)", r.Label, r.ListenType, r.TransportType) } diff --git a/echo/internal/transporter/base.go b/echo/internal/transporter/base.go index e41be343f4..77501864b5 100644 --- a/echo/internal/transporter/base.go +++ b/echo/internal/transporter/base.go @@ -44,8 +44,9 @@ func newBaseRelayServer(cfg *conf.Config, cmgr cmgr.Cmgr) (*BaseRelayServer, err } func (b *BaseRelayServer) RelayTCPConn(ctx context.Context, c net.Conn, remote *lb.Node) error { - metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Inc() - defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Dec() + labels := []string{b.cfg.Label, metrics.METRIC_CONN_TYPE_TCP, remote.Address} + metrics.CurConnectionCount.WithLabelValues(labels...).Inc() + defer metrics.CurConnectionCount.WithLabelValues(labels...).Dec() if err := b.checkConnectionLimit(); err != nil { return err @@ -68,8 +69,9 @@ func (b *BaseRelayServer) RelayTCPConn(ctx context.Context, c net.Conn, remote * } func (b *BaseRelayServer) RelayUDPConn(ctx context.Context, c net.Conn, remote *lb.Node) error { - metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_UDP).Inc() - defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_UDP).Dec() + labels := []string{b.cfg.Label, metrics.METRIC_CONN_TYPE_UDP, remote.Address} + metrics.CurConnectionCount.WithLabelValues(labels...).Inc() + defer metrics.CurConnectionCount.WithLabelValues(labels...).Dec() rc, err := b.relayer.HandShake(ctx, remote, false) if err != nil { diff --git a/echo/internal/transporter/raw.go b/echo/internal/transporter/raw.go index c9b7ac77bc..2f41dfdf28 100644 --- a/echo/internal/transporter/raw.go +++ b/echo/internal/transporter/raw.go @@ -47,7 +47,12 @@ func (raw *RawClient) HandShake(ctx context.Context, remote *lb.Node, isTCP bool return nil, err } latency := time.Since(t1) - metrics.HandShakeDuration.WithLabelValues(remote.Label).Observe(float64(latency.Milliseconds())) + connType := metrics.METRIC_CONN_TYPE_TCP + if !isTCP { + connType = metrics.METRIC_CONN_TYPE_UDP + } + labels := []string{raw.cfg.Label, connType, remote.Address} + metrics.HandShakeDurationMilliseconds.WithLabelValues(labels...).Observe(float64(latency.Milliseconds())) remote.HandShakeDuration = latency return rc, nil } diff --git a/echo/internal/transporter/ws.go b/echo/internal/transporter/ws.go index d281a79151..06bcf4d1b6 100644 --- a/echo/internal/transporter/ws.go +++ b/echo/internal/transporter/ws.go @@ -67,7 +67,12 @@ func (s *WsClient) HandShake(ctx context.Context, remote *lb.Node, isTCP bool) ( return nil, err } latency := time.Since(t1) - metrics.HandShakeDuration.WithLabelValues(remote.Label).Observe(float64(latency.Milliseconds())) + connType := metrics.METRIC_CONN_TYPE_TCP + if !isTCP { + connType = metrics.METRIC_CONN_TYPE_UDP + } + labels := []string{s.cfg.Label, connType, remote.Address} + metrics.HandShakeDurationMilliseconds.WithLabelValues(labels...).Observe(float64(latency.Milliseconds())) remote.HandShakeDuration = latency c := conn.NewWSConn(wsc, false) return c, nil @@ -97,7 +102,7 @@ func (s *WsServer) handleRequest(w http.ResponseWriter, req *http.Request) { var remote *lb.Node if addr := req.URL.Query().Get(conf.WS_QUERY_REMOTE_ADDR); addr != "" { - remote = &lb.Node{Address: addr, Label: addr} + remote = &lb.Node{Address: addr} } else { remote = s.remotes.Next() } diff --git a/echo/internal/web/handler_api.go b/echo/internal/web/handler_api.go new file mode 100644 index 0000000000..af1540decb --- /dev/null +++ b/echo/internal/web/handler_api.go @@ -0,0 +1,134 @@ +package web + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/Ehco1996/ehco/internal/cmgr/ms" + "github.com/labstack/echo/v4" +) + +const ( + defaultTimeRange = 60 // seconds + errInvalidParam = "invalid parameter: %s" +) + +type queryParams struct { + startTS int64 + endTS int64 + refresh bool +} + +func parseQueryParams(c echo.Context) (*queryParams, error) { + now := time.Now().Unix() + params := &queryParams{ + startTS: now - defaultTimeRange, + endTS: now, + refresh: false, + } + + if start, err := parseTimestamp(c.QueryParam("start_ts")); err == nil { + params.startTS = start + } + + if end, err := parseTimestamp(c.QueryParam("end_ts")); err == nil { + params.endTS = end + } + + if refresh, err := strconv.ParseBool(c.QueryParam("latest")); err == nil { + params.refresh = refresh + } + + if params.startTS >= params.endTS { + return nil, fmt.Errorf(errInvalidParam, "time range") + } + + return params, nil +} + +func parseTimestamp(s string) (int64, error) { + if s == "" { + return 0, fmt.Errorf("empty timestamp") + } + return strconv.ParseInt(s, 10, 64) +} + +func (s *Server) GetNodeMetrics(c echo.Context) error { + params, err := parseQueryParams(c) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + req := &ms.QueryNodeMetricsReq{StartTimestamp: params.startTS, EndTimestamp: params.endTS, Num: -1} + if params.refresh { + req.Num = 1 + } + metrics, err := s.connMgr.QueryNodeMetrics(c.Request().Context(), req, params.refresh) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + return c.JSON(http.StatusOK, metrics) +} + +func (s *Server) GetRuleMetrics(c echo.Context) error { + params, err := parseQueryParams(c) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + req := &ms.QueryRuleMetricsReq{ + StartTimestamp: params.startTS, + EndTimestamp: params.endTS, + Num: -1, + RuleLabel: c.QueryParam("label"), + Remote: c.QueryParam("remote"), + } + if params.refresh { + req.Num = 1 + } + + metrics, err := s.connMgr.QueryRuleMetrics(c.Request().Context(), req, params.refresh) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + return c.JSON(http.StatusOK, metrics) +} + +func (s *Server) CurrentConfig(c echo.Context) error { + ret, err := json.Marshal(s.cfg) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + return c.JSONBlob(http.StatusOK, ret) +} + +func (s *Server) HandleReload(c echo.Context) error { + if s.Reloader == nil { + return echo.NewHTTPError(http.StatusBadRequest, "reload not support") + } + err := s.Reloader.Reload(true) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + if _, err := c.Response().Write([]byte("reload success")); err != nil { + s.l.Errorf("write response meet err=%v", err) + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + return nil +} + +func (s *Server) HandleHealthCheck(c echo.Context) error { + relayLabel := c.QueryParam("relay_label") + if relayLabel == "" { + return echo.NewHTTPError(http.StatusBadRequest, "relay_label is required") + } + latency, err := s.HealthCheck(c.Request().Context(), relayLabel) + if err != nil { + res := HealthCheckResp{Message: err.Error(), ErrorCode: -1} + return c.JSON(http.StatusBadRequest, res) + } + return c.JSON(http.StatusOK, HealthCheckResp{Message: "connect success", Latency: latency}) +} diff --git a/echo/internal/web/handlers.go b/echo/internal/web/handler_page.go similarity index 50% rename from echo/internal/web/handlers.go rename to echo/internal/web/handler_page.go index dcd6a7d477..a50ce1a36f 100644 --- a/echo/internal/web/handlers.go +++ b/echo/internal/web/handler_page.go @@ -1,13 +1,10 @@ package web import ( - "encoding/json" "fmt" "net/http" "strconv" - "time" - "github.com/Ehco1996/ehco/internal/cmgr/ms" "github.com/Ehco1996/ehco/internal/config" "github.com/Ehco1996/ehco/internal/constant" "github.com/labstack/echo/v4" @@ -42,44 +39,6 @@ func (s *Server) index(c echo.Context) error { return c.Render(http.StatusOK, "index.html", data) } -func (s *Server) HandleReload(c echo.Context) error { - if s.Reloader == nil { - return echo.NewHTTPError(http.StatusBadRequest, "reload not support") - } - err := s.Reloader.Reload(true) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - if _, err := c.Response().Write([]byte("reload success")); err != nil { - s.l.Errorf("write response meet err=%v", err) - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - return nil -} - -func (s *Server) HandleHealthCheck(c echo.Context) error { - relayLabel := c.QueryParam("relay_label") - if relayLabel == "" { - return echo.NewHTTPError(http.StatusBadRequest, "relay_label is required") - } - latency, err := s.HealthCheck(c.Request().Context(), relayLabel) - if err != nil { - res := HealthCheckResp{Message: err.Error(), ErrorCode: -1} - return c.JSON(http.StatusBadRequest, res) - } - return c.JSON(http.StatusOK, HealthCheckResp{Message: "connect success", Latency: latency}) -} - -func (s *Server) CurrentConfig(c echo.Context) error { - ret, err := json.Marshal(s.cfg) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - return c.JSONBlob(http.StatusOK, ret) -} - func (s *Server) ListConnections(c echo.Context) error { pageStr := c.QueryParam("page") page, err := strconv.Atoi(pageStr) @@ -126,36 +85,12 @@ func (s *Server) ListRules(c echo.Context) error { }) } -func (s *Server) GetNodeMetrics(c echo.Context) error { - startTS := time.Now().Unix() - 60 - if c.QueryParam("start_ts") != "" { - star, err := strconv.ParseInt(c.QueryParam("start_ts"), 10, 64) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - startTS = star - } - endTS := time.Now().Unix() - if c.QueryParam("end_ts") != "" { - end, err := strconv.ParseInt(c.QueryParam("end_ts"), 10, 64) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - endTS = end - } - req := &ms.QueryNodeMetricsReq{StartTimestamp: startTS, EndTimestamp: endTS} - latest := c.QueryParam("latest") - if latest != "" { - r, err := strconv.ParseBool(latest) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - req.Latest = r - } - - metrics, err := s.connMgr.QueryNodeMetrics(c.Request().Context(), req) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - return c.JSON(http.StatusOK, metrics) +func (s *Server) RuleMetrics(c echo.Context) error { + return c.Render(http.StatusOK, "rule_metrics.html", map[string]interface{}{ + "Configs": s.cfg.RelayConfigs, + }) +} + +func (s *Server) LogsPage(c echo.Context) error { + return c.Render(http.StatusOK, "logs.html", nil) } diff --git a/echo/internal/web/handlers_ws.go b/echo/internal/web/handlers_ws.go new file mode 100644 index 0000000000..8ab97945be --- /dev/null +++ b/echo/internal/web/handlers_ws.go @@ -0,0 +1,33 @@ +package web + +import ( + "net" + + "github.com/Ehco1996/ehco/pkg/log" + "github.com/gobwas/ws" + "github.com/labstack/echo/v4" +) + +func (s *Server) handleWebSocketLogs(c echo.Context) error { + conn, _, _, err := ws.UpgradeHTTP(c.Request(), c.Response()) + if err != nil { + return err + } + defer conn.Close() + + log.SetWebSocketConn(conn) + + // 保持连接打开并处理可能的入站消息 + for { + _, err := ws.ReadFrame(conn) + if err != nil { + if _, ok := err.(net.Error); ok { + // 处理网络错误 + s.l.Errorf("WebSocket read error: %v", err) + } + break + } + } + log.SetWebSocketConn(nil) + return nil +} diff --git a/echo/internal/web/js/metrics.js b/echo/internal/web/js/metrics.js deleted file mode 100644 index b6b53d41ad..0000000000 --- a/echo/internal/web/js/metrics.js +++ /dev/null @@ -1,393 +0,0 @@ -const MetricsModule = (function () { - // Constants - const API_BASE_URL = '/api/v1'; - const NODE_METRICS_PATH = '/node_metrics/'; - const BYTE_TO_MB = 1024 * 1024; - - const handleError = (error) => { - console.error('Error:', error); - }; - - // API functions - const fetchData = async (path, params = {}) => { - const url = new URL(API_BASE_URL + path, window.location.origin); - Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value)); - try { - const response = await fetch(url.toString()); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return await response.json(); - } catch (error) { - handleError(error); - return null; - } - }; - - const fetchLatestMetric = () => fetchData(NODE_METRICS_PATH, { latest: true }).then((data) => data?.data[0]); - const fetchMetrics = (startTs, endTs) => fetchData(NODE_METRICS_PATH, { start_ts: startTs, end_ts: endTs }).then((data) => data?.data); - - // Chart functions - const initChart = (canvasId, type, datasets, legendPosition = '', yDisplayText = '', title = '', unit = '') => { - const ctx = $(`#${canvasId}`)[0].getContext('2d'); - const colors = { - cpu: 'rgba(255, 99, 132, 1)', - memory: 'rgba(54, 162, 235, 1)', - disk: 'rgba(255, 206, 86, 1)', - receive: 'rgba(0, 150, 255, 1)', - transmit: 'rgba(255, 140, 0, 1)', - }; - - const getDatasetConfig = (label) => { - const color = colors[label.toLowerCase()] || 'rgba(0, 0, 0, 1)'; - return { - label, - borderColor: color, - backgroundColor: color.replace('1)', '0.2)'), - borderWidth: 2, - pointRadius: 2, - pointHoverRadius: 2, - fill: true, - data: [], - }; - }; - - const data = { - labels: [], - datasets: $.isArray(datasets) ? datasets.map((dataset) => getDatasetConfig(dataset.label)) : [getDatasetConfig(datasets.label)], - }; - - return new Chart(ctx, { - type, - data, - options: { - line: { - spanGaps: false, // 设置为 false,不连接空值 - }, - responsive: true, - plugins: { - legend: { position: legendPosition }, - title: { - display: !!title, - text: title, - position: 'bottom', - font: { size: 14, weight: 'bold' }, - }, - tooltip: { - callbacks: { - title: function (tooltipItems) { - return new Date(tooltipItems[0].label).toLocaleString(); - }, - label: function (context) { - let label = context.dataset.label || ''; - if (label) { - label += ': '; - } - if (context.parsed.y !== null) { - label += context.parsed.y.toFixed(2) + ' ' + unit; - } - return label; - }, - }, - }, - }, - scales: { - x: { - type: 'time', - time: { - unit: 'minute', - displayFormats: { - minute: 'HH:mm', - }, - }, - ticks: { - maxRotation: 0, - autoSkip: true, - maxTicksLimit: 10, - }, - adapters: { - date: { - locale: 'en', - }, - }, - }, - y: { - beginAtZero: true, - title: { display: true, text: yDisplayText, font: { weight: 'bold' } }, - }, - }, - elements: { line: { tension: 0.4 } }, - downsample: { - enabled: true, - threshold: 200, - }, - }, - }); - }; - - const updateChart = (chart, newData, labels) => { - if (!newData || !labels) { - console.error('Invalid data or labels provided'); - return; - } - - if ($.isArray(newData) && $.isArray(newData[0])) { - $.each(chart.data.datasets, (index, dataset) => { - if (newData[index]) { - dataset.data = newData[index].map((value, i) => ({ x: moment(labels[i]), y: value })); - } - }); - } else { - chart.data.datasets[0].data = newData.map((value, i) => ({ x: moment(labels[i]), y: value })); - } - - chart.options.scales.x.min = moment(labels[0]); - chart.options.scales.x.max = moment(labels[labels.length - 1]); - chart.update(); - }; - - const updateCharts = (charts, metrics, startTs, endTs) => { - console.log('Raw metrics data:', metrics); - - const generateTimestamps = (start, end) => { - const timestamps = []; - let current = moment.unix(start); - const endMoment = moment.unix(end); - while (current.isSameOrBefore(endMoment)) { - timestamps.push(current.toISOString()); - current.add(1, 'minute'); - } - return timestamps; - }; - - const timestamps = generateTimestamps(startTs, endTs); - - const processData = (dataKey) => { - const data = new Array(timestamps.length).fill(null); - metrics.forEach((metric) => { - const index = Math.floor((metric.timestamp - startTs) / 60); - if (index >= 0 && index < data.length) { - data[index] = metric[dataKey]; - } - }); - return data; - }; - - updateChart(charts.cpu, processData('cpu_usage'), timestamps); - updateChart(charts.memory, processData('memory_usage'), timestamps); - updateChart(charts.disk, processData('disk_usage'), timestamps); - updateChart( - charts.network, - [ - processData('network_in').map((v) => (v === null ? null : v / BYTE_TO_MB)), - processData('network_out').map((v) => (v === null ? null : v / BYTE_TO_MB)), - ], - timestamps - ); - }; - - const addLatestDataToCharts = (charts, latestMetric) => { - console.log('Raw latestMetric data:', latestMetric); - const timestamp = moment.unix(latestMetric.timestamp); - - $.each(charts, (key, chart) => { - // 检查是否已经有这个时间戳的数据 - const existingDataIndex = chart.data.labels.findIndex((label) => label.isSame(timestamp)); - - if (existingDataIndex === -1) { - // 如果是新数据,添加到末尾 - chart.data.labels.push(timestamp); - if (key === 'network') { - chart.data.datasets[0].data.push({ x: timestamp, y: latestMetric.network_in / BYTE_TO_MB }); - chart.data.datasets[1].data.push({ x: timestamp, y: latestMetric.network_out / BYTE_TO_MB }); - } else { - chart.data.datasets[0].data.push({ x: timestamp, y: latestMetric[`${key}_usage`] }); - } - - // 更新x轴范围,但保持一定的时间窗口 - const timeWindow = moment.duration(30, 'minutes'); // 设置显示的时间窗口,例如30分钟 - const oldestAllowedTime = moment(timestamp).subtract(timeWindow); - - chart.options.scales.x.min = oldestAllowedTime; - chart.options.scales.x.max = timestamp; - - // 开启图表的平移和缩放功能 - chart.options.plugins.zoom = { - pan: { - enabled: true, - mode: 'x', - }, - zoom: { - wheel: { - enabled: true, - }, - pinch: { - enabled: true, - }, - mode: 'x', - }, - }; - - chart.update(); - } - // 如果数据已存在,我们不做任何操作,保持现有数据 - }); - }; - - // Chart initialization - const initializeCharts = async () => { - const metric = await fetchLatestMetric(); - if (!metric) return null; - return { - cpu: initChart('cpuChart', 'line', { label: 'CPU' }, 'top', 'Usage (%)', `CPU`, '%'), - memory: initChart('memoryChart', 'line', { label: 'Memory' }, 'top', 'Usage (%)', `Memory`, '%'), - disk: initChart('diskChart', 'line', { label: 'Disk' }, 'top', 'Usage (%)', `Disk`, '%'), - network: initChart( - 'networkChart', - 'line', - [{ label: 'Receive' }, { label: 'Transmit' }], - 'top', - 'Rate (MB/s)', - 'Network Rate', - 'MB/s' - ), - }; - }; - - // Date range functions - const setupDateRangeDropdown = (charts) => { - const $dateRangeDropdown = $('#dateRangeDropdown'); - const $dateRangeButton = $('#dateRangeButton'); - const $dateRangeText = $('#dateRangeText'); - const $dateRangeInput = $('#dateRangeInput'); - - $dateRangeDropdown.find('.dropdown-item[data-range]').on('click', function (e) { - e.preventDefault(); - const range = $(this).data('range'); - const now = new Date(); - let start, end; - switch (range) { - case '30m': - start = new Date(now - 30 * 60 * 1000); - break; - case '1h': - start = new Date(now - 60 * 60 * 1000); - break; - case '3h': - start = new Date(now - 3 * 60 * 60 * 1000); - break; - case '6h': - start = new Date(now - 6 * 60 * 60 * 1000); - break; - case '12h': - start = new Date(now - 12 * 60 * 60 * 1000); - break; - case '24h': - start = new Date(now - 24 * 60 * 60 * 1000); - break; - case '7d': - start = new Date(now - 7 * 24 * 60 * 60 * 1000); - break; - } - end = now; - - const startTs = Math.floor(start.getTime() / 1000); - const endTs = Math.floor(end.getTime() / 1000); - fetchDataForRange(charts, startTs, endTs); - $dateRangeText.text($(this).text()); - $dateRangeDropdown.removeClass('is-active'); - }); - - $dateRangeButton.on('click', (event) => { - event.stopPropagation(); - $dateRangeDropdown.toggleClass('is-active'); - }); - - $(document).on('click', (event) => { - if (!$dateRangeDropdown.has(event.target).length) { - $dateRangeDropdown.removeClass('is-active'); - } - }); - - const picker = flatpickr($dateRangeInput[0], { - mode: 'range', - enableTime: true, - dateFormat: 'Y-m-d H:i', - onChange: function (selectedDates) { - if (selectedDates.length === 2) { - const startTs = Math.floor(selectedDates[0].getTime() / 1000); - const endTs = Math.floor(selectedDates[1].getTime() / 1000); - fetchDataForRange(charts, startTs, endTs); - - const formattedStart = selectedDates[0].toLocaleString(); - const formattedEnd = selectedDates[1].toLocaleString(); - $dateRangeText.text(`${formattedStart} - ${formattedEnd}`); - - // 关闭下拉菜单 - $dateRangeDropdown.removeClass('is-active'); - } - }, - onClose: function () { - // 确保在日期选择器关闭时也关闭下拉菜单 - $dateRangeDropdown.removeClass('is-active'); - }, - }); - - // 防止点击日期选择器时关闭下拉菜单 - $dateRangeInput.on('click', (event) => { - event.stopPropagation(); - }); - }; - - const fetchDataForRange = async (charts, startTs, endTs) => { - const metrics = await fetchMetrics(startTs, endTs); - if (metrics) { - console.log('Raw metrics data:', metrics); - updateCharts(charts, metrics, startTs, endTs); - } - }; - - // Auto refresh functions - const setupAutoRefresh = (charts) => { - let autoRefreshInterval; - let isAutoRefreshing = false; - $('#refreshButton').click(function () { - if (isAutoRefreshing) { - clearInterval(autoRefreshInterval); - $(this).removeClass('is-info'); - $(this).find('span:last').text('Auto Refresh'); - isAutoRefreshing = false; - } else { - $(this).addClass('is-info'); - $(this).find('span:last').text('Stop Refresh'); - isAutoRefreshing = true; - refreshData(charts); - autoRefreshInterval = setInterval(() => refreshData(charts), 5000); - } - }); - }; - - const refreshData = async (charts) => { - const latestMetric = await fetchLatestMetric(); - if (latestMetric) { - addLatestDataToCharts(charts, latestMetric); - } - }; - - // Main initialization function - const init = async () => { - const charts = await initializeCharts(); - if (charts) { - setupDateRangeDropdown(charts); - setupAutoRefresh(charts); - } - }; - - // Public API - return { - init: init, - }; -})(); - -// Initialize when the DOM is ready -document.addEventListener('DOMContentLoaded', MetricsModule.init); diff --git a/echo/internal/web/js/node_metrics.js b/echo/internal/web/js/node_metrics.js new file mode 100644 index 0000000000..b40fe4e6b4 --- /dev/null +++ b/echo/internal/web/js/node_metrics.js @@ -0,0 +1,404 @@ +const Config = { + API_BASE_URL: '/api/v1', + NODE_METRICS_PATH: '/node_metrics/', + BYTE_TO_MB: 1024 * 1024, + CHART_COLORS: { + cpu: 'rgba(255, 99, 132, 1)', + memory: 'rgba(54, 162, 235, 1)', + disk: 'rgba(255, 206, 86, 1)', + receive: 'rgba(0, 150, 255, 1)', + transmit: 'rgba(255, 140, 0, 1)', + }, + TIME_WINDOW: 30, // minutes + AUTO_REFRESH_INTERVAL: 5000, // milliseconds +}; + +class ApiService { + static async fetchData(path, params = {}) { + const url = new URL(Config.API_BASE_URL + path, window.location.origin); + Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value)); + try { + const response = await fetch(url.toString()); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error('Error:', error); + return null; + } + } + + static async fetchLatestMetric() { + const data = await this.fetchData(Config.NODE_METRICS_PATH, { latest: true }); + return data?.data[0]; + } + + static async fetchMetrics(startTs, endTs) { + const data = await this.fetchData(Config.NODE_METRICS_PATH, { start_ts: startTs, end_ts: endTs }); + return data?.data; + } +} + +class ChartManager { + constructor() { + this.charts = {}; + } + + initializeCharts() { + this.charts = { + cpu: this.initChart('cpuChart', 'line', { label: 'CPU' }, 'top', 'Usage (%)', 'CPU', '%'), + memory: this.initChart('memoryChart', 'line', { label: 'Memory' }, 'top', 'Usage (%)', 'Memory', '%'), + disk: this.initChart('diskChart', 'line', { label: 'Disk' }, 'top', 'Usage (%)', 'Disk', '%'), + network: this.initChart( + 'networkChart', + 'line', + [{ label: 'Receive' }, { label: 'Transmit' }], + 'top', + 'Rate (MB/s)', + 'Network Rate', + 'MB/s' + ), + }; + } + + initChart(canvasId, type, datasets, legendPosition, yDisplayText, title, unit) { + const ctx = $(`#${canvasId}`)[0].getContext('2d'); + const data = { + labels: [], + datasets: Array.isArray(datasets) + ? datasets.map((dataset) => this.getDatasetConfig(dataset.label)) + : [this.getDatasetConfig(datasets.label)], + }; + + return new Chart(ctx, { + type, + data, + options: this.getChartOptions(legendPosition, yDisplayText, title, unit), + }); + } + + getDatasetConfig(label) { + const color = Config.CHART_COLORS[label.toLowerCase()] || 'rgba(0, 0, 0, 1)'; + return { + label, + borderColor: color, + backgroundColor: color.replace('1)', '0.2)'), + borderWidth: 2, + pointRadius: 2, + pointHoverRadius: 2, + fill: true, + data: [], + }; + } + + getChartOptions(legendPosition, yDisplayText, title, unit) { + return { + line: { spanGaps: false }, + responsive: true, + plugins: { + legend: { position: legendPosition }, + title: { + display: !!title, + text: title, + position: 'bottom', + font: { size: 14, weight: 'bold' }, + }, + tooltip: { + callbacks: { + title: (tooltipItems) => new Date(tooltipItems[0].label).toLocaleString(), + label: (context) => { + let label = context.dataset.label || ''; + if (label) { + label += ': '; + } + if (context.parsed.y !== null) { + label += context.parsed.y.toFixed(2) + ' ' + unit; + } + return label; + }, + }, + }, + zoom: { + pan: { enabled: true, mode: 'x' }, + zoom: { + wheel: { enabled: true }, + pinch: { enabled: true }, + mode: 'x', + }, + }, + }, + scales: { + x: { + type: 'time', + time: { + unit: 'minute', + displayFormats: { minute: 'HH:mm' }, + }, + ticks: { + maxRotation: 0, + autoSkip: true, + maxTicksLimit: 10, + }, + adapters: { + date: { locale: 'en' }, + }, + }, + y: { + beginAtZero: true, + title: { display: true, text: yDisplayText, font: { weight: 'bold' } }, + }, + }, + elements: { line: { tension: 0.4 } }, + downsample: { + enabled: true, + threshold: 200, + }, + }; + } + + updateCharts(metrics, startTs, endTs) { + const timestamps = this.generateTimestamps(startTs, endTs); + const processData = (dataKey) => { + const data = new Array(timestamps.length).fill(null); + metrics.forEach((metric) => { + const index = Math.floor((metric.timestamp - startTs) / 60); + if (index >= 0 && index < data.length) { + data[index] = metric[dataKey]; + } + }); + return data; + }; + + this.updateChart(this.charts.cpu, processData('cpu_usage'), timestamps); + this.updateChart(this.charts.memory, processData('memory_usage'), timestamps); + this.updateChart(this.charts.disk, processData('disk_usage'), timestamps); + this.updateChart( + this.charts.network, + [ + processData('network_in').map((v) => (v === null ? null : v / Config.BYTE_TO_MB)), + processData('network_out').map((v) => (v === null ? null : v / Config.BYTE_TO_MB)), + ], + timestamps + ); + } + + updateChart(chart, newData, labels) { + if (!newData || !labels) { + console.error('Invalid data or labels provided'); + return; + } + + if (Array.isArray(newData) && Array.isArray(newData[0])) { + chart.data.datasets.forEach((dataset, index) => { + if (newData[index]) { + dataset.data = newData[index].map((value, i) => ({ x: moment(labels[i]), y: value })); + } + }); + } else { + chart.data.datasets[0].data = newData.map((value, i) => ({ x: moment(labels[i]), y: value })); + } + + chart.options.scales.x.min = moment(labels[0]); + chart.options.scales.x.max = moment(labels[labels.length - 1]); + chart.update(); + } + + addLatestDataToCharts(latestMetric) { + const timestamp = moment.unix(latestMetric.timestamp); + + Object.entries(this.charts).forEach(([key, chart]) => { + const existingDataIndex = chart.data.labels.findIndex((label) => label.isSame(timestamp)); + + if (existingDataIndex === -1) { + chart.data.labels.push(timestamp); + if (key === 'network') { + chart.data.datasets[0].data.push({ x: timestamp, y: latestMetric.network_in / Config.BYTE_TO_MB }); + chart.data.datasets[1].data.push({ x: timestamp, y: latestMetric.network_out / Config.BYTE_TO_MB }); + } else { + chart.data.datasets[0].data.push({ x: timestamp, y: latestMetric[`${key}_usage`] }); + } + + const timeWindow = moment.duration(Config.TIME_WINDOW, 'minutes'); + const oldestAllowedTime = moment(timestamp).subtract(timeWindow); + + chart.options.scales.x.min = oldestAllowedTime; + chart.options.scales.x.max = timestamp; + + chart.update(); + } + }); + } + + generateTimestamps(start, end) { + const timestamps = []; + let current = moment.unix(start); + const endMoment = moment.unix(end); + while (current.isSameOrBefore(endMoment)) { + timestamps.push(current.toISOString()); + current.add(1, 'minute'); + } + return timestamps; + } +} + +class DateRangeManager { + constructor(chartManager) { + this.chartManager = chartManager; + this.$dateRangeDropdown = $('#dateRangeDropdown'); + this.$dateRangeButton = $('#dateRangeButton'); + this.$dateRangeText = $('#dateRangeText'); + this.$dateRangeInput = $('#dateRangeInput'); + this.setupEventListeners(); + } + + setupEventListeners() { + this.$dateRangeDropdown.find('.dropdown-item[data-range]').on('click', (e) => this.handlePresetDateRange(e)); + this.$dateRangeButton.on('click', (event) => this.toggleDropdown(event)); + $(document).on('click', (event) => this.closeDropdownOnOutsideClick(event)); + this.initializeDatePicker(); + } + + handlePresetDateRange(e) { + e.preventDefault(); + const range = $(e.currentTarget).data('range'); + const [start, end] = this.calculateDateRange(range); + this.fetchAndUpdateCharts(start, end); + this.$dateRangeText.text($(e.currentTarget).text()); + this.$dateRangeDropdown.removeClass('is-active'); + } + + calculateDateRange(range) { + const now = new Date(); + let start; + switch (range) { + case '30m': + start = new Date(now - 30 * 60 * 1000); + break; + case '1h': + start = new Date(now - 60 * 60 * 1000); + break; + case '3h': + start = new Date(now - 3 * 60 * 60 * 1000); + break; + case '6h': + start = new Date(now - 6 * 60 * 60 * 1000); + break; + case '12h': + start = new Date(now - 12 * 60 * 60 * 1000); + break; + case '24h': + start = new Date(now - 24 * 60 * 60 * 1000); + break; + case '7d': + start = new Date(now - 7 * 24 * 60 * 60 * 1000); + break; + } + return [start, now]; + } + + toggleDropdown(event) { + event.stopPropagation(); + this.$dateRangeDropdown.toggleClass('is-active'); + } + + closeDropdownOnOutsideClick(event) { + if (!this.$dateRangeDropdown.has(event.target).length) { + this.$dateRangeDropdown.removeClass('is-active'); + } + } + + initializeDatePicker() { + flatpickr(this.$dateRangeInput[0], { + mode: 'range', + enableTime: true, + dateFormat: 'Y-m-d H:i', + onChange: (selectedDates) => this.handleDatePickerChange(selectedDates), + onClose: () => this.$dateRangeDropdown.removeClass('is-active'), + }); + + this.$dateRangeInput.on('click', (event) => event.stopPropagation()); + } + + handleDatePickerChange(selectedDates) { + if (selectedDates.length === 2) { + const [start, end] = selectedDates; + this.fetchAndUpdateCharts(start, end); + const formattedStart = start.toLocaleString(); + const formattedEnd = end.toLocaleString(); + this.$dateRangeText.text(`${formattedStart} - ${formattedEnd}`); + this.$dateRangeDropdown.removeClass('is-active'); + } + } + + async fetchAndUpdateCharts(start, end) { + const startTs = Math.floor(start.getTime() / 1000); + const endTs = Math.floor(end.getTime() / 1000); + const metrics = await ApiService.fetchMetrics(startTs, endTs); + if (metrics) { + this.chartManager.updateCharts(metrics, startTs, endTs); + } + } +} + +class AutoRefreshManager { + constructor(chartManager) { + this.chartManager = chartManager; + this.autoRefreshInterval = null; + this.isAutoRefreshing = false; + this.$refreshButton = $('#refreshButton'); + this.setupEventListeners(); + } + + setupEventListeners() { + this.$refreshButton.click(() => this.toggleAutoRefresh()); + } + + toggleAutoRefresh() { + if (this.isAutoRefreshing) { + this.stopAutoRefresh(); + } else { + this.startAutoRefresh(); + } + } + + startAutoRefresh() { + this.isAutoRefreshing = true; + this.$refreshButton.addClass('is-info'); + this.$refreshButton.find('span:last').text('Stop Refresh'); + this.refreshData(); + this.autoRefreshInterval = setInterval(() => this.refreshData(), Config.AUTO_REFRESH_INTERVAL); + } + + stopAutoRefresh() { + this.isAutoRefreshing = false; + clearInterval(this.autoRefreshInterval); + this.$refreshButton.removeClass('is-info'); + this.$refreshButton.find('span:last').text('Auto Refresh'); + } + + async refreshData() { + const latestMetric = await ApiService.fetchLatestMetric(); + if (latestMetric) { + this.chartManager.addLatestDataToCharts(latestMetric); + } + } +} + +class MetricsModule { + constructor() { + this.chartManager = new ChartManager(); + this.dateRangeManager = new DateRangeManager(this.chartManager); + this.autoRefreshManager = new AutoRefreshManager(this.chartManager); + } + + async init() { + this.chartManager.initializeCharts(); + } +} + +// Initialize when the DOM is ready +document.addEventListener('DOMContentLoaded', () => { + const metricsModule = new MetricsModule(); + metricsModule.init(); +}); diff --git a/echo/internal/web/js/rule_metrics.js b/echo/internal/web/js/rule_metrics.js new file mode 100644 index 0000000000..fc44e29d48 --- /dev/null +++ b/echo/internal/web/js/rule_metrics.js @@ -0,0 +1,402 @@ +const Config = { + API_BASE_URL: '/api/v1', + RULE_METRICS_PATH: '/rule_metrics/', + BYTE_TO_MB: 1024 * 1024, + CHART_COLORS: { + connectionCount: 'rgba(255, 99, 132, 1)', + handshakeDuration: 'rgba(54, 162, 235, 1)', + pingLatency: 'rgba(255, 206, 86, 1)', + networkTransmitBytes: 'rgba(75, 192, 192, 1)', + }, + TIME_WINDOW: 30, // minutes + AUTO_REFRESH_INTERVAL: 5000, // milliseconds +}; + +class ApiService { + static async fetchData(path, params = {}) { + const url = new URL(Config.API_BASE_URL + path, window.location.origin); + Object.entries(params).forEach(([key, value]) => url.searchParams.append(key, value)); + try { + const response = await fetch(url.toString()); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error('Error:', error); + return null; + } + } + + static async fetchRuleMetrics(startTs, endTs, label = '', remote = '') { + const params = { start_ts: startTs, end_ts: endTs }; + if (label) params.label = label; + if (remote) params.remote = remote; + return await this.fetchData(Config.RULE_METRICS_PATH, params); + } + + static async fetchConfig() { + return await this.fetchData('/config/'); + } + static async fetchLabelsAndRemotes() { + const config = await this.fetchConfig(); + if (!config || !config.relay_configs) { + return { labels: [], remotes: [] }; + } + + const labels = new Set(); + const remotes = new Set(); + + config.relay_configs.forEach((relayConfig) => { + if (relayConfig.label) labels.add(relayConfig.label); + if (relayConfig.remotes) { + relayConfig.remotes.forEach((remote) => remotes.add(remote)); + } + }); + + return { + labels: Array.from(labels), + remotes: Array.from(remotes), + }; + } +} + +class ChartManager { + constructor() { + this.charts = {}; + } + + initializeCharts() { + this.charts = { + connectionCount: this.initChart('connectionCountChart', 'line', 'Connection Count', 'Count'), + handshakeDuration: this.initChart('handshakeDurationChart', 'line', 'Handshake Duration', 'ms'), + pingLatency: this.initChart('pingLatencyChart', 'line', 'Ping Latency', 'ms'), + networkTransmitBytes: this.initChart('networkTransmitBytesChart', 'line', 'Network Transmit', 'MB'), + }; + } + + initChart(canvasId, type, title, unit) { + const ctx = $(`#${canvasId}`)[0].getContext('2d'); + const color = Config.CHART_COLORS[canvasId.replace('Chart', '')]; + + return new Chart(ctx, { + type: type, + data: { + labels: [], + datasets: [ + { + label: title, + borderColor: color, + backgroundColor: color.replace('1)', '0.2)'), + borderWidth: 2, + data: [], + }, + ], + }, + options: this.getChartOptions(title, unit), + }); + } + + getChartOptions(title, unit) { + return { + responsive: true, + plugins: { + title: { + display: true, + text: title, + font: { size: 16, weight: 'bold' }, + }, + tooltip: { + callbacks: { + label: (context) => `${context.dataset.label}: ${context.parsed.y.toFixed(2)} ${unit}`, + }, + }, + }, + scales: { + x: { + type: 'time', + time: { unit: 'minute', displayFormats: { minute: 'HH:mm' } }, + title: { display: true, text: 'Time' }, + }, + y: { + beginAtZero: true, + title: { display: true, text: unit }, + }, + }, + }; + } + + fillMissingDataPoints(data, startTime, endTime) { + const filledData = []; + let currentTime = new Date(startTime); + const endTimeDate = new Date(endTime); + + while (currentTime <= endTimeDate) { + const existingPoint = data.find((point) => Math.abs(point.x.getTime() - currentTime.getTime()) < 60000); + if (existingPoint) { + filledData.push(existingPoint); + } else { + filledData.push({ x: new Date(currentTime), y: null }); + } + currentTime.setMinutes(currentTime.getMinutes() + 1); + } + + return filledData; + } + + updateCharts(metrics, startTime, endTime) { + // 检查metrics是否为null或undefined + if (!metrics) { + // 如果为null,则更新所有图表为空 + Object.values(this.charts).forEach((chart) => { + chart.data.datasets = [ + { + label: 'No Data', + data: [], + }, + ]; + chart.update(); + }); + return; + } + // 首先按时间正序排列数据 + metrics.sort((a, b) => a.timestamp - b.timestamp); + // 按 label-remote 分组 + const groupedMetrics = this.groupMetricsByLabelRemote(metrics); + console.log('groupedMetrics', groupedMetrics); + + // 预处理所有指标的数据 + const processedData = {}; + + Object.keys(this.charts).forEach((key) => { + processedData[key] = groupedMetrics.map((group, index) => { + const data = group.metrics.map((m) => ({ + x: new Date(m.timestamp * 1000), + y: this.getMetricValue(key, m), + })); + const filledData = this.fillMissingDataPoints(data, startTime, endTime); + return { + label: `${group.label} - ${group.remote}`, + borderColor: this.getColor(index), + backgroundColor: this.getColor(index, 0.2), + borderWidth: 2, + data: filledData, + }; + }); + }); + + // 更新每个图表 + Object.entries(this.charts).forEach(([key, chart]) => { + chart.data.datasets = processedData[key]; + chart.update(); + }); + } + + groupMetricsByLabelRemote(metrics) { + const groups = {}; + metrics.forEach((metric) => { + const key = `${metric.label}-${metric.remote}`; + if (!groups[key]) { + groups[key] = { label: metric.label, remote: metric.remote, metrics: [] }; + } + groups[key].metrics.push(metric); + }); + return Object.values(groups); + } + + getMetricValue(metricType, metric) { + switch (metricType) { + case 'connectionCount': + return metric.tcp_connection_count + metric.udp_connection_count; + case 'handshakeDuration': + return Math.max(metric.tcp_handshake_duration, metric.udp_handshake_duration); + case 'pingLatency': + return metric.ping_latency; + case 'networkTransmitBytes': + return (metric.tcp_network_transmit_bytes + metric.udp_network_transmit_bytes) / Config.BYTE_TO_MB; + default: + return 0; + } + } + + getColor(index, alpha = 1) { + const colors = [ + `rgba(255, 99, 132, ${alpha})`, + `rgba(54, 162, 235, ${alpha})`, + `rgba(255, 206, 86, ${alpha})`, + `rgba(75, 192, 192, ${alpha})`, + `rgba(153, 102, 255, ${alpha})`, + `rgba(255, 159, 64, ${alpha})`, + ]; + return colors[index % colors.length]; + } +} + +class FilterManager { + constructor(chartManager, dateRangeManager) { + this.chartManager = chartManager; + this.dateRangeManager = dateRangeManager; + this.$labelFilter = $('#labelFilter'); + this.$remoteFilter = $('#remoteFilter'); + this.relayConfigs = []; + this.currentStartDate = null; + this.currentEndDate = null; + this.setupEventListeners(); + this.loadFilters(); + } + + setupEventListeners() { + this.$labelFilter.on('change', () => this.onLabelChange()); + this.$remoteFilter.on('change', () => this.applyFilters()); + } + + async loadFilters() { + const config = await ApiService.fetchConfig(); + if (config && config.relay_configs) { + this.relayConfigs = config.relay_configs; + this.populateLabelFilter(); + this.onLabelChange(); // Initialize remotes for the first label + } + } + + populateLabelFilter() { + const labels = [...new Set(this.relayConfigs.map((config) => config.label))]; + this.populateFilter(this.$labelFilter, labels); + } + + onLabelChange() { + const selectedLabel = this.$labelFilter.val(); + const remotes = this.getRemotesForLabel(selectedLabel); + this.populateFilter(this.$remoteFilter, remotes); + this.applyFilters(); + } + + getRemotesForLabel(label) { + const config = this.relayConfigs.find((c) => c.label === label); + return config ? config.remotes : []; + } + + populateFilter($select, options) { + $select.empty().append($('
- - + + + diff --git a/echo/internal/web/templates/_rule_metrics_dash.html b/echo/internal/web/templates/_rule_metrics_dash.html new file mode 100644 index 0000000000..a0f71b8fb9 --- /dev/null +++ b/echo/internal/web/templates/_rule_metrics_dash.html @@ -0,0 +1,82 @@ +
+
+

Rule Metrics

+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ + diff --git a/echo/internal/web/templates/index.html b/echo/internal/web/templates/index.html index 58b7e60ea2..31c71f73a5 100644 --- a/echo/internal/web/templates/index.html +++ b/echo/internal/web/templates/index.html @@ -1,4 +1,4 @@ - + {{template "_head.html" .}} @@ -32,7 +32,7 @@ - {{template "_metrics.html" .}} + {{template "_node_metrics_dash.html" .}} diff --git a/echo/internal/web/templates/logs.html b/echo/internal/web/templates/logs.html new file mode 100644 index 0000000000..4be8824392 --- /dev/null +++ b/echo/internal/web/templates/logs.html @@ -0,0 +1,176 @@ + + + {{template "_head.html" .}} + + + + {{ template "_navbar.html" . }} +
+
+

Real-time Logs

+
+
+
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ + + + diff --git a/echo/internal/web/templates/rule_list.html b/echo/internal/web/templates/rule_list.html index bebf6e18c1..6b1a15e21b 100644 --- a/echo/internal/web/templates/rule_list.html +++ b/echo/internal/web/templates/rule_list.html @@ -1,4 +1,4 @@ - + {{template "_head.html" .}} @@ -53,7 +53,7 @@ response.msg + // Use 'msg' as per Go struct ' (Latency: ' + response.latency + // Ensure this matches the Go struct field name - 'ms)', + 'ms)' ); } else { // If error code is not 0, show error message diff --git a/echo/internal/web/templates/rule_metrics.html b/echo/internal/web/templates/rule_metrics.html new file mode 100644 index 0000000000..601174f908 --- /dev/null +++ b/echo/internal/web/templates/rule_metrics.html @@ -0,0 +1,14 @@ + + + {{template "_head.html" .}} + + {{ template "_navbar.html" . }} +
+
+

Rule Metrics

+ + {{template "_rule_metrics_dash.html" .}} +
+
+ + diff --git a/echo/pkg/log/log.go b/echo/pkg/log/log.go index ca76e3d1ae..98eacee367 100644 --- a/echo/pkg/log/log.go +++ b/echo/pkg/log/log.go @@ -11,6 +11,8 @@ import ( var ( doOnce sync.Once globalInitd bool + + globalWebSocketSyncher *WebSocketLogSyncher ) func initLogger(logLevel string, replaceGlobal bool) (*zap.Logger, error) { @@ -18,8 +20,8 @@ func initLogger(logLevel string, replaceGlobal bool) (*zap.Logger, error) { if err := level.UnmarshalText([]byte(logLevel)); err != nil { return nil, err } - writers := []zapcore.WriteSyncer{zapcore.AddSync(os.Stdout)} - encoder := zapcore.EncoderConfig{ + + consoleEncoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", MessageKey: "msg", @@ -27,12 +29,29 @@ func initLogger(logLevel string, replaceGlobal bool) (*zap.Logger, error) { EncodeLevel: zapcore.LowercaseColorLevelEncoder, EncodeTime: zapcore.RFC3339TimeEncoder, EncodeName: zapcore.FullNameEncoder, - } - core := zapcore.NewCore( - zapcore.NewConsoleEncoder(encoder), - zapcore.NewMultiWriteSyncer(writers...), - level, - ) + }) + stdoutCore := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), level) + + jsonEncoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + }) + + globalWebSocketSyncher = NewWebSocketLogSyncher() + wsCore := zapcore.NewCore(jsonEncoder, globalWebSocketSyncher, level) + + // 合并两个 core + core := zapcore.NewTee(stdoutCore, wsCore) + l := zap.New(core) if replaceGlobal { zap.ReplaceGlobals(l) diff --git a/echo/pkg/log/ws.go b/echo/pkg/log/ws.go new file mode 100644 index 0000000000..c7ed8ea5b4 --- /dev/null +++ b/echo/pkg/log/ws.go @@ -0,0 +1,52 @@ +package log + +import ( + "encoding/json" + "net" + "sync" + + "github.com/gobwas/ws" +) + +type WebSocketLogSyncher struct { + conn net.Conn + mu sync.Mutex +} + +func NewWebSocketLogSyncher() *WebSocketLogSyncher { + return &WebSocketLogSyncher{} +} + +func (wsSync *WebSocketLogSyncher) Write(p []byte) (n int, err error) { + wsSync.mu.Lock() + defer wsSync.mu.Unlock() + + if wsSync.conn != nil { + var logEntry map[string]interface{} + if err := json.Unmarshal(p, &logEntry); err == nil { + jsonData, _ := json.Marshal(logEntry) + _ = ws.WriteFrame(wsSync.conn, ws.NewTextFrame(jsonData)) + } + + if err != nil { + return 0, err + } + } + return len(p), nil +} + +func (wsSync *WebSocketLogSyncher) Sync() error { + return nil +} + +func (wsSync *WebSocketLogSyncher) SetWSConn(conn net.Conn) { + wsSync.mu.Lock() + defer wsSync.mu.Unlock() + wsSync.conn = conn +} + +func SetWebSocketConn(conn net.Conn) { + if globalWebSocketSyncher != nil { + globalWebSocketSyncher.SetWSConn(conn) + } +} diff --git a/echo/pkg/metric_reader/node.go b/echo/pkg/metric_reader/node.go new file mode 100644 index 0000000000..c97a6de5c4 --- /dev/null +++ b/echo/pkg/metric_reader/node.go @@ -0,0 +1,165 @@ +package metric_reader + +import ( + "fmt" + "math" + "strings" + "time" + + dto "github.com/prometheus/client_model/go" +) + +const ( + metricCPUSecondsTotal = "node_cpu_seconds_total" + metricLoad1 = "node_load1" + metricLoad5 = "node_load5" + metricLoad15 = "node_load15" + metricMemoryTotalBytes = "node_memory_total_bytes" + metricMemoryActiveBytes = "node_memory_active_bytes" + metricMemoryWiredBytes = "node_memory_wired_bytes" + metricMemoryMemTotalBytes = "node_memory_MemTotal_bytes" + metricMemoryMemAvailableBytes = "node_memory_MemAvailable_bytes" + metricFilesystemSizeBytes = "node_filesystem_size_bytes" + metricFilesystemAvailBytes = "node_filesystem_avail_bytes" + metricNetworkReceiveBytesTotal = "node_network_receive_bytes_total" + metricNetworkTransmitBytesTotal = "node_network_transmit_bytes_total" +) + +type NodeMetrics struct { + // cpu + CpuCoreCount int `json:"cpu_core_count"` + CpuLoadInfo string `json:"cpu_load_info"` + CpuUsagePercent float64 `json:"cpu_usage_percent"` + + // memory + MemoryTotalBytes int64 `json:"memory_total_bytes"` + MemoryUsageBytes int64 `json:"memory_usage_bytes"` + MemoryUsagePercent float64 `json:"memory_usage_percent"` + + // disk + DiskTotalBytes int64 `json:"disk_total_bytes"` + DiskUsageBytes int64 `json:"disk_usage_bytes"` + DiskUsagePercent float64 `json:"disk_usage_percent"` + + // network + NetworkReceiveBytesTotal int64 `json:"network_receive_bytes_total"` + NetworkTransmitBytesTotal int64 `json:"network_transmit_bytes_total"` + NetworkReceiveBytesRate float64 `json:"network_receive_bytes_rate"` + NetworkTransmitBytesRate float64 `json:"network_transmit_bytes_rate"` + + SyncTime time.Time +} +type cpuStats struct { + totalTime float64 + idleTime float64 + cores int +} + +func (b *readerImpl) ParseNodeMetrics(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) error { + isMac := metricMap[metricMemoryTotalBytes] != nil + cpu := &cpuStats{} + + b.processCPUMetrics(metricMap, cpu) + b.processMemoryMetrics(metricMap, nm, isMac) + b.processDiskMetrics(metricMap, nm) + b.processNetworkMetrics(metricMap, nm) + b.processLoadMetrics(metricMap, nm) + + b.calculateFinalMetrics(nm, cpu) + + return nil +} + +func (b *readerImpl) processCPUMetrics(metricMap map[string]*dto.MetricFamily, cpu *cpuStats) { + if cpuMetric, ok := metricMap[metricCPUSecondsTotal]; ok { + for _, metric := range cpuMetric.Metric { + value := getMetricValue(metric, cpuMetric.GetType()) + cpu.totalTime += value + if getLabel(metric, "mode") == "idle" { + cpu.idleTime += value + cpu.cores++ + } + } + } +} + +func (b *readerImpl) processMemoryMetrics(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics, isMac bool) { + if isMac { + nm.MemoryTotalBytes = sumInt64Metric(metricMap, metricMemoryTotalBytes) + nm.MemoryUsageBytes = sumInt64Metric(metricMap, metricMemoryActiveBytes) + sumInt64Metric(metricMap, metricMemoryWiredBytes) + } else { + nm.MemoryTotalBytes = sumInt64Metric(metricMap, metricMemoryMemTotalBytes) + availableMemory := sumInt64Metric(metricMap, metricMemoryMemAvailableBytes) + nm.MemoryUsageBytes = nm.MemoryTotalBytes - availableMemory + } +} + +func (b *readerImpl) processDiskMetrics(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) { + nm.DiskTotalBytes = sumInt64Metric(metricMap, metricFilesystemSizeBytes) + availableDisk := sumInt64Metric(metricMap, metricFilesystemAvailBytes) + nm.DiskUsageBytes = nm.DiskTotalBytes - availableDisk +} + +func (b *readerImpl) processNetworkMetrics(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) { + nm.NetworkReceiveBytesTotal = sumInt64Metric(metricMap, metricNetworkReceiveBytesTotal) + nm.NetworkTransmitBytesTotal = sumInt64Metric(metricMap, metricNetworkTransmitBytesTotal) +} + +func (b *readerImpl) processLoadMetrics(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) { + loads := []string{metricLoad1, metricLoad5, metricLoad15} + for _, load := range loads { + value := sumFloat64Metric(metricMap, load) + nm.CpuLoadInfo += fmt.Sprintf("%.2f|", value) + } + nm.CpuLoadInfo = strings.TrimRight(nm.CpuLoadInfo, "|") +} + +func (b *readerImpl) calculateFinalMetrics(nm *NodeMetrics, cpu *cpuStats) { + nm.CpuCoreCount = cpu.cores + nm.CpuUsagePercent = 100 * (cpu.totalTime - cpu.idleTime) / cpu.totalTime + nm.MemoryUsagePercent = 100 * float64(nm.MemoryUsageBytes) / float64(nm.MemoryTotalBytes) + nm.DiskUsagePercent = 100 * float64(nm.DiskUsageBytes) / float64(nm.DiskTotalBytes) + + nm.CpuUsagePercent = math.Round(nm.CpuUsagePercent*100) / 100 + nm.MemoryUsagePercent = math.Round(nm.MemoryUsagePercent*100) / 100 + nm.DiskUsagePercent = math.Round(nm.DiskUsagePercent*100) / 100 + + if b.lastMetrics != nil { + duration := time.Since(b.lastMetrics.SyncTime).Seconds() + if duration > 0.1 { + nm.NetworkReceiveBytesRate = math.Max(0, float64(nm.NetworkReceiveBytesTotal-b.lastMetrics.NetworkReceiveBytesTotal)/duration) + nm.NetworkTransmitBytesRate = math.Max(0, float64(nm.NetworkTransmitBytesTotal-b.lastMetrics.NetworkTransmitBytesTotal)/duration) + nm.NetworkReceiveBytesRate = math.Round(nm.NetworkReceiveBytesRate) + nm.NetworkTransmitBytesRate = math.Round(nm.NetworkTransmitBytesRate) + } + } +} + +func sumInt64Metric(metricMap map[string]*dto.MetricFamily, metricName string) int64 { + ret := int64(0) + if metric, ok := metricMap[metricName]; ok && len(metric.Metric) > 0 { + for _, m := range metric.Metric { + ret += int64(getMetricValue(m, metric.GetType())) + } + } + return ret +} + +func sumFloat64Metric(metricMap map[string]*dto.MetricFamily, metricName string) float64 { + ret := float64(0) + if metric, ok := metricMap[metricName]; ok && len(metric.Metric) > 0 { + for _, m := range metric.Metric { + ret += getMetricValue(m, metric.GetType()) + } + } + return 0 +} + +func getLabel(metric *dto.Metric, name string) string { + for _, label := range metric.Label { + if label.GetName() == name { + return label.GetValue() + } + } + return "" +} diff --git a/echo/pkg/metric_reader/reader.go b/echo/pkg/metric_reader/reader.go index 2b1c7564b0..4d95639052 100644 --- a/echo/pkg/metric_reader/reader.go +++ b/echo/pkg/metric_reader/reader.go @@ -2,25 +2,28 @@ package metric_reader import ( "context" - "fmt" "io" "net/http" "strings" "time" + "github.com/pkg/errors" dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "go.uber.org/zap" ) type Reader interface { - ReadOnce(ctx context.Context) (*NodeMetrics, error) + ReadOnce(ctx context.Context) (*NodeMetrics, map[string]*RuleMetrics, error) } type readerImpl struct { - metricsURL string - httpClient *http.Client - lastMetrics *NodeMetrics + metricsURL string + httpClient *http.Client + + lastMetrics *NodeMetrics + lastRuleMetrics map[string]*RuleMetrics // key: label value: RuleMetrics + l *zap.SugaredLogger } func NewReader(metricsURL string) *readerImpl { @@ -28,267 +31,47 @@ func NewReader(metricsURL string) *readerImpl { return &readerImpl{ httpClient: c, metricsURL: metricsURL, + l: zap.S().Named("metric_reader"), } } -func (b *readerImpl) parsePingInfo(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) error { - metric, ok := metricMap["ehco_ping_response_duration_seconds"] - if !ok { - // this metric is optional when enable_ping = false - zap.S().Debug("ping metric not found") - return nil - } - for _, m := range metric.Metric { - g := m.GetHistogram() - ip := "" - val := float64(g.GetSampleSum()) / float64(g.GetSampleCount()) * 1000 // to ms - for _, label := range m.GetLabel() { - if label.GetName() == "ip" { - ip = label.GetValue() - } - } - nm.PingMetrics = append(nm.PingMetrics, PingMetric{Latency: val, Target: ip}) - } - return nil -} - -func (b *readerImpl) parseCpuInfo(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) error { - handleMetric := func(metricName string, handleValue func(float64, string)) error { - metric, ok := metricMap[metricName] - if !ok { - return fmt.Errorf("%s not found", metricName) - } - - for _, m := range metric.Metric { - g := m.GetCounter() - mode := "" - for _, label := range m.GetLabel() { - if label.GetName() == "mode" { - mode = label.GetValue() - } - } - handleValue(g.GetValue(), mode) - } - return nil - } - - var ( - totalIdleTime float64 - totalCpuTime float64 - cpuCores int - ) - - err := handleMetric("node_cpu_seconds_total", func(val float64, mode string) { - totalCpuTime += val - if mode == "idle" { - totalIdleTime += val - cpuCores++ - } - }) +func (b *readerImpl) ReadOnce(ctx context.Context) (*NodeMetrics, map[string]*RuleMetrics, error) { + metricMap, err := b.fetchMetrics(ctx) if err != nil { - return err + return nil, nil, errors.Wrap(err, "failed to fetch metrics") + } + nm := &NodeMetrics{SyncTime: time.Now()} + if err := b.ParseNodeMetrics(metricMap, nm); err != nil { + return nil, nil, err } - nm.CpuCoreCount = cpuCores - nm.CpuUsagePercent = 100 * (totalCpuTime - totalIdleTime) / totalCpuTime - for _, load := range []string{"1", "5", "15"} { - loadMetricName := fmt.Sprintf("node_load%s", load) - loadMetric, ok := metricMap[loadMetricName] - if !ok { - return fmt.Errorf("%s not found", loadMetricName) - } - for _, m := range loadMetric.Metric { - g := m.GetGauge() - nm.CpuLoadInfo += fmt.Sprintf("%.2f|", g.GetValue()) - } - } - nm.CpuLoadInfo = strings.TrimRight(nm.CpuLoadInfo, "|") - return nil -} - -func (b *readerImpl) parseMemoryInfo(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) error { - handleMetric := func(metricName string, handleValue func(float64)) error { - metric, ok := metricMap[metricName] - if !ok { - return fmt.Errorf("%s not found", metricName) - } - for _, m := range metric.Metric { - g := m.GetGauge() - handleValue(g.GetValue()) - } - return nil - } - - isMac := false - if _, ok := metricMap["node_memory_total_bytes"]; ok { - isMac = true - } - - if isMac { - err := handleMetric("node_memory_total_bytes", func(val float64) { - nm.MemoryTotalBytes = val - }) - if err != nil { - return err - } - - err = handleMetric("node_memory_active_bytes", func(val float64) { - nm.MemoryUsageBytes += val - }) - if err != nil { - return err - } - - err = handleMetric("node_memory_wired_bytes", func(val float64) { - nm.MemoryUsageBytes += val - }) - if err != nil { - return err - } - } else { - err := handleMetric("node_memory_MemTotal_bytes", func(val float64) { - nm.MemoryTotalBytes = val - }) - if err != nil { - return err - } - - err = handleMetric("node_memory_MemAvailable_bytes", func(val float64) { - nm.MemoryUsageBytes = nm.MemoryTotalBytes - val - }) - if err != nil { - return err - } - } - if nm.MemoryTotalBytes != 0 { - nm.MemoryUsagePercent = 100 * nm.MemoryUsageBytes / nm.MemoryTotalBytes - } - return nil -} - -func (b *readerImpl) parseDiskInfo(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) error { - handleMetric := func(metricName string, handleValue func(float64)) error { - forMac := false - diskMap := make(map[string]float64) - metric, ok := metricMap[metricName] - if !ok { - return fmt.Errorf("%s not found", metricName) - } - for _, m := range metric.Metric { - g := m.GetGauge() - disk := "" - for _, label := range m.GetLabel() { - if label.GetName() == "device" { - disk = getDiskName(label.GetValue()) - } - if label.GetName() == "fstype" && label.GetValue() == "apfs" { - forMac = true - } - } - diskMap[disk] = g.GetValue() - } - // 对于 macos 的 apfs 文件系统,可能会有多个相同大小的磁盘,这是因为 apfs 磁盘(卷)会共享物理磁盘 - seenVal := map[float64]bool{} - for _, val := range diskMap { - if seenVal[val] && forMac { - continue - } - handleValue(val) - seenVal[val] = true - } - return nil - } - - err := handleMetric("node_filesystem_size_bytes", func(val float64) { - nm.DiskTotalBytes += val - }) - if err != nil { - return err - } - - var availBytes float64 - err = handleMetric("node_filesystem_avail_bytes", func(val float64) { - availBytes += val - }) - if err != nil { - return err - } - nm.DiskUsageBytes = nm.DiskTotalBytes - availBytes - if nm.DiskTotalBytes != 0 { - nm.DiskUsagePercent = 100 * nm.DiskUsageBytes / nm.DiskTotalBytes - } - return nil -} - -func (b *readerImpl) parseNetworkInfo(metricMap map[string]*dto.MetricFamily, nm *NodeMetrics) error { - now := time.Now() - handleMetric := func(metricName string, handleValue func(float64)) error { - metric, ok := metricMap[metricName] - if !ok { - return fmt.Errorf("%s not found", metricName) - } - for _, m := range metric.Metric { - g := m.GetCounter() - handleValue(g.GetValue()) - } - return nil - } - - err := handleMetric("node_network_receive_bytes_total", func(val float64) { - nm.NetworkReceiveBytesTotal += val - }) - if err != nil { - return err - } - - err = handleMetric("node_network_transmit_bytes_total", func(val float64) { - nm.NetworkTransmitBytesTotal += val - }) - if err != nil { - return err - } - - if b.lastMetrics != nil { - passedTime := now.Sub(b.lastMetrics.SyncTime).Seconds() - nm.NetworkReceiveBytesRate = (nm.NetworkReceiveBytesTotal - b.lastMetrics.NetworkReceiveBytesTotal) / passedTime - nm.NetworkTransmitBytesRate = (nm.NetworkTransmitBytesTotal - b.lastMetrics.NetworkTransmitBytesTotal) / passedTime - } - return nil -} - -func (b *readerImpl) ReadOnce(ctx context.Context) (*NodeMetrics, error) { - response, err := b.httpClient.Get(b.metricsURL) - if err != nil { - return nil, err - } - defer response.Body.Close() - - body, err := io.ReadAll(response.Body) - if err != nil { - return nil, err - } - var parser expfmt.TextParser - parsed, err := parser.TextToMetricFamilies(strings.NewReader(string(body))) - if err != nil { - return nil, err - } - nm := &NodeMetrics{SyncTime: time.Now(), PingMetrics: []PingMetric{}} - if err := b.parseCpuInfo(parsed, nm); err != nil { - return nil, err - } - if err := b.parseMemoryInfo(parsed, nm); err != nil { - return nil, err - } - if err := b.parseDiskInfo(parsed, nm); err != nil { - return nil, err - } - if err := b.parseNetworkInfo(parsed, nm); err != nil { - return nil, err - } - if err := b.parsePingInfo(parsed, nm); err != nil { - return nil, err + rm := make(map[string]*RuleMetrics) + if err := b.ParseRuleMetrics(metricMap, rm); err != nil { + return nil, nil, err } b.lastMetrics = nm - return nm, nil + b.lastRuleMetrics = rm + return nm, rm, nil +} + +func (r *readerImpl) fetchMetrics(ctx context.Context) (map[string]*dto.MetricFamily, error) { + req, err := http.NewRequestWithContext(ctx, "GET", r.metricsURL, nil) + if err != nil { + return nil, errors.Wrap(err, "failed to create request") + } + + resp, err := r.httpClient.Do(req) + if err != nil { + return nil, errors.Wrap(err, "failed to send request") + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrap(err, "failed to read response body") + } + + var parser expfmt.TextParser + return parser.TextToMetricFamilies(strings.NewReader(string(body))) } diff --git a/echo/pkg/metric_reader/rule.go b/echo/pkg/metric_reader/rule.go new file mode 100644 index 0000000000..c9fc1e664b --- /dev/null +++ b/echo/pkg/metric_reader/rule.go @@ -0,0 +1,146 @@ +package metric_reader + +import ( + "time" + + dto "github.com/prometheus/client_model/go" +) + +const ( + metricConnectionCount = "ehco_traffic_current_connection_count" + metricNetworkTransmit = "ehco_traffic_network_transmit_bytes" + metricPingResponse = "ehco_ping_response_duration_milliseconds" + metricHandshakeDuration = "ehco_traffic_handshake_duration_milliseconds" + + labelKey = "label" + remoteKey = "remote" + connTypeKey = "conn_type" + flowKey = "flow" + ipKey = "ip" +) + +type PingMetric struct { + Latency int64 `json:"latency"` // in ms + Target string `json:"target"` +} + +type RuleMetrics struct { + Label string // rule label + + PingMetrics map[string]*PingMetric // key: remote + + TCPConnectionCount map[string]int64 // key: remote + TCPHandShakeDuration map[string]int64 // key: remote in ms + TCPNetworkTransmitBytes map[string]int64 // key: remote + + UDPConnectionCount map[string]int64 // key: remote + UDPHandShakeDuration map[string]int64 // key: remote in ms + UDPNetworkTransmitBytes map[string]int64 // key: remote + + SyncTime time.Time +} + +func (b *readerImpl) ParseRuleMetrics(metricMap map[string]*dto.MetricFamily, rm map[string]*RuleMetrics) error { + requiredMetrics := []string{ + metricConnectionCount, + metricNetworkTransmit, + metricPingResponse, + metricHandshakeDuration, + } + + for _, metricName := range requiredMetrics { + metricFamily, ok := metricMap[metricName] + if !ok { + continue + } + + for _, metric := range metricFamily.Metric { + labels := getLabelMap(metric) + value := int64(getMetricValue(metric, metricFamily.GetType())) + label, ok := labels[labelKey] + if !ok || label == "" { + continue + } + + ruleMetric := b.ensureRuleMetric(rm, label) + + switch metricName { + case metricConnectionCount: + b.updateConnectionCount(ruleMetric, labels, value) + case metricNetworkTransmit: + b.updateNetworkTransmit(ruleMetric, labels, value) + case metricPingResponse: + b.updatePingMetrics(ruleMetric, labels, value) + case metricHandshakeDuration: + b.updateHandshakeDuration(ruleMetric, labels, value) + } + } + } + return nil +} + +func (b *readerImpl) ensureRuleMetric(rm map[string]*RuleMetrics, label string) *RuleMetrics { + if _, ok := rm[label]; !ok { + rm[label] = &RuleMetrics{ + Label: label, + PingMetrics: make(map[string]*PingMetric), + TCPConnectionCount: make(map[string]int64), + TCPHandShakeDuration: make(map[string]int64), + TCPNetworkTransmitBytes: make(map[string]int64), + UDPConnectionCount: make(map[string]int64), + UDPHandShakeDuration: make(map[string]int64), + UDPNetworkTransmitBytes: make(map[string]int64), + + SyncTime: time.Now(), + } + } + return rm[label] +} + +func (b *readerImpl) updateConnectionCount(rm *RuleMetrics, labels map[string]string, value int64) { + key := labels[remoteKey] + switch labels[connTypeKey] { + case "tcp": + rm.TCPConnectionCount[key] = value + default: + rm.UDPConnectionCount[key] = value + } +} + +func (b *readerImpl) updateNetworkTransmit(rm *RuleMetrics, labels map[string]string, value int64) { + if labels[flowKey] == "read" { + key := labels[remoteKey] + switch labels[connTypeKey] { + case "tcp": + rm.TCPNetworkTransmitBytes[key] += value + default: + rm.UDPNetworkTransmitBytes[key] += value + } + } +} + +func (b *readerImpl) updatePingMetrics(rm *RuleMetrics, labels map[string]string, value int64) { + remote := labels[remoteKey] + rm.PingMetrics[remote] = &PingMetric{ + Latency: value, + Target: labels[ipKey], + } +} + +func (b *readerImpl) updateHandshakeDuration(rm *RuleMetrics, labels map[string]string, value int64) { + key := labels[remoteKey] + switch labels[connTypeKey] { + case "tcp": + rm.TCPHandShakeDuration[key] = value + default: + rm.UDPHandShakeDuration[key] = value + } +} + +func getLabelMap(metric *dto.Metric) map[string]string { + labels := make(map[string]string) + for _, label := range metric.Label { + labels[label.GetName()] = label.GetValue() + } + return labels +} diff --git a/echo/pkg/metric_reader/types.go b/echo/pkg/metric_reader/types.go deleted file mode 100644 index 1195a7ceaf..0000000000 --- a/echo/pkg/metric_reader/types.go +++ /dev/null @@ -1,38 +0,0 @@ -package metric_reader - -import ( - "time" -) - -type NodeMetrics struct { - // cpu - CpuCoreCount int `json:"cpu_core_count"` - CpuLoadInfo string `json:"cpu_load_info"` - CpuUsagePercent float64 `json:"cpu_usage_percent"` - - // memory - MemoryTotalBytes float64 `json:"memory_total_bytes"` - MemoryUsageBytes float64 `json:"memory_usage_bytes"` - MemoryUsagePercent float64 `json:"memory_usage_percent"` - - // disk - DiskTotalBytes float64 `json:"disk_total_bytes"` - DiskUsageBytes float64 `json:"disk_usage_bytes"` - DiskUsagePercent float64 `json:"disk_usage_percent"` - - // network - NetworkReceiveBytesTotal float64 `json:"network_receive_bytes_total"` - NetworkTransmitBytesTotal float64 `json:"network_transmit_bytes_total"` - NetworkReceiveBytesRate float64 `json:"network_receive_bytes_rate"` - NetworkTransmitBytesRate float64 `json:"network_transmit_bytes_rate"` - - // ping - PingMetrics []PingMetric `json:"ping_metrics"` - - SyncTime time.Time -} - -type PingMetric struct { - Latency float64 `json:"latency"` // in ms - Target string `json:"target"` -} diff --git a/echo/pkg/metric_reader/utils.go b/echo/pkg/metric_reader/utils.go index 9749148197..290c2abc27 100644 --- a/echo/pkg/metric_reader/utils.go +++ b/echo/pkg/metric_reader/utils.go @@ -1,22 +1,46 @@ package metric_reader -import "regexp" +import ( + "math" -// parse disk name from device path,such as: -// e.g. /dev/disk1s1 -> disk1 -// e.g. /dev/disk1s2 -> disk1 -// e.g. ntfs://disk1s1 -> disk1 -// e.g. ntfs://disk1s2 -> disk1 -// e.g. /dev/sda1 -> sda -// e.g. /dev/sda2 -> sda -var diskNameRegex = regexp.MustCompile(`/dev/disk(\d+)|ntfs://disk(\d+)|/dev/sd[a-zA-Z]`) + dto "github.com/prometheus/client_model/go" +) -func getDiskName(devicePath string) string { - matches := diskNameRegex.FindStringSubmatch(devicePath) - for _, match := range matches { - if match != "" { - return match +func calculatePercentile(histogram *dto.Histogram, percentile float64) float64 { + if histogram == nil { + return 0 + } + totalSamples := histogram.GetSampleCount() + targetSample := percentile * float64(totalSamples) + cumulativeCount := uint64(0) + var lastBucketBound float64 + + for _, bucket := range histogram.Bucket { + cumulativeCount += bucket.GetCumulativeCount() + if float64(cumulativeCount) >= targetSample { + // Linear interpolation between bucket boundaries + if bucket.GetCumulativeCount() > 0 && lastBucketBound != bucket.GetUpperBound() { + return lastBucketBound + (float64(targetSample-float64(cumulativeCount-bucket.GetCumulativeCount()))/float64(bucket.GetCumulativeCount()))*(bucket.GetUpperBound()-lastBucketBound) + } else { + return bucket.GetUpperBound() + } + } + lastBucketBound = bucket.GetUpperBound() + } + return math.NaN() +} + +func getMetricValue(metric *dto.Metric, metricType dto.MetricType) float64 { + switch metricType { + case dto.MetricType_COUNTER: + return metric.Counter.GetValue() + case dto.MetricType_GAUGE: + return metric.Gauge.GetValue() + case dto.MetricType_HISTOGRAM: + histogram := metric.Histogram + if histogram != nil { + return calculatePercentile(histogram, 0.9) } } - return "" + return 0 } diff --git a/echo/test/echo/echo.go b/echo/test/echo/echo.go index 879549f6f3..5186b2b09b 100644 --- a/echo/test/echo/echo.go +++ b/echo/test/echo/echo.go @@ -2,6 +2,7 @@ package echo import ( + "errors" "fmt" "io" "log" @@ -118,6 +119,10 @@ func (s *EchoServer) handleTCPConn(conn net.Conn) { } } +func isClosedConnError(err error) bool { + return errors.Is(err, net.ErrClosed) +} + func (s *EchoServer) serveUDP() { defer s.wg.Done() buf := make([]byte, 1024) @@ -128,6 +133,9 @@ func (s *EchoServer) serveUDP() { default: n, remoteAddr, err := s.udpConn.ReadFromUDP(buf) if err != nil { + if isClosedConnError(err) { + break + } s.logger.Errorf("Error reading UDP: %v", err) continue } diff --git a/mieru/README.md b/mieru/README.md index be909e415a..4bcb625b95 100644 --- a/mieru/README.md +++ b/mieru/README.md @@ -20,25 +20,27 @@ For an explanation of the mieru protocol, see [mieru Proxy Protocol](./docs/prot ## Features 1. mieru uses a high-strength XChaCha20-Poly1305 encryption algorithm that generates encryption keys based on username, password and system time. -2. mieru implements complete encryption of all transmitted content between the client and the proxy server, without transmitting any plaintext information. -3. When mieru sends a packet, it is padded with random bytes at the end. Even when the same content is transmitted, the packet size varies. -4. When using the UDP transport protocol, mieru does not require a handshake between client and server. -5. When the server can not decrypt the data sent by the client, no content is returned. it is difficult for GFW to discover the mieru service through active probing. -6. mieru supports multiple users sharing a single proxy server. -7. mieru supports IPv4 and IPv6. -8. mieru provides socks5, HTTP and HTTPS proxy. -9. The client software supports Windows, Mac OS, Linux and Android. For Android users, please use [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid) version 1.3.1 or above, and install [mieru plugin](https://github.com/enfein/NekoBoxPlugins). -10. The server software supports socks5 outbound (proxy chain). -11. If you need advanced features like global proxy or customized routing rules, you can use mieru as the backend of a proxy platform such as [Xray](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box). +1. mieru implements complete encryption of all transmitted content between the client and the proxy server, without transmitting any plaintext information. +1. When mieru sends a packet, it is padded with random bytes at the end. Even when the same content is transmitted, the packet size varies. +1. When using the UDP transport protocol, mieru does not require a handshake between client and server. +1. When the server can not decrypt the data sent by the client, no content is returned. it is difficult for GFW to discover the mieru service through active probing. +1. mieru supports multiple users sharing a single proxy server. +1. mieru supports IPv4 and IPv6. +1. mieru provides socks5, HTTP and HTTPS proxy. +1. The server software supports socks5 outbound (proxy chain). +1. The client software supports Windows, Mac OS, Linux and Android. Android clients include + - [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid) version 1.3.1 or above, with [mieru plugin](https://github.com/enfein/NekoBoxPlugins). + - [Exclave](https://github.com/dyhkwong/Exclave), with mieru plugin. +1. If you need advanced features like global proxy or customized routing rules, you can use mieru as the backend of a proxy platform such as [Xray](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box). ## User Guide 1. [Server Installation & Configuration](./docs/server-install.md) -2. [Client Installation & Configuration](./docs/client-install.md) -3. [Client Installation & Configuration - OpenWrt](./docs/client-install-openwrt.md) -4. [Maintenance & Troubleshooting](./docs/operation.md) -5. [Security Guide](./docs/security.md) -6. [Compilation](./docs/compile.md) +1. [Client Installation & Configuration](./docs/client-install.md) +1. [Client Installation & Configuration - OpenWrt](./docs/client-install-openwrt.md) +1. [Maintenance & Troubleshooting](./docs/operation.md) +1. [Security Guide](./docs/security.md) +1. [Compilation](./docs/compile.md) ## Share diff --git a/mieru/README.zh_CN.md b/mieru/README.zh_CN.md index 9fcbf99704..287acfc96e 100644 --- a/mieru/README.zh_CN.md +++ b/mieru/README.zh_CN.md @@ -18,25 +18,27 @@ mieru 的翻墙原理与 shadowsocks / v2ray 等软件类似,在客户端和 ## 特性 1. 使用高强度的 XChaCha20-Poly1305 加密算法,基于用户名、密码和系统时间生成密钥。 -2. mieru 实现了客户端和代理服务器之间所有传输内容的完整加密,不传输任何明文信息。 -3. 当 mieru 发送数据包时,会在尾部填充随机信息。即便是传输相同的内容,数据包大小也不相同。 -4. 在使用 UDP 传输协议时,mieru 不需要客户端和服务器进行握手,即可直接发送数据。 -5. 当服务器无法解密客户端发送的数据时,不会返回任何内容。GFW 很难通过主动探测发现 mieru 服务。 -6. mieru 支持多个用户共享代理服务器。 -7. mieru 支持 IPv4 和 IPv6。 -8. mieru 提供 socks5, HTTP 和 HTTPS 代理。 -9. 客户端软件支持 Windows, Mac OS, Linux 和 Android 系统。Android 用户请使用 [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid) 1.3.1 及以上版本,并安装 [mieru 插件](https://github.com/enfein/NekoBoxPlugins)。 -10. 服务器软件支持 socks5 出站(链式代理)。 -11. 如果需要全局代理或自定义路由规则等高级功能,可以将 mieru 作为 [Xray](https://github.com/XTLS/Xray-core) 和 [sing-box](https://github.com/SagerNet/sing-box) 等代理平台的后端。 +1. mieru 实现了客户端和代理服务器之间所有传输内容的完整加密,不传输任何明文信息。 +1. 当 mieru 发送数据包时,会在尾部填充随机信息。即便是传输相同的内容,数据包大小也不相同。 +1. 在使用 UDP 传输协议时,mieru 不需要客户端和服务器进行握手,即可直接发送数据。 +1. 当服务器无法解密客户端发送的数据时,不会返回任何内容。GFW 很难通过主动探测发现 mieru 服务。 +1. mieru 支持多个用户共享代理服务器。 +1. mieru 支持 IPv4 和 IPv6。 +1. mieru 提供 socks5, HTTP 和 HTTPS 代理。 +1. 服务器软件支持 socks5 出站(链式代理)。 +1. 客户端软件支持 Windows, Mac OS, Linux 和 Android 系统。Android 客户端包括 + - [NekoBox](https://github.com/MatsuriDayo/NekoBoxForAndroid) 1.3.1 及以上版本,并安装 [mieru 插件](https://github.com/enfein/NekoBoxPlugins)。 + - [Exclave](https://github.com/dyhkwong/Exclave) 并安装 mieru 插件。 +1. 如果需要全局代理或自定义路由规则等高级功能,可以将 mieru 作为 [Xray](https://github.com/XTLS/Xray-core) 和 [sing-box](https://github.com/SagerNet/sing-box) 等代理平台的后端。 ## 使用教程 1. [服务器安装与配置](./docs/server-install.zh_CN.md) -2. [客户端安装与配置](./docs/client-install.zh_CN.md) -3. [客户端安装与配置 - OpenWrt](./docs/client-install-openwrt.zh_CN.md) -4. [运营维护与故障排查](./docs/operation.zh_CN.md) -5. [翻墙安全指南](./docs/security.zh_CN.md) -6. [编译](./docs/compile.zh_CN.md) +1. [客户端安装与配置](./docs/client-install.zh_CN.md) +1. [客户端安装与配置 - OpenWrt](./docs/client-install-openwrt.zh_CN.md) +1. [运营维护与故障排查](./docs/operation.zh_CN.md) +1. [翻墙安全指南](./docs/security.zh_CN.md) +1. [编译](./docs/compile.zh_CN.md) ## 分享 diff --git a/openwrt-packages/alist/files/alist.config b/openwrt-packages/alist/files/alist.config index 75aa0b3201..2ec1ed998c 100644 --- a/openwrt-packages/alist/files/alist.config +++ b/openwrt-packages/alist/files/alist.config @@ -1,9 +1,32 @@ + config alist - option 'enabled' '0' - option 'port' '5244' - option 'temp_dir' '/tmp/alist' - option 'ssl' '0' - option 'token_expires_in' '48' - option 'max_connections' '0' - option 'site_url' '' - option 'delayed_start' '0' + option enabled '0' + option port '5244' + option delayed_start '0' + option allow_wan '0' + option force '1' + option token_expires_in '48' + option max_connections '0' + option tls_insecure_skip_verify '1' + option data_dir '/etc/alist' + option temp_dir '/tmp/alist' + option log '1' + option log_max_size '10' + option log_max_backups '5' + option log_max_age '28' + option log_compress '0' + option database_type 'sqlite3' + option ssl '0' + option download_workers '5' + option download_max_retry '1' + option transfer_workers '5' + option transfer_max_retry '2' + option upload_workers '5' + option upload_max_retry '0' + option copy_workers '5' + option copy_max_retry '2' + option cors_allow_origins '*' + option cors_allow_methods '*' + option cors_allow_headers '*' + option s3 '0' + diff --git a/openwrt-packages/alist/files/alist.init b/openwrt-packages/alist/files/alist.init index 157aa47924..3553102468 100755 --- a/openwrt-packages/alist/files/alist.init +++ b/openwrt-packages/alist/files/alist.init @@ -10,21 +10,34 @@ LOG_FILE=/var/log/alist.log get_config() { config_get_bool enabled $1 enabled 1 config_get port $1 port 5244 - config_get log $1 log 1 - config_get site_url $1 site_url "" - config_get data_dir $1 data_dir "/etc/alist" - config_get temp_dir $1 temp_dir "/tmp/alist" - config_get ssl $1 ssl 0 - config_get ssl_cert $1 ssl_cert "" - config_get ssl_key $1 ssl_key "" - config_get token_expires_in $1 token_expires_in 48 config_get allow_wan $1 allow_wan 0 - config_get max_connections $1 max_connections 0 config_get delayed_start $1 delayed_start 0 - # mysql - config_get mysql $1 mysql 0 - config_get mysql_type $1 mysql_type "mysql" + config_get force $1 force 1 + config_get site_url $1 site_url "" + config_get cdn $1 cdn "" + config_get jwt_secret $1 jwt_secret "" + config_get data_dir $1 data_dir "/etc/alist" + config_get temp_dir $1 temp_dir "/tmp/alist" + config_get token_expires_in $1 token_expires_in 48 + config_get max_connections $1 max_connections 0 + config_get tls_insecure_skip_verify $1 tls_insecure_skip_verify 1 + + # log + config_get log $1 log 1 + config_get log_max_size $1 log_max_size 10 + config_get log_max_backups $1 log_max_backups 5 + config_get log_max_age $1 log_max_age 28 + config_get log_compress $1 log_compress 0 + + # scheme + config_get ssl $1 ssl 0 + config_get force_https $1 force_https 0 + config_get ssl_cert $1 ssl_cert "" + config_get ssl_key $1 ssl_key "" + + # database + config_get database_type $1 database_type "sqlite3" config_get mysql_host $1 mysql_host "" config_get mysql_port $1 mysql_port "3306" config_get mysql_username $1 mysql_username "" @@ -34,6 +47,26 @@ get_config() { config_get mysql_ssl_mode $1 mysql_ssl_mode "" config_get mysql_dsn $1 mysql_dsn "" + # tasks + config_get download_workers $1 download_workers 5 + config_get download_max_retry $1 download_max_retry 1 + config_get transfer_workers $1 transfer_workers 5 + config_get transfer_max_retry $1 transfer_max_retry 2 + config_get upload_workers $1 upload_workers 5 + config_get upload_max_retry $1 upload_max_retry 0 + config_get copy_workers $1 copy_workers 5 + config_get copy_max_retry $1 copy_max_retry 2 + + # cors + config_get cors_allow_origins $1 cors_allow_origins '*' + config_get cors_allow_methods $1 cors_allow_methods '*' + config_get cors_allow_headers $1 cors_allow_headers '*' + + # s3 + config_get s3 $1 s3 0 + config_get s3_port $1 s3_port 5246 + config_get s3_ssl $1 s3_ssl 0 + config_load network config_get lan_addr lan ipaddr "0.0.0.0" if echo "${lan_addr}" | grep -Fq ' '; then @@ -41,6 +74,11 @@ get_config() { else lan_addr=${lan_addr%%/*} fi + + # init jwt_secret + [ -z "$jwt_secret" ] && jwt_secret=$(tr -cd "a-zA-Z0-9" < "/dev/urandom" | head -c16) + uci -q set alist.@alist[0].jwt_secret="$jwt_secret" + uci commit alist } set_firewall() { @@ -81,23 +119,20 @@ start_service() { external_access="deny" fi - # mysql - [ "$mysql" -eq 1 ] && database=$mysql_type || database=sqlite3 - set_firewall true > $LOG_FILE # init config json_init - json_add_boolean "force" "1" + json_add_boolean "force" "$force" json_add_string "site_url" "$site_url" - json_add_string "cdn" "" - json_add_string "jwt_secret" "" + json_add_string "cdn" "$cdn" + json_add_string "jwt_secret" "$jwt_secret" json_add_int "token_expires_in" "$token_expires_in" # database json_add_object 'database' - json_add_string "type" "$database" + json_add_string "type" "$database_type" json_add_string "host" "$mysql_host" json_add_int "port" "$mysql_port" json_add_string "user" "$mysql_username" @@ -121,7 +156,7 @@ start_service() { json_add_string "address" "$listen_addr" json_add_int "http_port" "$http_port" json_add_int "https_port" "$https_port" - json_add_boolean "force_https" "0" + json_add_boolean "force_https" "$force_https" json_add_string "cert_file" "$ssl_cert" json_add_string "key_file" "$ssl_key" json_add_string "unix_file" "" @@ -136,61 +171,62 @@ start_service() { json_add_object "log" json_add_boolean "enable" "$log" json_add_string "name" "$LOG_FILE" - json_add_int "max_size" "10" - json_add_int "max_backups" "5" - json_add_int "max_age" "28" - json_add_boolean "compress" "0" + json_add_int "max_size" "$log_max_size" + json_add_int "max_backups" "$log_max_backups" + json_add_int "max_age" "$log_max_age" + json_add_boolean "compress" "$log_compress" json_close_object json_add_int "delayed_start" "$delayed_start" json_add_int "max_connections" "$max_connections" - json_add_boolean "tls_insecure_skip_verify" "1" + json_add_boolean "tls_insecure_skip_verify" "$tls_insecure_skip_verify" # tasks json_add_object "tasks" json_add_object "download" - json_add_int "workers" "5" - json_add_int "max_retry" "1" + json_add_int "workers" "$download_workers" + json_add_int "max_retry" "$download_max_retry" json_close_object json_add_object "transfer" - json_add_int "workers" "5" - json_add_int "max_retry" "2" + json_add_int "workers" "$transfer_workers" + json_add_int "max_retry" "$transfer_max_retry" json_close_object json_add_object "upload" - json_add_int "workers" "5" - json_add_int "max_retry" "0" + json_add_int "workers" "$upload_workers" + json_add_int "max_retry" "$upload_max_retry" json_close_object json_add_object "copy" - json_add_int "workers" "5" - json_add_int "max_retry" "2" + json_add_int "workers" "$copy_workers" + json_add_int "max_retry" "$copy_max_retry" json_close_object json_close_object # cors json_add_object "cors" json_add_array "allow_origins" - json_add_string "" "*" + json_add_string "" "$cors_allow_origins" json_close_array json_add_array "allow_methods" - json_add_string "" "*" + json_add_string "" "$cors_allow_methods" json_close_array json_add_array "allow_headers" - json_add_string "" "*" + json_add_string "" "$cors_allow_headers" json_close_array json_close_object # s3 json_add_object "s3" - json_add_boolean "enable" "0" - json_add_int "port" "5246" - json_add_boolean "ssl" "0" + json_add_boolean "enable" "$s3" + json_add_int "port" "$s3_port" + json_add_boolean "ssl" "$s3_ssl" json_close_object json_dump > $data_dir/config.json procd_open_instance alist procd_set_param command $PROG - procd_append_param command server --data $data_dir + procd_append_param command server + procd_append_param command --data $data_dir procd_set_param stdout 0 procd_set_param stderr 0 procd_set_param respawn @@ -201,7 +237,7 @@ start_service() { reload_service() { stop - sleep 3 + sleep 2 start } diff --git a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh index a1cc5bcb9d..530e212cc0 100755 --- a/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/openwrt-passwall/luci-app-passwall/root/usr/share/passwall/app.sh @@ -658,8 +658,11 @@ run_chinadns_ng() { ([ -z "${_default_tag}" ] || [ "${_default_tag}" = "smart" ] || [ "${_default_tag}" = "none_noip" ]) && _default_tag="none" echo "default-tag ${_default_tag}" >> ${_CONF_FILE} + echo "cache 4096" >> ${_CONF_FILE} + echo "cache-stale 3600" >> ${_CONF_FILE} + [ "${_flag}" = "default" ] && [ "${_default_tag}" = "none" ] && { - echo "verdict-cache 4096" >> ${_CONF_FILE} + echo "verdict-cache 5000" >> ${_CONF_FILE} } ln_run "$(first_type chinadns-ng)" chinadns-ng "${_LOG_FILE}" -C ${_CONF_FILE} @@ -1379,7 +1382,6 @@ start_dns() { LOCAL_DNS=$(config_t_get global direct_dns_udp 223.5.5.5 | sed 's/:/#/g') china_ng_local_dns=${LOCAL_DNS} sing_box_local_dns="direct_dns_udp_server=${LOCAL_DNS}" - IPT_APPEND_DNS=${LOCAL_DNS} ;; tcp) LOCAL_DNS="127.0.0.1#${dns_listen_port}" @@ -1387,7 +1389,6 @@ start_dns() { local DIRECT_DNS=$(config_t_get global direct_dns_tcp 223.5.5.5 | sed 's/:/#/g') china_ng_local_dns="tcp://${DIRECT_DNS}" sing_box_local_dns="direct_dns_tcp_server=${DIRECT_DNS}" - IPT_APPEND_DNS="${LOCAL_DNS},${DIRECT_DNS}" ln_run "$(first_type dns2tcp)" dns2tcp "/dev/null" -L "${LOCAL_DNS}" -R "$(get_first_dns DIRECT_DNS 53)" -v echolog " - dns2tcp(${LOCAL_DNS}) -> tcp://$(get_first_dns DIRECT_DNS 53 | sed 's/#/:/g')" echolog " * 请确保上游直连 DNS 支持 TCP 查询。" @@ -1405,8 +1406,8 @@ start_dns() { local tmp_dot_ip=$(echo "$DIRECT_DNS" | sed -n 's/.*:\/\/\([^@#]*@\)*\([^@#]*\).*/\2/p') local tmp_dot_port=$(echo "$DIRECT_DNS" | sed -n 's/.*#\([0-9]\+\).*/\1/p') - sing_box_local_dns="direct_dns_dot_server=$tmp_dot_ip#${tmp_dot_port:-853}" - IPT_APPEND_DNS="${LOCAL_DNS},$tmp_dot_ip#${tmp_dot_port:-853}" + DIRECT_DNS=$tmp_dot_ip#${tmp_dot_port:-853} + sing_box_local_dns="direct_dns_dot_server=${DIRECT_DNS}" else echolog " - 你的ChinaDNS-NG版本不支持DoT,直连DNS将使用默认地址。" fi @@ -1417,6 +1418,21 @@ start_dns() { ;; esac + # 追加直连DNS到iptables/nftables + [ "$(config_t_get global_haproxy balancing_enable 0)" != "1" ] && IPT_APPEND_DNS= + add_default_port() { + [ -z "$1" ] && echo "" || echo "$1" | awk -F',' '{for(i=1;i<=NF;i++){if($i !~ /#/) $i=$i"#53";} print $0;}' OFS=',' + } + LOCAL_DNS=$(add_default_port "$LOCAL_DNS") + IPT_APPEND_DNS=$(add_default_port "${IPT_APPEND_DNS:-$LOCAL_DNS}") + echo "$IPT_APPEND_DNS" | grep -q -E "(^|,)$LOCAL_DNS(,|$)" || IPT_APPEND_DNS="${IPT_APPEND_DNS:+$IPT_APPEND_DNS,}$LOCAL_DNS" + [ -n "$DIRECT_DNS" ] && { + DIRECT_DNS=$(add_default_port "$DIRECT_DNS") + echo "$IPT_APPEND_DNS" | grep -q -E "(^|,)$DIRECT_DNS(,|$)" || IPT_APPEND_DNS="${IPT_APPEND_DNS:+$IPT_APPEND_DNS,}$DIRECT_DNS" + } + # 排除127.0.0.1的条目 + IPT_APPEND_DNS=$(echo "$IPT_APPEND_DNS" | awk -F',' '{for(i=1;i<=NF;i++) if($i !~ /^127\.0\.0\.1/) printf (i>1?",":"") $i; print ""}' | sed 's/^,\|,$//g') + TUN_DNS="127.0.0.1#${dns_listen_port}" [ "${resolve_dns}" == "1" ] && TUN_DNS="127.0.0.1#${resolve_dns_port}" diff --git a/small/luci-app-homeproxy/.prepare.sh b/small/luci-app-homeproxy/.prepare.sh new file mode 100755 index 0000000000..c5aac9df36 --- /dev/null +++ b/small/luci-app-homeproxy/.prepare.sh @@ -0,0 +1,50 @@ +#!/bin/bash +PKG_NAME="$1" +CURDIR="$2" +PKG_BUILD_DIR="$3" +PKG_BUILD_BIN="$PKG_BUILD_DIR/bin" +export PATH="$PATH:$PKG_BUILD_BIN" +OS=linux +ARCH=amd64 +JQVERSION=1.7.1 +DOCNAME=Ruleset-URI-Scheme + +mkdir -p "$PKG_BUILD_BIN" +curl -L "https://github.com/jqlang/jq/releases/download/jq-${JQVERSION}/jq-${OS}-${ARCH}" -o "$PKG_BUILD_BIN"/jq +chmod +x "$PKG_BUILD_BIN"/jq +latest="$(curl -L https://api.github.com/repos/kpym/gm/releases/latest | jq -rc '.tag_name' 2>/dev/null)" +curl -L "https://github.com/kpym/gm/releases/download/${latest}/gm_${latest#v}_Linux_64bit.tar.gz" -o- | tar -xz -C "$PKG_BUILD_BIN" +latest="$(curl -L https://api.github.com/repos/tdewolff/minify/releases/latest | jq -rc '.tag_name' 2>/dev/null)" +curl -L "https://github.com/tdewolff/minify/releases/download/${latest}/minify_${OS}_${ARCH}.tar.gz" -o- | tar -xz -C "$PKG_BUILD_BIN" +chmod -R +x "$PKG_BUILD_BIN" + +cp "$CURDIR"/docs/$DOCNAME.md "$PKG_BUILD_DIR" +pushd "$PKG_BUILD_DIR" +gm $DOCNAME.md +p=$(sed -n '/github.min.css/=' $DOCNAME.html) +{ +head -n$(( $p -1 )) $DOCNAME.html +echo '' +tail -n +$(( $p +1 )) $DOCNAME.html +} > buildin.html +popd +minify "$PKG_BUILD_DIR"/buildin.html | base64 | tr -d '\n' > "$PKG_BUILD_DIR"/base64 +sed -i "s|'cmxzdHBsYWNlaG9sZGVy'|'$(cat "$PKG_BUILD_DIR"/base64)'|" "$PKG_BUILD_DIR"/htdocs/luci-static/resources/view/homeproxy/ruleset.js + +if [ -d "$CURDIR/.git" ]; then + config="$CURDIR/.git/config" +else + config="$(sed "s|^gitdir:\s*|$CURDIR/|;s|$|/config|" "$CURDIR/.git")" +fi +[ -n "$(sed -En '/^\[remote /{h;:top;n;/^\[/b;s,(https?://gitcode\.(com|net)),\1,;T top;H;x;s|\n\s*|: |;p;}' "$config")" ] && { + for d in luasrc ucode htdocs root src; do + rm -rf "$PKG_BUILD_DIR"/$d + done + mkdir -p "$PKG_BUILD_DIR"/htdocs/luci-static/resources/view + touch "$PKG_BUILD_DIR"/htdocs/luci-static/resources/view/$PKG_NAME.js + mkdir -p "$PKG_BUILD_DIR"/root/usr/share/luci/menu.d + touch "$PKG_BUILD_DIR"/root/usr/share/luci/menu.d/$PKG_NAME.json +} +exit 0 diff --git a/small/luci-app-homeproxy/Makefile b/small/luci-app-homeproxy/Makefile index 536f3d2aa6..654a94df30 100644 --- a/small/luci-app-homeproxy/Makefile +++ b/small/luci-app-homeproxy/Makefile @@ -10,7 +10,8 @@ LUCI_DEPENDS:= \ +sing-box \ +chinadns-ng \ +firewall4 \ - +kmod-nft-tproxy + +kmod-nft-tproxy \ + +unzip PKG_NAME:=luci-app-homeproxy @@ -20,8 +21,13 @@ define Package/luci-app-homeproxy/conffiles /etc/homeproxy/ruleset/ /etc/homeproxy/resources/direct_list.txt /etc/homeproxy/resources/proxy_list.txt +/etc/homeproxy/resources/clash_dashboard.ver +/etc/homeproxy/resources/*.zip +/etc/homeproxy/cache.db endef +PKG_UNPACK=$(CURDIR)/.prepare.sh $(PKG_NAME) $(CURDIR) $(PKG_BUILD_DIR) + include $(TOPDIR)/feeds/luci/luci.mk # call BuildPackage - OpenWrt buildroot signature diff --git a/small/luci-app-homeproxy/README b/small/luci-app-homeproxy/README index aea47fe629..d40045a958 100644 --- a/small/luci-app-homeproxy/README +++ b/small/luci-app-homeproxy/README @@ -1,5 +1,14 @@ +Recently, sagernet blocked my account unilaterally and without any reason. +So this fork will not continue to provide support for new SB features in the future. +Goodbye. everyone. + +For developers: +The code of the dev/* branch has supported new SB features up to 1.10.0-beta.3. +You can merge it yourself if necessary. + TODO: - Subscription page slow response with a large number of nodes - Refactor nft rules +- Support Clash selector, urltest etc. - Move ACL settings to a dedicated page - Any other improvements diff --git a/small/luci-app-homeproxy/docs/Ruleset-URI-Scheme.md b/small/luci-app-homeproxy/docs/Ruleset-URI-Scheme.md new file mode 100644 index 0000000000..7f6982293c --- /dev/null +++ b/small/luci-app-homeproxy/docs/Ruleset-URI-Scheme.md @@ -0,0 +1,54 @@ +# Import rule-set links format + +## Structure + +**remote:** `http[s]://[auth@]?file=[&key=value][#label]` +**local:** `file://[host]?file=[&key=value][#label]` +**inline:** `inline://[#label]` + +## Components + +### Scheme + +Can be `http` or `https` or `file` or `inline`. + +### Auth + +Add it only if required by the target host. + +### Host + +The format is `hostname[:port]`. +`hostname` can be **Domain** or **IP Address**. +`:port` is optional, add it only if required by the target host. + +### Path + +The shortest format is `/`. + +### Base64edJsonStr + +Generation steps: + + 1. Base64 encode **Headless Rule** `.rules`. + 2. Replace all `+` with `-` and all `/` with `_` in base64 string. + 3. Remove all `=` from the EOF the base64 string. + +### QueryParameters + ++ `file`: Required. Available values ​​refer to **Rulefmt**. ++ `rawquery`: Optional. Available values ​​refer to **rawQuery**. + +#### Rulefmt + +Can be `json` or `srs`. Rule file format. + +#### rawQuery + +This parameter is required if the original link contains a url query. +Encrypt the part `key1=value1&key2=value2` after `?` in the original link with `encodeURIComponent` and use it as the payload of this parameter. + +### URIFragment + +Ruleset label. Empty strings are not recommended. +Need encoded by `encodeURIComponent`. diff --git a/small/luci-app-homeproxy/docs/css/ClearnessDark.css b/small/luci-app-homeproxy/docs/css/ClearnessDark.css new file mode 100644 index 0000000000..6a749d4042 --- /dev/null +++ b/small/luci-app-homeproxy/docs/css/ClearnessDark.css @@ -0,0 +1,209 @@ +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote { + margin: 0; + padding: 0; +} +body { + font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif; + font-size: 13px; + line-height: 18px; + color: #fff; + background-color: #282a36; + margin: 10px 13px 10px 13px; +} +a { + color: #59acf3; +} +a:hover { + color: #a7d8ff; + text-decoration: none; +} +a img { + border: none; +} +p { + margin-bottom: 9px; +} +h1, +h2, +h3, +h4, +h5, +h6 { + color: #fff; + line-height: 36px; +} +h1 { + margin-bottom: 18px; + font-size: 30px; +} +h2 { + font-size: 24px; +} +h3 { + font-size: 18px; +} +h4 { + font-size: 16px; +} +h5 { + font-size: 14px; +} +h6 { + font-size: 13px; +} +hr { + margin: 0 0 19px; + border: 0; + border-bottom: 1px solid #ccc; +} +blockquote { + padding: 13px 13px 21px 15px; + margin-bottom: 18px; + font-family:georgia,serif; + font-style: italic; +} +blockquote:before { + content:"\201C"; + font-size:40px; + margin-left:-10px; + font-family:georgia,serif; + color:#eee; +} +blockquote p { + font-size: 14px; + font-weight: 300; + line-height: 18px; + margin-bottom: 0; + font-style: italic; +} +code, pre { + font-family: Monaco, Andale Mono, Courier New, monospace; +} +code { + color: #ff4a14; + padding: 1px 3px; + font-size: 12px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +pre { + display: block; + padding: 14px; + margin: 0 0 18px; + line-height: 16px; + font-size: 11px; + border: 1px solid #bf370f; + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} +pre code { + background-color: #282a36; + color: #ff4a14; + font-size: 11px; + padding: 0; +} +@media screen and (min-width: 768px) { + body { + width: 748px; + margin:10px auto; + } +} + +/** + * obsidian.css + * Obsidian style + * ported by Alexander Marenin (http://github.com/ioncreature) + */ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #282b2e; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-selector-id { + color: #93c763; +} + +.hljs-number { + color: #ffcd22; +} + +.hljs { + color: #e0e2e4; +} + +.hljs-attribute { + color: #668bb0; +} + +.hljs-code, +.hljs-class .hljs-title, +.hljs-section { + color: white; +} + +.hljs-regexp, +.hljs-link { + color: #d39745; +} + +.hljs-meta { + color: #557182; +} + +.hljs-tag, +.hljs-name, +.hljs-bullet, +.hljs-subst, +.hljs-emphasis, +.hljs-type, +.hljs-built_in, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #8cbbad; +} + +.hljs-string, +.hljs-symbol { + color: #ec7600; +} + +.hljs-comment, +.hljs-quote, +.hljs-deletion { + color: #818e96; +} + +.hljs-selector-class { + color: #A082BD +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-literal, +.hljs-doctag, +.hljs-title, +.hljs-section, +.hljs-type, +.hljs-name, +.hljs-strong { + font-weight: bold; +} diff --git a/small/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js b/small/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js index f80fb64302..f900933099 100644 --- a/small/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js +++ b/small/luci-app-homeproxy/htdocs/luci-static/resources/homeproxy.js @@ -11,6 +11,7 @@ 'require rpc'; 'require uci'; 'require ui'; +'require validation'; return baseclass.extend({ dns_strategy: { @@ -183,7 +184,8 @@ return baseclass.extend({ ).join(''); case 'uuid': /* Thanks to https://stackoverflow.com/a/2117523 */ - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) => + return (location.protocol === 'https:') ? crypto.randomUUID() : + ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); default: @@ -206,19 +208,55 @@ return baseclass.extend({ return label ? title + ' » ' + label : addtitle; }, - renderSectionAdd: function(section, extra_class) { + loadSubscriptionInfo: function(uciconfig) { + var subs = {}; + for (var suburl of (uci.get(uciconfig, 'subscription', 'subscription_url') || [])) { + const url = new URL(suburl); + const urlhash = this.calcStringMD5(suburl.replace(/#.*$/, '')); + subs[urlhash] = { + "url": suburl.replace(/#.*$/, ''), + "name": url.hash ? decodeURIComponent(url.hash.slice(1)) : url.hostname + }; + } + return subs; + }, + + loadNodesList: function(uciconfig, subinfo) { + var nodelist = {}; + uci.sections(uciconfig, 'node', (res) => { + var nodeaddr = ((res.type === 'direct') ? res.override_address : res.address) || '', + nodeport = ((res.type === 'direct') ? res.override_port : res.port) || ''; + + nodelist[res['.name']] = + String.format('%s [%s] %s', res.grouphash ? + String.format('[%s]', subinfo[res.grouphash]?.name || res.grouphash) : '', + res.type, res.label || ((validation.parseIPv6(nodeaddr) ? + String.format('[%s]', nodeaddr) : nodeaddr) + ':' + nodeport)); + }); + return nodelist; + }, + + renderSectionAdd: function(section, prefmt, LC, extra_class) { var el = form.GridSection.prototype.renderSectionAdd.apply(section, [ extra_class ]), nameEl = el.querySelector('.cbi-section-create-name'); ui.addValidator(nameEl, 'uciname', true, (v) => { var button = el.querySelector('.cbi-section-create > .cbi-button-add'); var uciconfig = section.uciconfig || section.map.config; + var prefix = prefmt?.prefix ? prefmt.prefix : '', + suffix = prefmt?.suffix ? prefmt.suffix : ''; if (!v) { button.disabled = true; return true; + } else if (LC && (v !== v.toLowerCase())) { + button.disabled = true; + return _('Expecting: %s').format(_('Lowercase only')); } else if (uci.get(uciconfig, v)) { button.disabled = true; return _('Expecting: %s').format(_('unique UCI identifier')); + } else if (uci.get(uciconfig, prefix + v + suffix)) { + button.disabled = true; + return _('Expecting: %s').format(_('unique label')); } else { button.disabled = null; return true; @@ -228,6 +266,13 @@ return baseclass.extend({ return el; }, + handleAdd: function(section, prefmt, ev, name) { + var prefix = prefmt?.prefix ? prefmt.prefix : '', + suffix = prefmt?.suffix ? prefmt.suffix : ''; + + return form.GridSection.prototype.handleAdd.apply(section, [ ev, prefix + name + suffix ]); + }, + uploadCertificate: function(option, type, filename, ev) { var callWriteCertificate = rpc.declare({ object: 'luci.homeproxy', diff --git a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js index 0da7350ba8..626309523b 100644 --- a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js +++ b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/client.js @@ -6,10 +6,12 @@ 'use strict'; 'require form'; +'require fs'; 'require network'; 'require poll'; 'require rpc'; 'require uci'; +'require ui'; 'require validation'; 'require view'; @@ -38,6 +40,20 @@ var callWriteDomainList = rpc.declare({ expect: { '': {} } }); +var callGetAPISecret = rpc.declare({ + object: 'luci.homeproxy', + method: 'clash_api_get_secret', + params: [], + expect: { '': {} } +}); + +var callResVersion = rpc.declare({ + object: 'luci.homeproxy', + method: 'resources_get_version', + params: ['type', 'repo'], + expect: { '': {} } +}); + function getServiceStatus() { return L.resolveDefault(callServiceList('homeproxy'), {}).then((res) => { var isRunning = false; @@ -48,12 +64,35 @@ function getServiceStatus() { }); } -function renderStatus(isRunning) { +function renderStatus(isRunning, args) { + let nginx = args.features.hp_has_nginx && args.nginx_support === '1'; var spanTemp = '%s %s'; + var urlParams; var renderHTML; - if (isRunning) - renderHTML = spanTemp.format('green', _('HomeProxy'), _('RUNNING')); - else + if (isRunning) { + if (args.set_dash_backend) { + switch (args.dashboard_repo) { + case 'metacubex/metacubexd': + urlParams = String.format('#/setup?hostname=%s&port=%s&secret=%s', window.location.hostname, args.api_port, args.api_secret); + break; + case 'metacubex/yacd-meta': + urlParams = String.format('?hostname=%s&port=%s&secret=%s', window.location.hostname, args.api_port, args.api_secret); + break; + case 'metacubex/razord-meta': + urlParams = String.format('?host=%s&port=%s&secret=%s', window.location.hostname, args.api_port, args.api_secret); + break; + default: + break; + } + } + if (args.dashboard_repo) { + var button = String.format(' %s', + (nginx ? 'https:' : 'http:') + '//' + window.location.hostname + + (nginx ? '/homeproxy' : ':' + args.api_port) + '/ui/' + (urlParams || ''), + _('Open Clash Dashboard')); + } + renderHTML = spanTemp.format('green', _('HomeProxy'), _('RUNNING')) + (button || ''); + } else renderHTML = spanTemp.format('red', _('HomeProxy'), _('NOT RUNNING')); return renderHTML; @@ -96,7 +135,8 @@ return view.extend({ return Promise.all([ uci.load('homeproxy'), hp.getBuiltinFeatures(), - network.getHostHints() + network.getHostHints(), + L.resolveDefault(callGetAPISecret(), {}) ]); }, @@ -104,18 +144,12 @@ return view.extend({ var m, s, o, ss, so; var features = data[1], - hosts = data[2]?.hosts; - - /* Cache all configured proxy nodes, they will be called multiple times */ - var proxy_nodes = {}; - uci.sections(data[0], 'node', (res) => { - var nodeaddr = ((res.type === 'direct') ? res.override_address : res.address) || '', - nodeport = ((res.type === 'direct') ? res.override_port : res.port) || ''; - - proxy_nodes[res['.name']] = - String.format('[%s] %s', res.type, res.label || ((stubValidator.apply('ip6addr', nodeaddr) ? - String.format('[%s]', nodeaddr) : nodeaddr) + ':' + nodeport)); - }); + hosts = data[2]?.hosts, + api_port = uci.get(data[0], 'experimental', 'clash_api_port'), + api_secret = data[3]?.secret || '', + nginx_support = uci.get(data[0], 'experimental', 'nginx_support') || '0', + dashboard_repo = uci.get(data[0], 'experimental', 'dashboard_repo'), + set_dash_backend = uci.get(data[0], 'experimental', 'set_dash_backend'); m = new form.Map('homeproxy', _('HomeProxy'), _('The modern ImmortalWrt proxy platform for ARM64/AMD64.')); @@ -125,7 +159,7 @@ return view.extend({ poll.add(function () { return L.resolveDefault(getServiceStatus()).then((res) => { var view = document.getElementById('service_status'); - view.innerHTML = renderStatus(res); + view.innerHTML = renderStatus(res, {features, nginx_support, dashboard_repo, set_dash_backend, api_port, api_secret}); }); }); @@ -134,14 +168,20 @@ return view.extend({ ]); } + /* Cache all subscription info, they will be called multiple times */ + var subs_info = hp.loadSubscriptionInfo(data[0]); + + /* Cache all configured proxy nodes, they will be called multiple times */ + var proxy_nodes = hp.loadNodesList(data[0], subs_info); + s = m.section(form.NamedSection, 'config', 'homeproxy'); s.tab('routing', _('Routing Settings')); o = s.taboption('routing', form.ListValue, 'main_node', _('Main node')); o.value('nil', _('Disable')); - for (var i in proxy_nodes) - o.value(i, proxy_nodes[i]); + for (var k in proxy_nodes) + o.value(k, proxy_nodes[k]); o.default = 'nil'; o.depends({'routing_mode': 'custom', '!reverse': true}); o.rmempty = false; @@ -149,8 +189,8 @@ return view.extend({ o = s.taboption('routing', form.ListValue, 'main_udp_node', _('Main UDP node')); o.value('nil', _('Disable')); o.value('same', _('Same as main node')); - for (var i in proxy_nodes) - o.value(i, proxy_nodes[i]); + for (var k in proxy_nodes) + o.value(k, proxy_nodes[k]); o.default = 'nil'; o.depends({'routing_mode': /^((?!custom).)+$/, 'proxy_mode': /^((?!redirect$).)+$/}); o.rmempty = false; @@ -164,7 +204,7 @@ return view.extend({ o.value('', '---'); o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)')); o.value('119.29.29.29', _('Tencent Public DNS (119.29.29.29)')); - o.value('117.50.10.10', _('ThreatBook Public DNS (117.50.10.10)')); + o.value('114.114.114.114', _('Xinfeng Public DNS (114.114.114.114)')); o.default = '8.8.8.8'; o.rmempty = false; o.depends({'routing_mode': 'custom', '!reverse': true}); @@ -187,7 +227,7 @@ return view.extend({ o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)')); o.value('210.2.4.8', _('CNNIC Public DNS (210.2.4.8)')); o.value('119.29.29.29', _('Tencent Public DNS (119.29.29.29)')); - o.value('117.50.10.10', _('ThreatBook Public DNS (117.50.10.10)')); + o.value('114.114.114.114', _('Xinfeng Public DNS (114.114.114.114)')); o.depends('routing_mode', 'bypass_mainland_china'); o.validate = function(section_id) { if (section_id) { @@ -234,11 +274,14 @@ return view.extend({ o = s.taboption('routing', form.Value, 'routing_port', _('Routing ports'), _('Specify target ports to be proxied. Multiple ports must be separated by commas.')); - o.value('', _('All ports')); + o.value('all', _('All ports')); o.value('common', _('Common ports only (bypass P2P traffic)')); o.default = 'common'; + o.rmempty = false; o.validate = function(section_id, value) { - if (section_id && value && value !== 'common') { + if (section_id && value !== 'all' && value !== 'common') { + if (!value) + return _('Expecting: %s').format(_('valid port value')); var ports = []; for (var i of value.split(',')) { @@ -324,11 +367,6 @@ return view.extend({ so.default = so.disabled; so.rmempty = false; - so = ss.option(form.ListValue, 'domain_strategy', _('Domain strategy'), - _('If set, the requested domain name will be resolved to IP before routing.')); - for (var i in hp.dns_strategy) - so.value(i, hp.dns_strategy[i]) - so = ss.option(form.Flag, 'sniff_override', _('Override destination'), _('Override the connection destination address with the sniffed domain.')); so.default = so.enabled; @@ -351,6 +389,17 @@ return view.extend({ } so.default = 'nil'; so.rmempty = false; + + so = ss.option(form.Button, '_reload_client', _('Quick Reload')); + so.inputtitle = _('Reload'); + so.inputstyle = 'apply'; + so.onclick = function() { + return fs.exec('/etc/init.d/homeproxy', ['reload', 'client']) + .then((res) => { return window.location = window.location.href.split('#')[0] }) + .catch((e) => { + ui.addNotification(null, E('p', _('Failed to execute "/etc/init.d/homeproxy %s %s" reason: %s').format('reload', 'client', e))); + }); + }; /* Routing settings end */ /* Routing nodes start */ @@ -365,7 +414,8 @@ return view.extend({ ss.nodescriptions = true; ss.modaltitle = L.bind(hp.loadModalTitle, this, _('Routing node'), _('Add a routing node'), data[0]); ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); - ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); + ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss, {}, true); + ss.handleAdd = L.bind(hp.handleAdd, this, ss, {}); so = ss.option(form.Value, 'label', _('Label')); so.load = L.bind(hp.loadDefaultLabel, this, data[0]); @@ -379,13 +429,13 @@ return view.extend({ so = ss.option(form.ListValue, 'node', _('Node'), _('Outbound node')); - for (var i in proxy_nodes) - so.value(i, proxy_nodes[i]); + for (var k in proxy_nodes) + so.value(k, proxy_nodes[k]); so.validate = L.bind(hp.validateUniqueValue, this, data[0], 'routing_node', 'node'); so.editable = true; so = ss.option(form.ListValue, 'domain_strategy', _('Domain strategy'), - _('If set, the server domain name will be resolved to IP before connecting.
')); + _('If set, the server domain name will be resolved to IP before connecting.
dns.strategy will be used if empty.')); for (var i in hp.dns_strategy) so.value(i, hp.dns_strategy[i]); so.modalonly = true; @@ -435,13 +485,15 @@ return view.extend({ o.depends('routing_mode', 'custom'); ss = o.subsection; + var prefmt = { 'prefix': '', 'suffix': '_host' }; ss.addremove = true; ss.rowcolors = true; ss.sortable = true; ss.nodescriptions = true; ss.modaltitle = L.bind(hp.loadModalTitle, this, _('Routing rule'), _('Add a routing rule'), data[0]); ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); - ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); + ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss, prefmt, false); + ss.handleAdd = L.bind(hp.handleAdd, this, ss, prefmt); ss.tab('field_other', _('Other fields')); ss.tab('field_host', _('Host fields')); @@ -516,7 +568,6 @@ return view.extend({ so = ss.taboption('field_source_ip', form.Flag, 'source_ip_is_private', _('Private source IP'), _('Match private source IP.')); so.default = so.disabled; - so.rmempty = false; so.modalonly = true; so = ss.taboption('field_host', form.DynamicList, 'ip_cidr', _('IP CIDR'), @@ -527,7 +578,6 @@ return view.extend({ so = ss.taboption('field_host', form.Flag, 'ip_is_private', _('Private IP'), _('Match private IP.')); so.default = so.disabled; - so.rmempty = false; so.modalonly = true; so = ss.taboption('field_source_port', form.DynamicList, 'source_port', _('Source port'), @@ -562,6 +612,14 @@ return view.extend({ _('Match user name.')); so.modalonly = true; + so = ss.taboption('field_other', form.ListValue, 'clash_mode', _('Clash mode'), + _('Match clash mode.')); + so.value('', _('None')); + so.value('global', _('Global')); + so.value('rule', _('Rule')); + so.value('direct', _('Direct')); + so.modalonly = true; + so = ss.taboption('field_other', form.MultiValue, 'rule_set', _('Rule set'), _('Match rule set.')); so.load = function(section_id) { @@ -581,7 +639,6 @@ return view.extend({ so = ss.taboption('field_other', form.Flag, 'rule_set_ipcidr_match_source', _('Match source IP via rule set'), _('Make IP CIDR in rule set used to match the source IP.')); so.default = so.disabled; - so.rmempty = false; so.modalonly = true; so = ss.taboption('field_other', form.Flag, 'invert', _('Invert'), @@ -672,13 +729,15 @@ return view.extend({ o.depends('routing_mode', 'custom'); ss = o.subsection; + var prefmt = { 'prefix': 'dns_', 'suffix': '' }; ss.addremove = true; ss.rowcolors = true; ss.sortable = true; ss.nodescriptions = true; ss.modaltitle = L.bind(hp.loadModalTitle, this, _('DNS server'), _('Add a DNS server'), data[0]); ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); - ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); + ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss, prefmt, true); + ss.handleAdd = L.bind(hp.handleAdd, this, ss, prefmt); so = ss.option(form.Value, 'label', _('Label')); so.load = L.bind(hp.loadDefaultLabel, this, data[0]); @@ -727,7 +786,7 @@ return view.extend({ so.modalonly = true; so = ss.option(form.ListValue, 'address_strategy', _('Address strategy'), - _('The domain strategy for resolving the domain name in the address.')); + _('The domain strategy for resolving the domain name in the address. dns.strategy will be used if empty.')); for (var i in hp.dns_strategy) so.value(i, hp.dns_strategy[i]); so.modalonly = true; @@ -767,13 +826,15 @@ return view.extend({ o.depends('routing_mode', 'custom'); ss = o.subsection; + var prefmt = { 'prefix': '', 'suffix': '_domain' }; ss.addremove = true; ss.rowcolors = true; ss.sortable = true; ss.nodescriptions = true; ss.modaltitle = L.bind(hp.loadModalTitle, this, _('DNS rule'), _('Add a DNS rule'), data[0]); ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); - ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); + ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss, prefmt, false); + ss.handleAdd = L.bind(hp.handleAdd, this, ss, prefmt); ss.tab('field_other', _('Other fields')); ss.tab('field_host', _('Host fields')); @@ -896,6 +957,14 @@ return view.extend({ _('Match user name.')); so.modalonly = true; + so = ss.taboption('field_other', form.ListValue, 'clash_mode', _('Clash mode'), + _('Match clash mode.')); + so.value('', _('None')); + so.value('global', _('Global')); + so.value('rule', _('Rule')); + so.value('direct', _('Direct')); + so.modalonly = true; + so = ss.taboption('field_other', form.MultiValue, 'rule_set', _('Rule set'), _('Match rule set.')); so.load = function(section_id) { @@ -938,6 +1007,13 @@ return view.extend({ return this.super('load', section_id); } + so.validate = function(section_id, value) { + let arr = value.trim().split(' '); + if (arr.length > 1 && arr.includes('any-out')) + return _('Expecting: %s').format(_('If Any is selected, uncheck others')); + + return true; + } so.modalonly = true; so = ss.taboption('field_other', form.ListValue, 'server', _('Server'), @@ -976,93 +1052,82 @@ return view.extend({ /* DNS rules end */ /* Custom routing settings end */ - /* Rule set settings start */ - s.tab('ruleset', _('Rule set')); - o = s.taboption('ruleset', form.SectionValue, '_ruleset', form.GridSection, 'ruleset'); + /* Clash API settings start */ + s.tab('clash', _('Clash API settings')); + o = s.taboption('clash', form.SectionValue, '_clash', form.NamedSection, 'experimental'); o.depends('routing_mode', 'custom'); ss = o.subsection; - ss.addremove = true; - ss.rowcolors = true; - ss.sortable = true; - ss.nodescriptions = true; - ss.modaltitle = L.bind(hp.loadModalTitle, this, _('Rule set'), _('Add a rule set'), data[0]); - ss.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); - ss.renderSectionAdd = L.bind(hp.renderSectionAdd, this, ss); + so = ss.option(form.Flag, 'clash_api_enabled', _('Enable Clash API')); + so.default = so.disabled; - so = ss.option(form.Value, 'label', _('Label')); - so.load = L.bind(hp.loadDefaultLabel, this, data[0]); - so.validate = L.bind(hp.validateUniqueValue, this, data[0], 'ruleset', 'label'); - so.modalonly = true; - - so = ss.option(form.Flag, 'enabled', _('Enable')); - so.default = so.enabled; - so.rmempty = false; - so.editable = true; - - so = ss.option(form.ListValue, 'type', _('Type')); - so.value('local', _('Local')); - so.value('remote', _('Remote')); - so.default = 'remote'; - so.rmempty = false; - - so = ss.option(form.ListValue, 'format', _('Format')); - so.value('source', _('Source file')); - so.value('binary', _('Binary file')); - so.default = 'source'; - so.rmempty = false; - - so = ss.option(form.Value, 'path', _('Path')); - so.datatype = 'file'; - so.placeholder = '/etc/homeproxy/ruleset/example.json'; - so.rmempty = false; - so.depends('type', 'local'); - so.modalonly = true; - - so = ss.option(form.Value, 'url', _('Rule set URL')); - so.validate = function(section_id, value) { - if (section_id) { - if (!value) - return _('Expecting: %s').format(_('non-empty value')); - - try { - var url = new URL(value); - if (!url.hostname) - return _('Expecting: %s').format(_('valid URL')); - } - catch(e) { - return _('Expecting: %s').format(_('valid URL')); - } - } - - return true; + so = ss.option(form.Flag, 'nginx_support', _('Nginx Support')); + so.rmempty = true; + if (! features.hp_has_nginx) { + so.description = _('To enable this feature you need install luci-nginx and luci-ssl-nginx
first'); + so.readonly = true; + } + so.write = function(section_id, value) { + return uci.set(data[0], section_id, 'nginx_support', features.hp_has_nginx ? value : null); } - so.rmempty = false; - so.depends('type', 'remote'); - so.modalonly = true; - so = ss.option(form.ListValue, 'outbound', _('Outbound'), - _('Tag of the outbound to download rule set.')); + so = ss.option(form.ListValue, 'clash_api_log_level', _('Log level')); + so.value('trace', 'Trace'); + so.value('debug', 'Debug'); + so.value('info', 'Info'); + so.value('warn', 'Warning'); + so.value('error', 'Error'); + so.value('fatal', 'Fatal'); + so.value('panic', 'Panic'); + so.default = 'warn'; + + so = ss.option(form.ListValue, 'dashboard_repo', _('Select Clash Dashboard'), + _('If the selected dashboard is ') + _('Not Installed') + _('.
you will need to check update via ') + + _('Service Status') + _(' » ') + _('Clash dashboard version') + _('.')); so.load = function(section_id) { delete this.keylist; delete this.vallist; - this.value('direct-out', _('Direct')); - uci.sections(data[0], 'routing_node', (res) => { - if (res.enabled === '1') - this.value(res['.name'], res.label); + let repos = [ + ['metacubex/metacubexd', _('metacubexd')], + ['metacubex/yacd-meta', _('yacd-meta')], + ['metacubex/razord-meta', _('razord-meta')] + ]; + + this.value('', _('Use Online Dashboard')); + repos.forEach((repo) => { + callResVersion('clash_dashboard', repo[0]).then((res) => { + this.value(repo[0], repo[1] + ' - ' + (res.error ? _('Not Installed') : _('Installed'))); + }); }); return this.super('load', section_id); } - so.default = 'direct-out'; - so.rmempty = false; - so.depends('type', 'remote'); + so.default = ''; + if (api_secret) { + if (features.hp_has_nginx && nginx_support === '1') { + so.description = _('The current API URL is %s') + .format('https://' + window.location.hostname + '/homeproxy/'); + } else { + so.description = _('The current API URL is %s') + .format('http://' + window.location.hostname + ':' + api_port); + } + } - so = ss.option(form.Value, 'update_interval', _('Update interval'), - _('Update interval of rule set.
1d will be used if empty.')); - so.depends('type', 'remote'); - /* Rule set settings end */ + so = ss.option(form.Flag, 'set_dash_backend', _('Auto set backend'), + _('Auto set backend address for dashboard.')); + so.default = so.disabled; + + so = ss.option(form.Value, 'clash_api_port', _('Port')); + so.datatype = "and(port, min(1))"; + so.default = '9090'; + so.rmempty = false; + + so = ss.option(form.Value, 'clash_api_secret', _('Secret'), _('Automatically generated if empty')); + so.password = true; + if (api_secret) + so.description = _('The current Secret is ' + api_secret + ''); + /* Clash API settings end */ /* ACL settings start */ s.tab('control', _('Access Control')); diff --git a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js index d84e2c8c12..02dde08a1b 100644 --- a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js +++ b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/node.js @@ -9,6 +9,7 @@ 'require fs'; 'require uci'; 'require ui'; +'require dom'; 'require view'; 'require homeproxy as hp'; @@ -355,7 +356,7 @@ function parseShareLink(uri, features) { return config; } -function renderNodeSettings(section, data, features, main_node, routing_mode) { +function renderNodeSettings(section, data, features, main_node, routing_mode, subs_info, proxy_nodes) { var s = section, o; s.rowcolors = true; s.sortable = true; @@ -408,16 +409,18 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { o.value('wireguard', _('WireGuard')); o.value('vless', _('VLESS')); o.value('vmess', _('VMess')); + o.value('selector', _('Selector')); + o.value('urltest', _('URLTest')); o.rmempty = false; o = s.option(form.Value, 'address', _('Address')); o.datatype = 'host'; - o.depends({'type': 'direct', '!reverse': true}); + o.depends({'type': /^(direct|selector|urltest)$/, '!reverse': true}); o.rmempty = false; o = s.option(form.Value, 'port', _('Port')); o.datatype = 'port'; - o.depends({'type': 'direct', '!reverse': true}); + o.depends({'type': /^(direct|selector|urltest)$/, '!reverse': true}); o.rmempty = false; o = s.option(form.Value, 'username', _('Username')); @@ -462,18 +465,24 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { _('Override the connection destination address.')); o.datatype = 'host'; o.depends('type', 'direct'); + o.modalonly = true; o = s.option(form.Value, 'override_port', _('Override port'), _('Override the connection destination port.')); o.datatype = 'port'; o.depends('type', 'direct'); + o.modalonly = true; - o = s.option(form.ListValue, 'proxy_protocol', _('Proxy protocol'), + o = s.option(form.Flag, 'proxy_protocol', _('Proxy protocol'), _('Write proxy protocol in the connection header.')); - o.value('', _('Disable')); + o.depends('type', 'direct'); + o.modalonly = true; + + o = s.option(form.ListValue, 'proxy_protocol_version', _('Proxy protocol version')); o.value('1', _('v1')); o.value('2', _('v2')); - o.depends('type', 'direct'); + o.default = '2'; + o.depends('proxy_protocol', '1'); o.modalonly = true; /* Hysteria (2) config start */ @@ -707,6 +716,89 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { o.modalonly = true; /* VMess config end */ + /* Selector config start */ + o = s.option(form.MultiValue, 'group', _('Subscription Groups'), + _('List of subscription groups.')); + o.value('', _('-- Please choose --')); + for (var key in subs_info) { + let title = subs_info[key].name; + o.value(key, _('Sub (%s)').format(title)); + } + o.depends('type', 'selector'); + o.depends('type', 'urltest'); + o.modalonly = true; + + o = s.option(form.MultiValue, 'order', _('Outbounds'), + _('List of outbound tags.')); + o.value('direct-out', _('Direct')); + o.value('block-out', _('Block')); + for (var key in proxy_nodes) + o.value(key, proxy_nodes[key]); + o.depends({'group': /^$/, 'type': /^(selector|urltest)$/}); + o.modalonly = true; + + o = s.option(form.Value, 'default_selected', _('Default Outbound'), + _('The default outbound tag. The first outbound will be used if empty.')); + o.value('', _('Default')); + o.value('direct-out', _('Direct')); + o.value('block-out', _('Block')); + for (var key in proxy_nodes) + o.value(key, proxy_nodes[key]); + o.default = ''; + o.depends({'group': /^$/, 'type': 'selector'}); + o.modalonly = true; + + o = s.option(form.ListValue, 'filter_nodes', _('Filter nodes'), + _('Drop/keep specific nodes from outbounds.')); + o.value('', _('Disable')); + o.value('blacklist', _('Blacklist mode')); + o.value('whitelist', _('Whitelist mode')); + o.default = ''; + o.depends('type', 'selector'); + o.depends('type', 'urltest'); + o.modalonly = true; + + o = s.option(form.DynamicList, 'filter_keywords', _('Filter keywords'), + _('Drop/keep nodes that contain the specific keywords. Regex is supported.')); + o.depends({'filter_nodes': '', '!reverse': true}); + o.modalonly = true; + /* Selector config end */ + + /* URLTest config start */ + o = s.option(form.Value, 'test_url', _('Test URL'), + _('The URL to test. https://www.gstatic.com/generate_204 will be used if empty.')); + o.value('', _('Default')); + o.default = 'http://cp.cloudflare.com/'; + o.depends('type', 'urltest'); + o.modalonly = true; + + o = s.option(form.Value, 'interval', _('Interval'), + _('The test interval. 3m will be used if empty.')); + o.value('', _('Default')); + o.default = '10m'; + o.depends('type', 'urltest'); + o.modalonly = true; + + o = s.option(form.Value, 'tolerance', _('Tolerance'), + _('The test tolerance in milliseconds. 50 will be used if empty.')); + o.datatype = 'uinteger'; + o.depends('type', 'urltest'); + o.modalonly = true; + + o = s.option(form.Value, 'idle_timeout', _('Idle timeout'), + _('The idle timeout. 30m will be used if empty.')); + o.default = '30m'; + o.depends('type', 'urltest'); + o.modalonly = true; + + o = s.option(form.Flag, 'interrupt_exist_connections', _('Interrupt existing connections'), + _('Interrupt existing connections when the selected outbound has changed.')); + o.default = o.disabled; + o.depends('type', 'selector'); + o.depends('type', 'urltest'); + o.modalonly = true; + /* URLTest config end */ + /* Transport config start */ o = s.option(form.ListValue, 'transport', _('Transport'), _('No TCP transport, plain HTTP is merged into the HTTP transport.')); @@ -1109,15 +1201,18 @@ function renderNodeSettings(section, data, features, main_node, routing_mode) { /* Extra settings start */ o = s.option(form.Flag, 'tcp_fast_open', _('TCP fast open')); o.default = o.disabled; + o.depends({'type': /^(selector|urltest)$/, '!reverse': true}); o.modalonly = true; o = s.option(form.Flag, 'tcp_multi_path', _('MultiPath TCP')); o.default = o.disabled; + o.depends({'type': /^(selector|urltest)$/, '!reverse': true}); o.modalonly = true; o = s.option(form.Flag, 'udp_fragment', _('UDP Fragment'), _('Enable UDP fragmentation.')); o.default = o.disabled; + o.depends({'type': /^(selector|urltest)$/, '!reverse': true}); o.modalonly = true; o = s.option(form.Flag, 'udp_over_tcp', _('UDP over TCP'), @@ -1152,31 +1247,25 @@ return view.extend({ var routing_mode = uci.get(data[0], 'config', 'routing_mode'); var features = data[1]; - /* Cache subscription information, it will be called multiple times */ - var subinfo = []; - for (var suburl of (uci.get(data[0], 'subscription', 'subscription_url') || [])) { - const url = new URL(suburl); - const urlhash = hp.calcStringMD5(suburl.replace(/#.*$/, '')); - const title = url.hash ? decodeURIComponent(url.hash.slice(1)) : url.hostname; - subinfo.push({ 'hash': urlhash, 'title': title }); - } - m = new form.Map('homeproxy', _('Edit nodes')); + /* Cache all subscription info, they will be called multiple times */ + var subs_info = hp.loadSubscriptionInfo(data[0]); + + /* Cache all configured proxy nodes, they will be called multiple times */ + var proxy_nodes = hp.loadNodesList(data[0], subs_info); + s = m.section(form.NamedSection, 'subscription', 'homeproxy'); /* Node settings start */ /* User nodes start */ s.tab('node', _('Nodes')); + o = s.taboption('node', form.SectionValue, '_node', form.GridSection, 'node'); - ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode); + ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode, subs_info, proxy_nodes); ss.addremove = true; ss.filter = function(section_id) { - for (var info of subinfo) - if (info.hash === uci.get(data[0], section_id, 'grouphash')) - return false; - - return true; + return uci.get(data[0], section_id, 'grouphash') ? false : true; } /* Import subscription links start */ /* Thanks to luci-app-shadowsocks-libev */ @@ -1239,13 +1328,31 @@ return view.extend({ ]) ]) } - ss.renderSectionAdd = function(/* ... */) { + ss.renderSectionAdd = function(extra_class) { var el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments), + selectEl = E('select', { + class: 'cbi-input-select', + change: L.bind(function(section, ev) { + var el = dom.parent(ev.target, '.cbi-section-create'), + button = el.querySelector('.cbi-section-create > .cbi-button-add'), + inputname = el.querySelector('.cbi-section-create-name').value || ''; + var uciconfig = section.uciconfig || section.map.config; + + button.toggleAttribute('disabled', + !inputname || + uci.get(uciconfig, inputname) || + uci.get(uciconfig, ev.target.value + inputname)); + }, this, ss) + }, [ + E('option', { value: 'node_' }, _('node')), + E('option', { value: 'sub_' }, _('sub')) + ]), nameEl = el.querySelector('.cbi-section-create-name'); ui.addValidator(nameEl, 'uciname', true, (v) => { var button = el.querySelector('.cbi-section-create > .cbi-button-add'); var uciconfig = this.uciconfig || this.map.config; + var prefix = el.querySelector('.cbi-input-select').value; if (!v) { button.disabled = true; @@ -1253,12 +1360,17 @@ return view.extend({ } else if (uci.get(uciconfig, v)) { button.disabled = true; return _('Expecting: %s').format(_('unique UCI identifier')); + } else if (uci.get(uciconfig, prefix + v)) { + button.disabled = true; + return _('Expecting: %s').format(_('unique label')); } else { button.disabled = null; return true; } }, 'blur', 'keyup'); + el.prepend(E('div', {}, selectEl)); + el.appendChild(E('button', { 'class': 'cbi-button cbi-button-add', 'title': _('Import share links'), @@ -1267,16 +1379,26 @@ return view.extend({ return el; } + ss.handleAdd = function(ev, name) { + var selectEl = ev.target.parentElement.firstElementChild.firstElementChild, + prefix = selectEl.value; + + return form.GridSection.prototype.handleAdd.apply(this, [ ev, prefix + name ]); + } /* Import subscription links end */ /* User nodes end */ /* Subscription nodes start */ - for (const info of subinfo) { - s.tab('sub_' + info.hash, _('Sub (%s)').format(info.title)); - o = s.taboption('sub_' + info.hash, form.SectionValue, '_sub_' + info.hash, form.GridSection, 'node'); - ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode); + for (var key in subs_info) { + const urlhash = key, + title = subs_info[key].name; + + s.tab('sub_' + urlhash, _('Sub (%s)').format(title)); + + o = s.taboption('sub_' + urlhash, form.SectionValue, '_sub_' + urlhash, form.GridSection, 'node'); + ss = renderNodeSettings(o.subsection, data, features, main_node, routing_mode, subs_info, proxy_nodes); ss.filter = function(section_id) { - return (uci.get(data[0], section_id, 'grouphash') === info.hash); + return (uci.get(data[0], section_id, 'grouphash') === urlhash); } } /* Subscription nodes end */ @@ -1286,14 +1408,16 @@ return view.extend({ s.tab('subscription', _('Subscriptions')); o = s.taboption('subscription', form.Flag, 'auto_update', _('Auto update'), - _('Auto update subscriptions and geodata.')); + _('Auto update subscriptions.')); o.default = o.disabled; o.rmempty = false; - o = s.taboption('subscription', form.ListValue, 'auto_update_time', _('Update time')); - for (var i = 0; i < 24; i++) - o.value(i, i + ':00'); - o.default = '2'; + o = s.taboption('subscription', form.Value, 'auto_update_expr', _('Cron expression'), + _('The default value is 2:00 every day')); + o.default = '0 2 * * *'; + o.placeholder = '0 2 * * *'; + o.rmempty = false; + o.retain = true; o.depends('auto_update', '1'); o = s.taboption('subscription', form.Flag, 'update_via_proxy', _('Update via proxy'), @@ -1303,6 +1427,7 @@ return view.extend({ o = s.taboption('subscription', form.DynamicList, 'subscription_url', _('Subscription URL-s'), _('Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) online configuration delivery standard.')); + o.placeholder = 'https://sub_url#sub_name'; o.validate = function(section_id, value) { if (section_id && value) { try { @@ -1320,16 +1445,15 @@ return view.extend({ o = s.taboption('subscription', form.ListValue, 'filter_nodes', _('Filter nodes'), _('Drop/keep specific nodes from subscriptions.')); - o.value('disabled', _('Disable')); + o.value('', _('Disable')); o.value('blacklist', _('Blacklist mode')); o.value('whitelist', _('Whitelist mode')); - o.default = 'disabled'; - o.rmempty = false; + o.default = ''; o = s.taboption('subscription', form.DynamicList, 'filter_keywords', _('Filter keywords'), _('Drop/keep nodes that contain the specific keywords. Regex is supported.')); - o.depends({'filter_nodes': 'disabled', '!reverse': true}); - o.rmempty = false; + o.depends({'filter_nodes': '', '!reverse': true}); + o.retain = true; o = s.taboption('subscription', form.Flag, 'allow_insecure', _('Allow insecure'), _('Allow insecure connection by default when add nodes from subscriptions.') + diff --git a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/ruleset.js b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/ruleset.js new file mode 100644 index 0000000000..e0b951d8cb --- /dev/null +++ b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/ruleset.js @@ -0,0 +1,277 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * + * Copyright (C) 2023 ImmortalWrt.org + */ + +'use strict'; +'require form'; +'require fs'; +'require uci'; +'require ui'; +'require view'; + +'require homeproxy as hp'; + +const docdata = 'base64,' + 'cmxzdHBsYWNlaG9sZGVy' + +function parseRulesetLink(uri) { + var config, + filefmt = new RegExp(/^(json|srs)$/), + unuciname = new RegExp(/[^a-zA-Z0-9_]+/, "g"); + + uri = uri.split('://'); + if (uri[0] && uri[1]) { + switch (uri[0]) { + case 'http': + case 'https': + var url = new URL('http://' + uri[1]); + var file = url.searchParams.get('file'); + var rawquery = url.searchParams.get('rawquery'); + var name = decodeURIComponent(url.pathname.split('/').pop()) + .replace(/[\s\.-]/g, '_').replace(unuciname, ''); + + if (filefmt.test(file)) { + var fullpath = (url.username ? url.username + '@' : '') + url.host + url.pathname + (rawquery ? '?' + decodeURIComponent(rawquery) : ''); + config = { + label: url.hash ? decodeURIComponent(url.hash.slice(1)) : name ? name : null, + type: 'remote', + format: file.match(/^json$/) ? 'source' : file.match(/^srs$/) ? 'binary' : 'unknown', + url: String.format('%s://%s', uri[0], fullpath), + href: String.format('http://%s', fullpath) + }; + } + + break; + case 'file': + var url = new URL('file://' + uri[1]); + var file = url.searchParams.get('file'); + var name = decodeURIComponent(url.pathname.split('/').pop()) + .replace(/[\s\.-]/g, '_').replace(unuciname, ''); + + if (filefmt.test(file)) { + config = { + label: url.hash ? decodeURIComponent(url.hash.slice(1)) : name ? name : null, + type: 'local', + format: file.match(/^json$/) ? 'source' : file.match(/^srs$/) ? 'binary' : 'unknown', + path: url.pathname, + href: String.format('file://%s%s', url.host, url.pathname) + }; + } + + break; + } + } + + if (config) { + if (!config.type || !config.href) + return null; + else if (!config.label) + config.label = hp.calcStringMD5(config.href); + } + + return config; +} + +return view.extend({ + load: function() { + return Promise.all([ + uci.load('homeproxy') + ]); + }, + + render: function(data) { + var m, s, o; + + m = new form.Map('homeproxy', _('Edit ruleset')); + + /* Rule set settings start */ + var prefix = 'rule_'; + s = m.section(form.GridSection, 'ruleset'); + s.addremove = true; + s.rowcolors = true; + s.sortable = true; + s.nodescriptions = true; + s.modaltitle = L.bind(hp.loadModalTitle, this, _('Rule set'), _('Add a rule set'), data[0]); + s.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); + /* Import rule-set links start */ + s.handleLinkImport = function() { + var textarea = new ui.Textarea('', { + 'placeholder': 'http(s)://github.com/sagernet/sing-geoip/raw/rule-set/geoip-hk.srs?file=srs&rawquery=good%3Djob#GeoIP-HK\n' + + 'file:///etc/homeproxy/ruleset/example.json?file=json#Example%20file\n' + }); + ui.showModal(_('Import rule-set links'), [ + E('p', _('Supports rule-set links of type: local, remote and format: source, binary.
') + + _('Please refer to %s for link format standards.') + .format('data:text/html;' + docdata, _('Ruleset-URI-Scheme'))), + textarea.render(), + E('div', { class: 'right' }, [ + E('button', { + class: 'btn', + click: ui.hideModal + }, [ _('Cancel') ]), + '', + E('button', { + class: 'btn cbi-button-action', + click: ui.createHandlerFn(this, function() { + var input_links = textarea.getValue().trim().split('\n'); + if (input_links && input_links[0]) { + /* Remove duplicate lines */ + input_links = input_links.reduce((pre, cur) => + (!pre.includes(cur) && pre.push(cur), pre), []); + + var imported_ruleset = 0; + input_links.forEach((l) => { + var config = parseRulesetLink(l); + if (config) { + var hrefHash = hp.calcStringMD5(config.href); + config.href = null; + var sid = uci.add(data[0], 'ruleset', hrefHash); + Object.keys(config).forEach((k) => { + uci.set(data[0], sid, k, config[k]); + }); + imported_ruleset++; + } + }); + + if (imported_ruleset === 0) + ui.addNotification(null, E('p', _('No valid rule-set link found.'))); + else + ui.addNotification(null, E('p', _('Successfully imported %s rule-set of total %s.').format( + imported_ruleset, input_links.length))); + + return uci.save() + .then(L.bind(this.map.load, this.map)) + .then(L.bind(this.map.reset, this.map)) + .then(L.ui.hideModal) + .catch(function() {}); + } else { + return ui.hideModal(); + } + }) + }, [ _('Import') ]) + ]) + ]) + } + s.renderSectionAdd = function(extra_class) { + var el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments), + nameEl = el.querySelector('.cbi-section-create-name'); + + ui.addValidator(nameEl, 'uciname', true, (v) => { + var button = el.querySelector('.cbi-section-create > .cbi-button-add'); + var uciconfig = this.uciconfig || this.map.config; + + if (!v) { + button.disabled = true; + return true; + } else if (uci.get(uciconfig, v)) { + button.disabled = true; + return _('Expecting: %s').format(_('unique UCI identifier')); + } else if (uci.get(uciconfig, prefix + v)) { + button.disabled = true; + return _('Expecting: %s').format(_('unique label')); + } else { + button.disabled = null; + return true; + } + }, 'blur', 'keyup'); + + el.appendChild(E('button', { + 'class': 'cbi-button cbi-button-add', + 'title': _('Import rule-set links'), + 'click': ui.createHandlerFn(this, 'handleLinkImport') + }, [ _('Import rule-set links') ])); + + return el; + } + s.handleAdd = function(ev, name) { + return form.GridSection.prototype.handleAdd.apply(this, [ ev, prefix + name ]); + } + /* Import rule-set links end */ + + o = s.option(form.Value, 'label', _('Label')); + o.load = L.bind(hp.loadDefaultLabel, this, data[0]); + o.validate = L.bind(hp.validateUniqueValue, this, data[0], 'ruleset', 'label'); + o.modalonly = true; + + o = s.option(form.Flag, 'enabled', _('Enable')); + o.default = o.enabled; + o.rmempty = false; + o.editable = true; + + o = s.option(form.ListValue, 'type', _('Type')); + o.value('local', _('Local')); + o.value('remote', _('Remote')); + o.default = 'remote'; + o.rmempty = false; + + o = s.option(form.ListValue, 'format', _('Format')); + o.value('source', _('Source file')); + o.value('binary', _('Binary file')); + o.default = 'source'; + o.rmempty = false; + + o = s.option(form.Value, 'path', _('Path')); + o.datatype = 'file'; + o.placeholder = '/etc/homeproxy/ruleset/example.json'; + o.rmempty = false; + o.depends('type', 'local'); + o.modalonly = true; + + o = s.option(form.Value, 'url', _('Rule set URL')); + o.validate = function(section_id, value) { + if (section_id) { + if (!value) + return _('Expecting: %s').format(_('non-empty value')); + + try { + var url = new URL(value); + if (!url.hostname) + return _('Expecting: %s').format(_('valid URL')); + } + catch(e) { + return _('Expecting: %s').format(_('valid URL')); + } + } + + return true; + } + o.rmempty = false; + o.depends('type', 'remote'); + o.modalonly = true; + + o = s.option(form.ListValue, 'outbound', _('Outbound'), + _('Tag of the outbound to download rule set.')); + o.load = function(section_id) { + delete this.keylist; + delete this.vallist; + + this.value('direct-out', _('Direct')); + uci.sections(data[0], 'routing_node', (res) => { + if (res.enabled === '1') + this.value(res['.name'], res.label); + }); + + return this.super('load', section_id); + } + o.default = 'direct-out'; + o.rmempty = false; + //o.editable = true; + o.textvalue = function(section_id) { + var cval = this.cfgvalue(section_id) || this.default; + var remote = L.bind(function() { + let cval = this.cfgvalue(section_id) || this.default; + return (cval === 'remote') ? true : false; + }, s.getOption('type')) + return remote() ? cval : _('none'); + }; + o.depends('type', 'remote'); + + o = s.option(form.Value, 'update_interval', _('Update interval'), + _('Update interval of rule set.
1d will be used if empty.')); + o.depends('type', 'remote'); + /* Rule set settings end */ + + return m.render(); + } +}); diff --git a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js index 117f2a0a85..624bc7ad5d 100644 --- a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js +++ b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/server.js @@ -6,6 +6,7 @@ 'use strict'; 'require form'; +'require fs'; 'require poll'; 'require rpc'; 'require uci'; @@ -54,6 +55,7 @@ function handleGenKey(option) { required_method = this.section.getOption('shadowsocks_encrypt_method')?.formvalue(section_id); switch (required_method) { + /* AEAD */ case 'aes-128-gcm': case '2022-blake3-aes-128-gcm': password = hp.generateRand('base64', 16); @@ -68,12 +70,15 @@ function handleGenKey(option) { case '2022-blake3-chacha20-poly1305': password = hp.generateRand('base64', 32); break; + /* NONE */ case 'none': password = ''; break; + /* UUID */ case 'uuid': password = hp.generateRand('uuid'); break; + /* PLAIN */ default: password = hp.generateRand('hex', 16); break; @@ -113,6 +118,17 @@ return view.extend({ s = m.section(form.NamedSection, 'server', 'homeproxy', _('Global settings')); + o = s.option(form.Button, '_reload_server', _('Quick Reload')); + o.inputtitle = _('Reload'); + o.inputstyle = 'apply'; + o.onclick = function() { + return fs.exec('/etc/init.d/homeproxy', ['reload', 'server']) + .then((res) => { return window.location = window.location.href.split('#')[0] }) + .catch((e) => { + ui.addNotification(null, E('p', _('Failed to execute "/etc/init.d/homeproxy %s %s" reason: %s').format('reload', 'server', e))); + }); + }; + o = s.option(form.Flag, 'enabled', _('Enable')); o.default = o.disabled; o.rmempty = false; @@ -122,13 +138,15 @@ return view.extend({ o.rmempty = false; s = m.section(form.GridSection, 'server', _('Server settings')); + var prefmt = { 'prefix': 'server_', 'suffix': '' }; s.addremove = true; s.rowcolors = true; s.sortable = true; s.nodescriptions = true; s.modaltitle = L.bind(hp.loadModalTitle, this, _('Server'), _('Add a server'), data[0]); s.sectiontitle = L.bind(hp.loadDefaultLabel, this, data[0]); - s.renderSectionAdd = L.bind(hp.renderSectionAdd, this, s); + s.renderSectionAdd = L.bind(hp.renderSectionAdd, this, s, prefmt, false); + s.handleAdd = L.bind(hp.handleAdd, this, s, prefmt); o = s.option(form.Value, 'label', _('Label')); o.load = L.bind(hp.loadDefaultLabel, this, data[0]); diff --git a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js index c81162ee2c..17572fa49b 100644 --- a/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js +++ b/small/luci-app-homeproxy/htdocs/luci-static/resources/view/homeproxy/status.js @@ -1,5 +1,4 @@ -/* - * SPDX-License-Identifier: GPL-2.0-only +/* SPDX-License-Identifier: GPL-2.0-only * * Copyright (C) 2022-2023 ImmortalWrt.org */ @@ -60,27 +59,27 @@ function getConnStat(self, site) { ]); } -function getResVersion(self, type) { +function getResVersion(self, type, repo) { var callResVersion = rpc.declare({ object: 'luci.homeproxy', method: 'resources_get_version', - params: ['type'], + params: ['type', 'repo'], expect: { '': {} } }); var callResUpdate = rpc.declare({ object: 'luci.homeproxy', method: 'resources_update', - params: ['type'], + params: ['type', 'repo'], expect: { '': {} } }); - return L.resolveDefault(callResVersion(type), {}).then((res) => { + return L.resolveDefault(callResVersion(type, repo), {}).then((res) => { var spanTemp = E('div', { 'style': 'cbi-value-field' }, [ E('button', { 'class': 'btn cbi-button cbi-button-action', 'click': ui.createHandlerFn(this, function() { - return L.resolveDefault(callResUpdate(type), {}).then((res) => { + return L.resolveDefault(callResUpdate(type, repo), {}).then((res) => { switch (res.status) { case 0: self.description = _('Successfully updated.'); @@ -184,7 +183,8 @@ return view.extend({ render: function(data) { var m, s, o; - var routing_mode = uci.get(data[0], 'config', 'routing_mode') || 'bypass_mainland_china'; + var routing_mode = uci.get(data[0], 'config', 'routing_mode') || 'bypass_mainland_china', + dashboard_repo = uci.get(data[0], 'experimental', 'dashboard_repo') || ''; m = new form.Map('homeproxy'); @@ -201,6 +201,12 @@ return view.extend({ s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management')); s.anonymous = true; + if (routing_mode === 'custom' && dashboard_repo !== '') { + o = s.option(form.DummyValue, '_clash_dashboard_version', _('Clash dashboard version')); + o.cfgvalue = function() { return getResVersion(this, 'clash_dashboard', dashboard_repo) }; + o.rawhtml = true; + } + o = s.option(form.DummyValue, '_china_ip4_version', _('China IPv4 list version')); o.cfgvalue = function() { return getResVersion(this, 'china_ip4') }; o.rawhtml = true; diff --git a/small/luci-app-homeproxy/po/templates/homeproxy.pot b/small/luci-app-homeproxy/po/templates/homeproxy.pot index c23a612df7..dcaccd45e9 100644 --- a/small/luci-app-homeproxy/po/templates/homeproxy.pot +++ b/small/luci-app-homeproxy/po/templates/homeproxy.pot @@ -1,207 +1,232 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" -#: htdocs/luci-static/resources/view/homeproxy/status.js:159 +#: htdocs/luci-static/resources/view/homeproxy/status.js:158 msgid "%s log" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1408 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1520 msgid "%s nodes removed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:571 -#: htdocs/luci-static/resources/view/homeproxy/client.js:905 +#: htdocs/luci-static/resources/view/homeproxy/client.js:633 +#: htdocs/luci-static/resources/view/homeproxy/client.js:978 +#: htdocs/luci-static/resources/view/homeproxy/node.js:710 msgid "-- Please choose --" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:476 +#: htdocs/luci-static/resources/view/homeproxy/client.js:532 msgid "4 or 6. Not limited if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1029 -#: htdocs/luci-static/resources/view/homeproxy/server.js:738 -#: htdocs/luci-static/resources/view/homeproxy/server.js:756 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +msgid " » " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +msgid "." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +msgid ".
you will need to check update via " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1109 +#: htdocs/luci-static/resources/view/homeproxy/server.js:677 +#: htdocs/luci-static/resources/view/homeproxy/server.js:695 msgid "Save your configuration before uploading files!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:647 +#: htdocs/luci-static/resources/view/homeproxy/server.js:586 msgid "API token" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:606 +#: htdocs/luci-static/resources/view/homeproxy/node.js:603 msgid "Accept any if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1068 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1137 msgid "Access Control" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:632 +#: htdocs/luci-static/resources/view/homeproxy/server.js:571 msgid "Access key ID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:637 +#: htdocs/luci-static/resources/view/homeproxy/server.js:576 msgid "Access key secret" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:774 +#: htdocs/luci-static/resources/view/homeproxy/client.js:838 msgid "Add a DNS rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:679 +#: htdocs/luci-static/resources/view/homeproxy/client.js:741 msgid "Add a DNS server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:363 +#: htdocs/luci-static/resources/view/homeproxy/node.js:364 msgid "Add a node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:366 +#: htdocs/luci-static/resources/view/homeproxy/client.js:419 msgid "Add a routing node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:442 +#: htdocs/luci-static/resources/view/homeproxy/client.js:497 msgid "Add a routing rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:989 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:91 msgid "Add a rule set" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:129 +#: htdocs/luci-static/resources/view/homeproxy/server.js:89 msgid "Add a server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:693 -#: htdocs/luci-static/resources/view/homeproxy/node.js:413 +#: htdocs/luci-static/resources/view/homeproxy/client.js:756 +#: htdocs/luci-static/resources/view/homeproxy/node.js:416 msgid "Address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:697 +#: htdocs/luci-static/resources/view/homeproxy/client.js:760 msgid "Address resolver" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:729 +#: htdocs/luci-static/resources/view/homeproxy/client.js:792 msgid "Address strategy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:625 +#: htdocs/luci-static/resources/view/homeproxy/server.js:564 msgid "Alibaba Cloud DNS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:165 -#: htdocs/luci-static/resources/view/homeproxy/client.js:187 +#: htdocs/luci-static/resources/view/homeproxy/client.js:221 +#: htdocs/luci-static/resources/view/homeproxy/client.js:243 msgid "Aliyun Public DNS (223.5.5.5)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:237 +#: htdocs/luci-static/resources/view/homeproxy/client.js:293 msgid "All ports" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:982 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1334 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1062 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1446 msgid "Allow insecure" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:983 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1063 msgid "Allow insecure connection at TLS client." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1335 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1447 msgid "Allow insecure connection by default when add nodes from subscriptions." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:813 -#: htdocs/luci-static/resources/view/homeproxy/server.js:463 +#: htdocs/luci-static/resources/view/homeproxy/node.js:893 +#: htdocs/luci-static/resources/view/homeproxy/server.js:402 msgid "Allowed payload size is in the request." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:95 +#: htdocs/luci-static/resources/view/homeproxy/status.js:94 msgid "Already at the latest version." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:92 +#: htdocs/luci-static/resources/view/homeproxy/status.js:91 msgid "Already in updating." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:679 -#: htdocs/luci-static/resources/view/homeproxy/server.js:372 +#: htdocs/luci-static/resources/view/homeproxy/node.js:676 +#: htdocs/luci-static/resources/view/homeproxy/server.js:311 msgid "Alter ID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:662 +#: htdocs/luci-static/resources/view/homeproxy/server.js:601 msgid "Alternative HTTP port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:668 +#: htdocs/luci-static/resources/view/homeproxy/server.js:607 msgid "Alternative TLS port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1371 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1483 msgid "An error occurred during updating subscriptions: %s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:931 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1004 msgid "Any" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:653 -#: htdocs/luci-static/resources/view/homeproxy/client.js:759 -#: htdocs/luci-static/resources/view/homeproxy/client.js:973 +#: htdocs/luci-static/resources/view/homeproxy/client.js:714 +#: htdocs/luci-static/resources/view/homeproxy/client.js:822 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1053 msgid "" "Append a edns0-subnet OPT extra record with the specified IP " "prefix to every query by default.
If value is an IP address instead of " "prefix, /32 or /128 will be appended automatically." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1015 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1095 msgid "Append self-signed certificate" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:374 +#: htdocs/luci-static/resources/view/homeproxy/node.js:375 msgid "Applied" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:367 -#: htdocs/luci-static/resources/view/homeproxy/node.js:377 +#: htdocs/luci-static/resources/view/homeproxy/node.js:368 +#: htdocs/luci-static/resources/view/homeproxy/node.js:378 msgid "Apply" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:18 +#: htdocs/luci-static/resources/view/homeproxy/node.js:19 msgid "Are you sure to allow insecure?" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:343 +#: htdocs/luci-static/resources/view/homeproxy/server.js:282 msgid "Auth timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:703 +#: htdocs/luci-static/resources/view/homeproxy/node.js:700 msgid "Authenticated length" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:498 -#: htdocs/luci-static/resources/view/homeproxy/server.js:252 +#: htdocs/luci-static/resources/view/homeproxy/node.js:495 +#: htdocs/luci-static/resources/view/homeproxy/server.js:202 msgid "Authentication payload" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:491 -#: htdocs/luci-static/resources/view/homeproxy/server.js:245 +#: htdocs/luci-static/resources/view/homeproxy/node.js:488 +#: htdocs/luci-static/resources/view/homeproxy/server.js:195 msgid "Authentication type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:120 +#: htdocs/luci-static/resources/view/homeproxy/server.js:79 msgid "Auto configure firewall" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1288 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1121 +msgid "Auto set backend" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1122 +msgid "Auto set backend address for dashboard." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1398 msgid "Auto update" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1289 -msgid "Auto update subscriptions and geodata." +#: htdocs/luci-static/resources/view/homeproxy/node.js:1399 +msgid "Auto update subscriptions." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:637 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1130 +msgid "Automatically generated if empty" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:634 msgid "BBR" msgstr "" @@ -209,119 +234,137 @@ msgstr "" msgid "BaiDu" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:493 -#: htdocs/luci-static/resources/view/homeproxy/server.js:247 +#: htdocs/luci-static/resources/view/homeproxy/node.js:490 +#: htdocs/luci-static/resources/view/homeproxy/server.js:197 msgid "Base64" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:301 +#: htdocs/luci-static/resources/view/homeproxy/client.js:360 msgid "Based on google/gvisor." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1011 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:202 msgid "Binary file" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:393 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1081 +#: htdocs/luci-static/resources/view/homeproxy/client.js:447 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1150 msgid "Bind interface" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1082 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1151 msgid "" "Bind outbound traffic to specific interface. Leave empty to auto detect." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1324 +#: htdocs/luci-static/resources/view/homeproxy/node.js:742 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1437 msgid "Blacklist mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:344 -#: htdocs/luci-static/resources/view/homeproxy/client.js:599 -#: htdocs/luci-static/resources/view/homeproxy/client.js:933 +#: htdocs/luci-static/resources/view/homeproxy/client.js:389 +#: htdocs/luci-static/resources/view/homeproxy/client.js:660 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1006 +#: htdocs/luci-static/resources/view/homeproxy/node.js:722 +#: htdocs/luci-static/resources/view/homeproxy/node.js:732 msgid "Block" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:629 -#: htdocs/luci-static/resources/view/homeproxy/client.js:951 +#: htdocs/luci-static/resources/view/homeproxy/client.js:690 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1031 msgid "Block DNS queries" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:479 -#: htdocs/luci-static/resources/view/homeproxy/client.js:492 -#: htdocs/luci-static/resources/view/homeproxy/client.js:810 -#: htdocs/luci-static/resources/view/homeproxy/client.js:820 -#: htdocs/luci-static/resources/view/homeproxy/server.js:802 +#: htdocs/luci-static/resources/view/homeproxy/client.js:535 +#: htdocs/luci-static/resources/view/homeproxy/client.js:548 +#: htdocs/luci-static/resources/view/homeproxy/client.js:875 +#: htdocs/luci-static/resources/view/homeproxy/client.js:885 +#: htdocs/luci-static/resources/view/homeproxy/server.js:734 msgid "Both" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:322 +#: htdocs/luci-static/resources/view/homeproxy/client.js:372 msgid "Bypass CN traffic" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:224 +#: htdocs/luci-static/resources/view/homeproxy/client.js:280 msgid "Bypass mainland China" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:323 +#: htdocs/luci-static/resources/view/homeproxy/client.js:373 msgid "Bypass mainland China traffic via firewall rules by default." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:611 +#: htdocs/luci-static/resources/view/homeproxy/server.js:550 msgid "CA provider" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:188 +#: htdocs/luci-static/resources/view/homeproxy/client.js:244 msgid "CNNIC Public DNS (210.2.4.8)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:635 +#: htdocs/luci-static/resources/view/homeproxy/node.js:632 msgid "CUBIC" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1192 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1269 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:105 msgid "Cancel" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1021 -#: htdocs/luci-static/resources/view/homeproxy/server.js:727 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1101 +#: htdocs/luci-static/resources/view/homeproxy/server.js:666 msgid "Certificate path" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:57 +#: htdocs/luci-static/resources/view/homeproxy/status.js:56 msgid "Check" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:105 +#: htdocs/luci-static/resources/view/homeproxy/status.js:104 msgid "Check update" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:185 +#: htdocs/luci-static/resources/view/homeproxy/client.js:241 msgid "China DNS server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:204 +#: htdocs/luci-static/resources/view/homeproxy/status.js:210 msgid "China IPv4 list version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:208 +#: htdocs/luci-static/resources/view/homeproxy/status.js:214 msgid "China IPv6 list version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:212 +#: htdocs/luci-static/resources/view/homeproxy/status.js:218 msgid "China list version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1007 -#: htdocs/luci-static/resources/view/homeproxy/server.js:569 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1087 +#: htdocs/luci-static/resources/view/homeproxy/server.js:508 msgid "Cipher suites" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:162 +#: htdocs/luci-static/resources/view/homeproxy/client.js:218 msgid "Cisco Public DNS (208.67.222.222)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:166 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1060 +msgid "Clash API settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/status.js:205 +msgid "Clash dashboard version" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:619 +#: htdocs/luci-static/resources/view/homeproxy/client.js:964 +msgid "Clash mode" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/status.js:165 msgid "Clean log" msgstr "" @@ -329,30 +372,34 @@ msgstr "" msgid "Client Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:600 +#: htdocs/luci-static/resources/view/homeproxy/node.js:597 msgid "Client version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:161 +#: htdocs/luci-static/resources/view/homeproxy/client.js:217 msgid "CloudFlare Public DNS (1.1.1.1)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:626 +#: htdocs/luci-static/resources/view/homeproxy/server.js:565 msgid "Cloudflare" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:133 -#: htdocs/luci-static/resources/view/homeproxy/server.js:110 -#: htdocs/luci-static/resources/view/homeproxy/status.js:129 +#: htdocs/luci-static/resources/view/homeproxy/client.js:183 +#: htdocs/luci-static/resources/view/homeproxy/server.js:69 +#: htdocs/luci-static/resources/view/homeproxy/status.js:128 msgid "Collecting data..." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:238 +#: htdocs/luci-static/resources/view/homeproxy/client.js:109 +msgid "Command failed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:294 msgid "Common ports only (bypass P2P traffic)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:633 -#: htdocs/luci-static/resources/view/homeproxy/server.js:334 +#: htdocs/luci-static/resources/view/homeproxy/node.js:630 +#: htdocs/luci-static/resources/view/homeproxy/server.js:273 msgid "Congestion control algorithm" msgstr "" @@ -360,214 +407,233 @@ msgstr "" msgid "Connection check" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:226 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1403 +msgid "Cron expression" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:282 msgid "Custom routing" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:827 +#: htdocs/luci-static/resources/view/homeproxy/client.js:892 msgid "DNS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:765 +#: htdocs/luci-static/resources/view/homeproxy/client.js:828 msgid "DNS Rules" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:670 +#: htdocs/luci-static/resources/view/homeproxy/client.js:731 msgid "DNS Servers" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:612 +#: htdocs/luci-static/resources/view/homeproxy/client.js:673 msgid "DNS Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:624 +#: htdocs/luci-static/resources/view/homeproxy/server.js:563 msgid "DNS provider" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:774 +#: htdocs/luci-static/resources/view/homeproxy/client.js:838 msgid "DNS rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:158 -#: htdocs/luci-static/resources/view/homeproxy/client.js:679 +#: htdocs/luci-static/resources/view/homeproxy/client.js:214 +#: htdocs/luci-static/resources/view/homeproxy/client.js:741 msgid "DNS server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:619 +#: htdocs/luci-static/resources/view/homeproxy/server.js:558 msgid "DNS01 challenge" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:17 -#: htdocs/luci-static/resources/view/homeproxy/client.js:470 -#: htdocs/luci-static/resources/view/homeproxy/client.js:802 -#: htdocs/luci-static/resources/view/homeproxy/node.js:645 +#: htdocs/luci-static/resources/homeproxy.js:18 +#: htdocs/luci-static/resources/view/homeproxy/client.js:526 +#: htdocs/luci-static/resources/view/homeproxy/client.js:867 +#: htdocs/luci-static/resources/view/homeproxy/node.js:642 +#: htdocs/luci-static/resources/view/homeproxy/node.js:730 +#: htdocs/luci-static/resources/view/homeproxy/node.js:758 +#: htdocs/luci-static/resources/view/homeproxy/node.js:765 msgid "Default" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:627 -#: htdocs/luci-static/resources/view/homeproxy/client.js:704 -#: htdocs/luci-static/resources/view/homeproxy/client.js:949 +#: htdocs/luci-static/resources/view/homeproxy/client.js:688 +#: htdocs/luci-static/resources/view/homeproxy/client.js:767 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1029 msgid "Default DNS (issued by WAN)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:622 +#: htdocs/luci-static/resources/view/homeproxy/client.js:683 msgid "Default DNS server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:617 +#: htdocs/luci-static/resources/view/homeproxy/client.js:678 msgid "Default DNS strategy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:736 +#: htdocs/luci-static/resources/view/homeproxy/node.js:728 +msgid "Default Outbound" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:799 msgid "Default domain strategy for resolving the domain names." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:337 +#: htdocs/luci-static/resources/view/homeproxy/client.js:382 msgid "Default outbound" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1342 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1454 msgid "Default packet encoding" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:590 +#: htdocs/luci-static/resources/view/homeproxy/server.js:529 msgid "Default server name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:343 -#: htdocs/luci-static/resources/view/homeproxy/client.js:406 -#: htdocs/luci-static/resources/view/homeproxy/client.js:598 -#: htdocs/luci-static/resources/view/homeproxy/client.js:746 -#: htdocs/luci-static/resources/view/homeproxy/client.js:932 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1050 -#: htdocs/luci-static/resources/view/homeproxy/node.js:394 +#: htdocs/luci-static/resources/view/homeproxy/client.js:388 +#: htdocs/luci-static/resources/view/homeproxy/client.js:460 +#: htdocs/luci-static/resources/view/homeproxy/client.js:624 +#: htdocs/luci-static/resources/view/homeproxy/client.js:659 +#: htdocs/luci-static/resources/view/homeproxy/client.js:809 +#: htdocs/luci-static/resources/view/homeproxy/client.js:969 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1005 +#: htdocs/luci-static/resources/view/homeproxy/node.js:395 +#: htdocs/luci-static/resources/view/homeproxy/node.js:721 +#: htdocs/luci-static/resources/view/homeproxy/node.js:731 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:241 msgid "Direct" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1180 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1249 msgid "Direct Domain List" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1097 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1166 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 msgid "Direct IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1100 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1145 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1169 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1214 msgid "Direct IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1103 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1172 msgid "Direct MAC-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:142 -#: htdocs/luci-static/resources/view/homeproxy/client.js:150 -#: htdocs/luci-static/resources/view/homeproxy/client.js:342 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1091 -#: htdocs/luci-static/resources/view/homeproxy/node.js:473 -#: htdocs/luci-static/resources/view/homeproxy/node.js:492 -#: htdocs/luci-static/resources/view/homeproxy/node.js:504 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1061 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1323 -#: htdocs/luci-static/resources/view/homeproxy/server.js:246 -#: htdocs/luci-static/resources/view/homeproxy/server.js:258 +#: htdocs/luci-static/resources/view/homeproxy/client.js:198 +#: htdocs/luci-static/resources/view/homeproxy/client.js:206 +#: htdocs/luci-static/resources/view/homeproxy/client.js:387 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1160 +#: htdocs/luci-static/resources/view/homeproxy/node.js:489 +#: htdocs/luci-static/resources/view/homeproxy/node.js:501 +#: htdocs/luci-static/resources/view/homeproxy/node.js:741 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1141 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1436 +#: htdocs/luci-static/resources/view/homeproxy/server.js:196 +#: htdocs/luci-static/resources/view/homeproxy/server.js:208 msgid "Disable" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:640 +#: htdocs/luci-static/resources/view/homeproxy/client.js:701 msgid "Disable DNS cache" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:652 +#: htdocs/luci-static/resources/view/homeproxy/server.js:591 msgid "Disable HTTP challenge" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:540 -#: htdocs/luci-static/resources/view/homeproxy/server.js:289 +#: htdocs/luci-static/resources/view/homeproxy/node.js:537 +#: htdocs/luci-static/resources/view/homeproxy/server.js:239 msgid "Disable Path MTU discovery" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:657 +#: htdocs/luci-static/resources/view/homeproxy/server.js:596 msgid "Disable TLS ALPN challenge" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:963 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1043 msgid "Disable cache and save cache in this query." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:643 +#: htdocs/luci-static/resources/view/homeproxy/client.js:704 msgid "Disable cache expire" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:962 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1042 msgid "Disable dns cache" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1043 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1123 msgid "Disable dynamic record sizing" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:541 -#: htdocs/luci-static/resources/view/homeproxy/server.js:290 +#: htdocs/luci-static/resources/view/homeproxy/node.js:538 +#: htdocs/luci-static/resources/view/homeproxy/server.js:240 msgid "" "Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 " "(IPv4) / 1232 (IPv6) bytes in size." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:503 -#: htdocs/luci-static/resources/view/homeproxy/client.js:839 +#: htdocs/luci-static/resources/view/homeproxy/client.js:559 +#: htdocs/luci-static/resources/view/homeproxy/client.js:904 msgid "Domain keyword" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:494 -#: htdocs/luci-static/resources/view/homeproxy/client.js:830 +#: htdocs/luci-static/resources/view/homeproxy/client.js:550 +#: htdocs/luci-static/resources/view/homeproxy/client.js:895 msgid "Domain name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:507 -#: htdocs/luci-static/resources/view/homeproxy/client.js:843 +#: htdocs/luci-static/resources/view/homeproxy/client.js:563 +#: htdocs/luci-static/resources/view/homeproxy/client.js:908 msgid "Domain regex" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:327 -#: htdocs/luci-static/resources/view/homeproxy/client.js:387 -#: htdocs/luci-static/resources/view/homeproxy/server.js:793 +#: htdocs/luci-static/resources/view/homeproxy/client.js:441 +#: htdocs/luci-static/resources/view/homeproxy/server.js:725 msgid "Domain strategy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:499 -#: htdocs/luci-static/resources/view/homeproxy/client.js:835 +#: htdocs/luci-static/resources/view/homeproxy/client.js:555 +#: htdocs/luci-static/resources/view/homeproxy/client.js:900 msgid "Domain suffix" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:584 +#: htdocs/luci-static/resources/view/homeproxy/server.js:523 msgid "Domains" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:931 -#: htdocs/luci-static/resources/view/homeproxy/server.js:501 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1011 +#: htdocs/luci-static/resources/view/homeproxy/server.js:440 msgid "Download bandwidth" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:932 -#: htdocs/luci-static/resources/view/homeproxy/server.js:502 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1012 +#: htdocs/luci-static/resources/view/homeproxy/server.js:441 msgid "Download bandwidth in Mbps." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1330 +#: htdocs/luci-static/resources/view/homeproxy/node.js:750 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1442 msgid "" "Drop/keep nodes that contain the specific keywords. Regex is supported." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1322 +#: htdocs/luci-static/resources/view/homeproxy/node.js:740 +msgid "Drop/keep specific nodes from outbounds." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1435 msgid "Drop/keep specific nodes from subscriptions." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:675 +#: htdocs/luci-static/resources/view/homeproxy/server.js:614 msgid "" "EAB (External Account Binding) contains information necessary to bind or map " "an ACME account to some other account known by the CA.
External account " @@ -575,235 +641,251 @@ msgid "" "a non-ACME system, such as a CA customer database." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1038 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1118 msgid "" "ECH (Encrypted Client Hello) is a TLS extension that allows a client to " "encrypt the first part of its ClientHello message." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1053 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1133 msgid "ECH config" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:652 -#: htdocs/luci-static/resources/view/homeproxy/client.js:758 -#: htdocs/luci-static/resources/view/homeproxy/client.js:972 +#: htdocs/luci-static/resources/view/homeproxy/client.js:713 +#: htdocs/luci-static/resources/view/homeproxy/client.js:821 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1052 msgid "EDNS Client subnet" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:812 -#: htdocs/luci-static/resources/view/homeproxy/server.js:462 +#: htdocs/luci-static/resources/view/homeproxy/node.js:892 +#: htdocs/luci-static/resources/view/homeproxy/server.js:401 msgid "Early data" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:819 -#: htdocs/luci-static/resources/view/homeproxy/server.js:469 +#: htdocs/luci-static/resources/view/homeproxy/node.js:899 +#: htdocs/luci-static/resources/view/homeproxy/server.js:408 msgid "Early data header name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:470 +#: htdocs/luci-static/resources/view/homeproxy/server.js:409 msgid "Early data is sent in path instead of header by default." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1164 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1238 msgid "Edit nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:596 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:82 +msgid "Edit ruleset" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/server.js:535 msgid "Email" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:375 -#: htdocs/luci-static/resources/view/homeproxy/client.js:457 -#: htdocs/luci-static/resources/view/homeproxy/client.js:688 -#: htdocs/luci-static/resources/view/homeproxy/client.js:789 -#: htdocs/luci-static/resources/view/homeproxy/client.js:998 -#: htdocs/luci-static/resources/view/homeproxy/server.js:116 -#: htdocs/luci-static/resources/view/homeproxy/server.js:139 +#: htdocs/luci-static/resources/view/homeproxy/client.js:429 +#: htdocs/luci-static/resources/view/homeproxy/client.js:513 +#: htdocs/luci-static/resources/view/homeproxy/client.js:751 +#: htdocs/luci-static/resources/view/homeproxy/client.js:854 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:189 +#: htdocs/luci-static/resources/view/homeproxy/server.js:75 +#: htdocs/luci-static/resources/view/homeproxy/server.js:100 msgid "Enable" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:658 -#: htdocs/luci-static/resources/view/homeproxy/server.js:351 +#: htdocs/luci-static/resources/view/homeproxy/node.js:655 +#: htdocs/luci-static/resources/view/homeproxy/server.js:290 msgid "" "Enable 0-RTT QUIC connection handshake on the client side. This is not " "impacting much on the performance, as the protocol is fully multiplexed.
Disabling this is highly recommended, as it is vulnerable to replay attacks." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:657 -#: htdocs/luci-static/resources/view/homeproxy/server.js:350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:654 +#: htdocs/luci-static/resources/view/homeproxy/server.js:289 msgid "Enable 0-RTT handshake" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:578 +#: htdocs/luci-static/resources/view/homeproxy/server.js:517 msgid "Enable ACME" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1037 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1065 +msgid "Enable Clash API" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1117 msgid "Enable ECH" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1048 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1128 msgid "Enable PQ signature schemes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:925 -#: htdocs/luci-static/resources/view/homeproxy/server.js:495 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1005 +#: htdocs/luci-static/resources/view/homeproxy/server.js:434 msgid "Enable TCP Brutal" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:926 -#: htdocs/luci-static/resources/view/homeproxy/server.js:496 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1006 +#: htdocs/luci-static/resources/view/homeproxy/server.js:435 msgid "Enable TCP Brutal congestion control algorithm" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1119 -#: htdocs/luci-static/resources/view/homeproxy/server.js:777 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1201 +#: htdocs/luci-static/resources/view/homeproxy/server.js:716 msgid "Enable UDP fragmentation." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:306 +#: htdocs/luci-static/resources/view/homeproxy/client.js:365 msgid "Enable endpoint-independent NAT" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:920 -#: htdocs/luci-static/resources/view/homeproxy/server.js:489 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1000 +#: htdocs/luci-static/resources/view/homeproxy/server.js:428 msgid "Enable padding" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:766 +#: htdocs/luci-static/resources/view/homeproxy/server.js:705 msgid "Enable tcp fast open for listener." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1124 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1207 msgid "" "Enable the SUoT protocol, requires server support. Conflict with multiplex." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:548 -#: htdocs/luci-static/resources/view/homeproxy/node.js:685 -#: htdocs/luci-static/resources/view/homeproxy/server.js:308 +#: htdocs/luci-static/resources/view/homeproxy/node.js:545 +#: htdocs/luci-static/resources/view/homeproxy/node.js:682 +#: htdocs/luci-static/resources/view/homeproxy/server.js:258 msgid "Encrypt method" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:221 -#: htdocs/luci-static/resources/homeproxy.js:255 -#: htdocs/luci-static/resources/homeproxy.js:263 -#: htdocs/luci-static/resources/homeproxy.js:272 -#: htdocs/luci-static/resources/homeproxy.js:281 -#: htdocs/luci-static/resources/homeproxy.js:283 -#: htdocs/luci-static/resources/view/homeproxy/client.js:75 -#: htdocs/luci-static/resources/view/homeproxy/client.js:176 -#: htdocs/luci-static/resources/view/homeproxy/client.js:178 -#: htdocs/luci-static/resources/view/homeproxy/client.js:206 -#: htdocs/luci-static/resources/view/homeproxy/client.js:246 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1026 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1031 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1034 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1173 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 -#: htdocs/luci-static/resources/view/homeproxy/node.js:452 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1082 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1255 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1311 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1314 -#: htdocs/luci-static/resources/view/homeproxy/server.js:211 -#: htdocs/luci-static/resources/view/homeproxy/server.js:602 -#: htdocs/luci-static/resources/view/homeproxy/server.js:604 +#: htdocs/luci-static/resources/homeproxy.js:237 +#: htdocs/luci-static/resources/homeproxy.js:240 +#: htdocs/luci-static/resources/homeproxy.js:243 +#: htdocs/luci-static/resources/homeproxy.js:284 +#: htdocs/luci-static/resources/homeproxy.js:292 +#: htdocs/luci-static/resources/homeproxy.js:301 +#: htdocs/luci-static/resources/homeproxy.js:310 +#: htdocs/luci-static/resources/homeproxy.js:312 +#: htdocs/luci-static/resources/view/homeproxy/client.js:130 +#: htdocs/luci-static/resources/view/homeproxy/client.js:232 +#: htdocs/luci-static/resources/view/homeproxy/client.js:234 +#: htdocs/luci-static/resources/view/homeproxy/client.js:262 +#: htdocs/luci-static/resources/view/homeproxy/client.js:300 +#: htdocs/luci-static/resources/view/homeproxy/client.js:305 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1017 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1242 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1271 +#: htdocs/luci-static/resources/view/homeproxy/node.js:455 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1162 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1353 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1427 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:161 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:164 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:217 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:222 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:225 +#: htdocs/luci-static/resources/view/homeproxy/server.js:161 +#: htdocs/luci-static/resources/view/homeproxy/server.js:541 +#: htdocs/luci-static/resources/view/homeproxy/server.js:543 msgid "Expecting: %s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:674 +#: htdocs/luci-static/resources/view/homeproxy/server.js:613 msgid "External Account Binding" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:686 +#: htdocs/luci-static/resources/view/homeproxy/server.js:625 msgid "External account MAC key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:681 +#: htdocs/luci-static/resources/view/homeproxy/server.js:620 msgid "External account key ID" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:245 +#: htdocs/luci-static/resources/view/homeproxy/client.js:113 +msgid "Failed to execute \"/etc/init.d/%s %s\" action: %s" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:274 msgid "Failed to upload %s, error: %s." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1329 +#: htdocs/luci-static/resources/view/homeproxy/node.js:749 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1441 msgid "Filter keywords" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1321 +#: htdocs/luci-static/resources/view/homeproxy/node.js:739 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1434 msgid "Filter nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:673 -#: htdocs/luci-static/resources/view/homeproxy/server.js:366 +#: htdocs/luci-static/resources/view/homeproxy/node.js:670 +#: htdocs/luci-static/resources/view/homeproxy/server.js:305 msgid "Flow" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1009 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:200 msgid "Format" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:781 +#: htdocs/luci-static/resources/view/homeproxy/node.js:861 msgid "GET" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:216 +#: htdocs/luci-static/resources/view/homeproxy/status.js:222 msgid "GFW list version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:223 +#: htdocs/luci-static/resources/view/homeproxy/client.js:279 msgid "GFWList" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1115 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1184 msgid "Gaming mode IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1117 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1186 msgid "Gaming mode IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1120 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1189 msgid "Gaming mode MAC-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:188 -#: htdocs/luci-static/resources/view/homeproxy/server.js:190 -#: htdocs/luci-static/resources/view/homeproxy/server.js:325 -#: htdocs/luci-static/resources/view/homeproxy/server.js:327 -msgid "Generate" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:279 -#: htdocs/luci-static/resources/view/homeproxy/node.js:835 +#: htdocs/luci-static/resources/view/homeproxy/client.js:338 +#: htdocs/luci-static/resources/view/homeproxy/node.js:915 msgid "Generic segmentation offload" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:283 +#: htdocs/luci-static/resources/view/homeproxy/client.js:622 +#: htdocs/luci-static/resources/view/homeproxy/client.js:967 msgid "Global" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:696 +#: htdocs/luci-static/resources/view/homeproxy/node.js:693 msgid "Global padding" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1122 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1191 msgid "Global proxy IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1125 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1194 msgid "Global proxy IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1128 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1197 msgid "Global proxy MAC-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:114 +#: htdocs/luci-static/resources/view/homeproxy/server.js:73 msgid "Global settings" msgstr "" @@ -811,7 +893,7 @@ msgstr "" msgid "Google" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:163 +#: htdocs/luci-static/resources/view/homeproxy/client.js:219 msgid "Google Public DNS (8.8.8.8)" msgstr "" @@ -819,552 +901,608 @@ msgstr "" msgid "Grant access to homeproxy configuration" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:484 -#: htdocs/luci-static/resources/view/homeproxy/client.js:824 -#: htdocs/luci-static/resources/view/homeproxy/node.js:395 -#: htdocs/luci-static/resources/view/homeproxy/node.js:715 -#: htdocs/luci-static/resources/view/homeproxy/server.js:145 -#: htdocs/luci-static/resources/view/homeproxy/server.js:384 +#: htdocs/luci-static/resources/view/homeproxy/client.js:540 +#: htdocs/luci-static/resources/view/homeproxy/client.js:889 +#: htdocs/luci-static/resources/view/homeproxy/node.js:396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:795 +#: htdocs/luci-static/resources/view/homeproxy/server.js:106 +#: htdocs/luci-static/resources/view/homeproxy/server.js:323 msgid "HTTP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:302 +#: htdocs/luci-static/resources/view/homeproxy/server.js:252 msgid "" "HTTP3 server behavior when authentication fails.
A 404 page will be " "returned if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:716 -#: htdocs/luci-static/resources/view/homeproxy/server.js:385 +#: htdocs/luci-static/resources/view/homeproxy/node.js:796 +#: htdocs/luci-static/resources/view/homeproxy/server.js:324 msgid "HTTPUpgrade" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:714 +#: htdocs/luci-static/resources/view/homeproxy/server.js:653 msgid "Handshake server address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:720 +#: htdocs/luci-static/resources/view/homeproxy/server.js:659 msgid "Handshake server port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:664 -#: htdocs/luci-static/resources/view/homeproxy/server.js:357 +#: htdocs/luci-static/resources/view/homeproxy/node.js:661 +#: htdocs/luci-static/resources/view/homeproxy/server.js:296 msgid "Heartbeat interval" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 -#: htdocs/luci-static/resources/view/homeproxy/client.js:120 -#: htdocs/luci-static/resources/view/homeproxy/status.js:224 +#: htdocs/luci-static/resources/view/homeproxy/client.js:99 +#: htdocs/luci-static/resources/view/homeproxy/client.js:101 +#: htdocs/luci-static/resources/view/homeproxy/client.js:170 +#: htdocs/luci-static/resources/view/homeproxy/status.js:230 #: root/usr/share/luci/menu.d/luci-app-homeproxy.json:3 msgid "HomeProxy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:38 -#: htdocs/luci-static/resources/view/homeproxy/server.js:40 -#: htdocs/luci-static/resources/view/homeproxy/server.js:97 +#: htdocs/luci-static/resources/view/homeproxy/server.js:37 +#: htdocs/luci-static/resources/view/homeproxy/server.js:39 +#: htdocs/luci-static/resources/view/homeproxy/server.js:56 msgid "HomeProxy Server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:765 -#: htdocs/luci-static/resources/view/homeproxy/node.js:770 -#: htdocs/luci-static/resources/view/homeproxy/node.js:804 -#: htdocs/luci-static/resources/view/homeproxy/server.js:418 -#: htdocs/luci-static/resources/view/homeproxy/server.js:423 -#: htdocs/luci-static/resources/view/homeproxy/server.js:454 +#: htdocs/luci-static/resources/view/homeproxy/node.js:845 +#: htdocs/luci-static/resources/view/homeproxy/node.js:850 +#: htdocs/luci-static/resources/view/homeproxy/node.js:884 +#: htdocs/luci-static/resources/view/homeproxy/server.js:357 +#: htdocs/luci-static/resources/view/homeproxy/server.js:362 +#: htdocs/luci-static/resources/view/homeproxy/server.js:393 msgid "Host" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:447 -#: htdocs/luci-static/resources/view/homeproxy/client.js:779 +#: htdocs/luci-static/resources/view/homeproxy/client.js:503 +#: htdocs/luci-static/resources/view/homeproxy/client.js:844 msgid "Host fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:605 +#: htdocs/luci-static/resources/view/homeproxy/node.js:602 msgid "Host key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:610 +#: htdocs/luci-static/resources/view/homeproxy/node.js:607 msgid "Host key algorithms" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:344 +#: htdocs/luci-static/resources/view/homeproxy/server.js:283 msgid "" "How long the server should wait for the client to send the authentication " "command (in seconds)." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:397 -#: htdocs/luci-static/resources/view/homeproxy/server.js:147 +#: htdocs/luci-static/resources/view/homeproxy/node.js:398 +#: htdocs/luci-static/resources/view/homeproxy/server.js:108 msgid "Hysteria" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:398 -#: htdocs/luci-static/resources/view/homeproxy/server.js:148 +#: htdocs/luci-static/resources/view/homeproxy/node.js:399 +#: htdocs/luci-static/resources/view/homeproxy/server.js:109 msgid "Hysteria2" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:522 -#: htdocs/luci-static/resources/view/homeproxy/client.js:867 +#: htdocs/luci-static/resources/view/homeproxy/client.js:577 +#: htdocs/luci-static/resources/view/homeproxy/client.js:932 msgid "IP CIDR" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:475 -#: htdocs/luci-static/resources/view/homeproxy/client.js:807 +#: htdocs/luci-static/resources/view/homeproxy/client.js:531 +#: htdocs/luci-static/resources/view/homeproxy/client.js:872 msgid "IP version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:477 -#: htdocs/luci-static/resources/view/homeproxy/client.js:808 +#: htdocs/luci-static/resources/view/homeproxy/client.js:533 +#: htdocs/luci-static/resources/view/homeproxy/client.js:873 msgid "IPv4" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:20 +#: htdocs/luci-static/resources/homeproxy.js:21 msgid "IPv4 only" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:478 -#: htdocs/luci-static/resources/view/homeproxy/client.js:809 +#: htdocs/luci-static/resources/view/homeproxy/client.js:534 +#: htdocs/luci-static/resources/view/homeproxy/client.js:874 msgid "IPv6" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:21 +#: htdocs/luci-static/resources/homeproxy.js:22 msgid "IPv6 only" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:269 +#: htdocs/luci-static/resources/view/homeproxy/client.js:328 msgid "IPv6 support" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:786 -#: htdocs/luci-static/resources/view/homeproxy/server.js:437 +#: htdocs/luci-static/resources/view/homeproxy/node.js:776 +#: htdocs/luci-static/resources/view/homeproxy/node.js:866 +#: htdocs/luci-static/resources/view/homeproxy/server.js:376 msgid "Idle timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:757 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1017 +msgid "If Any is selected, uncheck others" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:837 msgid "" "If enabled, the client transport sends keepalive pings even with no active " "connections." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:328 -#: htdocs/luci-static/resources/view/homeproxy/server.js:794 +#: htdocs/luci-static/resources/view/homeproxy/server.js:726 msgid "" "If set, the requested domain name will be resolved to IP before routing." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:388 +#: htdocs/luci-static/resources/view/homeproxy/client.js:442 msgid "" -"If set, the server domain name will be resolved to IP before connecting.
" +"If set, the server domain name will be resolved to IP before connecting.
dns.strategy will be used if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:742 -#: htdocs/luci-static/resources/view/homeproxy/server.js:406 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +msgid "If the selected dashboard is " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:822 +#: htdocs/luci-static/resources/view/homeproxy/server.js:345 msgid "" "If the transport doesn't see any activity after a duration of this time (in " "seconds), it pings the client to check if the connection is still active." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1016 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1096 msgid "" "If you have the root certificate, use this option instead of allowing " "insecure." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:295 +#: htdocs/luci-static/resources/view/homeproxy/server.js:245 msgid "Ignore client bandwidth" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1238 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1315 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:144 msgid "Import" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1185 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1264 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1266 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:98 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:173 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:175 +msgid "Import rule-set links" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1262 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1364 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1366 msgid "Import share links" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:314 -#: htdocs/luci-static/resources/view/homeproxy/server.js:783 -msgid "In seconds. 300 is used by default." -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:647 +#: htdocs/luci-static/resources/view/homeproxy/client.js:708 msgid "Independent cache per server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1074 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1104 +msgid "Installed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1143 msgid "Interface Control" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:665 -#: htdocs/luci-static/resources/view/homeproxy/server.js:358 +#: htdocs/luci-static/resources/view/homeproxy/node.js:782 +msgid "Interrupt existing connections" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:783 +msgid "Interrupt existing connections when the selected outbound has changed." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:763 +msgid "Interval" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:662 +#: htdocs/luci-static/resources/view/homeproxy/server.js:297 msgid "" "Interval for sending heartbeat packets for keeping the connection alive (in " "seconds)." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:587 -#: htdocs/luci-static/resources/view/homeproxy/client.js:920 +#: htdocs/luci-static/resources/view/homeproxy/client.js:648 +#: htdocs/luci-static/resources/view/homeproxy/client.js:993 msgid "Invert" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:588 -#: htdocs/luci-static/resources/view/homeproxy/client.js:921 +#: htdocs/luci-static/resources/view/homeproxy/client.js:649 +#: htdocs/luci-static/resources/view/homeproxy/client.js:994 msgid "Invert match result." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:159 +#: htdocs/luci-static/resources/view/homeproxy/client.js:215 msgid "It MUST support TCP query." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:745 +#: htdocs/luci-static/resources/view/homeproxy/server.js:684 msgid "Key path" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1088 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1157 msgid "LAN IP Policy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:370 -#: htdocs/luci-static/resources/view/homeproxy/client.js:452 -#: htdocs/luci-static/resources/view/homeproxy/client.js:683 -#: htdocs/luci-static/resources/view/homeproxy/client.js:784 -#: htdocs/luci-static/resources/view/homeproxy/client.js:993 -#: htdocs/luci-static/resources/view/homeproxy/node.js:388 -#: htdocs/luci-static/resources/view/homeproxy/server.js:133 +#: htdocs/luci-static/resources/view/homeproxy/client.js:424 +#: htdocs/luci-static/resources/view/homeproxy/client.js:508 +#: htdocs/luci-static/resources/view/homeproxy/client.js:746 +#: htdocs/luci-static/resources/view/homeproxy/client.js:849 +#: htdocs/luci-static/resources/view/homeproxy/node.js:389 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:184 +#: htdocs/luci-static/resources/view/homeproxy/server.js:94 msgid "Label" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:680 -#: htdocs/luci-static/resources/view/homeproxy/server.js:373 +#: htdocs/luci-static/resources/view/homeproxy/node.js:677 +#: htdocs/luci-static/resources/view/homeproxy/server.js:312 msgid "" "Legacy protocol support (VMess MD5 Authentication) is provided for " "compatibility purposes only, use of alterId > 1 is not recommended." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:303 +#: htdocs/luci-static/resources/view/homeproxy/client.js:362 msgid "Less compatibility and sometimes better performance." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:613 +#: htdocs/luci-static/resources/view/homeproxy/server.js:552 msgid "Let's Encrypt" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:842 +#: htdocs/luci-static/resources/view/homeproxy/node.js:922 msgid "" "List of IP (v4 or v6) addresses prefixes to be assigned to the interface." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:978 -#: htdocs/luci-static/resources/view/homeproxy/server.js:549 +#: htdocs/luci-static/resources/view/homeproxy/node.js:720 +msgid "List of outbound tags." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:709 +msgid "List of subscription groups." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1058 +#: htdocs/luci-static/resources/view/homeproxy/server.js:488 msgid "List of supported application level protocols, in order of preference." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:160 +#: htdocs/luci-static/resources/view/homeproxy/server.js:121 msgid "Listen address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1076 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1145 msgid "Listen interfaces" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:165 +#: htdocs/luci-static/resources/view/homeproxy/server.js:126 msgid "Listen port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:127 +#: htdocs/luci-static/resources/view/homeproxy/status.js:126 msgid "Loading" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1004 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:195 msgid "Local" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:841 +#: htdocs/luci-static/resources/view/homeproxy/node.js:921 msgid "Local address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:144 +#: htdocs/luci-static/resources/view/homeproxy/status.js:143 msgid "Log file does not exist." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:137 +#: htdocs/luci-static/resources/view/homeproxy/status.js:136 msgid "Log is empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:875 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1078 +msgid "Log level" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:237 +msgid "Lowercase only" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:955 msgid "MTU" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:149 +#: htdocs/luci-static/resources/view/homeproxy/client.js:205 msgid "Main UDP node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:141 +#: htdocs/luci-static/resources/view/homeproxy/client.js:197 msgid "Main node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:916 +#: htdocs/luci-static/resources/view/homeproxy/client.js:989 msgid "Make ipcidr in rule sets match the source IP." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:582 +#: htdocs/luci-static/resources/view/homeproxy/client.js:644 msgid "Make IP CIDR in rule set used to match the source IP." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:648 +#: htdocs/luci-static/resources/view/homeproxy/client.js:709 msgid "" "Make each DNS server's cache independent for special purposes. If enabled, " "will slightly degrade performance." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:301 +#: htdocs/luci-static/resources/view/homeproxy/server.js:251 msgid "Masquerade" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:926 +#: htdocs/luci-static/resources/view/homeproxy/client.js:999 msgid "Match .outbounds[].server domains." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:868 +#: htdocs/luci-static/resources/view/homeproxy/client.js:933 msgid "Match IP CIDR with query response." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:523 +#: htdocs/luci-static/resources/view/homeproxy/client.js:578 msgid "Match IP CIDR." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:500 -#: htdocs/luci-static/resources/view/homeproxy/client.js:836 +#: htdocs/luci-static/resources/view/homeproxy/client.js:620 +#: htdocs/luci-static/resources/view/homeproxy/client.js:965 +msgid "Match clash mode." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:556 +#: htdocs/luci-static/resources/view/homeproxy/client.js:901 msgid "Match domain suffix." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:504 -#: htdocs/luci-static/resources/view/homeproxy/client.js:840 +#: htdocs/luci-static/resources/view/homeproxy/client.js:560 +#: htdocs/luci-static/resources/view/homeproxy/client.js:905 msgid "Match domain using keyword." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:508 -#: htdocs/luci-static/resources/view/homeproxy/client.js:844 +#: htdocs/luci-static/resources/view/homeproxy/client.js:564 +#: htdocs/luci-static/resources/view/homeproxy/client.js:909 msgid "Match domain using regular expression." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:495 -#: htdocs/luci-static/resources/view/homeproxy/client.js:831 +#: htdocs/luci-static/resources/view/homeproxy/client.js:551 +#: htdocs/luci-static/resources/view/homeproxy/client.js:896 msgid "Match full domain." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:549 -#: htdocs/luci-static/resources/view/homeproxy/client.js:853 +#: htdocs/luci-static/resources/view/homeproxy/client.js:603 +#: htdocs/luci-static/resources/view/homeproxy/client.js:918 msgid "Match port range. Format as START:/:END/START:END." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:544 -#: htdocs/luci-static/resources/view/homeproxy/client.js:848 +#: htdocs/luci-static/resources/view/homeproxy/client.js:598 +#: htdocs/luci-static/resources/view/homeproxy/client.js:913 msgid "Match port." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:873 +#: htdocs/luci-static/resources/view/homeproxy/client.js:938 msgid "Match private IP with query response." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:528 +#: htdocs/luci-static/resources/view/homeproxy/client.js:583 msgid "Match private IP." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:517 -#: htdocs/luci-static/resources/view/homeproxy/client.js:863 +#: htdocs/luci-static/resources/view/homeproxy/client.js:573 +#: htdocs/luci-static/resources/view/homeproxy/client.js:928 msgid "Match private source IP." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:554 -#: htdocs/luci-static/resources/view/homeproxy/client.js:888 +#: htdocs/luci-static/resources/view/homeproxy/client.js:608 +#: htdocs/luci-static/resources/view/homeproxy/client.js:953 msgid "Match process name." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:558 -#: htdocs/luci-static/resources/view/homeproxy/client.js:892 +#: htdocs/luci-static/resources/view/homeproxy/client.js:612 +#: htdocs/luci-static/resources/view/homeproxy/client.js:957 msgid "Match process path." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:814 +#: htdocs/luci-static/resources/view/homeproxy/client.js:879 msgid "Match query type." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:566 -#: htdocs/luci-static/resources/view/homeproxy/client.js:900 +#: htdocs/luci-static/resources/view/homeproxy/client.js:628 +#: htdocs/luci-static/resources/view/homeproxy/client.js:973 msgid "Match rule set." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:512 -#: htdocs/luci-static/resources/view/homeproxy/client.js:858 +#: htdocs/luci-static/resources/view/homeproxy/client.js:568 +#: htdocs/luci-static/resources/view/homeproxy/client.js:923 msgid "Match source IP CIDR." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:581 +#: htdocs/luci-static/resources/view/homeproxy/client.js:643 msgid "Match source IP via rule set" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:539 -#: htdocs/luci-static/resources/view/homeproxy/client.js:883 +#: htdocs/luci-static/resources/view/homeproxy/client.js:593 +#: htdocs/luci-static/resources/view/homeproxy/client.js:948 msgid "Match source port range. Format as START:/:END/START:END." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:534 -#: htdocs/luci-static/resources/view/homeproxy/client.js:878 +#: htdocs/luci-static/resources/view/homeproxy/client.js:588 +#: htdocs/luci-static/resources/view/homeproxy/client.js:943 msgid "Match source port." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:562 -#: htdocs/luci-static/resources/view/homeproxy/client.js:896 +#: htdocs/luci-static/resources/view/homeproxy/client.js:616 +#: htdocs/luci-static/resources/view/homeproxy/client.js:961 msgid "Match user name." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:514 -#: htdocs/luci-static/resources/view/homeproxy/server.js:231 +#: htdocs/luci-static/resources/view/homeproxy/node.js:511 +#: htdocs/luci-static/resources/view/homeproxy/server.js:181 msgid "Max download speed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:515 -#: htdocs/luci-static/resources/view/homeproxy/server.js:232 +#: htdocs/luci-static/resources/view/homeproxy/node.js:512 +#: htdocs/luci-static/resources/view/homeproxy/server.js:182 msgid "Max download speed in Mbps." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:709 +#: htdocs/luci-static/resources/view/homeproxy/server.js:648 msgid "Max time difference" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:521 -#: htdocs/luci-static/resources/view/homeproxy/server.js:238 +#: htdocs/luci-static/resources/view/homeproxy/node.js:518 +#: htdocs/luci-static/resources/view/homeproxy/server.js:188 msgid "Max upload speed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:522 -#: htdocs/luci-static/resources/view/homeproxy/server.js:239 +#: htdocs/luci-static/resources/view/homeproxy/node.js:519 +#: htdocs/luci-static/resources/view/homeproxy/server.js:189 msgid "Max upload speed in Mbps." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:999 -#: htdocs/luci-static/resources/view/homeproxy/server.js:561 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1079 +#: htdocs/luci-static/resources/view/homeproxy/server.js:500 msgid "Maximum TLS version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:902 +#: htdocs/luci-static/resources/view/homeproxy/node.js:982 msgid "Maximum connections" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:914 +#: htdocs/luci-static/resources/view/homeproxy/node.js:994 msgid "" "Maximum multiplexed streams in a connection before opening a new connection." "
Conflict with Maximum connections and Minimum " "streams." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:913 +#: htdocs/luci-static/resources/view/homeproxy/node.js:993 msgid "Maximum streams" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:780 -#: htdocs/luci-static/resources/view/homeproxy/server.js:433 +#: htdocs/luci-static/resources/view/homeproxy/node.js:860 +#: htdocs/luci-static/resources/view/homeproxy/server.js:372 msgid "Method" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:991 -#: htdocs/luci-static/resources/view/homeproxy/server.js:553 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1071 +#: htdocs/luci-static/resources/view/homeproxy/server.js:492 msgid "Minimum TLS version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:908 +#: htdocs/luci-static/resources/view/homeproxy/node.js:988 msgid "" "Minimum multiplexed streams in a connection before opening a new connection." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:907 +#: htdocs/luci-static/resources/view/homeproxy/node.js:987 msgid "Minimum streams" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:288 +#: htdocs/luci-static/resources/view/homeproxy/client.js:347 msgid "Mixed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:299 +#: htdocs/luci-static/resources/view/homeproxy/client.js:358 msgid "Mixed system TCP stack and gVisor UDP stack." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:462 -#: htdocs/luci-static/resources/view/homeproxy/client.js:794 +#: htdocs/luci-static/resources/view/homeproxy/client.js:518 +#: htdocs/luci-static/resources/view/homeproxy/client.js:859 msgid "Mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1114 -#: htdocs/luci-static/resources/view/homeproxy/server.js:771 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1195 +#: htdocs/luci-static/resources/view/homeproxy/server.js:710 msgid "MultiPath TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:884 -#: htdocs/luci-static/resources/view/homeproxy/server.js:481 +#: htdocs/luci-static/resources/view/homeproxy/node.js:964 +#: htdocs/luci-static/resources/view/homeproxy/server.js:420 msgid "Multiplex" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:893 +#: htdocs/luci-static/resources/view/homeproxy/node.js:973 msgid "Multiplex protocol." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 -#: htdocs/luci-static/resources/view/homeproxy/server.js:40 +#: htdocs/luci-static/resources/view/homeproxy/client.js:101 +#: htdocs/luci-static/resources/view/homeproxy/server.js:39 msgid "NOT RUNNING" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1348 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1460 msgid "NOTE: Save current settings before updating subscriptions." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:646 +#: htdocs/luci-static/resources/view/homeproxy/node.js:643 msgid "Native" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:149 +#: htdocs/luci-static/resources/view/homeproxy/server.js:110 msgid "NaïveProxy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:489 -#: htdocs/luci-static/resources/view/homeproxy/client.js:817 -#: htdocs/luci-static/resources/view/homeproxy/server.js:799 +#: htdocs/luci-static/resources/view/homeproxy/client.js:545 +#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/server.js:731 msgid "Network" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:636 +#: htdocs/luci-static/resources/view/homeproxy/node.js:633 msgid "New Reno" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:712 -#: htdocs/luci-static/resources/view/homeproxy/node.js:729 -#: htdocs/luci-static/resources/view/homeproxy/server.js:381 -#: htdocs/luci-static/resources/view/homeproxy/server.js:398 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1068 +msgid "Nginx Support" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:792 +#: htdocs/luci-static/resources/view/homeproxy/node.js:809 +#: htdocs/luci-static/resources/view/homeproxy/server.js:320 +#: htdocs/luci-static/resources/view/homeproxy/server.js:337 msgid "No TCP transport, plain HTTP is merged into the HTTP transport." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:727 -#: htdocs/luci-static/resources/view/homeproxy/server.js:396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:807 +#: htdocs/luci-static/resources/view/homeproxy/server.js:335 msgid "No additional encryption support: It's basically duplicate encryption." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1364 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1476 msgid "No subscription available" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1389 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1501 msgid "No subscription node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1224 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:130 +msgid "No valid rule-set link found." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1301 msgid "No valid share link found." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:380 -#: htdocs/luci-static/resources/view/homeproxy/node.js:363 +#: htdocs/luci-static/resources/view/homeproxy/client.js:434 +#: htdocs/luci-static/resources/view/homeproxy/node.js:364 msgid "Node" msgstr "" @@ -1372,347 +1510,367 @@ msgstr "" msgid "Node Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1170 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1250 msgid "Nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:703 -#: htdocs/luci-static/resources/view/homeproxy/node.js:674 -#: htdocs/luci-static/resources/view/homeproxy/node.js:713 -#: htdocs/luci-static/resources/view/homeproxy/server.js:367 -#: htdocs/luci-static/resources/view/homeproxy/server.js:382 +#: htdocs/luci-static/resources/view/homeproxy/client.js:621 +#: htdocs/luci-static/resources/view/homeproxy/client.js:766 +#: htdocs/luci-static/resources/view/homeproxy/client.js:966 +#: htdocs/luci-static/resources/view/homeproxy/node.js:671 +#: htdocs/luci-static/resources/view/homeproxy/node.js:793 +#: htdocs/luci-static/resources/view/homeproxy/server.js:306 +#: htdocs/luci-static/resources/view/homeproxy/server.js:321 msgid "None" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:509 -#: htdocs/luci-static/resources/view/homeproxy/server.js:263 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1104 +msgid "Not Installed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:506 +#: htdocs/luci-static/resources/view/homeproxy/server.js:213 msgid "Obfuscate password" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:503 -#: htdocs/luci-static/resources/view/homeproxy/server.js:257 +#: htdocs/luci-static/resources/view/homeproxy/node.js:500 +#: htdocs/luci-static/resources/view/homeproxy/server.js:207 msgid "Obfuscate type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1077 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1146 msgid "Only process traffic from specific interfaces. Leave empty for all." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:225 +#: htdocs/luci-static/resources/view/homeproxy/client.js:281 msgid "Only proxy mainland China" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:446 -#: htdocs/luci-static/resources/view/homeproxy/client.js:778 +#: htdocs/luci-static/resources/view/homeproxy/client.js:97 +msgid "Open Clash Dashboard" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:502 +#: htdocs/luci-static/resources/view/homeproxy/client.js:843 msgid "Other fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:400 -#: htdocs/luci-static/resources/view/homeproxy/client.js:592 -#: htdocs/luci-static/resources/view/homeproxy/client.js:740 -#: htdocs/luci-static/resources/view/homeproxy/client.js:925 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1044 +#: htdocs/luci-static/resources/view/homeproxy/client.js:454 +#: htdocs/luci-static/resources/view/homeproxy/client.js:653 +#: htdocs/luci-static/resources/view/homeproxy/client.js:803 +#: htdocs/luci-static/resources/view/homeproxy/client.js:998 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:235 msgid "Outbound" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:381 +#: htdocs/luci-static/resources/view/homeproxy/client.js:435 msgid "Outbound node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:461 +#: htdocs/luci-static/resources/view/homeproxy/node.js:719 +msgid "Outbounds" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:464 msgid "Override address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:332 -#: htdocs/luci-static/resources/view/homeproxy/server.js:789 +#: htdocs/luci-static/resources/view/homeproxy/client.js:377 +#: htdocs/luci-static/resources/view/homeproxy/server.js:721 msgid "Override destination" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:466 +#: htdocs/luci-static/resources/view/homeproxy/node.js:470 msgid "Override port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:333 -#: htdocs/luci-static/resources/view/homeproxy/server.js:790 +#: htdocs/luci-static/resources/view/homeproxy/client.js:378 +#: htdocs/luci-static/resources/view/homeproxy/server.js:722 msgid "Override the connection destination address with the sniffed domain." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:462 +#: htdocs/luci-static/resources/view/homeproxy/node.js:465 msgid "Override the connection destination address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:467 +#: htdocs/luci-static/resources/view/homeproxy/node.js:471 msgid "Override the connection destination port." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:782 +#: htdocs/luci-static/resources/view/homeproxy/node.js:862 msgid "PUT" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:825 +#: htdocs/luci-static/resources/view/homeproxy/node.js:905 msgid "Packet encoding" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:429 -#: htdocs/luci-static/resources/view/homeproxy/server.js:176 +#: htdocs/luci-static/resources/view/homeproxy/node.js:432 +#: htdocs/luci-static/resources/view/homeproxy/server.js:137 msgid "Password" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1015 -#: htdocs/luci-static/resources/view/homeproxy/node.js:775 -#: htdocs/luci-static/resources/view/homeproxy/node.js:808 -#: htdocs/luci-static/resources/view/homeproxy/server.js:428 -#: htdocs/luci-static/resources/view/homeproxy/server.js:458 +#: htdocs/luci-static/resources/view/homeproxy/node.js:855 +#: htdocs/luci-static/resources/view/homeproxy/node.js:888 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:206 +#: htdocs/luci-static/resources/view/homeproxy/server.js:367 +#: htdocs/luci-static/resources/view/homeproxy/server.js:397 msgid "Path" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:856 +#: htdocs/luci-static/resources/view/homeproxy/node.js:936 msgid "Peer pubkic key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:307 +#: htdocs/luci-static/resources/view/homeproxy/client.js:366 msgid "" "Performance may degrade slightly, so it is not recommended to enable on when " "it is not needed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:794 -#: htdocs/luci-static/resources/view/homeproxy/server.js:445 +#: htdocs/luci-static/resources/view/homeproxy/node.js:874 +#: htdocs/luci-static/resources/view/homeproxy/server.js:384 msgid "Ping timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:566 +#: htdocs/luci-static/resources/view/homeproxy/node.js:563 msgid "Plugin" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:573 +#: htdocs/luci-static/resources/view/homeproxy/node.js:570 msgid "Plugin opts" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:543 -#: htdocs/luci-static/resources/view/homeproxy/client.js:847 -#: htdocs/luci-static/resources/view/homeproxy/node.js:418 +#: htdocs/luci-static/resources/view/homeproxy/client.js:597 +#: htdocs/luci-static/resources/view/homeproxy/client.js:912 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1125 +#: htdocs/luci-static/resources/view/homeproxy/node.js:421 msgid "Port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:248 +#: htdocs/luci-static/resources/view/homeproxy/client.js:307 msgid "Port %s alrealy exists!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:448 -#: htdocs/luci-static/resources/view/homeproxy/client.js:780 +#: htdocs/luci-static/resources/view/homeproxy/client.js:504 +#: htdocs/luci-static/resources/view/homeproxy/client.js:845 msgid "Port fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:548 -#: htdocs/luci-static/resources/view/homeproxy/client.js:852 +#: htdocs/luci-static/resources/view/homeproxy/client.js:602 +#: htdocs/luci-static/resources/view/homeproxy/client.js:917 msgid "Port range" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:863 +#: htdocs/luci-static/resources/view/homeproxy/node.js:943 msgid "Pre-shared key" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:18 +#: htdocs/luci-static/resources/homeproxy.js:19 msgid "Prefer IPv4" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:19 +#: htdocs/luci-static/resources/homeproxy.js:20 msgid "Prefer IPv6" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:527 -#: htdocs/luci-static/resources/view/homeproxy/client.js:872 +#: htdocs/luci-static/resources/view/homeproxy/client.js:582 +#: htdocs/luci-static/resources/view/homeproxy/client.js:937 msgid "Private IP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:614 -#: htdocs/luci-static/resources/view/homeproxy/node.js:848 +#: htdocs/luci-static/resources/view/homeproxy/node.js:611 +#: htdocs/luci-static/resources/view/homeproxy/node.js:928 msgid "Private key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:619 +#: htdocs/luci-static/resources/view/homeproxy/node.js:616 msgid "Private key passphrase" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:516 -#: htdocs/luci-static/resources/view/homeproxy/client.js:862 +#: htdocs/luci-static/resources/view/homeproxy/client.js:572 +#: htdocs/luci-static/resources/view/homeproxy/client.js:927 msgid "Private source IP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:553 -#: htdocs/luci-static/resources/view/homeproxy/client.js:887 +#: htdocs/luci-static/resources/view/homeproxy/client.js:607 +#: htdocs/luci-static/resources/view/homeproxy/client.js:952 msgid "Process name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:557 -#: htdocs/luci-static/resources/view/homeproxy/client.js:891 +#: htdocs/luci-static/resources/view/homeproxy/client.js:611 +#: htdocs/luci-static/resources/view/homeproxy/client.js:956 msgid "Process path" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:482 -#: htdocs/luci-static/resources/view/homeproxy/client.js:822 -#: htdocs/luci-static/resources/view/homeproxy/node.js:480 -#: htdocs/luci-static/resources/view/homeproxy/node.js:892 -#: htdocs/luci-static/resources/view/homeproxy/server.js:220 +#: htdocs/luci-static/resources/view/homeproxy/client.js:538 +#: htdocs/luci-static/resources/view/homeproxy/client.js:887 +#: htdocs/luci-static/resources/view/homeproxy/node.js:477 +#: htdocs/luci-static/resources/view/homeproxy/node.js:972 +#: htdocs/luci-static/resources/view/homeproxy/server.js:170 msgid "Protocol" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:704 +#: htdocs/luci-static/resources/view/homeproxy/node.js:701 msgid "Protocol parameter. Enable length block encryption." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:697 +#: htdocs/luci-static/resources/view/homeproxy/node.js:694 msgid "" "Protocol parameter. Will waste traffic randomly if enabled (enabled by " "default in v2ray and cannot be disabled)." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1151 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1220 msgid "Proxy Domain List" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1106 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1135 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1175 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1204 msgid "Proxy IPv4 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1109 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1138 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1178 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1207 msgid "Proxy IPv6 IP-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1112 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1181 msgid "Proxy MAC-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1093 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1162 msgid "Proxy all except listed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1159 msgid "Proxy filter mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1092 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1161 msgid "Proxy listed only" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:256 +#: htdocs/luci-static/resources/view/homeproxy/client.js:315 msgid "Proxy mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:471 -msgid "Proxy protocol" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:486 -#: htdocs/luci-static/resources/view/homeproxy/client.js:826 -#: htdocs/luci-static/resources/view/homeproxy/node.js:647 -#: htdocs/luci-static/resources/view/homeproxy/node.js:717 -#: htdocs/luci-static/resources/view/homeproxy/server.js:386 +#: htdocs/luci-static/resources/view/homeproxy/client.js:542 +#: htdocs/luci-static/resources/view/homeproxy/client.js:891 +#: htdocs/luci-static/resources/view/homeproxy/node.js:644 +#: htdocs/luci-static/resources/view/homeproxy/node.js:797 +#: htdocs/luci-static/resources/view/homeproxy/server.js:325 msgid "QUIC" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:634 -#: htdocs/luci-static/resources/view/homeproxy/server.js:335 +#: htdocs/luci-static/resources/view/homeproxy/node.js:631 +#: htdocs/luci-static/resources/view/homeproxy/server.js:274 msgid "QUIC congestion control algorithm." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:534 -#: htdocs/luci-static/resources/view/homeproxy/server.js:275 +#: htdocs/luci-static/resources/view/homeproxy/node.js:531 +#: htdocs/luci-static/resources/view/homeproxy/server.js:225 msgid "QUIC connection receive window" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:282 +#: htdocs/luci-static/resources/view/homeproxy/server.js:232 msgid "QUIC maximum concurrent bidirectional streams" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:528 -#: htdocs/luci-static/resources/view/homeproxy/server.js:268 +#: htdocs/luci-static/resources/view/homeproxy/node.js:525 +#: htdocs/luci-static/resources/view/homeproxy/server.js:218 msgid "QUIC stream receive window" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:813 +#: htdocs/luci-static/resources/view/homeproxy/client.js:878 msgid "Query type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:663 +#: htdocs/luci-static/resources/view/homeproxy/client.js:400 +msgid "Quick Reload" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:724 msgid "RDRC timeout" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1093 -#: htdocs/luci-static/resources/view/homeproxy/server.js:693 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1173 +#: htdocs/luci-static/resources/view/homeproxy/server.js:632 msgid "REALITY" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:699 +#: htdocs/luci-static/resources/view/homeproxy/server.js:638 msgid "REALITY private key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1098 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1178 msgid "REALITY public key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1103 -#: htdocs/luci-static/resources/view/homeproxy/server.js:704 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1183 +#: htdocs/luci-static/resources/view/homeproxy/server.js:643 msgid "REALITY short ID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 -#: htdocs/luci-static/resources/view/homeproxy/server.js:38 +#: htdocs/luci-static/resources/view/homeproxy/client.js:99 +#: htdocs/luci-static/resources/view/homeproxy/server.js:37 msgid "RUNNING" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:601 +#: htdocs/luci-static/resources/view/homeproxy/node.js:598 msgid "Random version will be used if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:425 +#: htdocs/luci-static/resources/view/homeproxy/client.js:479 msgid "Recursive outbound detected!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:722 +#: htdocs/luci-static/resources/view/homeproxy/client.js:785 msgid "Recursive resolver detected!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:257 +#: htdocs/luci-static/resources/view/homeproxy/client.js:316 msgid "Redirect TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:259 +#: htdocs/luci-static/resources/view/homeproxy/client.js:318 msgid "Redirect TCP + TProxy UDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:261 +#: htdocs/luci-static/resources/view/homeproxy/client.js:320 msgid "Redirect TCP + Tun UDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:171 +#: htdocs/luci-static/resources/view/homeproxy/status.js:170 msgid "Refresh every %s seconds." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:642 +#: htdocs/luci-static/resources/view/homeproxy/server.js:581 msgid "Region ID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1005 +#: htdocs/luci-static/resources/view/homeproxy/client.js:401 +msgid "Reload" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:196 msgid "Remote" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1386 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1498 msgid "Remove %s nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1376 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1488 msgid "Remove all nodes from subscriptions" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:870 +#: htdocs/luci-static/resources/view/homeproxy/node.js:950 msgid "Reserved field bytes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:735 +#: htdocs/luci-static/resources/view/homeproxy/client.js:798 msgid "Resolve strategy" msgstr "" @@ -1720,182 +1878,203 @@ msgstr "" msgid "Resources management" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:967 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1047 msgid "Rewrite TTL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:968 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1048 msgid "Rewrite TTL in DNS responses." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:357 +#: htdocs/luci-static/resources/view/homeproxy/client.js:410 msgid "Routing Nodes" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:433 +#: htdocs/luci-static/resources/view/homeproxy/client.js:487 msgid "Routing Rules" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:139 +#: htdocs/luci-static/resources/view/homeproxy/client.js:195 msgid "Routing Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:222 +#: htdocs/luci-static/resources/view/homeproxy/client.js:278 msgid "Routing mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:366 +#: htdocs/luci-static/resources/view/homeproxy/client.js:419 msgid "Routing node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:235 +#: htdocs/luci-static/resources/view/homeproxy/client.js:291 msgid "Routing ports" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:442 +#: htdocs/luci-static/resources/view/homeproxy/client.js:497 msgid "Routing rule" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:565 -#: htdocs/luci-static/resources/view/homeproxy/client.js:899 -#: htdocs/luci-static/resources/view/homeproxy/client.js:980 -#: htdocs/luci-static/resources/view/homeproxy/client.js:989 +#: htdocs/luci-static/resources/view/homeproxy/client.js:623 +#: htdocs/luci-static/resources/view/homeproxy/client.js:968 +msgid "Rule" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:627 +#: htdocs/luci-static/resources/view/homeproxy/client.js:972 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:91 msgid "Rule set" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:915 +#: htdocs/luci-static/resources/view/homeproxy/client.js:988 msgid "Rule set IP CIDR as source IP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1022 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:213 msgid "Rule set URL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:449 -#: htdocs/luci-static/resources/view/homeproxy/client.js:781 +#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:30 +msgid "Ruleset Settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:505 +#: htdocs/luci-static/resources/view/homeproxy/client.js:846 msgid "SRC-IP fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:450 -#: htdocs/luci-static/resources/view/homeproxy/client.js:782 +#: htdocs/luci-static/resources/view/homeproxy/client.js:506 +#: htdocs/luci-static/resources/view/homeproxy/client.js:847 msgid "SRC-Port fields" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:403 +#: htdocs/luci-static/resources/view/homeproxy/node.js:404 msgid "SSH" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:487 -#: htdocs/luci-static/resources/view/homeproxy/client.js:828 +#: htdocs/luci-static/resources/view/homeproxy/client.js:543 +#: htdocs/luci-static/resources/view/homeproxy/client.js:893 msgid "STUN" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1130 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1213 msgid "SUoT version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:505 -#: htdocs/luci-static/resources/view/homeproxy/server.js:259 +#: htdocs/luci-static/resources/view/homeproxy/node.js:502 +#: htdocs/luci-static/resources/view/homeproxy/server.js:209 msgid "Salamander" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:151 +#: htdocs/luci-static/resources/view/homeproxy/client.js:207 msgid "Same as main node" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1462 msgid "Save current settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1347 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1459 msgid "Save subscriptions settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:943 -#: htdocs/luci-static/resources/view/homeproxy/server.js:129 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1130 +msgid "Secret" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1088 +msgid "Select Clash Dashboard" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:412 +msgid "Selector" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1023 +#: htdocs/luci-static/resources/view/homeproxy/server.js:89 msgid "Server" msgstr "" -#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:30 +#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:38 msgid "Server Settings" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:591 +#: htdocs/luci-static/resources/view/homeproxy/server.js:530 msgid "" "Server name to use when choosing a certificate if the ClientHello's " "ServerName field is empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:124 +#: htdocs/luci-static/resources/view/homeproxy/server.js:83 msgid "Server settings" msgstr "" -#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:38 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:46 msgid "Service Status" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:401 +#: htdocs/luci-static/resources/view/homeproxy/node.js:402 msgid "ShadowTLS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:580 +#: htdocs/luci-static/resources/view/homeproxy/node.js:577 msgid "ShadowTLS version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:400 -#: htdocs/luci-static/resources/view/homeproxy/server.js:151 +#: htdocs/luci-static/resources/view/homeproxy/node.js:401 +#: htdocs/luci-static/resources/view/homeproxy/server.js:112 msgid "Shadowsocks" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:483 -#: htdocs/luci-static/resources/view/homeproxy/client.js:823 +#: htdocs/luci-static/resources/view/homeproxy/client.js:539 +#: htdocs/luci-static/resources/view/homeproxy/client.js:888 msgid "" "Sniffed protocol, see Sniff for details." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:402 -#: htdocs/luci-static/resources/view/homeproxy/server.js:152 +#: htdocs/luci-static/resources/view/homeproxy/node.js:403 +#: htdocs/luci-static/resources/view/homeproxy/server.js:113 msgid "Socks" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:590 +#: htdocs/luci-static/resources/view/homeproxy/node.js:587 msgid "Socks version" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:591 +#: htdocs/luci-static/resources/view/homeproxy/node.js:588 msgid "Socks4" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:592 +#: htdocs/luci-static/resources/view/homeproxy/node.js:589 msgid "Socks4A" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:593 +#: htdocs/luci-static/resources/view/homeproxy/node.js:590 msgid "Socks5" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:511 -#: htdocs/luci-static/resources/view/homeproxy/client.js:857 +#: htdocs/luci-static/resources/view/homeproxy/client.js:567 +#: htdocs/luci-static/resources/view/homeproxy/client.js:922 msgid "Source IP CIDR" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1010 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:201 msgid "Source file" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:533 -#: htdocs/luci-static/resources/view/homeproxy/client.js:877 +#: htdocs/luci-static/resources/view/homeproxy/client.js:587 +#: htdocs/luci-static/resources/view/homeproxy/client.js:942 msgid "Source port" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:538 -#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/client.js:592 +#: htdocs/luci-static/resources/view/homeproxy/client.js:947 msgid "Source port range" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:734 -#: htdocs/luci-static/resources/view/homeproxy/node.js:787 +#: htdocs/luci-static/resources/view/homeproxy/node.js:814 +#: htdocs/luci-static/resources/view/homeproxy/node.js:867 msgid "" "Specifies the period of time (in seconds) after which a health check will be " "performed using a ping frame if no frames have been received on the " @@ -1904,15 +2083,15 @@ msgid "" "will be executed every interval." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:403 -#: htdocs/luci-static/resources/view/homeproxy/server.js:438 +#: htdocs/luci-static/resources/view/homeproxy/server.js:342 +#: htdocs/luci-static/resources/view/homeproxy/server.js:377 msgid "" "Specifies the time (in seconds) until idle clients should be closed with a " "GOAWAY frame. PING frames are not considered as activity." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:738 -#: htdocs/luci-static/resources/view/homeproxy/node.js:795 +#: htdocs/luci-static/resources/view/homeproxy/node.js:818 +#: htdocs/luci-static/resources/view/homeproxy/node.js:875 msgid "" "Specifies the timeout duration (in seconds) after sending a PING frame, " "within which a response must be received.
If a response to the PING " @@ -1920,172 +2099,209 @@ msgid "" "will be closed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:236 +#: htdocs/luci-static/resources/view/homeproxy/client.js:292 msgid "" "Specify target ports to be proxied. Multiple ports must be separated by " "commas." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:657 +#: htdocs/luci-static/resources/view/homeproxy/client.js:718 msgid "Store RDRC" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:658 +#: htdocs/luci-static/resources/view/homeproxy/client.js:719 msgid "" "Store rejected DNS response cache.
The check results of Address " "filter DNS rule items will be cached until expiration." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:494 -#: htdocs/luci-static/resources/view/homeproxy/server.js:248 +#: htdocs/luci-static/resources/view/homeproxy/node.js:491 +#: htdocs/luci-static/resources/view/homeproxy/server.js:198 msgid "String" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1275 +#: htdocs/luci-static/resources/view/homeproxy/node.js:713 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1384 msgid "Sub (%s)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1304 +#: htdocs/luci-static/resources/view/homeproxy/node.js:708 +msgid "Subscription Groups" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1416 msgid "Subscription URL-s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1286 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1396 msgid "Subscriptions" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1226 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1303 msgid "Successfully imported %s nodes of total %s." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:86 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:132 +msgid "Successfully imported %s rule-set of total %s." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/status.js:85 msgid "Successfully updated." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1186 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1305 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1263 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1417 msgid "" "Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) " "online configuration delivery standard." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:291 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:99 +msgid "" +"Supports rule-set links of type: local, remote and format: " +"source, binary." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:350 msgid "System" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:628 -#: htdocs/luci-static/resources/view/homeproxy/client.js:705 -#: htdocs/luci-static/resources/view/homeproxy/client.js:950 +#: htdocs/luci-static/resources/view/homeproxy/client.js:689 +#: htdocs/luci-static/resources/view/homeproxy/client.js:768 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1030 msgid "System DNS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:490 -#: htdocs/luci-static/resources/view/homeproxy/client.js:818 -#: htdocs/luci-static/resources/view/homeproxy/server.js:800 +#: htdocs/luci-static/resources/view/homeproxy/client.js:546 +#: htdocs/luci-static/resources/view/homeproxy/client.js:883 +#: htdocs/luci-static/resources/view/homeproxy/server.js:732 msgid "TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1110 -#: htdocs/luci-static/resources/view/homeproxy/server.js:765 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1190 +#: htdocs/luci-static/resources/view/homeproxy/server.js:704 msgid "TCP fast open" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:285 +#: htdocs/luci-static/resources/view/homeproxy/client.js:344 msgid "TCP/IP stack" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:286 +#: htdocs/luci-static/resources/view/homeproxy/client.js:345 msgid "TCP/IP stack." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:485 -#: htdocs/luci-static/resources/view/homeproxy/client.js:825 -#: htdocs/luci-static/resources/view/homeproxy/node.js:945 -#: htdocs/luci-static/resources/view/homeproxy/server.js:516 +#: htdocs/luci-static/resources/view/homeproxy/client.js:541 +#: htdocs/luci-static/resources/view/homeproxy/client.js:890 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1025 +#: htdocs/luci-static/resources/view/homeproxy/server.js:455 msgid "TLS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:977 -#: htdocs/luci-static/resources/view/homeproxy/server.js:548 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1057 +#: htdocs/luci-static/resources/view/homeproxy/server.js:487 msgid "TLS ALPN" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:972 -#: htdocs/luci-static/resources/view/homeproxy/server.js:543 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1052 +#: htdocs/luci-static/resources/view/homeproxy/server.js:482 msgid "TLS SNI" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:725 -#: htdocs/luci-static/resources/view/homeproxy/server.js:394 +#: htdocs/luci-static/resources/view/homeproxy/node.js:805 +#: htdocs/luci-static/resources/view/homeproxy/server.js:333 msgid "TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:698 +#: htdocs/luci-static/resources/view/homeproxy/client.js:761 msgid "" "Tag of a another server to resolve the domain name in the address. Required " "if address contains domain." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:741 +#: htdocs/luci-static/resources/view/homeproxy/client.js:804 msgid "Tag of an outbound for connecting to the dns server." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1045 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:236 msgid "Tag of the outbound to download rule set." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:944 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1024 msgid "Tag of the target dns server." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:593 +#: htdocs/luci-static/resources/view/homeproxy/client.js:654 msgid "Tag of the target outbound." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:296 +#: htdocs/luci-static/resources/view/homeproxy/server.js:246 msgid "" "Tell the client to use the BBR flow control algorithm instead of Hysteria CC." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:166 -#: htdocs/luci-static/resources/view/homeproxy/client.js:189 +#: htdocs/luci-static/resources/view/homeproxy/client.js:222 +#: htdocs/luci-static/resources/view/homeproxy/client.js:245 msgid "Tencent Public DNS (119.29.29.29)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:612 +#: htdocs/luci-static/resources/view/homeproxy/node.js:756 +msgid "Test URL" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/server.js:551 msgid "The ACME CA provider to use." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:618 +#: htdocs/luci-static/resources/view/homeproxy/client.js:679 msgid "The DNS strategy for resolving the domain name in the address." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:535 -#: htdocs/luci-static/resources/view/homeproxy/server.js:276 +#: htdocs/luci-static/resources/view/homeproxy/node.js:532 +#: htdocs/luci-static/resources/view/homeproxy/server.js:226 msgid "The QUIC connection-level flow control window for receiving data." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:529 -#: htdocs/luci-static/resources/view/homeproxy/server.js:269 +#: htdocs/luci-static/resources/view/homeproxy/node.js:526 +#: htdocs/luci-static/resources/view/homeproxy/server.js:219 msgid "The QUIC stream-level flow control window for receiving data." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:694 +#: htdocs/luci-static/resources/view/homeproxy/node.js:757 +msgid "" +"The URL to test. https://www.gstatic.com/generate_204 will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:757 msgid "The address of the dns server. Support UDP, TCP, DoT, DoH and RCode." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:663 +#: htdocs/luci-static/resources/view/homeproxy/server.js:602 msgid "" "The alternate port to use for the ACME HTTP challenge; if non-empty, this " "port will be used instead of 80 to spin up a listener for the HTTP challenge." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:669 +#: htdocs/luci-static/resources/view/homeproxy/server.js:608 msgid "" "The alternate port to use for the ACME TLS-ALPN challenge; the system must " "forward 443 to this port for challenge to succeed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:463 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1113 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1116 +msgid "The current API URL is %s" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1133 +msgid "The current Secret is " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:729 +msgid "The default outbound tag. The first outbound will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:519 msgid "" "The default rule uses the following matching logic:
(domain || " "domain_suffix || domain_keyword || domain_regex || ip_cidr || " @@ -2096,7 +2312,7 @@ msgid "" "than as a single rule sub-item." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:795 +#: htdocs/luci-static/resources/view/homeproxy/client.js:860 msgid "" "The default rule uses the following matching logic:
(domain || " "domain_suffix || domain_keyword || domain_regex) &&
(port " @@ -2106,476 +2322,526 @@ msgid "" "considered merged rather than as a single rule sub-item." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:730 -msgid "The domain strategy for resolving the domain name in the address." +#: htdocs/luci-static/resources/view/homeproxy/node.js:1404 +msgid "The default value is 2:00 every day" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1008 -#: htdocs/luci-static/resources/view/homeproxy/server.js:570 +#: htdocs/luci-static/resources/view/homeproxy/client.js:793 +msgid "" +"The domain strategy for resolving the domain name in the address. dns." +"strategy will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1088 +#: htdocs/luci-static/resources/view/homeproxy/server.js:509 msgid "" "The elliptic curves that will be used in an ECDHE handshake, in preference " "order. If empty, the default will be used." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:597 +#: htdocs/luci-static/resources/view/homeproxy/server.js:536 msgid "" "The email address to use when creating or selecting an existing ACME server " "account." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1000 -#: htdocs/luci-static/resources/view/homeproxy/server.js:562 +#: htdocs/luci-static/resources/view/homeproxy/node.js:777 +msgid "The idle timeout. 30m will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1080 +#: htdocs/luci-static/resources/view/homeproxy/server.js:501 msgid "The maximum TLS version that is acceptable." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:283 +#: htdocs/luci-static/resources/view/homeproxy/server.js:233 msgid "" "The maximum number of QUIC concurrent bidirectional streams that a peer is " "allowed to open." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:710 +#: htdocs/luci-static/resources/view/homeproxy/server.js:649 msgid "The maximum time difference between the server and the client." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:992 -#: htdocs/luci-static/resources/view/homeproxy/server.js:554 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1072 +#: htdocs/luci-static/resources/view/homeproxy/server.js:493 msgid "The minimum TLS version that is acceptable." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:121 -#: htdocs/luci-static/resources/view/homeproxy/server.js:98 +#: htdocs/luci-static/resources/view/homeproxy/client.js:171 +#: htdocs/luci-static/resources/view/homeproxy/server.js:57 msgid "The modern ImmortalWrt proxy platform for ARM64/AMD64." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:394 +#: htdocs/luci-static/resources/view/homeproxy/client.js:448 msgid "The network interface to bind to." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1022 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1102 msgid "The path to the server certificate, in PEM format." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:166 +#: htdocs/luci-static/resources/view/homeproxy/server.js:127 msgid "The port must be unique." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:746 +#: htdocs/luci-static/resources/view/homeproxy/server.js:685 msgid "The server private key, in PEM format." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:728 +#: htdocs/luci-static/resources/view/homeproxy/server.js:667 msgid "The server public key, in PEM format." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:401 +#: htdocs/luci-static/resources/view/homeproxy/client.js:455 msgid "" "The tag of the upstream outbound.
Other dial fields will be ignored when " "enabled." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:745 -#: htdocs/luci-static/resources/view/homeproxy/server.js:446 +#: htdocs/luci-static/resources/view/homeproxy/node.js:764 +msgid "The test interval. 3m will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:771 +msgid "The test tolerance in milliseconds. 50 will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:825 +#: htdocs/luci-static/resources/view/homeproxy/server.js:385 msgid "" "The timeout (in seconds) that after performing a keepalive check, the client " "will wait for activity. If no activity is detected, the connection will be " "closed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:985 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1337 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1065 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1449 msgid "" "This is DANGEROUS, your traffic is almost like " "PLAIN TEXT! Use at your own risk!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:652 +#: htdocs/luci-static/resources/view/homeproxy/node.js:649 msgid "" "This is the TUIC port of the UDP over TCP protocol, designed to provide a " "QUIC stream based UDP relay mode that TUIC does not provide." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:167 -#: htdocs/luci-static/resources/view/homeproxy/client.js:190 -msgid "ThreatBook Public DNS (117.50.10.10)" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:664 +#: htdocs/luci-static/resources/view/homeproxy/client.js:725 msgid "" "Timeout of rejected DNS response cache. 7d is used by default." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:472 +#: htdocs/luci-static/resources/view/homeproxy/server.js:411 msgid "" "To be compatible with Xray-core, set this to Sec-WebSocket-Protocol." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:323 msgid "" "To enable Tun support, you need to install ip-full and " "kmod-tun" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:711 -#: htdocs/luci-static/resources/view/homeproxy/server.js:380 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1071 +msgid "" +"To enable this feature you need install luci-nginx and luci-ssl-" +"nginx
first" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:770 +msgid "Tolerance" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:791 +#: htdocs/luci-static/resources/view/homeproxy/server.js:319 msgid "Transport" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:404 -#: htdocs/luci-static/resources/view/homeproxy/server.js:153 +#: htdocs/luci-static/resources/view/homeproxy/node.js:405 +#: htdocs/luci-static/resources/view/homeproxy/server.js:114 msgid "Trojan" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:406 -#: htdocs/luci-static/resources/view/homeproxy/server.js:155 +#: htdocs/luci-static/resources/view/homeproxy/node.js:407 +#: htdocs/luci-static/resources/view/homeproxy/server.js:116 msgid "Tuic" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:262 +#: htdocs/luci-static/resources/view/homeproxy/client.js:321 msgid "Tun TCP/UDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1003 -#: htdocs/luci-static/resources/view/homeproxy/node.js:393 -#: htdocs/luci-static/resources/view/homeproxy/server.js:144 +#: htdocs/luci-static/resources/view/homeproxy/node.js:394 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:194 +#: htdocs/luci-static/resources/view/homeproxy/server.js:105 msgid "Type" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:491 -#: htdocs/luci-static/resources/view/homeproxy/client.js:819 -#: htdocs/luci-static/resources/view/homeproxy/server.js:801 +#: htdocs/luci-static/resources/view/homeproxy/client.js:547 +#: htdocs/luci-static/resources/view/homeproxy/client.js:884 +#: htdocs/luci-static/resources/view/homeproxy/server.js:733 msgid "UDP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1118 -#: htdocs/luci-static/resources/view/homeproxy/server.js:776 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1200 +#: htdocs/luci-static/resources/view/homeproxy/server.js:715 msgid "UDP Fragment" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:313 -#: htdocs/luci-static/resources/view/homeproxy/server.js:782 -msgid "UDP NAT expiration time" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1123 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1206 msgid "UDP over TCP" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:651 +#: htdocs/luci-static/resources/view/homeproxy/node.js:648 msgid "UDP over stream" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:644 +#: htdocs/luci-static/resources/view/homeproxy/node.js:641 msgid "UDP packet relay mode." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:643 +#: htdocs/luci-static/resources/view/homeproxy/node.js:640 msgid "UDP relay mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:626 -#: htdocs/luci-static/resources/view/homeproxy/server.js:316 +#: htdocs/luci-static/resources/view/homeproxy/node.js:413 +msgid "URLTest" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:623 +#: htdocs/luci-static/resources/view/homeproxy/server.js:266 msgid "UUID" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:98 +#: htdocs/luci-static/resources/view/homeproxy/status.js:97 msgid "Unknown error." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:148 +#: htdocs/luci-static/resources/view/homeproxy/status.js:147 msgid "Unknown error: %s" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1086 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1166 msgid "Unsupported fingerprint!" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1361 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1473 msgid "Update %s subscriptions" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:89 +#: htdocs/luci-static/resources/view/homeproxy/status.js:88 msgid "Update failed." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1062 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:254 msgid "Update interval" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1063 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:255 msgid "Update interval of rule set.
1d will be used if empty." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1356 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1468 msgid "Update nodes from subscriptions" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1300 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1412 msgid "Update subscriptions via proxy." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1293 -msgid "Update time" -msgstr "" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1299 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1411 msgid "Update via proxy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:937 -#: htdocs/luci-static/resources/view/homeproxy/server.js:507 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1017 +#: htdocs/luci-static/resources/view/homeproxy/server.js:446 msgid "Upload bandwidth" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:938 -#: htdocs/luci-static/resources/view/homeproxy/server.js:508 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1018 +#: htdocs/luci-static/resources/view/homeproxy/server.js:447 msgid "Upload bandwidth in Mbps." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1028 -#: htdocs/luci-static/resources/view/homeproxy/server.js:737 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1108 +#: htdocs/luci-static/resources/view/homeproxy/server.js:676 msgid "Upload certificate" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:755 +#: htdocs/luci-static/resources/view/homeproxy/server.js:694 msgid "Upload key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1031 -#: htdocs/luci-static/resources/view/homeproxy/server.js:740 -#: htdocs/luci-static/resources/view/homeproxy/server.js:758 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1111 +#: htdocs/luci-static/resources/view/homeproxy/server.js:679 +#: htdocs/luci-static/resources/view/homeproxy/server.js:697 msgid "Upload..." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:579 +#: htdocs/luci-static/resources/view/homeproxy/server.js:518 msgid "Use ACME TLS certificate issuer." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:973 -#: htdocs/luci-static/resources/view/homeproxy/server.js:544 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1101 +msgid "Use Online Dashboard" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1053 +#: htdocs/luci-static/resources/view/homeproxy/server.js:483 msgid "" "Used to verify the hostname on the returned certificates unless insecure is " "given." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:561 -#: htdocs/luci-static/resources/view/homeproxy/client.js:895 +#: htdocs/luci-static/resources/view/homeproxy/client.js:615 +#: htdocs/luci-static/resources/view/homeproxy/client.js:960 msgid "User" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:423 -#: htdocs/luci-static/resources/view/homeproxy/server.js:170 +#: htdocs/luci-static/resources/view/homeproxy/node.js:426 +#: htdocs/luci-static/resources/view/homeproxy/server.js:131 msgid "Username" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:409 -#: htdocs/luci-static/resources/view/homeproxy/server.js:156 +#: htdocs/luci-static/resources/view/homeproxy/node.js:410 +#: htdocs/luci-static/resources/view/homeproxy/server.js:117 msgid "VLESS" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:410 -#: htdocs/luci-static/resources/view/homeproxy/server.js:157 +#: htdocs/luci-static/resources/view/homeproxy/node.js:411 +#: htdocs/luci-static/resources/view/homeproxy/server.js:118 msgid "VMess" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:160 -#: htdocs/luci-static/resources/view/homeproxy/client.js:186 +#: htdocs/luci-static/resources/view/homeproxy/client.js:216 +#: htdocs/luci-static/resources/view/homeproxy/client.js:242 msgid "WAN DNS (read from interface)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1133 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 msgid "WAN IP Policy" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:718 -#: htdocs/luci-static/resources/view/homeproxy/server.js:387 +#: htdocs/luci-static/resources/view/homeproxy/node.js:798 +#: htdocs/luci-static/resources/view/homeproxy/server.js:326 msgid "WebSocket" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1325 +#: htdocs/luci-static/resources/view/homeproxy/node.js:743 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1438 msgid "Whitelist mode" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:408 +#: htdocs/luci-static/resources/view/homeproxy/node.js:409 msgid "WireGuard" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:857 +#: htdocs/luci-static/resources/view/homeproxy/node.js:937 msgid "WireGuard peer public key." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:864 +#: htdocs/luci-static/resources/view/homeproxy/node.js:944 msgid "WireGuard pre-shared key." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:849 +#: htdocs/luci-static/resources/view/homeproxy/node.js:929 msgid "WireGuard requires base64-encoded private keys." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:472 -msgid "Write proxy protocol in the connection header." +#: htdocs/luci-static/resources/view/homeproxy/client.js:223 +#: htdocs/luci-static/resources/view/homeproxy/client.js:246 +msgid "Xinfeng Public DNS (114.114.114.114)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:828 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1345 +#: htdocs/luci-static/resources/view/homeproxy/node.js:908 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1457 msgid "Xudp (Xray-core)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:199 +#: htdocs/luci-static/resources/view/homeproxy/client.js:255 msgid "You can only have two servers set at maximum." msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:243 +#: htdocs/luci-static/resources/homeproxy.js:272 msgid "Your %s was successfully uploaded. Size: %sB." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:614 +#: htdocs/luci-static/resources/view/homeproxy/server.js:553 msgid "ZeroSSL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1033 -#: htdocs/luci-static/resources/view/homeproxy/server.js:742 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1113 +#: htdocs/luci-static/resources/view/homeproxy/server.js:681 msgid "certificate" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:993 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1001 -#: htdocs/luci-static/resources/view/homeproxy/server.js:555 -#: htdocs/luci-static/resources/view/homeproxy/server.js:563 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1073 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1081 +#: htdocs/luci-static/resources/view/homeproxy/server.js:494 +#: htdocs/luci-static/resources/view/homeproxy/server.js:502 msgid "default" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:53 +#: htdocs/luci-static/resources/view/homeproxy/status.js:52 msgid "failed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:714 -#: htdocs/luci-static/resources/view/homeproxy/server.js:383 +#: htdocs/luci-static/resources/view/homeproxy/node.js:794 +#: htdocs/luci-static/resources/view/homeproxy/server.js:322 msgid "gRPC" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:756 +#: htdocs/luci-static/resources/view/homeproxy/node.js:836 msgid "gRPC permit without stream" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:751 -#: htdocs/luci-static/resources/view/homeproxy/server.js:411 +#: htdocs/luci-static/resources/view/homeproxy/node.js:831 +#: htdocs/luci-static/resources/view/homeproxy/server.js:350 msgid "gRPC service name" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:289 +#: htdocs/luci-static/resources/view/homeproxy/client.js:348 msgid "gVisor" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:263 -#: htdocs/luci-static/resources/homeproxy.js:281 -#: htdocs/luci-static/resources/view/homeproxy/client.js:176 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1026 -#: htdocs/luci-static/resources/view/homeproxy/node.js:452 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1082 -#: htdocs/luci-static/resources/view/homeproxy/server.js:211 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1096 +msgid "metacubexd" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1335 +msgid "node" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:292 +#: htdocs/luci-static/resources/homeproxy.js:310 +#: htdocs/luci-static/resources/view/homeproxy/client.js:232 +#: htdocs/luci-static/resources/view/homeproxy/node.js:455 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1162 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:217 +#: htdocs/luci-static/resources/view/homeproxy/server.js:161 msgid "non-empty value" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:567 -#: htdocs/luci-static/resources/view/homeproxy/node.js:826 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1343 +#: htdocs/luci-static/resources/view/homeproxy/node.js:564 +#: htdocs/luci-static/resources/view/homeproxy/node.js:906 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1455 msgid "none" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:827 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1344 +#: htdocs/luci-static/resources/view/homeproxy/node.js:907 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1456 msgid "packet addr (v2ray-core v5+)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:50 +#: htdocs/luci-static/resources/view/homeproxy/status.js:49 msgid "passed" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/server.js:760 +#: htdocs/luci-static/resources/view/homeproxy/server.js:699 msgid "private key" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1098 +msgid "razord-meta" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/status.js:233 msgid "sing-box client" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:230 +#: htdocs/luci-static/resources/view/homeproxy/status.js:236 msgid "sing-box server" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1059 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1336 +msgid "sub" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1139 msgid "uTLS fingerprint" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1060 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1140 msgid "" "uTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting " "resistance." msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/status.js:59 +#: htdocs/luci-static/resources/view/homeproxy/status.js:58 msgid "unchecked" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:221 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1255 +#: htdocs/luci-static/resources/homeproxy.js:240 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:161 msgid "unique UCI identifier" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:272 +#: htdocs/luci-static/resources/homeproxy.js:243 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1353 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:164 +msgid "unique label" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:301 msgid "unique value" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:474 -#: htdocs/luci-static/resources/view/homeproxy/node.js:581 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 +#: htdocs/luci-static/resources/view/homeproxy/node.js:578 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1214 msgid "v1" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:475 -#: htdocs/luci-static/resources/view/homeproxy/node.js:582 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1132 +#: htdocs/luci-static/resources/view/homeproxy/node.js:579 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1215 msgid "v2" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:583 +#: htdocs/luci-static/resources/view/homeproxy/node.js:580 msgid "v3" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:178 +#: htdocs/luci-static/resources/view/homeproxy/client.js:234 msgid "valid IP address" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1031 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1034 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1311 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1314 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1427 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:222 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:225 msgid "valid URL" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:206 +#: htdocs/luci-static/resources/view/homeproxy/client.js:262 msgid "valid address#port" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:255 +#: htdocs/luci-static/resources/homeproxy.js:284 msgid "valid base64 key with %d characters" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1173 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1242 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1271 msgid "valid hostname" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:75 +#: htdocs/luci-static/resources/view/homeproxy/client.js:130 msgid "valid port range (port1:port2)" msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/client.js:246 +#: htdocs/luci-static/resources/view/homeproxy/client.js:300 +#: htdocs/luci-static/resources/view/homeproxy/client.js:305 msgid "valid port value" msgstr "" -#: htdocs/luci-static/resources/homeproxy.js:283 +#: htdocs/luci-static/resources/homeproxy.js:312 msgid "valid uuid" msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1097 +msgid "yacd-meta" +msgstr "" diff --git a/small/luci-app-homeproxy/po/zh_Hans/homeproxy.po b/small/luci-app-homeproxy/po/zh_Hans/homeproxy.po index 204cc1d011..1af8fc589b 100644 --- a/small/luci-app-homeproxy/po/zh_Hans/homeproxy.po +++ b/small/luci-app-homeproxy/po/zh_Hans/homeproxy.po @@ -8,153 +8,166 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Transfer-Encoding: 8bit\n" -#: htdocs/luci-static/resources/view/homeproxy/status.js:159 +#: htdocs/luci-static/resources/view/homeproxy/status.js:158 msgid "%s log" msgstr "%s 日志" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1408 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1520 msgid "%s nodes removed" msgstr "移除了 %s 个节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:571 -#: htdocs/luci-static/resources/view/homeproxy/client.js:905 +#: htdocs/luci-static/resources/view/homeproxy/client.js:633 +#: htdocs/luci-static/resources/view/homeproxy/client.js:978 +#: htdocs/luci-static/resources/view/homeproxy/node.js:710 msgid "-- Please choose --" msgstr "-- 请选择 --" -#: htdocs/luci-static/resources/view/homeproxy/client.js:476 +#: htdocs/luci-static/resources/view/homeproxy/client.js:532 msgid "4 or 6. Not limited if empty." msgstr "4 或 6。留空不限制。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1029 -#: htdocs/luci-static/resources/view/homeproxy/server.js:738 -#: htdocs/luci-static/resources/view/homeproxy/server.js:756 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +msgid "
» " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +msgid "." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +msgid "
.
you will need to check update via " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1109 +#: htdocs/luci-static/resources/view/homeproxy/server.js:677 +#: htdocs/luci-static/resources/view/homeproxy/server.js:695 msgid "Save your configuration before uploading files!" msgstr "上传文件前请先保存配置!" -#: htdocs/luci-static/resources/view/homeproxy/server.js:647 +#: htdocs/luci-static/resources/view/homeproxy/server.js:586 msgid "API token" msgstr "API 令牌" -#: htdocs/luci-static/resources/view/homeproxy/node.js:606 +#: htdocs/luci-static/resources/view/homeproxy/node.js:603 msgid "Accept any if empty." msgstr "留空则不校验。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1068 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1137 msgid "Access Control" msgstr "访问控制" -#: htdocs/luci-static/resources/view/homeproxy/server.js:632 +#: htdocs/luci-static/resources/view/homeproxy/server.js:571 msgid "Access key ID" msgstr "访问密钥 ID" -#: htdocs/luci-static/resources/view/homeproxy/server.js:637 +#: htdocs/luci-static/resources/view/homeproxy/server.js:576 msgid "Access key secret" msgstr "访问密钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:774 +#: htdocs/luci-static/resources/view/homeproxy/client.js:838 msgid "Add a DNS rule" msgstr "新增 DNS 规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:679 +#: htdocs/luci-static/resources/view/homeproxy/client.js:741 msgid "Add a DNS server" msgstr "新增 DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/node.js:363 +#: htdocs/luci-static/resources/view/homeproxy/node.js:364 msgid "Add a node" msgstr "新增节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:366 +#: htdocs/luci-static/resources/view/homeproxy/client.js:419 msgid "Add a routing node" msgstr "新增路由节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:442 +#: htdocs/luci-static/resources/view/homeproxy/client.js:497 msgid "Add a routing rule" msgstr "新增路由规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:989 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:91 msgid "Add a rule set" msgstr "新增规则集" -#: htdocs/luci-static/resources/view/homeproxy/server.js:129 +#: htdocs/luci-static/resources/view/homeproxy/server.js:89 msgid "Add a server" msgstr "新增服务器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:693 -#: htdocs/luci-static/resources/view/homeproxy/node.js:413 +#: htdocs/luci-static/resources/view/homeproxy/client.js:756 +#: htdocs/luci-static/resources/view/homeproxy/node.js:416 msgid "Address" msgstr "地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:697 +#: htdocs/luci-static/resources/view/homeproxy/client.js:760 msgid "Address resolver" msgstr "地址解析器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:729 +#: htdocs/luci-static/resources/view/homeproxy/client.js:792 msgid "Address strategy" msgstr "地址解析策略" -#: htdocs/luci-static/resources/view/homeproxy/server.js:625 +#: htdocs/luci-static/resources/view/homeproxy/server.js:564 msgid "Alibaba Cloud DNS" msgstr "阿里云 DNS" -#: htdocs/luci-static/resources/view/homeproxy/client.js:165 -#: htdocs/luci-static/resources/view/homeproxy/client.js:187 +#: htdocs/luci-static/resources/view/homeproxy/client.js:221 +#: htdocs/luci-static/resources/view/homeproxy/client.js:243 msgid "Aliyun Public DNS (223.5.5.5)" msgstr "阿里云公共 DNS(223.5.5.5)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:237 +#: htdocs/luci-static/resources/view/homeproxy/client.js:293 msgid "All ports" msgstr "所有端口" -#: htdocs/luci-static/resources/view/homeproxy/node.js:982 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1334 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1062 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1446 msgid "Allow insecure" msgstr "允许不安全连接" -#: htdocs/luci-static/resources/view/homeproxy/node.js:983 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1063 msgid "Allow insecure connection at TLS client." msgstr "允许 TLS 客户端侧的不安全连接。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1335 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1447 msgid "Allow insecure connection by default when add nodes from subscriptions." msgstr "从订阅获取节点时,默认允许不安全连接。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:813 -#: htdocs/luci-static/resources/view/homeproxy/server.js:463 +#: htdocs/luci-static/resources/view/homeproxy/node.js:893 +#: htdocs/luci-static/resources/view/homeproxy/server.js:402 msgid "Allowed payload size is in the request." msgstr "请求中允许的载荷大小。" -#: htdocs/luci-static/resources/view/homeproxy/status.js:95 +#: htdocs/luci-static/resources/view/homeproxy/status.js:94 msgid "Already at the latest version." msgstr "已是最新版本。" -#: htdocs/luci-static/resources/view/homeproxy/status.js:92 +#: htdocs/luci-static/resources/view/homeproxy/status.js:91 msgid "Already in updating." msgstr "已在更新中。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:679 -#: htdocs/luci-static/resources/view/homeproxy/server.js:372 +#: htdocs/luci-static/resources/view/homeproxy/node.js:676 +#: htdocs/luci-static/resources/view/homeproxy/server.js:311 msgid "Alter ID" msgstr "额外 ID" -#: htdocs/luci-static/resources/view/homeproxy/server.js:662 +#: htdocs/luci-static/resources/view/homeproxy/server.js:601 msgid "Alternative HTTP port" msgstr "替代 HTTP 端口" -#: htdocs/luci-static/resources/view/homeproxy/server.js:668 +#: htdocs/luci-static/resources/view/homeproxy/server.js:607 msgid "Alternative TLS port" msgstr "替代 HTTPS 端口" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1371 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1483 msgid "An error occurred during updating subscriptions: %s" msgstr "更新订阅时发生错误:%s" -#: htdocs/luci-static/resources/view/homeproxy/client.js:931 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1004 msgid "Any" msgstr "任何" -#: htdocs/luci-static/resources/view/homeproxy/client.js:653 -#: htdocs/luci-static/resources/view/homeproxy/client.js:759 -#: htdocs/luci-static/resources/view/homeproxy/client.js:973 +#: htdocs/luci-static/resources/view/homeproxy/client.js:714 +#: htdocs/luci-static/resources/view/homeproxy/client.js:822 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1053 msgid "" "Append a edns0-subnet OPT extra record with the specified IP " "prefix to every query by default.
If value is an IP address instead of " @@ -163,54 +176,66 @@ msgstr "" "将带有指定 IP 前缀的 edns0-subnet OPT 记录附加到每个查询。如果值" "是 IP 地址而不是前缀,则会自动添加 /32/128。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1015 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1095 msgid "Append self-signed certificate" msgstr "追加自签名证书" -#: htdocs/luci-static/resources/view/homeproxy/node.js:374 +#: htdocs/luci-static/resources/view/homeproxy/node.js:375 msgid "Applied" msgstr "已应用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:367 -#: htdocs/luci-static/resources/view/homeproxy/node.js:377 +#: htdocs/luci-static/resources/view/homeproxy/node.js:368 +#: htdocs/luci-static/resources/view/homeproxy/node.js:378 msgid "Apply" msgstr "应用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:18 +#: htdocs/luci-static/resources/view/homeproxy/node.js:19 msgid "Are you sure to allow insecure?" msgstr "确定要允许不安全连接吗?" -#: htdocs/luci-static/resources/view/homeproxy/server.js:343 +#: htdocs/luci-static/resources/view/homeproxy/server.js:282 msgid "Auth timeout" msgstr "认证超时" -#: htdocs/luci-static/resources/view/homeproxy/node.js:703 +#: htdocs/luci-static/resources/view/homeproxy/node.js:700 msgid "Authenticated length" msgstr "认证长度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:498 -#: htdocs/luci-static/resources/view/homeproxy/server.js:252 +#: htdocs/luci-static/resources/view/homeproxy/node.js:495 +#: htdocs/luci-static/resources/view/homeproxy/server.js:202 msgid "Authentication payload" msgstr "认证载荷" -#: htdocs/luci-static/resources/view/homeproxy/node.js:491 -#: htdocs/luci-static/resources/view/homeproxy/server.js:245 +#: htdocs/luci-static/resources/view/homeproxy/node.js:488 +#: htdocs/luci-static/resources/view/homeproxy/server.js:195 msgid "Authentication type" msgstr "认证类型" -#: htdocs/luci-static/resources/view/homeproxy/server.js:120 +#: htdocs/luci-static/resources/view/homeproxy/server.js:79 msgid "Auto configure firewall" msgstr "自动配置防火墙" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1288 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1121 +msgid "Auto set backend" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1122 +msgid "Auto set backend address for dashboard." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1398 msgid "Auto update" msgstr "自动更新" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1289 -msgid "Auto update subscriptions and geodata." -msgstr "自动更新订阅和地理数据。" +#: htdocs/luci-static/resources/view/homeproxy/node.js:1399 +msgid "Auto update subscriptions." +msgstr "自动更新订阅。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:637 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1130 +msgid "Automatically generated if empty" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:634 msgid "BBR" msgstr "BBR" @@ -218,119 +243,137 @@ msgstr "BBR" msgid "BaiDu" msgstr "百度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:493 -#: htdocs/luci-static/resources/view/homeproxy/server.js:247 +#: htdocs/luci-static/resources/view/homeproxy/node.js:490 +#: htdocs/luci-static/resources/view/homeproxy/server.js:197 msgid "Base64" msgstr "Base64" -#: htdocs/luci-static/resources/view/homeproxy/client.js:301 +#: htdocs/luci-static/resources/view/homeproxy/client.js:360 msgid "Based on google/gvisor." msgstr "基于 google/gvisor。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1011 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:202 msgid "Binary file" msgstr "二进制文件" -#: htdocs/luci-static/resources/view/homeproxy/client.js:393 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1081 +#: htdocs/luci-static/resources/view/homeproxy/client.js:447 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1150 msgid "Bind interface" msgstr "绑定接口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1082 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1151 msgid "" "Bind outbound traffic to specific interface. Leave empty to auto detect." msgstr "绑定出站流量至指定端口。留空自动检测。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1324 +#: htdocs/luci-static/resources/view/homeproxy/node.js:742 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1437 msgid "Blacklist mode" msgstr "黑名单模式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:344 -#: htdocs/luci-static/resources/view/homeproxy/client.js:599 -#: htdocs/luci-static/resources/view/homeproxy/client.js:933 +#: htdocs/luci-static/resources/view/homeproxy/client.js:389 +#: htdocs/luci-static/resources/view/homeproxy/client.js:660 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1006 +#: htdocs/luci-static/resources/view/homeproxy/node.js:722 +#: htdocs/luci-static/resources/view/homeproxy/node.js:732 msgid "Block" msgstr "封锁" -#: htdocs/luci-static/resources/view/homeproxy/client.js:629 -#: htdocs/luci-static/resources/view/homeproxy/client.js:951 +#: htdocs/luci-static/resources/view/homeproxy/client.js:690 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1031 msgid "Block DNS queries" msgstr "封锁 DNS 请求" -#: htdocs/luci-static/resources/view/homeproxy/client.js:479 -#: htdocs/luci-static/resources/view/homeproxy/client.js:492 -#: htdocs/luci-static/resources/view/homeproxy/client.js:810 -#: htdocs/luci-static/resources/view/homeproxy/client.js:820 -#: htdocs/luci-static/resources/view/homeproxy/server.js:802 +#: htdocs/luci-static/resources/view/homeproxy/client.js:535 +#: htdocs/luci-static/resources/view/homeproxy/client.js:548 +#: htdocs/luci-static/resources/view/homeproxy/client.js:875 +#: htdocs/luci-static/resources/view/homeproxy/client.js:885 +#: htdocs/luci-static/resources/view/homeproxy/server.js:734 msgid "Both" msgstr "全部" -#: htdocs/luci-static/resources/view/homeproxy/client.js:322 +#: htdocs/luci-static/resources/view/homeproxy/client.js:372 msgid "Bypass CN traffic" msgstr "绕过中国流量" -#: htdocs/luci-static/resources/view/homeproxy/client.js:224 +#: htdocs/luci-static/resources/view/homeproxy/client.js:280 msgid "Bypass mainland China" msgstr "大陆白名单" -#: htdocs/luci-static/resources/view/homeproxy/client.js:323 +#: htdocs/luci-static/resources/view/homeproxy/client.js:373 msgid "Bypass mainland China traffic via firewall rules by default." msgstr "默认使用防火墙规则绕过中国大陆流量。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:611 +#: htdocs/luci-static/resources/view/homeproxy/server.js:550 msgid "CA provider" msgstr "CA 颁发机构" -#: htdocs/luci-static/resources/view/homeproxy/client.js:188 +#: htdocs/luci-static/resources/view/homeproxy/client.js:244 msgid "CNNIC Public DNS (210.2.4.8)" msgstr "CNNIC 公共 DNS(210.2.4.8)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:635 +#: htdocs/luci-static/resources/view/homeproxy/node.js:632 msgid "CUBIC" msgstr "CUBIC" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1192 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1269 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:105 msgid "Cancel" msgstr "取消" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1021 -#: htdocs/luci-static/resources/view/homeproxy/server.js:727 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1101 +#: htdocs/luci-static/resources/view/homeproxy/server.js:666 msgid "Certificate path" msgstr "证书路径" -#: htdocs/luci-static/resources/view/homeproxy/status.js:57 +#: htdocs/luci-static/resources/view/homeproxy/status.js:56 msgid "Check" msgstr "检查" -#: htdocs/luci-static/resources/view/homeproxy/status.js:105 +#: htdocs/luci-static/resources/view/homeproxy/status.js:104 msgid "Check update" msgstr "检查更新" -#: htdocs/luci-static/resources/view/homeproxy/client.js:185 +#: htdocs/luci-static/resources/view/homeproxy/client.js:241 msgid "China DNS server" msgstr "国内 DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/status.js:204 +#: htdocs/luci-static/resources/view/homeproxy/status.js:210 msgid "China IPv4 list version" msgstr "大陆 IPv4 库版本" -#: htdocs/luci-static/resources/view/homeproxy/status.js:208 +#: htdocs/luci-static/resources/view/homeproxy/status.js:214 msgid "China IPv6 list version" msgstr "大陆 IPv6 库版本" -#: htdocs/luci-static/resources/view/homeproxy/status.js:212 +#: htdocs/luci-static/resources/view/homeproxy/status.js:218 msgid "China list version" msgstr "大陆域名列表版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1007 -#: htdocs/luci-static/resources/view/homeproxy/server.js:569 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1087 +#: htdocs/luci-static/resources/view/homeproxy/server.js:508 msgid "Cipher suites" msgstr "密码套件" -#: htdocs/luci-static/resources/view/homeproxy/client.js:162 +#: htdocs/luci-static/resources/view/homeproxy/client.js:218 msgid "Cisco Public DNS (208.67.222.222)" msgstr "思科公共 DNS(208.67.222.222)" -#: htdocs/luci-static/resources/view/homeproxy/status.js:166 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1060 +msgid "Clash API settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/status.js:205 +msgid "Clash dashboard version" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:619 +#: htdocs/luci-static/resources/view/homeproxy/client.js:964 +msgid "Clash mode" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/status.js:165 msgid "Clean log" msgstr "清空日志" @@ -338,30 +381,34 @@ msgstr "清空日志" msgid "Client Settings" msgstr "客户端设置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:600 +#: htdocs/luci-static/resources/view/homeproxy/node.js:597 msgid "Client version" msgstr "客户端版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:161 +#: htdocs/luci-static/resources/view/homeproxy/client.js:217 msgid "CloudFlare Public DNS (1.1.1.1)" msgstr "CloudFlare 公共 DNS(1.1.1.1)" -#: htdocs/luci-static/resources/view/homeproxy/server.js:626 +#: htdocs/luci-static/resources/view/homeproxy/server.js:565 msgid "Cloudflare" msgstr "Cloudflare" -#: htdocs/luci-static/resources/view/homeproxy/client.js:133 -#: htdocs/luci-static/resources/view/homeproxy/server.js:110 -#: htdocs/luci-static/resources/view/homeproxy/status.js:129 +#: htdocs/luci-static/resources/view/homeproxy/client.js:183 +#: htdocs/luci-static/resources/view/homeproxy/server.js:69 +#: htdocs/luci-static/resources/view/homeproxy/status.js:128 msgid "Collecting data..." msgstr "收集数据中..." -#: htdocs/luci-static/resources/view/homeproxy/client.js:238 +#: htdocs/luci-static/resources/view/homeproxy/client.js:109 +msgid "Command failed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:294 msgid "Common ports only (bypass P2P traffic)" msgstr "仅常用端口(绕过 P2P 流量)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:633 -#: htdocs/luci-static/resources/view/homeproxy/server.js:334 +#: htdocs/luci-static/resources/view/homeproxy/node.js:630 +#: htdocs/luci-static/resources/view/homeproxy/server.js:273 msgid "Congestion control algorithm" msgstr "拥塞控制算法" @@ -369,157 +416,172 @@ msgstr "拥塞控制算法" msgid "Connection check" msgstr "连接检查" -#: htdocs/luci-static/resources/view/homeproxy/client.js:226 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1403 +msgid "Cron expression" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:282 msgid "Custom routing" msgstr "自定义路由" -#: htdocs/luci-static/resources/view/homeproxy/client.js:827 +#: htdocs/luci-static/resources/view/homeproxy/client.js:892 msgid "DNS" msgstr "DNS" -#: htdocs/luci-static/resources/view/homeproxy/client.js:765 +#: htdocs/luci-static/resources/view/homeproxy/client.js:828 msgid "DNS Rules" msgstr "DNS 规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:670 +#: htdocs/luci-static/resources/view/homeproxy/client.js:731 msgid "DNS Servers" msgstr "DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:612 +#: htdocs/luci-static/resources/view/homeproxy/client.js:673 msgid "DNS Settings" msgstr "DNS 设置" -#: htdocs/luci-static/resources/view/homeproxy/server.js:624 +#: htdocs/luci-static/resources/view/homeproxy/server.js:563 msgid "DNS provider" msgstr "DNS 提供商" -#: htdocs/luci-static/resources/view/homeproxy/client.js:774 +#: htdocs/luci-static/resources/view/homeproxy/client.js:838 msgid "DNS rule" msgstr "DNS 规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:158 -#: htdocs/luci-static/resources/view/homeproxy/client.js:679 +#: htdocs/luci-static/resources/view/homeproxy/client.js:214 +#: htdocs/luci-static/resources/view/homeproxy/client.js:741 msgid "DNS server" msgstr "DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/server.js:619 +#: htdocs/luci-static/resources/view/homeproxy/server.js:558 msgid "DNS01 challenge" msgstr "DNS01 验证" -#: htdocs/luci-static/resources/homeproxy.js:17 -#: htdocs/luci-static/resources/view/homeproxy/client.js:470 -#: htdocs/luci-static/resources/view/homeproxy/client.js:802 -#: htdocs/luci-static/resources/view/homeproxy/node.js:645 +#: htdocs/luci-static/resources/homeproxy.js:18 +#: htdocs/luci-static/resources/view/homeproxy/client.js:526 +#: htdocs/luci-static/resources/view/homeproxy/client.js:867 +#: htdocs/luci-static/resources/view/homeproxy/node.js:642 +#: htdocs/luci-static/resources/view/homeproxy/node.js:730 +#: htdocs/luci-static/resources/view/homeproxy/node.js:758 +#: htdocs/luci-static/resources/view/homeproxy/node.js:765 msgid "Default" msgstr "默认" -#: htdocs/luci-static/resources/view/homeproxy/client.js:627 -#: htdocs/luci-static/resources/view/homeproxy/client.js:704 -#: htdocs/luci-static/resources/view/homeproxy/client.js:949 +#: htdocs/luci-static/resources/view/homeproxy/client.js:688 +#: htdocs/luci-static/resources/view/homeproxy/client.js:767 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1029 msgid "Default DNS (issued by WAN)" msgstr "默认 DNS(由 WAN 下发)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:622 +#: htdocs/luci-static/resources/view/homeproxy/client.js:683 msgid "Default DNS server" msgstr "默认 DNS 服务器" -#: htdocs/luci-static/resources/view/homeproxy/client.js:617 +#: htdocs/luci-static/resources/view/homeproxy/client.js:678 msgid "Default DNS strategy" msgstr "默认 DNS 解析策略" -#: htdocs/luci-static/resources/view/homeproxy/client.js:736 +#: htdocs/luci-static/resources/view/homeproxy/node.js:728 +msgid "Default Outbound" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:799 msgid "Default domain strategy for resolving the domain names." msgstr "默认域名解析策略。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:337 +#: htdocs/luci-static/resources/view/homeproxy/client.js:382 msgid "Default outbound" msgstr "默认出站" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1342 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1454 msgid "Default packet encoding" msgstr "默认包封装格式" -#: htdocs/luci-static/resources/view/homeproxy/server.js:590 +#: htdocs/luci-static/resources/view/homeproxy/server.js:529 msgid "Default server name" msgstr "默认服务器名称" -#: htdocs/luci-static/resources/view/homeproxy/client.js:343 -#: htdocs/luci-static/resources/view/homeproxy/client.js:406 -#: htdocs/luci-static/resources/view/homeproxy/client.js:598 -#: htdocs/luci-static/resources/view/homeproxy/client.js:746 -#: htdocs/luci-static/resources/view/homeproxy/client.js:932 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1050 -#: htdocs/luci-static/resources/view/homeproxy/node.js:394 +#: htdocs/luci-static/resources/view/homeproxy/client.js:388 +#: htdocs/luci-static/resources/view/homeproxy/client.js:460 +#: htdocs/luci-static/resources/view/homeproxy/client.js:624 +#: htdocs/luci-static/resources/view/homeproxy/client.js:659 +#: htdocs/luci-static/resources/view/homeproxy/client.js:809 +#: htdocs/luci-static/resources/view/homeproxy/client.js:969 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1005 +#: htdocs/luci-static/resources/view/homeproxy/node.js:395 +#: htdocs/luci-static/resources/view/homeproxy/node.js:721 +#: htdocs/luci-static/resources/view/homeproxy/node.js:731 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:241 msgid "Direct" msgstr "直连" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1180 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1249 msgid "Direct Domain List" msgstr "直连域名列表" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1097 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1142 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1166 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1211 msgid "Direct IPv4 IP-s" msgstr "直连 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1100 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1145 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1169 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1214 msgid "Direct IPv6 IP-s" msgstr "直连 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1103 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1172 msgid "Direct MAC-s" msgstr "直连 MAC 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:142 -#: htdocs/luci-static/resources/view/homeproxy/client.js:150 -#: htdocs/luci-static/resources/view/homeproxy/client.js:342 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1091 -#: htdocs/luci-static/resources/view/homeproxy/node.js:473 -#: htdocs/luci-static/resources/view/homeproxy/node.js:492 -#: htdocs/luci-static/resources/view/homeproxy/node.js:504 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1061 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1323 -#: htdocs/luci-static/resources/view/homeproxy/server.js:246 -#: htdocs/luci-static/resources/view/homeproxy/server.js:258 +#: htdocs/luci-static/resources/view/homeproxy/client.js:198 +#: htdocs/luci-static/resources/view/homeproxy/client.js:206 +#: htdocs/luci-static/resources/view/homeproxy/client.js:387 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1160 +#: htdocs/luci-static/resources/view/homeproxy/node.js:489 +#: htdocs/luci-static/resources/view/homeproxy/node.js:501 +#: htdocs/luci-static/resources/view/homeproxy/node.js:741 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1141 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1436 +#: htdocs/luci-static/resources/view/homeproxy/server.js:196 +#: htdocs/luci-static/resources/view/homeproxy/server.js:208 msgid "Disable" msgstr "禁用" -#: htdocs/luci-static/resources/view/homeproxy/client.js:640 +#: htdocs/luci-static/resources/view/homeproxy/client.js:701 msgid "Disable DNS cache" msgstr "禁用 DNS 缓存" -#: htdocs/luci-static/resources/view/homeproxy/server.js:652 +#: htdocs/luci-static/resources/view/homeproxy/server.js:591 msgid "Disable HTTP challenge" msgstr "禁用 HTTP 验证" -#: htdocs/luci-static/resources/view/homeproxy/node.js:540 -#: htdocs/luci-static/resources/view/homeproxy/server.js:289 +#: htdocs/luci-static/resources/view/homeproxy/node.js:537 +#: htdocs/luci-static/resources/view/homeproxy/server.js:239 msgid "Disable Path MTU discovery" msgstr "禁用路径 MTU 探测" -#: htdocs/luci-static/resources/view/homeproxy/server.js:657 +#: htdocs/luci-static/resources/view/homeproxy/server.js:596 msgid "Disable TLS ALPN challenge" msgstr "禁用 TLS ALPN 认证" -#: htdocs/luci-static/resources/view/homeproxy/client.js:963 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1043 msgid "Disable cache and save cache in this query." msgstr "在本次查询中禁用缓存。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:643 +#: htdocs/luci-static/resources/view/homeproxy/client.js:704 msgid "Disable cache expire" msgstr "缓存永不过期" -#: htdocs/luci-static/resources/view/homeproxy/client.js:962 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1042 msgid "Disable dns cache" msgstr "禁用 DNS 缓存" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1043 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1123 msgid "Disable dynamic record sizing" msgstr "禁用动态记录大小" -#: htdocs/luci-static/resources/view/homeproxy/node.js:541 -#: htdocs/luci-static/resources/view/homeproxy/server.js:290 +#: htdocs/luci-static/resources/view/homeproxy/node.js:538 +#: htdocs/luci-static/resources/view/homeproxy/server.js:240 msgid "" "Disables Path MTU Discovery (RFC 8899). Packets will then be at most 1252 " "(IPv4) / 1232 (IPv6) bytes in size." @@ -527,47 +589,47 @@ msgstr "" "禁用路径 MTU 发现 (RFC 8899)。 数据包的大小最多为 1252 (IPv4) / 1232 (IPv6) " "字节。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:503 -#: htdocs/luci-static/resources/view/homeproxy/client.js:839 +#: htdocs/luci-static/resources/view/homeproxy/client.js:559 +#: htdocs/luci-static/resources/view/homeproxy/client.js:904 msgid "Domain keyword" msgstr "域名关键词" -#: htdocs/luci-static/resources/view/homeproxy/client.js:494 -#: htdocs/luci-static/resources/view/homeproxy/client.js:830 +#: htdocs/luci-static/resources/view/homeproxy/client.js:550 +#: htdocs/luci-static/resources/view/homeproxy/client.js:895 msgid "Domain name" msgstr "域名" -#: htdocs/luci-static/resources/view/homeproxy/client.js:507 -#: htdocs/luci-static/resources/view/homeproxy/client.js:843 +#: htdocs/luci-static/resources/view/homeproxy/client.js:563 +#: htdocs/luci-static/resources/view/homeproxy/client.js:908 msgid "Domain regex" msgstr "域名正则表达式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:327 -#: htdocs/luci-static/resources/view/homeproxy/client.js:387 -#: htdocs/luci-static/resources/view/homeproxy/server.js:793 +#: htdocs/luci-static/resources/view/homeproxy/client.js:441 +#: htdocs/luci-static/resources/view/homeproxy/server.js:725 msgid "Domain strategy" msgstr "域名解析策略" -#: htdocs/luci-static/resources/view/homeproxy/client.js:499 -#: htdocs/luci-static/resources/view/homeproxy/client.js:835 +#: htdocs/luci-static/resources/view/homeproxy/client.js:555 +#: htdocs/luci-static/resources/view/homeproxy/client.js:900 msgid "Domain suffix" msgstr "域名后缀" -#: htdocs/luci-static/resources/view/homeproxy/server.js:584 +#: htdocs/luci-static/resources/view/homeproxy/server.js:523 msgid "Domains" msgstr "域名" -#: htdocs/luci-static/resources/view/homeproxy/node.js:931 -#: htdocs/luci-static/resources/view/homeproxy/server.js:501 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1011 +#: htdocs/luci-static/resources/view/homeproxy/server.js:440 msgid "Download bandwidth" msgstr "下载带宽" -#: htdocs/luci-static/resources/view/homeproxy/node.js:932 -#: htdocs/luci-static/resources/view/homeproxy/server.js:502 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1012 +#: htdocs/luci-static/resources/view/homeproxy/server.js:441 msgid "Download bandwidth in Mbps." msgstr "下载带宽(单位:Mbps)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1330 +#: htdocs/luci-static/resources/view/homeproxy/node.js:750 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1442 msgid "" "Drop/keep nodes that contain the specific keywords. " "正则表达式。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1322 +#: htdocs/luci-static/resources/view/homeproxy/node.js:740 +msgid "Drop/keep specific nodes from outbounds." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1435 msgid "Drop/keep specific nodes from subscriptions." msgstr "从订阅中 丢弃/保留 指定节点" -#: htdocs/luci-static/resources/view/homeproxy/server.js:675 +#: htdocs/luci-static/resources/view/homeproxy/server.js:614 msgid "" "EAB (External Account Binding) contains information necessary to bind or map " "an ACME account to some other account known by the CA.
External account " @@ -592,7 +658,7 @@ msgstr "" "
外部帐户绑定“用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA " "客户数据库。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1038 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1118 msgid "" "ECH (Encrypted Client Hello) is a TLS extension that allows a client to " "encrypt the first part of its ClientHello message." @@ -600,50 +666,54 @@ msgstr "" "ECH(Encrypted Client Hello)是一个 TLS 扩展,它允许客户端加密其 ClientHello " "信息的第一部分。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1053 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1133 msgid "ECH config" msgstr "ECH 配置" -#: htdocs/luci-static/resources/view/homeproxy/client.js:652 -#: htdocs/luci-static/resources/view/homeproxy/client.js:758 -#: htdocs/luci-static/resources/view/homeproxy/client.js:972 +#: htdocs/luci-static/resources/view/homeproxy/client.js:713 +#: htdocs/luci-static/resources/view/homeproxy/client.js:821 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1052 msgid "EDNS Client subnet" msgstr "ENDS 客户端子网" -#: htdocs/luci-static/resources/view/homeproxy/node.js:812 -#: htdocs/luci-static/resources/view/homeproxy/server.js:462 +#: htdocs/luci-static/resources/view/homeproxy/node.js:892 +#: htdocs/luci-static/resources/view/homeproxy/server.js:401 msgid "Early data" msgstr "前置数据" -#: htdocs/luci-static/resources/view/homeproxy/node.js:819 -#: htdocs/luci-static/resources/view/homeproxy/server.js:469 +#: htdocs/luci-static/resources/view/homeproxy/node.js:899 +#: htdocs/luci-static/resources/view/homeproxy/server.js:408 msgid "Early data header name" msgstr "前置数据标头" -#: htdocs/luci-static/resources/view/homeproxy/server.js:470 +#: htdocs/luci-static/resources/view/homeproxy/server.js:409 msgid "Early data is sent in path instead of header by default." msgstr "前置数据默认发送在路径而不是标头中。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1164 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1238 msgid "Edit nodes" msgstr "修改节点" -#: htdocs/luci-static/resources/view/homeproxy/server.js:596 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:82 +msgid "Edit ruleset" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/server.js:535 msgid "Email" msgstr "Email" -#: htdocs/luci-static/resources/view/homeproxy/client.js:375 -#: htdocs/luci-static/resources/view/homeproxy/client.js:457 -#: htdocs/luci-static/resources/view/homeproxy/client.js:688 -#: htdocs/luci-static/resources/view/homeproxy/client.js:789 -#: htdocs/luci-static/resources/view/homeproxy/client.js:998 -#: htdocs/luci-static/resources/view/homeproxy/server.js:116 -#: htdocs/luci-static/resources/view/homeproxy/server.js:139 +#: htdocs/luci-static/resources/view/homeproxy/client.js:429 +#: htdocs/luci-static/resources/view/homeproxy/client.js:513 +#: htdocs/luci-static/resources/view/homeproxy/client.js:751 +#: htdocs/luci-static/resources/view/homeproxy/client.js:854 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:189 +#: htdocs/luci-static/resources/view/homeproxy/server.js:75 +#: htdocs/luci-static/resources/view/homeproxy/server.js:100 msgid "Enable" msgstr "启用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:658 -#: htdocs/luci-static/resources/view/homeproxy/server.js:351 +#: htdocs/luci-static/resources/view/homeproxy/node.js:655 +#: htdocs/luci-static/resources/view/homeproxy/server.js:290 msgid "" "Enable 0-RTT QUIC connection handshake on the client side. This is not " "impacting much on the performance, as the protocol is fully multiplexed.
强烈建议禁用此功能,因为它容易受到重放攻击。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:657 -#: htdocs/luci-static/resources/view/homeproxy/server.js:350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:654 +#: htdocs/luci-static/resources/view/homeproxy/server.js:289 msgid "Enable 0-RTT handshake" msgstr "启用 0-RTT 握手" -#: htdocs/luci-static/resources/view/homeproxy/server.js:578 +#: htdocs/luci-static/resources/view/homeproxy/server.js:517 msgid "Enable ACME" msgstr "启用 ACME" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1037 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1065 +msgid "Enable Clash API" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1117 msgid "Enable ECH" msgstr "启用 ECH" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1048 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1128 msgid "Enable PQ signature schemes" msgstr "启用 PQ 签名方案" -#: htdocs/luci-static/resources/view/homeproxy/node.js:925 -#: htdocs/luci-static/resources/view/homeproxy/server.js:495 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1005 +#: htdocs/luci-static/resources/view/homeproxy/server.js:434 msgid "Enable TCP Brutal" msgstr "启用 TCP Brutal" -#: htdocs/luci-static/resources/view/homeproxy/node.js:926 -#: htdocs/luci-static/resources/view/homeproxy/server.js:496 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1006 +#: htdocs/luci-static/resources/view/homeproxy/server.js:435 msgid "Enable TCP Brutal congestion control algorithm" msgstr "启用 TCP Brutal 拥塞控制算法。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1119 -#: htdocs/luci-static/resources/view/homeproxy/server.js:777 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1201 +#: htdocs/luci-static/resources/view/homeproxy/server.js:716 msgid "Enable UDP fragmentation." msgstr "启用 UDP 分片。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:306 +#: htdocs/luci-static/resources/view/homeproxy/client.js:365 msgid "Enable endpoint-independent NAT" msgstr "启用端点独立 NAT" -#: htdocs/luci-static/resources/view/homeproxy/node.js:920 -#: htdocs/luci-static/resources/view/homeproxy/server.js:489 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1000 +#: htdocs/luci-static/resources/view/homeproxy/server.js:428 msgid "Enable padding" msgstr "启用填充" -#: htdocs/luci-static/resources/view/homeproxy/server.js:766 +#: htdocs/luci-static/resources/view/homeproxy/server.js:705 msgid "Enable tcp fast open for listener." msgstr "为监听器启用 TCP 快速打开。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1124 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1207 msgid "" "Enable the SUoT protocol, requires server support. Conflict with multiplex." msgstr "启用 SUoT 协议,需要服务端支持。与多路复用冲突。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:548 -#: htdocs/luci-static/resources/view/homeproxy/node.js:685 -#: htdocs/luci-static/resources/view/homeproxy/server.js:308 +#: htdocs/luci-static/resources/view/homeproxy/node.js:545 +#: htdocs/luci-static/resources/view/homeproxy/node.js:682 +#: htdocs/luci-static/resources/view/homeproxy/server.js:258 msgid "Encrypt method" msgstr "加密方式" -#: htdocs/luci-static/resources/homeproxy.js:221 -#: htdocs/luci-static/resources/homeproxy.js:255 -#: htdocs/luci-static/resources/homeproxy.js:263 -#: htdocs/luci-static/resources/homeproxy.js:272 -#: htdocs/luci-static/resources/homeproxy.js:281 -#: htdocs/luci-static/resources/homeproxy.js:283 -#: htdocs/luci-static/resources/view/homeproxy/client.js:75 -#: htdocs/luci-static/resources/view/homeproxy/client.js:176 -#: htdocs/luci-static/resources/view/homeproxy/client.js:178 -#: htdocs/luci-static/resources/view/homeproxy/client.js:206 -#: htdocs/luci-static/resources/view/homeproxy/client.js:246 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1026 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1031 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1034 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1173 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 -#: htdocs/luci-static/resources/view/homeproxy/node.js:452 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1082 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1255 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1311 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1314 -#: htdocs/luci-static/resources/view/homeproxy/server.js:211 -#: htdocs/luci-static/resources/view/homeproxy/server.js:602 -#: htdocs/luci-static/resources/view/homeproxy/server.js:604 +#: htdocs/luci-static/resources/homeproxy.js:237 +#: htdocs/luci-static/resources/homeproxy.js:240 +#: htdocs/luci-static/resources/homeproxy.js:243 +#: htdocs/luci-static/resources/homeproxy.js:284 +#: htdocs/luci-static/resources/homeproxy.js:292 +#: htdocs/luci-static/resources/homeproxy.js:301 +#: htdocs/luci-static/resources/homeproxy.js:310 +#: htdocs/luci-static/resources/homeproxy.js:312 +#: htdocs/luci-static/resources/view/homeproxy/client.js:130 +#: htdocs/luci-static/resources/view/homeproxy/client.js:232 +#: htdocs/luci-static/resources/view/homeproxy/client.js:234 +#: htdocs/luci-static/resources/view/homeproxy/client.js:262 +#: htdocs/luci-static/resources/view/homeproxy/client.js:300 +#: htdocs/luci-static/resources/view/homeproxy/client.js:305 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1017 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1242 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1271 +#: htdocs/luci-static/resources/view/homeproxy/node.js:455 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1162 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1353 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1427 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:161 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:164 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:217 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:222 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:225 +#: htdocs/luci-static/resources/view/homeproxy/server.js:161 +#: htdocs/luci-static/resources/view/homeproxy/server.js:541 +#: htdocs/luci-static/resources/view/homeproxy/server.js:543 msgid "Expecting: %s" msgstr "请输入:%s" -#: htdocs/luci-static/resources/view/homeproxy/server.js:674 +#: htdocs/luci-static/resources/view/homeproxy/server.js:613 msgid "External Account Binding" msgstr "外部账户绑定" -#: htdocs/luci-static/resources/view/homeproxy/server.js:686 +#: htdocs/luci-static/resources/view/homeproxy/server.js:625 msgid "External account MAC key" msgstr "外部账户 MAC 密钥" -#: htdocs/luci-static/resources/view/homeproxy/server.js:681 +#: htdocs/luci-static/resources/view/homeproxy/server.js:620 msgid "External account key ID" msgstr "外部账户密钥标识符" -#: htdocs/luci-static/resources/homeproxy.js:245 +#: htdocs/luci-static/resources/view/homeproxy/client.js:113 +msgid "Failed to execute \"/etc/init.d/%s %s\" action: %s" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:274 msgid "Failed to upload %s, error: %s." msgstr "上传 %s 失败,错误:%s。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1329 +#: htdocs/luci-static/resources/view/homeproxy/node.js:749 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1441 msgid "Filter keywords" msgstr "过滤关键词" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1321 +#: htdocs/luci-static/resources/view/homeproxy/node.js:739 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1434 msgid "Filter nodes" msgstr "过滤节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:673 -#: htdocs/luci-static/resources/view/homeproxy/server.js:366 +#: htdocs/luci-static/resources/view/homeproxy/node.js:670 +#: htdocs/luci-static/resources/view/homeproxy/server.js:305 msgid "Flow" msgstr "流控" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1009 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:200 msgid "Format" msgstr "格式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:781 +#: htdocs/luci-static/resources/view/homeproxy/node.js:861 msgid "GET" msgstr "GET" -#: htdocs/luci-static/resources/view/homeproxy/status.js:216 +#: htdocs/luci-static/resources/view/homeproxy/status.js:222 msgid "GFW list version" msgstr "GFW 域名列表版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:223 +#: htdocs/luci-static/resources/view/homeproxy/client.js:279 msgid "GFWList" msgstr "GFWList" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1115 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1184 msgid "Gaming mode IPv4 IP-s" msgstr "游戏模式 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1117 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1186 msgid "Gaming mode IPv6 IP-s" msgstr "游戏模式 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1120 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1189 msgid "Gaming mode MAC-s" msgstr "游戏模式 MAC 地址" -#: htdocs/luci-static/resources/view/homeproxy/server.js:188 -#: htdocs/luci-static/resources/view/homeproxy/server.js:190 -#: htdocs/luci-static/resources/view/homeproxy/server.js:325 -#: htdocs/luci-static/resources/view/homeproxy/server.js:327 -msgid "Generate" -msgstr "生成" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:279 -#: htdocs/luci-static/resources/view/homeproxy/node.js:835 +#: htdocs/luci-static/resources/view/homeproxy/client.js:338 +#: htdocs/luci-static/resources/view/homeproxy/node.js:915 msgid "Generic segmentation offload" msgstr "通用分段卸载(GSO)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:283 +#: htdocs/luci-static/resources/view/homeproxy/client.js:622 +#: htdocs/luci-static/resources/view/homeproxy/client.js:967 msgid "Global" msgstr "全局" -#: htdocs/luci-static/resources/view/homeproxy/node.js:696 +#: htdocs/luci-static/resources/view/homeproxy/node.js:693 msgid "Global padding" msgstr "全局填充" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1122 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1191 msgid "Global proxy IPv4 IP-s" msgstr "全局代理 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1125 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1194 msgid "Global proxy IPv6 IP-s" msgstr "全局代理 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1128 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1197 msgid "Global proxy MAC-s" msgstr "全局代理 MAC 地址" -#: htdocs/luci-static/resources/view/homeproxy/server.js:114 +#: htdocs/luci-static/resources/view/homeproxy/server.js:73 msgid "Global settings" msgstr "全局设置" @@ -832,7 +914,7 @@ msgstr "全局设置" msgid "Google" msgstr "谷歌" -#: htdocs/luci-static/resources/view/homeproxy/client.js:163 +#: htdocs/luci-static/resources/view/homeproxy/client.js:219 msgid "Google Public DNS (8.8.8.8)" msgstr "谷歌公共 DNS(8.8.8.8)" @@ -840,147 +922,157 @@ msgstr "谷歌公共 DNS(8.8.8.8)" msgid "Grant access to homeproxy configuration" msgstr "授予 homeproxy 访问 UCI 配置的权限" -#: htdocs/luci-static/resources/view/homeproxy/client.js:484 -#: htdocs/luci-static/resources/view/homeproxy/client.js:824 -#: htdocs/luci-static/resources/view/homeproxy/node.js:395 -#: htdocs/luci-static/resources/view/homeproxy/node.js:715 -#: htdocs/luci-static/resources/view/homeproxy/server.js:145 -#: htdocs/luci-static/resources/view/homeproxy/server.js:384 +#: htdocs/luci-static/resources/view/homeproxy/client.js:540 +#: htdocs/luci-static/resources/view/homeproxy/client.js:889 +#: htdocs/luci-static/resources/view/homeproxy/node.js:396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:795 +#: htdocs/luci-static/resources/view/homeproxy/server.js:106 +#: htdocs/luci-static/resources/view/homeproxy/server.js:323 msgid "HTTP" msgstr "HTTP" -#: htdocs/luci-static/resources/view/homeproxy/server.js:302 +#: htdocs/luci-static/resources/view/homeproxy/server.js:252 msgid "" "HTTP3 server behavior when authentication fails.
A 404 page will be " "returned if empty." msgstr "身份验证失败时的 HTTP3 服务器响应。默认返回 404 页面。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:716 -#: htdocs/luci-static/resources/view/homeproxy/server.js:385 +#: htdocs/luci-static/resources/view/homeproxy/node.js:796 +#: htdocs/luci-static/resources/view/homeproxy/server.js:324 msgid "HTTPUpgrade" msgstr "HTTPUpgrade" -#: htdocs/luci-static/resources/view/homeproxy/server.js:714 +#: htdocs/luci-static/resources/view/homeproxy/server.js:653 msgid "Handshake server address" msgstr "握手服务器地址" -#: htdocs/luci-static/resources/view/homeproxy/server.js:720 +#: htdocs/luci-static/resources/view/homeproxy/server.js:659 msgid "Handshake server port" msgstr "握手服务器端口" -#: htdocs/luci-static/resources/view/homeproxy/node.js:664 -#: htdocs/luci-static/resources/view/homeproxy/server.js:357 +#: htdocs/luci-static/resources/view/homeproxy/node.js:661 +#: htdocs/luci-static/resources/view/homeproxy/server.js:296 msgid "Heartbeat interval" msgstr "心跳间隔" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 -#: htdocs/luci-static/resources/view/homeproxy/client.js:120 -#: htdocs/luci-static/resources/view/homeproxy/status.js:224 +#: htdocs/luci-static/resources/view/homeproxy/client.js:99 +#: htdocs/luci-static/resources/view/homeproxy/client.js:101 +#: htdocs/luci-static/resources/view/homeproxy/client.js:170 +#: htdocs/luci-static/resources/view/homeproxy/status.js:230 #: root/usr/share/luci/menu.d/luci-app-homeproxy.json:3 msgid "HomeProxy" msgstr "HomeProxy" -#: htdocs/luci-static/resources/view/homeproxy/server.js:38 -#: htdocs/luci-static/resources/view/homeproxy/server.js:40 -#: htdocs/luci-static/resources/view/homeproxy/server.js:97 +#: htdocs/luci-static/resources/view/homeproxy/server.js:37 +#: htdocs/luci-static/resources/view/homeproxy/server.js:39 +#: htdocs/luci-static/resources/view/homeproxy/server.js:56 msgid "HomeProxy Server" msgstr "HomeProxy 服务端" -#: htdocs/luci-static/resources/view/homeproxy/node.js:765 -#: htdocs/luci-static/resources/view/homeproxy/node.js:770 -#: htdocs/luci-static/resources/view/homeproxy/node.js:804 -#: htdocs/luci-static/resources/view/homeproxy/server.js:418 -#: htdocs/luci-static/resources/view/homeproxy/server.js:423 -#: htdocs/luci-static/resources/view/homeproxy/server.js:454 +#: htdocs/luci-static/resources/view/homeproxy/node.js:845 +#: htdocs/luci-static/resources/view/homeproxy/node.js:850 +#: htdocs/luci-static/resources/view/homeproxy/node.js:884 +#: htdocs/luci-static/resources/view/homeproxy/server.js:357 +#: htdocs/luci-static/resources/view/homeproxy/server.js:362 +#: htdocs/luci-static/resources/view/homeproxy/server.js:393 msgid "Host" msgstr "主机名" -#: htdocs/luci-static/resources/view/homeproxy/client.js:447 -#: htdocs/luci-static/resources/view/homeproxy/client.js:779 +#: htdocs/luci-static/resources/view/homeproxy/client.js:503 +#: htdocs/luci-static/resources/view/homeproxy/client.js:844 msgid "Host fields" msgstr "主机字段" -#: htdocs/luci-static/resources/view/homeproxy/node.js:605 +#: htdocs/luci-static/resources/view/homeproxy/node.js:602 msgid "Host key" msgstr "主机密钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:610 +#: htdocs/luci-static/resources/view/homeproxy/node.js:607 msgid "Host key algorithms" msgstr "主机密钥算法" -#: htdocs/luci-static/resources/view/homeproxy/server.js:344 +#: htdocs/luci-static/resources/view/homeproxy/server.js:283 msgid "" "How long the server should wait for the client to send the authentication " "command (in seconds)." msgstr "服务器等待客户端发送认证命令的时间(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:397 -#: htdocs/luci-static/resources/view/homeproxy/server.js:147 +#: htdocs/luci-static/resources/view/homeproxy/node.js:398 +#: htdocs/luci-static/resources/view/homeproxy/server.js:108 msgid "Hysteria" msgstr "Hysteria" -#: htdocs/luci-static/resources/view/homeproxy/node.js:398 -#: htdocs/luci-static/resources/view/homeproxy/server.js:148 +#: htdocs/luci-static/resources/view/homeproxy/node.js:399 +#: htdocs/luci-static/resources/view/homeproxy/server.js:109 msgid "Hysteria2" msgstr "Hysteria2" -#: htdocs/luci-static/resources/view/homeproxy/client.js:522 -#: htdocs/luci-static/resources/view/homeproxy/client.js:867 +#: htdocs/luci-static/resources/view/homeproxy/client.js:577 +#: htdocs/luci-static/resources/view/homeproxy/client.js:932 msgid "IP CIDR" msgstr "IP CIDR" -#: htdocs/luci-static/resources/view/homeproxy/client.js:475 -#: htdocs/luci-static/resources/view/homeproxy/client.js:807 +#: htdocs/luci-static/resources/view/homeproxy/client.js:531 +#: htdocs/luci-static/resources/view/homeproxy/client.js:872 msgid "IP version" msgstr "IP 版本" -#: htdocs/luci-static/resources/view/homeproxy/client.js:477 -#: htdocs/luci-static/resources/view/homeproxy/client.js:808 +#: htdocs/luci-static/resources/view/homeproxy/client.js:533 +#: htdocs/luci-static/resources/view/homeproxy/client.js:873 msgid "IPv4" msgstr "IPv4" -#: htdocs/luci-static/resources/homeproxy.js:20 +#: htdocs/luci-static/resources/homeproxy.js:21 msgid "IPv4 only" msgstr "仅 IPv4" -#: htdocs/luci-static/resources/view/homeproxy/client.js:478 -#: htdocs/luci-static/resources/view/homeproxy/client.js:809 +#: htdocs/luci-static/resources/view/homeproxy/client.js:534 +#: htdocs/luci-static/resources/view/homeproxy/client.js:874 msgid "IPv6" msgstr "IPv6" -#: htdocs/luci-static/resources/homeproxy.js:21 +#: htdocs/luci-static/resources/homeproxy.js:22 msgid "IPv6 only" msgstr "仅 IPv6" -#: htdocs/luci-static/resources/view/homeproxy/client.js:269 +#: htdocs/luci-static/resources/view/homeproxy/client.js:328 msgid "IPv6 support" msgstr "IPv6 支持" -#: htdocs/luci-static/resources/view/homeproxy/node.js:786 -#: htdocs/luci-static/resources/view/homeproxy/server.js:437 +#: htdocs/luci-static/resources/view/homeproxy/node.js:776 +#: htdocs/luci-static/resources/view/homeproxy/node.js:866 +#: htdocs/luci-static/resources/view/homeproxy/server.js:376 msgid "Idle timeout" msgstr "空闲超时" -#: htdocs/luci-static/resources/view/homeproxy/node.js:757 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1017 +msgid "If Any is selected, uncheck others" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:837 msgid "" "If enabled, the client transport sends keepalive pings even with no active " "connections." msgstr "如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:328 -#: htdocs/luci-static/resources/view/homeproxy/server.js:794 +#: htdocs/luci-static/resources/view/homeproxy/server.js:726 msgid "" "If set, the requested domain name will be resolved to IP before routing." msgstr "如果设置,请求的域名将在路由前被解析为 IP 地址。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:388 +#: htdocs/luci-static/resources/view/homeproxy/client.js:442 msgid "" -"If set, the server domain name will be resolved to IP before connecting.
" -msgstr "如果设置,服务器域名将在连接前被解析为 IP。" +"If set, the server domain name will be resolved to IP before connecting.
dns.strategy will be used if empty." +msgstr "" +"如果设置,服务器域名将在连接前被解析为 IP。
默认使用 dns.strategy。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:742 -#: htdocs/luci-static/resources/view/homeproxy/server.js:406 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +msgid "If the selected dashboard is " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:822 +#: htdocs/luci-static/resources/view/homeproxy/server.js:345 msgid "" "If the transport doesn't see any activity after a duration of this time (in " "seconds), it pings the client to check if the connection is still active." @@ -988,80 +1080,98 @@ msgstr "" "如果传输在此时间段(单位:秒)后没有看到任何活动,它会向客户端发送 ping 请求" "以检查连接是否仍然活动。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1016 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1096 msgid "" "If you have the root certificate, use this option instead of allowing " "insecure." msgstr "如果你拥有根证书,使用此选项而不是允许不安全连接。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:295 +#: htdocs/luci-static/resources/view/homeproxy/server.js:245 msgid "Ignore client bandwidth" msgstr "忽略客户端带宽" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1238 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1315 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:144 msgid "Import" msgstr "导入" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1185 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1264 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1266 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:98 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:173 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:175 +msgid "Import rule-set links" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1262 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1364 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1366 msgid "Import share links" msgstr "导入分享链接" -#: htdocs/luci-static/resources/view/homeproxy/client.js:314 -#: htdocs/luci-static/resources/view/homeproxy/server.js:783 -msgid "In seconds. 300 is used by default." -msgstr "单位:秒。默认使用 300。" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:647 +#: htdocs/luci-static/resources/view/homeproxy/client.js:708 msgid "Independent cache per server" msgstr "独立缓存" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1074 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1104 +msgid "Installed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1143 msgid "Interface Control" msgstr "接口控制" -#: htdocs/luci-static/resources/view/homeproxy/node.js:665 -#: htdocs/luci-static/resources/view/homeproxy/server.js:358 +#: htdocs/luci-static/resources/view/homeproxy/node.js:782 +msgid "Interrupt existing connections" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:783 +msgid "Interrupt existing connections when the selected outbound has changed." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:763 +msgid "Interval" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:662 +#: htdocs/luci-static/resources/view/homeproxy/server.js:297 msgid "" "Interval for sending heartbeat packets for keeping the connection alive (in " "seconds)." msgstr "发送心跳包以保持连接存活的时间间隔(单位:秒)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:587 -#: htdocs/luci-static/resources/view/homeproxy/client.js:920 +#: htdocs/luci-static/resources/view/homeproxy/client.js:648 +#: htdocs/luci-static/resources/view/homeproxy/client.js:993 msgid "Invert" msgstr "反转" -#: htdocs/luci-static/resources/view/homeproxy/client.js:588 -#: htdocs/luci-static/resources/view/homeproxy/client.js:921 +#: htdocs/luci-static/resources/view/homeproxy/client.js:649 +#: htdocs/luci-static/resources/view/homeproxy/client.js:994 msgid "Invert match result." msgstr "反转匹配结果" -#: htdocs/luci-static/resources/view/homeproxy/client.js:159 +#: htdocs/luci-static/resources/view/homeproxy/client.js:215 msgid "It MUST support TCP query." msgstr "它必须支持 TCP 查询。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:745 +#: htdocs/luci-static/resources/view/homeproxy/server.js:684 msgid "Key path" msgstr "证书路径" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1088 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1157 msgid "LAN IP Policy" msgstr "LAN IP 策略" -#: htdocs/luci-static/resources/view/homeproxy/client.js:370 -#: htdocs/luci-static/resources/view/homeproxy/client.js:452 -#: htdocs/luci-static/resources/view/homeproxy/client.js:683 -#: htdocs/luci-static/resources/view/homeproxy/client.js:784 -#: htdocs/luci-static/resources/view/homeproxy/client.js:993 -#: htdocs/luci-static/resources/view/homeproxy/node.js:388 -#: htdocs/luci-static/resources/view/homeproxy/server.js:133 +#: htdocs/luci-static/resources/view/homeproxy/client.js:424 +#: htdocs/luci-static/resources/view/homeproxy/client.js:508 +#: htdocs/luci-static/resources/view/homeproxy/client.js:746 +#: htdocs/luci-static/resources/view/homeproxy/client.js:849 +#: htdocs/luci-static/resources/view/homeproxy/node.js:389 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:184 +#: htdocs/luci-static/resources/view/homeproxy/server.js:94 msgid "Label" msgstr "标签" -#: htdocs/luci-static/resources/view/homeproxy/node.js:680 -#: htdocs/luci-static/resources/view/homeproxy/server.js:373 +#: htdocs/luci-static/resources/view/homeproxy/node.js:677 +#: htdocs/luci-static/resources/view/homeproxy/server.js:312 msgid "" "Legacy protocol support (VMess MD5 Authentication) is provided for " "compatibility purposes only, use of alterId > 1 is not recommended." @@ -1069,218 +1179,239 @@ msgstr "" "提供旧协议支持(VMess MD5 身份验证)仅出于兼容性目的,不建议使用 alterId > " "1。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:303 +#: htdocs/luci-static/resources/view/homeproxy/client.js:362 msgid "Less compatibility and sometimes better performance." msgstr "有时性能更好。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:613 +#: htdocs/luci-static/resources/view/homeproxy/server.js:552 msgid "Let's Encrypt" msgstr "Let's Encrypt" -#: htdocs/luci-static/resources/view/homeproxy/node.js:842 +#: htdocs/luci-static/resources/view/homeproxy/node.js:922 msgid "" "List of IP (v4 or v6) addresses prefixes to be assigned to the interface." msgstr "分配给接口的 IP(v4 或 v6)地址前缀列表。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:978 -#: htdocs/luci-static/resources/view/homeproxy/server.js:549 +#: htdocs/luci-static/resources/view/homeproxy/node.js:720 +msgid "List of outbound tags." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:709 +msgid "List of subscription groups." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1058 +#: htdocs/luci-static/resources/view/homeproxy/server.js:488 msgid "List of supported application level protocols, in order of preference." msgstr "支持的应用层协议协商列表,按顺序排列。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:160 +#: htdocs/luci-static/resources/view/homeproxy/server.js:121 msgid "Listen address" msgstr "监听地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1076 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1145 msgid "Listen interfaces" msgstr "监听接口" -#: htdocs/luci-static/resources/view/homeproxy/server.js:165 +#: htdocs/luci-static/resources/view/homeproxy/server.js:126 msgid "Listen port" msgstr "监听端口" -#: htdocs/luci-static/resources/view/homeproxy/status.js:127 +#: htdocs/luci-static/resources/view/homeproxy/status.js:126 msgid "Loading" msgstr "加载中" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1004 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:195 msgid "Local" msgstr "本地" -#: htdocs/luci-static/resources/view/homeproxy/node.js:841 +#: htdocs/luci-static/resources/view/homeproxy/node.js:921 msgid "Local address" msgstr "本地地址" -#: htdocs/luci-static/resources/view/homeproxy/status.js:144 +#: htdocs/luci-static/resources/view/homeproxy/status.js:143 msgid "Log file does not exist." msgstr "日志文件不存在。" -#: htdocs/luci-static/resources/view/homeproxy/status.js:137 +#: htdocs/luci-static/resources/view/homeproxy/status.js:136 msgid "Log is empty." msgstr "日志为空。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:875 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1078 +msgid "Log level" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:237 +msgid "Lowercase only" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:955 msgid "MTU" msgstr "MTU" -#: htdocs/luci-static/resources/view/homeproxy/client.js:149 +#: htdocs/luci-static/resources/view/homeproxy/client.js:205 msgid "Main UDP node" msgstr "主 UDP 节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:141 +#: htdocs/luci-static/resources/view/homeproxy/client.js:197 msgid "Main node" msgstr "主节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:916 +#: htdocs/luci-static/resources/view/homeproxy/client.js:989 msgid "Make ipcidr in rule sets match the source IP." msgstr "使规则集中的 ipcidr 用于匹配源 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:582 +#: htdocs/luci-static/resources/view/homeproxy/client.js:644 msgid "Make IP CIDR in rule set used to match the source IP." msgstr "使规则集中的 IP CIDR 用于匹配源 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:648 +#: htdocs/luci-static/resources/view/homeproxy/client.js:709 msgid "" "Make each DNS server's cache independent for special purposes. If enabled, " "will slightly degrade performance." msgstr "独立缓存每个 DNS 服务器的结果以供特殊用途。启用后会略微降低性能。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:301 +#: htdocs/luci-static/resources/view/homeproxy/server.js:251 msgid "Masquerade" msgstr "伪装" -#: htdocs/luci-static/resources/view/homeproxy/client.js:926 +#: htdocs/luci-static/resources/view/homeproxy/client.js:999 msgid "Match .outbounds[].server domains." msgstr "匹配 .outbounds[].server 域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:868 +#: htdocs/luci-static/resources/view/homeproxy/client.js:933 msgid "Match IP CIDR with query response." msgstr "使用查询响应匹配 IP CIDR。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:523 +#: htdocs/luci-static/resources/view/homeproxy/client.js:578 msgid "Match IP CIDR." msgstr "匹配 IP CIDR。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:500 -#: htdocs/luci-static/resources/view/homeproxy/client.js:836 +#: htdocs/luci-static/resources/view/homeproxy/client.js:620 +#: htdocs/luci-static/resources/view/homeproxy/client.js:965 +msgid "Match clash mode." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:556 +#: htdocs/luci-static/resources/view/homeproxy/client.js:901 msgid "Match domain suffix." msgstr "匹配域名后缀。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:504 -#: htdocs/luci-static/resources/view/homeproxy/client.js:840 +#: htdocs/luci-static/resources/view/homeproxy/client.js:560 +#: htdocs/luci-static/resources/view/homeproxy/client.js:905 msgid "Match domain using keyword." msgstr "使用关键词匹配域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:508 -#: htdocs/luci-static/resources/view/homeproxy/client.js:844 +#: htdocs/luci-static/resources/view/homeproxy/client.js:564 +#: htdocs/luci-static/resources/view/homeproxy/client.js:909 msgid "Match domain using regular expression." msgstr "使用正则表达式匹配域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:495 -#: htdocs/luci-static/resources/view/homeproxy/client.js:831 +#: htdocs/luci-static/resources/view/homeproxy/client.js:551 +#: htdocs/luci-static/resources/view/homeproxy/client.js:896 msgid "Match full domain." msgstr "匹配完整域名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:549 -#: htdocs/luci-static/resources/view/homeproxy/client.js:853 +#: htdocs/luci-static/resources/view/homeproxy/client.js:603 +#: htdocs/luci-static/resources/view/homeproxy/client.js:918 msgid "Match port range. Format as START:/:END/START:END." msgstr "匹配端口范围。格式为 START:/:END/START:END。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:544 -#: htdocs/luci-static/resources/view/homeproxy/client.js:848 +#: htdocs/luci-static/resources/view/homeproxy/client.js:598 +#: htdocs/luci-static/resources/view/homeproxy/client.js:913 msgid "Match port." msgstr "匹配端口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:873 +#: htdocs/luci-static/resources/view/homeproxy/client.js:938 msgid "Match private IP with query response." msgstr "使用查询响应匹配私有 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:528 +#: htdocs/luci-static/resources/view/homeproxy/client.js:583 msgid "Match private IP." msgstr "匹配私有 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:517 -#: htdocs/luci-static/resources/view/homeproxy/client.js:863 +#: htdocs/luci-static/resources/view/homeproxy/client.js:573 +#: htdocs/luci-static/resources/view/homeproxy/client.js:928 msgid "Match private source IP." msgstr "匹配私有源 IP。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:554 -#: htdocs/luci-static/resources/view/homeproxy/client.js:888 +#: htdocs/luci-static/resources/view/homeproxy/client.js:608 +#: htdocs/luci-static/resources/view/homeproxy/client.js:953 msgid "Match process name." msgstr "匹配进程名。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:558 -#: htdocs/luci-static/resources/view/homeproxy/client.js:892 +#: htdocs/luci-static/resources/view/homeproxy/client.js:612 +#: htdocs/luci-static/resources/view/homeproxy/client.js:957 msgid "Match process path." msgstr "匹配进程路径。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:814 +#: htdocs/luci-static/resources/view/homeproxy/client.js:879 msgid "Match query type." msgstr "匹配请求类型。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:566 -#: htdocs/luci-static/resources/view/homeproxy/client.js:900 +#: htdocs/luci-static/resources/view/homeproxy/client.js:628 +#: htdocs/luci-static/resources/view/homeproxy/client.js:973 msgid "Match rule set." msgstr "匹配规则集。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:512 -#: htdocs/luci-static/resources/view/homeproxy/client.js:858 +#: htdocs/luci-static/resources/view/homeproxy/client.js:568 +#: htdocs/luci-static/resources/view/homeproxy/client.js:923 msgid "Match source IP CIDR." msgstr "匹配源 IP CIDR。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:581 +#: htdocs/luci-static/resources/view/homeproxy/client.js:643 msgid "Match source IP via rule set" msgstr "通过规则集匹配源 IP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:539 -#: htdocs/luci-static/resources/view/homeproxy/client.js:883 +#: htdocs/luci-static/resources/view/homeproxy/client.js:593 +#: htdocs/luci-static/resources/view/homeproxy/client.js:948 msgid "Match source port range. Format as START:/:END/START:END." msgstr "匹配源端口范围。格式为 START:/:END/START:END。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:534 -#: htdocs/luci-static/resources/view/homeproxy/client.js:878 +#: htdocs/luci-static/resources/view/homeproxy/client.js:588 +#: htdocs/luci-static/resources/view/homeproxy/client.js:943 msgid "Match source port." msgstr "匹配源端口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:562 -#: htdocs/luci-static/resources/view/homeproxy/client.js:896 +#: htdocs/luci-static/resources/view/homeproxy/client.js:616 +#: htdocs/luci-static/resources/view/homeproxy/client.js:961 msgid "Match user name." msgstr "匹配用户名。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:514 -#: htdocs/luci-static/resources/view/homeproxy/server.js:231 +#: htdocs/luci-static/resources/view/homeproxy/node.js:511 +#: htdocs/luci-static/resources/view/homeproxy/server.js:181 msgid "Max download speed" msgstr "最大下载速度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:515 -#: htdocs/luci-static/resources/view/homeproxy/server.js:232 +#: htdocs/luci-static/resources/view/homeproxy/node.js:512 +#: htdocs/luci-static/resources/view/homeproxy/server.js:182 msgid "Max download speed in Mbps." msgstr "最大下载速度(Mbps)。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:709 +#: htdocs/luci-static/resources/view/homeproxy/server.js:648 msgid "Max time difference" msgstr "最大时间差" -#: htdocs/luci-static/resources/view/homeproxy/node.js:521 -#: htdocs/luci-static/resources/view/homeproxy/server.js:238 +#: htdocs/luci-static/resources/view/homeproxy/node.js:518 +#: htdocs/luci-static/resources/view/homeproxy/server.js:188 msgid "Max upload speed" msgstr "最大上传速度" -#: htdocs/luci-static/resources/view/homeproxy/node.js:522 -#: htdocs/luci-static/resources/view/homeproxy/server.js:239 +#: htdocs/luci-static/resources/view/homeproxy/node.js:519 +#: htdocs/luci-static/resources/view/homeproxy/server.js:189 msgid "Max upload speed in Mbps." msgstr "最大上传速度(Mbps)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:999 -#: htdocs/luci-static/resources/view/homeproxy/server.js:561 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1079 +#: htdocs/luci-static/resources/view/homeproxy/server.js:500 msgid "Maximum TLS version" msgstr "最大 TLS 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:902 +#: htdocs/luci-static/resources/view/homeproxy/node.js:982 msgid "Maximum connections" msgstr "最大连接数" -#: htdocs/luci-static/resources/view/homeproxy/node.js:914 +#: htdocs/luci-static/resources/view/homeproxy/node.js:994 msgid "" "Maximum multiplexed streams in a connection before opening a new connection." "
Conflict with Maximum connections and Minimum " @@ -1289,109 +1420,117 @@ msgstr "" "在打开新连接之前,连接中的最大多路复用流数量。与 Maximum connectionsMinimum streams 冲突。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:913 +#: htdocs/luci-static/resources/view/homeproxy/node.js:993 msgid "Maximum streams" msgstr "最大流数量" -#: htdocs/luci-static/resources/view/homeproxy/node.js:780 -#: htdocs/luci-static/resources/view/homeproxy/server.js:433 +#: htdocs/luci-static/resources/view/homeproxy/node.js:860 +#: htdocs/luci-static/resources/view/homeproxy/server.js:372 msgid "Method" msgstr "方式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:991 -#: htdocs/luci-static/resources/view/homeproxy/server.js:553 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1071 +#: htdocs/luci-static/resources/view/homeproxy/server.js:492 msgid "Minimum TLS version" msgstr "最低 TLS 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:908 +#: htdocs/luci-static/resources/view/homeproxy/node.js:988 msgid "" "Minimum multiplexed streams in a connection before opening a new connection." msgstr "在打开新连接之前,连接中的最小多路复用流数量。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:907 +#: htdocs/luci-static/resources/view/homeproxy/node.js:987 msgid "Minimum streams" msgstr "最小流数量" -#: htdocs/luci-static/resources/view/homeproxy/client.js:288 +#: htdocs/luci-static/resources/view/homeproxy/client.js:347 msgid "Mixed" msgstr "混合" -#: htdocs/luci-static/resources/view/homeproxy/client.js:299 +#: htdocs/luci-static/resources/view/homeproxy/client.js:358 msgid "Mixed system TCP stack and gVisor UDP stack." msgstr "混合系统 TCP 栈和 gVisor UDP 栈。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:462 -#: htdocs/luci-static/resources/view/homeproxy/client.js:794 +#: htdocs/luci-static/resources/view/homeproxy/client.js:518 +#: htdocs/luci-static/resources/view/homeproxy/client.js:859 msgid "Mode" msgstr "模式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1114 -#: htdocs/luci-static/resources/view/homeproxy/server.js:771 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1195 +#: htdocs/luci-static/resources/view/homeproxy/server.js:710 msgid "MultiPath TCP" msgstr "多路径 TCP(MPTCP)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:884 -#: htdocs/luci-static/resources/view/homeproxy/server.js:481 +#: htdocs/luci-static/resources/view/homeproxy/node.js:964 +#: htdocs/luci-static/resources/view/homeproxy/server.js:420 msgid "Multiplex" msgstr "多路复用" -#: htdocs/luci-static/resources/view/homeproxy/node.js:893 +#: htdocs/luci-static/resources/view/homeproxy/node.js:973 msgid "Multiplex protocol." msgstr "多路复用协议。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:57 -#: htdocs/luci-static/resources/view/homeproxy/server.js:40 +#: htdocs/luci-static/resources/view/homeproxy/client.js:101 +#: htdocs/luci-static/resources/view/homeproxy/server.js:39 msgid "NOT RUNNING" msgstr "未运行" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1348 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1460 msgid "NOTE: Save current settings before updating subscriptions." msgstr "注意:更新订阅前先保存当前配置。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:646 +#: htdocs/luci-static/resources/view/homeproxy/node.js:643 msgid "Native" msgstr "原生" -#: htdocs/luci-static/resources/view/homeproxy/server.js:149 +#: htdocs/luci-static/resources/view/homeproxy/server.js:110 msgid "NaïveProxy" msgstr "NaïveProxy" -#: htdocs/luci-static/resources/view/homeproxy/client.js:489 -#: htdocs/luci-static/resources/view/homeproxy/client.js:817 -#: htdocs/luci-static/resources/view/homeproxy/server.js:799 +#: htdocs/luci-static/resources/view/homeproxy/client.js:545 +#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/server.js:731 msgid "Network" msgstr "网络" -#: htdocs/luci-static/resources/view/homeproxy/node.js:636 +#: htdocs/luci-static/resources/view/homeproxy/node.js:633 msgid "New Reno" msgstr "New Reno" -#: htdocs/luci-static/resources/view/homeproxy/node.js:712 -#: htdocs/luci-static/resources/view/homeproxy/node.js:729 -#: htdocs/luci-static/resources/view/homeproxy/server.js:381 -#: htdocs/luci-static/resources/view/homeproxy/server.js:398 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1068 +msgid "Nginx Support" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:792 +#: htdocs/luci-static/resources/view/homeproxy/node.js:809 +#: htdocs/luci-static/resources/view/homeproxy/server.js:320 +#: htdocs/luci-static/resources/view/homeproxy/server.js:337 msgid "No TCP transport, plain HTTP is merged into the HTTP transport." msgstr "无 TCP 传输层, 纯 HTTP 已合并到 HTTP 传输层。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:727 -#: htdocs/luci-static/resources/view/homeproxy/server.js:396 +#: htdocs/luci-static/resources/view/homeproxy/node.js:807 +#: htdocs/luci-static/resources/view/homeproxy/server.js:335 msgid "No additional encryption support: It's basically duplicate encryption." msgstr "无额外加密支持:它基本上是重复加密。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1364 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1476 msgid "No subscription available" msgstr "无可用订阅" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1389 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1501 msgid "No subscription node" msgstr "无订阅节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1224 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:130 +msgid "No valid rule-set link found." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1301 msgid "No valid share link found." msgstr "找不到有效分享链接。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:380 -#: htdocs/luci-static/resources/view/homeproxy/node.js:363 +#: htdocs/luci-static/resources/view/homeproxy/client.js:434 +#: htdocs/luci-static/resources/view/homeproxy/node.js:364 msgid "Node" msgstr "节点" @@ -1399,347 +1538,367 @@ msgstr "节点" msgid "Node Settings" msgstr "节点设置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1170 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1250 msgid "Nodes" msgstr "节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:703 -#: htdocs/luci-static/resources/view/homeproxy/node.js:674 -#: htdocs/luci-static/resources/view/homeproxy/node.js:713 -#: htdocs/luci-static/resources/view/homeproxy/server.js:367 -#: htdocs/luci-static/resources/view/homeproxy/server.js:382 +#: htdocs/luci-static/resources/view/homeproxy/client.js:621 +#: htdocs/luci-static/resources/view/homeproxy/client.js:766 +#: htdocs/luci-static/resources/view/homeproxy/client.js:966 +#: htdocs/luci-static/resources/view/homeproxy/node.js:671 +#: htdocs/luci-static/resources/view/homeproxy/node.js:793 +#: htdocs/luci-static/resources/view/homeproxy/server.js:306 +#: htdocs/luci-static/resources/view/homeproxy/server.js:321 msgid "None" msgstr "无" -#: htdocs/luci-static/resources/view/homeproxy/node.js:509 -#: htdocs/luci-static/resources/view/homeproxy/server.js:263 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1089 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1104 +msgid "Not Installed" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:506 +#: htdocs/luci-static/resources/view/homeproxy/server.js:213 msgid "Obfuscate password" msgstr "混淆密码" -#: htdocs/luci-static/resources/view/homeproxy/node.js:503 -#: htdocs/luci-static/resources/view/homeproxy/server.js:257 +#: htdocs/luci-static/resources/view/homeproxy/node.js:500 +#: htdocs/luci-static/resources/view/homeproxy/server.js:207 msgid "Obfuscate type" msgstr "混淆类型" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1077 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1146 msgid "Only process traffic from specific interfaces. Leave empty for all." msgstr "只处理来自指定接口的流量。留空表示全部。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:225 +#: htdocs/luci-static/resources/view/homeproxy/client.js:281 msgid "Only proxy mainland China" msgstr "仅代理中国大陆" -#: htdocs/luci-static/resources/view/homeproxy/client.js:446 -#: htdocs/luci-static/resources/view/homeproxy/client.js:778 +#: htdocs/luci-static/resources/view/homeproxy/client.js:97 +msgid "Open Clash Dashboard" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:502 +#: htdocs/luci-static/resources/view/homeproxy/client.js:843 msgid "Other fields" msgstr "其他字段" -#: htdocs/luci-static/resources/view/homeproxy/client.js:400 -#: htdocs/luci-static/resources/view/homeproxy/client.js:592 -#: htdocs/luci-static/resources/view/homeproxy/client.js:740 -#: htdocs/luci-static/resources/view/homeproxy/client.js:925 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1044 +#: htdocs/luci-static/resources/view/homeproxy/client.js:454 +#: htdocs/luci-static/resources/view/homeproxy/client.js:653 +#: htdocs/luci-static/resources/view/homeproxy/client.js:803 +#: htdocs/luci-static/resources/view/homeproxy/client.js:998 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:235 msgid "Outbound" msgstr "出站" -#: htdocs/luci-static/resources/view/homeproxy/client.js:381 +#: htdocs/luci-static/resources/view/homeproxy/client.js:435 msgid "Outbound node" msgstr "出站节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:461 +#: htdocs/luci-static/resources/view/homeproxy/node.js:719 +msgid "Outbounds" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:464 msgid "Override address" msgstr "覆盖地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:332 -#: htdocs/luci-static/resources/view/homeproxy/server.js:789 +#: htdocs/luci-static/resources/view/homeproxy/client.js:377 +#: htdocs/luci-static/resources/view/homeproxy/server.js:721 msgid "Override destination" msgstr "覆盖目标地址" -#: htdocs/luci-static/resources/view/homeproxy/node.js:466 +#: htdocs/luci-static/resources/view/homeproxy/node.js:470 msgid "Override port" msgstr "覆盖端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:333 -#: htdocs/luci-static/resources/view/homeproxy/server.js:790 +#: htdocs/luci-static/resources/view/homeproxy/client.js:378 +#: htdocs/luci-static/resources/view/homeproxy/server.js:722 msgid "Override the connection destination address with the sniffed domain." msgstr "使用嗅探到的域名覆盖连接目标。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:462 +#: htdocs/luci-static/resources/view/homeproxy/node.js:465 msgid "Override the connection destination address." msgstr "覆盖目标连接地址。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:467 +#: htdocs/luci-static/resources/view/homeproxy/node.js:471 msgid "Override the connection destination port." msgstr "覆盖目标连接端口。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:782 +#: htdocs/luci-static/resources/view/homeproxy/node.js:862 msgid "PUT" msgstr "PUT" -#: htdocs/luci-static/resources/view/homeproxy/node.js:825 +#: htdocs/luci-static/resources/view/homeproxy/node.js:905 msgid "Packet encoding" msgstr "数据包编码" -#: htdocs/luci-static/resources/view/homeproxy/node.js:429 -#: htdocs/luci-static/resources/view/homeproxy/server.js:176 +#: htdocs/luci-static/resources/view/homeproxy/node.js:432 +#: htdocs/luci-static/resources/view/homeproxy/server.js:137 msgid "Password" msgstr "密码" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1015 -#: htdocs/luci-static/resources/view/homeproxy/node.js:775 -#: htdocs/luci-static/resources/view/homeproxy/node.js:808 -#: htdocs/luci-static/resources/view/homeproxy/server.js:428 -#: htdocs/luci-static/resources/view/homeproxy/server.js:458 +#: htdocs/luci-static/resources/view/homeproxy/node.js:855 +#: htdocs/luci-static/resources/view/homeproxy/node.js:888 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:206 +#: htdocs/luci-static/resources/view/homeproxy/server.js:367 +#: htdocs/luci-static/resources/view/homeproxy/server.js:397 msgid "Path" msgstr "路径" -#: htdocs/luci-static/resources/view/homeproxy/node.js:856 +#: htdocs/luci-static/resources/view/homeproxy/node.js:936 msgid "Peer pubkic key" msgstr "对端公钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:307 +#: htdocs/luci-static/resources/view/homeproxy/client.js:366 msgid "" "Performance may degrade slightly, so it is not recommended to enable on when " "it is not needed." msgstr "性能可能会略有下降,建议仅在需要时开启。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:794 -#: htdocs/luci-static/resources/view/homeproxy/server.js:445 +#: htdocs/luci-static/resources/view/homeproxy/node.js:874 +#: htdocs/luci-static/resources/view/homeproxy/server.js:384 msgid "Ping timeout" msgstr "Ping 超时" -#: htdocs/luci-static/resources/view/homeproxy/node.js:566 +#: htdocs/luci-static/resources/view/homeproxy/node.js:563 msgid "Plugin" msgstr "插件" -#: htdocs/luci-static/resources/view/homeproxy/node.js:573 +#: htdocs/luci-static/resources/view/homeproxy/node.js:570 msgid "Plugin opts" msgstr "插件参数" -#: htdocs/luci-static/resources/view/homeproxy/client.js:543 -#: htdocs/luci-static/resources/view/homeproxy/client.js:847 -#: htdocs/luci-static/resources/view/homeproxy/node.js:418 +#: htdocs/luci-static/resources/view/homeproxy/client.js:597 +#: htdocs/luci-static/resources/view/homeproxy/client.js:912 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1125 +#: htdocs/luci-static/resources/view/homeproxy/node.js:421 msgid "Port" msgstr "端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:248 +#: htdocs/luci-static/resources/view/homeproxy/client.js:307 msgid "Port %s alrealy exists!" msgstr "端口 %s 已存在!" -#: htdocs/luci-static/resources/view/homeproxy/client.js:448 -#: htdocs/luci-static/resources/view/homeproxy/client.js:780 +#: htdocs/luci-static/resources/view/homeproxy/client.js:504 +#: htdocs/luci-static/resources/view/homeproxy/client.js:845 msgid "Port fields" msgstr "端口字段" -#: htdocs/luci-static/resources/view/homeproxy/client.js:548 -#: htdocs/luci-static/resources/view/homeproxy/client.js:852 +#: htdocs/luci-static/resources/view/homeproxy/client.js:602 +#: htdocs/luci-static/resources/view/homeproxy/client.js:917 msgid "Port range" msgstr "端口范围" -#: htdocs/luci-static/resources/view/homeproxy/node.js:863 +#: htdocs/luci-static/resources/view/homeproxy/node.js:943 msgid "Pre-shared key" msgstr "预共享密钥" -#: htdocs/luci-static/resources/homeproxy.js:18 +#: htdocs/luci-static/resources/homeproxy.js:19 msgid "Prefer IPv4" msgstr "优先 IPv4" -#: htdocs/luci-static/resources/homeproxy.js:19 +#: htdocs/luci-static/resources/homeproxy.js:20 msgid "Prefer IPv6" msgstr "优先 IPv6" -#: htdocs/luci-static/resources/view/homeproxy/client.js:527 -#: htdocs/luci-static/resources/view/homeproxy/client.js:872 +#: htdocs/luci-static/resources/view/homeproxy/client.js:582 +#: htdocs/luci-static/resources/view/homeproxy/client.js:937 msgid "Private IP" msgstr "私有 IP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:614 -#: htdocs/luci-static/resources/view/homeproxy/node.js:848 +#: htdocs/luci-static/resources/view/homeproxy/node.js:611 +#: htdocs/luci-static/resources/view/homeproxy/node.js:928 msgid "Private key" msgstr "私钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:619 +#: htdocs/luci-static/resources/view/homeproxy/node.js:616 msgid "Private key passphrase" msgstr "私钥指纹" -#: htdocs/luci-static/resources/view/homeproxy/client.js:516 -#: htdocs/luci-static/resources/view/homeproxy/client.js:862 +#: htdocs/luci-static/resources/view/homeproxy/client.js:572 +#: htdocs/luci-static/resources/view/homeproxy/client.js:927 msgid "Private source IP" msgstr "私有源 IP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:553 -#: htdocs/luci-static/resources/view/homeproxy/client.js:887 +#: htdocs/luci-static/resources/view/homeproxy/client.js:607 +#: htdocs/luci-static/resources/view/homeproxy/client.js:952 msgid "Process name" msgstr "进程名" -#: htdocs/luci-static/resources/view/homeproxy/client.js:557 -#: htdocs/luci-static/resources/view/homeproxy/client.js:891 +#: htdocs/luci-static/resources/view/homeproxy/client.js:611 +#: htdocs/luci-static/resources/view/homeproxy/client.js:956 msgid "Process path" msgstr "进程路径" -#: htdocs/luci-static/resources/view/homeproxy/client.js:482 -#: htdocs/luci-static/resources/view/homeproxy/client.js:822 -#: htdocs/luci-static/resources/view/homeproxy/node.js:480 -#: htdocs/luci-static/resources/view/homeproxy/node.js:892 -#: htdocs/luci-static/resources/view/homeproxy/server.js:220 +#: htdocs/luci-static/resources/view/homeproxy/client.js:538 +#: htdocs/luci-static/resources/view/homeproxy/client.js:887 +#: htdocs/luci-static/resources/view/homeproxy/node.js:477 +#: htdocs/luci-static/resources/view/homeproxy/node.js:972 +#: htdocs/luci-static/resources/view/homeproxy/server.js:170 msgid "Protocol" msgstr "协议" -#: htdocs/luci-static/resources/view/homeproxy/node.js:704 +#: htdocs/luci-static/resources/view/homeproxy/node.js:701 msgid "Protocol parameter. Enable length block encryption." msgstr "协议参数。启用长度块加密。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:697 +#: htdocs/luci-static/resources/view/homeproxy/node.js:694 msgid "" "Protocol parameter. Will waste traffic randomly if enabled (enabled by " "default in v2ray and cannot be disabled)." msgstr "协议参数。 如启用会随机浪费流量(在 v2ray 中默认启用并且无法禁用)。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1151 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1220 msgid "Proxy Domain List" msgstr "代理域名列表" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1106 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1135 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1175 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1204 msgid "Proxy IPv4 IP-s" msgstr "代理 IPv4 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1109 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1138 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1178 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1207 msgid "Proxy IPv6 IP-s" msgstr "代理 IPv6 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1112 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1181 msgid "Proxy MAC-s" msgstr "代理 MAC 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1093 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1162 msgid "Proxy all except listed" msgstr "仅允许列表外" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1159 msgid "Proxy filter mode" msgstr "代理过滤模式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1092 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1161 msgid "Proxy listed only" msgstr "仅允许列表内" -#: htdocs/luci-static/resources/view/homeproxy/client.js:256 +#: htdocs/luci-static/resources/view/homeproxy/client.js:315 msgid "Proxy mode" msgstr "代理模式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:471 -msgid "Proxy protocol" -msgstr "代理协议" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:486 -#: htdocs/luci-static/resources/view/homeproxy/client.js:826 -#: htdocs/luci-static/resources/view/homeproxy/node.js:647 -#: htdocs/luci-static/resources/view/homeproxy/node.js:717 -#: htdocs/luci-static/resources/view/homeproxy/server.js:386 +#: htdocs/luci-static/resources/view/homeproxy/client.js:542 +#: htdocs/luci-static/resources/view/homeproxy/client.js:891 +#: htdocs/luci-static/resources/view/homeproxy/node.js:644 +#: htdocs/luci-static/resources/view/homeproxy/node.js:797 +#: htdocs/luci-static/resources/view/homeproxy/server.js:325 msgid "QUIC" msgstr "QUIC" -#: htdocs/luci-static/resources/view/homeproxy/node.js:634 -#: htdocs/luci-static/resources/view/homeproxy/server.js:335 +#: htdocs/luci-static/resources/view/homeproxy/node.js:631 +#: htdocs/luci-static/resources/view/homeproxy/server.js:274 msgid "QUIC congestion control algorithm." msgstr "QUIC 拥塞控制算法。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:534 -#: htdocs/luci-static/resources/view/homeproxy/server.js:275 +#: htdocs/luci-static/resources/view/homeproxy/node.js:531 +#: htdocs/luci-static/resources/view/homeproxy/server.js:225 msgid "QUIC connection receive window" msgstr "QUIC 连接窗口" -#: htdocs/luci-static/resources/view/homeproxy/server.js:282 +#: htdocs/luci-static/resources/view/homeproxy/server.js:232 msgid "QUIC maximum concurrent bidirectional streams" msgstr "QUIC 最大双向并发流" -#: htdocs/luci-static/resources/view/homeproxy/node.js:528 -#: htdocs/luci-static/resources/view/homeproxy/server.js:268 +#: htdocs/luci-static/resources/view/homeproxy/node.js:525 +#: htdocs/luci-static/resources/view/homeproxy/server.js:218 msgid "QUIC stream receive window" msgstr "QUIC 流接收窗口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:813 +#: htdocs/luci-static/resources/view/homeproxy/client.js:878 msgid "Query type" msgstr "请求类型" -#: htdocs/luci-static/resources/view/homeproxy/client.js:663 +#: htdocs/luci-static/resources/view/homeproxy/client.js:400 +msgid "Quick Reload" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:724 msgid "RDRC timeout" msgstr "RDRC 超时" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1093 -#: htdocs/luci-static/resources/view/homeproxy/server.js:693 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1173 +#: htdocs/luci-static/resources/view/homeproxy/server.js:632 msgid "REALITY" msgstr "REALITY" -#: htdocs/luci-static/resources/view/homeproxy/server.js:699 +#: htdocs/luci-static/resources/view/homeproxy/server.js:638 msgid "REALITY private key" msgstr "REALITY 私钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1098 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1178 msgid "REALITY public key" msgstr "REALITY 公钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1103 -#: htdocs/luci-static/resources/view/homeproxy/server.js:704 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1183 +#: htdocs/luci-static/resources/view/homeproxy/server.js:643 msgid "REALITY short ID" msgstr "REALITY 标识符" -#: htdocs/luci-static/resources/view/homeproxy/client.js:55 -#: htdocs/luci-static/resources/view/homeproxy/server.js:38 +#: htdocs/luci-static/resources/view/homeproxy/client.js:99 +#: htdocs/luci-static/resources/view/homeproxy/server.js:37 msgid "RUNNING" msgstr "运行中" -#: htdocs/luci-static/resources/view/homeproxy/node.js:601 +#: htdocs/luci-static/resources/view/homeproxy/node.js:598 msgid "Random version will be used if empty." msgstr "如留空,则使用随机版本。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:425 +#: htdocs/luci-static/resources/view/homeproxy/client.js:479 msgid "Recursive outbound detected!" msgstr "检测到递归出站!" -#: htdocs/luci-static/resources/view/homeproxy/client.js:722 +#: htdocs/luci-static/resources/view/homeproxy/client.js:785 msgid "Recursive resolver detected!" msgstr "检测到递归解析器!" -#: htdocs/luci-static/resources/view/homeproxy/client.js:257 +#: htdocs/luci-static/resources/view/homeproxy/client.js:316 msgid "Redirect TCP" msgstr "Redirect TCP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:259 +#: htdocs/luci-static/resources/view/homeproxy/client.js:318 msgid "Redirect TCP + TProxy UDP" msgstr "Redirect TCP + TProxy UDP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:261 +#: htdocs/luci-static/resources/view/homeproxy/client.js:320 msgid "Redirect TCP + Tun UDP" msgstr "Redirect TCP + Tun UDP" -#: htdocs/luci-static/resources/view/homeproxy/status.js:171 +#: htdocs/luci-static/resources/view/homeproxy/status.js:170 msgid "Refresh every %s seconds." msgstr "每 %s 秒刷新。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:642 +#: htdocs/luci-static/resources/view/homeproxy/server.js:581 msgid "Region ID" msgstr "区域 ID" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1005 +#: htdocs/luci-static/resources/view/homeproxy/client.js:401 +msgid "Reload" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:196 msgid "Remote" msgstr "远程" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1386 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1498 msgid "Remove %s nodes" msgstr "移除 %s 个节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1376 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1488 msgid "Remove all nodes from subscriptions" msgstr "移除所有订阅节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:870 +#: htdocs/luci-static/resources/view/homeproxy/node.js:950 msgid "Reserved field bytes" msgstr "保留字段字节" -#: htdocs/luci-static/resources/view/homeproxy/client.js:735 +#: htdocs/luci-static/resources/view/homeproxy/client.js:798 msgid "Resolve strategy" msgstr "解析策略" @@ -1747,135 +1906,156 @@ msgstr "解析策略" msgid "Resources management" msgstr "资源管理" -#: htdocs/luci-static/resources/view/homeproxy/client.js:967 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1047 msgid "Rewrite TTL" msgstr "重写 TTL" -#: htdocs/luci-static/resources/view/homeproxy/client.js:968 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1048 msgid "Rewrite TTL in DNS responses." msgstr "在 DNS 响应中重写 TTL。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:357 +#: htdocs/luci-static/resources/view/homeproxy/client.js:410 msgid "Routing Nodes" msgstr "路由节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:433 +#: htdocs/luci-static/resources/view/homeproxy/client.js:487 msgid "Routing Rules" msgstr "路由规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:139 +#: htdocs/luci-static/resources/view/homeproxy/client.js:195 msgid "Routing Settings" msgstr "路由设置" -#: htdocs/luci-static/resources/view/homeproxy/client.js:222 +#: htdocs/luci-static/resources/view/homeproxy/client.js:278 msgid "Routing mode" msgstr "路由模式" -#: htdocs/luci-static/resources/view/homeproxy/client.js:366 +#: htdocs/luci-static/resources/view/homeproxy/client.js:419 msgid "Routing node" msgstr "路由节点" -#: htdocs/luci-static/resources/view/homeproxy/client.js:235 +#: htdocs/luci-static/resources/view/homeproxy/client.js:291 msgid "Routing ports" msgstr "路由端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:442 +#: htdocs/luci-static/resources/view/homeproxy/client.js:497 msgid "Routing rule" msgstr "路由规则" -#: htdocs/luci-static/resources/view/homeproxy/client.js:565 -#: htdocs/luci-static/resources/view/homeproxy/client.js:899 -#: htdocs/luci-static/resources/view/homeproxy/client.js:980 -#: htdocs/luci-static/resources/view/homeproxy/client.js:989 +#: htdocs/luci-static/resources/view/homeproxy/client.js:623 +#: htdocs/luci-static/resources/view/homeproxy/client.js:968 +msgid "Rule" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:627 +#: htdocs/luci-static/resources/view/homeproxy/client.js:972 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:91 msgid "Rule set" msgstr "规则集" -#: htdocs/luci-static/resources/view/homeproxy/client.js:915 +#: htdocs/luci-static/resources/view/homeproxy/client.js:988 msgid "Rule set IP CIDR as source IP" msgstr "规则集 IP CIDR 作为源 IP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1022 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:213 msgid "Rule set URL" msgstr "规则集 URL" -#: htdocs/luci-static/resources/view/homeproxy/client.js:449 -#: htdocs/luci-static/resources/view/homeproxy/client.js:781 +#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:30 +msgid "Ruleset Settings" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:505 +#: htdocs/luci-static/resources/view/homeproxy/client.js:846 msgid "SRC-IP fields" msgstr "源 IP 字段" -#: htdocs/luci-static/resources/view/homeproxy/client.js:450 -#: htdocs/luci-static/resources/view/homeproxy/client.js:782 +#: htdocs/luci-static/resources/view/homeproxy/client.js:506 +#: htdocs/luci-static/resources/view/homeproxy/client.js:847 msgid "SRC-Port fields" msgstr "源端口字段" -#: htdocs/luci-static/resources/view/homeproxy/node.js:403 +#: htdocs/luci-static/resources/view/homeproxy/node.js:404 msgid "SSH" msgstr "SSH" -#: htdocs/luci-static/resources/view/homeproxy/client.js:487 -#: htdocs/luci-static/resources/view/homeproxy/client.js:828 +#: htdocs/luci-static/resources/view/homeproxy/client.js:543 +#: htdocs/luci-static/resources/view/homeproxy/client.js:893 msgid "STUN" msgstr "STUN" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1130 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1213 msgid "SUoT version" msgstr "SUoT 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:505 -#: htdocs/luci-static/resources/view/homeproxy/server.js:259 +#: htdocs/luci-static/resources/view/homeproxy/node.js:502 +#: htdocs/luci-static/resources/view/homeproxy/server.js:209 msgid "Salamander" msgstr "Salamander" -#: htdocs/luci-static/resources/view/homeproxy/client.js:151 +#: htdocs/luci-static/resources/view/homeproxy/client.js:207 msgid "Same as main node" msgstr "保持与主节点一致" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1462 msgid "Save current settings" msgstr "保存当前设置" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1347 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1459 msgid "Save subscriptions settings" msgstr "保存订阅设置" -#: htdocs/luci-static/resources/view/homeproxy/client.js:943 -#: htdocs/luci-static/resources/view/homeproxy/server.js:129 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1130 +msgid "Secret" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1088 +msgid "Select Clash Dashboard" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:412 +msgid "Selector" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1023 +#: htdocs/luci-static/resources/view/homeproxy/server.js:89 msgid "Server" msgstr "服务器" -#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:30 +#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:38 msgid "Server Settings" msgstr "服务器设置" -#: htdocs/luci-static/resources/view/homeproxy/server.js:591 +#: htdocs/luci-static/resources/view/homeproxy/server.js:530 msgid "" "Server name to use when choosing a certificate if the ClientHello's " "ServerName field is empty." msgstr "当 ClientHello 的 ServerName 字段为空时,选择证书所使用的服务器名称。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:124 +#: htdocs/luci-static/resources/view/homeproxy/server.js:83 msgid "Server settings" msgstr "服务器设置" -#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:38 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1090 +#: root/usr/share/luci/menu.d/luci-app-homeproxy.json:46 msgid "Service Status" msgstr "服务状态" -#: htdocs/luci-static/resources/view/homeproxy/node.js:401 +#: htdocs/luci-static/resources/view/homeproxy/node.js:402 msgid "ShadowTLS" msgstr "ShadowTLS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:580 +#: htdocs/luci-static/resources/view/homeproxy/node.js:577 msgid "ShadowTLS version" msgstr "ShadowTLS 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:400 -#: htdocs/luci-static/resources/view/homeproxy/server.js:151 +#: htdocs/luci-static/resources/view/homeproxy/node.js:401 +#: htdocs/luci-static/resources/view/homeproxy/server.js:112 msgid "Shadowsocks" msgstr "Shadowsocks" -#: htdocs/luci-static/resources/view/homeproxy/client.js:483 -#: htdocs/luci-static/resources/view/homeproxy/client.js:823 +#: htdocs/luci-static/resources/view/homeproxy/client.js:539 +#: htdocs/luci-static/resources/view/homeproxy/client.js:888 msgid "" "Sniffed protocol, see Sniff for details." @@ -1883,48 +2063,48 @@ msgstr "" "嗅探协议,具体参见 Sniff。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:402 -#: htdocs/luci-static/resources/view/homeproxy/server.js:152 +#: htdocs/luci-static/resources/view/homeproxy/node.js:403 +#: htdocs/luci-static/resources/view/homeproxy/server.js:113 msgid "Socks" msgstr "Socks" -#: htdocs/luci-static/resources/view/homeproxy/node.js:590 +#: htdocs/luci-static/resources/view/homeproxy/node.js:587 msgid "Socks version" msgstr "Socks 版本" -#: htdocs/luci-static/resources/view/homeproxy/node.js:591 +#: htdocs/luci-static/resources/view/homeproxy/node.js:588 msgid "Socks4" msgstr "Socks4" -#: htdocs/luci-static/resources/view/homeproxy/node.js:592 +#: htdocs/luci-static/resources/view/homeproxy/node.js:589 msgid "Socks4A" msgstr "Socks4A" -#: htdocs/luci-static/resources/view/homeproxy/node.js:593 +#: htdocs/luci-static/resources/view/homeproxy/node.js:590 msgid "Socks5" msgstr "Socks5" -#: htdocs/luci-static/resources/view/homeproxy/client.js:511 -#: htdocs/luci-static/resources/view/homeproxy/client.js:857 +#: htdocs/luci-static/resources/view/homeproxy/client.js:567 +#: htdocs/luci-static/resources/view/homeproxy/client.js:922 msgid "Source IP CIDR" msgstr "源 IP CIDR" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1010 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:201 msgid "Source file" msgstr "源文件" -#: htdocs/luci-static/resources/view/homeproxy/client.js:533 -#: htdocs/luci-static/resources/view/homeproxy/client.js:877 +#: htdocs/luci-static/resources/view/homeproxy/client.js:587 +#: htdocs/luci-static/resources/view/homeproxy/client.js:942 msgid "Source port" msgstr "源端口" -#: htdocs/luci-static/resources/view/homeproxy/client.js:538 -#: htdocs/luci-static/resources/view/homeproxy/client.js:882 +#: htdocs/luci-static/resources/view/homeproxy/client.js:592 +#: htdocs/luci-static/resources/view/homeproxy/client.js:947 msgid "Source port range" msgstr "源端口范围" -#: htdocs/luci-static/resources/view/homeproxy/node.js:734 -#: htdocs/luci-static/resources/view/homeproxy/node.js:787 +#: htdocs/luci-static/resources/view/homeproxy/node.js:814 +#: htdocs/luci-static/resources/view/homeproxy/node.js:867 msgid "" "Specifies the period of time (in seconds) after which a health check will be " "performed using a ping frame if no frames have been received on the " @@ -1936,8 +2116,8 @@ msgstr "" "查。
需要注意的是,PING 响应被视为已接收的帧,因此如果连接上没有其他流" "量,则健康检查将在每个间隔执行一次。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:403 -#: htdocs/luci-static/resources/view/homeproxy/server.js:438 +#: htdocs/luci-static/resources/view/homeproxy/server.js:342 +#: htdocs/luci-static/resources/view/homeproxy/server.js:377 msgid "" "Specifies the time (in seconds) until idle clients should be closed with a " "GOAWAY frame. PING frames are not considered as activity." @@ -1945,8 +2125,8 @@ msgstr "" "指定闲置客户端应在多长时间(单位:秒)内使用 GOAWAY 帧关闭。PING 帧不被视为活" "动。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:738 -#: htdocs/luci-static/resources/view/homeproxy/node.js:795 +#: htdocs/luci-static/resources/view/homeproxy/node.js:818 +#: htdocs/luci-static/resources/view/homeproxy/node.js:875 msgid "" "Specifies the timeout duration (in seconds) after sending a PING frame, " "within which a response must be received.
If a response to the PING " @@ -1956,17 +2136,17 @@ msgstr "" "指定发送 PING 帧后,在指定的超时时间(单位:秒)内必须接收到响应。
如果在" "指定的超时时间内没有收到 PING 帧的响应,则连接将关闭。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:236 +#: htdocs/luci-static/resources/view/homeproxy/client.js:292 msgid "" "Specify target ports to be proxied. Multiple ports must be separated by " "commas." msgstr "指定需要被代理的目标端口。多个端口必须用逗号隔开。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:657 +#: htdocs/luci-static/resources/view/homeproxy/client.js:718 msgid "Store RDRC" msgstr "存储 RDRC" -#: htdocs/luci-static/resources/view/homeproxy/client.js:658 +#: htdocs/luci-static/resources/view/homeproxy/client.js:719 msgid "" "Store rejected DNS response cache.
The check results of Address " "filter DNS rule items will be cached until expiration." @@ -1974,33 +2154,42 @@ msgstr "" "存储被拒绝的 DNS 响应缓存。
地址过滤 DNS 规则 的检查结果将被" "缓存直到过期。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:494 -#: htdocs/luci-static/resources/view/homeproxy/server.js:248 +#: htdocs/luci-static/resources/view/homeproxy/node.js:491 +#: htdocs/luci-static/resources/view/homeproxy/server.js:198 msgid "String" msgstr "字符串" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1275 +#: htdocs/luci-static/resources/view/homeproxy/node.js:713 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1384 msgid "Sub (%s)" msgstr "订阅(%s)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1304 +#: htdocs/luci-static/resources/view/homeproxy/node.js:708 +msgid "Subscription Groups" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1416 msgid "Subscription URL-s" msgstr "订阅地址" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1286 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1396 msgid "Subscriptions" msgstr "订阅" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1226 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1303 msgid "Successfully imported %s nodes of total %s." msgstr "成功导入 %s 个节点,共 %s 个。" -#: htdocs/luci-static/resources/view/homeproxy/status.js:86 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:132 +msgid "Successfully imported %s rule-set of total %s." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/status.js:85 msgid "Successfully updated." msgstr "更新成功。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1186 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1305 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1263 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1417 msgid "" "Support Hysteria, Shadowsocks, Trojan, v2rayN (VMess), and XTLS (VLESS) " "online configuration delivery standard." @@ -2008,58 +2197,64 @@ msgstr "" "支持 Hysteria、Shadowsocks、Trojan、v2rayN(VMess)和 XTLS(VLESS)在线配置交" "付标准。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:291 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:99 +msgid "" +"Supports rule-set links of type: local, remote and format: " +"source, binary." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:350 msgid "System" msgstr "系统" -#: htdocs/luci-static/resources/view/homeproxy/client.js:628 -#: htdocs/luci-static/resources/view/homeproxy/client.js:705 -#: htdocs/luci-static/resources/view/homeproxy/client.js:950 +#: htdocs/luci-static/resources/view/homeproxy/client.js:689 +#: htdocs/luci-static/resources/view/homeproxy/client.js:768 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1030 msgid "System DNS" msgstr "系统 DNS" -#: htdocs/luci-static/resources/view/homeproxy/client.js:490 -#: htdocs/luci-static/resources/view/homeproxy/client.js:818 -#: htdocs/luci-static/resources/view/homeproxy/server.js:800 +#: htdocs/luci-static/resources/view/homeproxy/client.js:546 +#: htdocs/luci-static/resources/view/homeproxy/client.js:883 +#: htdocs/luci-static/resources/view/homeproxy/server.js:732 msgid "TCP" msgstr "TCP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1110 -#: htdocs/luci-static/resources/view/homeproxy/server.js:765 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1190 +#: htdocs/luci-static/resources/view/homeproxy/server.js:704 msgid "TCP fast open" msgstr "TCP 快速打开" -#: htdocs/luci-static/resources/view/homeproxy/client.js:285 +#: htdocs/luci-static/resources/view/homeproxy/client.js:344 msgid "TCP/IP stack" msgstr "TCP/IP 协议栈" -#: htdocs/luci-static/resources/view/homeproxy/client.js:286 +#: htdocs/luci-static/resources/view/homeproxy/client.js:345 msgid "TCP/IP stack." msgstr "TCP/IP 协议栈。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:485 -#: htdocs/luci-static/resources/view/homeproxy/client.js:825 -#: htdocs/luci-static/resources/view/homeproxy/node.js:945 -#: htdocs/luci-static/resources/view/homeproxy/server.js:516 +#: htdocs/luci-static/resources/view/homeproxy/client.js:541 +#: htdocs/luci-static/resources/view/homeproxy/client.js:890 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1025 +#: htdocs/luci-static/resources/view/homeproxy/server.js:455 msgid "TLS" msgstr "TLS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:977 -#: htdocs/luci-static/resources/view/homeproxy/server.js:548 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1057 +#: htdocs/luci-static/resources/view/homeproxy/server.js:487 msgid "TLS ALPN" msgstr "TLS ALPN" -#: htdocs/luci-static/resources/view/homeproxy/node.js:972 -#: htdocs/luci-static/resources/view/homeproxy/server.js:543 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1052 +#: htdocs/luci-static/resources/view/homeproxy/server.js:482 msgid "TLS SNI" msgstr "TLS SNI" -#: htdocs/luci-static/resources/view/homeproxy/node.js:725 -#: htdocs/luci-static/resources/view/homeproxy/server.js:394 +#: htdocs/luci-static/resources/view/homeproxy/node.js:805 +#: htdocs/luci-static/resources/view/homeproxy/server.js:333 msgid "TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used." msgstr "不强制执行 TLS。如未配置 TLS,将使用纯 HTTP 1.1。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:698 +#: htdocs/luci-static/resources/view/homeproxy/client.js:761 msgid "" "Tag of a another server to resolve the domain name in the address. Required " "if address contains domain." @@ -2067,55 +2262,64 @@ msgstr "" "用于解析本 DNS 服务器的域名的另一个 DNS 服务器的标签。如果服务器地址包括域名" "则必须。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:741 +#: htdocs/luci-static/resources/view/homeproxy/client.js:804 msgid "Tag of an outbound for connecting to the dns server." msgstr "用于连接到 DNS 服务器的出站标签。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1045 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:236 msgid "Tag of the outbound to download rule set." msgstr "用于下载规则集的出站标签。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:944 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1024 msgid "Tag of the target dns server." msgstr "目标 DNS 服务器标签。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:593 +#: htdocs/luci-static/resources/view/homeproxy/client.js:654 msgid "Tag of the target outbound." msgstr "目标出站标签。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:296 +#: htdocs/luci-static/resources/view/homeproxy/server.js:246 msgid "" "Tell the client to use the BBR flow control algorithm instead of Hysteria CC." msgstr "让客户端使用 BBR 流控算法。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:166 -#: htdocs/luci-static/resources/view/homeproxy/client.js:189 +#: htdocs/luci-static/resources/view/homeproxy/client.js:222 +#: htdocs/luci-static/resources/view/homeproxy/client.js:245 msgid "Tencent Public DNS (119.29.29.29)" msgstr "腾讯公共 DNS(119.29.29.29)" -#: htdocs/luci-static/resources/view/homeproxy/server.js:612 +#: htdocs/luci-static/resources/view/homeproxy/node.js:756 +msgid "Test URL" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/server.js:551 msgid "The ACME CA provider to use." msgstr "使用的 ACME CA 颁发机构。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:618 +#: htdocs/luci-static/resources/view/homeproxy/client.js:679 msgid "The DNS strategy for resolving the domain name in the address." msgstr "解析域名的默认策略。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:535 -#: htdocs/luci-static/resources/view/homeproxy/server.js:276 +#: htdocs/luci-static/resources/view/homeproxy/node.js:532 +#: htdocs/luci-static/resources/view/homeproxy/server.js:226 msgid "The QUIC connection-level flow control window for receiving data." msgstr "用于接收数据的 QUIC 连接级流控制窗口。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:529 -#: htdocs/luci-static/resources/view/homeproxy/server.js:269 +#: htdocs/luci-static/resources/view/homeproxy/node.js:526 +#: htdocs/luci-static/resources/view/homeproxy/server.js:219 msgid "The QUIC stream-level flow control window for receiving data." msgstr "用于接收数据的 QUIC 流级流控制窗口。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:694 +#: htdocs/luci-static/resources/view/homeproxy/node.js:757 +msgid "" +"The URL to test. https://www.gstatic.com/generate_204 will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:757 msgid "The address of the dns server. Support UDP, TCP, DoT, DoH and RCode." msgstr "DNS 服务器的地址。支持 UDP、TCP、DoT、DoH 和 RCode。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:663 +#: htdocs/luci-static/resources/view/homeproxy/server.js:602 msgid "" "The alternate port to use for the ACME HTTP challenge; if non-empty, this " "port will be used instead of 80 to spin up a listener for the HTTP challenge." @@ -2123,14 +2327,27 @@ msgstr "" "用于 ACME HTTP 质询的备用端口;如果非空,将使用此端口而不是 80 来启动 HTTP 质" "询的侦听器。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:669 +#: htdocs/luci-static/resources/view/homeproxy/server.js:608 msgid "" "The alternate port to use for the ACME TLS-ALPN challenge; the system must " "forward 443 to this port for challenge to succeed." msgstr "" "用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:463 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1113 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1116 +msgid "The current API URL is %s" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1133 +msgid "The current Secret is " +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:729 +msgid "The default outbound tag. The first outbound will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:519 msgid "" "The default rule uses the following matching logic:
(domain || " "domain_suffix || domain_keyword || domain_regex || ip_cidr || " @@ -2147,7 +2364,7 @@ msgstr "" "source_port_range) &&
其他字段。此外,包含的所有规则" "集会被合并而不是独立生效。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:795 +#: htdocs/luci-static/resources/view/homeproxy/client.js:860 msgid "" "The default rule uses the following matching logic:
(domain || " "domain_suffix || domain_keyword || domain_regex) &&
(port " @@ -2162,76 +2379,94 @@ msgstr "" ">(source_port || source_port_range) &&
其他字段。此外,包含的所有规则集会被合并而不是独立生效。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:730 -msgid "The domain strategy for resolving the domain name in the address." -msgstr "用于解析本 DNS 服务器的域名的策略。" +#: htdocs/luci-static/resources/view/homeproxy/node.js:1404 +msgid "The default value is 2:00 every day" +msgstr "" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1008 -#: htdocs/luci-static/resources/view/homeproxy/server.js:570 +#: htdocs/luci-static/resources/view/homeproxy/client.js:793 +msgid "" +"The domain strategy for resolving the domain name in the address. dns." +"strategy will be used if empty." +msgstr "用于解析本 DNS 服务器的域名的策略。默认使用 dns.strategy。" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1088 +#: htdocs/luci-static/resources/view/homeproxy/server.js:509 msgid "" "The elliptic curves that will be used in an ECDHE handshake, in preference " "order. If empty, the default will be used." msgstr "将在 ECDHE 握手中使用的椭圆曲线,按优先顺序排列。留空使用默认值。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:597 +#: htdocs/luci-static/resources/view/homeproxy/server.js:536 msgid "" "The email address to use when creating or selecting an existing ACME server " "account." msgstr "创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1000 -#: htdocs/luci-static/resources/view/homeproxy/server.js:562 +#: htdocs/luci-static/resources/view/homeproxy/node.js:777 +msgid "The idle timeout. 30m will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1080 +#: htdocs/luci-static/resources/view/homeproxy/server.js:501 msgid "The maximum TLS version that is acceptable." msgstr "可接受的最高 TLS 版本。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:283 +#: htdocs/luci-static/resources/view/homeproxy/server.js:233 msgid "" "The maximum number of QUIC concurrent bidirectional streams that a peer is " "allowed to open." msgstr "允许对等点打开的 QUIC 并发双向流的最大数量。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:710 +#: htdocs/luci-static/resources/view/homeproxy/server.js:649 msgid "The maximum time difference between the server and the client." msgstr "服务器和客户端之间的最大时间差。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:992 -#: htdocs/luci-static/resources/view/homeproxy/server.js:554 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1072 +#: htdocs/luci-static/resources/view/homeproxy/server.js:493 msgid "The minimum TLS version that is acceptable." msgstr "可接受的最低 TLS 版本。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:121 -#: htdocs/luci-static/resources/view/homeproxy/server.js:98 +#: htdocs/luci-static/resources/view/homeproxy/client.js:171 +#: htdocs/luci-static/resources/view/homeproxy/server.js:57 msgid "The modern ImmortalWrt proxy platform for ARM64/AMD64." msgstr "为 ARM64/AMD64 设计的现代 ImmortalWrt 代理平台。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:394 +#: htdocs/luci-static/resources/view/homeproxy/client.js:448 msgid "The network interface to bind to." msgstr "绑定到的网络接口。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1022 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1102 msgid "The path to the server certificate, in PEM format." msgstr "服务端证书路径,需要 PEM 格式。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:166 +#: htdocs/luci-static/resources/view/homeproxy/server.js:127 msgid "The port must be unique." msgstr "必须是唯一端口。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:746 +#: htdocs/luci-static/resources/view/homeproxy/server.js:685 msgid "The server private key, in PEM format." msgstr "服务端私钥,需要 PEM 格式。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:728 +#: htdocs/luci-static/resources/view/homeproxy/server.js:667 msgid "The server public key, in PEM format." msgstr "服务端公钥,需要 PEM 格式。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:401 +#: htdocs/luci-static/resources/view/homeproxy/client.js:455 msgid "" "The tag of the upstream outbound.
Other dial fields will be ignored when " "enabled." msgstr "上游出站的标签。
启用时,其他拨号字段将被忽略。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:745 -#: htdocs/luci-static/resources/view/homeproxy/server.js:446 +#: htdocs/luci-static/resources/view/homeproxy/node.js:764 +msgid "The test interval. 3m will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:771 +msgid "The test tolerance in milliseconds. 50 will be used if empty." +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:825 +#: htdocs/luci-static/resources/view/homeproxy/server.js:385 msgid "" "The timeout (in seconds) that after performing a keepalive check, the client " "will wait for activity. If no activity is detected, the connection will be " @@ -2240,15 +2475,15 @@ msgstr "" "经过一段时间(单位:秒)之后,客户端将执行 keepalive 检查并等待活动。如果没有" "检测到任何活动,则会关闭连接。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:985 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1337 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1065 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1449 msgid "" "This is DANGEROUS, your traffic is almost like " "PLAIN TEXT! Use at your own risk!" msgstr "" "这是危险行为,您的流量将几乎等同于明文!使用风险自负!" -#: htdocs/luci-static/resources/view/homeproxy/node.js:652 +#: htdocs/luci-static/resources/view/homeproxy/node.js:649 msgid "" "This is the TUIC port of the UDP over TCP protocol, designed to provide a " "QUIC stream based UDP relay mode that TUIC does not provide." @@ -2256,390 +2491,428 @@ msgstr "" "这是 TUIC 的 UDP over TCP 协议移植, 旨在提供 TUIC 不提供的基于 QUIC 流的 " "UDP 中继模式。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:167 -#: htdocs/luci-static/resources/view/homeproxy/client.js:190 -msgid "ThreatBook Public DNS (117.50.10.10)" -msgstr "微步在线公共 DNS(117.50.10.10)" - -#: htdocs/luci-static/resources/view/homeproxy/client.js:664 +#: htdocs/luci-static/resources/view/homeproxy/client.js:725 msgid "" "Timeout of rejected DNS response cache. 7d is used by default." msgstr "被拒绝的 DNS 响应缓存超时。默认时长 7d。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:472 +#: htdocs/luci-static/resources/view/homeproxy/server.js:411 msgid "" "To be compatible with Xray-core, set this to Sec-WebSocket-Protocol." msgstr "" "要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:264 +#: htdocs/luci-static/resources/view/homeproxy/client.js:323 msgid "" "To enable Tun support, you need to install ip-full and " "kmod-tun" msgstr "" "要启用 Tun 支持,您需要安装 ip-fullkmod-tun。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:711 -#: htdocs/luci-static/resources/view/homeproxy/server.js:380 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1071 +msgid "" +"To enable this feature you need install luci-nginx and luci-ssl-" +"nginx
first" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:770 +msgid "Tolerance" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:791 +#: htdocs/luci-static/resources/view/homeproxy/server.js:319 msgid "Transport" msgstr "传输层" -#: htdocs/luci-static/resources/view/homeproxy/node.js:404 -#: htdocs/luci-static/resources/view/homeproxy/server.js:153 +#: htdocs/luci-static/resources/view/homeproxy/node.js:405 +#: htdocs/luci-static/resources/view/homeproxy/server.js:114 msgid "Trojan" msgstr "Trojan" -#: htdocs/luci-static/resources/view/homeproxy/node.js:406 -#: htdocs/luci-static/resources/view/homeproxy/server.js:155 +#: htdocs/luci-static/resources/view/homeproxy/node.js:407 +#: htdocs/luci-static/resources/view/homeproxy/server.js:116 msgid "Tuic" msgstr "Tuic" -#: htdocs/luci-static/resources/view/homeproxy/client.js:262 +#: htdocs/luci-static/resources/view/homeproxy/client.js:321 msgid "Tun TCP/UDP" msgstr "Tun TCP/UDP" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1003 -#: htdocs/luci-static/resources/view/homeproxy/node.js:393 -#: htdocs/luci-static/resources/view/homeproxy/server.js:144 +#: htdocs/luci-static/resources/view/homeproxy/node.js:394 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:194 +#: htdocs/luci-static/resources/view/homeproxy/server.js:105 msgid "Type" msgstr "类型" -#: htdocs/luci-static/resources/view/homeproxy/client.js:491 -#: htdocs/luci-static/resources/view/homeproxy/client.js:819 -#: htdocs/luci-static/resources/view/homeproxy/server.js:801 +#: htdocs/luci-static/resources/view/homeproxy/client.js:547 +#: htdocs/luci-static/resources/view/homeproxy/client.js:884 +#: htdocs/luci-static/resources/view/homeproxy/server.js:733 msgid "UDP" msgstr "UDP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1118 -#: htdocs/luci-static/resources/view/homeproxy/server.js:776 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1200 +#: htdocs/luci-static/resources/view/homeproxy/server.js:715 msgid "UDP Fragment" msgstr "UDP 分片" -#: htdocs/luci-static/resources/view/homeproxy/client.js:313 -#: htdocs/luci-static/resources/view/homeproxy/server.js:782 -msgid "UDP NAT expiration time" -msgstr "UDP NAT 过期时间" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1123 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1206 msgid "UDP over TCP" msgstr "UDP over TCP" -#: htdocs/luci-static/resources/view/homeproxy/node.js:651 +#: htdocs/luci-static/resources/view/homeproxy/node.js:648 msgid "UDP over stream" msgstr "UDP over stream" -#: htdocs/luci-static/resources/view/homeproxy/node.js:644 +#: htdocs/luci-static/resources/view/homeproxy/node.js:641 msgid "UDP packet relay mode." msgstr "UDP 包中继模式。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:643 +#: htdocs/luci-static/resources/view/homeproxy/node.js:640 msgid "UDP relay mode" msgstr "UDP 中继模式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:626 -#: htdocs/luci-static/resources/view/homeproxy/server.js:316 +#: htdocs/luci-static/resources/view/homeproxy/node.js:413 +msgid "URLTest" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:623 +#: htdocs/luci-static/resources/view/homeproxy/server.js:266 msgid "UUID" msgstr "UUID" -#: htdocs/luci-static/resources/view/homeproxy/status.js:98 +#: htdocs/luci-static/resources/view/homeproxy/status.js:97 msgid "Unknown error." msgstr "未知错误。" -#: htdocs/luci-static/resources/view/homeproxy/status.js:148 +#: htdocs/luci-static/resources/view/homeproxy/status.js:147 msgid "Unknown error: %s" msgstr "未知错误:%s" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1086 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1166 msgid "Unsupported fingerprint!" msgstr "不支持的指纹!" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1361 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1473 msgid "Update %s subscriptions" msgstr "更新 %s 个订阅" -#: htdocs/luci-static/resources/view/homeproxy/status.js:89 +#: htdocs/luci-static/resources/view/homeproxy/status.js:88 msgid "Update failed." msgstr "更新失败。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1062 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:254 msgid "Update interval" msgstr "更新间隔" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1063 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:255 msgid "Update interval of rule set.
1d will be used if empty." msgstr "规则集更新间隔。
留空使用 1d。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1356 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1468 msgid "Update nodes from subscriptions" msgstr "从订阅更新节点" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1300 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1412 msgid "Update subscriptions via proxy." msgstr "使用代理更新订阅。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1293 -msgid "Update time" -msgstr "更新时间" - -#: htdocs/luci-static/resources/view/homeproxy/node.js:1299 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1411 msgid "Update via proxy" msgstr "使用代理更新" -#: htdocs/luci-static/resources/view/homeproxy/node.js:937 -#: htdocs/luci-static/resources/view/homeproxy/server.js:507 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1017 +#: htdocs/luci-static/resources/view/homeproxy/server.js:446 msgid "Upload bandwidth" msgstr "上传带宽" -#: htdocs/luci-static/resources/view/homeproxy/node.js:938 -#: htdocs/luci-static/resources/view/homeproxy/server.js:508 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1018 +#: htdocs/luci-static/resources/view/homeproxy/server.js:447 msgid "Upload bandwidth in Mbps." msgstr "上传带宽(单位:Mbps)。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1028 -#: htdocs/luci-static/resources/view/homeproxy/server.js:737 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1108 +#: htdocs/luci-static/resources/view/homeproxy/server.js:676 msgid "Upload certificate" msgstr "上传证书" -#: htdocs/luci-static/resources/view/homeproxy/server.js:755 +#: htdocs/luci-static/resources/view/homeproxy/server.js:694 msgid "Upload key" msgstr "上传密钥" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1031 -#: htdocs/luci-static/resources/view/homeproxy/server.js:740 -#: htdocs/luci-static/resources/view/homeproxy/server.js:758 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1111 +#: htdocs/luci-static/resources/view/homeproxy/server.js:679 +#: htdocs/luci-static/resources/view/homeproxy/server.js:697 msgid "Upload..." msgstr "上传..." -#: htdocs/luci-static/resources/view/homeproxy/server.js:579 +#: htdocs/luci-static/resources/view/homeproxy/server.js:518 msgid "Use ACME TLS certificate issuer." msgstr "使用 ACME TLS 证书颁发机构。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:973 -#: htdocs/luci-static/resources/view/homeproxy/server.js:544 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1101 +msgid "Use Online Dashboard" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1053 +#: htdocs/luci-static/resources/view/homeproxy/server.js:483 msgid "" "Used to verify the hostname on the returned certificates unless insecure is " "given." msgstr "用于验证返回证书上的主机名。如允许不安全连接,此配置无效。" -#: htdocs/luci-static/resources/view/homeproxy/client.js:561 -#: htdocs/luci-static/resources/view/homeproxy/client.js:895 +#: htdocs/luci-static/resources/view/homeproxy/client.js:615 +#: htdocs/luci-static/resources/view/homeproxy/client.js:960 msgid "User" msgstr "用户" -#: htdocs/luci-static/resources/view/homeproxy/node.js:423 -#: htdocs/luci-static/resources/view/homeproxy/server.js:170 +#: htdocs/luci-static/resources/view/homeproxy/node.js:426 +#: htdocs/luci-static/resources/view/homeproxy/server.js:131 msgid "Username" msgstr "用户名" -#: htdocs/luci-static/resources/view/homeproxy/node.js:409 -#: htdocs/luci-static/resources/view/homeproxy/server.js:156 +#: htdocs/luci-static/resources/view/homeproxy/node.js:410 +#: htdocs/luci-static/resources/view/homeproxy/server.js:117 msgid "VLESS" msgstr "VLESS" -#: htdocs/luci-static/resources/view/homeproxy/node.js:410 -#: htdocs/luci-static/resources/view/homeproxy/server.js:157 +#: htdocs/luci-static/resources/view/homeproxy/node.js:411 +#: htdocs/luci-static/resources/view/homeproxy/server.js:118 msgid "VMess" msgstr "VMess" -#: htdocs/luci-static/resources/view/homeproxy/client.js:160 -#: htdocs/luci-static/resources/view/homeproxy/client.js:186 +#: htdocs/luci-static/resources/view/homeproxy/client.js:216 +#: htdocs/luci-static/resources/view/homeproxy/client.js:242 msgid "WAN DNS (read from interface)" msgstr "WAN DNS(从接口获取)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1133 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 msgid "WAN IP Policy" msgstr "WAN IP 策略" -#: htdocs/luci-static/resources/view/homeproxy/node.js:718 -#: htdocs/luci-static/resources/view/homeproxy/server.js:387 +#: htdocs/luci-static/resources/view/homeproxy/node.js:798 +#: htdocs/luci-static/resources/view/homeproxy/server.js:326 msgid "WebSocket" msgstr "WebSocket" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1325 +#: htdocs/luci-static/resources/view/homeproxy/node.js:743 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1438 msgid "Whitelist mode" msgstr "白名单模式" -#: htdocs/luci-static/resources/view/homeproxy/node.js:408 +#: htdocs/luci-static/resources/view/homeproxy/node.js:409 msgid "WireGuard" msgstr "WireGuard" -#: htdocs/luci-static/resources/view/homeproxy/node.js:857 +#: htdocs/luci-static/resources/view/homeproxy/node.js:937 msgid "WireGuard peer public key." msgstr "WireGuard 对端公钥。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:864 +#: htdocs/luci-static/resources/view/homeproxy/node.js:944 msgid "WireGuard pre-shared key." msgstr "WireGuard 预共享密钥。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:849 +#: htdocs/luci-static/resources/view/homeproxy/node.js:929 msgid "WireGuard requires base64-encoded private keys." msgstr "WireGuard 要求 base64 编码的私钥。" -#: htdocs/luci-static/resources/view/homeproxy/node.js:472 -msgid "Write proxy protocol in the connection header." -msgstr "在连接头中写入代理协议。" +#: htdocs/luci-static/resources/view/homeproxy/client.js:223 +#: htdocs/luci-static/resources/view/homeproxy/client.js:246 +msgid "Xinfeng Public DNS (114.114.114.114)" +msgstr "信风公共 DNS(114.114.114.114)" -#: htdocs/luci-static/resources/view/homeproxy/node.js:828 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1345 +#: htdocs/luci-static/resources/view/homeproxy/node.js:908 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1457 msgid "Xudp (Xray-core)" msgstr "Xudp (Xray-core)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:199 +#: htdocs/luci-static/resources/view/homeproxy/client.js:255 msgid "You can only have two servers set at maximum." msgstr "您最多只能设置两个服务器。" -#: htdocs/luci-static/resources/homeproxy.js:243 +#: htdocs/luci-static/resources/homeproxy.js:272 msgid "Your %s was successfully uploaded. Size: %sB." msgstr "您的 %s 已成功上传。大小:%sB。" -#: htdocs/luci-static/resources/view/homeproxy/server.js:614 +#: htdocs/luci-static/resources/view/homeproxy/server.js:553 msgid "ZeroSSL" msgstr "ZeroSSL" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1033 -#: htdocs/luci-static/resources/view/homeproxy/server.js:742 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1113 +#: htdocs/luci-static/resources/view/homeproxy/server.js:681 msgid "certificate" msgstr "证书" -#: htdocs/luci-static/resources/view/homeproxy/node.js:993 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1001 -#: htdocs/luci-static/resources/view/homeproxy/server.js:555 -#: htdocs/luci-static/resources/view/homeproxy/server.js:563 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1073 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1081 +#: htdocs/luci-static/resources/view/homeproxy/server.js:494 +#: htdocs/luci-static/resources/view/homeproxy/server.js:502 msgid "default" msgstr "默认" -#: htdocs/luci-static/resources/view/homeproxy/status.js:53 +#: htdocs/luci-static/resources/view/homeproxy/status.js:52 msgid "failed" msgstr "失败" -#: htdocs/luci-static/resources/view/homeproxy/node.js:714 -#: htdocs/luci-static/resources/view/homeproxy/server.js:383 +#: htdocs/luci-static/resources/view/homeproxy/node.js:794 +#: htdocs/luci-static/resources/view/homeproxy/server.js:322 msgid "gRPC" msgstr "gRPC" -#: htdocs/luci-static/resources/view/homeproxy/node.js:756 +#: htdocs/luci-static/resources/view/homeproxy/node.js:836 msgid "gRPC permit without stream" msgstr "gRPC 允许无活动连接" -#: htdocs/luci-static/resources/view/homeproxy/node.js:751 -#: htdocs/luci-static/resources/view/homeproxy/server.js:411 +#: htdocs/luci-static/resources/view/homeproxy/node.js:831 +#: htdocs/luci-static/resources/view/homeproxy/server.js:350 msgid "gRPC service name" msgstr "gRPC 服务名称" -#: htdocs/luci-static/resources/view/homeproxy/client.js:289 +#: htdocs/luci-static/resources/view/homeproxy/client.js:348 msgid "gVisor" msgstr "gVisor" -#: htdocs/luci-static/resources/homeproxy.js:263 -#: htdocs/luci-static/resources/homeproxy.js:281 -#: htdocs/luci-static/resources/view/homeproxy/client.js:176 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1026 -#: htdocs/luci-static/resources/view/homeproxy/node.js:452 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1082 -#: htdocs/luci-static/resources/view/homeproxy/server.js:211 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1096 +msgid "metacubexd" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1335 +msgid "node" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:292 +#: htdocs/luci-static/resources/homeproxy.js:310 +#: htdocs/luci-static/resources/view/homeproxy/client.js:232 +#: htdocs/luci-static/resources/view/homeproxy/node.js:455 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1162 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:217 +#: htdocs/luci-static/resources/view/homeproxy/server.js:161 msgid "non-empty value" msgstr "非空值" -#: htdocs/luci-static/resources/view/homeproxy/node.js:567 -#: htdocs/luci-static/resources/view/homeproxy/node.js:826 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1343 +#: htdocs/luci-static/resources/view/homeproxy/node.js:564 +#: htdocs/luci-static/resources/view/homeproxy/node.js:906 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1455 msgid "none" msgstr "无" -#: htdocs/luci-static/resources/view/homeproxy/node.js:827 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1344 +#: htdocs/luci-static/resources/view/homeproxy/node.js:907 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1456 msgid "packet addr (v2ray-core v5+)" msgstr "packet addr (v2ray-core v5+)" -#: htdocs/luci-static/resources/view/homeproxy/status.js:50 +#: htdocs/luci-static/resources/view/homeproxy/status.js:49 msgid "passed" msgstr "通过" -#: htdocs/luci-static/resources/view/homeproxy/server.js:760 +#: htdocs/luci-static/resources/view/homeproxy/server.js:699 msgid "private key" msgstr "私钥" -#: htdocs/luci-static/resources/view/homeproxy/status.js:227 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1098 +msgid "razord-meta" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/status.js:233 msgid "sing-box client" msgstr "sing-box 客户端" -#: htdocs/luci-static/resources/view/homeproxy/status.js:230 +#: htdocs/luci-static/resources/view/homeproxy/status.js:236 msgid "sing-box server" msgstr "sing-box 服务端" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1059 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1336 +msgid "sub" +msgstr "" + +#: htdocs/luci-static/resources/view/homeproxy/node.js:1139 msgid "uTLS fingerprint" msgstr "uTLS 指纹" -#: htdocs/luci-static/resources/view/homeproxy/node.js:1060 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1140 msgid "" "uTLS is a fork of \"crypto/tls\", which provides ClientHello fingerprinting " "resistance." msgstr "" "uTLS 是 \"crypto/tls\" 的一个分支,拥有抵抗 ClientHello 指纹识别的能力。" -#: htdocs/luci-static/resources/view/homeproxy/status.js:59 +#: htdocs/luci-static/resources/view/homeproxy/status.js:58 msgid "unchecked" msgstr "未检查" -#: htdocs/luci-static/resources/homeproxy.js:221 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1255 +#: htdocs/luci-static/resources/homeproxy.js:240 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1350 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:161 msgid "unique UCI identifier" msgstr "独立 UCI 标识" -#: htdocs/luci-static/resources/homeproxy.js:272 +#: htdocs/luci-static/resources/homeproxy.js:243 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1353 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:164 +msgid "unique label" +msgstr "" + +#: htdocs/luci-static/resources/homeproxy.js:301 msgid "unique value" msgstr "独立值" -#: htdocs/luci-static/resources/view/homeproxy/node.js:474 -#: htdocs/luci-static/resources/view/homeproxy/node.js:581 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1131 +#: htdocs/luci-static/resources/view/homeproxy/node.js:578 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1214 msgid "v1" msgstr "v1" -#: htdocs/luci-static/resources/view/homeproxy/node.js:475 -#: htdocs/luci-static/resources/view/homeproxy/node.js:582 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1132 +#: htdocs/luci-static/resources/view/homeproxy/node.js:579 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1215 msgid "v2" msgstr "v2" -#: htdocs/luci-static/resources/view/homeproxy/node.js:583 +#: htdocs/luci-static/resources/view/homeproxy/node.js:580 msgid "v3" msgstr "v3" -#: htdocs/luci-static/resources/view/homeproxy/client.js:178 +#: htdocs/luci-static/resources/view/homeproxy/client.js:234 msgid "valid IP address" msgstr "有效 IP 地址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1031 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1034 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1311 -#: htdocs/luci-static/resources/view/homeproxy/node.js:1314 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1424 +#: htdocs/luci-static/resources/view/homeproxy/node.js:1427 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:222 +#: htdocs/luci-static/resources/view/homeproxy/ruleset.js:225 msgid "valid URL" msgstr "有效网址" -#: htdocs/luci-static/resources/view/homeproxy/client.js:206 +#: htdocs/luci-static/resources/view/homeproxy/client.js:262 msgid "valid address#port" msgstr "有效 地址#端口" -#: htdocs/luci-static/resources/homeproxy.js:255 +#: htdocs/luci-static/resources/homeproxy.js:284 msgid "valid base64 key with %d characters" msgstr "包含 %d 个字符的有效 base64 密钥" -#: htdocs/luci-static/resources/view/homeproxy/client.js:1173 -#: htdocs/luci-static/resources/view/homeproxy/client.js:1202 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1242 +#: htdocs/luci-static/resources/view/homeproxy/client.js:1271 msgid "valid hostname" msgstr "有效主机名" -#: htdocs/luci-static/resources/view/homeproxy/client.js:75 +#: htdocs/luci-static/resources/view/homeproxy/client.js:130 msgid "valid port range (port1:port2)" msgstr "有效端口范围(port1:port2)" -#: htdocs/luci-static/resources/view/homeproxy/client.js:246 +#: htdocs/luci-static/resources/view/homeproxy/client.js:300 +#: htdocs/luci-static/resources/view/homeproxy/client.js:305 msgid "valid port value" msgstr "有效端口值" -#: htdocs/luci-static/resources/homeproxy.js:283 +#: htdocs/luci-static/resources/homeproxy.js:312 msgid "valid uuid" msgstr "有效 uuid" + +#: htdocs/luci-static/resources/view/homeproxy/client.js:1097 +msgid "yacd-meta" +msgstr "" + +#~ msgid "Update time" +#~ msgstr "更新时间" + +#~ msgid "Match outbound." +#~ msgstr "匹配出站。" diff --git a/small/luci-app-homeproxy/root/etc/config/homeproxy b/small/luci-app-homeproxy/root/etc/config/homeproxy index cc6a0ead86..4407a0906a 100644 --- a/small/luci-app-homeproxy/root/etc/config/homeproxy +++ b/small/luci-app-homeproxy/root/etc/config/homeproxy @@ -27,6 +27,9 @@ config homeproxy 'config' option proxy_mode 'redirect_tproxy' option ipv6_support '1' +config homeproxy 'experimental' + option clash_api_port '9090' + config homeproxy 'control' option lan_proxy_mode 'disabled' list wan_proxy_ipv4_ips '91.105.192.0/23' diff --git a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/firewall_post.ut b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/firewall_post.ut index 1f31d4de89..662a31daaf 100755 --- a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/firewall_post.ut +++ b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/firewall_post.ut @@ -50,7 +50,7 @@ if (routing_mode !== 'custom') { bypass_cn_traffic = uci.get(cfgname, 'routing', 'bypass_cn_traffic') || '0'; } -let routing_port = uci.get(cfgname, 'config', 'routing_port'); +let routing_port = uci.get(cfgname, 'config', 'routing_port') || 'common'; if (routing_port === 'common') routing_port = uci.get(cfgname, 'infra', 'common_port') || '22,53,80,143,443,465,587,853,873,993,995,8080,8443,9418'; @@ -88,9 +88,6 @@ const control_info = {}; for (let i in control_options) control_info[i] = uci.get(cfgname, 'control', i); - -const dns_hijacked = uci.get('dhcp', '@dnsmasq[0]', 'dns_redirect') || '0', - dns_port = uci.get('dhcp', '@dnsmasq[0]', 'port') || '53'; /* UCI config end */ -%} @@ -222,7 +219,7 @@ set homeproxy_wan_direct_addr_v6 { } {% endif /* ipv6_support */ %} -{% if (routing_port): %} +{% if (routing_port !== 'all'): %} set homeproxy_routing_port { type inet_service flags interval @@ -231,16 +228,6 @@ set homeproxy_routing_port { } {% endif %} -{# DNS hijack & TCP redirect #} -chain dstnat { -{% if (dns_hijacked !== '1'): %} - meta nfproto { ipv4, ipv6 } udp dport 53 counter redirect to :{{ dns_port }} comment "!{{ cfgname }}: DNS hijack" -{% endif /* dns_hijacked */ %} -{% if (match(proxy_mode, /redirect/)): %} - meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect_lanac -{% endif /* proxy_mode */ %} -} - {# TCP redirect #} {% if (match(proxy_mode, /redirect/)): %} chain homeproxy_redirect_proxy { @@ -248,7 +235,7 @@ chain homeproxy_redirect_proxy { } chain homeproxy_redirect_proxy_port { - {% if (routing_port): %} + {% if (routing_port !== 'all'): %} tcp dport != @homeproxy_routing_port counter return {% endif %} goto homeproxy_redirect_proxy @@ -351,6 +338,10 @@ chain homeproxy_output_redir { type nat hook output priority filter -105; policy accept meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect } + +chain dstnat { + meta nfproto { {{ (ipv6_support === '1') ? 'ipv4, ipv6' : 'ipv4' }} } meta l4proto tcp jump homeproxy_redirect_lanac +} {% endif %} {# UDP tproxy #} @@ -363,14 +354,14 @@ chain homeproxy_mangle_tproxy { } chain homeproxy_mangle_tproxy_port { - {% if (routing_port): %} + {% if (routing_port !== 'all'): %} udp dport != @homeproxy_routing_port counter return {% endif %} goto homeproxy_mangle_tproxy } chain homeproxy_mangle_mark { - {% if (routing_port): %} + {% if (routing_port !== 'all'): %} udp dport != @homeproxy_routing_port counter return {% endif %} meta l4proto udp mark set {{ tproxy_mark }} counter accept @@ -380,7 +371,6 @@ chain homeproxy_mangle_lanac { {% if (control_info.listen_interfaces): %} meta iifname != {{ array_to_nftarr(split(join(' ', control_info.listen_interfaces) + ' lo', ' ')) }} counter return {% endif %} - meta iifname != lo udp dport 53 counter return meta mark {{ self_mark }} counter return {% if (control_info.lan_proxy_mode === 'listed_only'): %} @@ -523,7 +513,6 @@ chain mangle_output { {% if (match(proxy_mode, /tun/)): %} chain homeproxy_mangle_lanac { iifname {{ tun_name }} counter return - udp dport 53 counter return {% if (control_info.listen_interfaces): %} meta iifname != {{ array_to_nftarr(control_info.listen_interfaces) }} counter return @@ -557,7 +546,7 @@ chain homeproxy_mangle_lanac { } chain homeproxy_mangle_tun_mark { - {% if (routing_port): %} + {% if (routing_port !== 'all'): %} {% if (proxy_mode === 'tun'): %} tcp dport != @homeproxy_routing_port counter return {% endif /* proxy_mode */ %} diff --git a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc index 9941d30d4d..7efef75d10 100755 --- a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc +++ b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/generate_client.uc @@ -11,9 +11,11 @@ import { readfile, writefile } from 'fs'; import { isnan } from 'math'; import { cursor } from 'uci'; +import { urldecode } from 'luci.http'; + import { - executeCommand, isEmpty, strToBool, strToInt, - removeBlankAttrs, validateHostname, validation, + executeCommand, shellQuote, calcStringCRC8, calcStringMD5, isEmpty, strToBool, strToInt, + removeBlankAttrs, parseURL, validateHostname, validation, filterCheck, HP_DIR, RUN_DIR } from 'homeproxy'; @@ -25,6 +27,7 @@ uci.load(uciconfig); const uciinfra = 'infra', ucimain = 'config', + ucisub = 'subscription', uciexp = 'experimental', ucicontrol = 'control'; @@ -49,7 +52,7 @@ else const dns_port = uci.get(uciconfig, uciinfra, 'dns_port') || '5333'; -let main_node, main_udp_node, dedicated_udp_node, default_outbound, domain_strategy, sniff_override = '1', +let main_node, main_udp_node, dedicated_udp_node, default_outbound, sniff_override = '1', dns_server, dns_default_strategy, dns_default_server, dns_disable_cache, dns_disable_cache_expire, dns_independent_cache, dns_client_subnet, direct_domain_list, proxy_domain_list; @@ -80,7 +83,6 @@ if (routing_mode !== 'custom') { /* Routing settings */ default_outbound = uci.get(uciconfig, uciroutingsetting, 'default_outbound') || 'nil'; - domain_strategy = uci.get(uciconfig, uciroutingsetting, 'domain_strategy'); sniff_override = uci.get(uciconfig, uciroutingsetting, 'sniff_override'); } @@ -91,6 +93,13 @@ const proxy_mode = uci.get(uciconfig, ucimain, 'proxy_mode') || 'redirect_tproxy const cache_file_store_rdrc = uci.get(uciconfig, uciexp, 'cache_file_store_rdrc'), cache_file_rdrc_timeout = uci.get(uciconfig, uciexp, 'cache_file_rdrc_timeout'); +const clash_api_enabled = uci.get(uciconfig, uciexp, 'clash_api_enabled'), + nginx_support = uci.get(uciconfig, uciexp, 'nginx_support'), + clash_api_log_level = uci.get(uciconfig, uciexp, 'clash_api_log_level') || 'warn', + dashboard_repo = uci.get(uciconfig, uciexp, 'dashboard_repo'), + clash_api_port = uci.get(uciconfig, uciexp, 'clash_api_port') || '9090', + clash_api_secret = uci.get(uciconfig, uciexp, 'clash_api_secret') || trim(readfile('/proc/sys/kernel/random/uuid')); + const mixed_port = uci.get(uciconfig, uciinfra, 'mixed_port') || '5330'; let self_mark, redirect_port, tproxy_port, tun_name, tun_addr4, tun_addr6, tun_mtu, tun_gso, @@ -118,6 +127,24 @@ if (match(proxy_mode), /tun/) { endpoint_independent_nat = uci.get(uciconfig, uciroutingsetting, 'endpoint_independent_nat'); } } + +let subs_info = {}; +{ + const suburls = uci.get(uciconfig, ucisub, 'subscription_url') || []; + for (let i = 0; i < length(suburls); i++) { + const url = parseURL(suburls[i]); + const urlhash = calcStringMD5(replace(suburls[i], /#.*$/, '')); + subs_info[urlhash] = { + "url": replace(suburls[i], /#.*$/, ''), + "name": url.hash ? urldecode(url.hash) : url.hostname + }; + } +} + +let checkedout_nodes = [], + nodes_tobe_checkedout = [], + checkedout_groups = [], + groups_tobe_checkedout = []; /* UCI config end */ /* Config helper start */ @@ -145,13 +172,75 @@ function parse_dnsquery(strquery) { } +function get_tag(cfg, failback_tag, filterable) { + if (isEmpty(cfg)) + return null; + + let node = {}; + if (type(cfg) === 'object') + node = cfg; + else { + if (cfg in ['direct-out', 'block-out']) + return cfg; + else + node = uci.get_all(uciconfig, cfg); + } + + //filter check + if (!isEmpty(filterable)) + if (filterCheck(node.label, filterable.filter_nodes, filterable.filter_keywords)) + return null; + + const sub_info = subs_info[node.grouphash]; + return node.label ? sprintf("%s%s", node.grouphash ? + sprintf("[%s] ", sub_info ? sub_info.name : calcStringCRC8(node.grouphash)) : '', + node.label) : + (failback_tag || null); +} + function generate_outbound(node) { if (type(node) !== 'object' || isEmpty(node)) return null; + push(checkedout_nodes, node['.name']); + + if (node.type in ['selector', 'urltest']) { + let outbounds = []; + for (let grouphash in node.group) { + if (!isEmpty(grouphash)) { + const output = executeCommand(`/sbin/uci -q show ${shellQuote(uciconfig)} | /bin/grep "\.grouphash='*${shellQuote(grouphash)}'*" | /usr/bin/cut -f2 -d'.'`) || {}; + if (!isEmpty(trim(output.stdout))) + for (let order in split(trim(output.stdout), /\n/)) + push(outbounds, get_tag(order, 'cfg-' + order + '-out', { "filter_nodes": node.filter_nodes, "filter_keywords": node.filter_keywords })); + if (!(grouphash in groups_tobe_checkedout)) + push(groups_tobe_checkedout, grouphash); + } + } + for (let order in node.order) { + push(outbounds, get_tag(order, 'cfg-' + order + '-out', { "filter_nodes": node.filter_nodes, "filter_keywords": node.filter_keywords })); + if (!(order in ['direct-out', 'block-out']) && !(order in nodes_tobe_checkedout)) + push(nodes_tobe_checkedout, order); + } + if (length(outbounds) === 0) + push(outbounds, 'direct-out', 'block-out'); + return { + type: node.type, + tag: get_tag(node, 'cfg-' + node['.name'] + '-out'), + /* Selector */ + outbounds: outbounds, + default: node.default_selected ? (get_tag(node.default_selected, 'cfg-' + node.default_selected + '-out')) : null, + /* URLTest */ + url: node.test_url, + interval: node.interval, + tolerance: strToInt(node.tolerance), + idle_timeout: node.idle_timeout, + interrupt_exist_connections: strToBool(node.interrupt_exist_connections) + }; + } + const outbound = { type: node.type, - tag: 'cfg-' + node['.name'] + '-out', + tag: get_tag(node, 'cfg-' + node['.name'] + '-out'), routing_mark: strToInt(self_mark), server: node.address, @@ -164,7 +253,10 @@ function generate_outbound(node) { /* Direct */ override_address: node.override_address, override_port: strToInt(node.override_port), - proxy_protocol: strToInt(node.proxy_protocol), + proxy_protocol: (node.proxy_protocol === '1') ? { + enabled: true, + version: strToInt(node.proxy_protocol_version) + } : null, /* Hysteria (2) */ up_mbps: strToInt(node.hysteria_up_mbps), down_mbps: strToInt(node.hysteria_down_mbps), @@ -299,7 +391,7 @@ function get_outbound(cfg) { if (isEmpty(node)) die(sprintf("%s's node is missing, please check your configuration.", cfg)); else - return 'cfg-' + node + '-out'; + return get_tag(node, 'cfg-' + node + '-out'); } } } @@ -330,7 +422,7 @@ const config = {}; /* Log */ config.log = { disabled: false, - level: 'warn', + level: (clash_api_enabled === '1') ? clash_api_log_level : 'warn', output: RUN_DIR + '/sing-box-c.log', timestamp: true }; @@ -454,6 +546,7 @@ if (!isEmpty(main_node)) { process_name: cfg.process_name, process_path: cfg.process_path, user: cfg.user, + clash_mode: cfg.clash_mode, rule_set: get_ruleset(cfg.rule_set), rule_set_ipcidr_match_source: (cfg.rule_set_ipcidr_match_source === '1') || null, invert: (cfg.invert === '1') || null, @@ -490,7 +583,6 @@ push(config.inbounds, { udp_timeout: udp_timeout ? (udp_timeout + 's') : null, sniff: true, sniff_override_destination: (sniff_override === '1'), - domain_strategy: domain_strategy, set_system_proxy: false }); @@ -502,8 +594,7 @@ if (match(proxy_mode, /redirect/)) listen: '::', listen_port: int(redirect_port), sniff: true, - sniff_override_destination: (sniff_override === '1'), - domain_strategy: domain_strategy, + sniff_override_destination: (sniff_override === '1') }); if (match(proxy_mode, /tproxy/)) push(config.inbounds, { @@ -515,8 +606,7 @@ if (match(proxy_mode, /tproxy/)) network: 'udp', udp_timeout: udp_timeout ? (udp_timeout + 's') : null, sniff: true, - sniff_override_destination: (sniff_override === '1'), - domain_strategy: domain_strategy, + sniff_override_destination: (sniff_override === '1') }); if (match(proxy_mode, /tun/)) push(config.inbounds, { @@ -534,7 +624,6 @@ if (match(proxy_mode, /tun/)) stack: tcpip_stack, sniff: true, sniff_override_destination: (sniff_override === '1'), - domain_strategy: domain_strategy, }); /* Inbound end */ @@ -574,10 +663,45 @@ if (!isEmpty(main_node)) { const outbound = uci.get_all(uciconfig, cfg.node) || {}; push(config.outbounds, generate_outbound(outbound)); - config.outbounds[length(config.outbounds)-1].domain_strategy = cfg.domain_strategy; - config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface; - config.outbounds[length(config.outbounds)-1].detour = get_outbound(cfg.outbound); + const type = config.outbounds[length(config.outbounds)-1].type; + if (!(type in ['selector', 'urltest'])) { + config.outbounds[length(config.outbounds)-1].domain_strategy = cfg.domain_strategy; + config.outbounds[length(config.outbounds)-1].bind_interface = cfg.bind_interface; + config.outbounds[length(config.outbounds)-1].detour = get_outbound(cfg.outbound); + } }); +/* Second level outbounds */ +while (length(nodes_tobe_checkedout) > 0) { + const oldarr = uniq(nodes_tobe_checkedout); + + nodes_tobe_checkedout = []; + map(oldarr, (k) => { + if (!(k in checkedout_nodes)) { + const outbound = uci.get_all(uciconfig, k) || {}; + push(config.outbounds, generate_outbound(outbound)); + push(checkedout_nodes, k); + } + }); +} +while (length(groups_tobe_checkedout) > 0) { + const oldarr = uniq(groups_tobe_checkedout); + let newarr = []; + + groups_tobe_checkedout = []; + map(oldarr, (k) => { + if (!(k in checkedout_groups)) { + push(newarr, k); + push(checkedout_groups, k); + } + }); + const hashexp = regexp('^' + replace(replace(replace(sprintf("%J", newarr), /^\[(.*)\]$/g, "($1)"), /[" ]/g, ''), ',', '|') + '$', 'is'); + uci.foreach(uciconfig, ucinode, (cfg) => { + if (!(cfg['.name'] in checkedout_nodes) && match(cfg?.grouphash, hashexp)) { + push(config.outbounds, generate_outbound(cfg)); + push(checkedout_nodes, cfg['.name']); + } + }); +} /* Outbound end */ /* Routing rules start */ @@ -639,6 +763,7 @@ if (!isEmpty(main_node)) { process_name: cfg.process_name, process_path: cfg.process_path, user: cfg.user, + clash_mode: cfg.clash_mode, rule_set: get_ruleset(cfg.rule_set), rule_set_ipcidr_match_source: (cfg.rule_set_ipcidr_match_source === '1') || null, invert: (cfg.invert === '1') || null, @@ -673,11 +798,23 @@ if (routing_mode === 'custom') { config.experimental = { cache_file: { enabled: true, - path: RUN_DIR + '/cache.db', + path: HP_DIR + '/cache.db', store_rdrc: (cache_file_store_rdrc === '1') || null, rdrc_timeout: cache_file_rdrc_timeout } }; + /* Clash API */ + if (dashboard_repo) { + system('rm -rf ' + RUN_DIR + '/ui'); + const dashpkg = HP_DIR + '/resources/' + replace(dashboard_repo, '/', '_') + '.zip'; + system('unzip -qo ' + dashpkg + ' -d ' + RUN_DIR + '/'); + system('mv ' + RUN_DIR + '/*-gh-pages/ ' + RUN_DIR + '/ui/'); + } + config.experimental.clash_api = { + external_controller: (clash_api_enabled === '1') ? (nginx_support ? '[::1]:' : '[::]:') + clash_api_port : null, + external_ui: dashboard_repo ? RUN_DIR + '/ui' : null, + secret: clash_api_secret + }; } /* Experimental end */ diff --git a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc index 43a825b3c7..38d5301c60 100644 --- a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc +++ b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/homeproxy.uc @@ -52,6 +52,48 @@ export function executeCommand(...args) { }; }; +export function hexencArray(str) { + if (!str || type(str) !== 'string') + return null; + + const hexstr = hexenc(str); + let arr = []; + + for (let i = 0; i < length(hexstr) / 2; i++) + push(arr, hex('0x' + substr(hexstr, i * 2, 2))); + return arr; +}; + +export function calcStringCRC8(str) { + if (!str || type(str) !== 'string') + return null; + + const crc8Table = [ + 0, 7, 14, 9, 28, 27, 18, 21, 56, 63, 54, 49, 36, 35, 42, 45, + 112, 119, 126, 121, 108, 107, 98, 101, 72, 79, 70, 65, 84, 83, 90, 93, + 224, 231, 238, 233, 252, 251, 242, 245, 216, 223, 214, 209, 196, 195, 202, 205, + 144, 151, 158, 153, 140, 139, 130, 133, 168, 175, 166, 161, 180, 179, 186, 189, + 199, 192, 201, 206, 219, 220, 213, 210, 255, 248, 241, 246, 227, 228, 237, 234, + 183, 176, 185, 190, 171, 172, 165, 162, 143, 136, 129, 134, 147, 148, 157, 154, + 39, 32, 41, 46, 59, 60, 53, 50, 31, 24, 17, 22, 3, 4, 13, 10, + 87, 80, 89, 94, 75, 76, 69, 66, 111, 104, 97, 102, 115, 116, 125, 122, + 137, 142, 135, 128, 149, 146, 155, 156, 177, 182, 191, 184, 173, 170, 163, 164, + 249, 254, 247, 240, 229, 226, 235, 236, 193, 198, 207, 200, 221, 218, 211, 212, + 105, 110, 103, 96, 117, 114, 123, 124, 81, 86, 95, 88, 77, 74, 67, 68, + 25, 30, 23, 16, 5, 2, 11, 12, 33, 38, 47, 40, 61, 58, 51, 52, + 78, 73, 64, 71, 82, 85, 92, 91, 118, 113, 120, 127, 106, 109, 100, 99, + 62, 57, 48, 55, 34, 37, 44, 43, 6, 1, 8, 15, 26, 29, 20, 19, + 174, 169, 160, 167, 178, 181, 188, 187, 150, 145, 152, 159, 138, 141, 132, 131, + 222, 217, 208, 215, 194, 197, 204, 203, 230, 225, 232, 239, 250, 253, 244, 243 + ]; + const strArray = hexencArray(str); + let crc8 = 0; + + for (let i = 0; i < length(strArray); i++) + crc8 = crc8Table[(crc8 ^ strArray[i]) & 255]; + return substr('00' + sprintf("%X", crc8), -2); +}; + export function calcStringMD5(str) { if (!str || type(str) !== 'string') return null; @@ -134,6 +176,22 @@ export function validation(datatype, data) { const ret = system(`/sbin/validate_data ${shellQuote(datatype)} ${shellQuote(data)} 2>/dev/null`); return (ret === 0); }; + +export function filterCheck(name, filter_mode, filter_keywords) { + if (isEmpty(name) || isEmpty(filter_mode) || isEmpty(filter_keywords)) + return false; + + let ret = false; + for (let i in filter_keywords) { + const patten = regexp(i); + if (match(name, patten)) + ret = true; + } + if (filter_mode === 'whitelist') + ret = !ret; + + return ret; +}; /* String helper end */ /* String parser start */ diff --git a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh index 576293d016..401d7194a5 100755 --- a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh +++ b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_resources.sh @@ -3,6 +3,8 @@ # # Copyright (C) 2022-2023 ImmortalWrt.org +. /usr/share/libubox/jshn.sh + NAME="homeproxy" RESOURCES_DIR="/etc/$NAME/resources" @@ -37,6 +39,67 @@ to_upper() { echo -e "$1" | tr "[a-z]" "[A-Z]" } +get_local_vers() { + local ver_file="$1" + local repoid="$2" + + local ver="$(eval "jsonfilter -qi \"$ver_file\" -e '@[\"$repoid\"].version'")" + [ -n "$ver" ] && echo "$ver" || return 1 +} + +check_clash_dashboard_update() { + local dashtype="$1" + local dashrepo="$2" + local dashrepoid="$(echo -n "$dashrepo" | md5sum | cut -f1 -d' ')" + local wget="wget --timeout=10 -q" + + set_lock "set" "$dashtype" + + local dashdata_ver="$($wget -O- "https://api.github.com/repos/$dashrepo/releases/latest" | jsonfilter -e "@.tag_name")" + [ -n "$dashdata_ver" ] || { + dashdata_ver="$($wget -O- "https://api.github.com/repos/$dashrepo/tags" | jsonfilter -e "@[*].name" | head -n1)" + } + if [ -z "$dashdata_ver" ]; then + log "[$(to_upper "$dashtype")] [$dashrepo] Failed to get the latest version, please retry later." + + set_lock "remove" "$dashtype" + return 1 + fi + + local local_dashdata_ver="$(get_local_vers "$RESOURCES_DIR/$dashtype.ver" "$dashrepoid" || echo "NOT FOUND")" + if [ "$local_dashdata_ver" = "$dashdata_ver" ]; then + log "[$(to_upper "$dashtype")] [$dashrepo] Current version: $dashdata_ver." + log "[$(to_upper "$dashtype")] [$dashrepo] You're already at the latest version." + + set_lock "remove" "$dashtype" + return 3 + else + log "[$(to_upper "$dashtype")] [$dashrepo] Local version: $local_dashdata_ver, latest version: $dashdata_ver." + fi + + $wget "https://codeload.github.com/$dashrepo/zip/refs/heads/gh-pages" -O "$RUN_DIR/$dashtype.zip" + if [ ! -s "$RUN_DIR/$dashtype.zip" ]; then + rm -f "$RUN_DIR/$dashtype.zip" + log "[$(to_upper "$dashtype")] [$dashrepo] Update failed." + + set_lock "remove" "$dashtype" + return 1 + fi + + mv -f "$RUN_DIR/$dashtype.zip" "$RESOURCES_DIR/${dashrepo//\//_}.zip" + touch "$RESOURCES_DIR/$dashtype.ver" + json_init + json_load_file "$RESOURCES_DIR/$dashtype.ver" + json_select "$dashrepoid" 2>/dev/null || json_add_object "$dashrepoid" + json_add_string repo "$dashrepo" + json_add_string version "$dashdata_ver" + json_dump > "$RESOURCES_DIR/$dashtype.ver" + log "[$(to_upper "$dashtype")] [$dashrepo] Successfully updated." + + set_lock "remove" "$dashtype" + return 0 +} + check_list_update() { local listtype="$1" local listrepo="$2" @@ -85,6 +148,9 @@ check_list_update() { } case "$1" in +"clash_dashboard") + check_clash_dashboard_update "$1" "$2" + ;; "china_ip4") check_list_update "$1" "1715173329/IPCIDR-CHINA" "master" "ipv4.txt" ;; @@ -99,7 +165,7 @@ case "$1" in sed -i -e "s/full://g" -e "/:/d" "$RESOURCES_DIR/china_list.txt" ;; *) - echo -e "Usage: $0 " + echo -e "Usage: $0 " exit 1 ;; esac diff --git a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc index 996bebc02b..f66053578e 100755 --- a/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc +++ b/small/luci-app-homeproxy/root/etc/homeproxy/scripts/update_subscriptions.uc @@ -16,7 +16,7 @@ import { init_action } from 'luci.sys'; import { calcStringMD5, wGET, executeCommand, decodeBase64Str, - getTime, isEmpty, parseURL, validation, + getTime, isEmpty, parseURL, validation, filterCheck, HP_DIR, RUN_DIR } from 'homeproxy'; @@ -31,7 +31,7 @@ const ucimain = 'config', ucisubscription = 'subscription'; const allow_insecure = uci.get(uciconfig, ucisubscription, 'allow_insecure') || '0', - filter_mode = uci.get(uciconfig, ucisubscription, 'filter_nodes') || 'disabled', + filter_mode = uci.get(uciconfig, ucisubscription, 'filter_nodes') || 'nil', filter_keywords = uci.get(uciconfig, ucisubscription, 'filter_keywords') || [], packet_encoding = uci.get(uciconfig, ucisubscription, 'packet_encoding') || 'xudp', subscription_urls = uci.get(uciconfig, ucisubscription, 'subscription_url') || [], @@ -46,21 +46,6 @@ if (routing_mode !== 'custom') { /* UCI config end */ /* String helper start */ -function filter_check(name) { - if (isEmpty(name) || filter_mode === 'disabled' || isEmpty(filter_keywords)) - return false; - - let ret = false; - for (let i in filter_keywords) { - const patten = regexp(i); - if (match(name, patten)) - ret = true; - } - if (filter_mode === 'whitelist') - ret = !ret; - - return ret; -} /* String helper end */ /* Common var start */ @@ -489,7 +474,7 @@ function main() { nameHash = calcStringMD5(label); config.label = label; - if (filter_check(config.label)) + if (filterCheck(config.label, filter_mode, filter_keywords)) log(sprintf('Skipping blacklist node: %s.', config.label)); else if (node_cache[groupHash][confHash] || node_cache[groupHash][nameHash]) log(sprintf('Skipping duplicate node: %s.', config.label)); @@ -543,10 +528,7 @@ function main() { log(sprintf('Removing node: %s.', cfg.label || cfg['name'])); } else { map(keys(node_cache[cfg.grouphash][cfg['.name']]), (v) => { - if (v in node_cache[cfg.grouphash][cfg['.name']]) - uci.set(uciconfig, cfg['.name'], v, node_cache[cfg.grouphash][cfg['.name']][v]); - else - uci.delete(uciconfig, cfg['.name'], v); + uci.set(uciconfig, cfg['.name'], v, node_cache[cfg.grouphash][cfg['.name']][v]); }); node_cache[cfg.grouphash][cfg['.name']].isExisting = true; } diff --git a/small/luci-app-homeproxy/root/etc/init.d/homeproxy b/small/luci-app-homeproxy/root/etc/init.d/homeproxy index 0bd90b7fd5..542efaf32d 100755 --- a/small/luci-app-homeproxy/root/etc/init.d/homeproxy +++ b/small/luci-app-homeproxy/root/etc/init.d/homeproxy @@ -15,6 +15,7 @@ HP_DIR="/etc/homeproxy" RUN_DIR="/var/run/homeproxy" LOG_PATH="$RUN_DIR/homeproxy.log" DNSMASQ_DIR="/tmp/dnsmasq.d/dnsmasq-homeproxy.d" +APILOCATION_PATH="/etc/nginx/conf.d/homeproxy.locations" log() { echo -e "$(date "+%Y-%m-%d %H:%M:%S") [DAEMON] $*" >> "$LOG_PATH" @@ -56,14 +57,24 @@ start_service() { fi # Auto update - local auto_update auto_update_time + local auto_update auto_update_expr config_get_bool auto_update "subscription" "auto_update" "0" if [ "$auto_update" = "1" ]; then - config_get auto_update_time "subscription" "auto_update_time" "2" - echo -e "0 $auto_update_time * * * $HP_DIR/scripts/update_crond.sh" >> "/etc/crontabs/root" + config_get auto_update_expr "subscription" "auto_update_expr" "0 2 * * *" + echo -e "$auto_update_expr $HP_DIR/scripts/update_crond.sh" >> "/etc/crontabs/root" /etc/init.d/cron restart fi + # Clash API uses Nginx reverse proxy + local clash_api_enabled clash_api_port nginx_support + config_get_bool clash_api_enabled "experimental" "clash_api_enabled" "0" + config_get_bool nginx_support "experimental" "nginx_support" "0" + if [ "$clash_api_enabled" = "1" -a "$nginx_support" = "1" ]; then + config_get clash_api_port "experimental" "clash_api_port" "9090" + [ "$(sed -En "s|^\s*proxy_pass\s+https?://[^:]+:(\d+).*|\1|p" "$APILOCATION_PATH")" = "$clash_api_port" ] || sed -Ei "/\bproxy_pass\b/{s|(proxy_pass\s+https?://[^:]+:)(\d+)(.*)|\1$clash_api_port\3|}" "$APILOCATION_PATH" + /etc/init.d/nginx reload + fi + # DNSMasq rules local ipv6_support config_get_bool ipv6_support "config" "ipv6_support" "0" diff --git a/small/luci-app-homeproxy/root/etc/nginx/conf.d/homeproxy.locations b/small/luci-app-homeproxy/root/etc/nginx/conf.d/homeproxy.locations new file mode 100644 index 0000000000..bf1c07db06 --- /dev/null +++ b/small/luci-app-homeproxy/root/etc/nginx/conf.d/homeproxy.locations @@ -0,0 +1,12 @@ +location /homeproxy/ { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://localhost:9090/; + proxy_redirect default; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + add_header Cache-Control no-cache; +} diff --git a/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy b/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy index 35abcd988c..bcc7fa74f9 100644 --- a/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy +++ b/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy @@ -15,4 +15,6 @@ uci -q batch <<-EOF >"/dev/null" commit firewall EOF +[ -z "$(uci -q get homeproxy.experimental)" ] && uci set homeproxy.experimental=homeproxy && uci commit homeproxy + exit 0 diff --git a/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy-migration b/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy-migration index e3a268eda7..bbcff6dcf7 100644 --- a/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy-migration +++ b/small/luci-app-homeproxy/root/etc/uci-defaults/luci-homeproxy-migration @@ -11,11 +11,18 @@ elif echo "$china_dns_server" | grep -q ","; then uci -q add_list "homeproxy.config.china_dns_server"="$dns" done fi - -if [ "$(uci -q get homeproxy.config.routing_port)" = "all" ]; then - uci -q delete "homeproxy.config.routing_port" +# rm Subscription Name-s +subscription_urls="$(uci -q get "homeproxy.subscription.subscription_url")" +subscription_names="$(uci -q get "homeproxy.subscription.subscription_name")" +if [ -n "$subscription_names" ]; then + uci -q delete "homeproxy.subscription.subscription_url" + uci -q delete "homeproxy.subscription.subscription_name" + i=1 + for suburl in $subscription_urls; do + uci -q add_list "homeproxy.subscription.subscription_url"="${suburl}#$(echo "$subscription_names" | cut -f$i -d' ')" + let i++ + done fi - [ -z "$(uci -q changes "homeproxy")" ] || uci -q commit "homeproxy" exit 0 diff --git a/small/luci-app-homeproxy/root/usr/share/luci/menu.d/luci-app-homeproxy.json b/small/luci-app-homeproxy/root/usr/share/luci/menu.d/luci-app-homeproxy.json index 9892a9b979..41d9107ecb 100644 --- a/small/luci-app-homeproxy/root/usr/share/luci/menu.d/luci-app-homeproxy.json +++ b/small/luci-app-homeproxy/root/usr/share/luci/menu.d/luci-app-homeproxy.json @@ -26,6 +26,14 @@ "path": "homeproxy/node" } }, + "admin/services/homeproxy/ruleset": { + "title": "Ruleset Settings", + "order": 18, + "action": { + "type": "view", + "path": "homeproxy/ruleset" + } + }, "admin/services/homeproxy/server": { "title": "Server Settings", "order": 20, diff --git a/small/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json b/small/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json index b4b97ea842..e656f4b420 100644 --- a/small/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json +++ b/small/luci-app-homeproxy/root/usr/share/rpcd/acl.d/luci-app-homeproxy.json @@ -4,6 +4,7 @@ "read": { "file": { "/etc/homeproxy/scripts/update_subscriptions.uc": [ "exec" ], + "/etc/init.d/homeproxy reload *": [ "exec" ], "/var/run/homeproxy/homeproxy.log": [ "read" ], "/var/run/homeproxy/sing-box-c.log": [ "read" ], "/var/run/homeproxy/sing-box-s.log": [ "read" ] diff --git a/small/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy b/small/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy index 34754c525f..11693c4270 100755 --- a/small/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy +++ b/small/luci-app-homeproxy/root/usr/share/rpcd/ucode/luci.homeproxy @@ -179,28 +179,45 @@ const methods = { features.hp_has_tcp_brutal = hasKernelModule('brutal.ko'); features.hp_has_tproxy = hasKernelModule('nft_tproxy.ko') || access('/etc/modules.d/nft-tproxy'); features.hp_has_tun = hasKernelModule('tun.ko') || access('/etc/modules.d/30-tun'); + features.hp_has_nginx = access('/usr/sbin/nginx'); return features; } }, resources_get_version: { - args: { type: 'type' }, + args: { type: 'type', repo: 'repo' }, call: function(req) { - const version = trim(readfile(`${HP_DIR}/resources/${req.args?.type}.ver`)); - return { version: version, error: error() }; + const versions = trim(readfile(`${HP_DIR}/resources/${req.args?.type}.ver`)); + if (req.args?.repo && versions) { + const vers_arr = values(json(versions)); + for (obj in vers_arr) { + if (obj.repo === req.args?.repo) + return { version: obj.version, error: 0 }; + } + return { version: '', error: 1 }; + } else + return { version: versions, error: error() }; } }, resources_update: { - args: { type: 'type' }, + args: { type: 'type', repo: 'repo' }, call: function(req) { if (req.args?.type) { - const type = shellquote(req.args?.type); - const exit_code = system(`${HP_DIR}/scripts/update_resources.sh ${type}`); + const type = shellquote(req.args?.type), + repo = shellquote(req.args?.repo); + const exit_code = system(`${HP_DIR}/scripts/update_resources.sh ${type} ${repo}`); return { status: exit_code }; } else return { status: 255, error: 'illegal type' }; } + }, + + clash_api_get_secret: { + call: function() { + const client_json = json(trim(readfile(`${RUN_DIR}/sing-box-c.json`))); + return { secret: client_json.experimental.clash_api.secret }; + } } }; diff --git a/small/luci-app-passwall/root/usr/share/passwall/app.sh b/small/luci-app-passwall/root/usr/share/passwall/app.sh index a1cc5bcb9d..530e212cc0 100755 --- a/small/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/small/luci-app-passwall/root/usr/share/passwall/app.sh @@ -658,8 +658,11 @@ run_chinadns_ng() { ([ -z "${_default_tag}" ] || [ "${_default_tag}" = "smart" ] || [ "${_default_tag}" = "none_noip" ]) && _default_tag="none" echo "default-tag ${_default_tag}" >> ${_CONF_FILE} + echo "cache 4096" >> ${_CONF_FILE} + echo "cache-stale 3600" >> ${_CONF_FILE} + [ "${_flag}" = "default" ] && [ "${_default_tag}" = "none" ] && { - echo "verdict-cache 4096" >> ${_CONF_FILE} + echo "verdict-cache 5000" >> ${_CONF_FILE} } ln_run "$(first_type chinadns-ng)" chinadns-ng "${_LOG_FILE}" -C ${_CONF_FILE} @@ -1379,7 +1382,6 @@ start_dns() { LOCAL_DNS=$(config_t_get global direct_dns_udp 223.5.5.5 | sed 's/:/#/g') china_ng_local_dns=${LOCAL_DNS} sing_box_local_dns="direct_dns_udp_server=${LOCAL_DNS}" - IPT_APPEND_DNS=${LOCAL_DNS} ;; tcp) LOCAL_DNS="127.0.0.1#${dns_listen_port}" @@ -1387,7 +1389,6 @@ start_dns() { local DIRECT_DNS=$(config_t_get global direct_dns_tcp 223.5.5.5 | sed 's/:/#/g') china_ng_local_dns="tcp://${DIRECT_DNS}" sing_box_local_dns="direct_dns_tcp_server=${DIRECT_DNS}" - IPT_APPEND_DNS="${LOCAL_DNS},${DIRECT_DNS}" ln_run "$(first_type dns2tcp)" dns2tcp "/dev/null" -L "${LOCAL_DNS}" -R "$(get_first_dns DIRECT_DNS 53)" -v echolog " - dns2tcp(${LOCAL_DNS}) -> tcp://$(get_first_dns DIRECT_DNS 53 | sed 's/#/:/g')" echolog " * 请确保上游直连 DNS 支持 TCP 查询。" @@ -1405,8 +1406,8 @@ start_dns() { local tmp_dot_ip=$(echo "$DIRECT_DNS" | sed -n 's/.*:\/\/\([^@#]*@\)*\([^@#]*\).*/\2/p') local tmp_dot_port=$(echo "$DIRECT_DNS" | sed -n 's/.*#\([0-9]\+\).*/\1/p') - sing_box_local_dns="direct_dns_dot_server=$tmp_dot_ip#${tmp_dot_port:-853}" - IPT_APPEND_DNS="${LOCAL_DNS},$tmp_dot_ip#${tmp_dot_port:-853}" + DIRECT_DNS=$tmp_dot_ip#${tmp_dot_port:-853} + sing_box_local_dns="direct_dns_dot_server=${DIRECT_DNS}" else echolog " - 你的ChinaDNS-NG版本不支持DoT,直连DNS将使用默认地址。" fi @@ -1417,6 +1418,21 @@ start_dns() { ;; esac + # 追加直连DNS到iptables/nftables + [ "$(config_t_get global_haproxy balancing_enable 0)" != "1" ] && IPT_APPEND_DNS= + add_default_port() { + [ -z "$1" ] && echo "" || echo "$1" | awk -F',' '{for(i=1;i<=NF;i++){if($i !~ /#/) $i=$i"#53";} print $0;}' OFS=',' + } + LOCAL_DNS=$(add_default_port "$LOCAL_DNS") + IPT_APPEND_DNS=$(add_default_port "${IPT_APPEND_DNS:-$LOCAL_DNS}") + echo "$IPT_APPEND_DNS" | grep -q -E "(^|,)$LOCAL_DNS(,|$)" || IPT_APPEND_DNS="${IPT_APPEND_DNS:+$IPT_APPEND_DNS,}$LOCAL_DNS" + [ -n "$DIRECT_DNS" ] && { + DIRECT_DNS=$(add_default_port "$DIRECT_DNS") + echo "$IPT_APPEND_DNS" | grep -q -E "(^|,)$DIRECT_DNS(,|$)" || IPT_APPEND_DNS="${IPT_APPEND_DNS:+$IPT_APPEND_DNS,}$DIRECT_DNS" + } + # 排除127.0.0.1的条目 + IPT_APPEND_DNS=$(echo "$IPT_APPEND_DNS" | awk -F',' '{for(i=1;i<=NF;i++) if($i !~ /^127\.0\.0\.1/) printf (i>1?",":"") $i; print ""}' | sed 's/^,\|,$//g') + TUN_DNS="127.0.0.1#${dns_listen_port}" [ "${resolve_dns}" == "1" ] && TUN_DNS="127.0.0.1#${resolve_dns_port}" diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 94c0605b31..e17955dd7a 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=944465ad5f3a3cccebf2930624f528cae3ca054f69295979cf4c4e002a575e90 endef -GEOSITE_VER:=20240905094227 +GEOSITE_VER:=20240905162746 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=8edb9186aea5ef40b310f29b89bcf2be67ea65b04c010b4cdb9ddb02408557f0 + HASH:=859306b7bc3a7891d5e0f5c8f38c2eaa8ede776c3a0aa1512b96c4956cf511c1 endef GEOSITE_IRAN_VER:=202409020032 diff --git a/v2ray-core/common/net/system.go b/v2ray-core/common/net/system.go index cb5657e966..e205e4222d 100644 --- a/v2ray-core/common/net/system.go +++ b/v2ray-core/common/net/system.go @@ -14,6 +14,7 @@ var ( DialUDP = net.DialUDP DialUnix = net.DialUnix FileConn = net.FileConn + FileListener = net.FileListener Listen = net.Listen ListenTCP = net.ListenTCP ListenUDP = net.ListenUDP diff --git a/v2ray-core/go.mod b/v2ray-core/go.mod index 5739511055..e20daff56a 100644 --- a/v2ray-core/go.mod +++ b/v2ray-core/go.mod @@ -15,7 +15,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/gopacket v1.1.19 github.com/gorilla/websocket v1.5.3 - github.com/jhump/protoreflect v1.16.0 + github.com/jhump/protoreflect v1.17.0 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/miekg/dns v1.1.62 github.com/mustafaturan/bus v1.0.2 @@ -34,10 +34,10 @@ require ( github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 go.starlark.net v0.0.0-20230612165344-9532f5667272 go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 - golang.org/x/crypto v0.26.0 - golang.org/x/net v0.28.0 + golang.org/x/crypto v0.27.0 + golang.org/x/net v0.29.0 golang.org/x/sync v0.8.0 - golang.org/x/sys v0.24.0 + golang.org/x/sys v0.25.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 @@ -51,7 +51,7 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d // indirect - github.com/bufbuild/protocompile v0.10.0 // indirect + github.com/bufbuild/protocompile v0.14.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect @@ -80,7 +80,7 @@ require ( go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.18.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect diff --git a/v2ray-core/go.sum b/v2ray-core/go.sum index e559d9ac3e..14786d32fd 100644 --- a/v2ray-core/go.sum +++ b/v2ray-core/go.sum @@ -34,8 +34,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI= github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI= -github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM= -github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -168,8 +168,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= -github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -366,8 +366,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -420,8 +420,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -469,8 +469,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -489,8 +489,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/v2ray-core/main/commands/run.go b/v2ray-core/main/commands/run.go index 066bfae184..2fbb7a2bce 100644 --- a/v2ray-core/main/commands/run.go +++ b/v2ray-core/main/commands/run.go @@ -14,6 +14,7 @@ import ( "github.com/v2fly/v2ray-core/v5/common/cmdarg" "github.com/v2fly/v2ray-core/v5/common/platform" "github.com/v2fly/v2ray-core/v5/main/commands/base" + "github.com/v2fly/v2ray-core/v5/main/plugins" ) // CmdRun runs V2Ray with config @@ -75,6 +76,12 @@ func setConfigFlags(cmd *base.Command) { func executeRun(cmd *base.Command, args []string) { setConfigFlags(cmd) + var pluginFuncs []func() error + for _, plugin := range plugins.Plugins { + if f := plugin(cmd); f != nil { + pluginFuncs = append(pluginFuncs, f) + } + } cmd.Flag.Parse(args) printVersion() configFiles = getConfigFilePath() @@ -83,6 +90,14 @@ func executeRun(cmd *base.Command, args []string) { base.Fatalf("Failed to start: %s", err) } + for _, f := range pluginFuncs { + go func(f func() error) { + if err := f(); err != nil { + log.Print(err) + } + }(f) + } + if err := server.Start(); err != nil { base.Fatalf("Failed to start: %s", err) } diff --git a/v2ray-core/main/distro/all/all.go b/v2ray-core/main/distro/all/all.go index 16ca28b09e..668f80c978 100644 --- a/v2ray-core/main/distro/all/all.go +++ b/v2ray-core/main/distro/all/all.go @@ -33,7 +33,6 @@ import ( // Developer preview features _ "github.com/v2fly/v2ray-core/v5/app/instman" _ "github.com/v2fly/v2ray-core/v5/app/observatory" - _ "github.com/v2fly/v2ray-core/v5/app/restfulapi" _ "github.com/v2fly/v2ray-core/v5/app/tun" // Inbound and outbound proxies. diff --git a/v2ray-core/main/plugins/plugin.go b/v2ray-core/main/plugins/plugin.go new file mode 100644 index 0000000000..58786b00ab --- /dev/null +++ b/v2ray-core/main/plugins/plugin.go @@ -0,0 +1,11 @@ +package plugins + +import "github.com/v2fly/v2ray-core/v5/main/commands/base" + +var Plugins []Plugin + +type Plugin func(*base.Command) func() error + +func RegisterPlugin(plugin Plugin) { + Plugins = append(Plugins, plugin) +} diff --git a/v2ray-core/main/plugins/plugin_pprof/plugin_pprof.go b/v2ray-core/main/plugins/plugin_pprof/plugin_pprof.go new file mode 100644 index 0000000000..5da1a7b75c --- /dev/null +++ b/v2ray-core/main/plugins/plugin_pprof/plugin_pprof.go @@ -0,0 +1,29 @@ +package plugin_pprof + +import ( + "github.com/v2fly/v2ray-core/v5/main/plugins" + "net/http" + "net/http/pprof" + + "github.com/v2fly/v2ray-core/v5/main/commands/base" +) + +var pprofPlugin plugins.Plugin = func(cmd *base.Command) func() error { + addr := cmd.Flag.String("pprof", "", "") + return func() error { + if *addr != "" { + h := http.NewServeMux() + h.HandleFunc("/debug/pprof/", pprof.Index) + h.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + h.HandleFunc("/debug/pprof/profile", pprof.Profile) + h.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + h.HandleFunc("/debug/pprof/trace", pprof.Trace) + return (&http.Server{Addr: *addr, Handler: h}).ListenAndServe() + } + return nil + } +} + +func init() { + plugins.RegisterPlugin(pprofPlugin) +} diff --git a/v2ray-core/transport/internet/socket_activation_other.go b/v2ray-core/transport/internet/socket_activation_other.go new file mode 100644 index 0000000000..5bb93136d6 --- /dev/null +++ b/v2ray-core/transport/internet/socket_activation_other.go @@ -0,0 +1,13 @@ +//go:build !unix +// +build !unix + +package internet + +import ( + "fmt" + "github.com/v2fly/v2ray-core/v5/common/net" +) + +func activateSocket(address string, f func(network, address string, fd uintptr)) (net.Listener, error) { + return nil, fmt.Errorf("socket activation is not supported on this platform") +} diff --git a/v2ray-core/transport/internet/socket_activation_unix.go b/v2ray-core/transport/internet/socket_activation_unix.go new file mode 100644 index 0000000000..6dd92ab2f5 --- /dev/null +++ b/v2ray-core/transport/internet/socket_activation_unix.go @@ -0,0 +1,63 @@ +//go:build unix +// +build unix + +package internet + +import ( + "fmt" + "os" + "path" + "strconv" + "syscall" + + "github.com/v2fly/v2ray-core/v5/common/net" +) + +func activateSocket(address string, f func(network, address string, fd uintptr)) (net.Listener, error) { + fd, err := strconv.Atoi(path.Base(address)) + if err != nil { + return nil, err + } + + err = syscall.SetNonblock(fd, true) + if err != nil { + return nil, err + } + + acceptConn, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_ACCEPTCONN) + if err != nil { + return nil, err + } + if acceptConn == 0 { + return nil, fmt.Errorf("socket '%s' has not been marked to accept connections", address) + } + + sockType, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TYPE) + if err != nil { + return nil, err + } + if sockType != syscall.SOCK_STREAM { + // XXX: currently only stream socks are supported + return nil, fmt.Errorf("socket '%s' is not a stream socket", address) + } + + ufd := uintptr(fd) + + sa, err := syscall.Getsockname(fd) + if err != nil { + return nil, err + } + switch sa := sa.(type) { + case *syscall.SockaddrInet4: + addr := net.TCPAddr{IP: sa.Addr[:], Port: sa.Port, Zone: ""} + f("tcp4", addr.String(), ufd) + case *syscall.SockaddrInet6: + addr := net.TCPAddr{IP: sa.Addr[:], Port: sa.Port, Zone: strconv.Itoa(int(sa.ZoneId))} + f("tcp6", addr.String(), ufd) + } + + file := os.NewFile(ufd, address) + defer file.Close() + + return net.FileListener(file) +} diff --git a/v2ray-core/transport/internet/system_listener.go b/v2ray-core/transport/internet/system_listener.go index 84f9f1e8af..e9ca184cef 100644 --- a/v2ray-core/transport/internet/system_listener.go +++ b/v2ray-core/transport/internet/system_listener.go @@ -36,29 +36,35 @@ func (l *combinedListener) Close() error { return l.Listener.Close() } +func getRawControlFunc(network, address string, ctx context.Context, sockopt *SocketConfig, controllers []controller) func(fd uintptr) { + return func(fd uintptr) { + if sockopt != nil { + if err := applyInboundSocketOptions(network, fd, sockopt); err != nil { + newError("failed to apply socket options to incoming connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + } + + setReusePort(fd) // nolint: staticcheck + + for _, controller := range controllers { + if err := controller(network, address, fd); err != nil { + newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) + } + } + } +} + func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []controller) func(network, address string, c syscall.RawConn) error { return func(network, address string, c syscall.RawConn) error { - return c.Control(func(fd uintptr) { - if sockopt != nil { - if err := applyInboundSocketOptions(network, fd, sockopt); err != nil { - newError("failed to apply socket options to incoming connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - } - - setReusePort(fd) // nolint: staticcheck - - for _, controller := range controllers { - if err := controller(network, address, fd); err != nil { - newError("failed to apply external controller").Base(err).WriteToLog(session.ExportIDToError(ctx)) - } - } - }) + return c.Control(getRawControlFunc(network, address, ctx, sockopt, controllers)) } } func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.Listener, error) { var lc net.ListenConfig var network, address string + var l net.Listener + var err error // callback is called after the Listen function returns // this is used to wrap the listener and do some post processing callback := func(l net.Listener, err error) (net.Listener, error) { @@ -93,6 +99,14 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S copy(fullAddr, address[1:]) address = string(fullAddr) } + } else if strings.HasPrefix(address, "/dev/fd/") { + // socket activation + l, err = activateSocket(address, func(network, address string, fd uintptr) { + getRawControlFunc(network, address, ctx, sockopt, dl.controllers)(fd) + }) + if err != nil { + return nil, err + } } else { // normal unix domain socket var fileMode *os.FileMode @@ -133,13 +147,18 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S } } - l, err := lc.Listen(ctx, network, address) - l, err = callback(l, err) - if err == nil && sockopt != nil && sockopt.AcceptProxyProtocol { + if l == nil { + l, err = lc.Listen(ctx, network, address) + l, err = callback(l, err) + if err != nil { + return nil, err + } + } + if sockopt != nil && sockopt.AcceptProxyProtocol { policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil } l = &proxyproto.Listener{Listener: l, Policy: policyFunc} } - return l, err + return l, nil } func (dl *DefaultListener) ListenPacket(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.PacketConn, error) { diff --git a/v2ray-core/transport/internet/tls/config.go b/v2ray-core/transport/internet/tls/config.go index 534055d7d9..8de83b938f 100644 --- a/v2ray-core/transport/internet/tls/config.go +++ b/v2ray-core/transport/internet/tls/config.go @@ -237,6 +237,10 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { ClientCAs: clientRoot, } + if c.AllowInsecureIfPinnedPeerCertificate && c.PinnedPeerCertificateChainSha256 != nil { + config.InsecureSkipVerify = true + } + for _, opt := range opts { opt(config) } diff --git a/v2ray-core/transport/internet/tls/config.pb.go b/v2ray-core/transport/internet/tls/config.pb.go index 9bb8894d73..6559d82efd 100644 --- a/v2ray-core/transport/internet/tls/config.pb.go +++ b/v2ray-core/transport/internet/tls/config.pb.go @@ -232,6 +232,8 @@ type Config struct { MinVersion Config_TLSVersion `protobuf:"varint,9,opt,name=min_version,json=minVersion,proto3,enum=v2ray.core.transport.internet.tls.Config_TLSVersion" json:"min_version,omitempty"` // Maximum TLS version to support. MaxVersion Config_TLSVersion `protobuf:"varint,10,opt,name=max_version,json=maxVersion,proto3,enum=v2ray.core.transport.internet.tls.Config_TLSVersion" json:"max_version,omitempty"` + // Whether or not to allow self-signed certificates when pinned_peer_certificate_chain_sha256 is present. + AllowInsecureIfPinnedPeerCertificate bool `protobuf:"varint,11,opt,name=allow_insecure_if_pinned_peer_certificate,json=allowInsecureIfPinnedPeerCertificate,proto3" json:"allow_insecure_if_pinned_peer_certificate,omitempty"` } func (x *Config) Reset() { @@ -336,6 +338,13 @@ func (x *Config) GetMaxVersion() Config_TLSVersion { return Config_Default } +func (x *Config) GetAllowInsecureIfPinnedPeerCertificate() bool { + if x != nil { + return x.AllowInsecureIfPinnedPeerCertificate + } + return false +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -367,7 +376,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x5f, 0x43, 0x4c, 0x49, - 0x45, 0x4e, 0x54, 0x10, 0x03, 0x22, 0xd9, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x45, 0x4e, 0x54, 0x10, 0x03, 0x22, 0xb2, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x06, 0x82, 0xb5, 0x18, 0x02, 0x28, 0x01, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, @@ -406,22 +415,28 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x54, 0x4c, 0x53, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x49, 0x0a, - 0x0a, 0x54, 0x4c, 0x53, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x44, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, 0x31, - 0x5f, 0x30, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x31, 0x10, 0x02, - 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x32, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, - 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x33, 0x10, 0x04, 0x3a, 0x17, 0x82, 0xb5, 0x18, 0x13, 0x0a, 0x08, - 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x12, 0x03, 0x74, 0x6c, 0x73, 0x90, 0xff, 0x29, - 0x01, 0x42, 0x84, 0x01, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x35, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, - 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, - 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x21, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, - 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x57, 0x0a, + 0x29, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, + 0x69, 0x66, 0x5f, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x24, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x49, + 0x66, 0x50, 0x69, 0x6e, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x22, 0x49, 0x0a, 0x0a, 0x54, 0x4c, 0x53, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x10, + 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x30, 0x10, 0x01, 0x12, 0x0a, 0x0a, + 0x06, 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x31, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, + 0x31, 0x5f, 0x32, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x54, 0x4c, 0x53, 0x31, 0x5f, 0x33, 0x10, + 0x04, 0x3a, 0x17, 0x82, 0xb5, 0x18, 0x13, 0x0a, 0x08, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, + 0x79, 0x12, 0x03, 0x74, 0x6c, 0x73, 0x90, 0xff, 0x29, 0x01, 0x42, 0x84, 0x01, 0x0a, 0x25, 0x63, + 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, + 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x21, + 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, + 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -438,7 +453,7 @@ func file_transport_internet_tls_config_proto_rawDescGZIP() []byte { var file_transport_internet_tls_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_transport_internet_tls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_transport_internet_tls_config_proto_goTypes = []interface{}{ +var file_transport_internet_tls_config_proto_goTypes = []any{ (Certificate_Usage)(0), // 0: v2ray.core.transport.internet.tls.Certificate.Usage (Config_TLSVersion)(0), // 1: v2ray.core.transport.internet.tls.Config.TLSVersion (*Certificate)(nil), // 2: v2ray.core.transport.internet.tls.Certificate @@ -462,7 +477,7 @@ func file_transport_internet_tls_config_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_transport_internet_tls_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_transport_internet_tls_config_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Certificate); i { case 0: return &v.state @@ -474,7 +489,7 @@ func file_transport_internet_tls_config_proto_init() { return nil } } - file_transport_internet_tls_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_transport_internet_tls_config_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Config); i { case 0: return &v.state diff --git a/v2ray-core/transport/internet/tls/config.proto b/v2ray-core/transport/internet/tls/config.proto index 1573f07a81..8c9f92e55d 100644 --- a/v2ray-core/transport/internet/tls/config.proto +++ b/v2ray-core/transport/internet/tls/config.proto @@ -76,4 +76,6 @@ message Config { // Maximum TLS version to support. TLSVersion max_version = 10; + // Whether or not to allow self-signed certificates when pinned_peer_certificate_chain_sha256 is present. + bool allow_insecure_if_pinned_peer_certificate = 11; } diff --git a/v2rayn/v2rayN/ServiceLib/Common/YamlUtils.cs b/v2rayn/v2rayN/ServiceLib/Common/YamlUtils.cs index 7fd4c67e8c..f87fd63c68 100644 --- a/v2rayn/v2rayN/ServiceLib/Common/YamlUtils.cs +++ b/v2rayn/v2rayN/ServiceLib/Common/YamlUtils.cs @@ -1,4 +1,5 @@ -using YamlDotNet.Serialization; +using YamlDotNet.Core; +using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace ServiceLib.Common @@ -35,13 +36,17 @@ namespace ServiceLib.Common /// /// /// - public static string ToYaml(Object obj) + public static string ToYaml(Object? obj) { + string result = string.Empty; + if (obj == null) + { + return result; + } var serializer = new SerializerBuilder() .WithNamingConvention(HyphenatedNamingConvention.Instance) .Build(); - string result = string.Empty; try { result = serializer.Serialize(obj); @@ -53,6 +58,24 @@ namespace ServiceLib.Common return result; } + public static string? PreprocessYaml(string str) + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(PascalCaseNamingConvention.Instance) + .Build(); + try + { + var mergingParser = new MergingParser(new Parser(new StringReader(str))); + var obj = new DeserializerBuilder().Build().Deserialize(mergingParser); + return ToYaml(obj); + } + catch (Exception ex) + { + Logging.SaveLog("PreprocessYaml", ex); + return null; + } + } + #endregion YAML } } \ No newline at end of file diff --git a/v2rayn/v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigClash.cs b/v2rayn/v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigClash.cs index 878e7d7c48..9e84f8efd5 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigClash.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/CoreConfig/CoreConfigClash.cs @@ -64,6 +64,12 @@ var txtFile = File.ReadAllText(addressFileName); txtFile = txtFile.Replace(tagYamlStr1, tagYamlStr2); + //YAML anchors + if (txtFile.Contains("<<:") && txtFile.Contains("*") && txtFile.Contains("&")) + { + txtFile = YamlUtils.PreprocessYaml(txtFile); + } + var fileContent = YamlUtils.FromYaml>(txtFile); if (fileContent == null) { diff --git a/v2rayn/v2rayN/ServiceLib/Handler/UpdateHandler.cs b/v2rayn/v2rayN/ServiceLib/Handler/UpdateHandler.cs index d025ea0069..48c4f5e94d 100644 --- a/v2rayn/v2rayN/ServiceLib/Handler/UpdateHandler.cs +++ b/v2rayn/v2rayN/ServiceLib/Handler/UpdateHandler.cs @@ -437,6 +437,15 @@ namespace ServiceLib.Handler { if (Utils.IsWindows()) { + //Check for standalone windows .Net version + if (coreInfo?.coreType == ECoreType.v2rayN + && File.Exists(Path.Combine(Utils.StartupPath(), "wpfgfx_cor3.dll")) + && File.Exists(Path.Combine(Utils.StartupPath(), "D3DCompiler_47_cor3.dll")) + ) + { + return coreInfo?.coreDownloadUrl64.Replace("v2rayN.zip", "zz_v2rayN-SelfContained.zip"); + } + return RuntimeInformation.ProcessArchitecture switch { Architecture.Arm64 => coreInfo?.coreDownloadUrlArm64, diff --git a/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs b/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs index b1e3b7989c..da18f0cec9 100644 --- a/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs +++ b/v2rayn/v2rayN/ServiceLib/ViewModels/CheckUpdateViewModel.cs @@ -11,6 +11,7 @@ namespace ServiceLib.ViewModels public class CheckUpdateViewModel : MyReactiveObject { private const string _geo = "GeoFiles"; + private string _v2rayN = ECoreType.v2rayN.ToString(); private List _lstUpdated = []; private IObservableCollection _checkUpdateItem = new ObservableCollectionExtended(); @@ -52,7 +53,7 @@ namespace ServiceLib.ViewModels _checkUpdateItem.Add(new CheckUpdateItem() { isSelected = false, - coreType = ECoreType.v2rayN.ToString(), + coreType = _v2rayN, remarks = ResUI.menuCheckUpdate, }); _checkUpdateItem.Add(new CheckUpdateItem() @@ -98,7 +99,7 @@ namespace ServiceLib.ViewModels { await CheckUpdateGeo(); } - else if (item.coreType == ECoreType.v2rayN.ToString()) + else if (item.coreType == _v2rayN) { await CheckUpdateN(EnableCheckPreReleaseUpdate); } @@ -147,29 +148,29 @@ namespace ServiceLib.ViewModels private async Task CheckUpdateN(bool preRelease) { - //Check for standalone windows .Net version - if (Utils.IsWindows() - && File.Exists(Path.Combine(Utils.StartupPath(), "wpfgfx_cor3.dll")) - && File.Exists(Path.Combine(Utils.StartupPath(), "D3DCompiler_47_cor3.dll")) - ) - { - UpdateView(ResUI.UpdateStandalonePackageTip, ResUI.UpdateStandalonePackageTip); - return; - } + ////Check for standalone windows .Net version + //if (Utils.IsWindows() + // && File.Exists(Path.Combine(Utils.StartupPath(), "wpfgfx_cor3.dll")) + // && File.Exists(Path.Combine(Utils.StartupPath(), "D3DCompiler_47_cor3.dll")) + // ) + //{ + // UpdateView(_v2rayN, ResUI.UpdateStandalonePackageTip); + // return; + //} void _updateUI(bool success, string msg) { - UpdateView(ECoreType.v2rayN.ToString(), msg); + UpdateView(_v2rayN, msg); if (success) { - UpdateView(ECoreType.v2rayN.ToString(), ResUI.OperationSuccess); - UpdatedPlusPlus(ECoreType.v2rayN.ToString(), msg); + UpdateView(_v2rayN, ResUI.OperationSuccess); + UpdatedPlusPlus(_v2rayN, msg); } } await (new UpdateHandler()).CheckUpdateGuiN(_config, _updateUI, preRelease) .ContinueWith(t => { - UpdatedPlusPlus(ECoreType.v2rayN.ToString(), ""); + UpdatedPlusPlus(_v2rayN, ""); }); } @@ -201,7 +202,7 @@ namespace ServiceLib.ViewModels Task.Delay(1000); UpgradeCore(); - if (_lstUpdated.Any(x => x.coreType == ECoreType.v2rayN.ToString() && x.isFinished == true)) + if (_lstUpdated.Any(x => x.coreType == _v2rayN && x.isFinished == true)) { Task.Delay(1000); UpgradeN(); @@ -228,7 +229,7 @@ namespace ServiceLib.ViewModels { try { - var fileName = _lstUpdated.FirstOrDefault(x => x.coreType == ECoreType.v2rayN.ToString())?.fileName; + var fileName = _lstUpdated.FirstOrDefault(x => x.coreType == _v2rayN)?.fileName; if (fileName.IsNullOrEmpty()) { return; @@ -251,7 +252,7 @@ namespace ServiceLib.ViewModels } catch (Exception ex) { - UpdateView(ECoreType.v2rayN.ToString(), ex.Message); + UpdateView(_v2rayN, ex.Message); } } diff --git a/v2rayn/v2rayN/v2rayN/Common/UI.cs b/v2rayn/v2rayN/v2rayN/Common/UI.cs index b89cdf79f5..29caed2246 100644 --- a/v2rayn/v2rayN/v2rayN/Common/UI.cs +++ b/v2rayn/v2rayN/v2rayN/Common/UI.cs @@ -35,5 +35,25 @@ namespace v2rayN return true; } + + public static bool? SaveFileDialog(out string fileName, string filter) + { + fileName = string.Empty; + + SaveFileDialog fileDialog = new() + { + Filter = filter, + FilterIndex = 2, + RestoreDirectory = true + }; + if (fileDialog.ShowDialog() != true) + { + return false; + } + + fileName = fileDialog.FileName; + + return true; + } } } \ No newline at end of file diff --git a/v2rayn/v2rayN/v2rayN/Views/ProfilesView.xaml.cs b/v2rayn/v2rayN/v2rayN/Views/ProfilesView.xaml.cs index bffda55d17..b6d0ee76d2 100644 --- a/v2rayn/v2rayN/v2rayN/Views/ProfilesView.xaml.cs +++ b/v2rayn/v2rayN/v2rayN/Views/ProfilesView.xaml.cs @@ -129,17 +129,11 @@ namespace v2rayN.Views case EViewAction.SaveFileDialog: if (obj is null) return false; - SaveFileDialog fileDialog = new() - { - Filter = "Config|*.json", - FilterIndex = 2, - RestoreDirectory = true - }; - if (fileDialog.ShowDialog() != true) + if (UI.SaveFileDialog(out string fileName, "Config|*.json") != true) { return false; } - ViewModel?.Export2ClientConfigResult(fileDialog.FileName, (ProfileItem)obj); + ViewModel?.Export2ClientConfigResult(fileName, (ProfileItem)obj); break; case EViewAction.AddServerWindow: diff --git a/v2rayn/v2rayN/v2rayUpgrade/Program.cs b/v2rayn/v2rayN/v2rayUpgrade/Program.cs index 906bbde099..ab351ec523 100644 --- a/v2rayn/v2rayN/v2rayUpgrade/Program.cs +++ b/v2rayn/v2rayN/v2rayUpgrade/Program.cs @@ -66,7 +66,7 @@ namespace v2rayUpgrade { string thisAppOldFile = $"{GetExePath()}.tmp"; File.Delete(thisAppOldFile); - string startKey = "v2rayN/"; + string splitKey = "/"; using ZipArchive archive = ZipFile.OpenRead(fileName); foreach (ZipArchiveEntry entry in archive.Entries) @@ -77,11 +77,11 @@ namespace v2rayUpgrade { continue; } - string fullName = entry.FullName; - if (fullName.StartsWith(startKey)) - { - fullName = fullName[startKey.Length..]; - } + + var lst = entry.FullName.Split(splitKey); + if (lst.Length == 1) continue; + string fullName = string.Join(splitKey, lst[1..lst.Length]); + if (string.Equals(GetExePath(), GetPath(fullName), StringComparison.OrdinalIgnoreCase)) { File.Move(GetExePath(), thisAppOldFile); @@ -90,6 +90,8 @@ namespace v2rayUpgrade string entryOutputPath = GetPath(fullName); Directory.CreateDirectory(Path.GetDirectoryName(entryOutputPath)!); entry.ExtractToFile(entryOutputPath, true); + + Console.WriteLine(entryOutputPath); } catch (Exception ex) { @@ -104,11 +106,12 @@ namespace v2rayUpgrade } if (sb.Length > 0) { - Console.WriteLine("Upgrade Failed,Hold ctrl + c to copy to clipboard.\n" + - "(升级失败,按住ctrl+c可以复制到剪贴板)." + sb.ToString()); + Console.WriteLine("Upgrade Failed.\n" + + "(升级失败)." + sb.ToString()); return; } + Console.WriteLine("Start v2rayN, please wait...(正在重启,请等待)"); Process.Start("v2rayN"); } diff --git a/xray-core/go.mod b/xray-core/go.mod index d90c87f471..fb2e7ca598 100644 --- a/xray-core/go.mod +++ b/xray-core/go.mod @@ -22,10 +22,10 @@ require ( github.com/vishvananda/netlink v1.3.0 github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.26.0 - golang.org/x/net v0.28.0 + golang.org/x/crypto v0.27.0 + golang.org/x/net v0.29.0 golang.org/x/sync v0.8.0 - golang.org/x/sys v0.24.0 + golang.org/x/sys v0.25.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 google.golang.org/grpc v1.66.0 google.golang.org/protobuf v1.34.2 @@ -38,7 +38,6 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect - github.com/francoispqt/gojay v1.2.13 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect @@ -52,7 +51,7 @@ require ( go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/mod v0.18.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect diff --git a/xray-core/go.sum b/xray-core/go.sum index c2011cc5aa..7900f26d06 100644 --- a/xray-core/go.sum +++ b/xray-core/go.sum @@ -1,115 +1,51 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= -dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= -dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= -dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= -dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I= github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= -github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g= github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y= @@ -118,48 +54,18 @@ github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk= github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4= github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= -github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= -github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= -github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= -github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= -github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= -github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= -github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= -github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= -github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= -github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= -github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= -github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= -github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= -github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= -github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= -github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= -github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= -github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= -github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= @@ -167,59 +73,29 @@ github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZla github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg= github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -227,24 +103,17 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= @@ -256,32 +125,14 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -289,15 +140,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE= gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo= h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= -sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/xray-core/infra/conf/transport.go b/xray-core/infra/conf/transport.go deleted file mode 100644 index 7957ee7d38..0000000000 --- a/xray-core/infra/conf/transport.go +++ /dev/null @@ -1,106 +0,0 @@ -package conf - -import ( - "github.com/xtls/xray-core/common/errors" - "github.com/xtls/xray-core/common/serial" - "github.com/xtls/xray-core/transport/global" - "github.com/xtls/xray-core/transport/internet" -) - -type TransportConfig struct { - TCPConfig *TCPConfig `json:"tcpSettings"` - KCPConfig *KCPConfig `json:"kcpSettings"` - WSConfig *WebSocketConfig `json:"wsSettings"` - HTTPConfig *HTTPConfig `json:"httpSettings"` - GRPCConfig *GRPCConfig `json:"grpcSettings"` - GUNConfig *GRPCConfig `json:"gunSettings"` - HTTPUPGRADEConfig *HttpUpgradeConfig `json:"httpupgradeSettings"` - SplitHTTPConfig *SplitHTTPConfig `json:"splithttpSettings"` -} - -// Build implements Buildable. -func (c *TransportConfig) Build() (*global.Config, error) { - config := new(global.Config) - - if c.TCPConfig != nil { - ts, err := c.TCPConfig.Build() - if err != nil { - return nil, errors.New("failed to build TCP config").Base(err).AtError() - } - config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ - ProtocolName: "tcp", - Settings: serial.ToTypedMessage(ts), - }) - } - - if c.KCPConfig != nil { - ts, err := c.KCPConfig.Build() - if err != nil { - return nil, errors.New("failed to build mKCP config").Base(err).AtError() - } - config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ - ProtocolName: "mkcp", - Settings: serial.ToTypedMessage(ts), - }) - } - - if c.WSConfig != nil { - ts, err := c.WSConfig.Build() - if err != nil { - return nil, errors.New("failed to build WebSocket config").Base(err) - } - config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ - ProtocolName: "websocket", - Settings: serial.ToTypedMessage(ts), - }) - } - - if c.HTTPConfig != nil { - ts, err := c.HTTPConfig.Build() - if err != nil { - return nil, errors.New("Failed to build HTTP config.").Base(err) - } - config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ - ProtocolName: "http", - Settings: serial.ToTypedMessage(ts), - }) - } - - if c.GRPCConfig == nil { - c.GRPCConfig = c.GUNConfig - } - if c.GRPCConfig != nil { - gs, err := c.GRPCConfig.Build() - if err != nil { - return nil, errors.New("Failed to build gRPC config.").Base(err) - } - config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ - ProtocolName: "grpc", - Settings: serial.ToTypedMessage(gs), - }) - } - - if c.HTTPUPGRADEConfig != nil { - hs, err := c.HTTPUPGRADEConfig.Build() - if err != nil { - return nil, errors.New("failed to build HttpUpgrade config").Base(err) - } - config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ - ProtocolName: "httpupgrade", - Settings: serial.ToTypedMessage(hs), - }) - } - - if c.SplitHTTPConfig != nil { - shs, err := c.SplitHTTPConfig.Build() - if err != nil { - return nil, errors.New("failed to build SplitHTTP config").Base(err) - } - config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ - ProtocolName: "splithttp", - Settings: serial.ToTypedMessage(shs), - }) - } - - return config, nil -} diff --git a/xray-core/infra/conf/transport_test.go b/xray-core/infra/conf/transport_test.go index 86f707bd7c..e747008298 100644 --- a/xray-core/infra/conf/transport_test.go +++ b/xray-core/infra/conf/transport_test.go @@ -4,16 +4,8 @@ import ( "encoding/json" "testing" - "github.com/xtls/xray-core/common/serial" . "github.com/xtls/xray-core/infra/conf" - "github.com/xtls/xray-core/transport/global" "github.com/xtls/xray-core/transport/internet" - "github.com/xtls/xray-core/transport/internet/grpc" - "github.com/xtls/xray-core/transport/internet/headers/http" - "github.com/xtls/xray-core/transport/internet/headers/noop" - "github.com/xtls/xray-core/transport/internet/kcp" - "github.com/xtls/xray-core/transport/internet/tcp" - "github.com/xtls/xray-core/transport/internet/websocket" "google.golang.org/protobuf/proto" ) @@ -157,139 +149,3 @@ func TestSocketConfig(t *testing.T) { t.Fatalf("unexpected parsed TFO value, which should be -1") } } - -func TestTransportConfig(t *testing.T) { - createParser := func() func(string) (proto.Message, error) { - return func(s string) (proto.Message, error) { - config := new(TransportConfig) - if err := json.Unmarshal([]byte(s), config); err != nil { - return nil, err - } - return config.Build() - } - } - - runMultiTestCase(t, []TestCase{ - { - Input: `{ - "tcpSettings": { - "header": { - "type": "http", - "request": { - "version": "1.1", - "method": "GET", - "path": "/b", - "headers": { - "a": "b", - "c": "d" - } - }, - "response": { - "version": "1.0", - "status": "404", - "reason": "Not Found" - } - } - }, - "kcpSettings": { - "mtu": 1200, - "header": { - "type": "none" - } - }, - "wsSettings": { - "path": "/t" - }, - "grpcSettings": { - "serviceName": "name", - "multiMode": true - } - }`, - Parser: createParser(), - Output: &global.Config{ - TransportSettings: []*internet.TransportConfig{ - { - ProtocolName: "tcp", - Settings: serial.ToTypedMessage(&tcp.Config{ - HeaderSettings: serial.ToTypedMessage(&http.Config{ - Request: &http.RequestConfig{ - Version: &http.Version{Value: "1.1"}, - Method: &http.Method{Value: "GET"}, - Uri: []string{"/b"}, - Header: []*http.Header{ - {Name: "a", Value: []string{"b"}}, - {Name: "c", Value: []string{"d"}}, - }, - }, - Response: &http.ResponseConfig{ - Version: &http.Version{Value: "1.0"}, - Status: &http.Status{Code: "404", Reason: "Not Found"}, - Header: []*http.Header{ - { - Name: "Content-Type", - Value: []string{"application/octet-stream", "video/mpeg"}, - }, - { - Name: "Transfer-Encoding", - Value: []string{"chunked"}, - }, - { - Name: "Connection", - Value: []string{"keep-alive"}, - }, - { - Name: "Pragma", - Value: []string{"no-cache"}, - }, - { - Name: "Cache-Control", - Value: []string{"private", "no-cache"}, - }, - }, - }, - }), - }), - }, - { - ProtocolName: "mkcp", - Settings: serial.ToTypedMessage(&kcp.Config{ - Mtu: &kcp.MTU{Value: 1200}, - HeaderConfig: serial.ToTypedMessage(&noop.Config{}), - }), - }, - { - ProtocolName: "websocket", - Settings: serial.ToTypedMessage(&websocket.Config{ - Path: "/t", - }), - }, - { - ProtocolName: "grpc", - Settings: serial.ToTypedMessage(&grpc.Config{ - ServiceName: "name", - MultiMode: true, - }), - }, - }, - }, - }, - { - Input: `{ - "gunSettings": { - "serviceName": "name" - } - }`, - Parser: createParser(), - Output: &global.Config{ - TransportSettings: []*internet.TransportConfig{ - { - ProtocolName: "grpc", - Settings: serial.ToTypedMessage(&grpc.Config{ - ServiceName: "name", - }), - }, - }, - }, - }, - }) -} diff --git a/xray-core/infra/conf/xray.go b/xray-core/infra/conf/xray.go index 9488589ca0..e0f838c87e 100644 --- a/xray-core/infra/conf/xray.go +++ b/xray-core/infra/conf/xray.go @@ -405,12 +405,15 @@ type Config struct { // and should not be used. OutboundDetours []OutboundDetourConfig `json:"outboundDetour"` + // Deprecated: Global transport config is no longer used + // left for returning error + Transport map[string]json.RawMessage `json:"transport"` + LogConfig *LogConfig `json:"log"` RouterConfig *RouterConfig `json:"routing"` DNSConfig *DNSConfig `json:"dns"` InboundConfigs []InboundDetourConfig `json:"inbounds"` OutboundConfigs []OutboundDetourConfig `json:"outbounds"` - Transport *TransportConfig `json:"transport"` Policy *PolicyConfig `json:"policy"` API *APIConfig `json:"api"` Metrics *MetricsConfig `json:"metrics"` @@ -540,27 +543,6 @@ func (c *Config) Override(o *Config, fn string) { } } -func applyTransportConfig(s *StreamConfig, t *TransportConfig) { - if s.TCPSettings == nil { - s.TCPSettings = t.TCPConfig - } - if s.KCPSettings == nil { - s.KCPSettings = t.KCPConfig - } - if s.WSSettings == nil { - s.WSSettings = t.WSConfig - } - if s.HTTPSettings == nil { - s.HTTPSettings = t.HTTPConfig - } - if s.HTTPUPGRADESettings == nil { - s.HTTPUPGRADESettings = t.HTTPUPGRADEConfig - } - if s.SplitHTTPSettings == nil { - s.SplitHTTPSettings = t.SplitHTTPConfig - } -} - // Build implements Buildable. func (c *Config) Build() (*core.Config, error) { if err := PostProcessConfigureFile(c); err != nil { @@ -685,13 +667,11 @@ func (c *Config) Build() (*core.Config, error) { }}} } + if len(c.Transport) > 0 { + return nil, errors.New("Global transport config is deprecated") + } + for _, rawInboundConfig := range inbounds { - if c.Transport != nil { - if rawInboundConfig.StreamSetting == nil { - rawInboundConfig.StreamSetting = &StreamConfig{} - } - applyTransportConfig(rawInboundConfig.StreamSetting, c.Transport) - } ic, err := rawInboundConfig.Build() if err != nil { return nil, err @@ -714,12 +694,6 @@ func (c *Config) Build() (*core.Config, error) { } for _, rawOutboundConfig := range outbounds { - if c.Transport != nil { - if rawOutboundConfig.StreamSetting == nil { - rawOutboundConfig.StreamSetting = &StreamConfig{} - } - applyTransportConfig(rawOutboundConfig.StreamSetting, c.Transport) - } oc, err := rawOutboundConfig.Build() if err != nil { return nil, err diff --git a/xray-core/infra/conf/xray_test.go b/xray-core/infra/conf/xray_test.go index 03e2714212..0e0b28f404 100644 --- a/xray-core/infra/conf/xray_test.go +++ b/xray-core/infra/conf/xray_test.go @@ -23,7 +23,6 @@ import ( "github.com/xtls/xray-core/proxy/vmess" "github.com/xtls/xray-core/proxy/vmess/inbound" "github.com/xtls/xray-core/transport/internet" - "github.com/xtls/xray-core/transport/internet/http" "github.com/xtls/xray-core/transport/internet/tls" "github.com/xtls/xray-core/transport/internet/websocket" "google.golang.org/protobuf/proto" @@ -128,11 +127,6 @@ func TestXrayConfig(t *testing.T) { } ] } - }, - "transport": { - "httpSettings": { - "path": "/test" - } } }`, Parser: createParser(), @@ -172,17 +166,6 @@ func TestXrayConfig(t *testing.T) { Outbound: []*core.OutboundHandlerConfig{ { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ - StreamSettings: &internet.StreamConfig{ - ProtocolName: "tcp", - TransportSettings: []*internet.TransportConfig{ - { - ProtocolName: "http", - Settings: serial.ToTypedMessage(&http.Config{ - Path: "/test", - }), - }, - }, - }, }), ProxySettings: serial.ToTypedMessage(&freedom.Config{ DomainStrategy: freedom.Config_AS_IS, @@ -192,33 +175,11 @@ func TestXrayConfig(t *testing.T) { { Tag: "blocked", SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ - StreamSettings: &internet.StreamConfig{ - ProtocolName: "tcp", - TransportSettings: []*internet.TransportConfig{ - { - ProtocolName: "http", - Settings: serial.ToTypedMessage(&http.Config{ - Path: "/test", - }), - }, - }, - }, }), ProxySettings: serial.ToTypedMessage(&blackhole.Config{}), }, { SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{ - StreamSettings: &internet.StreamConfig{ - ProtocolName: "tcp", - TransportSettings: []*internet.TransportConfig{ - { - ProtocolName: "http", - Settings: serial.ToTypedMessage(&http.Config{ - Path: "/test", - }), - }, - }, - }, }), ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{ Server: &net.Endpoint{}, @@ -242,12 +203,6 @@ func TestXrayConfig(t *testing.T) { }, }), }, - { - ProtocolName: "http", - Settings: serial.ToTypedMessage(&http.Config{ - Path: "/test", - }), - }, }, SecurityType: "xray.transport.internet.tls.Config", SecuritySettings: []*serial.TypedMessage{ @@ -295,12 +250,6 @@ func TestXrayConfig(t *testing.T) { }, }), }, - { - ProtocolName: "http", - Settings: serial.ToTypedMessage(&http.Config{ - Path: "/test", - }), - }, }, SecurityType: "xray.transport.internet.tls.Config", SecuritySettings: []*serial.TypedMessage{ @@ -387,7 +336,6 @@ func TestConfig_Override(t *testing.T) { LogConfig: &LogConfig{}, RouterConfig: &RouterConfig{}, DNSConfig: &DNSConfig{}, - Transport: &TransportConfig{}, Policy: &PolicyConfig{}, API: &APIConfig{}, Stats: &StatsConfig{}, @@ -398,7 +346,6 @@ func TestConfig_Override(t *testing.T) { LogConfig: &LogConfig{}, RouterConfig: &RouterConfig{}, DNSConfig: &DNSConfig{}, - Transport: &TransportConfig{}, Policy: &PolicyConfig{}, API: &APIConfig{}, Stats: &StatsConfig{}, diff --git a/yass/scripts/build-rust.sh b/yass/scripts/build-rust.sh index 1851ad7fff..09730f9ca8 100755 --- a/yass/scripts/build-rust.sh +++ b/yass/scripts/build-rust.sh @@ -6,8 +6,8 @@ cd $PWD/.. DEFAULT_TARGET=$(rustc -vV | sed -n 's|host: ||p') -RUST_VER=1.80.1 -CARGO_VER=0.81.0 +RUST_VER=1.81.0 +CARGO_VER=0.82.0 # https://github.com/Homebrew/homebrew-core/blob/master/Formula/r/rust.rb curl -L -O -C - https://static.rust-lang.org/dist/rustc-$RUST_VER-src.tar.xz @@ -24,7 +24,7 @@ cat > rustc-$RUST_VER-src/config.toml.in << EOF profile = "compiler" # latest change id in src/bootstrap/src/utils/change_tracker.rs -change-id = 125535 +change-id = 127866 [build] # see https://github.com/llvm/llvm-project/issues/60115 diff --git a/yass/scripts/setup-android-rust.sh b/yass/scripts/setup-android-rust.sh index 4b7c72700a..98ae3e250e 100755 --- a/yass/scripts/setup-android-rust.sh +++ b/yass/scripts/setup-android-rust.sh @@ -14,8 +14,8 @@ fi echo "Adding rustup toolchain..." -rustup toolchain install 1.80.1 -rustup default 1.80.1 +rustup toolchain install 1.81.0 +rustup default 1.81.0 echo "Adding rustup toolchain...done" diff --git a/yass/scripts/setup-ios-rust.sh b/yass/scripts/setup-ios-rust.sh index f8e2a5aed9..d7ab7ad4cb 100755 --- a/yass/scripts/setup-ios-rust.sh +++ b/yass/scripts/setup-ios-rust.sh @@ -9,8 +9,8 @@ fi echo "Adding rustup toolchain..." -rustup toolchain install 1.80.1 -rustup default 1.80.1 +rustup toolchain install 1.81.0 +rustup default 1.81.0 echo "Adding rustup toolchain...done" diff --git a/yass/third_party/tun2proxy/Cargo.toml b/yass/third_party/tun2proxy/Cargo.toml index 7ff95da05b..83cf2590d0 100644 --- a/yass/third_party/tun2proxy/Cargo.toml +++ b/yass/third_party/tun2proxy/Cargo.toml @@ -8,19 +8,18 @@ version = "0.1.12" crate-type = ["staticlib", "lib"] [dependencies] -base64 = { version = "0.21" } -clap = { version = "4.4", features = ["derive"] } -ctrlc2 = { version = "3.5", features = ["termination"] } +base64 = { version = "0.22" } +clap = { version = "4", features = ["derive"] } +ctrlc2 = { version = "3", features = ["termination"] } digest_auth = "0.3" dotenvy = "0.15" -env_logger = "0.10" +env_logger = "0.11" hashlink = "0.9" -httparse = "1.8" +httparse = "1" libc = "0.2" log = "0.4" mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] } -nix = { version = "0.27", features = [ - "process", +nix = { version = "0.29", default-features = false, features = [ "signal", "fs", "mount", @@ -29,16 +28,16 @@ nix = { version = "0.27", features = [ prctl = "1.0" smoltcp = { version = "0.11", features = ["std", "phy-tuntap_interface"] } socks5-impl = { version = "0.5", default-features = false } -thiserror = "1.0" +thiserror = "1" trust-dns-proto = "0.23" -unicase = "2.7" -url = "2.5" +unicase = "2" +url = "2" [target.'cfg(target_family="unix")'.dependencies] fork = "0.1" [target.'cfg(target_os="android")'.dependencies] -android_logger = "0.13" +android_logger = "0.14" jni = { version = "0.21", default-features = false } [target.'cfg(all(target_os = "linux", target_env = "ohos"))'.dependencies] diff --git a/yass/third_party/tun2proxy/src/setup.rs b/yass/third_party/tun2proxy/src/setup.rs index c3c69f1693..72ff484da6 100644 --- a/yass/third_party/tun2proxy/src/setup.rs +++ b/yass/third_party/tun2proxy/src/setup.rs @@ -9,7 +9,7 @@ use std::{ fs, io::BufRead, net::{Ipv4Addr, Ipv6Addr}, - os::unix::io::RawFd, + os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, process::{Command, Output}, str::FromStr, }; @@ -152,7 +152,7 @@ impl Setup { Ok(false) } - fn write_buffer_to_fd(fd: RawFd, data: &[u8]) -> Result<(), Error> { + fn write_buffer_to_fd(fd: &OwnedFd, data: &[u8]) -> Result<(), Error> { let mut written = 0; loop { if written >= data.len() { @@ -163,10 +163,10 @@ impl Setup { Ok(()) } - fn write_nameserver(fd: RawFd) -> Result<(), Error> { + fn write_nameserver(fd: &OwnedFd) -> Result<(), Error> { let data = "nameserver 198.18.0.1\n".as_bytes(); Self::write_buffer_to_fd(fd, data)?; - nix::sys::stat::fchmod(fd, nix::sys::stat::Mode::from_bits(0o444).unwrap())?; + nix::sys::stat::fchmod(fd.as_raw_fd(), nix::sys::stat::Mode::from_bits(0o444).unwrap())?; Ok(()) } @@ -176,7 +176,9 @@ impl Setup { nix::fcntl::OFlag::O_RDWR | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_CREAT, nix::sys::stat::Mode::from_bits(0o644).unwrap(), )?; - Self::write_nameserver(fd)?; + let owned_fd = unsafe { OwnedFd::from_raw_fd(fd) }; + Self::write_nameserver(&owned_fd)?; + fd = owned_fd.into_raw_fd(); let source = format!("/proc/self/fd/{}", fd); if Ok(()) != nix::mount::mount( @@ -197,7 +199,9 @@ impl Setup { nix::fcntl::OFlag::O_WRONLY | nix::fcntl::OFlag::O_CLOEXEC | nix::fcntl::OFlag::O_TRUNC, nix::sys::stat::Mode::from_bits(0o644).unwrap(), )?; - Self::write_nameserver(fd)?; + let owned_fd = unsafe { OwnedFd::from_raw_fd(fd) }; + Self::write_nameserver(&owned_fd)?; + fd = owned_fd.into_raw_fd(); } else { self.unmount_resolvconf = true; } @@ -235,7 +239,7 @@ impl Setup { Ok(()) } - fn setup_and_handle_signals(&mut self, read_from_child: RawFd, write_to_parent: RawFd) { + fn setup_and_handle_signals(&mut self, read_from_child: RawFd, write_to_parent: OwnedFd) { if let Err(e) = (|| -> Result<(), Error> { nix::unistd::close(read_from_child)?; run_iproute( @@ -263,10 +267,10 @@ impl Setup { self.add_tunnel_routes()?; // Signal to child that we are done setting up everything. - if nix::unistd::write(write_to_parent, &[1])? != 1 { + if nix::unistd::write(&write_to_parent, &[1])? != 1 { return Err("Failed to write to pipe".into()); } - nix::unistd::close(write_to_parent)?; + nix::unistd::close(write_to_parent.into_raw_fd())?; // Now wait for the termination signals. let mut mask = nix::sys::signal::SigSet::empty(); @@ -275,7 +279,7 @@ impl Setup { mask.add(nix::sys::signal::SIGQUIT); mask.thread_block().unwrap(); - let mut fd = nix::sys::signalfd::SignalFd::new(&mask).unwrap(); + let fd = nix::sys::signalfd::SignalFd::new(&mask).unwrap(); loop { let res = fd.read_signal().unwrap().unwrap(); let signo = nix::sys::signal::Signal::try_from(res.ssi_signo as i32).unwrap(); @@ -311,17 +315,17 @@ impl Setup { match fork::fork() { Ok(Fork::Child) => { prctl::set_death_signal(nix::sys::signal::SIGINT as isize).unwrap(); - self.setup_and_handle_signals(read_from_child, write_to_parent); + self.setup_and_handle_signals(read_from_child.into_raw_fd(), write_to_parent); std::process::exit(0); } Ok(Fork::Parent(child)) => { self.child = child; - nix::unistd::close(write_to_parent)?; + nix::unistd::close(write_to_parent.into_raw_fd())?; let mut buf = [0]; - if nix::unistd::read(read_from_child, &mut buf)? != 1 { + if nix::unistd::read(read_from_child.as_raw_fd(), &mut buf)? != 1 { return Err("Failed to read from pipe".into()); } - nix::unistd::close(read_from_child)?; + nix::unistd::close(read_from_child.into_raw_fd())?; Ok(()) } diff --git a/yt-dlp/yt_dlp/extractor/khanacademy.py b/yt-dlp/yt_dlp/extractor/khanacademy.py index 3f03f9e4c4..42eef3c922 100644 --- a/yt-dlp/yt_dlp/extractor/khanacademy.py +++ b/yt-dlp/yt_dlp/extractor/khanacademy.py @@ -15,7 +15,7 @@ from ..utils import ( class KhanAcademyBaseIE(InfoExtractor): _VALID_URL_TEMPL = r'https?://(?:www\.)?khanacademy\.org/(?P(?:[^/]+/){%s}%s[^?#/&]+)' - _PUBLISHED_CONTENT_VERSION = '171419ab20465d931b356f22d20527f13969bb70' + _PUBLISHED_CONTENT_VERSION = 'dc34750f0572c80f5effe7134082fe351143c1e4' def _parse_video(self, video): return { @@ -39,7 +39,7 @@ class KhanAcademyBaseIE(InfoExtractor): query={ 'fastly_cacheable': 'persist_until_publish', 'pcv': self._PUBLISHED_CONTENT_VERSION, - 'hash': '1242644265', + 'hash': '3712657851', 'variables': json.dumps({ 'path': display_id, 'countryCode': 'US',