mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 04:43:44 +08:00
feat: global merge and script
This commit is contained in:
parent
a63fc25f14
commit
fb4648d2af
@ -1,5 +1,6 @@
|
|||||||
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
|
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::PrfItem,
|
||||||
enhance,
|
enhance,
|
||||||
utils::{dirs, help},
|
utils::{dirs, help},
|
||||||
};
|
};
|
||||||
@ -47,6 +48,22 @@ impl Config {
|
|||||||
|
|
||||||
/// 初始化订阅
|
/// 初始化订阅
|
||||||
pub async fn init_config() -> Result<()> {
|
pub async fn init_config() -> Result<()> {
|
||||||
|
if Self::profiles()
|
||||||
|
.data()
|
||||||
|
.get_item(&"Merge".to_string())
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
let merge_item = PrfItem::from_merge(Some("Merge".to_string()))?;
|
||||||
|
Self::profiles().data().append_item(merge_item.clone())?;
|
||||||
|
}
|
||||||
|
if Self::profiles()
|
||||||
|
.data()
|
||||||
|
.get_item(&"Script".to_string())
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
|
||||||
|
Self::profiles().data().append_item(script_item.clone())?;
|
||||||
|
}
|
||||||
crate::log_err!(Self::generate().await);
|
crate::log_err!(Self::generate().await);
|
||||||
if let Err(err) = Self::generate_file(ConfigType::Run) {
|
if let Err(err) = Self::generate_file(ConfigType::Run) {
|
||||||
log::error!(target: "app", "{err}");
|
log::error!(target: "app", "{err}");
|
||||||
|
@ -175,12 +175,12 @@ impl PrfItem {
|
|||||||
let mut groups = opt_ref.and_then(|o| o.groups.clone());
|
let mut groups = opt_ref.and_then(|o| o.groups.clone());
|
||||||
|
|
||||||
if merge.is_none() {
|
if merge.is_none() {
|
||||||
let merge_item = PrfItem::from_merge()?;
|
let merge_item = PrfItem::from_merge(None)?;
|
||||||
Config::profiles().data().append_item(merge_item.clone())?;
|
Config::profiles().data().append_item(merge_item.clone())?;
|
||||||
merge = merge_item.uid;
|
merge = merge_item.uid;
|
||||||
}
|
}
|
||||||
if script.is_none() {
|
if script.is_none() {
|
||||||
let script_item = PrfItem::from_script()?;
|
let script_item = PrfItem::from_script(None)?;
|
||||||
Config::profiles().data().append_item(script_item.clone())?;
|
Config::profiles().data().append_item(script_item.clone())?;
|
||||||
script = script_item.uid;
|
script = script_item.uid;
|
||||||
}
|
}
|
||||||
@ -248,12 +248,12 @@ impl PrfItem {
|
|||||||
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() {
|
if merge.is_none() {
|
||||||
let merge_item = PrfItem::from_merge()?;
|
let merge_item = PrfItem::from_merge(None)?;
|
||||||
Config::profiles().data().append_item(merge_item.clone())?;
|
Config::profiles().data().append_item(merge_item.clone())?;
|
||||||
merge = merge_item.uid;
|
merge = merge_item.uid;
|
||||||
}
|
}
|
||||||
if script.is_none() {
|
if script.is_none() {
|
||||||
let script_item = PrfItem::from_script()?;
|
let script_item = PrfItem::from_script(None)?;
|
||||||
Config::profiles().data().append_item(script_item.clone())?;
|
Config::profiles().data().append_item(script_item.clone())?;
|
||||||
script = script_item.uid;
|
script = script_item.uid;
|
||||||
}
|
}
|
||||||
@ -426,12 +426,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() -> Result<PrfItem> {
|
pub fn from_merge(uid: Option<String>) -> Result<PrfItem> {
|
||||||
let uid = help::get_uid("m");
|
let mut id = help::get_uid("m");
|
||||||
let file = format!("{uid}.yaml");
|
if let Some(uid) = uid {
|
||||||
|
id = uid;
|
||||||
|
}
|
||||||
|
let file = format!("{id}.yaml");
|
||||||
|
|
||||||
Ok(PrfItem {
|
Ok(PrfItem {
|
||||||
uid: Some(uid),
|
uid: Some(id),
|
||||||
itype: Some("merge".into()),
|
itype: Some("merge".into()),
|
||||||
name: None,
|
name: None,
|
||||||
desc: None,
|
desc: None,
|
||||||
@ -448,12 +451,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() -> Result<PrfItem> {
|
pub fn from_script(uid: Option<String>) -> Result<PrfItem> {
|
||||||
let uid = help::get_uid("s");
|
let mut id = help::get_uid("s");
|
||||||
let file = format!("{uid}.js"); // js ext
|
if let Some(uid) = uid {
|
||||||
|
id = uid;
|
||||||
|
}
|
||||||
|
let file = format!("{id}.js"); // js ext
|
||||||
|
|
||||||
Ok(PrfItem {
|
Ok(PrfItem {
|
||||||
uid: Some(uid),
|
uid: Some(id),
|
||||||
itype: Some("script".into()),
|
itype: Some("script".into()),
|
||||||
name: None,
|
name: None,
|
||||||
desc: None,
|
desc: None,
|
||||||
|
@ -50,7 +50,16 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 从profiles里拿东西
|
// 从profiles里拿东西
|
||||||
let (mut config, merge_item, script_item, rules_item, proxies_item, groups_item) = {
|
let (
|
||||||
|
mut config,
|
||||||
|
merge_item,
|
||||||
|
script_item,
|
||||||
|
rules_item,
|
||||||
|
proxies_item,
|
||||||
|
groups_item,
|
||||||
|
global_merge,
|
||||||
|
global_script,
|
||||||
|
) = {
|
||||||
let profiles = Config::profiles();
|
let profiles = Config::profiles();
|
||||||
let profiles = profiles.latest();
|
let profiles = profiles.latest();
|
||||||
|
|
||||||
@ -96,7 +105,34 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
data: ChainType::Groups(SeqMap::default()),
|
data: ChainType::Groups(SeqMap::default()),
|
||||||
});
|
});
|
||||||
|
|
||||||
(current, merge, script, rules, proxies, groups)
|
let global_merge = profiles
|
||||||
|
.get_item(&"Merge".to_string())
|
||||||
|
.ok()
|
||||||
|
.and_then(<Option<ChainItem>>::from)
|
||||||
|
.unwrap_or_else(|| ChainItem {
|
||||||
|
uid: "Merge".into(),
|
||||||
|
data: ChainType::Merge(Mapping::new()),
|
||||||
|
});
|
||||||
|
|
||||||
|
let global_script = profiles
|
||||||
|
.get_item(&"Script".to_string())
|
||||||
|
.ok()
|
||||||
|
.and_then(<Option<ChainItem>>::from)
|
||||||
|
.unwrap_or_else(|| ChainItem {
|
||||||
|
uid: "Script".into(),
|
||||||
|
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
(
|
||||||
|
current,
|
||||||
|
merge,
|
||||||
|
script,
|
||||||
|
rules,
|
||||||
|
proxies,
|
||||||
|
groups,
|
||||||
|
global_merge,
|
||||||
|
global_script,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result_map = HashMap::new(); // 保存脚本日志
|
let mut result_map = HashMap::new(); // 保存脚本日志
|
||||||
@ -136,6 +172,27 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
|||||||
result_map.insert(script_item.uid, logs);
|
result_map.insert(script_item.uid, logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局Merge和Script
|
||||||
|
if let ChainType::Merge(merge) = global_merge.data {
|
||||||
|
exists_keys.extend(use_keys(&merge));
|
||||||
|
config = use_merge(merge, config.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ChainType::Script(script) = global_script.data {
|
||||||
|
let mut logs = vec![];
|
||||||
|
|
||||||
|
match use_script(script, config.to_owned()) {
|
||||||
|
Ok((res_config, res_logs)) => {
|
||||||
|
exists_keys.extend(use_keys(&res_config));
|
||||||
|
config = res_config;
|
||||||
|
logs.extend(res_logs);
|
||||||
|
}
|
||||||
|
Err(err) => logs.push(("exception".into(), err.to_string())),
|
||||||
|
}
|
||||||
|
|
||||||
|
result_map.insert(global_script.uid, logs);
|
||||||
|
}
|
||||||
|
|
||||||
// 合并默认的config
|
// 合并默认的config
|
||||||
for (key, value) in clash_config.into_iter() {
|
for (key, value) in clash_config.into_iter() {
|
||||||
if key.as_str() == Some("tun") {
|
if key.as_str() == Some("tun") {
|
||||||
|
197
src/components/profile/profile-more.tsx
Normal file
197
src/components/profile/profile-more.tsx
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useLockFn } from "ahooks";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Badge,
|
||||||
|
Chip,
|
||||||
|
Typography,
|
||||||
|
MenuItem,
|
||||||
|
Menu,
|
||||||
|
IconButton,
|
||||||
|
} 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";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
logInfo?: [string, string][];
|
||||||
|
id: "Merge" | "Script";
|
||||||
|
onChange?: (prev?: string, curr?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// profile enhanced item
|
||||||
|
export const ProfileMore = (props: Props) => {
|
||||||
|
const { id, logInfo = [], onChange } = props;
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const [anchorEl, setAnchorEl] = useState<any>(null);
|
||||||
|
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||||
|
const [fileOpen, setFileOpen] = useState(false);
|
||||||
|
const [logOpen, setLogOpen] = useState(false);
|
||||||
|
|
||||||
|
const onEditFile = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
setFileOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenFile = useLockFn(async () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
try {
|
||||||
|
await viewProfile(id);
|
||||||
|
} 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 itemMenu = [
|
||||||
|
{ label: "Edit File", handler: onEditFile },
|
||||||
|
{ label: "Open File", handler: onOpenFile },
|
||||||
|
];
|
||||||
|
|
||||||
|
const boxStyle = {
|
||||||
|
height: 26,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
lineHeight: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProfileBox
|
||||||
|
onDoubleClick={onEditFile}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
const { clientX, clientY } = event;
|
||||||
|
setPosition({ top: clientY, left: clientX });
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
mb={0.5}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
width="calc(100% - 52px)"
|
||||||
|
variant="h6"
|
||||||
|
component="h2"
|
||||||
|
noWrap
|
||||||
|
title={t(`Global ${id}`)}
|
||||||
|
>
|
||||||
|
{t(`Global ${id}`)}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Chip
|
||||||
|
label={id}
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ height: 20, textTransform: "capitalize" }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={boxStyle}>
|
||||||
|
{id === "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={t(`${id} Description`)}
|
||||||
|
sx={i18n.language === "zh" ? { width: "calc(100% - 75px)" } : {}}
|
||||||
|
>
|
||||||
|
{t(`${id} Description`)}
|
||||||
|
</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();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{itemMenu
|
||||||
|
.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={id}
|
||||||
|
open={fileOpen}
|
||||||
|
language={id === "Merge" ? "yaml" : "javascript"}
|
||||||
|
schema={id === "Merge" ? "merge" : undefined}
|
||||||
|
onChange={onChange}
|
||||||
|
onClose={() => setFileOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LogViewer
|
||||||
|
open={logOpen}
|
||||||
|
logInfo={logInfo}
|
||||||
|
onClose={() => setLogOpen(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -41,6 +41,7 @@ import {
|
|||||||
ProfileViewer,
|
ProfileViewer,
|
||||||
ProfileViewerRef,
|
ProfileViewerRef,
|
||||||
} from "@/components/profile/profile-viewer";
|
} from "@/components/profile/profile-viewer";
|
||||||
|
import { ProfileMore } from "@/components/profile/profile-more";
|
||||||
import { ProfileItem } from "@/components/profile/profile-item";
|
import { ProfileItem } from "@/components/profile/profile-item";
|
||||||
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";
|
||||||
@ -49,6 +50,7 @@ import { BaseStyledTextField } from "@/components/base/base-styled-text-field";
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { readTextFile } from "@tauri-apps/api/fs";
|
import { readTextFile } from "@tauri-apps/api/fs";
|
||||||
import { readText } from "@tauri-apps/api/clipboard";
|
import { readText } from "@tauri-apps/api/clipboard";
|
||||||
|
import { EditorViewer } from "@/components/profile/editor-viewer";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -244,6 +246,12 @@ const ProfilePage = () => {
|
|||||||
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
|
||||||
full
|
full
|
||||||
@ -383,7 +391,38 @@ const ProfilePage = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
<Divider
|
||||||
|
variant="middle"
|
||||||
|
flexItem
|
||||||
|
sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }}
|
||||||
|
></Divider>
|
||||||
|
<Box sx={{ mt: 1.5 }}>
|
||||||
|
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||||
|
<Grid item sm={6} md={6} lg={6}>
|
||||||
|
<ProfileMore
|
||||||
|
id="Merge"
|
||||||
|
onChange={async (prev, curr) => {
|
||||||
|
if (prev !== curr) {
|
||||||
|
await onEnhance();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sm={6} md={6} lg={6}>
|
||||||
|
<ProfileMore
|
||||||
|
id="Script"
|
||||||
|
logInfo={chainLogs["Script"]}
|
||||||
|
onChange={async (prev, curr) => {
|
||||||
|
if (prev !== curr) {
|
||||||
|
await onEnhance();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
|
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
|
||||||
<ConfigViewer ref={configRef} />
|
<ConfigViewer ref={configRef} />
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user