mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 05:03:45 +08:00
refactor: Associate Profile with Merge/Script.
This commit is contained in:
parent
b85929772e
commit
3efef52398
@ -94,6 +94,16 @@ pub struct PrfOption {
|
|||||||
/// default is `false`
|
/// default is `false`
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub danger_accept_invalid_certs: Option<bool>,
|
pub danger_accept_invalid_certs: Option<bool>,
|
||||||
|
|
||||||
|
pub merge: Option<String>,
|
||||||
|
|
||||||
|
pub script: Option<String>,
|
||||||
|
|
||||||
|
pub rules: Option<String>,
|
||||||
|
|
||||||
|
pub proxies: Option<String>,
|
||||||
|
|
||||||
|
pub groups: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrfOption {
|
impl PrfOption {
|
||||||
@ -107,6 +117,11 @@ impl PrfOption {
|
|||||||
.danger_accept_invalid_certs
|
.danger_accept_invalid_certs
|
||||||
.or(a.danger_accept_invalid_certs);
|
.or(a.danger_accept_invalid_certs);
|
||||||
a.update_interval = b.update_interval.or(a.update_interval);
|
a.update_interval = b.update_interval.or(a.update_interval);
|
||||||
|
a.merge = b.merge.or(a.merge);
|
||||||
|
a.script = b.script.or(a.script);
|
||||||
|
a.rules = b.rules.or(a.rules);
|
||||||
|
a.proxies = b.proxies.or(a.proxies);
|
||||||
|
a.groups = b.groups.or(a.groups);
|
||||||
Some(a)
|
Some(a)
|
||||||
}
|
}
|
||||||
t => t.0.or(t.1),
|
t => t.0.or(t.1),
|
||||||
@ -137,16 +152,8 @@ impl PrfItem {
|
|||||||
let desc = item.desc.unwrap_or("".into());
|
let desc = item.desc.unwrap_or("".into());
|
||||||
PrfItem::from_local(name, desc, file_data, item.option)
|
PrfItem::from_local(name, desc, file_data, item.option)
|
||||||
}
|
}
|
||||||
"merge" => {
|
"merge" => PrfItem::from_merge(),
|
||||||
let name = item.name.unwrap_or("Merge".into());
|
"script" => PrfItem::from_script(),
|
||||||
let desc = item.desc.unwrap_or("".into());
|
|
||||||
PrfItem::from_merge(name, desc)
|
|
||||||
}
|
|
||||||
"script" => {
|
|
||||||
let name = item.name.unwrap_or("Script".into());
|
|
||||||
let desc = item.desc.unwrap_or("".into());
|
|
||||||
PrfItem::from_script(name, desc)
|
|
||||||
}
|
|
||||||
typ => bail!("invalid profile item type \"{typ}\""),
|
typ => bail!("invalid profile item type \"{typ}\""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,7 +168,24 @@ impl PrfItem {
|
|||||||
) -> Result<PrfItem> {
|
) -> Result<PrfItem> {
|
||||||
let uid = help::get_uid("l");
|
let uid = help::get_uid("l");
|
||||||
let file = format!("{uid}.yaml");
|
let file = format!("{uid}.yaml");
|
||||||
|
let opt_ref = option.as_ref();
|
||||||
|
let update_interval = opt_ref.and_then(|o| o.update_interval);
|
||||||
|
let mut merge = opt_ref.and_then(|o| o.merge.clone());
|
||||||
|
let mut script = opt_ref.and_then(|o| o.script.clone());
|
||||||
|
let rules = opt_ref.and_then(|o| o.rules.clone());
|
||||||
|
let proxies = opt_ref.and_then(|o| o.proxies.clone());
|
||||||
|
let groups = opt_ref.and_then(|o| o.groups.clone());
|
||||||
|
|
||||||
|
if merge.is_none() {
|
||||||
|
let merge_item = PrfItem::from_merge()?;
|
||||||
|
Config::profiles().data().append_item(merge_item.clone())?;
|
||||||
|
merge = merge_item.uid;
|
||||||
|
}
|
||||||
|
if script.is_none() {
|
||||||
|
let script_item = PrfItem::from_script()?;
|
||||||
|
Config::profiles().data().append_item(script_item.clone())?;
|
||||||
|
script = script_item.uid;
|
||||||
|
}
|
||||||
Ok(PrfItem {
|
Ok(PrfItem {
|
||||||
uid: Some(uid),
|
uid: Some(uid),
|
||||||
itype: Some("local".into()),
|
itype: Some("local".into()),
|
||||||
@ -172,7 +196,12 @@ impl PrfItem {
|
|||||||
selected: None,
|
selected: None,
|
||||||
extra: None,
|
extra: None,
|
||||||
option: Some(PrfOption {
|
option: Some(PrfOption {
|
||||||
update_interval: option.unwrap_or_default().update_interval,
|
update_interval,
|
||||||
|
merge,
|
||||||
|
script,
|
||||||
|
rules,
|
||||||
|
proxies,
|
||||||
|
groups,
|
||||||
..PrfOption::default()
|
..PrfOption::default()
|
||||||
}),
|
}),
|
||||||
home: None,
|
home: None,
|
||||||
@ -196,9 +225,23 @@ impl PrfItem {
|
|||||||
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
|
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
|
||||||
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
|
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
|
||||||
let update_interval = opt_ref.and_then(|o| o.update_interval);
|
let update_interval = opt_ref.and_then(|o| o.update_interval);
|
||||||
|
let mut merge = opt_ref.and_then(|o| o.merge.clone());
|
||||||
|
let mut script = opt_ref.and_then(|o| o.script.clone());
|
||||||
|
let rules = opt_ref.and_then(|o| o.rules.clone());
|
||||||
|
let proxies = opt_ref.and_then(|o| o.proxies.clone());
|
||||||
|
let groups = opt_ref.and_then(|o| o.groups.clone());
|
||||||
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
|
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
|
||||||
|
|
||||||
|
if merge.is_none() {
|
||||||
|
let merge_item = PrfItem::from_merge()?;
|
||||||
|
Config::profiles().data().append_item(merge_item.clone())?;
|
||||||
|
merge = merge_item.uid;
|
||||||
|
}
|
||||||
|
if script.is_none() {
|
||||||
|
let script_item = PrfItem::from_script()?;
|
||||||
|
Config::profiles().data().append_item(script_item.clone())?;
|
||||||
|
script = script_item.uid;
|
||||||
|
}
|
||||||
// 使用软件自己的代理
|
// 使用软件自己的代理
|
||||||
if self_proxy {
|
if self_proxy {
|
||||||
let port = Config::verge()
|
let port = Config::verge()
|
||||||
@ -290,17 +333,11 @@ impl PrfItem {
|
|||||||
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
|
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let option = match update_interval {
|
let update_interval = match update_interval {
|
||||||
Some(val) => Some(PrfOption {
|
Some(val) => Some(val),
|
||||||
update_interval: Some(val),
|
|
||||||
..PrfOption::default()
|
|
||||||
}),
|
|
||||||
None => match header.get("profile-update-interval") {
|
None => match header.get("profile-update-interval") {
|
||||||
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
|
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
|
||||||
Ok(val) => Some(PrfOption {
|
Ok(val) => Some(val * 60), // hour -> min
|
||||||
update_interval: Some(val * 60), // hour -> min
|
|
||||||
..PrfOption::default()
|
|
||||||
}),
|
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
@ -340,7 +377,15 @@ impl PrfItem {
|
|||||||
url: Some(url.into()),
|
url: Some(url.into()),
|
||||||
selected: None,
|
selected: None,
|
||||||
extra,
|
extra,
|
||||||
option,
|
option: Some(PrfOption {
|
||||||
|
update_interval,
|
||||||
|
merge,
|
||||||
|
script,
|
||||||
|
rules,
|
||||||
|
proxies,
|
||||||
|
groups,
|
||||||
|
..PrfOption::default()
|
||||||
|
}),
|
||||||
home,
|
home,
|
||||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||||
file_data: Some(data.into()),
|
file_data: Some(data.into()),
|
||||||
@ -349,15 +394,15 @@ impl PrfItem {
|
|||||||
|
|
||||||
/// ## Merge type (enhance)
|
/// ## Merge type (enhance)
|
||||||
/// create the enhanced item by using `merge` rule
|
/// create the enhanced item by using `merge` rule
|
||||||
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
|
pub fn from_merge() -> Result<PrfItem> {
|
||||||
let uid = help::get_uid("m");
|
let uid = help::get_uid("m");
|
||||||
let file = format!("{uid}.yaml");
|
let file = format!("{uid}.yaml");
|
||||||
|
|
||||||
Ok(PrfItem {
|
Ok(PrfItem {
|
||||||
uid: Some(uid),
|
uid: Some(uid),
|
||||||
itype: Some("merge".into()),
|
itype: Some("merge".into()),
|
||||||
name: Some(name),
|
name: None,
|
||||||
desc: Some(desc),
|
desc: None,
|
||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: None,
|
url: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
@ -371,15 +416,15 @@ impl PrfItem {
|
|||||||
|
|
||||||
/// ## Script type (enhance)
|
/// ## Script type (enhance)
|
||||||
/// create the enhanced item by using javascript quick.js
|
/// create the enhanced item by using javascript quick.js
|
||||||
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
|
pub fn from_script() -> Result<PrfItem> {
|
||||||
let uid = help::get_uid("s");
|
let uid = help::get_uid("s");
|
||||||
let file = format!("{uid}.js"); // js ext
|
let file = format!("{uid}.js"); // js ext
|
||||||
|
|
||||||
Ok(PrfItem {
|
Ok(PrfItem {
|
||||||
uid: Some(uid),
|
uid: Some(uid),
|
||||||
itype: Some("script".into()),
|
itype: Some("script".into()),
|
||||||
name: Some(name),
|
name: None,
|
||||||
desc: Some(desc),
|
desc: None,
|
||||||
file: Some(file),
|
file: Some(file),
|
||||||
url: None,
|
url: None,
|
||||||
home: None,
|
home: None,
|
||||||
|
@ -11,9 +11,6 @@ pub struct IProfiles {
|
|||||||
/// same as PrfConfig.current
|
/// same as PrfConfig.current
|
||||||
pub current: Option<String>,
|
pub current: Option<String>,
|
||||||
|
|
||||||
/// same as PrfConfig.chain
|
|
||||||
pub chain: Option<Vec<String>>,
|
|
||||||
|
|
||||||
/// profile list
|
/// profile list
|
||||||
pub items: Option<Vec<PrfItem>>,
|
pub items: Option<Vec<PrfItem>>,
|
||||||
}
|
}
|
||||||
@ -80,10 +77,6 @@ impl IProfiles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(chain) = patch.chain {
|
|
||||||
self.chain = Some(chain);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,9 +236,19 @@ impl IProfiles {
|
|||||||
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
|
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
|
||||||
let current = self.current.as_ref().unwrap_or(&uid);
|
let current = self.current.as_ref().unwrap_or(&uid);
|
||||||
let current = current.clone();
|
let current = current.clone();
|
||||||
|
let item = self.get_item(&uid)?;
|
||||||
|
let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
|
||||||
|
let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
|
||||||
|
let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
|
||||||
|
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
|
||||||
|
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
|
||||||
let mut items = self.items.take().unwrap_or_default();
|
let mut items = self.items.take().unwrap_or_default();
|
||||||
let mut index = None;
|
let mut index = None;
|
||||||
|
let mut merge_index = None;
|
||||||
|
let mut script_index = None;
|
||||||
|
// let mut rules_index = None;
|
||||||
|
// let mut proxies_index = None;
|
||||||
|
// let mut groups_index = None;
|
||||||
|
|
||||||
// get the index
|
// get the index
|
||||||
for (i, _) in items.iter().enumerate() {
|
for (i, _) in items.iter().enumerate() {
|
||||||
@ -266,6 +269,44 @@ impl IProfiles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the merge index
|
||||||
|
for (i, _) in items.iter().enumerate() {
|
||||||
|
if items[i].uid == merge_uid {
|
||||||
|
merge_index = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) = merge_index {
|
||||||
|
if let Some(file) = items.remove(index).file {
|
||||||
|
let _ = dirs::app_profiles_dir().map(|path| {
|
||||||
|
let path = path.join(file);
|
||||||
|
if path.exists() {
|
||||||
|
let _ = fs::remove_file(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the script index
|
||||||
|
for (i, _) in items.iter().enumerate() {
|
||||||
|
if items[i].uid == script_uid {
|
||||||
|
script_index = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) = script_index {
|
||||||
|
if let Some(file) = items.remove(index).file {
|
||||||
|
let _ = dirs::app_profiles_dir().map(|path| {
|
||||||
|
let path = path.join(file);
|
||||||
|
if path.exists() {
|
||||||
|
let _ = fs::remove_file(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// delete the original uid
|
// delete the original uid
|
||||||
if current == uid {
|
if current == uid {
|
||||||
self.current = match !items.is_empty() {
|
self.current = match !items.is_empty() {
|
||||||
@ -295,4 +336,32 @@ impl IProfiles {
|
|||||||
_ => Ok(Mapping::new()),
|
_ => Ok(Mapping::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取current指向的订阅的merge
|
||||||
|
pub fn current_merge(&self) -> Option<String> {
|
||||||
|
match (self.current.as_ref(), self.items.as_ref()) {
|
||||||
|
(Some(current), Some(items)) => {
|
||||||
|
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
|
||||||
|
let merge = item.option.as_ref().and_then(|e| e.merge.clone());
|
||||||
|
return merge;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取current指向的订阅的script
|
||||||
|
pub fn current_script(&self) -> Option<String> {
|
||||||
|
match (self.current.as_ref(), self.items.as_ref()) {
|
||||||
|
(Some(current), Some(items)) => {
|
||||||
|
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
|
||||||
|
let script = item.option.as_ref().and_then(|e| e.script.clone());
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use self::merge::*;
|
|||||||
use self::script::*;
|
use self::script::*;
|
||||||
use self::tun::*;
|
use self::tun::*;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::utils::tmpl;
|
||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@ -47,33 +48,45 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 从profiles里拿东西
|
// 从profiles里拿东西
|
||||||
let (mut config, chain) = {
|
let (mut config, merge_item, script_item) = {
|
||||||
let profiles = Config::profiles();
|
let profiles = Config::profiles();
|
||||||
let profiles = profiles.latest();
|
let profiles = profiles.latest();
|
||||||
|
|
||||||
let current = profiles.current_mapping().unwrap_or_default();
|
let current = profiles.current_mapping().unwrap_or_default();
|
||||||
|
let merge = profiles
|
||||||
|
.get_item(&profiles.current_merge().unwrap_or_default())
|
||||||
|
.ok()
|
||||||
|
.and_then(<Option<ChainItem>>::from)
|
||||||
|
.unwrap_or_else(|| ChainItem {
|
||||||
|
uid: "".into(),
|
||||||
|
data: ChainType::Merge(
|
||||||
|
serde_yaml::from_str::<Mapping>(tmpl::ITEM_MERGE).unwrap_or_default(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
let script = profiles
|
||||||
|
.get_item(&profiles.current_script().unwrap_or_default())
|
||||||
|
.ok()
|
||||||
|
.and_then(<Option<ChainItem>>::from)
|
||||||
|
.unwrap_or_else(|| ChainItem {
|
||||||
|
uid: "".into(),
|
||||||
|
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
||||||
|
});
|
||||||
|
|
||||||
let chain = match profiles.chain.as_ref() {
|
(current, merge, script)
|
||||||
Some(chain) => chain
|
|
||||||
.iter()
|
|
||||||
.filter_map(|uid| profiles.get_item(uid).ok())
|
|
||||||
.filter_map(<Option<ChainItem>>::from)
|
|
||||||
.collect::<Vec<ChainItem>>(),
|
|
||||||
None => vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
(current, chain)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result_map = HashMap::new(); // 保存脚本日志
|
let mut result_map = HashMap::new(); // 保存脚本日志
|
||||||
let mut exists_keys = use_keys(&config); // 保存出现过的keys
|
let mut exists_keys = use_keys(&config); // 保存出现过的keys
|
||||||
|
|
||||||
// 处理用户的profile
|
// 处理用户的profile
|
||||||
chain.into_iter().for_each(|item| match item.data {
|
match merge_item.data {
|
||||||
ChainType::Merge(merge) => {
|
ChainType::Merge(merge) => {
|
||||||
exists_keys.extend(use_keys(&merge));
|
exists_keys.extend(use_keys(&merge));
|
||||||
config = use_merge(merge, config.to_owned());
|
config = use_merge(merge, config.to_owned());
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
match script_item.data {
|
||||||
ChainType::Script(script) => {
|
ChainType::Script(script) => {
|
||||||
let mut logs = vec![];
|
let mut logs = vec![];
|
||||||
|
|
||||||
@ -86,9 +99,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
Err(err) => logs.push(("exception".into(), err.to_string())),
|
Err(err) => logs.push(("exception".into(), err.to_string())),
|
||||||
}
|
}
|
||||||
|
|
||||||
result_map.insert(item.uid, logs);
|
result_map.insert(script_item.uid, logs);
|
||||||
}
|
}
|
||||||
});
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
// 合并默认的config
|
// 合并默认的config
|
||||||
for (key, value) in clash_config.into_iter() {
|
for (key, value) in clash_config.into_iter() {
|
||||||
|
@ -292,7 +292,6 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
|
|||||||
Some((url, opt)) => {
|
Some((url, opt)) => {
|
||||||
let merged_opt = PrfOption::merge(opt, option);
|
let merged_opt = PrfOption::merge(opt, option);
|
||||||
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
|
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
|
||||||
|
|
||||||
let profiles = Config::profiles();
|
let profiles = Config::profiles();
|
||||||
let mut profiles = profiles.latest();
|
let mut profiles = profiles.latest();
|
||||||
profiles.update_item(uid.clone(), item)?;
|
profiles.update_item(uid.clone(), item)?;
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
use crate::config::{IVerge, PrfOption};
|
use crate::cmds::import_profile;
|
||||||
use crate::{
|
use crate::config::IVerge;
|
||||||
config::{Config, PrfItem},
|
use crate::{config::Config, core::*, utils::init, utils::server};
|
||||||
core::*,
|
|
||||||
utils::init,
|
|
||||||
utils::server,
|
|
||||||
};
|
|
||||||
use crate::{log_err, trace_err};
|
use crate::{log_err, trace_err};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@ -102,7 +98,7 @@ pub async fn resolve_setup(app: &mut App) {
|
|||||||
if argvs.len() > 1 {
|
if argvs.len() > 1 {
|
||||||
let param = argvs[1].as_str();
|
let param = argvs[1].as_str();
|
||||||
if param.starts_with("clash:") {
|
if param.starts_with("clash:") {
|
||||||
resolve_scheme(argvs[1].to_owned()).await;
|
log_err!(resolve_scheme(argvs[1].to_owned()).await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,31 +236,26 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve_scheme(param: String) {
|
pub async fn resolve_scheme(param: String) -> Result<()> {
|
||||||
let url = param
|
let url = param
|
||||||
.trim_start_matches("clash://install-config/?url=")
|
.trim_start_matches("clash://install-config/?url=")
|
||||||
.trim_start_matches("clash://install-config?url=");
|
.trim_start_matches("clash://install-config?url=");
|
||||||
let option = PrfOption {
|
match import_profile(url.to_string(), None).await {
|
||||||
user_agent: None,
|
Ok(_) => {
|
||||||
with_proxy: Some(true),
|
|
||||||
self_proxy: None,
|
|
||||||
danger_accept_invalid_certs: None,
|
|
||||||
update_interval: None,
|
|
||||||
};
|
|
||||||
if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await {
|
|
||||||
if Config::profiles().data().append_item(item).is_ok() {
|
|
||||||
notification::Notification::new(crate::utils::dirs::APP_ID)
|
notification::Notification::new(crate::utils::dirs::APP_ID)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.body("Import profile success")
|
.body("Import profile success")
|
||||||
.show()
|
.show()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
};
|
}
|
||||||
} else {
|
Err(e) => {
|
||||||
notification::Notification::new(crate::utils::dirs::APP_ID)
|
notification::Notification::new(crate::utils::dirs::APP_ID)
|
||||||
.title("Clash Verge")
|
.title("Clash Verge")
|
||||||
.body("Import profile failed")
|
.body(format!("Import profile failed: {e}"))
|
||||||
.show()
|
.show()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
log::error!("failed to parse url: {}", url);
|
log::error!("Import profile failed: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
extern crate warp;
|
extern crate warp;
|
||||||
|
|
||||||
use super::resolve;
|
use super::resolve;
|
||||||
use crate::config::{Config, IVerge, DEFAULT_PAC};
|
use crate::{
|
||||||
|
config::{Config, IVerge, DEFAULT_PAC},
|
||||||
|
log_err,
|
||||||
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use port_scanner::local_port_available;
|
use port_scanner::local_port_available;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
@ -85,7 +88,7 @@ pub fn embed_server(app_handle: AppHandle) {
|
|||||||
.and_then(scheme_handler);
|
.and_then(scheme_handler);
|
||||||
|
|
||||||
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
|
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
|
||||||
resolve::resolve_scheme(query.param).await;
|
log_err!(resolve::resolve_scheme(query.param).await);
|
||||||
Ok("ok")
|
Ok("ok")
|
||||||
}
|
}
|
||||||
let commands = ping.or(visible).or(pac).or(scheme);
|
let commands = ping.or(visible).or(pac).or(scheme);
|
||||||
|
@ -59,7 +59,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
const loadingCache = useLoadingCache();
|
const loadingCache = useLoadingCache();
|
||||||
const setLoadingCache = useSetLoadingCache();
|
const setLoadingCache = useSetLoadingCache();
|
||||||
|
|
||||||
const { uid, name = "Profile", extra, updated = 0 } = itemData;
|
const { uid, name = "Profile", extra, updated = 0, option } = itemData;
|
||||||
|
|
||||||
// local file mode
|
// local file mode
|
||||||
// remote file mode
|
// remote file mode
|
||||||
@ -105,6 +105,8 @@ export const ProfileItem = (props: Props) => {
|
|||||||
}, [hasUrl, updated]);
|
}, [hasUrl, updated]);
|
||||||
|
|
||||||
const [fileOpen, setFileOpen] = useState(false);
|
const [fileOpen, setFileOpen] = useState(false);
|
||||||
|
const [mergeOpen, setMergeOpen] = useState(false);
|
||||||
|
const [scriptOpen, setScriptOpen] = useState(false);
|
||||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||||
|
|
||||||
const onOpenHome = () => {
|
const onOpenHome = () => {
|
||||||
@ -122,6 +124,16 @@ export const ProfileItem = (props: Props) => {
|
|||||||
setFileOpen(true);
|
setFileOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onEditMerge = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
setMergeOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditScript = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
setScriptOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const onForceSelect = () => {
|
const onForceSelect = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
onSelect(true);
|
onSelect(true);
|
||||||
@ -174,33 +186,55 @@ export const ProfileItem = (props: Props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const urlModeMenu = (
|
const urlModeMenu = (
|
||||||
hasHome ? [{ label: "Home", handler: onOpenHome }] : []
|
hasHome ? [{ label: "Home", handler: onOpenHome, disabled: false }] : []
|
||||||
).concat([
|
).concat([
|
||||||
{ label: "Select", handler: onForceSelect },
|
{ label: "Select", handler: onForceSelect, disabled: false },
|
||||||
{ label: "Edit Info", handler: onEditInfo },
|
{ label: "Edit Info", handler: onEditInfo, disabled: false },
|
||||||
{ label: "Edit File", handler: onEditFile },
|
{ label: "Edit File", handler: onEditFile, disabled: false },
|
||||||
{ label: "Open File", handler: onOpenFile },
|
{
|
||||||
{ label: "Update", handler: () => onUpdate(0) },
|
label: "Edit Merge",
|
||||||
{ label: "Update(Proxy)", handler: () => onUpdate(2) },
|
handler: onEditMerge,
|
||||||
|
disabled: option?.merge === null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Edit Script",
|
||||||
|
handler: onEditScript,
|
||||||
|
disabled: option?.script === null,
|
||||||
|
},
|
||||||
|
{ label: "Open File", handler: onOpenFile, disabled: false },
|
||||||
|
{ label: "Update", handler: () => onUpdate(0), disabled: false },
|
||||||
|
{ label: "Update(Proxy)", handler: () => onUpdate(2), disabled: false },
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: "Delete",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
setConfirmOpen(true);
|
setConfirmOpen(true);
|
||||||
},
|
},
|
||||||
|
disabled: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const fileModeMenu = [
|
const fileModeMenu = [
|
||||||
{ label: "Select", handler: onForceSelect },
|
{ label: "Select", handler: onForceSelect, disabled: false },
|
||||||
{ label: "Edit Info", handler: onEditInfo },
|
{ label: "Edit Info", handler: onEditInfo, disabled: false },
|
||||||
{ label: "Edit File", handler: onEditFile },
|
{ label: "Edit File", handler: onEditFile, disabled: false },
|
||||||
{ label: "Open File", handler: onOpenFile },
|
{
|
||||||
|
label: "Edit Merge",
|
||||||
|
handler: onEditMerge,
|
||||||
|
disabled: option?.merge === null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Edit Script",
|
||||||
|
handler: onEditScript,
|
||||||
|
disabled: option?.script === null,
|
||||||
|
},
|
||||||
|
{ label: "Open File", handler: onOpenFile, disabled: false },
|
||||||
{
|
{
|
||||||
label: "Delete",
|
label: "Delete",
|
||||||
handler: () => {
|
handler: () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
setConfirmOpen(true);
|
setConfirmOpen(true);
|
||||||
},
|
},
|
||||||
|
disabled: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -369,6 +403,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key={item.label}
|
key={item.label}
|
||||||
onClick={item.handler}
|
onClick={item.handler}
|
||||||
|
disabled={item.disabled}
|
||||||
sx={[
|
sx={[
|
||||||
{
|
{
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
@ -398,6 +433,24 @@ export const ProfileItem = (props: Props) => {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onClose={() => setFileOpen(false)}
|
onClose={() => setFileOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
<EditorViewer
|
||||||
|
mode="profile"
|
||||||
|
property={option?.merge ?? "123"}
|
||||||
|
open={mergeOpen}
|
||||||
|
language="yaml"
|
||||||
|
schema="merge"
|
||||||
|
onChange={onChange}
|
||||||
|
onClose={() => setMergeOpen(false)}
|
||||||
|
/>
|
||||||
|
<EditorViewer
|
||||||
|
mode="profile"
|
||||||
|
property={option?.script ?? ""}
|
||||||
|
open={scriptOpen}
|
||||||
|
language="javascript"
|
||||||
|
schema={undefined}
|
||||||
|
onChange={onChange}
|
||||||
|
onClose={() => setScriptOpen(false)}
|
||||||
|
/>
|
||||||
<ConfirmViewer
|
<ConfirmViewer
|
||||||
title={t("Confirm deletion")}
|
title={t("Confirm deletion")}
|
||||||
message={t("This operation is not reversible")}
|
message={t("This operation is not reversible")}
|
||||||
|
@ -1,285 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useLockFn } from "ahooks";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Badge,
|
|
||||||
Chip,
|
|
||||||
Typography,
|
|
||||||
MenuItem,
|
|
||||||
Menu,
|
|
||||||
IconButton,
|
|
||||||
CircularProgress,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { FeaturedPlayListRounded } from "@mui/icons-material";
|
|
||||||
import { viewProfile } from "@/services/cmds";
|
|
||||||
import { Notice } from "@/components/base";
|
|
||||||
import { EditorViewer } from "@/components/profile/editor-viewer";
|
|
||||||
import { ProfileBox } from "./profile-box";
|
|
||||||
import { LogViewer } from "./log-viewer";
|
|
||||||
import { ConfirmViewer } from "./confirm-viewer";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
selected: boolean;
|
|
||||||
activating: boolean;
|
|
||||||
itemData: IProfileItem;
|
|
||||||
enableNum: number;
|
|
||||||
logInfo?: [string, string][];
|
|
||||||
onEnable: () => void;
|
|
||||||
onDisable: () => void;
|
|
||||||
onMoveTop: () => void;
|
|
||||||
onMoveEnd: () => void;
|
|
||||||
onEdit: () => void;
|
|
||||||
onChange?: (prev?: string, curr?: string) => void;
|
|
||||||
onDelete: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// profile enhanced item
|
|
||||||
export const ProfileMore = (props: Props) => {
|
|
||||||
const {
|
|
||||||
selected,
|
|
||||||
activating,
|
|
||||||
itemData,
|
|
||||||
enableNum,
|
|
||||||
logInfo = [],
|
|
||||||
onEnable,
|
|
||||||
onDisable,
|
|
||||||
onMoveTop,
|
|
||||||
onMoveEnd,
|
|
||||||
onDelete,
|
|
||||||
onEdit,
|
|
||||||
onChange,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const { uid, type } = itemData;
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const [anchorEl, setAnchorEl] = useState<any>(null);
|
|
||||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
|
||||||
const [fileOpen, setFileOpen] = useState(false);
|
|
||||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
||||||
const [logOpen, setLogOpen] = useState(false);
|
|
||||||
|
|
||||||
const onEditInfo = () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
onEdit();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onEditFile = () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
setFileOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onOpenFile = useLockFn(async () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
try {
|
|
||||||
await viewProfile(itemData.uid);
|
|
||||||
} catch (err: any) {
|
|
||||||
Notice.error(err?.message || err.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fnWrapper = (fn: () => void) => () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
return fn();
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasError = !!logInfo.find((e) => e[0] === "exception");
|
|
||||||
const showMove = enableNum > 1 && !hasError;
|
|
||||||
|
|
||||||
const enableMenu = [
|
|
||||||
{ label: "Disable", handler: fnWrapper(onDisable) },
|
|
||||||
{ label: "Edit Info", handler: onEditInfo },
|
|
||||||
{ label: "Edit File", handler: onEditFile },
|
|
||||||
{ label: "Open File", handler: onOpenFile },
|
|
||||||
{ label: "To Top", show: showMove, handler: fnWrapper(onMoveTop) },
|
|
||||||
{ label: "To End", show: showMove, handler: fnWrapper(onMoveEnd) },
|
|
||||||
{
|
|
||||||
label: "Delete",
|
|
||||||
handler: () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
setConfirmOpen(true);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const disableMenu = [
|
|
||||||
{ label: "Enable", handler: fnWrapper(onEnable) },
|
|
||||||
{ label: "Edit Info", handler: onEditInfo },
|
|
||||||
{ label: "Edit File", handler: onEditFile },
|
|
||||||
{ label: "Open File", handler: onOpenFile },
|
|
||||||
{
|
|
||||||
label: "Delete",
|
|
||||||
handler: () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
setConfirmOpen(true);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const boxStyle = {
|
|
||||||
height: 26,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
lineHeight: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ProfileBox
|
|
||||||
aria-selected={selected}
|
|
||||||
onDoubleClick={onEditFile}
|
|
||||||
// onClick={() => onSelect(false)}
|
|
||||||
onContextMenu={(event) => {
|
|
||||||
const { clientX, clientY } = event;
|
|
||||||
setPosition({ top: clientY, left: clientX });
|
|
||||||
setAnchorEl(event.currentTarget);
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{activating && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
top: 10,
|
|
||||||
left: 10,
|
|
||||||
right: 10,
|
|
||||||
bottom: 2,
|
|
||||||
zIndex: 10,
|
|
||||||
backdropFilter: "blur(2px)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CircularProgress color="inherit" size={20} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Box
|
|
||||||
display="flex"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
mb={0.5}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
width="calc(100% - 52px)"
|
|
||||||
variant="h6"
|
|
||||||
component="h2"
|
|
||||||
noWrap
|
|
||||||
title={itemData.name}
|
|
||||||
>
|
|
||||||
{itemData.name}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Chip
|
|
||||||
label={type}
|
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
sx={{ height: 20, textTransform: "capitalize" }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={boxStyle}>
|
|
||||||
{selected && type === "script" ? (
|
|
||||||
hasError ? (
|
|
||||||
<Badge color="error" variant="dot" overlap="circular">
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
edge="start"
|
|
||||||
color="error"
|
|
||||||
title={t("Script Console")}
|
|
||||||
onClick={() => setLogOpen(true)}
|
|
||||||
>
|
|
||||||
<FeaturedPlayListRounded fontSize="inherit" />
|
|
||||||
</IconButton>
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
edge="start"
|
|
||||||
color="inherit"
|
|
||||||
title={t("Script Console")}
|
|
||||||
onClick={() => setLogOpen(true)}
|
|
||||||
>
|
|
||||||
<FeaturedPlayListRounded fontSize="inherit" />
|
|
||||||
</IconButton>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<Typography
|
|
||||||
noWrap
|
|
||||||
title={itemData.desc}
|
|
||||||
sx={i18n.language === "zh" ? { width: "calc(100% - 75px)" } : {}}
|
|
||||||
>
|
|
||||||
{itemData.desc}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</ProfileBox>
|
|
||||||
|
|
||||||
<Menu
|
|
||||||
open={!!anchorEl}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
onClose={() => setAnchorEl(null)}
|
|
||||||
anchorPosition={position}
|
|
||||||
anchorReference="anchorPosition"
|
|
||||||
transitionDuration={225}
|
|
||||||
MenuListProps={{ sx: { py: 0.5 } }}
|
|
||||||
onContextMenu={(e) => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(selected ? enableMenu : disableMenu)
|
|
||||||
.filter((item: any) => item.show !== false)
|
|
||||||
.map((item) => (
|
|
||||||
<MenuItem
|
|
||||||
key={item.label}
|
|
||||||
onClick={item.handler}
|
|
||||||
sx={[
|
|
||||||
{ minWidth: 120 },
|
|
||||||
(theme) => {
|
|
||||||
return {
|
|
||||||
color:
|
|
||||||
item.label === "Delete"
|
|
||||||
? theme.palette.error.main
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
dense
|
|
||||||
>
|
|
||||||
{t(item.label)}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
|
|
||||||
<EditorViewer
|
|
||||||
mode="profile"
|
|
||||||
property={uid}
|
|
||||||
open={fileOpen}
|
|
||||||
language={type === "merge" ? "yaml" : "javascript"}
|
|
||||||
schema={type === "merge" ? "merge" : undefined}
|
|
||||||
onChange={onChange}
|
|
||||||
onClose={() => setFileOpen(false)}
|
|
||||||
/>
|
|
||||||
<ConfirmViewer
|
|
||||||
title={t("Confirm deletion")}
|
|
||||||
message={t("This operation is not reversible")}
|
|
||||||
open={confirmOpen}
|
|
||||||
onClose={() => setConfirmOpen(false)}
|
|
||||||
onConfirm={() => {
|
|
||||||
onDelete();
|
|
||||||
setConfirmOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{selected && (
|
|
||||||
<LogViewer
|
|
||||||
open={logOpen}
|
|
||||||
logInfo={logInfo}
|
|
||||||
onClose={() => setLogOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -33,7 +33,7 @@ export interface ProfileViewerRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create or edit the profile
|
// create or edit the profile
|
||||||
// remote / local / merge / script
|
// remote / local
|
||||||
export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -92,9 +92,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
if (form.type === "remote" && !form.url) {
|
if (form.type === "remote" && !form.url) {
|
||||||
throw new Error("The URL should not be null");
|
throw new Error("The URL should not be null");
|
||||||
}
|
}
|
||||||
if (form.type !== "remote" && form.type !== "local") {
|
|
||||||
delete form.option;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (form.option?.update_interval) {
|
if (form.option?.update_interval) {
|
||||||
form.option.update_interval = +form.option.update_interval;
|
form.option.update_interval = +form.option.update_interval;
|
||||||
@ -168,8 +165,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
|||||||
<Select {...field} autoFocus label={t("Type")}>
|
<Select {...field} autoFocus label={t("Type")}>
|
||||||
<MenuItem value="remote">Remote</MenuItem>
|
<MenuItem value="remote">Remote</MenuItem>
|
||||||
<MenuItem value="local">Local</MenuItem>
|
<MenuItem value="local">Local</MenuItem>
|
||||||
<MenuItem value="script">Script</MenuItem>
|
|
||||||
<MenuItem value="merge">Merge</MenuItem>
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
{
|
{
|
||||||
"millis": "millis",
|
"millis": "millis",
|
||||||
"mins": "mins",
|
"mins": "mins",
|
||||||
|
|
||||||
"Back": "Back",
|
"Back": "Back",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
"Empty": "Empty",
|
"Empty": "Empty",
|
||||||
|
|
||||||
"New": "New",
|
"New": "New",
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Delete": "Delete",
|
"Delete": "Delete",
|
||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"Disable": "Disable",
|
"Disable": "Disable",
|
||||||
|
|
||||||
"Label-Proxies": "Proxies",
|
"Label-Proxies": "Proxies",
|
||||||
"Label-Profiles": "Profiles",
|
"Label-Profiles": "Profiles",
|
||||||
"Label-Connections": "Connections",
|
"Label-Connections": "Connections",
|
||||||
@ -22,7 +19,6 @@
|
|||||||
"Label-Logs": "Logs",
|
"Label-Logs": "Logs",
|
||||||
"Label-Test": "Test",
|
"Label-Test": "Test",
|
||||||
"Label-Settings": "Settings",
|
"Label-Settings": "Settings",
|
||||||
|
|
||||||
"Proxies": "Proxies",
|
"Proxies": "Proxies",
|
||||||
"Proxy Groups": "Proxy Groups",
|
"Proxy Groups": "Proxy Groups",
|
||||||
"Proxy Provider": "Proxy Provider",
|
"Proxy Provider": "Proxy Provider",
|
||||||
@ -41,7 +37,6 @@
|
|||||||
"Delay check to cancel fixed": "Delay check to cancel fixed",
|
"Delay check to cancel fixed": "Delay check to cancel fixed",
|
||||||
"Proxy basic": "Proxy basic",
|
"Proxy basic": "Proxy basic",
|
||||||
"Proxy detail": "Proxy detail",
|
"Proxy detail": "Proxy detail",
|
||||||
|
|
||||||
"Profiles": "Profiles",
|
"Profiles": "Profiles",
|
||||||
"Update All Profiles": "Update All Profiles",
|
"Update All Profiles": "Update All Profiles",
|
||||||
"View Runtime Config": "View Runtime Config",
|
"View Runtime Config": "View Runtime Config",
|
||||||
@ -55,6 +50,8 @@
|
|||||||
"Expire Time": "Expire Time",
|
"Expire Time": "Expire Time",
|
||||||
"Create Profile": "Create Profile",
|
"Create Profile": "Create Profile",
|
||||||
"Edit Profile": "Edit Profile",
|
"Edit Profile": "Edit Profile",
|
||||||
|
"Edit Merge": "Edit Merge",
|
||||||
|
"Edit Script": "Edit Script",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Descriptions": "Descriptions",
|
"Descriptions": "Descriptions",
|
||||||
@ -77,7 +74,6 @@
|
|||||||
"Script Console": "Script Console",
|
"Script Console": "Script Console",
|
||||||
"To Top": "To Top",
|
"To Top": "To Top",
|
||||||
"To End": "To End",
|
"To End": "To End",
|
||||||
|
|
||||||
"Connections": "Connections",
|
"Connections": "Connections",
|
||||||
"Table View": "Table View",
|
"Table View": "Table View",
|
||||||
"List View": "List View",
|
"List View": "List View",
|
||||||
@ -97,21 +93,17 @@
|
|||||||
"Source": "Source",
|
"Source": "Source",
|
||||||
"Destination IP": "Destination IP",
|
"Destination IP": "Destination IP",
|
||||||
"Close Connection": "Close Connection",
|
"Close Connection": "Close Connection",
|
||||||
|
|
||||||
"Rules": "Rules",
|
"Rules": "Rules",
|
||||||
"Rule Provider": "Rule Provider",
|
"Rule Provider": "Rule Provider",
|
||||||
|
|
||||||
"Logs": "Logs",
|
"Logs": "Logs",
|
||||||
"Pause": "Pause",
|
"Pause": "Pause",
|
||||||
"Clear": "Clear",
|
"Clear": "Clear",
|
||||||
|
|
||||||
"Test": "Test",
|
"Test": "Test",
|
||||||
"Test All": "Test All",
|
"Test All": "Test All",
|
||||||
"Create Test": "Create Test",
|
"Create Test": "Create Test",
|
||||||
"Edit Test": "Edit Test",
|
"Edit Test": "Edit Test",
|
||||||
"Icon": "Icon",
|
"Icon": "Icon",
|
||||||
"Test URL": "Test URL",
|
"Test URL": "Test URL",
|
||||||
|
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"System Setting": "System Setting",
|
"System Setting": "System Setting",
|
||||||
"Tun Mode": "Tun Mode",
|
"Tun Mode": "Tun Mode",
|
||||||
@ -157,7 +149,6 @@
|
|||||||
"Auto Launch": "Auto Launch",
|
"Auto Launch": "Auto Launch",
|
||||||
"Silent Start": "Silent Start",
|
"Silent Start": "Silent Start",
|
||||||
"Silent Start Info": "Start the program in background mode without displaying the panel",
|
"Silent Start Info": "Start the program in background mode without displaying the panel",
|
||||||
|
|
||||||
"Clash Setting": "Clash Setting",
|
"Clash Setting": "Clash Setting",
|
||||||
"Allow Lan": "Allow Lan",
|
"Allow Lan": "Allow Lan",
|
||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
@ -181,7 +172,6 @@
|
|||||||
"Open UWP tool": "Open UWP tool",
|
"Open UWP tool": "Open UWP tool",
|
||||||
"Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",
|
"Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",
|
||||||
"Update GeoData": "Update GeoData",
|
"Update GeoData": "Update GeoData",
|
||||||
|
|
||||||
"TG Channel": "Telegram Channel",
|
"TG Channel": "Telegram Channel",
|
||||||
"Manual": "Manual",
|
"Manual": "Manual",
|
||||||
"Github Repo": "Github Repo",
|
"Github Repo": "Github Repo",
|
||||||
@ -253,7 +243,6 @@
|
|||||||
"Open Dev Tools": "Open Dev Tools",
|
"Open Dev Tools": "Open Dev Tools",
|
||||||
"Exit": "Exit",
|
"Exit": "Exit",
|
||||||
"Verge Version": "Verge Version",
|
"Verge Version": "Verge Version",
|
||||||
|
|
||||||
"ReadOnly": "ReadOnly",
|
"ReadOnly": "ReadOnly",
|
||||||
"ReadOnlyMessage": "Cannot edit in read-only editor",
|
"ReadOnlyMessage": "Cannot edit in read-only editor",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
@ -261,7 +250,6 @@
|
|||||||
"Match Case": "Match Case",
|
"Match Case": "Match Case",
|
||||||
"Match Whole Word": "Match Whole Word",
|
"Match Whole Word": "Match Whole Word",
|
||||||
"Use Regular Expression": "Use Regular Expression",
|
"Use Regular Expression": "Use Regular Expression",
|
||||||
|
|
||||||
"Profile Imported Successfully": "Profile Imported Successfully",
|
"Profile Imported Successfully": "Profile Imported Successfully",
|
||||||
"Clash Config Updated": "Clash Config Updated",
|
"Clash Config Updated": "Clash Config Updated",
|
||||||
"Profile Switched": "Profile Switched",
|
"Profile Switched": "Profile Switched",
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
{
|
{
|
||||||
"millis": "میلیثانیه",
|
"millis": "میلیثانیه",
|
||||||
"mins": "دقیقه",
|
"mins": "دقیقه",
|
||||||
|
|
||||||
"Back": "بازگشت",
|
"Back": "بازگشت",
|
||||||
"Close": "بستن",
|
"Close": "بستن",
|
||||||
"Cancel": "لغو",
|
"Cancel": "لغو",
|
||||||
"Confirm": "تأیید",
|
"Confirm": "تأیید",
|
||||||
"Empty": "خالی خالی",
|
"Empty": "خالی خالی",
|
||||||
|
|
||||||
"New": "جدید",
|
"New": "جدید",
|
||||||
"Edit": "ویرایش",
|
"Edit": "ویرایش",
|
||||||
"Save": "ذخیره",
|
"Save": "ذخیره",
|
||||||
"Delete": "حذف",
|
"Delete": "حذف",
|
||||||
"Enable": "فعال کردن",
|
"Enable": "فعال کردن",
|
||||||
"Disable": "غیرفعال کردن",
|
"Disable": "غیرفعال کردن",
|
||||||
|
|
||||||
"Label-Proxies": "پراکسیها",
|
"Label-Proxies": "پراکسیها",
|
||||||
"Label-Profiles": "پروفایلها",
|
"Label-Profiles": "پروفایلها",
|
||||||
"Label-Connections": "اتصالات",
|
"Label-Connections": "اتصالات",
|
||||||
@ -22,7 +19,6 @@
|
|||||||
"Label-Logs": "لاگها",
|
"Label-Logs": "لاگها",
|
||||||
"Label-Test": "آزمون",
|
"Label-Test": "آزمون",
|
||||||
"Label-Settings": "تنظیمات",
|
"Label-Settings": "تنظیمات",
|
||||||
|
|
||||||
"Proxies": "پراکسیها",
|
"Proxies": "پراکسیها",
|
||||||
"Proxy Groups": "گروههای پراکسی",
|
"Proxy Groups": "گروههای پراکسی",
|
||||||
"Proxy Provider": "تأمینکننده پروکسی",
|
"Proxy Provider": "تأمینکننده پروکسی",
|
||||||
@ -41,7 +37,6 @@
|
|||||||
"Delay check to cancel fixed": "بررسی تأخیر برای لغو ثابت",
|
"Delay check to cancel fixed": "بررسی تأخیر برای لغو ثابت",
|
||||||
"Proxy basic": "پراکسی پایه",
|
"Proxy basic": "پراکسی پایه",
|
||||||
"Proxy detail": "جزئیات پراکسی",
|
"Proxy detail": "جزئیات پراکسی",
|
||||||
|
|
||||||
"Profiles": "پروفایلها",
|
"Profiles": "پروفایلها",
|
||||||
"Update All Profiles": "بهروزرسانی همه پروفایلها",
|
"Update All Profiles": "بهروزرسانی همه پروفایلها",
|
||||||
"View Runtime Config": "مشاهده پیکربندی زمان اجرا",
|
"View Runtime Config": "مشاهده پیکربندی زمان اجرا",
|
||||||
@ -55,6 +50,8 @@
|
|||||||
"Expire Time": "زمان انقضا",
|
"Expire Time": "زمان انقضا",
|
||||||
"Create Profile": "ایجاد پروفایل",
|
"Create Profile": "ایجاد پروفایل",
|
||||||
"Edit Profile": "ویرایش پروفایل",
|
"Edit Profile": "ویرایش پروفایل",
|
||||||
|
"Edit Merge": "ادغام ویرایش",
|
||||||
|
"Edit Script": "ویرایش اسکریپت",
|
||||||
"Type": "نوع",
|
"Type": "نوع",
|
||||||
"Name": "نام",
|
"Name": "نام",
|
||||||
"Descriptions": "توضیحات",
|
"Descriptions": "توضیحات",
|
||||||
@ -77,7 +74,6 @@
|
|||||||
"Script Console": "کنسول اسکریپت",
|
"Script Console": "کنسول اسکریپت",
|
||||||
"To Top": "به بالا",
|
"To Top": "به بالا",
|
||||||
"To End": "به پایان",
|
"To End": "به پایان",
|
||||||
|
|
||||||
"Connections": "اتصالات",
|
"Connections": "اتصالات",
|
||||||
"Table View": "نمای جدولی",
|
"Table View": "نمای جدولی",
|
||||||
"List View": "نمای لیستی",
|
"List View": "نمای لیستی",
|
||||||
@ -97,21 +93,17 @@
|
|||||||
"Source": "منبع",
|
"Source": "منبع",
|
||||||
"Destination IP": "آدرس IP مقصد",
|
"Destination IP": "آدرس IP مقصد",
|
||||||
"Close Connection": "بستن اتصال",
|
"Close Connection": "بستن اتصال",
|
||||||
|
|
||||||
"Rules": "قوانین",
|
"Rules": "قوانین",
|
||||||
"Rule Provider": "تأمینکننده قانون",
|
"Rule Provider": "تأمینکننده قانون",
|
||||||
|
|
||||||
"Logs": "لاگها",
|
"Logs": "لاگها",
|
||||||
"Pause": "توقف",
|
"Pause": "توقف",
|
||||||
"Clear": "پاک کردن",
|
"Clear": "پاک کردن",
|
||||||
|
|
||||||
"Test": "آزمون",
|
"Test": "آزمون",
|
||||||
"Test All": "آزمون همه",
|
"Test All": "آزمون همه",
|
||||||
"Create Test": "ایجاد آزمون",
|
"Create Test": "ایجاد آزمون",
|
||||||
"Edit Test": "ویرایش آزمون",
|
"Edit Test": "ویرایش آزمون",
|
||||||
"Icon": "آیکون",
|
"Icon": "آیکون",
|
||||||
"Test URL": "آدرس آزمون",
|
"Test URL": "آدرس آزمون",
|
||||||
|
|
||||||
"Settings": "تنظیمات",
|
"Settings": "تنظیمات",
|
||||||
"System Setting": "تنظیمات سیستم",
|
"System Setting": "تنظیمات سیستم",
|
||||||
"Tun Mode": "حالت Tun",
|
"Tun Mode": "حالت Tun",
|
||||||
@ -157,7 +149,6 @@
|
|||||||
"Auto Launch": "راهاندازی خودکار",
|
"Auto Launch": "راهاندازی خودکار",
|
||||||
"Silent Start": "شروع بیصدا",
|
"Silent Start": "شروع بیصدا",
|
||||||
"Silent Start Info": "برنامه را در حالت پسزمینه بدون نمایش پانل اجرا کنید",
|
"Silent Start Info": "برنامه را در حالت پسزمینه بدون نمایش پانل اجرا کنید",
|
||||||
|
|
||||||
"Clash Setting": "تنظیمات Clash",
|
"Clash Setting": "تنظیمات Clash",
|
||||||
"Allow Lan": "اجازه LAN",
|
"Allow Lan": "اجازه LAN",
|
||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
@ -186,7 +177,6 @@
|
|||||||
"Open UWP tool": "باز کردن ابزار UWP",
|
"Open UWP tool": "باز کردن ابزار UWP",
|
||||||
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامههای UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شدهاند و این ابزار میتواند برای دور زدن این محدودیت استفاده شود",
|
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامههای UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شدهاند و این ابزار میتواند برای دور زدن این محدودیت استفاده شود",
|
||||||
"Update GeoData": "بهروزرسانی GeoData",
|
"Update GeoData": "بهروزرسانی GeoData",
|
||||||
|
|
||||||
"TG Channel": "کانال تلگرام",
|
"TG Channel": "کانال تلگرام",
|
||||||
"Manual": "راهنما",
|
"Manual": "راهنما",
|
||||||
"Github Repo": "مخزن GitHub",
|
"Github Repo": "مخزن GitHub",
|
||||||
@ -258,7 +248,6 @@
|
|||||||
"Open Dev Tools": "باز کردن ابزارهای توسعهدهنده",
|
"Open Dev Tools": "باز کردن ابزارهای توسعهدهنده",
|
||||||
"Exit": "خروج",
|
"Exit": "خروج",
|
||||||
"Verge Version": "نسخه Verge",
|
"Verge Version": "نسخه Verge",
|
||||||
|
|
||||||
"ReadOnly": "فقط خواندنی",
|
"ReadOnly": "فقط خواندنی",
|
||||||
"ReadOnlyMessage": "نمیتوان در ویرایشگر فقط خواندنی ویرایش کرد",
|
"ReadOnlyMessage": "نمیتوان در ویرایشگر فقط خواندنی ویرایش کرد",
|
||||||
"Filter": "فیلتر",
|
"Filter": "فیلتر",
|
||||||
@ -266,7 +255,6 @@
|
|||||||
"Match Case": "تطبیق حروف کوچک و بزرگ",
|
"Match Case": "تطبیق حروف کوچک و بزرگ",
|
||||||
"Match Whole Word": "تطبیق کل کلمه",
|
"Match Whole Word": "تطبیق کل کلمه",
|
||||||
"Use Regular Expression": "استفاده از عبارت منظم",
|
"Use Regular Expression": "استفاده از عبارت منظم",
|
||||||
|
|
||||||
"Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
|
"Profile Imported Successfully": "پروفایل با موفقیت وارد شد",
|
||||||
"Clash Config Updated": "پیکربندی Clash بهروزرسانی شد",
|
"Clash Config Updated": "پیکربندی Clash بهروزرسانی شد",
|
||||||
"Profile Switched": "پروفایل تغییر یافت",
|
"Profile Switched": "پروفایل تغییر یافت",
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
{
|
{
|
||||||
"millis": "миллисекунды",
|
"millis": "миллисекунды",
|
||||||
"mins": "минуты",
|
"mins": "минуты",
|
||||||
|
|
||||||
"Back": "Назад",
|
"Back": "Назад",
|
||||||
"Close": "Закрыть",
|
"Close": "Закрыть",
|
||||||
"Cancel": "Отмена",
|
"Cancel": "Отмена",
|
||||||
"Confirm": "Подтвердить",
|
"Confirm": "Подтвердить",
|
||||||
"Empty": "Пусто",
|
"Empty": "Пусто",
|
||||||
|
|
||||||
"New": "Новый",
|
"New": "Новый",
|
||||||
"Edit": "Редактировать",
|
"Edit": "Редактировать",
|
||||||
"Save": "Сохранить",
|
"Save": "Сохранить",
|
||||||
"Delete": "Удалить",
|
"Delete": "Удалить",
|
||||||
"Enable": "Включить",
|
"Enable": "Включить",
|
||||||
"Disable": "Отключить",
|
"Disable": "Отключить",
|
||||||
|
|
||||||
"Label-Proxies": "Прокси",
|
"Label-Proxies": "Прокси",
|
||||||
"Label-Profiles": "Профили",
|
"Label-Profiles": "Профили",
|
||||||
"Label-Connections": "Соединения",
|
"Label-Connections": "Соединения",
|
||||||
@ -22,7 +19,6 @@
|
|||||||
"Label-Logs": "Логи",
|
"Label-Logs": "Логи",
|
||||||
"Label-Test": "Тест",
|
"Label-Test": "Тест",
|
||||||
"Label-Settings": "Настройки",
|
"Label-Settings": "Настройки",
|
||||||
|
|
||||||
"Proxies": "Прокси",
|
"Proxies": "Прокси",
|
||||||
"Proxy Groups": "Группы прокси",
|
"Proxy Groups": "Группы прокси",
|
||||||
"Proxy Provider": "Провайдер прокси",
|
"Proxy Provider": "Провайдер прокси",
|
||||||
@ -41,7 +37,6 @@
|
|||||||
"Delay check to cancel fixed": "Проверка задержки для отмены фиксированного",
|
"Delay check to cancel fixed": "Проверка задержки для отмены фиксированного",
|
||||||
"Proxy basic": "Резюме о прокси",
|
"Proxy basic": "Резюме о прокси",
|
||||||
"Proxy detail": "Подробности о прокси",
|
"Proxy detail": "Подробности о прокси",
|
||||||
|
|
||||||
"Profiles": "Профили",
|
"Profiles": "Профили",
|
||||||
"Update All Profiles": "Обновить все профили",
|
"Update All Profiles": "Обновить все профили",
|
||||||
"View Runtime Config": "Просмотреть используемый конфиг",
|
"View Runtime Config": "Просмотреть используемый конфиг",
|
||||||
@ -55,6 +50,8 @@
|
|||||||
"Expire Time": "Время окончания",
|
"Expire Time": "Время окончания",
|
||||||
"Create Profile": "Создать профиль",
|
"Create Profile": "Создать профиль",
|
||||||
"Edit Profile": "Изменить профиль",
|
"Edit Profile": "Изменить профиль",
|
||||||
|
"Edit Merge": "Изменить Merge.",
|
||||||
|
"Edit Script": "Изменить Script",
|
||||||
"Type": "Тип",
|
"Type": "Тип",
|
||||||
"Name": "Название",
|
"Name": "Название",
|
||||||
"Descriptions": "Описания",
|
"Descriptions": "Описания",
|
||||||
@ -77,7 +74,6 @@
|
|||||||
"Script Console": "Консоль скрипта",
|
"Script Console": "Консоль скрипта",
|
||||||
"To Top": "Наверх",
|
"To Top": "Наверх",
|
||||||
"To End": "Вниз",
|
"To End": "Вниз",
|
||||||
|
|
||||||
"Connections": "Соединения",
|
"Connections": "Соединения",
|
||||||
"Table View": "Tablichnyy vid",
|
"Table View": "Tablichnyy vid",
|
||||||
"List View": "Spiskovyy vid",
|
"List View": "Spiskovyy vid",
|
||||||
@ -97,21 +93,17 @@
|
|||||||
"Source": "Исходный адрес",
|
"Source": "Исходный адрес",
|
||||||
"Destination IP": "IP-адрес назначения",
|
"Destination IP": "IP-адрес назначения",
|
||||||
"Close Connection": "Закрыть соединение",
|
"Close Connection": "Закрыть соединение",
|
||||||
|
|
||||||
"Rules": "Правила",
|
"Rules": "Правила",
|
||||||
"Rule Provider": "Провайдер правило",
|
"Rule Provider": "Провайдер правило",
|
||||||
|
|
||||||
"Logs": "Логи",
|
"Logs": "Логи",
|
||||||
"Pause": "Пауза",
|
"Pause": "Пауза",
|
||||||
"Clear": "Очистить",
|
"Clear": "Очистить",
|
||||||
|
|
||||||
"Test": "Тест",
|
"Test": "Тест",
|
||||||
"Test All": "Тест Все",
|
"Test All": "Тест Все",
|
||||||
"Create Test": "Создать тест",
|
"Create Test": "Создать тест",
|
||||||
"Edit Test": "Редактировать тест",
|
"Edit Test": "Редактировать тест",
|
||||||
"Icon": "Икона",
|
"Icon": "Икона",
|
||||||
"Test URL": "Тестовый URL",
|
"Test URL": "Тестовый URL",
|
||||||
|
|
||||||
"Settings": "Настройки",
|
"Settings": "Настройки",
|
||||||
"System Setting": "Настройки системы",
|
"System Setting": "Настройки системы",
|
||||||
"Tun Mode": "Режим туннеля",
|
"Tun Mode": "Режим туннеля",
|
||||||
@ -157,7 +149,6 @@
|
|||||||
"Auto Launch": "Автозапуск",
|
"Auto Launch": "Автозапуск",
|
||||||
"Silent Start": "Тихий запуск",
|
"Silent Start": "Тихий запуск",
|
||||||
"Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
|
"Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
|
||||||
|
|
||||||
"Clash Setting": "Настройки Clash",
|
"Clash Setting": "Настройки Clash",
|
||||||
"Allow Lan": "Разрешить локальную сеть",
|
"Allow Lan": "Разрешить локальную сеть",
|
||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
@ -186,7 +177,6 @@
|
|||||||
"Open UWP tool": "Открыть UWP инструмент",
|
"Open UWP tool": "Открыть UWP инструмент",
|
||||||
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
|
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
|
||||||
"Update GeoData": "Обновление GeoData",
|
"Update GeoData": "Обновление GeoData",
|
||||||
|
|
||||||
"TG Channel": "Канал Telegram",
|
"TG Channel": "Канал Telegram",
|
||||||
"Manual": "Документация",
|
"Manual": "Документация",
|
||||||
"Github Repo": "GitHub репозиторий",
|
"Github Repo": "GitHub репозиторий",
|
||||||
@ -258,7 +248,6 @@
|
|||||||
"Open Dev Tools": "Открыть инструменты разработчика",
|
"Open Dev Tools": "Открыть инструменты разработчика",
|
||||||
"Exit": "Выход",
|
"Exit": "Выход",
|
||||||
"Verge Version": "Версия Verge",
|
"Verge Version": "Версия Verge",
|
||||||
|
|
||||||
"ReadOnly": "Только для чтения",
|
"ReadOnly": "Только для чтения",
|
||||||
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
|
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
|
||||||
"Filter": "Фильтр",
|
"Filter": "Фильтр",
|
||||||
@ -266,7 +255,6 @@
|
|||||||
"Match Case": "Учитывать регистр",
|
"Match Case": "Учитывать регистр",
|
||||||
"Match Whole Word": "Полное совпадение слова",
|
"Match Whole Word": "Полное совпадение слова",
|
||||||
"Use Regular Expression": "Использовать регулярные выражения",
|
"Use Regular Expression": "Использовать регулярные выражения",
|
||||||
|
|
||||||
"Profile Imported Successfully": "Профиль успешно импортирован",
|
"Profile Imported Successfully": "Профиль успешно импортирован",
|
||||||
"Clash Config Updated": "Clash конфигурация Обновлена",
|
"Clash Config Updated": "Clash конфигурация Обновлена",
|
||||||
"Profile Switched": "Профиль изменен",
|
"Profile Switched": "Профиль изменен",
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
{
|
{
|
||||||
"millis": "毫秒",
|
"millis": "毫秒",
|
||||||
"mins": "分钟",
|
"mins": "分钟",
|
||||||
|
|
||||||
"Back": "返回",
|
"Back": "返回",
|
||||||
"Close": "关闭",
|
"Close": "关闭",
|
||||||
"Cancel": "取消",
|
"Cancel": "取消",
|
||||||
"Confirm": "确认",
|
"Confirm": "确认",
|
||||||
"Empty": "空空如也",
|
"Empty": "空空如也",
|
||||||
|
|
||||||
"New": "新建",
|
"New": "新建",
|
||||||
"Edit": "编辑",
|
"Edit": "编辑",
|
||||||
"Save": "保存",
|
"Save": "保存",
|
||||||
"Delete": "删除",
|
"Delete": "删除",
|
||||||
"Enable": "启用",
|
"Enable": "启用",
|
||||||
"Disable": "禁用",
|
"Disable": "禁用",
|
||||||
|
|
||||||
"Label-Proxies": "代 理",
|
"Label-Proxies": "代 理",
|
||||||
"Label-Profiles": "订 阅",
|
"Label-Profiles": "订 阅",
|
||||||
"Label-Connections": "连 接",
|
"Label-Connections": "连 接",
|
||||||
@ -22,7 +19,6 @@
|
|||||||
"Label-Logs": "日 志",
|
"Label-Logs": "日 志",
|
||||||
"Label-Test": "测 试",
|
"Label-Test": "测 试",
|
||||||
"Label-Settings": "设 置",
|
"Label-Settings": "设 置",
|
||||||
|
|
||||||
"Proxies": "代理",
|
"Proxies": "代理",
|
||||||
"Proxy Groups": "代理组",
|
"Proxy Groups": "代理组",
|
||||||
"Proxy Provider": "代理集合",
|
"Proxy Provider": "代理集合",
|
||||||
@ -41,7 +37,6 @@
|
|||||||
"Delay check to cancel fixed": "进行延迟测试,以取消固定",
|
"Delay check to cancel fixed": "进行延迟测试,以取消固定",
|
||||||
"Proxy basic": "隐藏节点细节",
|
"Proxy basic": "隐藏节点细节",
|
||||||
"Proxy detail": "展示节点细节",
|
"Proxy detail": "展示节点细节",
|
||||||
|
|
||||||
"Profiles": "订阅",
|
"Profiles": "订阅",
|
||||||
"Update All Profiles": "更新所有订阅",
|
"Update All Profiles": "更新所有订阅",
|
||||||
"View Runtime Config": "查看运行时订阅",
|
"View Runtime Config": "查看运行时订阅",
|
||||||
@ -55,6 +50,8 @@
|
|||||||
"Expire Time": "到期时间",
|
"Expire Time": "到期时间",
|
||||||
"Create Profile": "新建配置",
|
"Create Profile": "新建配置",
|
||||||
"Edit Profile": "编辑配置",
|
"Edit Profile": "编辑配置",
|
||||||
|
"Edit Merge": "编辑 Merge",
|
||||||
|
"Edit Script": "编辑 Script",
|
||||||
"Type": "类型",
|
"Type": "类型",
|
||||||
"Name": "名称",
|
"Name": "名称",
|
||||||
"Descriptions": "描述",
|
"Descriptions": "描述",
|
||||||
@ -77,7 +74,6 @@
|
|||||||
"Script Console": "脚本控制台输出",
|
"Script Console": "脚本控制台输出",
|
||||||
"To Top": "移到最前",
|
"To Top": "移到最前",
|
||||||
"To End": "移到末尾",
|
"To End": "移到末尾",
|
||||||
|
|
||||||
"Connections": "连接",
|
"Connections": "连接",
|
||||||
"Table View": "表格视图",
|
"Table View": "表格视图",
|
||||||
"List View": "列表视图",
|
"List View": "列表视图",
|
||||||
@ -97,21 +93,17 @@
|
|||||||
"Source": "源地址",
|
"Source": "源地址",
|
||||||
"Destination IP": "目标地址",
|
"Destination IP": "目标地址",
|
||||||
"Close Connection": "关闭连接",
|
"Close Connection": "关闭连接",
|
||||||
|
|
||||||
"Rules": "规则",
|
"Rules": "规则",
|
||||||
"Rule Provider": "规则集合",
|
"Rule Provider": "规则集合",
|
||||||
|
|
||||||
"Logs": "日志",
|
"Logs": "日志",
|
||||||
"Pause": "暂停",
|
"Pause": "暂停",
|
||||||
"Clear": "清除",
|
"Clear": "清除",
|
||||||
|
|
||||||
"Test": "测试",
|
"Test": "测试",
|
||||||
"Test All": "测试全部",
|
"Test All": "测试全部",
|
||||||
"Create Test": "新建测试",
|
"Create Test": "新建测试",
|
||||||
"Edit Test": "编辑测试",
|
"Edit Test": "编辑测试",
|
||||||
"Icon": "图标",
|
"Icon": "图标",
|
||||||
"Test URL": "测试地址",
|
"Test URL": "测试地址",
|
||||||
|
|
||||||
"Settings": "设置",
|
"Settings": "设置",
|
||||||
"System Setting": "系统设置",
|
"System Setting": "系统设置",
|
||||||
"Tun Mode": "Tun 模式",
|
"Tun Mode": "Tun 模式",
|
||||||
@ -157,7 +149,6 @@
|
|||||||
"Auto Launch": "开机自启",
|
"Auto Launch": "开机自启",
|
||||||
"Silent Start": "静默启动",
|
"Silent Start": "静默启动",
|
||||||
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
|
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
|
||||||
|
|
||||||
"TG Channel": "Telegram 频道",
|
"TG Channel": "Telegram 频道",
|
||||||
"Manual": "使用手册",
|
"Manual": "使用手册",
|
||||||
"Github Repo": "GitHub 项目地址",
|
"Github Repo": "GitHub 项目地址",
|
||||||
@ -189,7 +180,6 @@
|
|||||||
"Open UWP tool": "UWP 工具",
|
"Open UWP tool": "UWP 工具",
|
||||||
"Open UWP tool Info": "Windows 8开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制",
|
"Open UWP tool Info": "Windows 8开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制",
|
||||||
"Update GeoData": "更新 GeoData",
|
"Update GeoData": "更新 GeoData",
|
||||||
|
|
||||||
"Verge Setting": "Verge 设置",
|
"Verge Setting": "Verge 设置",
|
||||||
"Language": "语言设置",
|
"Language": "语言设置",
|
||||||
"Theme Mode": "主题模式",
|
"Theme Mode": "主题模式",
|
||||||
@ -258,7 +248,6 @@
|
|||||||
"Open Dev Tools": "打开开发者工具",
|
"Open Dev Tools": "打开开发者工具",
|
||||||
"Exit": "退出",
|
"Exit": "退出",
|
||||||
"Verge Version": "Verge 版本",
|
"Verge Version": "Verge 版本",
|
||||||
|
|
||||||
"ReadOnly": "只读",
|
"ReadOnly": "只读",
|
||||||
"ReadOnlyMessage": "无法在只读模式下编辑",
|
"ReadOnlyMessage": "无法在只读模式下编辑",
|
||||||
"Filter": "过滤节点",
|
"Filter": "过滤节点",
|
||||||
@ -266,7 +255,6 @@
|
|||||||
"Match Case": "区分大小写",
|
"Match Case": "区分大小写",
|
||||||
"Match Whole Word": "全字匹配",
|
"Match Whole Word": "全字匹配",
|
||||||
"Use Regular Expression": "使用正则表达式",
|
"Use Regular Expression": "使用正则表达式",
|
||||||
|
|
||||||
"Profile Imported Successfully": "导入订阅成功",
|
"Profile Imported Successfully": "导入订阅成功",
|
||||||
"Clash Config Updated": "Clash 配置已更新",
|
"Clash Config Updated": "Clash 配置已更新",
|
||||||
"Profile Switched": "订阅已切换",
|
"Profile Switched": "订阅已切换",
|
||||||
|
@ -42,7 +42,6 @@ import {
|
|||||||
ProfileViewerRef,
|
ProfileViewerRef,
|
||||||
} from "@/components/profile/profile-viewer";
|
} from "@/components/profile/profile-viewer";
|
||||||
import { ProfileItem } from "@/components/profile/profile-item";
|
import { ProfileItem } from "@/components/profile/profile-item";
|
||||||
import { ProfileMore } from "@/components/profile/profile-more";
|
|
||||||
import { useProfiles } from "@/hooks/use-profiles";
|
import { useProfiles } from "@/hooks/use-profiles";
|
||||||
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
|
import { ConfigViewer } from "@/components/setting/mods/config-viewer";
|
||||||
import { throttle } from "lodash-es";
|
import { throttle } from "lodash-es";
|
||||||
@ -105,31 +104,22 @@ const ProfilePage = () => {
|
|||||||
getRuntimeLogs
|
getRuntimeLogs
|
||||||
);
|
);
|
||||||
|
|
||||||
const chain = profiles.chain || [];
|
|
||||||
const viewerRef = useRef<ProfileViewerRef>(null);
|
const viewerRef = useRef<ProfileViewerRef>(null);
|
||||||
const configRef = useRef<DialogRef>(null);
|
const configRef = useRef<DialogRef>(null);
|
||||||
|
|
||||||
// distinguish type
|
// distinguish type
|
||||||
const { regularItems, enhanceItems } = useMemo(() => {
|
const profileItems = useMemo(() => {
|
||||||
const items = profiles.items || [];
|
const items = profiles.items || [];
|
||||||
const chain = profiles.chain || [];
|
|
||||||
|
|
||||||
const type1 = ["local", "remote"];
|
const type1 = ["local", "remote"];
|
||||||
const type2 = ["merge", "script"];
|
|
||||||
|
|
||||||
const regularItems = items.filter((i) => i && type1.includes(i.type!));
|
const profileItems = items.filter((i) => i && type1.includes(i.type!));
|
||||||
const restItems = items.filter((i) => i && type2.includes(i.type!));
|
|
||||||
const restMap = Object.fromEntries(restItems.map((i) => [i.uid, i]));
|
|
||||||
const enhanceItems = chain
|
|
||||||
.map((i) => restMap[i]!)
|
|
||||||
.filter(Boolean)
|
|
||||||
.concat(restItems.filter((i) => !chain.includes(i.uid)));
|
|
||||||
|
|
||||||
return { regularItems, enhanceItems };
|
return profileItems;
|
||||||
}, [profiles]);
|
}, [profiles]);
|
||||||
|
|
||||||
const currentActivatings = () => {
|
const currentActivatings = () => {
|
||||||
return [...new Set([profiles.current ?? "", ...chain])].filter(Boolean);
|
return [...new Set([profiles.current ?? ""])].filter(Boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onImport = async () => {
|
const onImport = async () => {
|
||||||
@ -205,38 +195,9 @@ const ProfilePage = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onEnable = useLockFn(async (uid: string) => {
|
|
||||||
if (chain.includes(uid)) return;
|
|
||||||
try {
|
|
||||||
setActivatings([...currentActivatings(), uid]);
|
|
||||||
const newChain = [...chain, uid];
|
|
||||||
await patchProfiles({ chain: newChain });
|
|
||||||
mutateLogs();
|
|
||||||
} catch (err: any) {
|
|
||||||
Notice.error(err.message || err.toString(), 3000);
|
|
||||||
} finally {
|
|
||||||
setActivatings([]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onDisable = useLockFn(async (uid: string) => {
|
|
||||||
if (!chain.includes(uid)) return;
|
|
||||||
try {
|
|
||||||
setActivatings([...currentActivatings(), uid]);
|
|
||||||
const newChain = chain.filter((i) => i !== uid);
|
|
||||||
await patchProfiles({ chain: newChain });
|
|
||||||
mutateLogs();
|
|
||||||
} catch (err: any) {
|
|
||||||
Notice.error(err.message || err.toString(), 3000);
|
|
||||||
} finally {
|
|
||||||
setActivatings([]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onDelete = useLockFn(async (uid: string) => {
|
const onDelete = useLockFn(async (uid: string) => {
|
||||||
const current = profiles.current === uid;
|
const current = profiles.current === uid;
|
||||||
try {
|
try {
|
||||||
await onDisable(uid);
|
|
||||||
setActivatings([...(current ? currentActivatings() : []), uid]);
|
setActivatings([...(current ? currentActivatings() : []), uid]);
|
||||||
await deleteProfile(uid);
|
await deleteProfile(uid);
|
||||||
mutateProfiles();
|
mutateProfiles();
|
||||||
@ -249,20 +210,6 @@ const ProfilePage = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onMoveTop = useLockFn(async (uid: string) => {
|
|
||||||
if (!chain.includes(uid)) return;
|
|
||||||
const newChain = [uid].concat(chain.filter((i) => i !== uid));
|
|
||||||
await patchProfiles({ chain: newChain });
|
|
||||||
mutateLogs();
|
|
||||||
});
|
|
||||||
|
|
||||||
const onMoveEnd = useLockFn(async (uid: string) => {
|
|
||||||
if (!chain.includes(uid)) return;
|
|
||||||
const newChain = chain.filter((i) => i !== uid).concat([uid]);
|
|
||||||
await patchProfiles({ chain: newChain });
|
|
||||||
mutateLogs();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新所有订阅
|
// 更新所有订阅
|
||||||
const setLoadingCache = useSetLoadingCache();
|
const setLoadingCache = useSetLoadingCache();
|
||||||
const onUpdateAll = useLockFn(async () => {
|
const onUpdateAll = useLockFn(async () => {
|
||||||
@ -281,7 +228,7 @@ const ProfilePage = () => {
|
|||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setLoadingCache((cache) => {
|
setLoadingCache((cache) => {
|
||||||
// 获取没有正在更新的订阅
|
// 获取没有正在更新的订阅
|
||||||
const items = regularItems.filter(
|
const items = profileItems.filter(
|
||||||
(e) => e.type === "remote" && !cache[e.uid]
|
(e) => e.type === "remote" && !cache[e.uid]
|
||||||
);
|
);
|
||||||
const change = Object.fromEntries(items.map((e) => [e.uid, true]));
|
const change = Object.fromEntries(items.map((e) => [e.uid, true]));
|
||||||
@ -296,11 +243,6 @@ const ProfilePage = () => {
|
|||||||
const text = await readText();
|
const text = await readText();
|
||||||
if (text) setUrl(text);
|
if (text) setUrl(text);
|
||||||
};
|
};
|
||||||
const mode = useThemeMode();
|
|
||||||
const islight = mode === "light" ? true : false;
|
|
||||||
const dividercolor = islight
|
|
||||||
? "rgba(0, 0, 0, 0.06)"
|
|
||||||
: "rgba(255, 255, 255, 0.06)";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePage
|
<BasePage
|
||||||
@ -415,11 +357,11 @@ const ProfilePage = () => {
|
|||||||
<Box sx={{ mb: 1.5 }}>
|
<Box sx={{ mb: 1.5 }}>
|
||||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
items={regularItems.map((x) => {
|
items={profileItems.map((x) => {
|
||||||
return x.uid;
|
return x.uid;
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{regularItems.map((item) => (
|
{profileItems.map((item) => (
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
||||||
<ProfileItem
|
<ProfileItem
|
||||||
id={item.uid}
|
id={item.uid}
|
||||||
@ -441,43 +383,6 @@ const ProfilePage = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
|
||||||
{enhanceItems.length > 0 && (
|
|
||||||
<Divider
|
|
||||||
variant="middle"
|
|
||||||
flexItem
|
|
||||||
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
|
|
||||||
></Divider>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{enhanceItems.length > 0 && (
|
|
||||||
<Box sx={{ mt: 1.5 }}>
|
|
||||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
|
||||||
{enhanceItems.map((item) => (
|
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
|
||||||
<ProfileMore
|
|
||||||
selected={!!chain.includes(item.uid)}
|
|
||||||
activating={activatings.includes(item.uid)}
|
|
||||||
itemData={item}
|
|
||||||
enableNum={chain.length || 0}
|
|
||||||
logInfo={chainLogs[item.uid]}
|
|
||||||
onEnable={() => onEnable(item.uid)}
|
|
||||||
onDisable={() => onDisable(item.uid)}
|
|
||||||
onDelete={() => onDelete(item.uid)}
|
|
||||||
onMoveTop={() => onMoveTop(item.uid)}
|
|
||||||
onMoveEnd={() => onMoveEnd(item.uid)}
|
|
||||||
onEdit={() => viewerRef.current?.edit(item)}
|
|
||||||
onChange={async (prev, curr) => {
|
|
||||||
if (prev !== curr && chain.includes(item.uid)) {
|
|
||||||
await onEnhance();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
|
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
|
||||||
<ConfigViewer ref={configRef} />
|
<ConfigViewer ref={configRef} />
|
||||||
|
78
src/services/types.d.ts
vendored
78
src/services/types.d.ts
vendored
@ -178,11 +178,15 @@ interface IProfileOption {
|
|||||||
self_proxy?: boolean;
|
self_proxy?: boolean;
|
||||||
update_interval?: number;
|
update_interval?: number;
|
||||||
danger_accept_invalid_certs?: boolean;
|
danger_accept_invalid_certs?: boolean;
|
||||||
|
merge?: string;
|
||||||
|
script?: string;
|
||||||
|
rules?: string;
|
||||||
|
proxies?: string;
|
||||||
|
groups?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProfilesConfig {
|
interface IProfilesConfig {
|
||||||
current?: string;
|
current?: string;
|
||||||
chain?: string[];
|
|
||||||
valid?: string[];
|
valid?: string[];
|
||||||
items?: IProfileItem[];
|
items?: IProfileItem[];
|
||||||
}
|
}
|
||||||
@ -254,75 +258,3 @@ interface IVergeConfig {
|
|||||||
proxy_layout_column?: number;
|
proxy_layout_column?: number;
|
||||||
test_list?: IVergeTestItem[];
|
test_list?: IVergeTestItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type IClashConfigValue = any;
|
|
||||||
|
|
||||||
interface IProfileMerge {
|
|
||||||
// clash config fields (default supports)
|
|
||||||
rules?: IClashConfigValue;
|
|
||||||
proxies?: IClashConfigValue;
|
|
||||||
"proxy-groups"?: IClashConfigValue;
|
|
||||||
"proxy-providers"?: IClashConfigValue;
|
|
||||||
"rule-providers"?: IClashConfigValue;
|
|
||||||
// clash config fields (use flag)
|
|
||||||
tun?: IClashConfigValue;
|
|
||||||
dns?: IClashConfigValue;
|
|
||||||
hosts?: IClashConfigValue;
|
|
||||||
script?: IClashConfigValue;
|
|
||||||
profile?: IClashConfigValue;
|
|
||||||
payload?: IClashConfigValue;
|
|
||||||
"interface-name"?: IClashConfigValue;
|
|
||||||
"routing-mark"?: IClashConfigValue;
|
|
||||||
// functional fields
|
|
||||||
use?: string[];
|
|
||||||
"prepend-rules"?: any[];
|
|
||||||
"append-rules"?: any[];
|
|
||||||
"prepend-proxies"?: any[];
|
|
||||||
"append-proxies"?: any[];
|
|
||||||
"prepend-proxy-groups"?: any[];
|
|
||||||
"append-proxy-groups"?: any[];
|
|
||||||
// fix
|
|
||||||
ebpf?: any;
|
|
||||||
experimental?: any;
|
|
||||||
iptables?: any;
|
|
||||||
sniffer?: any;
|
|
||||||
authentication?: any;
|
|
||||||
"bind-address"?: any;
|
|
||||||
"external-ui"?: any;
|
|
||||||
"auto-redir"?: any;
|
|
||||||
"socks-port"?: any;
|
|
||||||
"redir-port"?: any;
|
|
||||||
"tproxy-port"?: any;
|
|
||||||
"geodata-mode"?: any;
|
|
||||||
"tcp-concurrent"?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
// partial of the clash config
|
|
||||||
type IProfileData = Partial<{
|
|
||||||
rules: any[];
|
|
||||||
proxies: any[];
|
|
||||||
"proxy-groups": any[];
|
|
||||||
"proxy-providers": any[];
|
|
||||||
"rule-providers": any[];
|
|
||||||
|
|
||||||
[k: string]: any;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
interface IChainItem {
|
|
||||||
item: IProfileItem;
|
|
||||||
merge?: IProfileMerge;
|
|
||||||
script?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IEnhancedPayload {
|
|
||||||
chain: IChainItem[];
|
|
||||||
valid: string[];
|
|
||||||
current: IProfileData;
|
|
||||||
callback: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IEnhancedResult {
|
|
||||||
data: IProfileData;
|
|
||||||
status: string;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user