mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Tue May 20 20:37:27 CEST 2025
This commit is contained in:
@@ -1004,3 +1004,4 @@ Update On Fri May 16 20:36:33 CEST 2025
|
||||
Update On Sat May 17 20:35:13 CEST 2025
|
||||
Update On Sun May 18 20:35:03 CEST 2025
|
||||
Update On Mon May 19 20:37:08 CEST 2025
|
||||
Update On Tue May 20 20:37:19 CEST 2025
|
||||
|
||||
@@ -12,6 +12,8 @@ public static class Config
|
||||
public static string HOST { get; set; } = "api.bilibili.com";
|
||||
//BiliPlus EP Host
|
||||
public static string EPHOST { get; set; } = "api.bilibili.com";
|
||||
//Bili Tv Api Host
|
||||
public static string TVHOST { get; set; } = "api.snm0516.aisee.tv";
|
||||
//BiliPlus Area
|
||||
public static string AREA { get; set; } = "";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public static partial class Parser
|
||||
|
||||
if (appApi) return await AppHelper.DoReqAsync(aid, cid, epId, qn, bangumi, encoding, Config.TOKEN);
|
||||
|
||||
string prefix = tvApi ? bangumi ? "api.snm0516.aisee.tv/pgc/player/api/playurltv" : "api.snm0516.aisee.tv/x/tv/playurl"
|
||||
string prefix = tvApi ? bangumi ? $"{Config.TVHOST}/pgc/player/api/playurltv" : $"{Config.TVHOST}/x/tv/playurl"
|
||||
: bangumi ? $"{Config.HOST}/pgc/player/web/v2/playurl" : "api.bilibili.com/x/player/wbi/playurl";
|
||||
prefix = $"https://{prefix}?";
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ internal static class CommandLineInvoker
|
||||
private static readonly Option<string> MultiFilePattern = new(["--multi-file-pattern", "-M"], $"使用内置变量自定义多P存储文件名:\r\n\r\n默认为: {Program.MultiPageDefaultSavePath}\r\n");
|
||||
private static readonly Option<string> Host = new(["--host"], "指定BiliPlus host(使用BiliPlus需要access_token, 不需要cookie, 解析服务器能够获取你账号的大部分权限!)");
|
||||
private static readonly Option<string> EpHost = new(["--ep-host"], "指定BiliPlus EP host(用于代理api.bilibili.com/pgc/view/web/season, 大部分解析服务器不支持代理该接口)");
|
||||
private static readonly Option<string> TvHost = new(["--tv-host"], "自定义tv端接口请求Host(用于代理api.snm0516.aisee.tv)");
|
||||
private static readonly Option<string> Area = new(["--area"], "(hk|tw|th) 使用BiliPlus时必选, 指定BiliPlus area");
|
||||
private static readonly Option<string> ConfigFile = new(["--config-file"], "读取指定的BBDown本地配置文件(默认为: BBDown.config)");//以下仅为兼容旧版本命令行, 不建议使用
|
||||
private static readonly Option<string> Aria2cProxy = new(["--aria2c-proxy"], "调用aria2c进行下载时的代理地址配置") { IsHidden = true };
|
||||
@@ -144,6 +145,7 @@ internal static class CommandLineInvoker
|
||||
if (bindingContext.ParseResult.HasOption(DelayPerPage)) option.DelayPerPage = bindingContext.ParseResult.GetValueForOption(DelayPerPage)!;
|
||||
if (bindingContext.ParseResult.HasOption(Host)) option.Host = bindingContext.ParseResult.GetValueForOption(Host)!;
|
||||
if (bindingContext.ParseResult.HasOption(EpHost)) option.EpHost = bindingContext.ParseResult.GetValueForOption(EpHost)!;
|
||||
if (bindingContext.ParseResult.HasOption(TvHost)) option.TvHost = bindingContext.ParseResult.GetValueForOption(TvHost)!;
|
||||
if (bindingContext.ParseResult.HasOption(Area)) option.Area = bindingContext.ParseResult.GetValueForOption(Area)!;
|
||||
if (bindingContext.ParseResult.HasOption(ConfigFile)) option.ConfigFile = bindingContext.ParseResult.GetValueForOption(ConfigFile)!;
|
||||
if (bindingContext.ParseResult.HasOption(Aria2cProxy)) option.Aria2cProxy = bindingContext.ParseResult.GetValueForOption(Aria2cProxy)!;
|
||||
@@ -208,6 +210,7 @@ internal static class CommandLineInvoker
|
||||
DelayPerPage,
|
||||
Host,
|
||||
EpHost,
|
||||
TvHost,
|
||||
Area,
|
||||
ConfigFile,
|
||||
Aria2cProxy,
|
||||
|
||||
@@ -50,6 +50,7 @@ internal class MyOption
|
||||
public string DelayPerPage { get; set; } = "0";
|
||||
public string Host { get; set; } = "api.bilibili.com";
|
||||
public string EpHost { get; set; } = "api.bilibili.com";
|
||||
public string TvHost { get; set; } = "api.snm0516.aisee.tv";
|
||||
public string Area { get; set; } = "";
|
||||
public string? ConfigFile { get; set; }
|
||||
//以下仅为兼容旧版本命令行,不建议使用
|
||||
|
||||
@@ -217,6 +217,7 @@ partial class Program
|
||||
Config.DEBUG_LOG = myOption.Debug;
|
||||
Config.HOST = myOption.Host;
|
||||
Config.EPHOST = myOption.EpHost;
|
||||
Config.TVHOST = myOption.TvHost;
|
||||
Config.AREA = myOption.Area;
|
||||
Config.COOKIE = myOption.Cookie;
|
||||
Config.TOKEN = myOption.AccessToken.Replace("access_token=", "");
|
||||
|
||||
@@ -111,6 +111,7 @@ Options:
|
||||
--delay-per-page <delay-per-page> 设置下载合集分P之间的下载间隔时间(单位: 秒, 默认无间隔)
|
||||
--host <host> 指定BiliPlus host(使用BiliPlus需要access_token, 不需要cookie, 解析服务器能够获取你账号的大部分权限!)
|
||||
--ep-host <ep-host> 指定BiliPlus EP host(用于代理api.bilibili.com/pgc/view/web/season, 大部分解析服务器不支持代理该接口)
|
||||
--tv-host <tv-host> 自定义tv端接口请求Host(用于代理api.snm0516.aisee.tv)
|
||||
--area <area> (hk|tw|th) 使用BiliPlus时必选, 指定BiliPlus area
|
||||
--config-file <config-file> 读取指定的BBDown本地配置文件(默认为: BBDown.config)
|
||||
--version Show version information
|
||||
|
||||
@@ -118,7 +118,10 @@ func (u *UIUpdater) downloadUI() error {
|
||||
|
||||
tmpDir := C.Path.Resolve("downloadUI.tmp")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
extractedFolder, err := extract(data, tmpDir)
|
||||
|
||||
os.RemoveAll(tmpDir) // cleanup tmp dir before extract
|
||||
log.Debugln("extractedFolder: %s", tmpDir)
|
||||
err = extract(data, tmpDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't extract compressed file: %w", err)
|
||||
}
|
||||
@@ -136,8 +139,8 @@ func (u *UIUpdater) downloadUI() error {
|
||||
return fmt.Errorf("prepare UI path failed: %w", err)
|
||||
}
|
||||
|
||||
log.Debugln("moveFolder from %s to %s", extractedFolder, u.externalUIPath)
|
||||
err = moveDir(extractedFolder, u.externalUIPath) // move files from tmp to target
|
||||
log.Debugln("moveFolder from %s to %s", tmpDir, u.externalUIPath)
|
||||
err = moveDir(tmpDir, u.externalUIPath) // move files from tmp to target
|
||||
if err != nil {
|
||||
return fmt.Errorf("move UI folder failed: %w", err)
|
||||
}
|
||||
@@ -154,63 +157,19 @@ func (u *UIUpdater) prepareUIPath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unzip(data []byte, dest string) (string, error) {
|
||||
func unzip(data []byte, dest string) error {
|
||||
r, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
// check whether or not only exists singleRoot dir
|
||||
rootDir := ""
|
||||
isSingleRoot := true
|
||||
rootItemCount := 0
|
||||
for _, f := range r.File {
|
||||
parts := strings.Split(strings.Trim(f.Name, "/"), "/")
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
isDir := strings.HasSuffix(f.Name, "/")
|
||||
if !isDir {
|
||||
isSingleRoot = false
|
||||
break
|
||||
}
|
||||
|
||||
if rootDir == "" {
|
||||
rootDir = parts[0]
|
||||
}
|
||||
rootItemCount++
|
||||
}
|
||||
}
|
||||
|
||||
if rootItemCount != 1 {
|
||||
isSingleRoot = false
|
||||
}
|
||||
|
||||
// build the dir of extraction
|
||||
var extractedFolder string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
// if the singleRoot, use it directly
|
||||
log.Debugln("Match the singleRoot")
|
||||
extractedFolder = filepath.Join(dest, rootDir)
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
} else {
|
||||
log.Debugln("Match the multiRoot")
|
||||
extractedFolder = dest
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
}
|
||||
|
||||
for _, f := range r.File {
|
||||
var fpath string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
fpath = filepath.Join(dest, f.Name)
|
||||
} else {
|
||||
fpath = filepath.Join(extractedFolder, f.Name)
|
||||
}
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
|
||||
if !inDest(fpath, dest) {
|
||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||
return fmt.Errorf("invalid file path: %s", fpath)
|
||||
}
|
||||
info := f.FileInfo()
|
||||
if info.IsDir() {
|
||||
@@ -221,128 +180,77 @@ func unzip(data []byte, dest string) (string, error) {
|
||||
continue // disallow symlink
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(outFile, rc)
|
||||
outFile.Close()
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
}
|
||||
return extractedFolder, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func untgz(data []byte, dest string) (string, error) {
|
||||
func untgz(data []byte, dest string) error {
|
||||
gzr, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
rootDir := ""
|
||||
isSingleRoot := true
|
||||
rootItemCount := 0
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parts := strings.Split(cleanTarPath(header.Name), string(os.PathSeparator))
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
isDir := header.Typeflag == tar.TypeDir
|
||||
if !isDir {
|
||||
isSingleRoot = false
|
||||
break
|
||||
}
|
||||
|
||||
if rootDir == "" {
|
||||
rootDir = parts[0]
|
||||
}
|
||||
rootItemCount++
|
||||
}
|
||||
}
|
||||
|
||||
if rootItemCount != 1 {
|
||||
isSingleRoot = false
|
||||
}
|
||||
|
||||
_ = gzr.Reset(bytes.NewReader(data))
|
||||
tr = tar.NewReader(gzr)
|
||||
|
||||
var extractedFolder string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
log.Debugln("Match the singleRoot")
|
||||
extractedFolder = filepath.Join(dest, rootDir)
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
} else {
|
||||
log.Debugln("Match the multiRoot")
|
||||
extractedFolder = dest
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
}
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
var fpath string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
fpath = filepath.Join(dest, cleanTarPath(header.Name))
|
||||
} else {
|
||||
fpath = filepath.Join(extractedFolder, cleanTarPath(header.Name))
|
||||
}
|
||||
fpath := filepath.Join(dest, header.Name)
|
||||
|
||||
if !inDest(fpath, dest) {
|
||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||
return fmt.Errorf("invalid file path: %s", fpath)
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(outFile, tr); err != nil {
|
||||
outFile.Close()
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile.Close()
|
||||
}
|
||||
}
|
||||
return extractedFolder, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func extract(data []byte, dest string) (string, error) {
|
||||
func extract(data []byte, dest string) error {
|
||||
fileType := detectFileType(data)
|
||||
log.Debugln("compression Type: %s", fileType)
|
||||
switch fileType {
|
||||
@@ -351,7 +259,7 @@ func extract(data []byte, dest string) (string, error) {
|
||||
case typeTarGzip:
|
||||
return untgz(data, dest)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown or unsupported file type")
|
||||
return fmt.Errorf("unknown or unsupported file type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +301,15 @@ func moveDir(src string, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dirEntryList) == 1 && dirEntryList[0].IsDir() {
|
||||
src = filepath.Join(src, dirEntryList[0].Name())
|
||||
log.Debugln("match the singleRoot: %s", src)
|
||||
dirEntryList, err = os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, dirEntry := range dirEntryList {
|
||||
err = os.Rename(filepath.Join(src, dirEntry.Name()), filepath.Join(dst, dirEntry.Name()))
|
||||
if err != nil {
|
||||
|
||||
@@ -645,6 +645,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: xxx
|
||||
short-id: xxx # optional
|
||||
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
|
||||
client-fingerprint: chrome # cannot be empty
|
||||
|
||||
- name: "vless-reality-grpc"
|
||||
@@ -664,6 +665,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
short-id: 10f897e26c4b9478
|
||||
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
|
||||
|
||||
- name: "vless-ws"
|
||||
type: vless
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ require (
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
|
||||
github.com/metacubex/sing-mux v0.3.2
|
||||
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336
|
||||
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.3
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
|
||||
+2
-4
@@ -120,10 +120,8 @@ github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxM
|
||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
||||
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336 h1:5BgpaFkTzkePwF1A8rmhCqgyOMG79BLsAhFR8W8SiRo=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a h1:Ho73vGiB94LmtK5T+tKVwtCNEi/YiHmPjlqpHSAmAVs=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
@@ -42,16 +41,19 @@ type DialFn = func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
type Conn struct {
|
||||
initFn func() (io.ReadCloser, netAddr, error)
|
||||
writer io.Writer
|
||||
writer io.Writer // writer must not nil
|
||||
closer io.Closer
|
||||
netAddr
|
||||
|
||||
reader io.ReadCloser
|
||||
once sync.Once
|
||||
closed atomic.Bool
|
||||
err error
|
||||
remain int
|
||||
br *bufio.Reader
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
reader io.ReadCloser
|
||||
br *bufio.Reader
|
||||
remain int
|
||||
|
||||
closeMutex sync.Mutex
|
||||
closed bool
|
||||
|
||||
// deadlines
|
||||
deadline *time.Timer
|
||||
}
|
||||
@@ -65,7 +67,7 @@ type Config struct {
|
||||
func (g *Conn) initReader() {
|
||||
reader, addr, err := g.initFn()
|
||||
if err != nil {
|
||||
g.err = err
|
||||
g.initErr = err
|
||||
if closer, ok := g.writer.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
@@ -73,17 +75,21 @@ func (g *Conn) initReader() {
|
||||
}
|
||||
g.netAddr = addr
|
||||
|
||||
if !g.closed.Load() {
|
||||
g.reader = reader
|
||||
g.br = bufio.NewReader(reader)
|
||||
} else {
|
||||
reader.Close()
|
||||
g.closeMutex.Lock()
|
||||
defer g.closeMutex.Unlock()
|
||||
if g.closed { // if g.Close() be called between g.initFn(), direct close the initFn returned reader
|
||||
_ = reader.Close()
|
||||
g.initErr = net.ErrClosed
|
||||
return
|
||||
}
|
||||
|
||||
g.reader = reader
|
||||
g.br = bufio.NewReader(reader)
|
||||
}
|
||||
|
||||
func (g *Conn) Init() error {
|
||||
g.once.Do(g.initReader)
|
||||
return g.err
|
||||
g.initOnce.Do(g.initReader)
|
||||
return g.initErr
|
||||
}
|
||||
|
||||
func (g *Conn) Read(b []byte) (n int, err error) {
|
||||
@@ -100,8 +106,6 @@ func (g *Conn) Read(b []byte) (n int, err error) {
|
||||
n, err = io.ReadFull(g.br, b[:size])
|
||||
g.remain -= n
|
||||
return
|
||||
} else if g.reader == nil {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
|
||||
// 0x00 grpclength(uint32) 0x0A uleb128 payload
|
||||
@@ -147,8 +151,8 @@ func (g *Conn) Write(b []byte) (n int, err error) {
|
||||
buf.Write(b)
|
||||
|
||||
_, err = g.writer.Write(buf.Bytes())
|
||||
if err == io.ErrClosedPipe && g.err != nil {
|
||||
err = g.err
|
||||
if err == io.ErrClosedPipe && g.initErr != nil {
|
||||
err = g.initErr
|
||||
}
|
||||
|
||||
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||
@@ -170,8 +174,8 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
binary.PutUvarint(header[6:], uint64(dataLen))
|
||||
_, err := g.writer.Write(buffer.Bytes())
|
||||
|
||||
if err == io.ErrClosedPipe && g.err != nil {
|
||||
err = g.err
|
||||
if err == io.ErrClosedPipe && g.initErr != nil {
|
||||
err = g.initErr
|
||||
}
|
||||
|
||||
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||
@@ -186,7 +190,17 @@ func (g *Conn) FrontHeadroom() int {
|
||||
}
|
||||
|
||||
func (g *Conn) Close() error {
|
||||
g.closed.Store(true)
|
||||
g.initOnce.Do(func() { // if initReader not called, it should not be run anymore
|
||||
g.initErr = net.ErrClosed
|
||||
})
|
||||
|
||||
g.closeMutex.Lock()
|
||||
defer g.closeMutex.Unlock()
|
||||
if g.closed {
|
||||
return nil
|
||||
}
|
||||
g.closed = true
|
||||
|
||||
var errorArr []error
|
||||
|
||||
if reader := g.reader; reader != nil {
|
||||
|
||||
Generated
+554
-466
File diff suppressed because it is too large
Load Diff
@@ -110,7 +110,7 @@ semver = "1.0"
|
||||
|
||||
# Compression & Encoding
|
||||
flate2 = "1.0"
|
||||
zip = "2.0.0"
|
||||
zip = "3.0.0"
|
||||
zip-extensions = "0.8.0"
|
||||
base64 = "0.22"
|
||||
adler = "1.0.2"
|
||||
@@ -172,12 +172,12 @@ display-info = "0.5.0" # should be removed after upgrading to tauri v2
|
||||
|
||||
# OXC (The Oxidation Compiler)
|
||||
# We use it to parse and transpile the old script profile to esm based script profile
|
||||
oxc_parser = "0.68"
|
||||
oxc_allocator = "0.68"
|
||||
oxc_span = "0.68"
|
||||
oxc_ast = "0.68"
|
||||
oxc_syntax = "0.68"
|
||||
oxc_ast_visit = "0.68"
|
||||
oxc_parser = "0.71"
|
||||
oxc_allocator = "0.71"
|
||||
oxc_span = "0.71"
|
||||
oxc_ast = "0.71"
|
||||
oxc_syntax = "0.71"
|
||||
oxc_ast_visit = "0.71"
|
||||
|
||||
# Lua Integration
|
||||
mlua = { version = "0.10", features = [
|
||||
|
||||
@@ -92,10 +92,16 @@ impl Instance {
|
||||
.unwrap_or(&ClashCore::ClashPremium))
|
||||
.into()
|
||||
};
|
||||
let data_dir = dirs::app_data_dir()?;
|
||||
let binary = find_binary_path(&core_type)?;
|
||||
let config_path = Config::generate_file(ConfigType::Run)?;
|
||||
let pid_path = dirs::clash_pid_path()?;
|
||||
let data_dir = camino::Utf8PathBuf::from_path_buf(dirs::app_data_dir()?)
|
||||
.map_err(|e| anyhow::anyhow!("failed to convert data dir to utf8 path: {:?}", e))?;
|
||||
let binary = camino::Utf8PathBuf::from_path_buf(find_binary_path(&core_type)?)
|
||||
.map_err(|e| anyhow::anyhow!("failed to convert binary path to utf8 path: {:?}", e))?;
|
||||
let config_path = camino::Utf8PathBuf::from_path_buf(Config::generate_file(
|
||||
ConfigType::Run,
|
||||
)?)
|
||||
.map_err(|e| anyhow::anyhow!("failed to convert config path to utf8 path: {:?}", e))?;
|
||||
let pid_path = camino::Utf8PathBuf::from_path_buf(dirs::clash_pid_path()?)
|
||||
.map_err(|e| anyhow::anyhow!("failed to convert pid path to utf8 path: {:?}", e))?;
|
||||
match run_type {
|
||||
RunType::Normal => {
|
||||
let instance = Arc::new(
|
||||
@@ -114,7 +120,7 @@ impl Instance {
|
||||
})
|
||||
}
|
||||
RunType::Service => Ok(Instance::Service {
|
||||
config_path,
|
||||
config_path: config_path.into(),
|
||||
core_type,
|
||||
}),
|
||||
RunType::Elevated => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import getSystem from '@/utils/get-system'
|
||||
import { alpha, useTheme } from '@mui/material'
|
||||
import { Box } from '@mui/material'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import 'allotment/dist/style.css'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { ReactNode, useEffect, useRef } from 'react'
|
||||
import { atomIsDrawerOnlyIcon } from '@/store'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { alpha, cn } from '@nyanpasu/ui'
|
||||
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { TauriEvent, UnlistenFn } from '@tauri-apps/api/event'
|
||||
import { LayoutControl } from '../layout/layout-control'
|
||||
@@ -25,7 +25,6 @@ export const AppContainer = ({
|
||||
children?: ReactNode
|
||||
isDrawer?: boolean
|
||||
}) => {
|
||||
const { palette } = useTheme()
|
||||
const { data: isMaximized } = useSuspenseQuery({
|
||||
queryKey: ['isMaximized'],
|
||||
queryFn: () => appWindow.isMaximized(),
|
||||
@@ -78,9 +77,11 @@ export const AppContainer = ({
|
||||
)}
|
||||
{/* TODO: add a framer motion animation to toggle the maximized state */}
|
||||
{OS === 'macos' && !isMaximized && (
|
||||
<div
|
||||
<Box
|
||||
className="z-top fixed top-3 left-4 h-8 w-[4.5rem] rounded-full"
|
||||
style={{ backgroundColor: alpha(palette.primary.main, 0.1) }}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { useState } from 'react'
|
||||
import getSystem from '@/utils/get-system'
|
||||
import { MenuOpen } from '@mui/icons-material'
|
||||
import { alpha, Backdrop, IconButton } from '@mui/material'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { Backdrop, IconButton } from '@mui/material'
|
||||
import { alpha, cn } from '@nyanpasu/ui'
|
||||
import AnimatedLogo from '../layout/animated-logo'
|
||||
import DrawerContent from './drawer-content'
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { createElement } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { languageQuirks } from '@/utils/language'
|
||||
import { SvgIconComponent } from '@mui/icons-material'
|
||||
import { alpha, ListItemButton, ListItemIcon, useTheme } from '@mui/material'
|
||||
import { Box, ListItemButton, ListItemIcon } from '@mui/material'
|
||||
import { useSetting } from '@nyanpasu/interface'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { alpha, cn } from '@nyanpasu/ui'
|
||||
import { useMatch, useNavigate } from '@tanstack/react-router'
|
||||
|
||||
export const RouteListItem = ({
|
||||
@@ -19,8 +19,6 @@ export const RouteListItem = ({
|
||||
onlyIcon?: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
const match = useMatch({
|
||||
strict: false,
|
||||
shouldThrow: false,
|
||||
@@ -37,24 +35,18 @@ export const RouteListItem = ({
|
||||
onlyIcon ? '!mx-auto !size-16 !rounded-3xl' : '!rounded-full !pr-14',
|
||||
)}
|
||||
sx={[
|
||||
match
|
||||
? {
|
||||
backgroundColor: alpha(palette.primary.main, 0.3),
|
||||
}
|
||||
: {
|
||||
backgroundColor: alpha(palette.background.paper, 0.15),
|
||||
},
|
||||
match
|
||||
? {
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(palette.primary.main, 0.5),
|
||||
},
|
||||
}
|
||||
: {
|
||||
'&:hover': {
|
||||
backgroundColor: null,
|
||||
},
|
||||
},
|
||||
(theme) => ({
|
||||
backgroundColor: match
|
||||
? alpha(theme.vars.palette.primary.main, 0.3)
|
||||
: alpha(theme.vars.palette.background.paper, 0.15),
|
||||
}),
|
||||
(theme) => ({
|
||||
'&:hover': {
|
||||
backgroundColor: match
|
||||
? alpha(theme.vars.palette.primary.main, 0.5)
|
||||
: null,
|
||||
},
|
||||
}),
|
||||
]}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
@@ -64,22 +56,24 @@ export const RouteListItem = ({
|
||||
>
|
||||
<ListItemIcon>
|
||||
{createElement(icon, {
|
||||
sx: {
|
||||
fill: match ? palette.primary.main : undefined,
|
||||
},
|
||||
sx: (theme) => ({
|
||||
fill: match ? theme.vars.palette.primary.main : undefined,
|
||||
}),
|
||||
className: onlyIcon ? '!size-8' : undefined,
|
||||
})}
|
||||
</ListItemIcon>
|
||||
{!onlyIcon && (
|
||||
<div
|
||||
<Box
|
||||
className={cn(
|
||||
'w-full pt-1 pb-1 text-nowrap',
|
||||
language && languageQuirks[language].drawer.itemClassNames,
|
||||
)}
|
||||
style={{ color: match ? palette.primary.main : undefined }}
|
||||
sx={(theme) => ({
|
||||
color: match ? theme.vars.palette.primary.main : undefined,
|
||||
})}
|
||||
>
|
||||
{t(`label_${name}`)}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</ListItemButton>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InboxRounded } from '@mui/icons-material'
|
||||
import { alpha, Box, Typography } from '@mui/material'
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
|
||||
interface Props {
|
||||
text?: React.ReactNode
|
||||
@@ -11,14 +12,14 @@ export const BaseEmpty = (props: Props) => {
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={({ palette }) => ({
|
||||
sx={(theme) => ({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: alpha(palette.text.secondary, 0.75),
|
||||
color: alpha(theme.vars.palette.text.secondary, 0.75),
|
||||
})}
|
||||
>
|
||||
<InboxRounded sx={{ fontSize: '4em' }} />
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { filesize } from 'filesize'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Download, Upload } from '@mui/icons-material'
|
||||
import { darken, lighten, Paper } from '@mui/material'
|
||||
import { Paper } from '@mui/material'
|
||||
import { useClashConnections } from '@nyanpasu/interface'
|
||||
import { darken, lighten } from '@nyanpasu/ui'
|
||||
|
||||
export default function ConnectionTotal() {
|
||||
const {
|
||||
@@ -65,12 +66,12 @@ export default function ConnectionTotal() {
|
||||
sx={[
|
||||
(theme) => ({
|
||||
color: darken(
|
||||
theme.palette.primary.main,
|
||||
theme.vars.palette.primary.main,
|
||||
downloadHighlight ? 0.9 : 0.3,
|
||||
),
|
||||
...theme.applyStyles('dark', {
|
||||
color: lighten(
|
||||
theme.palette.primary.main,
|
||||
theme.vars.palette.primary.main,
|
||||
downloadHighlight ? 0.2 : 0.9,
|
||||
),
|
||||
}),
|
||||
@@ -94,12 +95,12 @@ export default function ConnectionTotal() {
|
||||
sx={[
|
||||
(theme) => ({
|
||||
color: darken(
|
||||
theme.palette.primary.main,
|
||||
theme.vars.palette.primary.main,
|
||||
uploadHighlight ? 0.9 : 0.3,
|
||||
),
|
||||
...theme.applyStyles('dark', {
|
||||
color: lighten(
|
||||
theme.palette.primary.main,
|
||||
theme.vars.palette.primary.main,
|
||||
downloadHighlight ? 0.2 : 0.9,
|
||||
),
|
||||
}),
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
alpha,
|
||||
FilledInputProps,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { FilledInputProps, TextField, TextFieldProps } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
|
||||
export const HeaderSearch = (props: TextFieldProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const inputProps: Partial<FilledInputProps> = {
|
||||
sx: {
|
||||
sx: (theme) => ({
|
||||
borderRadius: 7,
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
|
||||
'&::before': {
|
||||
display: 'none',
|
||||
@@ -24,7 +17,7 @@ export const HeaderSearch = (props: TextFieldProps) => {
|
||||
'&::after': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -36,7 +29,9 @@ export const HeaderSearch = (props: TextFieldProps) => {
|
||||
variant="filled"
|
||||
className="!pb-0"
|
||||
sx={{ input: { py: 1, fontSize: 14 } }}
|
||||
InputProps={inputProps}
|
||||
slotProps={{
|
||||
input: inputProps,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useColorForDelay } from '@/hooks/theme'
|
||||
import { useColorSxForDelay } from '@/hooks/theme'
|
||||
import { atomIsDrawer } from '@/store'
|
||||
import { Paper } from '@mui/material'
|
||||
import { Box, Paper } from '@mui/material'
|
||||
import Grid from '@mui/material/Grid'
|
||||
import { useSetting } from '@nyanpasu/interface'
|
||||
|
||||
function LatencyTag({ name, value }: { name: string; value: number }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const color = useColorForDelay(value)
|
||||
const sx = useColorSxForDelay(value)
|
||||
|
||||
return (
|
||||
<div className="flex justify-between gap-1">
|
||||
<div className="font-bold">{name}:</div>
|
||||
|
||||
<div className="truncate" style={{ color }}>
|
||||
<Box className="truncate" sx={sx}>
|
||||
{value ? `${value.toFixed(0)} ms` : t('Timeout')}
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,20 +6,25 @@ import { useTranslation } from 'react-i18next'
|
||||
import useSWR from 'swr'
|
||||
import { atomIsDrawer } from '@/store'
|
||||
import {
|
||||
alpha,
|
||||
Box,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
SxProps,
|
||||
Theme,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import Grid from '@mui/material/Grid'
|
||||
import { getCoreStatus, useSystemService } from '@nyanpasu/interface'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
|
||||
type Status = {
|
||||
label: string
|
||||
sx: SxProps<Theme>
|
||||
}
|
||||
|
||||
export const ServiceShortcuts = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const isDrawer = useAtomValue(atomIsDrawer)
|
||||
|
||||
const {
|
||||
@@ -32,19 +37,29 @@ export const ServiceShortcuts = () => {
|
||||
revalidateOnFocus: false,
|
||||
})
|
||||
|
||||
const status = useMemo(() => {
|
||||
const status: Status = useMemo(() => {
|
||||
switch (serviceStatus?.status) {
|
||||
case 'running': {
|
||||
return {
|
||||
label: t('running'),
|
||||
color: alpha(palette.success[palette.mode], 0.3),
|
||||
sx: (theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.success.light, 0.3),
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: alpha(theme.vars.palette.success.dark, 0.3),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
case 'stopped': {
|
||||
return {
|
||||
label: t('stopped'),
|
||||
color: alpha(palette.error[palette.mode], 0.3),
|
||||
sx: (theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.error.light, 0.3),
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: alpha(theme.vars.palette.error.dark, 0.3),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,24 +67,18 @@ export const ServiceShortcuts = () => {
|
||||
default: {
|
||||
return {
|
||||
label: t('not_installed'),
|
||||
color:
|
||||
palette.mode === 'light'
|
||||
? palette.grey[100]
|
||||
: palette.background.paper,
|
||||
sx: (theme) => ({
|
||||
backgroundColor: theme.vars.palette.grey[100],
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: theme.vars.palette.background.paper,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
serviceStatus,
|
||||
t,
|
||||
palette.success,
|
||||
palette.mode,
|
||||
palette.error,
|
||||
palette.grey,
|
||||
palette.background.paper,
|
||||
])
|
||||
}, [serviceStatus, t])
|
||||
|
||||
const coreStatus = useMemo(() => {
|
||||
const coreStatus: Status = useMemo(() => {
|
||||
const status = coreStatusSWR.data || [{ Stopped: null }, 0, 'normal']
|
||||
if (
|
||||
isObject(status[0]) &&
|
||||
@@ -81,16 +90,26 @@ export const ServiceShortcuts = () => {
|
||||
!!Stopped && Stopped.trim()
|
||||
? t('stopped_reason', { reason: Stopped })
|
||||
: t('stopped'),
|
||||
color: alpha(palette.success[palette.mode], 0.3),
|
||||
sx: (theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.success.light, 0.3),
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: alpha(theme.vars.palette.success.dark, 0.3),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: t('service_shortcuts.core_started_by', {
|
||||
by: t(status[2] === 'normal' ? 'UI' : 'service'),
|
||||
}),
|
||||
color: alpha(palette.success[palette.mode], 0.3),
|
||||
sx: (theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.success.light, 0.3),
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: alpha(theme.vars.palette.success.dark, 0.3),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}, [coreStatusSWR.data, palette.mode, palette.success, t])
|
||||
}, [coreStatusSWR.data, t])
|
||||
|
||||
return (
|
||||
<Grid
|
||||
@@ -109,17 +128,17 @@ export const ServiceShortcuts = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div
|
||||
<Box
|
||||
className="flex w-full justify-center gap-[2px] rounded-2xl py-2"
|
||||
style={{ backgroundColor: status.color }}
|
||||
sx={status.sx}
|
||||
>
|
||||
<div>{t('service_shortcuts.service_status')}</div>
|
||||
<div>{t(status.label)}</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div
|
||||
<Box
|
||||
className="flex w-full justify-center gap-[2px] rounded-2xl py-2"
|
||||
style={{ backgroundColor: coreStatus.color }}
|
||||
sx={coreStatus.sx}
|
||||
>
|
||||
<div>{t('service_shortcuts.core_status')}</div>
|
||||
<Tooltip
|
||||
@@ -132,7 +151,7 @@ export const ServiceShortcuts = () => {
|
||||
>
|
||||
<div>{coreStatus.label}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
PushPin,
|
||||
PushPinOutlined,
|
||||
} from '@mui/icons-material'
|
||||
import { alpha, Button, ButtonProps, useTheme } from '@mui/material'
|
||||
import { Button, ButtonProps } from '@mui/material'
|
||||
import { saveWindowSizeState, useSetting } from '@nyanpasu/interface'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { alpha, cn } from '@nyanpasu/ui'
|
||||
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { listen, TauriEvent, UnlistenFn } from '@tauri-apps/api/event'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
@@ -19,15 +19,13 @@ import { platform as getPlatform } from '@tauri-apps/plugin-os'
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
|
||||
const CtrlButton = (props: ButtonProps) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="!size-8 !min-w-0"
|
||||
sx={{
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
sx={(theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
svg: { transform: 'scale(0.9)' },
|
||||
}}
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { alpha, FilledInputProps, TextField, useTheme } from '@mui/material'
|
||||
import { FilledInputProps, TextField } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
import { useLogContext } from './log-provider'
|
||||
|
||||
export const LogFilter = () => {
|
||||
@@ -7,17 +8,15 @@ export const LogFilter = () => {
|
||||
|
||||
const { filterText, setFilterText } = useLogContext()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const inputProps: Partial<FilledInputProps> = {
|
||||
sx: {
|
||||
sx: (theme) => ({
|
||||
borderRadius: 7,
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
|
||||
fieldset: {
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -30,7 +29,9 @@ export const LogFilter = () => {
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
className="!pb-0"
|
||||
sx={{ input: { py: 1, fontSize: 14 } }}
|
||||
InputProps={inputProps}
|
||||
slotProps={{
|
||||
input: inputProps,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import { useAsyncEffect } from 'ahooks'
|
||||
import { CSSProperties, useState } from 'react'
|
||||
import { formatAnsi } from '@/utils/shiki'
|
||||
import { useTheme } from '@mui/material'
|
||||
import { Box, SxProps, Theme, useColorScheme } from '@mui/material'
|
||||
import { LogMessage } from '@nyanpasu/interface'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import styles from './log-item.module.scss'
|
||||
|
||||
const colorMapping: { [key: string]: SxProps<Theme> } = {
|
||||
error: (theme) => ({
|
||||
color: theme.vars.palette.error.main,
|
||||
}),
|
||||
warning: (theme) => ({
|
||||
color: theme.vars.palette.warning.main,
|
||||
}),
|
||||
info: (theme) => ({
|
||||
color: theme.vars.palette.info.main,
|
||||
}),
|
||||
}
|
||||
|
||||
export const LogItem = ({
|
||||
value,
|
||||
className,
|
||||
@@ -13,15 +25,9 @@ export const LogItem = ({
|
||||
value: LogMessage
|
||||
className?: string
|
||||
}) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [payload, setPayload] = useState(value.payload)
|
||||
|
||||
const colorMapping: { [key: string]: string } = {
|
||||
error: palette.error.main,
|
||||
warning: palette.warning.main,
|
||||
info: palette.info.main,
|
||||
}
|
||||
const { mode } = useColorScheme()
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
setPayload(await formatAnsi(value.payload))
|
||||
@@ -34,19 +40,18 @@ export const LogItem = ({
|
||||
<div className="flex gap-2">
|
||||
<span className="font-thin">{value.time}</span>
|
||||
|
||||
<span
|
||||
<Box
|
||||
component="span"
|
||||
className="inline-block font-semibold uppercase"
|
||||
style={{
|
||||
color: colorMapping[value.type],
|
||||
}}
|
||||
sx={colorMapping[value.type]}
|
||||
>
|
||||
{value.type}
|
||||
</span>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
<div className="pb-2 text-wrap">
|
||||
<div
|
||||
className={cn(styles.item, palette.mode === 'dark' && styles.dark)}
|
||||
className={cn(styles.item, mode === 'dark' && styles.dark)}
|
||||
style={
|
||||
{
|
||||
'--item-font': 'var(--font-mono)',
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useState } from 'react'
|
||||
import { alpha, Button, Menu, MenuItem, useTheme } from '@mui/material'
|
||||
import { Button, Menu, MenuItem } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
import { useLogContext } from './log-provider'
|
||||
|
||||
export const LogLevel = () => {
|
||||
const { logLevel, setLogLevel } = useLogContext()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||
|
||||
const handleClick = (value: string) => {
|
||||
@@ -25,10 +24,10 @@ export const LogLevel = () => {
|
||||
<>
|
||||
<Button
|
||||
size="small"
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
textTransform: 'none',
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
}}
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
})}
|
||||
onClick={(e) => setAnchorEl(e.currentTarget)}
|
||||
>
|
||||
{mapping[logLevel]}
|
||||
|
||||
@@ -2,16 +2,9 @@ import { Reorder } from 'framer-motion'
|
||||
import { memo, PointerEvent, useRef, useState, useTransition } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Menu as MenuIcon } from '@mui/icons-material'
|
||||
import {
|
||||
alpha,
|
||||
Button,
|
||||
ListItemButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { Button, ListItemButton, Menu, MenuItem } from '@mui/material'
|
||||
import { ProfileQueryResultItem } from '@nyanpasu/interface'
|
||||
import { cleanDeepClickEvent } from '@nyanpasu/ui'
|
||||
import { alpha, cleanDeepClickEvent } from '@nyanpasu/ui'
|
||||
|
||||
const longPressDelay = 200
|
||||
|
||||
@@ -28,8 +21,6 @@ export const ChainItem = memo(function ChainItem({
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
const handleClick = () => {
|
||||
@@ -89,24 +80,18 @@ export const ChainItem = memo(function ChainItem({
|
||||
{
|
||||
borderRadius: 4,
|
||||
},
|
||||
selected
|
||||
? {
|
||||
backgroundColor: alpha(palette.primary.main, 0.3),
|
||||
}
|
||||
: {
|
||||
backgroundColor: alpha(palette.secondary.main, 0.1),
|
||||
},
|
||||
selected
|
||||
? {
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(palette.primary.main, 0.5),
|
||||
},
|
||||
}
|
||||
: {
|
||||
'&:hover': {
|
||||
backgroundColor: null,
|
||||
},
|
||||
},
|
||||
(theme) => ({
|
||||
backgroundColor: selected
|
||||
? alpha(theme.vars.palette.primary.main, 0.3)
|
||||
: alpha(theme.vars.palette.secondary.main, 0.1),
|
||||
}),
|
||||
(theme) => ({
|
||||
'&:hover': {
|
||||
backgroundColor: selected
|
||||
? alpha(theme.vars.palette.primary.main, 0.5)
|
||||
: null,
|
||||
},
|
||||
}),
|
||||
]}
|
||||
onClick={handleClick}
|
||||
disabled={isPending}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { alpha, useTheme } from '@mui/material'
|
||||
import { Box } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
import { getLanguage, ProfileType } from '../utils'
|
||||
|
||||
export const LanguageChip = ({ type }: { type: ProfileType }) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
const lang = getLanguage(type, true)
|
||||
|
||||
return (
|
||||
lang && (
|
||||
<div
|
||||
<Box
|
||||
className="my-auto rounded-full px-2 py-0.5 text-center text-sm font-bold"
|
||||
style={{
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
color: palette.primary.main,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
color: theme.vars.palette.primary.main,
|
||||
})}
|
||||
>
|
||||
{lang}
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import { useTranslation } from 'react-i18next'
|
||||
import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { Add } from '@mui/icons-material'
|
||||
import { alpha, ListItemButton, useTheme } from '@mui/material'
|
||||
import { ListItemButton } from '@mui/material'
|
||||
import { ProfileQueryResultItem, useProfile } from '@nyanpasu/interface'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
import { ClashProfile, filterProfiles } from '../utils'
|
||||
import ChainItem from './chain-item'
|
||||
import { atomChainsSelected, atomGlobalChainCurrent } from './store'
|
||||
@@ -19,8 +20,6 @@ export interface SideChainProps {
|
||||
export const SideChain = ({ onChainEdit }: SideChainProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const isGlobalChainCurrent = useAtomValue(atomGlobalChainCurrent)
|
||||
|
||||
const currentProfileUid = useAtomValue(atomChainsSelected)
|
||||
@@ -103,10 +102,10 @@ export const SideChain = ({ onChainEdit }: SideChainProps) => {
|
||||
|
||||
<ListItemButton
|
||||
className="!mt-2 !mb-2 flex justify-center gap-2"
|
||||
sx={{
|
||||
backgroundColor: alpha(palette.secondary.main, 0.1),
|
||||
sx={(theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.secondary.main, 0.1),
|
||||
borderRadius: 4,
|
||||
}}
|
||||
})}
|
||||
onClick={() => onChainEdit()}
|
||||
>
|
||||
<Add color="primary" />
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
Update,
|
||||
} from '@mui/icons-material'
|
||||
import {
|
||||
alpha,
|
||||
Badge,
|
||||
Button,
|
||||
Chip,
|
||||
@@ -24,18 +23,16 @@ import {
|
||||
MenuItem,
|
||||
Paper,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import {
|
||||
Profile,
|
||||
ProfileQueryResultItem,
|
||||
RemoteProfile,
|
||||
RemoteProfileOptions,
|
||||
RemoteProfileOptionsBuilder,
|
||||
useClashConnections,
|
||||
useProfile,
|
||||
} from '@nyanpasu/interface'
|
||||
import { cleanDeepClickEvent, cn } from '@nyanpasu/ui'
|
||||
import { alpha, cleanDeepClickEvent, cn } from '@nyanpasu/ui'
|
||||
import { ProfileDialog } from './profile-dialog'
|
||||
import { GlobalUpdatePendingContext } from './provider'
|
||||
|
||||
@@ -59,8 +56,6 @@ export const ProfileItem = memo(function ProfileItem({
|
||||
}: ProfileItemProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const { deleteConnections } = useClashConnections()
|
||||
|
||||
const { upsert } = useProfile()
|
||||
@@ -227,13 +222,11 @@ export const ProfileItem = memo(function ProfileItem({
|
||||
{
|
||||
borderRadius: 6,
|
||||
},
|
||||
selected
|
||||
? {
|
||||
backgroundColor: alpha(palette.primary.main, 0.2),
|
||||
}
|
||||
: {
|
||||
backgroundColor: null,
|
||||
},
|
||||
(theme) => ({
|
||||
backgroundColor: selected
|
||||
? alpha(theme.vars.palette.primary.main, 0.2)
|
||||
: null,
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<div
|
||||
@@ -252,9 +245,9 @@ export const ProfileItem = memo(function ProfileItem({
|
||||
{selected && (
|
||||
<FiberManualRecord
|
||||
className="top-0 mr-auto !size-3 animate-bounce"
|
||||
sx={{
|
||||
fill: palette.success.main,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
fill: theme.vars.palette.success.main,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -2,22 +2,19 @@ import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ClearRounded, ContentCopyRounded, Download } from '@mui/icons-material'
|
||||
import {
|
||||
alpha,
|
||||
CircularProgress,
|
||||
FilledInputProps,
|
||||
IconButton,
|
||||
TextField,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { useProfile } from '@nyanpasu/interface'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
import { readText } from '@tauri-apps/plugin-clipboard-manager'
|
||||
|
||||
export const QuickImport = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [url, setUrl] = useState('')
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -82,14 +79,14 @@ export const QuickImport = () => {
|
||||
}
|
||||
|
||||
const inputProps: Partial<FilledInputProps> = {
|
||||
sx: {
|
||||
sx: (theme) => ({
|
||||
borderRadius: 7,
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
|
||||
fieldset: {
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
}),
|
||||
endAdornment: endAdornment(),
|
||||
}
|
||||
|
||||
@@ -104,7 +101,9 @@ export const QuickImport = () => {
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
onKeyDown={(e) => url !== '' && e.key === 'Enter' && handleImport()}
|
||||
sx={{ input: { py: 1, px: 2 } }}
|
||||
InputProps={inputProps}
|
||||
slotProps={{
|
||||
input: inputProps,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,8 @@ import { useDebounceFn, useLockFn } from 'ahooks'
|
||||
import { memo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Bolt, Done } from '@mui/icons-material'
|
||||
import {
|
||||
alpha,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { Button, CircularProgress, Tooltip } from '@mui/material'
|
||||
import { alpha, cn } from '@nyanpasu/ui'
|
||||
|
||||
export const DelayButton = memo(function DelayButton({
|
||||
onClick,
|
||||
@@ -18,8 +12,6 @@ export const DelayButton = memo(function DelayButton({
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const [mounted, setMounted] = useState(false)
|
||||
@@ -48,21 +40,21 @@ export const DelayButton = memo(function DelayButton({
|
||||
<Tooltip title={t('Latency check')}>
|
||||
<Button
|
||||
className="!fixed right-8 bottom-8 z-10 size-16 !rounded-2xl backdrop-blur"
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
boxShadow: 8,
|
||||
backgroundColor: alpha(
|
||||
palette[isSuccess ? 'success' : 'primary'].main,
|
||||
theme.vars.palette[isSuccess ? 'success' : 'primary'].main,
|
||||
isSuccess ? 0.7 : 0.3,
|
||||
),
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(palette.primary.main, 0.45),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.45),
|
||||
},
|
||||
|
||||
'&.MuiButton-loading': {
|
||||
backgroundColor: alpha(palette.primary.main, 0.15),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.15),
|
||||
},
|
||||
}}
|
||||
})}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Bolt
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { memo, useState } from 'react'
|
||||
import { useColorForDelay } from '@/hooks/theme'
|
||||
import { useColorSxForDelay } from '@/hooks/theme'
|
||||
import { mergeSxProps } from '@/utils/mui-theme'
|
||||
import { Bolt } from '@mui/icons-material'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
@@ -29,10 +30,12 @@ export const DelayChip = memo(function DelayChip({
|
||||
return (
|
||||
<FeatureChip
|
||||
className={cn(className, loading && '!visible')}
|
||||
sx={{
|
||||
ml: 'auto',
|
||||
color: useColorForDelay(delay),
|
||||
}}
|
||||
sx={mergeSxProps(
|
||||
{
|
||||
ml: 'auto',
|
||||
},
|
||||
useColorSxForDelay(delay),
|
||||
)}
|
||||
label={
|
||||
<>
|
||||
<span
|
||||
|
||||
@@ -5,16 +5,14 @@ import { Virtualizer } from 'virtua'
|
||||
import { proxyGroupAtom } from '@/store'
|
||||
import { proxiesFilterAtom } from '@/store/proxies'
|
||||
import {
|
||||
alpha,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemButtonProps,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { getServerPort, useClashProxies } from '@nyanpasu/interface'
|
||||
import { LazyImage } from '@nyanpasu/ui'
|
||||
import { alpha, LazyImage } from '@nyanpasu/ui'
|
||||
|
||||
const IconRender = memo(function IconRender({ icon }: { icon: string }) {
|
||||
const {
|
||||
@@ -55,8 +53,6 @@ export const GroupList = ({
|
||||
}: GroupListProps) => {
|
||||
const { data } = useClashProxies()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [proxyGroup, setProxyGroup] = useAtom(proxyGroupAtom)
|
||||
const proxiesFilter = useAtomValue(proxiesFilterAtom)
|
||||
const deferredProxiesFilter = useDeferredValue(proxiesFilter)
|
||||
@@ -97,13 +93,11 @@ export const GroupList = ({
|
||||
selected={selected}
|
||||
onClick={() => handleSelect(index)}
|
||||
sx={[
|
||||
selected
|
||||
? {
|
||||
backgroundColor: `${alpha(palette.primary.main, 0.3)} !important`,
|
||||
}
|
||||
: {
|
||||
backgroundColor: null,
|
||||
},
|
||||
(theme) => ({
|
||||
backgroundColor: selected
|
||||
? `${alpha(theme.vars.palette.primary.main, 0.3)} !important`
|
||||
: null,
|
||||
}),
|
||||
]}
|
||||
{...listItemButtonProps}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useLockFn } from 'ahooks'
|
||||
import { CSSProperties, memo, useMemo } from 'react'
|
||||
import { alpha, useTheme } from '@mui/material'
|
||||
import Box from '@mui/material/Box'
|
||||
import { ClashProxiesQueryProxyItem } from '@nyanpasu/interface'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { alpha, cn } from '@nyanpasu/ui'
|
||||
import { PaperSwitchButton } from '../setting/modules/system-proxy'
|
||||
import DelayChip from './delay-chip'
|
||||
import FeatureChip from './feature-chip'
|
||||
@@ -21,8 +20,6 @@ export const NodeCard = memo(function NodeCard({
|
||||
disabled?: boolean
|
||||
style?: CSSProperties
|
||||
}) {
|
||||
const { palette } = useTheme()
|
||||
|
||||
const delay = useMemo(() => filterDelay(node.history), [node.history])
|
||||
|
||||
const checked = node.name === now
|
||||
@@ -44,13 +41,16 @@ export const NodeCard = memo(function NodeCard({
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
className={cn(styles.Card, delay === -1 && styles.NoDelay)}
|
||||
sxPaper={{
|
||||
sxPaper={(theme) => ({
|
||||
backgroundColor: checked
|
||||
? alpha(palette.primary.main, 0.3)
|
||||
: palette.mode === 'dark'
|
||||
? alpha(palette.grey[900], 0.3)
|
||||
: palette.grey[100],
|
||||
}}
|
||||
? alpha(theme.vars.palette.primary.main, 0.3)
|
||||
: theme.vars.palette.grey[100],
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: checked
|
||||
? alpha(theme.vars.palette.primary.main, 0.3)
|
||||
: theme.vars.palette.grey[900],
|
||||
}),
|
||||
})}
|
||||
>
|
||||
<Box width="100%" display="flex" gap={0.5}>
|
||||
<FeatureChip label={node.type} />
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Radar } from '@mui/icons-material'
|
||||
import { alpha, Button, Tooltip, useTheme } from '@mui/material'
|
||||
import { Button, Tooltip } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
|
||||
export const ScrollCurrentNode = ({ onClick }: { onClick?: () => void }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
return (
|
||||
<Tooltip title={t('Locate')}>
|
||||
<Button
|
||||
size="small"
|
||||
className="!size-8 !min-w-0"
|
||||
sx={{
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
})}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Radar />
|
||||
|
||||
@@ -2,13 +2,12 @@ import { useAtom } from 'jotai'
|
||||
import { memo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { proxyGroupSortAtom } from '@/store'
|
||||
import { alpha, Button, Menu, MenuItem, useTheme } from '@mui/material'
|
||||
import { Button, Menu, MenuItem } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
|
||||
export const SortSelector = memo(function SortSelector() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [proxyGroupSort, setProxyGroupSort] = useAtom(proxyGroupSortAtom)
|
||||
|
||||
type SortType = typeof proxyGroupSort
|
||||
@@ -31,10 +30,10 @@ export const SortSelector = memo(function SortSelector() {
|
||||
<Button
|
||||
size="small"
|
||||
className="!px-2"
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
textTransform: 'none',
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
}}
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
})}
|
||||
onClick={(e) => setAnchorEl(e.currentTarget)}
|
||||
>
|
||||
{t(tmaps[proxyGroupSort])}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTheme } from '@mui/material'
|
||||
import { Box, SxProps, Theme } from '@mui/material'
|
||||
import { ClashRule } from '@nyanpasu/interface'
|
||||
|
||||
interface Props {
|
||||
@@ -6,26 +6,36 @@ interface Props {
|
||||
value: ClashRule
|
||||
}
|
||||
|
||||
const COLOR = [
|
||||
(theme) => ({
|
||||
color: theme.vars.palette.primary.main,
|
||||
}),
|
||||
(theme) => ({
|
||||
color: theme.vars.palette.secondary.main,
|
||||
}),
|
||||
(theme) => ({
|
||||
color: theme.vars.palette.info.main,
|
||||
}),
|
||||
(theme) => ({
|
||||
color: theme.vars.palette.warning.main,
|
||||
}),
|
||||
(theme) => ({
|
||||
color: theme.vars.palette.success.main,
|
||||
}),
|
||||
] satisfies SxProps<Theme>[]
|
||||
|
||||
const RuleItem = ({ index, value }: Props) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
const COLOR = [
|
||||
palette.primary.main,
|
||||
palette.secondary.main,
|
||||
palette.info.main,
|
||||
palette.warning.main,
|
||||
palette.success.main,
|
||||
]
|
||||
|
||||
const parseColor = (text: string) => {
|
||||
const parseColorSx: (text: string) => SxProps<Theme> = (text) => {
|
||||
const TYPE = {
|
||||
reject: ['REJECT', 'REJECT-DROP'],
|
||||
direct: ['DIRECT'],
|
||||
}
|
||||
|
||||
if (TYPE.reject.includes(text)) return palette.error.main
|
||||
if (TYPE.reject.includes(text))
|
||||
return (theme) => ({ color: theme.vars.palette.error.main })
|
||||
|
||||
if (TYPE.direct.includes(text)) return palette.text.primary
|
||||
if (TYPE.direct.includes(text))
|
||||
return (theme) => ({ color: theme.vars.palette.text.primary })
|
||||
|
||||
let sum = 0
|
||||
|
||||
@@ -38,24 +48,24 @@ const RuleItem = ({ index, value }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="flex p-2 pr-7 pl-7 select-text">
|
||||
<div style={{ color: palette.text.secondary }} className="min-w-14">
|
||||
<Box
|
||||
sx={(theme) => ({ color: theme.vars.palette.text.secondary })}
|
||||
className="min-w-14"
|
||||
>
|
||||
{index + 1}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<div style={{ color: palette.text.primary }}>
|
||||
<Box sx={(theme) => ({ color: theme.vars.palette.text.primary })}>
|
||||
{value.payload || '-'}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex gap-8">
|
||||
<div className="min-w-40 text-sm">{value.type}</div>
|
||||
|
||||
<div
|
||||
className="text-s text-sm"
|
||||
style={{ color: parseColor(value.proxy) }}
|
||||
>
|
||||
<Box className="text-s text-sm" sx={parseColorSx(value.proxy)}>
|
||||
{value.proxy}
|
||||
</div>
|
||||
</Box>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,10 +10,9 @@ import { message } from '@/utils/notification'
|
||||
import parseTraffic from '@/utils/parse-traffic'
|
||||
import FiberManualRecord from '@mui/icons-material/FiberManualRecord'
|
||||
import Update from '@mui/icons-material/Update'
|
||||
import { Button } from '@mui/material'
|
||||
import { Box, Button } from '@mui/material'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import ListItemButton from '@mui/material/ListItemButton'
|
||||
import { alpha, useTheme } from '@mui/material/styles'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import {
|
||||
ClashCore,
|
||||
@@ -22,7 +21,7 @@ import {
|
||||
inspectUpdater,
|
||||
useClashCores,
|
||||
} from '@nyanpasu/interface'
|
||||
import { cleanDeepClickEvent, cn } from '@nyanpasu/ui'
|
||||
import { alpha, cleanDeepClickEvent, cn } from '@nyanpasu/ui'
|
||||
|
||||
export const getImage = (core: ClashCore) => {
|
||||
switch (core) {
|
||||
@@ -56,8 +55,6 @@ const CardProgress = ({
|
||||
data?: InspectUpdater
|
||||
show?: boolean
|
||||
}) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
const parsedState = () => {
|
||||
if (data?.downloader?.state) {
|
||||
return 'waiting'
|
||||
@@ -69,14 +66,15 @@ const CardProgress = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<Box
|
||||
component={motion.div}
|
||||
className={cn(
|
||||
'absolute top-0 left-0 z-10 h-full w-full rounded-2xl backdrop-blur',
|
||||
'flex flex-col items-center justify-center gap-2',
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: alpha(palette.primary.main, 0.3),
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.3),
|
||||
})}
|
||||
animate={show ? 'open' : 'closed'}
|
||||
initial={{ opacity: 0 }}
|
||||
variants={{
|
||||
@@ -92,12 +90,12 @@ const CardProgress = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div
|
||||
<Box
|
||||
className="absolute left-0 h-full rounded-2xl transition-all"
|
||||
style={{
|
||||
backgroundColor: alpha(palette.primary.main, 0.3),
|
||||
sx={(theme) => ({
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.3),
|
||||
width: `${calcProgress(data) < 10 ? 10 : calcProgress(data)}%`,
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
|
||||
<div className="truncate capitalize">{parsedState()}</div>
|
||||
@@ -106,7 +104,7 @@ const CardProgress = ({
|
||||
{calcProgress(data).toFixed(0)}%{''}
|
||||
<span>({parseTraffic(data?.downloader.speed || 0)}/s)</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -138,8 +136,6 @@ export const ClashCoreItem = ({
|
||||
}: ClashCoreItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const { query, updateCore } = useClashCores()
|
||||
|
||||
const haveNewVersion = data.latestVersion
|
||||
@@ -204,14 +200,14 @@ export const ClashCoreItem = ({
|
||||
<ListItem sx={{ pl: 0, pr: 0 }}>
|
||||
<ListItemButton
|
||||
className="!relative !p-0"
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
borderRadius: '16px',
|
||||
backgroundColor: alpha(palette.background.paper, 0.3),
|
||||
backgroundColor: alpha(theme.vars.palette.background.paper, 0.3),
|
||||
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: alpha(palette.primary.main, 0.3),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.3),
|
||||
},
|
||||
}}
|
||||
})}
|
||||
selected={selected}
|
||||
onClick={() => {
|
||||
if (!downloadState) {
|
||||
@@ -230,7 +226,10 @@ export const ClashCoreItem = ({
|
||||
|
||||
{haveNewVersion && (
|
||||
<FiberManualRecord
|
||||
sx={{ height: 10, fill: palette.success.main }}
|
||||
sx={(theme) => ({
|
||||
height: 10,
|
||||
fill: theme.vars.palette.success.main,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ChangeEvent, useState } from 'react'
|
||||
import Marquee from 'react-fast-marquee'
|
||||
import ArrowForwardIos from '@mui/icons-material/ArrowForwardIos'
|
||||
import OpenInNewRounded from '@mui/icons-material/OpenInNewRounded'
|
||||
import { alpha, useTheme } from '@mui/material'
|
||||
import Box from '@mui/material/Box'
|
||||
import ButtonBase, { ButtonBaseProps } from '@mui/material/ButtonBase'
|
||||
import Grid from '@mui/material/Grid'
|
||||
@@ -12,7 +11,7 @@ import { SwitchProps } from '@mui/material/Switch'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { openThat } from '@nyanpasu/interface'
|
||||
import { LoadingSwitch } from '@nyanpasu/ui'
|
||||
import { alpha, LoadingSwitch } from '@nyanpasu/ui'
|
||||
|
||||
export interface LabelSwitchProps extends SwitchProps {
|
||||
label: string
|
||||
@@ -42,8 +41,6 @@ export const LabelSwitch = ({
|
||||
onChange,
|
||||
...props
|
||||
}: LabelSwitchProps) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleChange = async (
|
||||
@@ -63,14 +60,14 @@ export const LabelSwitch = ({
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: 2,
|
||||
borderRadius: 6,
|
||||
backgroundColor: alpha(palette.primary.light, 0.1),
|
||||
}}
|
||||
backgroundColor: alpha(theme.vars.palette.primary.light, 0.1),
|
||||
})}
|
||||
elevation={0}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
@@ -114,8 +111,6 @@ export const ClashFieldItem = ({
|
||||
fields,
|
||||
...props
|
||||
}: ClashFieldItemProps) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
return (
|
||||
<Grid
|
||||
size={{
|
||||
@@ -125,10 +120,10 @@ export const ClashFieldItem = ({
|
||||
>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
borderRadius: 6,
|
||||
backgroundColor: alpha(palette.primary.light, 0.1),
|
||||
}}
|
||||
backgroundColor: alpha(theme.vars.palette.primary.light, 0.1),
|
||||
})}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
|
||||
@@ -7,9 +7,10 @@ import Box from '@mui/material/Box'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Paper, { PaperProps } from '@mui/material/Paper'
|
||||
import { alpha, styled } from '@mui/material/styles'
|
||||
import { styled } from '@mui/material/styles'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { openThat } from '@nyanpasu/interface'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
|
||||
/**
|
||||
* @example
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { parseHotkey } from '@/utils/parse-hotkey'
|
||||
import { Dangerous, DeleteRounded } from '@mui/icons-material'
|
||||
import { alpha, CircularProgress, IconButton, useTheme } from '@mui/material'
|
||||
import { CircularProgress, IconButton, useTheme } from '@mui/material'
|
||||
import type {} from '@mui/material/themeCssVarsAugmentation'
|
||||
import { CSSProperties, useEffect, useRef, useState } from 'react'
|
||||
import { cn, Kbd } from '@nyanpasu/ui'
|
||||
import { alpha, cn, Kbd } from '@nyanpasu/ui'
|
||||
import styles from './hotkey-input.module.scss'
|
||||
|
||||
export interface Props extends React.HTMLAttributes<HTMLInputElement> {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { memo, ReactNode } from 'react'
|
||||
import { mergeSxProps } from '@/utils/mui-theme'
|
||||
import {
|
||||
alpha,
|
||||
ButtonBase,
|
||||
ButtonBaseProps,
|
||||
Paper,
|
||||
SxProps,
|
||||
Theme,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
|
||||
export interface PaperButtonProps extends ButtonBaseProps {
|
||||
label?: string
|
||||
children?: ReactNode
|
||||
sxPaper?: SxProps
|
||||
sxButton?: SxProps
|
||||
sxPaper?: SxProps<Theme>
|
||||
sxButton?: SxProps<Theme>
|
||||
}
|
||||
|
||||
export const PaperButton = memo(function PaperButton({
|
||||
@@ -23,33 +24,35 @@ export const PaperButton = memo(function PaperButton({
|
||||
sxButton,
|
||||
...props
|
||||
}: PaperButtonProps) {
|
||||
const { palette } = useTheme()
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 6,
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
...sxPaper,
|
||||
}}
|
||||
sx={mergeSxProps(
|
||||
(theme) => ({
|
||||
borderRadius: 6,
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
}),
|
||||
sxPaper,
|
||||
)}
|
||||
>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
borderRadius: 6,
|
||||
width: '100%',
|
||||
textAlign: 'start',
|
||||
padding: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
sx={mergeSxProps(
|
||||
{
|
||||
borderRadius: 6,
|
||||
width: '100%',
|
||||
textAlign: 'start',
|
||||
padding: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
|
||||
'&.Mui-disabled': {
|
||||
pointerEvents: 'auto',
|
||||
cursor: 'not-allowed',
|
||||
'&.Mui-disabled': {
|
||||
pointerEvents: 'auto',
|
||||
cursor: 'not-allowed',
|
||||
},
|
||||
},
|
||||
...sxButton,
|
||||
}}
|
||||
sxButton,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{label && (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useControllableValue } from 'ahooks'
|
||||
import { merge } from 'lodash-es'
|
||||
import { memo, ReactNode } from 'react'
|
||||
import { alpha, CircularProgress, SxProps, useTheme } from '@mui/material'
|
||||
import { mergeSxProps } from '@/utils/mui-theme'
|
||||
import { CircularProgress, SxProps, Theme } from '@mui/material'
|
||||
import { alpha } from '@nyanpasu/ui'
|
||||
import { PaperButton, PaperButtonProps } from './nyanpasu-path'
|
||||
|
||||
export interface PaperSwitchButtonProps extends PaperButtonProps {
|
||||
@@ -11,7 +12,7 @@ export interface PaperSwitchButtonProps extends PaperButtonProps {
|
||||
disableLoading?: boolean
|
||||
children?: ReactNode
|
||||
onClick?: () => Promise<void> | void
|
||||
sxPaper?: SxProps
|
||||
sxPaper?: SxProps<Theme>
|
||||
}
|
||||
|
||||
export const PaperSwitchButton = memo(function PaperSwitchButton({
|
||||
@@ -24,8 +25,6 @@ export const PaperSwitchButton = memo(function PaperSwitchButton({
|
||||
sxPaper,
|
||||
...props
|
||||
}: PaperSwitchButtonProps) {
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [pending, setPending] = useControllableValue<boolean>(
|
||||
{ loading },
|
||||
{
|
||||
@@ -48,15 +47,17 @@ export const PaperSwitchButton = memo(function PaperSwitchButton({
|
||||
return (
|
||||
<PaperButton
|
||||
label={label}
|
||||
sxPaper={merge(
|
||||
{
|
||||
sxPaper={mergeSxProps(
|
||||
(theme) => ({
|
||||
backgroundColor: checked
|
||||
? alpha(palette.primary.main, 0.1)
|
||||
: palette.mode === 'dark'
|
||||
? palette.common.black
|
||||
: palette.grey[100],
|
||||
cursor: pending ? 'progress' : 'none',
|
||||
},
|
||||
? alpha(theme.vars.palette.primary.main, 0.1)
|
||||
: theme.vars.palette.grey[100],
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: checked
|
||||
? alpha(theme.vars.palette.primary.main, 0.1)
|
||||
: theme.vars.palette.common.black,
|
||||
}),
|
||||
}),
|
||||
sxPaper,
|
||||
)}
|
||||
sxButton={{
|
||||
|
||||
+5
-16
@@ -7,18 +7,9 @@ import { checkUpdate, useUpdaterPlatformSupported } from '@/hooks/use-updater'
|
||||
import { UpdaterInstanceAtom } from '@/store/updater'
|
||||
import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import {
|
||||
alpha,
|
||||
Box,
|
||||
Button,
|
||||
List,
|
||||
ListItem,
|
||||
Paper,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { Box, Button, List, ListItem, Paper, Typography } from '@mui/material'
|
||||
import { useSetting } from '@nyanpasu/interface'
|
||||
import { BaseCard } from '@nyanpasu/ui'
|
||||
import { alpha, BaseCard } from '@nyanpasu/ui'
|
||||
import { version } from '@root/package.json'
|
||||
import { LabelSwitch } from './modules/clash-field'
|
||||
|
||||
@@ -39,8 +30,6 @@ const AutoCheckUpdate = () => {
|
||||
export const SettingNyanpasuVersion = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const setUpdaterInstance = useSetAtom(UpdaterInstanceAtom)
|
||||
@@ -78,13 +67,13 @@ export const SettingNyanpasuVersion = () => {
|
||||
<ListItem sx={{ pl: 0, pr: 0 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
mt: 1,
|
||||
padding: 2,
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
borderRadius: 6,
|
||||
width: '100%',
|
||||
}}
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
display="flex"
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { useTheme } from '@mui/material'
|
||||
import { SxProps, Theme } from '@mui/material'
|
||||
|
||||
export const useColorForDelay = (delay: number): string => {
|
||||
const { palette } = useTheme()
|
||||
const delayColorMapping: { [key: string]: SxProps<Theme> } = {
|
||||
'-1': (theme) => ({ color: theme.vars.palette.text.primary }),
|
||||
'0': (theme) => ({ color: theme.vars.palette.text.secondary }),
|
||||
'1': (theme) => ({ color: theme.vars.palette.text.secondary }),
|
||||
'500': (theme) => ({ color: theme.vars.palette.success.main }),
|
||||
'2000': (theme) => ({ color: theme.vars.palette.warning.main }),
|
||||
'10000': (theme) => ({ color: theme.vars.palette.error.main }),
|
||||
}
|
||||
|
||||
const delayColorMapping: { [key: string]: string } = {
|
||||
'-1': palette.text.primary,
|
||||
'0': palette.text.secondary,
|
||||
'1': palette.text.secondary,
|
||||
'500': palette.success.main,
|
||||
'2000': palette.warning.main,
|
||||
'10000': palette.error.main,
|
||||
}
|
||||
|
||||
let color: string = palette.text.secondary
|
||||
export const useColorSxForDelay = (delay: number): SxProps<Theme> => {
|
||||
let sx: SxProps<Theme> = (theme: Theme) => ({
|
||||
color: theme.vars.palette.text.secondary,
|
||||
})
|
||||
|
||||
for (const key in delayColorMapping) {
|
||||
if (delay <= parseInt(key)) {
|
||||
color = delayColorMapping[key]
|
||||
sx = delayColorMapping[key]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return color
|
||||
return sx
|
||||
}
|
||||
|
||||
@@ -15,19 +15,13 @@ import SortSelector from '@/components/proxies/sort-selector'
|
||||
import { proxyGroupAtom } from '@/store'
|
||||
import { proxiesFilterAtom } from '@/store/proxies'
|
||||
import { Check } from '@mui/icons-material'
|
||||
import {
|
||||
alpha,
|
||||
TextField,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
useTheme,
|
||||
} from '@mui/material'
|
||||
import { TextField, ToggleButton, ToggleButtonGroup } from '@mui/material'
|
||||
import {
|
||||
ProxyGroupItem,
|
||||
useClashProxies,
|
||||
useProxyMode,
|
||||
} from '@nyanpasu/interface'
|
||||
import { cn, SidePage } from '@nyanpasu/ui'
|
||||
import { alpha, cn, SidePage } from '@nyanpasu/ui'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/proxies')({
|
||||
@@ -35,7 +29,6 @@ export const Route = createFileRoute('/proxies')({
|
||||
})
|
||||
|
||||
function SideBar() {
|
||||
const { palette } = useTheme()
|
||||
const [proxiesFilter, setProxiesFilter] = useAtom(proxiesFilterAtom)
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -52,14 +45,16 @@ function SideBar() {
|
||||
onChange={(e) =>
|
||||
setProxiesFilter(!e.target.value.trim().length ? null : e.target.value)
|
||||
}
|
||||
InputProps={{
|
||||
sx: {
|
||||
borderRadius: 7,
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
slotProps={{
|
||||
input: {
|
||||
sx: (theme) => ({
|
||||
borderRadius: 7,
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
|
||||
fieldset: {
|
||||
border: 'none',
|
||||
},
|
||||
fieldset: {
|
||||
border: 'none',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -3,9 +3,9 @@ import { useSetAtom } from 'jotai'
|
||||
import { lazy, RefObject, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { atomRulePage } from '@/components/rules/modules/store'
|
||||
import { alpha, FilledInputProps, TextField, useTheme } from '@mui/material'
|
||||
import { FilledInputProps, TextField } from '@mui/material'
|
||||
import { useClashRules } from '@nyanpasu/interface'
|
||||
import { BasePage } from '@nyanpasu/ui'
|
||||
import { alpha, BasePage } from '@nyanpasu/ui'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/rules')({
|
||||
@@ -15,8 +15,6 @@ export const Route = createFileRoute('/rules')({
|
||||
function RulesPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
|
||||
const { data } = useClashRules()
|
||||
|
||||
const [filterText, setFilterText] = useState('')
|
||||
@@ -37,14 +35,14 @@ function RulesPage() {
|
||||
)
|
||||
|
||||
const inputProps: Partial<FilledInputProps> = {
|
||||
sx: {
|
||||
sx: (theme) => ({
|
||||
borderRadius: 7,
|
||||
backgroundColor: alpha(palette.primary.main, 0.1),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.1),
|
||||
|
||||
fieldset: {
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
const Component = lazy(() => import('@/components/rules/rule-page'))
|
||||
@@ -63,7 +61,9 @@ function RulesPage() {
|
||||
onChange={(e) => setFilterText(e.target.value)}
|
||||
className="!pb-0"
|
||||
sx={{ input: { py: 1, fontSize: 14 } }}
|
||||
InputProps={inputProps}
|
||||
slotProps={{
|
||||
input: inputProps,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
viewportRef={viewportRef}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { SxProps, Theme } from '@mui/material'
|
||||
|
||||
export const mergeSxProps = (...props: Array<SxProps<Theme> | undefined>) => {
|
||||
return props.reduce((acc, curr) => {
|
||||
if (!curr) {
|
||||
return acc
|
||||
}
|
||||
|
||||
if (Array.isArray(curr)) {
|
||||
return [...acc, ...curr]
|
||||
}
|
||||
|
||||
return [...acc, curr]
|
||||
}, [] as SxProps<Theme>[])
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as d3 from 'd3'
|
||||
import { interpolatePath } from 'd3-interpolate-path'
|
||||
import { CSSProperties, FC, useEffect, useRef } from 'react'
|
||||
import { alpha, useTheme } from '@mui/material'
|
||||
import { CSSProperties, FC, useEffect, useMemo, useRef } from 'react'
|
||||
import { alpha, useColorScheme, useTheme } from '@mui/material'
|
||||
|
||||
interface SparklineProps {
|
||||
data: number[]
|
||||
@@ -10,7 +10,24 @@ interface SparklineProps {
|
||||
}
|
||||
|
||||
export const Sparkline: FC<SparklineProps> = ({ data, className, style }) => {
|
||||
const { palette } = useTheme()
|
||||
const theme = useTheme()
|
||||
const { mode } = useColorScheme()
|
||||
|
||||
const lineColor = useMemo(
|
||||
() =>
|
||||
mode === 'dark'
|
||||
? alpha(theme.colorSchemes.light!.palette.primary.main, 0.7)
|
||||
: alpha(theme.colorSchemes.dark!.palette.primary.main, 0.7),
|
||||
[mode, theme],
|
||||
)
|
||||
|
||||
const areaColor = useMemo(
|
||||
() =>
|
||||
mode === 'dark'
|
||||
? alpha(theme.colorSchemes.light!.palette.primary.main, 0.1)
|
||||
: alpha(theme.colorSchemes.dark!.palette.primary.main, 0.1),
|
||||
[mode, theme],
|
||||
)
|
||||
|
||||
const svgRef = useRef<SVGSVGElement | null>(null)
|
||||
|
||||
@@ -67,7 +84,7 @@ export const Sparkline: FC<SparklineProps> = ({ data, className, style }) => {
|
||||
.append('path')
|
||||
.datum(data)
|
||||
.attr('class', 'area')
|
||||
.attr('fill', alpha(palette.primary.main, 0.1))
|
||||
.attr('fill', areaColor)
|
||||
.attr('d', area)
|
||||
|
||||
svg
|
||||
@@ -75,7 +92,7 @@ export const Sparkline: FC<SparklineProps> = ({ data, className, style }) => {
|
||||
.datum(data)
|
||||
.attr('class', 'line')
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', alpha(palette.primary.main, 0.7))
|
||||
.attr('stroke', lineColor)
|
||||
.attr('stroke-width', 2)
|
||||
.attr('d', line)
|
||||
|
||||
@@ -106,7 +123,7 @@ export const Sparkline: FC<SparklineProps> = ({ data, className, style }) => {
|
||||
}
|
||||
|
||||
updateChart()
|
||||
}, [data, palette.primary.main])
|
||||
}, [data, lineColor, areaColor])
|
||||
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getSystem, useClickPosition } from '@/hooks'
|
||||
import { cn } from '@/utils'
|
||||
import { Button, Divider } from '@mui/material'
|
||||
import { alpha, useTheme } from '@mui/material/styles'
|
||||
import { alpha, cn } from '@/utils'
|
||||
import { Box, Button, Divider } from '@mui/material'
|
||||
import { useColorScheme } from '@mui/material/styles'
|
||||
import * as Portal from '@radix-ui/react-portal'
|
||||
|
||||
const OS = getSystem()
|
||||
@@ -48,7 +48,7 @@ export const BaseDialog = ({
|
||||
}: BaseDialogProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { palette } = useTheme()
|
||||
const { mode } = useColorScheme()
|
||||
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
@@ -76,6 +76,7 @@ export const BaseDialog = ({
|
||||
y: getClickPosition()?.y ?? 0,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open])
|
||||
|
||||
const handleClose = useLockFn(async () => {
|
||||
@@ -125,17 +126,21 @@ export const BaseDialog = ({
|
||||
{mounted && (
|
||||
<Portal.Root className="fixed top-0 left-0 z-50 h-dvh w-full">
|
||||
{!full && (
|
||||
<motion.div
|
||||
<Box
|
||||
component={motion.div}
|
||||
className={cn(
|
||||
'fixed inset-0 z-50',
|
||||
OS === 'linux' ? 'bg-black/50' : 'backdrop-blur-xl',
|
||||
)}
|
||||
style={{
|
||||
backgroundColor:
|
||||
OS === 'linux'
|
||||
? undefined
|
||||
: alpha(palette.primary[palette.mode], 0.1),
|
||||
}}
|
||||
sx={[
|
||||
OS === 'linux' ||
|
||||
((theme) => ({
|
||||
backgroundColor: alpha(
|
||||
theme.vars.palette.primary.main,
|
||||
0.1,
|
||||
),
|
||||
})),
|
||||
]}
|
||||
animate={open ? 'open' : 'closed'}
|
||||
initial={{ opacity: 0 }}
|
||||
variants={{
|
||||
@@ -146,17 +151,16 @@ export const BaseDialog = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
<Box
|
||||
component={motion.div}
|
||||
className={cn(
|
||||
'fixed top-[50%] left-[50%] z-50',
|
||||
full ? 'h-dvh w-full' : 'min-w-96 rounded-3xl shadow',
|
||||
palette.mode === 'dark'
|
||||
? 'text-white shadow-zinc-900'
|
||||
: 'text-black',
|
||||
mode === 'dark' ? 'text-white shadow-zinc-900' : 'text-black',
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: palette.background.default,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: theme.vars.palette.background.default,
|
||||
})}
|
||||
animate={open ? 'open' : 'closed'}
|
||||
initial={{
|
||||
opacity: 0.3,
|
||||
@@ -238,7 +242,7 @@ export const BaseDialog = ({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</Portal.Root>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { cn } from '@/utils'
|
||||
import { alpha, Button, ButtonProps, useTheme } from '@mui/material'
|
||||
import { alpha, cn } from '@/utils'
|
||||
import { Button, ButtonProps } from '@mui/material'
|
||||
|
||||
export interface FloatingButtonProps extends ButtonProps {
|
||||
children: ReactNode
|
||||
@@ -12,23 +12,21 @@ export const FloatingButton = ({
|
||||
className,
|
||||
...props
|
||||
}: FloatingButtonProps) => {
|
||||
const { palette } = useTheme()
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
`right-8 bottom-8 z-10 size-16 !rounded-2xl backdrop-blur`,
|
||||
className,
|
||||
)}
|
||||
sx={{
|
||||
sx={(theme) => ({
|
||||
position: 'fixed',
|
||||
boxShadow: 8,
|
||||
backgroundColor: alpha(palette.primary.main, 0.3),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.3),
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(palette.primary.main, 0.45),
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.45),
|
||||
},
|
||||
}}
|
||||
})}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { mode } from 'd3'
|
||||
import { cn } from '@/utils'
|
||||
import { useColorScheme, useTheme } from '@mui/material'
|
||||
import { useColorScheme } from '@mui/material'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
export type Props = React.DetailedHTMLProps<
|
||||
|
||||
@@ -1,59 +1,63 @@
|
||||
import { alpha, darken, Theme } from '@mui/material'
|
||||
import { alpha } from '@/utils/color-mix'
|
||||
import { darken, Theme } from '@mui/material'
|
||||
import { Components } from '@mui/material/styles'
|
||||
|
||||
export const MuiButtonGroup: Components<Theme>['MuiButtonGroup'] = {
|
||||
styleOverrides: {
|
||||
grouped: ({ theme }) => ({
|
||||
fontWeight: 700,
|
||||
height: '2.5em',
|
||||
padding: '0 1.25em',
|
||||
border: `1px solid ${darken(theme.vars.palette.primary.main, 0.09)}`,
|
||||
color: darken(theme.vars.palette.primary.main, 0.2),
|
||||
grouped: ({ theme }) =>
|
||||
theme.unstable_sx({
|
||||
fontWeight: 700,
|
||||
height: '2.5em',
|
||||
padding: '0 1.25em',
|
||||
border: `1px solid ${darken(theme.vars.palette.primary.main, 0.09)}`,
|
||||
color: darken(theme.vars.palette.primary.main, 0.2),
|
||||
|
||||
'&.MuiButton-contained.MuiButton-colorPrimary': {
|
||||
boxShadow: 'none',
|
||||
border: `1px solid ${theme.vars.palette.primary.mainChannel}`,
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.2),
|
||||
color: theme.vars.palette.primary.main,
|
||||
'&::before': {
|
||||
content: 'none',
|
||||
'&.MuiButton-contained.MuiButton-colorPrimary': {
|
||||
boxShadow: 'none',
|
||||
border: `1px solid ${theme.vars.palette.primary.mainChannel}`,
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.2),
|
||||
color: theme.vars.palette.primary.main,
|
||||
'&::before': {
|
||||
content: 'none',
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.3),
|
||||
},
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.vars.palette.primary.main, 0.3),
|
||||
}),
|
||||
firstButton: ({ theme }) =>
|
||||
theme.unstable_sx({
|
||||
borderTopLeftRadius: 48,
|
||||
borderBottomLeftRadius: 48,
|
||||
|
||||
'&.MuiButton-sizeSmall': {
|
||||
paddingLeft: '1.5em',
|
||||
},
|
||||
},
|
||||
}),
|
||||
firstButton: {
|
||||
borderTopLeftRadius: 48,
|
||||
borderBottomLeftRadius: 48,
|
||||
|
||||
'&.MuiButton-sizeSmall': {
|
||||
paddingLeft: '1.5em',
|
||||
},
|
||||
'&.MuiButton-sizeMedium': {
|
||||
paddingLeft: '20px',
|
||||
},
|
||||
|
||||
'&.MuiButton-sizeMedium': {
|
||||
paddingLeft: '20px',
|
||||
},
|
||||
'&.MuiButton-sizeLarge': {
|
||||
paddingLeft: '26px',
|
||||
},
|
||||
}),
|
||||
lastButton: ({ theme }) =>
|
||||
theme.unstable_sx({
|
||||
borderTopRightRadius: 48,
|
||||
borderBottomRightRadius: 48,
|
||||
|
||||
'&.MuiButton-sizeLarge': {
|
||||
paddingLeft: '26px',
|
||||
},
|
||||
},
|
||||
lastButton: {
|
||||
borderTopRightRadius: 48,
|
||||
borderBottomRightRadius: 48,
|
||||
'&.MuiButton-sizeSmall': {
|
||||
paddingRight: '1.5em',
|
||||
},
|
||||
|
||||
'&.MuiButton-sizeSmall': {
|
||||
paddingRight: '1.5em',
|
||||
},
|
||||
'&.MuiButton-sizeMedium': {
|
||||
paddingRight: '20px',
|
||||
},
|
||||
|
||||
'&.MuiButton-sizeMedium': {
|
||||
paddingRight: '20px',
|
||||
},
|
||||
|
||||
'&.MuiButton-sizeLarge': {
|
||||
paddingRight: '26px',
|
||||
},
|
||||
},
|
||||
'&.MuiButton-sizeLarge': {
|
||||
paddingRight: '26px',
|
||||
},
|
||||
}),
|
||||
},
|
||||
} satisfies Components<Theme>['MuiButtonGroup']
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export const alpha = (color: string, alpha: number) => {
|
||||
return `color-mix(in srgb, ${color} ${(alpha * 100).toFixed(2)}%, transparent ${((1 - alpha) * 100).toFixed(2)}%)`
|
||||
}
|
||||
|
||||
export const lighten = (color: string, alpha: number) => {
|
||||
return `color-mix(in lch, ${color} ${((1 - alpha) * 100).toFixed(2)}%, white ${(alpha * 100).toFixed(2)}%)`
|
||||
}
|
||||
|
||||
export const darken = (color: string, alpha: number) => {
|
||||
return `color-mix(in lch, ${color} ${((1 - alpha) * 100).toFixed(2)}%, black ${(alpha * 100).toFixed(2)}%)`
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { cn } from './cn'
|
||||
export * from './event'
|
||||
export * from './ts-helper'
|
||||
export * from './color-mix'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.19.8",
|
||||
"mihomo_alpha": "alpha-d036d98",
|
||||
"mihomo_alpha": "alpha-9f7a2a3",
|
||||
"clash_rs": "v0.7.8",
|
||||
"clash_premium": "2023-09-05-gdcc8d87",
|
||||
"clash_rs_alpha": "0.7.7-alpha+sha.72871ca"
|
||||
@@ -69,5 +69,5 @@
|
||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2025-05-18T22:20:58.018Z"
|
||||
"updated_at": "2025-05-19T22:21:05.925Z"
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"globals": "16.1.0",
|
||||
"knip": "5.55.1",
|
||||
"lint-staged": "15.5.2",
|
||||
"lint-staged": "16.0.0",
|
||||
"neostandard": "0.12.1",
|
||||
"npm-run-all2": "8.0.1",
|
||||
"postcss": "8.5.3",
|
||||
@@ -117,6 +117,7 @@
|
||||
"vite-plugin-monaco-editor": "npm:vite-plugin-monaco-editor-new@1.1.3"
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"@tailwindcss/oxide",
|
||||
"core-js",
|
||||
"esbuild"
|
||||
]
|
||||
|
||||
Generated
+58
-113
@@ -103,8 +103,8 @@ importers:
|
||||
specifier: 5.55.1
|
||||
version: 5.55.1(@types/node@22.15.18)(typescript@5.8.3)
|
||||
lint-staged:
|
||||
specifier: 15.5.2
|
||||
version: 15.5.2
|
||||
specifier: 16.0.0
|
||||
version: 16.0.0
|
||||
neostandard:
|
||||
specifier: 0.12.1
|
||||
version: 0.12.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2)))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
@@ -349,7 +349,7 @@ importers:
|
||||
version: 1.120.3(@tanstack/react-router@1.120.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.120.3)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tiny-invariant@1.3.3)
|
||||
'@tanstack/router-plugin':
|
||||
specifier: 1.120.3
|
||||
version: 1.120.3(@tanstack/react-router@1.120.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 1.120.3(@tanstack/react-router@1.120.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@tauri-apps/plugin-clipboard-manager':
|
||||
specifier: 2.2.2
|
||||
version: 2.2.2
|
||||
@@ -385,13 +385,13 @@ importers:
|
||||
version: 13.15.0
|
||||
'@vitejs/plugin-legacy':
|
||||
specifier: 6.1.1
|
||||
version: 6.1.1(terser@5.36.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 6.1.1(terser@5.36.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@vitejs/plugin-react':
|
||||
specifier: 4.4.1
|
||||
version: 4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@vitejs/plugin-react-swc':
|
||||
specifier: 3.9.0
|
||||
version: 3.9.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 3.9.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
change-case:
|
||||
specifier: 5.4.4
|
||||
version: 5.4.4
|
||||
@@ -430,19 +430,19 @@ importers:
|
||||
version: 13.15.0
|
||||
vite:
|
||||
specifier: 6.3.5
|
||||
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
vite-plugin-html:
|
||||
specifier: 3.2.2
|
||||
version: 3.2.2(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 3.2.2(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
vite-plugin-sass-dts:
|
||||
specifier: 1.3.31
|
||||
version: 1.3.31(postcss@8.5.3)(prettier@3.5.3)(sass-embedded@1.88.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 1.3.31(postcss@8.5.3)(prettier@3.5.3)(sass-embedded@1.88.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
vite-plugin-svgr:
|
||||
specifier: 4.3.0
|
||||
version: 4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
vite-tsconfig-paths:
|
||||
specifier: 5.1.4
|
||||
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
zod:
|
||||
specifier: 3.24.4
|
||||
version: 3.24.4
|
||||
@@ -478,7 +478,7 @@ importers:
|
||||
version: 19.1.4
|
||||
'@vitejs/plugin-react':
|
||||
specifier: 4.4.1
|
||||
version: 4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
ahooks:
|
||||
specifier: 3.8.4
|
||||
version: 3.8.4(react@19.1.0)
|
||||
@@ -508,10 +508,10 @@ importers:
|
||||
version: 4.1.6
|
||||
vite:
|
||||
specifier: 6.3.5
|
||||
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
vite-tsconfig-paths:
|
||||
specifier: 5.1.4
|
||||
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
devDependencies:
|
||||
'@emotion/react':
|
||||
specifier: 11.14.0
|
||||
@@ -536,7 +536,7 @@ importers:
|
||||
version: 5.1.0(typescript@5.8.3)
|
||||
vite-plugin-dts:
|
||||
specifier: 4.5.4
|
||||
version: 4.5.4(@types/node@22.15.18)(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))
|
||||
version: 4.5.4(@types/node@22.15.18)(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))
|
||||
|
||||
scripts:
|
||||
dependencies:
|
||||
@@ -4733,10 +4733,6 @@ packages:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
execa@8.0.1:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
express-rate-limit@7.5.0:
|
||||
resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==}
|
||||
engines: {node: '>= 16'}
|
||||
@@ -4981,10 +4977,6 @@ packages:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
get-stream@8.0.1:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
get-symbol-description@1.0.2:
|
||||
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5206,10 +5198,6 @@ packages:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
|
||||
human-signals@5.0.0:
|
||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
||||
husky@9.1.7:
|
||||
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5538,10 +5526,6 @@ packages:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-stream@3.0.0:
|
||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
is-string@1.0.7:
|
||||
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5837,13 +5821,13 @@ packages:
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
lint-staged@15.5.2:
|
||||
resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
lint-staged@16.0.0:
|
||||
resolution: {integrity: sha512-sUCprePs6/rbx4vKC60Hez6X10HPkpDJaGcy3D1NdwR7g1RcNkWL8q9mJMreOqmHBTs+1sNFp+wOiX9fr+hoOQ==}
|
||||
engines: {node: '>=20.18'}
|
||||
hasBin: true
|
||||
|
||||
listr2@8.2.5:
|
||||
resolution: {integrity: sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==}
|
||||
listr2@8.3.3:
|
||||
resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
local-pkg@1.0.0:
|
||||
@@ -6130,10 +6114,6 @@ packages:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
mimic-function@5.0.1:
|
||||
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -6239,6 +6219,10 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
|
||||
nano-spawn@1.0.1:
|
||||
resolution: {integrity: sha512-BfcvzBlUTxSDWfT+oH7vd6CbUV+rThLLHCIym/QO6GGLBsyVXleZs00fto2i2jzC/wPiBYk5jyOmpXWg4YopiA==}
|
||||
engines: {node: '>=20.18'}
|
||||
|
||||
nanoid@3.3.8:
|
||||
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -6332,10 +6316,6 @@ packages:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
@@ -6401,10 +6381,6 @@ packages:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
onetime@6.0.0:
|
||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
onetime@7.0.0:
|
||||
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -6520,10 +6496,6 @@ packages:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-key@4.0.0:
|
||||
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
@@ -7562,10 +7534,6 @@ packages:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
strip-final-newline@3.0.0:
|
||||
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-json-comments@2.0.1:
|
||||
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -8349,6 +8317,11 @@ packages:
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
|
||||
yaml@2.8.0:
|
||||
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -10802,7 +10775,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@tanstack/react-router': 1.120.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
|
||||
'@tanstack/router-plugin@1.120.3(@tanstack/react-router@1.120.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))':
|
||||
'@tanstack/router-plugin@1.120.3(@tanstack/react-router@1.120.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10
|
||||
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10)
|
||||
@@ -10823,7 +10796,7 @@ snapshots:
|
||||
zod: 3.24.4
|
||||
optionalDependencies:
|
||||
'@tanstack/react-router': 1.120.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -11334,7 +11307,7 @@ snapshots:
|
||||
|
||||
'@ungap/structured-clone@1.2.0': {}
|
||||
|
||||
'@vitejs/plugin-legacy@6.1.1(terser@5.36.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))':
|
||||
'@vitejs/plugin-legacy@6.1.1(terser@5.36.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10
|
||||
'@babel/preset-env': 7.26.9(@babel/core@7.26.10)
|
||||
@@ -11345,25 +11318,25 @@ snapshots:
|
||||
regenerator-runtime: 0.14.1
|
||||
systemjs: 6.15.1
|
||||
terser: 5.36.0
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-react-swc@3.9.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))':
|
||||
'@vitejs/plugin-react-swc@3.9.0(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@swc/core': 1.11.21
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
|
||||
'@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0))':
|
||||
'@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10
|
||||
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10)
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -13055,18 +13028,6 @@ snapshots:
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
execa@8.0.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
get-stream: 8.0.1
|
||||
human-signals: 5.0.0
|
||||
is-stream: 3.0.0
|
||||
merge-stream: 2.0.0
|
||||
npm-run-path: 5.3.0
|
||||
onetime: 6.0.0
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 3.0.0
|
||||
|
||||
express-rate-limit@7.5.0(express@5.1.0):
|
||||
dependencies:
|
||||
express: 5.1.0
|
||||
@@ -13362,8 +13323,6 @@ snapshots:
|
||||
|
||||
get-stream@6.0.1: {}
|
||||
|
||||
get-stream@8.0.1: {}
|
||||
|
||||
get-symbol-description@1.0.2:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@@ -13657,8 +13616,6 @@ snapshots:
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
|
||||
husky@9.1.7: {}
|
||||
|
||||
hyphenate-style-name@1.1.0: {}
|
||||
@@ -13938,8 +13895,6 @@ snapshots:
|
||||
|
||||
is-stream@2.0.1: {}
|
||||
|
||||
is-stream@3.0.0: {}
|
||||
|
||||
is-string@1.0.7:
|
||||
dependencies:
|
||||
has-tostringtag: 1.0.2
|
||||
@@ -14205,22 +14160,22 @@ snapshots:
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
lint-staged@15.5.2:
|
||||
lint-staged@16.0.0:
|
||||
dependencies:
|
||||
chalk: 5.4.1
|
||||
commander: 13.1.0
|
||||
debug: 4.4.0
|
||||
execa: 8.0.1
|
||||
lilconfig: 3.1.3
|
||||
listr2: 8.2.5
|
||||
listr2: 8.3.3
|
||||
micromatch: 4.0.8
|
||||
nano-spawn: 1.0.1
|
||||
pidtree: 0.6.0
|
||||
string-argv: 0.3.2
|
||||
yaml: 2.7.0
|
||||
yaml: 2.8.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
listr2@8.2.5:
|
||||
listr2@8.3.3:
|
||||
dependencies:
|
||||
cli-truncate: 4.0.0
|
||||
colorette: 2.0.20
|
||||
@@ -14614,8 +14569,6 @@ snapshots:
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
|
||||
mimic-function@5.0.1: {}
|
||||
|
||||
mimic-response@1.0.1: {}
|
||||
@@ -14729,6 +14682,8 @@ snapshots:
|
||||
stacktrace-js: 2.0.2
|
||||
stylis: 4.3.2
|
||||
|
||||
nano-spawn@1.0.1: {}
|
||||
|
||||
nanoid@3.3.8: {}
|
||||
|
||||
nanoid@5.1.5: {}
|
||||
@@ -14822,10 +14777,6 @@ snapshots:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
dependencies:
|
||||
path-key: 4.0.0
|
||||
|
||||
nth-check@2.1.1:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
@@ -14918,10 +14869,6 @@ snapshots:
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
|
||||
onetime@6.0.0:
|
||||
dependencies:
|
||||
mimic-fn: 4.0.0
|
||||
|
||||
onetime@7.0.0:
|
||||
dependencies:
|
||||
mimic-function: 5.0.1
|
||||
@@ -15044,8 +14991,6 @@ snapshots:
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-key@4.0.0: {}
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
@@ -16082,8 +16027,6 @@ snapshots:
|
||||
|
||||
strip-final-newline@2.0.0: {}
|
||||
|
||||
strip-final-newline@3.0.0: {}
|
||||
|
||||
strip-json-comments@2.0.1: {}
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
@@ -16729,7 +16672,7 @@ snapshots:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
vite-plugin-dts@4.5.4(@types/node@22.15.18)(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)):
|
||||
vite-plugin-dts@4.5.4(@types/node@22.15.18)(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@microsoft/api-extractor': 7.51.0(@types/node@22.15.18)
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.40.0)
|
||||
@@ -16742,13 +16685,13 @@ snapshots:
|
||||
magic-string: 0.30.17
|
||||
typescript: 5.8.3
|
||||
optionalDependencies:
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
vite-plugin-html@3.2.2(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)):
|
||||
vite-plugin-html@3.2.2(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
colorette: 2.0.20
|
||||
@@ -16762,39 +16705,39 @@ snapshots:
|
||||
html-minifier-terser: 6.1.0
|
||||
node-html-parser: 5.4.2
|
||||
pathe: 0.2.0
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
|
||||
vite-plugin-sass-dts@1.3.31(postcss@8.5.3)(prettier@3.5.3)(sass-embedded@1.88.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)):
|
||||
vite-plugin-sass-dts@1.3.31(postcss@8.5.3)(prettier@3.5.3)(sass-embedded@1.88.0)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
postcss: 8.5.3
|
||||
postcss-js: 4.0.1(postcss@8.5.3)
|
||||
prettier: 3.5.3
|
||||
sass-embedded: 1.88.0
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
|
||||
vite-plugin-svgr@4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)):
|
||||
vite-plugin-svgr@4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.3(rollup@4.40.0)
|
||||
'@svgr/core': 8.1.0(typescript@5.8.3)
|
||||
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)):
|
||||
vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
debug: 4.3.7
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.0.3(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0)
|
||||
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.7.0):
|
||||
vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.2)(sass-embedded@1.88.0)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.4)(yaml@2.8.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.0
|
||||
fdir: 6.4.4(picomatch@4.0.2)
|
||||
@@ -16813,7 +16756,7 @@ snapshots:
|
||||
stylus: 0.62.0
|
||||
terser: 5.36.0
|
||||
tsx: 4.19.4
|
||||
yaml: 2.7.0
|
||||
yaml: 2.8.0
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
@@ -16981,6 +16924,8 @@ snapshots:
|
||||
|
||||
yaml@2.7.0: {}
|
||||
|
||||
yaml@2.8.0: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@17.7.2:
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
#pnpm pretty-quick --staged
|
||||
|
||||
# 运行 clippy fmt
|
||||
cargo fmt --manifest-path ./src-tauri/Cargo.toml
|
||||
cd src-tauri
|
||||
cargo fmt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "rustfmt failed to format the code. Please fix the issues and try again."
|
||||
exit 1
|
||||
fi
|
||||
cd ..
|
||||
|
||||
#git add .
|
||||
git add .
|
||||
|
||||
# 允许提交
|
||||
exit 0
|
||||
|
||||
@@ -96,6 +96,24 @@ pnpm portable
|
||||
|
||||
## Contributing Your Changes
|
||||
|
||||
#### Before commit your changes
|
||||
|
||||
If you changed the rust code, it's recommanded to execute code style formatting and quailty checks.
|
||||
|
||||
1. Code style formatting
|
||||
|
||||
```bash
|
||||
$ clash-verge-rev: cd src-tauri
|
||||
$ clash-verge-rev/src-tauri: cargo fmt
|
||||
```
|
||||
|
||||
2. Code quailty checks
|
||||
|
||||
```bash
|
||||
$ clash-verge-rev: pnpm clippy
|
||||
```
|
||||
|
||||
|
||||
Once you have made your changes:
|
||||
|
||||
1. Fork the repository.
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
- 同步更新多语言翻译
|
||||
- 修复 .window-state.json 无法删除的问题
|
||||
- 无法修改配置更新 HTTP 请求超时
|
||||
- 修复 getDelayFix 钩子问题
|
||||
|
||||
#### 新增了:
|
||||
- Mihomo(Meta)内核升级至 1.19.8
|
||||
@@ -49,6 +50,8 @@
|
||||
- 添加了Zashboard的一键跳转URL
|
||||
- 使用操作系统默认的窗口管理器
|
||||
- 切换、升级、重启内核的状态管理
|
||||
- 增加了一键随机API端口和密钥/单独刷新按钮
|
||||
- 更精细化控制自动日志清理,新增1天选项。
|
||||
|
||||
#### 优化了:
|
||||
- 系统代理 Bypass 设置
|
||||
@@ -73,6 +76,9 @@
|
||||
- 优化窗口状态初始化逻辑和添加缺失的权限设置
|
||||
- 异步化配置:优化端口查找和配置保存逻辑
|
||||
- 重构事件通知机制到独立线程,避免前端卡死
|
||||
- 优化端口设置,每个端口可随机设置端口号
|
||||
- 优化了随机端口和密钥机制,防止随机时卡死!
|
||||
- 优化了保存机制,使用平滑函数,防止客户端卡死!
|
||||
|
||||
## v2.2.3
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"release-version": "node scripts/release_version.mjs",
|
||||
"release-alpha-version": "node scripts/release-alpha_version.mjs",
|
||||
"prepare": "husky",
|
||||
"fmt": "cargo fmt --manifest-path ./src-tauri/Cargo.toml",
|
||||
"clippy": "cargo clippy --manifest-path ./src-tauri/Cargo.toml"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -134,7 +134,7 @@ pub struct IVerge {
|
||||
pub test_list: Option<Vec<IVergeTestItem>>,
|
||||
|
||||
/// 日志清理
|
||||
/// 0: 不清理; 1: 7天; 2: 30天; 3: 90天
|
||||
/// 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天
|
||||
pub auto_log_clean: Option<i32>,
|
||||
|
||||
/// 是否启用随机端口
|
||||
|
||||
@@ -472,19 +472,16 @@ impl CoreManager {
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
tauri_plugin_shell::process::CommandEvent::Stdout(line) => {
|
||||
if let Err(e) = writeln!(log_file, "{}", String::from_utf8_lossy(&line)) {
|
||||
logging!(
|
||||
error,
|
||||
Type::Core,
|
||||
true,
|
||||
"[Sidecar] Failed to write stdout to file: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
if let tauri_plugin_shell::process::CommandEvent::Stdout(line) = event {
|
||||
if let Err(e) = writeln!(log_file, "{}", String::from_utf8_lossy(&line)) {
|
||||
logging!(
|
||||
error,
|
||||
Type::Core,
|
||||
true,
|
||||
"[Sidecar] Failed to write stdout to file: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -317,9 +317,13 @@ impl Handle {
|
||||
|
||||
let system_opt = handle.notification_system.read();
|
||||
if let Some(system) = system_opt.as_ref() {
|
||||
system.send_event(FrontendEvent::ProfileChanged { current_profile_id: profile_id });
|
||||
system.send_event(FrontendEvent::ProfileChanged {
|
||||
current_profile_id: profile_id,
|
||||
});
|
||||
} else {
|
||||
log::warn!("Notification system not initialized when trying to send ProfileChanged event.");
|
||||
log::warn!(
|
||||
"Notification system not initialized when trying to send ProfileChanged event."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +337,9 @@ impl Handle {
|
||||
if let Some(system) = system_opt.as_ref() {
|
||||
system.send_event(FrontendEvent::TimerUpdated { profile_index });
|
||||
} else {
|
||||
log::warn!("Notification system not initialized when trying to send TimerUpdated event.");
|
||||
log::warn!(
|
||||
"Notification system not initialized when trying to send TimerUpdated event."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +353,9 @@ impl Handle {
|
||||
if let Some(system) = system_opt.as_ref() {
|
||||
system.send_event(FrontendEvent::StartupCompleted);
|
||||
} else {
|
||||
log::warn!("Notification system not initialized when trying to send StartupCompleted event.");
|
||||
log::warn!(
|
||||
"Notification system not initialized when trying to send StartupCompleted event."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ impl MihomoManager {
|
||||
url: String,
|
||||
data: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let client_response = self.client
|
||||
let client_response = self
|
||||
.client
|
||||
.request(method.clone(), &url)
|
||||
.json(&data.unwrap_or(json!({})))
|
||||
.send()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct MihomoData {
|
||||
pub(crate) proxies: serde_json::Value,
|
||||
@@ -24,5 +24,4 @@ impl Drop for MihomoManager {
|
||||
fn drop(&mut self) {
|
||||
println!("Dropping MihomoManager");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,4 +26,4 @@ async fn test_refresh_providers_proxies() {
|
||||
let providers = manager.get_providers_proxies();
|
||||
assert_eq!(proxies, serde_json::Value::Null);
|
||||
assert_ne!(providers, serde_json::Value::Null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,13 +101,13 @@ export const CurrentProxyCard = () => {
|
||||
const mode = clashConfig?.mode?.toLowerCase() || "rule";
|
||||
const isGlobalMode = mode === "global";
|
||||
const isDirectMode = mode === "direct";
|
||||
|
||||
|
||||
// 添加排序类型状态
|
||||
const [sortType, setSortType] = useState<ProxySortType>(() => {
|
||||
const savedSortType = localStorage.getItem(STORAGE_KEY_SORT_TYPE);
|
||||
return savedSortType ? Number(savedSortType) as ProxySortType : 0;
|
||||
});
|
||||
|
||||
|
||||
// 定义状态类型
|
||||
type ProxyState = {
|
||||
proxyData: {
|
||||
@@ -128,7 +128,7 @@ export const CurrentProxyCard = () => {
|
||||
groups: [],
|
||||
records: {},
|
||||
globalProxy: "",
|
||||
directProxy: null,
|
||||
directProxy: { name: "DIRECT" }, // 默认值避免 undefined
|
||||
},
|
||||
selection: {
|
||||
group: "",
|
||||
@@ -160,9 +160,9 @@ export const CurrentProxyCard = () => {
|
||||
|
||||
return primaryGroup?.name || "";
|
||||
};
|
||||
|
||||
|
||||
const primaryGroupName = getPrimaryGroupName();
|
||||
|
||||
|
||||
// 根据模式确定初始组
|
||||
if (isGlobalMode) {
|
||||
setState((prev) => ({
|
||||
@@ -204,7 +204,7 @@ export const CurrentProxyCard = () => {
|
||||
now: g.now || "",
|
||||
all: g.all.map((p: { name: string }) => p.name),
|
||||
}));
|
||||
|
||||
|
||||
let newProxy = "";
|
||||
let newDisplayProxy = null;
|
||||
let newGroup = prev.selection.group;
|
||||
@@ -213,7 +213,7 @@ export const CurrentProxyCard = () => {
|
||||
if (isDirectMode) {
|
||||
newGroup = "DIRECT";
|
||||
newProxy = "DIRECT";
|
||||
newDisplayProxy = proxies.records?.DIRECT || null;
|
||||
newDisplayProxy = proxies.records?.DIRECT || { name: "DIRECT" }; // 确保非空
|
||||
} else if (isGlobalMode && proxies.global) {
|
||||
newGroup = "GLOBAL";
|
||||
newProxy = proxies.global.now || "";
|
||||
@@ -248,7 +248,7 @@ export const CurrentProxyCard = () => {
|
||||
groups: filteredGroups,
|
||||
records: proxies.records || {},
|
||||
globalProxy: proxies.global?.now || "",
|
||||
directProxy: proxies.records?.DIRECT || null,
|
||||
directProxy: proxies.records?.DIRECT || { name: "DIRECT" },
|
||||
},
|
||||
selection: {
|
||||
group: newGroup,
|
||||
@@ -364,12 +364,15 @@ export const CurrentProxyCard = () => {
|
||||
return state.displayProxy;
|
||||
}, [state.displayProxy]);
|
||||
|
||||
// 获取当前节点的延迟
|
||||
const currentDelay = currentProxy
|
||||
// 获取当前节点的延迟(增加非空校验)
|
||||
const currentDelay = currentProxy && state.selection.group
|
||||
? delayManager.getDelayFix(currentProxy, state.selection.group)
|
||||
: -1;
|
||||
|
||||
const signalInfo = getSignalIcon(currentDelay);
|
||||
// 信号图标(增加非空校验)
|
||||
const signalInfo = currentProxy && state.selection.group
|
||||
? getSignalIcon(currentDelay)
|
||||
: { icon: <SignalNone />, text: "未初始化", color: "text.secondary" };
|
||||
|
||||
// 自定义渲染选择框中的值
|
||||
const renderProxyValue = useCallback(
|
||||
@@ -378,7 +381,7 @@ export const CurrentProxyCard = () => {
|
||||
|
||||
const delayValue = delayManager.getDelayFix(
|
||||
state.proxyData.records[selected],
|
||||
state.selection.group,
|
||||
state.selection.group
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -402,23 +405,27 @@ export const CurrentProxyCard = () => {
|
||||
localStorage.setItem(STORAGE_KEY_SORT_TYPE, newSortType.toString());
|
||||
}, [sortType]);
|
||||
|
||||
// 排序代理函数
|
||||
// 排序代理函数(增加非空校验)
|
||||
const sortProxies = useCallback(
|
||||
(proxies: ProxyOption[]) => {
|
||||
if (!proxies || sortType === 0) return proxies;
|
||||
|
||||
|
||||
// 确保数据存在
|
||||
if (!state.proxyData.records || !state.selection.group) return proxies;
|
||||
|
||||
const list = [...proxies];
|
||||
|
||||
|
||||
if (sortType === 1) {
|
||||
list.sort((a, b) => {
|
||||
const ad = delayManager.getDelayFix(
|
||||
state.proxyData.records[a.name],
|
||||
state.selection.group
|
||||
);
|
||||
const bd = delayManager.getDelayFix(
|
||||
state.proxyData.records[b.name],
|
||||
state.selection.group
|
||||
);
|
||||
const recordA = state.proxyData.records[a.name];
|
||||
const recordB = state.proxyData.records[b.name];
|
||||
|
||||
// 处理 record 不存在的情况
|
||||
if (!recordA) return 1;
|
||||
if (!recordB) return -1;
|
||||
|
||||
const ad = delayManager.getDelayFix(recordA, state.selection.group);
|
||||
const bd = delayManager.getDelayFix(recordB, state.selection.group);
|
||||
|
||||
if (ad === -1 || ad === -2) return 1;
|
||||
if (bd === -1 || bd === -2) return -1;
|
||||
@@ -428,13 +435,13 @@ export const CurrentProxyCard = () => {
|
||||
} else {
|
||||
list.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
|
||||
return list;
|
||||
},
|
||||
[sortType, state?.proxyData?.records, state?.selection?.group]
|
||||
[sortType, state.proxyData.records, state.selection.group]
|
||||
);
|
||||
|
||||
// 计算要显示的代理选项
|
||||
// 计算要显示的代理选项(增加非空校验)
|
||||
const proxyOptions = useMemo(() => {
|
||||
if (isDirectMode) {
|
||||
return [{ name: "DIRECT" }];
|
||||
@@ -445,21 +452,23 @@ export const CurrentProxyCard = () => {
|
||||
const name = typeof p === 'string' ? p : p.name;
|
||||
return name !== "DIRECT" && name !== "REJECT";
|
||||
})
|
||||
.map((p: any) => ({
|
||||
name: typeof p === 'string' ? p : p.name
|
||||
.map((p: any) => ({
|
||||
name: typeof p === 'string' ? p : p.name
|
||||
}));
|
||||
|
||||
return sortProxies(options);
|
||||
}
|
||||
|
||||
// 规则模式
|
||||
const group = state.proxyData.groups.find(
|
||||
(g: { name: string }) => g.name === state.selection.group,
|
||||
);
|
||||
const group = state.selection.group
|
||||
? state.proxyData.groups.find(g => g.name === state.selection.group)
|
||||
: null;
|
||||
|
||||
if (group) {
|
||||
const options = group.all.map((name) => ({ name }));
|
||||
return sortProxies(options);
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [isDirectMode, isGlobalMode, proxies, state.proxyData, state.selection.group, sortProxies]);
|
||||
|
||||
@@ -509,8 +518,8 @@ export const CurrentProxyCard = () => {
|
||||
action={
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Tooltip title={getSortTooltip()}>
|
||||
<IconButton
|
||||
size="small"
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
onClick={handleSortTypeChange}
|
||||
sx={{ mr: 1 }}
|
||||
@@ -648,10 +657,12 @@ export const CurrentProxyCard = () => {
|
||||
{isDirectMode
|
||||
? null
|
||||
: proxyOptions.map((proxy, index) => {
|
||||
const delayValue = delayManager.getDelayFix(
|
||||
state.proxyData.records[proxy.name],
|
||||
state.selection.group,
|
||||
);
|
||||
const delayValue = state.proxyData.records[proxy.name] && state.selection.group
|
||||
? delayManager.getDelayFix(
|
||||
state.proxyData.records[proxy.name],
|
||||
state.selection.group,
|
||||
)
|
||||
: -1;
|
||||
return (
|
||||
<MenuItem
|
||||
key={`${proxy.name}-${index}`}
|
||||
@@ -692,4 +703,4 @@ export const CurrentProxyCard = () => {
|
||||
)}
|
||||
</EnhancedCard>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,286 +1,282 @@
|
||||
import { BaseDialog, Switch } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { Shuffle } from "@mui/icons-material";
|
||||
import { IconButton, List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
const OS = getSystem();
|
||||
|
||||
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
interface ClashPortViewerProps {}
|
||||
|
||||
const { clashInfo, patchInfo } = useClashInfo();
|
||||
const { verge, patchVerge } = useVerge();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [redirPort, setRedirPort] = useState(
|
||||
verge?.verge_redir_port ?? clashInfo?.redir_port ?? 7895
|
||||
);
|
||||
const [redirEnabled, setRedirEnabled] = useState(
|
||||
verge?.verge_redir_enabled ?? false
|
||||
);
|
||||
const [tproxyPort, setTproxyPort] = useState(
|
||||
verge?.verge_tproxy_port ?? clashInfo?.tproxy_port ?? 7896
|
||||
);
|
||||
const [tproxyEnabled, setTproxyEnabled] = useState(
|
||||
verge?.verge_tproxy_enabled ?? false
|
||||
);
|
||||
const [mixedPort, setMixedPort] = useState(
|
||||
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
|
||||
);
|
||||
const [socksPort, setSocksPort] = useState(
|
||||
verge?.verge_socks_port ?? clashInfo?.socks_port ?? 7898
|
||||
);
|
||||
const [socksEnabled, setSocksEnabled] = useState(
|
||||
verge?.verge_socks_enabled ?? false
|
||||
);
|
||||
const [port, setPort] = useState(
|
||||
verge?.verge_port ?? clashInfo?.port ?? 7899
|
||||
);
|
||||
const [httpEnabled, setHttpEnabled] = useState(
|
||||
verge?.verge_http_enabled ?? false
|
||||
);
|
||||
interface ClashPortViewerRef {
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
if (verge?.verge_redir_port) setRedirPort(verge?.verge_redir_port);
|
||||
setRedirEnabled(verge?.verge_redir_enabled ?? false);
|
||||
if (verge?.verge_tproxy_port) setTproxyPort(verge?.verge_tproxy_port);
|
||||
setTproxyEnabled(verge?.verge_tproxy_enabled ?? false);
|
||||
if (verge?.verge_mixed_port) setMixedPort(verge?.verge_mixed_port);
|
||||
if (verge?.verge_socks_port) setSocksPort(verge?.verge_socks_port);
|
||||
setSocksEnabled(verge?.verge_socks_enabled ?? false);
|
||||
if (verge?.verge_port) setPort(verge?.verge_port);
|
||||
setHttpEnabled(verge?.verge_http_enabled ?? false);
|
||||
setOpen(true);
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025;
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
if (
|
||||
redirPort === verge?.verge_redir_port &&
|
||||
tproxyPort === verge?.verge_tproxy_port &&
|
||||
mixedPort === verge?.verge_mixed_port &&
|
||||
socksPort === verge?.verge_socks_port &&
|
||||
port === verge?.verge_port &&
|
||||
redirEnabled === verge?.verge_redir_enabled &&
|
||||
tproxyEnabled === verge?.verge_tproxy_enabled &&
|
||||
socksEnabled === verge?.verge_socks_enabled &&
|
||||
httpEnabled === verge?.verge_http_enabled
|
||||
) {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
export const ClashPortViewer = forwardRef<ClashPortViewerRef, ClashPortViewerProps>(
|
||||
(props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { clashInfo, patchInfo } = useClashInfo();
|
||||
const { verge, patchVerge } = useVerge();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (
|
||||
OS === "linux" &&
|
||||
new Set([redirPort, tproxyPort, mixedPort, socksPort, port]).size !== 5
|
||||
) {
|
||||
showNotice('error', t("Port Conflict"));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
OS === "macos" &&
|
||||
new Set([redirPort, mixedPort, socksPort, port]).size !== 4
|
||||
) {
|
||||
showNotice('error', t("Port Conflict"));
|
||||
return;
|
||||
}
|
||||
if (OS === "windows" && new Set([mixedPort, socksPort, port]).size !== 3) {
|
||||
showNotice('error', t("Port Conflict"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (OS === "windows") {
|
||||
// Mixed Port
|
||||
const [mixedPort, setMixedPort] = useState(
|
||||
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
|
||||
);
|
||||
|
||||
// 其他端口状态
|
||||
const [socksPort, setSocksPort] = useState(verge?.verge_socks_port ?? 7898);
|
||||
const [socksEnabled, setSocksEnabled] = useState(verge?.verge_socks_enabled ?? false);
|
||||
const [httpPort, setHttpPort] = useState(verge?.verge_port ?? 7899);
|
||||
const [httpEnabled, setHttpEnabled] = useState(verge?.verge_http_enabled ?? false);
|
||||
const [redirPort, setRedirPort] = useState(verge?.verge_redir_port ?? 7895);
|
||||
const [redirEnabled, setRedirEnabled] = useState(verge?.verge_redir_enabled ?? false);
|
||||
const [tproxyPort, setTproxyPort] = useState(verge?.verge_tproxy_port ?? 7896);
|
||||
const [tproxyEnabled, setTproxyEnabled] = useState(verge?.verge_tproxy_enabled ?? false);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
setMixedPort(verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897);
|
||||
setSocksPort(verge?.verge_socks_port ?? 7898);
|
||||
setSocksEnabled(verge?.verge_socks_enabled ?? false);
|
||||
setHttpPort(verge?.verge_port ?? 7899);
|
||||
setHttpEnabled(verge?.verge_http_enabled ?? false);
|
||||
setRedirPort(verge?.verge_redir_port ?? 7895);
|
||||
setRedirEnabled(verge?.verge_redir_enabled ?? false);
|
||||
setTproxyPort(verge?.verge_tproxy_port ?? 7896);
|
||||
setTproxyEnabled(verge?.verge_tproxy_enabled ?? false);
|
||||
setOpen(true);
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
// 端口冲突检测
|
||||
const portList = [
|
||||
mixedPort,
|
||||
socksEnabled ? socksPort : -1,
|
||||
httpEnabled ? httpPort : -1,
|
||||
redirEnabled ? redirPort : -1,
|
||||
tproxyEnabled ? tproxyPort : -1
|
||||
].filter(p => p !== -1);
|
||||
|
||||
if (new Set(portList).size !== portList.length) {
|
||||
showNotice("error", t("Port Conflict"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 更新Clash配置
|
||||
await patchInfo({
|
||||
"mixed-port": mixedPort,
|
||||
"socks-port": socksPort,
|
||||
port,
|
||||
});
|
||||
port: httpPort,
|
||||
"redir-port": redirPort,
|
||||
"tproxy-port": tproxyPort
|
||||
} as any);
|
||||
|
||||
// 更新Verge配置
|
||||
await patchVerge({
|
||||
verge_mixed_port: mixedPort,
|
||||
verge_socks_port: socksPort,
|
||||
verge_socks_enabled: socksEnabled,
|
||||
verge_port: port,
|
||||
verge_port: httpPort,
|
||||
verge_http_enabled: httpEnabled,
|
||||
});
|
||||
}
|
||||
if (OS === "macos") {
|
||||
await patchInfo({
|
||||
"redir-port": redirPort,
|
||||
"mixed-port": mixedPort,
|
||||
"socks-port": socksPort,
|
||||
port,
|
||||
});
|
||||
await patchVerge({
|
||||
verge_redir_port: redirPort,
|
||||
verge_redir_enabled: redirEnabled,
|
||||
verge_mixed_port: mixedPort,
|
||||
verge_socks_port: socksPort,
|
||||
verge_socks_enabled: socksEnabled,
|
||||
verge_port: port,
|
||||
verge_http_enabled: httpEnabled,
|
||||
});
|
||||
}
|
||||
if (OS === "linux") {
|
||||
await patchInfo({
|
||||
"redir-port": redirPort,
|
||||
"tproxy-port": tproxyPort,
|
||||
"mixed-port": mixedPort,
|
||||
"socks-port": socksPort,
|
||||
port,
|
||||
});
|
||||
await patchVerge({
|
||||
verge_redir_port: redirPort,
|
||||
verge_redir_enabled: redirEnabled,
|
||||
verge_tproxy_port: tproxyPort,
|
||||
verge_tproxy_enabled: tproxyEnabled,
|
||||
verge_mixed_port: mixedPort,
|
||||
verge_socks_port: socksPort,
|
||||
verge_socks_enabled: socksEnabled,
|
||||
verge_port: port,
|
||||
verge_http_enabled: httpEnabled,
|
||||
verge_tproxy_enabled: tproxyEnabled
|
||||
});
|
||||
}
|
||||
setOpen(false);
|
||||
showNotice('success', t("Clash Port Modified"));
|
||||
} catch (err: any) {
|
||||
showNotice('error', err.message || err.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={t("Port Config")}
|
||||
contentSx={{ width: 300 }}
|
||||
okBtn={t("Save")}
|
||||
cancelBtn={t("Cancel")}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
onOk={onSave}
|
||||
>
|
||||
<List>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Mixed Port")} />
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 135 }}
|
||||
value={mixedPort}
|
||||
onChange={(e) =>
|
||||
setMixedPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Socks Port")} />
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 135 }}
|
||||
value={socksPort}
|
||||
onChange={(e) =>
|
||||
setSocksPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
slotProps={{
|
||||
input: {
|
||||
sx: { pr: 1 },
|
||||
endAdornment: (
|
||||
<Switch
|
||||
checked={socksEnabled}
|
||||
onChange={(_, c) => {
|
||||
setSocksEnabled(c);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Http Port")} />
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 135 }}
|
||||
value={port}
|
||||
onChange={(e) =>
|
||||
setPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
slotProps={{
|
||||
input: {
|
||||
sx: { pr: 1 },
|
||||
endAdornment: (
|
||||
<Switch
|
||||
checked={httpEnabled}
|
||||
onChange={(_, c) => {
|
||||
setHttpEnabled(c);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
{OS !== "windows" && (
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Redir Port")} />
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 135 }}
|
||||
value={redirPort}
|
||||
onChange={(e) =>
|
||||
setRedirPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
slotProps={{
|
||||
input: {
|
||||
sx: { pr: 1 },
|
||||
endAdornment: (
|
||||
<Switch
|
||||
checked={redirEnabled}
|
||||
onChange={(_, c) => {
|
||||
setRedirEnabled(c);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}}
|
||||
setOpen(false);
|
||||
showNotice("success", t("Port settings saved"));
|
||||
} catch (err) {
|
||||
showNotice("error", t("Failed to save settings"));
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={t("Port Configuration")}
|
||||
contentSx={{ width: 360, padding: "8px" }}
|
||||
okBtn={t("Save")}
|
||||
cancelBtn={t("Cancel")}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={onSave}
|
||||
>
|
||||
<List sx={{ width: "100%" }}>
|
||||
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
|
||||
<ListItemText
|
||||
primary={t("Mixed Port")}
|
||||
primaryTypographyProps={{ fontSize: 12 }}
|
||||
/>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<TextField
|
||||
size="small"
|
||||
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
|
||||
value={mixedPort}
|
||||
onChange={(e) => setMixedPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
|
||||
inputProps={{ style: { fontSize: 12 } }}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setMixedPort(generateRandomPort())}
|
||||
title={t("Random Port")}
|
||||
sx={{ mr: 0.5 }}
|
||||
>
|
||||
<Shuffle fontSize="small" />
|
||||
</IconButton>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={true}
|
||||
disabled={true}
|
||||
sx={{ ml: 0.5, opacity: 0.7 }}
|
||||
/>
|
||||
</div>
|
||||
</ListItem>
|
||||
)}
|
||||
{OS === "linux" && (
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Tproxy Port")} />
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 135 }}
|
||||
value={tproxyPort}
|
||||
onChange={(e) =>
|
||||
setTproxyPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
slotProps={{
|
||||
input: {
|
||||
sx: { pr: 1 },
|
||||
endAdornment: (
|
||||
<Switch
|
||||
checked={tproxyEnabled}
|
||||
onChange={(_, c) => {
|
||||
setTproxyEnabled(c);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}
|
||||
}}
|
||||
|
||||
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
|
||||
<ListItemText
|
||||
primary={t("Socks Port")}
|
||||
primaryTypographyProps={{ fontSize: 12 }}
|
||||
/>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<TextField
|
||||
size="small"
|
||||
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
|
||||
value={socksPort}
|
||||
onChange={(e) => setSocksPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
|
||||
disabled={!socksEnabled}
|
||||
inputProps={{ style: { fontSize: 12 } }}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setSocksPort(generateRandomPort())}
|
||||
title={t("Random Port")}
|
||||
disabled={!socksEnabled}
|
||||
sx={{ mr: 0.5 }}
|
||||
>
|
||||
<Shuffle fontSize="small" />
|
||||
</IconButton>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={socksEnabled}
|
||||
onChange={(_, c) => setSocksEnabled(c)}
|
||||
sx={{ ml: 0.5 }}
|
||||
/>
|
||||
</div>
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
|
||||
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
|
||||
<ListItemText
|
||||
primary={t("HTTP Port")}
|
||||
primaryTypographyProps={{ fontSize: 12 }}
|
||||
/>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<TextField
|
||||
size="small"
|
||||
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
|
||||
value={httpPort}
|
||||
onChange={(e) => setHttpPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
|
||||
disabled={!httpEnabled}
|
||||
inputProps={{ style: { fontSize: 12 } }}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setHttpPort(generateRandomPort())}
|
||||
title={t("Random Port")}
|
||||
disabled={!httpEnabled}
|
||||
sx={{ mr: 0.5 }}
|
||||
>
|
||||
<Shuffle fontSize="small" />
|
||||
</IconButton>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={httpEnabled}
|
||||
onChange={(_, c) => setHttpEnabled(c)}
|
||||
sx={{ ml: 0.5 }}
|
||||
/>
|
||||
</div>
|
||||
</ListItem>
|
||||
|
||||
{OS !== "windows" && (
|
||||
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
|
||||
<ListItemText
|
||||
primary={t("Redir Port")}
|
||||
primaryTypographyProps={{ fontSize: 12 }}
|
||||
/>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<TextField
|
||||
size="small"
|
||||
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
|
||||
value={redirPort}
|
||||
onChange={(e) => setRedirPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
|
||||
disabled={!redirEnabled}
|
||||
inputProps={{ style: { fontSize: 12 } }}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setRedirPort(generateRandomPort())}
|
||||
title={t("Random Port")}
|
||||
disabled={!redirEnabled}
|
||||
sx={{ mr: 0.5 }}
|
||||
>
|
||||
<Shuffle fontSize="small" />
|
||||
</IconButton>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={redirEnabled}
|
||||
onChange={(_, c) => setRedirEnabled(c)}
|
||||
sx={{ ml: 0.5 }}
|
||||
/>
|
||||
</div>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
{OS === "linux" && (
|
||||
<ListItem sx={{ padding: "4px 0", minHeight: 36 }}>
|
||||
<ListItemText
|
||||
primary={t("Tproxy Port")}
|
||||
primaryTypographyProps={{ fontSize: 12 }}
|
||||
/>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<TextField
|
||||
size="small"
|
||||
sx={{ width: 80, mr: 0.5, fontSize: 12 }}
|
||||
value={tproxyPort}
|
||||
onChange={(e) => setTproxyPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))}
|
||||
disabled={!tproxyEnabled}
|
||||
inputProps={{ style: { fontSize: 12 } }}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setTproxyPort(generateRandomPort())}
|
||||
title={t("Random Port")}
|
||||
disabled={!tproxyEnabled}
|
||||
sx={{ mr: 0.5 }}
|
||||
>
|
||||
<Shuffle fontSize="small" />
|
||||
</IconButton>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={tproxyEnabled}
|
||||
onChange={(_, c) => setTproxyEnabled(c)}
|
||||
sx={{ ml: 0.5 }}
|
||||
/>
|
||||
</div>
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,36 +1,202 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import {
|
||||
ContentCopy,
|
||||
RefreshRounded,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
CircularProgress,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Snackbar,
|
||||
Switch,
|
||||
TextField,
|
||||
Tooltip
|
||||
} from "@mui/material";
|
||||
import { useLocalStorageState, useLockFn } from "ahooks";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// 随机端口和密码生成
|
||||
const generateRandomPort = (): number => {
|
||||
return Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024;
|
||||
};
|
||||
|
||||
const generateRandomPassword = (length: number = 32): string => {
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let password = "";
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * charset.length);
|
||||
password += charset.charAt(randomIndex);
|
||||
}
|
||||
|
||||
return password;
|
||||
};
|
||||
|
||||
export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// 防止数值null
|
||||
const [autoGenerateState, setAutoGenerate] = useLocalStorageState<boolean>(
|
||||
'autoGenerateConfig',
|
||||
{ defaultValue: false as boolean }
|
||||
);
|
||||
const autoGenerate = autoGenerateState!;
|
||||
|
||||
const [copySuccess, setCopySuccess] = useState<null | string>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isRestarting, setIsRestarting] = useState(false);
|
||||
|
||||
const { clashInfo, patchInfo } = useClashInfo();
|
||||
|
||||
const [controller, setController] = useState(clashInfo?.server || "");
|
||||
const [secret, setSecret] = useState(clashInfo?.secret || "");
|
||||
|
||||
// 直接通过API重启内核
|
||||
const restartCoreDirectly = useLockFn(async () => {
|
||||
try {
|
||||
const controllerUrl = controller || clashInfo?.server || 'http://localhost:9090';
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (secret) {
|
||||
headers['Authorization'] = `Bearer ${secret}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${controllerUrl}/restart`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(errorText || 'Failed to restart core');
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
return await response.json();
|
||||
} else {
|
||||
const text = await response.text();
|
||||
console.log('Non-JSON response:', text);
|
||||
return { message: 'Restart request sent successfully' };
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Error restarting core:', err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
// 生成随机配置并重启内核
|
||||
const generateAndRestart = useLockFn(async () => {
|
||||
try {
|
||||
setIsRestarting(true);
|
||||
|
||||
const port = generateRandomPort();
|
||||
const password = generateRandomPassword();
|
||||
|
||||
const host = controller.split(':')[0] || '127.0.0.1';
|
||||
const newController = `${host}:${port}`;
|
||||
|
||||
setController(newController);
|
||||
setSecret(password);
|
||||
|
||||
// 更新配置
|
||||
await patchInfo({ "external-controller": newController, secret: password });
|
||||
|
||||
// 直接重启内核
|
||||
await restartCoreDirectly();
|
||||
|
||||
// 静默执行,不显示通知
|
||||
} catch (err: any) {
|
||||
showNotice('error', err.message || t("Failed to generate configuration or restart core"), 4000);
|
||||
} finally {
|
||||
setIsRestarting(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 仅在对话框打开时生成配置
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
open: async () => {
|
||||
setOpen(true);
|
||||
setController(clashInfo?.server || "");
|
||||
setSecret(clashInfo?.secret || "");
|
||||
|
||||
if (autoGenerate) {
|
||||
await generateAndRestart();
|
||||
} else {
|
||||
setController(clashInfo?.server || "");
|
||||
setSecret(clashInfo?.secret || "");
|
||||
}
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
// 当自动生成开关状态变化时触发
|
||||
useEffect(() => {
|
||||
if (autoGenerate && open) {
|
||||
generateAndRestart();
|
||||
}
|
||||
}, [autoGenerate, open]);
|
||||
|
||||
// 保存函数(优化)
|
||||
const onSave = useLockFn(async () => {
|
||||
if (!controller.trim()) {
|
||||
showNotice('info', t("Controller address cannot be empty"), 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
|
||||
await patchInfo({ "external-controller": controller, secret });
|
||||
showNotice('success', t("External Controller Address Modified"), 1000);
|
||||
|
||||
showNotice('success', t("Configuration saved successfully"), 2000);
|
||||
setOpen(false);
|
||||
} catch (err: any) {
|
||||
showNotice('error', err.message || err.toString(), 4000);
|
||||
showNotice('error', err.message || t("Failed to save configuration"), 4000);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 生成随机端口
|
||||
const handleGeneratePort = useLockFn(async () => {
|
||||
if (!autoGenerate) {
|
||||
const port = generateRandomPort();
|
||||
const host = controller.split(':')[0] || '127.0.0.1';
|
||||
setController(`${host}:${port}`);
|
||||
showNotice('success', t("Random port generated"), 1000);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// 生成随机 Secret
|
||||
const handleGenerateSecret = useLockFn(async () => {
|
||||
if (!autoGenerate) {
|
||||
const password = generateRandomPassword();
|
||||
setSecret(password);
|
||||
showNotice('success', t("Random secret generated"), 1000);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// 复制到剪贴板
|
||||
const handleCopyToClipboard = useLockFn(async (text: string, type: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopySuccess(type);
|
||||
setTimeout(() => setCopySuccess(null), 2000);
|
||||
} catch (err) {
|
||||
showNotice('error', t("Failed to copy"), 2000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -39,39 +205,141 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
open={open}
|
||||
title={t("External Controller")}
|
||||
contentSx={{ width: 400 }}
|
||||
okBtn={t("Save")}
|
||||
okBtn={
|
||||
isSaving ? (
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
{t("Saving...")}
|
||||
</Box>
|
||||
) : (
|
||||
t("Save")
|
||||
)
|
||||
}
|
||||
cancelBtn={t("Cancel")}
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
onOk={onSave}
|
||||
>
|
||||
<List>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("External Controller")} />
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 175 }}
|
||||
value={controller}
|
||||
placeholder="Required"
|
||||
onChange={(e) => setController(e.target.value)}
|
||||
/>
|
||||
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<ListItemText primary={t("External Controller")} />
|
||||
<Tooltip title={t("Generate Random Port")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleGeneratePort}
|
||||
color="primary"
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
>
|
||||
<RefreshRounded fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 175, opacity: autoGenerate ? 0.7 : 1 }}
|
||||
value={controller}
|
||||
placeholder="Required"
|
||||
onChange={(e) => setController(e.target.value)}
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
/>
|
||||
{autoGenerate && (
|
||||
<Tooltip title={t("Copy to clipboard")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => handleCopyToClipboard(controller, "controller")}
|
||||
color="primary"
|
||||
disabled={isSaving || isRestarting}
|
||||
>
|
||||
<ContentCopy fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Core Secret")} />
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 175 }}
|
||||
value={secret}
|
||||
placeholder={t("Recommended")}
|
||||
onChange={(e) =>
|
||||
setSecret(e.target.value?.replace(/[^\x00-\x7F]/g, ""))
|
||||
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<ListItemText primary={t("Core Secret")} />
|
||||
<Tooltip title={t("Generate Random Secret")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleGenerateSecret}
|
||||
color="primary"
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
>
|
||||
<RefreshRounded fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
size="small"
|
||||
sx={{ width: 175, opacity: autoGenerate ? 0.7 : 1 }}
|
||||
value={secret}
|
||||
placeholder={t("Recommended")}
|
||||
onChange={(e) =>
|
||||
setSecret(e.target.value?.replace(/[^\x00-\x7F]/g, ""))
|
||||
}
|
||||
disabled={autoGenerate || isSaving || isRestarting}
|
||||
/>
|
||||
{autoGenerate && (
|
||||
<Tooltip title={t("Copy to clipboard")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => handleCopyToClipboard(secret, "secret")}
|
||||
color="primary"
|
||||
disabled={isSaving || isRestarting}
|
||||
>
|
||||
<ContentCopy fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px", display: "flex", justifyContent: "space-between" }}>
|
||||
<ListItemText
|
||||
primary={t("Auto Random Config")}
|
||||
secondary={
|
||||
autoGenerate
|
||||
? t("Generate new config and restart core when entering settings")
|
||||
: t("Manual configuration")
|
||||
}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={autoGenerate}
|
||||
onChange={() => setAutoGenerate(!autoGenerate)}
|
||||
color="primary"
|
||||
disabled={isSaving || isRestarting}
|
||||
/>
|
||||
}
|
||||
label={autoGenerate ? t("On") : t("Off")}
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<Snackbar
|
||||
open={copySuccess !== null}
|
||||
autoHideDuration={2000}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
>
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{copySuccess === "controller"
|
||||
? t("Controller address copied to clipboard")
|
||||
: t("Secret copied to clipboard")
|
||||
}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -187,9 +187,10 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
>
|
||||
{[
|
||||
{ key: t("Never Clean"), value: 0 },
|
||||
{ key: t("Retain _n Days", { n: 7 }), value: 1 },
|
||||
{ key: t("Retain _n Days", { n: 30 }), value: 2 },
|
||||
{ key: t("Retain _n Days", { n: 90 }), value: 3 },
|
||||
{ key: t("Retain _n Days", { n: 1 }), value: 1 },
|
||||
{ key: t("Retain _n Days", { n: 7 }), value: 2 },
|
||||
{ key: t("Retain _n Days", { n: 30 }), value: 3 },
|
||||
{ key: t("Retain _n Days", { n: 90 }), value: 4 },
|
||||
].map((i) => (
|
||||
<MenuItem key={i.value} value={i.value}>
|
||||
{i.key}
|
||||
|
||||
@@ -67,13 +67,13 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
url = url.replaceAll("%port", port || "9097");
|
||||
url = url.replaceAll(
|
||||
"%secret",
|
||||
encodeURIComponent(clashInfo.secret || "")
|
||||
encodeURIComponent(clashInfo.secret || ""),
|
||||
);
|
||||
}
|
||||
|
||||
await openWebUrl(url);
|
||||
} catch (e: any) {
|
||||
showNotice('error', e.message || e.toString());
|
||||
showNotice("error", e.message || e.toString());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TextField, Select, MenuItem, Typography } from "@mui/material";
|
||||
import {
|
||||
SettingsRounded,
|
||||
ShuffleRounded,
|
||||
LanRounded,
|
||||
} from "@mui/icons-material";
|
||||
import { DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { GuardState } from "./mods/guard-state";
|
||||
import { WebUIViewer } from "./mods/web-ui-viewer";
|
||||
import { ClashPortViewer } from "./mods/clash-port-viewer";
|
||||
import { ControllerViewer } from "./mods/controller-viewer";
|
||||
import { SettingList, SettingItem } from "./mods/setting-comp";
|
||||
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
||||
import { invoke_uwp_tool } from "@/services/cmds";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { useListen } from "@/hooks/use-listen";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { updateGeoData } from "@/services/api";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { NetworkInterfaceViewer } from "./mods/network-interface-viewer";
|
||||
import { DnsViewer } from "./mods/dns-viewer";
|
||||
import { invoke_uwp_tool } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import {
|
||||
LanRounded,
|
||||
SettingsRounded
|
||||
} from "@mui/icons-material";
|
||||
import ErrorOutlineRounded from '@mui/icons-material/ErrorOutlineRounded';
|
||||
import { MenuItem, Select, TextField, Typography } from "@mui/material";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useListen } from "@/hooks/use-listen";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
||||
import { ClashPortViewer } from "./mods/clash-port-viewer";
|
||||
import { ControllerViewer } from "./mods/controller-viewer";
|
||||
import { DnsViewer } from "./mods/dns-viewer";
|
||||
import { GuardState } from "./mods/guard-state";
|
||||
import { NetworkInterfaceViewer } from "./mods/network-interface-viewer";
|
||||
import { SettingItem, SettingList } from "./mods/setting-comp";
|
||||
import { WebUIViewer } from "./mods/web-ui-viewer";
|
||||
|
||||
const isWIN = getSystem() === "windows";
|
||||
|
||||
@@ -50,16 +50,9 @@ const SettingClash = ({ onError }: Props) => {
|
||||
|
||||
// 独立跟踪DNS设置开关状态
|
||||
const [dnsSettingsEnabled, setDnsSettingsEnabled] = useState(() => {
|
||||
// 尝试从localStorage获取之前保存的状态
|
||||
// 如果重装(或删除数据更新)前开关处于关闭状态,重装后会获取到错误的状态
|
||||
// const savedState = localStorage.getItem("dns_settings_enabled");
|
||||
// if (savedState !== null) {
|
||||
// return savedState === "true";
|
||||
// }
|
||||
// 如果没有保存的状态,则从verge配置中获取
|
||||
return verge?.enable_dns_settings ?? false;
|
||||
});
|
||||
|
||||
|
||||
const { addListener } = useListen();
|
||||
|
||||
const webRef = useRef<DialogRef>(null);
|
||||
@@ -88,24 +81,18 @@ const SettingClash = ({ onError }: Props) => {
|
||||
// 实现DNS设置开关处理函数
|
||||
const handleDnsToggle = useLockFn(async (enable: boolean) => {
|
||||
try {
|
||||
// 立即更新UI状态
|
||||
setDnsSettingsEnabled(enable);
|
||||
// 保存到localStorage,用于记住用户的选择
|
||||
localStorage.setItem("dns_settings_enabled", String(enable));
|
||||
// 更新verge配置
|
||||
await patchVerge({ enable_dns_settings: enable });
|
||||
await invoke("apply_dns_config", { apply: enable });
|
||||
setTimeout(() => {
|
||||
mutateClash();
|
||||
}, 500); // 延迟500ms确保后端完成处理
|
||||
}, 500);
|
||||
} catch (err: any) {
|
||||
// 如果出错,恢复原始状态
|
||||
setDnsSettingsEnabled(!enable);
|
||||
localStorage.setItem("dns_settings_enabled", String(!enable));
|
||||
showNotice('error', err.message || err.toString());
|
||||
await patchVerge({ enable_dns_settings: !enable }).catch(() => {
|
||||
// 忽略恢复状态时的错误
|
||||
});
|
||||
await patchVerge({ enable_dns_settings: !enable }).catch(() => {});
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
@@ -219,22 +206,10 @@ const SettingClash = ({ onError }: Props) => {
|
||||
|
||||
<SettingItem
|
||||
label={t("Port Config")}
|
||||
extra={
|
||||
<TooltipIcon
|
||||
title={t("Random Port")}
|
||||
color={enable_random_port ? "primary" : "inherit"}
|
||||
icon={ShuffleRounded}
|
||||
onClick={() => {
|
||||
showNotice('success', t("Restart Application to Apply Modifications"), 1000);
|
||||
onChangeVerge({ enable_random_port: !enable_random_port });
|
||||
patchVerge({ enable_random_port: !enable_random_port });
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextField
|
||||
autoComplete="new-password"
|
||||
disabled={enable_random_port}
|
||||
disabled={false}
|
||||
size="small"
|
||||
value={verge_mixed_port ?? 7897}
|
||||
sx={{ width: 100, input: { py: "7.5px", cursor: "pointer" } }}
|
||||
@@ -245,10 +220,20 @@ const SettingClash = ({ onError }: Props) => {
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
onClick={() => ctrlRef.current?.open()}
|
||||
label={t("External")}
|
||||
/>
|
||||
<SettingItem
|
||||
onClick={() => ctrlRef.current?.open()}
|
||||
label={
|
||||
<>
|
||||
{t("External")}
|
||||
<TooltipIcon
|
||||
title={t("Enable one-click random API port and key. Click to randomize the port and key")}
|
||||
color={"error"}
|
||||
icon={ErrorOutlineRounded}
|
||||
sx={{ fontSize: "0.875em", ml: 0.5 }}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingItem onClick={() => webRef.current?.open()} label={t("Web UI")} />
|
||||
|
||||
|
||||
@@ -274,11 +274,11 @@
|
||||
"Unified Delay Info": "When unified delay is turned on, two delay tests will be performed to eliminate the delay differences between different types of nodes caused by connection handshakes, etc",
|
||||
"Log Level": "Log Level",
|
||||
"Log Level Info": "This parameter is valid only for kernel log files in the log directory Service folder",
|
||||
"Port Config": "Port Config",
|
||||
"Port Configuration": "Port Configuration",
|
||||
"Random Port": "Random Port",
|
||||
"Mixed Port": "Mixed Port",
|
||||
"Socks Port": "Socks Port",
|
||||
"Http Port": "Http(s) Port",
|
||||
"HTTP Port": "Http(s) Port",
|
||||
"Redir Port": "Redir Port",
|
||||
"Tproxy Port": "Tproxy Port",
|
||||
"External": "External",
|
||||
@@ -392,7 +392,7 @@
|
||||
"Stopping Core...": "Stopping Core...",
|
||||
"Restarting Core...": "Restarting Core...",
|
||||
"Installing Service...": "Installing Service...",
|
||||
"Uninstall Service":"Uninstall Service",
|
||||
"Uninstall Service": "Uninstall Service",
|
||||
"Service Installed Successfully": "Service Installed Successfully",
|
||||
"Service is ready and core restarted": "Service is ready and core restarted",
|
||||
"Core restarted. Service is now available.": "Core restarted. Service is now available.",
|
||||
@@ -615,5 +615,18 @@
|
||||
"Originals Only": "Originals Only",
|
||||
"No (IP Banned By Disney+)": "No (IP Banned By Disney+)",
|
||||
"Unsupported Country/Region": "Unsupported Country/Region",
|
||||
"Failed (Network Connection)": "Failed (Network Connection)"
|
||||
}
|
||||
"Failed (Network Connection)": "Failed (Network Connection)",
|
||||
"Auto Random Config": "Auto Random Config",
|
||||
"Generate new config and restart core when entering settings": "Generate new config and restart core when entering settings",
|
||||
"Manual configuration": "Manual configuration",
|
||||
"Controller address copied to clipboard": "Controller address copied to clipboard",
|
||||
"Secret copied to clipboard": "Secret copied to clipboard",
|
||||
"Copy to clipboard": "Copy to clipboard",
|
||||
"Generate Random Secret": "Generate Random Secret",
|
||||
"Generate Random Port": "Generate Random Port",
|
||||
"Port Config": "Port Config",
|
||||
"Configuration saved successfully": "Configuration saved successfully",
|
||||
"Last generated": "Last generated",
|
||||
"External Controller Config": "External Controller Config",
|
||||
"Enable one-click random API port and key. Click to randomize the port and key": "Enable one-click random API port and key. Click to randomize the port and key"
|
||||
}
|
||||
|
||||
@@ -274,13 +274,13 @@
|
||||
"Unified Delay Info": "开启统一延迟时,会进行两次延迟测试,以消除连接握手等带来的不同类型节点的延迟差异",
|
||||
"Log Level": "日志等级",
|
||||
"Log Level Info": "仅对日志目录 Service 文件夹下的内核日志文件生效",
|
||||
"Port Config": "端口设置",
|
||||
"Port Configuration": "端口设置",
|
||||
"Random Port": "随机端口",
|
||||
"Mixed Port": "混合代理端口",
|
||||
"Socks Port": "SOCKS 代理端口",
|
||||
"Http Port": "HTTP(S) 代理端口",
|
||||
"HTTP Port": "HTTP(S) 代理端口",
|
||||
"Redir Port": "Redir 透明代理端口",
|
||||
"TPROXY Port": "TPROXY 透明代理端口",
|
||||
"Tproxy Port": "TPROXY 透明代理端口",
|
||||
"External": "外部控制",
|
||||
"External Controller": "外部控制器监听地址",
|
||||
"Core Secret": "API 访问密钥",
|
||||
@@ -392,7 +392,7 @@
|
||||
"Stopping Core...": "内核停止中...",
|
||||
"Restarting Core...": "内核重启中...",
|
||||
"Installing Service...": "安装服务中...",
|
||||
"Uninstall Service":"卸载服务",
|
||||
"Uninstall Service": "卸载服务",
|
||||
"Service Installed Successfully": "已成功安装服务",
|
||||
"Service is ready and core restarted": "服务已就绪,内核已重启",
|
||||
"Core restarted. Service is now available.": "内核已重启,服务已就绪",
|
||||
@@ -615,5 +615,18 @@
|
||||
"Originals Only": "仅限原创",
|
||||
"No (IP Banned By Disney+)": "不支持(IP被Disney+禁止)",
|
||||
"Unsupported Country/Region": "不支持的国家/地区",
|
||||
"Failed (Network Connection)": "测试失败(网络连接问题)"
|
||||
}
|
||||
"Failed (Network Connection)": "测试失败(网络连接问题)",
|
||||
"Auto Random Config": "启动时自动随机端口和密码",
|
||||
"Generate new config and restart core when entering settings": "自动随机API端口和密码,重新进入设置即可",
|
||||
"Manual configuration": "手动配置",
|
||||
"Controller address copied to clipboard": "API端口已经复制到剪贴板",
|
||||
"Secret copied to clipboard": "API密钥已经复制到剪贴板",
|
||||
"Copy to clipboard": "点击我复制",
|
||||
"Generate Random Secret": "随机API密钥",
|
||||
"Generate Random Port": "随机API端口",
|
||||
"Port Config": "端口设置",
|
||||
"Configuration saved successfully": "随机配置,保存完成",
|
||||
"Last generated": "记录生成",
|
||||
"External Controller Config": "API配置",
|
||||
"Enable one-click random API port and key. Click to randomize the port and key": "开启一键随机API端口和密钥,点进去就可以随机端口和密钥"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import dayjs from "dayjs";
|
||||
import i18next from "i18next";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import { SWRConfig, mutate } from "swr";
|
||||
import { useEffect, useCallback } from "react";
|
||||
import { useEffect, useCallback, useState, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useRoutes, useNavigate } from "react-router-dom";
|
||||
import { List, Paper, ThemeProvider, SvgIcon } from "@mui/material";
|
||||
@@ -150,6 +150,7 @@ const Layout = () => {
|
||||
const location = useLocation();
|
||||
const routersEles = useRoutes(routers);
|
||||
const { addListener, setupCloseListener } = useListen();
|
||||
const initRef = useRef(false);
|
||||
|
||||
const handleNotice = useCallback(
|
||||
(payload: [string, string]) => {
|
||||
@@ -215,53 +216,60 @@ const Layout = () => {
|
||||
}, [handleNotice]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initRef.current) {
|
||||
console.log("[Layout] 初始化代码已执行过,跳过");
|
||||
return;
|
||||
}
|
||||
console.log("[Layout] 开始执行初始化代码");
|
||||
initRef.current = true;
|
||||
|
||||
const notifyUiStage = async (stage: string) => {
|
||||
try {
|
||||
console.log(`UI加载阶段: ${stage}`);
|
||||
console.log(`[Layout] UI加载阶段: ${stage}`);
|
||||
await invoke("update_ui_stage", { stage });
|
||||
} catch (err) {
|
||||
console.error(`通知UI加载阶段(${stage})失败:`, err);
|
||||
console.error(`[Layout] 通知UI加载阶段(${stage})失败:`, err);
|
||||
}
|
||||
};
|
||||
|
||||
const notifyUiCoreReady = async () => {
|
||||
try {
|
||||
console.log("核心组件已加载,通知后端");
|
||||
console.log("[Layout] 核心组件已加载,通知后端");
|
||||
await invoke("update_ui_stage", { stage: "DomReady" });
|
||||
} catch (err) {
|
||||
console.error("通知核心组件加载完成失败:", err);
|
||||
console.error("[Layout] 通知核心组件加载完成失败:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const notifyUiResourcesLoaded = async () => {
|
||||
try {
|
||||
console.log("所有资源已加载,通知后端");
|
||||
console.log("[Layout] 所有资源已加载,通知后端");
|
||||
await invoke("update_ui_stage", { stage: "ResourcesLoaded" });
|
||||
} catch (err) {
|
||||
console.error("通知资源加载完成失败:", err);
|
||||
console.error("[Layout] 通知资源加载完成失败:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const notifyUiReady = async () => {
|
||||
try {
|
||||
console.log("UI完全准备就绪,通知后端");
|
||||
console.log("[Layout] UI完全准备就绪,通知后端");
|
||||
await invoke("notify_ui_ready");
|
||||
} catch (err) {
|
||||
console.error("通知UI准备就绪失败:", err);
|
||||
console.error("[Layout] 通知UI准备就绪失败:", err);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听后端发送的启动完成事件
|
||||
const listenStartupCompleted = async () => {
|
||||
try {
|
||||
console.log("开始监听启动完成事件");
|
||||
console.log("[Layout] 开始监听启动完成事件");
|
||||
const unlisten = await listen("verge://startup-completed", () => {
|
||||
console.log("收到启动完成事件,开始通知UI就绪");
|
||||
console.log("[Layout] 收到启动完成事件,开始通知UI就绪");
|
||||
notifyUiReady();
|
||||
});
|
||||
return unlisten;
|
||||
} catch (err) {
|
||||
console.error("监听启动完成事件失败:", err);
|
||||
console.error("[Layout] 监听启动完成事件失败:", err);
|
||||
return () => {};
|
||||
}
|
||||
};
|
||||
|
||||
+1
-1
@@ -785,7 +785,7 @@ interface IVergeConfig {
|
||||
default_latency_test?: string;
|
||||
default_latency_timeout?: number;
|
||||
enable_builtin_enhanced?: boolean;
|
||||
auto_log_clean?: 0 | 1 | 2 | 3;
|
||||
auto_log_clean?: 0 | 1 | 2 | 3 | 4;
|
||||
proxy_layout_column?: number;
|
||||
test_list?: IVergeTestItem[];
|
||||
webdav_url?: string;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-5.15 = .182
|
||||
LINUX_KERNEL_HASH-5.15.182 = b6abfa53315a04e459070b927c58beb41f085433117d58756504d68b67f6a31e
|
||||
LINUX_VERSION-5.15 = .183
|
||||
LINUX_KERNEL_HASH-5.15.183 = d06f7f629a4d61a87ebd0db285ace9ebf4fce0226b10b2c0ec235e3550c58ee8
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-6.1 = .138
|
||||
LINUX_KERNEL_HASH-6.1.138 = e319a5bb9049ba9fb8cbc08cba4874716e8985bd10f7971f2573ea802c257911
|
||||
LINUX_VERSION-6.1 = .139
|
||||
LINUX_KERNEL_HASH-6.1.139 = f66affdfee8b6cf8a14cfa00cc7842f79af0e7a70a68604289074f3ecffc9f18
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LINUX_VERSION-6.6 = .90
|
||||
LINUX_KERNEL_HASH-6.6.90 = ff856748671629c1fefef219099e0b4b81131c2d325e768cb0806e204157014e
|
||||
LINUX_VERSION-6.6 = .91
|
||||
LINUX_KERNEL_HASH-6.6.91 = d08d3d175407a52cd0b25fc95e149bbd2fd6922cd37816c8fcfad18f95e254f4
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=openssl
|
||||
PKG_VERSION:=3.0.16
|
||||
PKG_VERSION:=3.5.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_USE_MIPS16:=0
|
||||
PKG_BUILD_FLAGS:=gc-sections no-lto
|
||||
@@ -19,7 +19,7 @@ PKG_BASE:=$(subst $(space),.,$(wordlist 1,2,$(subst .,$(space),$(PKG_VERSION))))
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/openssl/openssl/releases/download/$(PKG_NAME)-$(PKG_VERSION)/
|
||||
|
||||
PKG_HASH:=57e03c50feab5d31b152af2b764f10379aecd8ee92f16c985983ce4a99f7ef86
|
||||
PKG_HASH:=344d0a79f1a9b08029b0744e2cc401a43f9c90acd1044d09a530b4885a8e9fc0
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
@@ -10,7 +10,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
|
||||
--- a/Configure
|
||||
+++ b/Configure
|
||||
@@ -1677,7 +1677,9 @@ $config{CFLAGS} = [ map { $_ eq '--ossl-
|
||||
@@ -1810,7 +1810,9 @@ $config{CFLAGS} = [ map { $_ eq '--ossl-
|
||||
|
||||
unless ($disabled{afalgeng}) {
|
||||
$config{afalgeng}="";
|
||||
@@ -18,6 +18,6 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
+ if ($target =~ m/openwrt$/) {
|
||||
+ push @{$config{engdirs}}, "afalg";
|
||||
+ } elsif (grep { $_ eq 'afalgeng' } @{$target{enable}}) {
|
||||
my $minver = 4*10000 + 1*100 + 0;
|
||||
if ($config{CROSS_COMPILE} eq "") {
|
||||
my $verstr = `uname -r`;
|
||||
push @{$config{engdirs}}, "afalg";
|
||||
} else {
|
||||
disable('not-linux', 'afalgeng');
|
||||
|
||||
@@ -10,7 +10,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
|
||||
--- a/crypto/build.info
|
||||
+++ b/crypto/build.info
|
||||
@@ -109,7 +109,7 @@ DEFINE[../libcrypto]=$UPLINKDEF
|
||||
@@ -115,7 +115,7 @@ DEFINE[../libcrypto]=$UPLINKDEF
|
||||
|
||||
DEPEND[info.o]=buildinf.h
|
||||
DEPEND[cversion.o]=buildinf.h
|
||||
|
||||
@@ -16,7 +16,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
|
||||
--- a/ssl/ssl_ciph.c
|
||||
+++ b/ssl/ssl_ciph.c
|
||||
@@ -1506,11 +1506,29 @@ STACK_OF(SSL_CIPHER) *ssl_create_cipher_
|
||||
@@ -1488,11 +1488,29 @@ STACK_OF(SSL_CIPHER) *ssl_create_cipher_
|
||||
ssl_cipher_apply_rule(0, SSL_kECDHE, 0, 0, 0, 0, 0, CIPHER_DEL, -1, &head,
|
||||
&tail);
|
||||
|
||||
@@ -46,7 +46,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
|
||||
/*
|
||||
* ...and generally, our preferred cipher is AES.
|
||||
@@ -1565,7 +1583,7 @@ STACK_OF(SSL_CIPHER) *ssl_create_cipher_
|
||||
@@ -1547,7 +1565,7 @@ STACK_OF(SSL_CIPHER) *ssl_create_cipher_
|
||||
* Within each group, ciphers remain sorted by strength and previous
|
||||
* preference, i.e.,
|
||||
* 1) ECDHE > DHE
|
||||
@@ -55,7 +55,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
* 3) AES > rest
|
||||
* 4) TLS 1.2 > legacy
|
||||
*
|
||||
@@ -2236,7 +2254,13 @@ const char *OSSL_default_cipher_list(voi
|
||||
@@ -2246,7 +2264,13 @@ const char *OSSL_default_cipher_list(voi
|
||||
*/
|
||||
const char *OSSL_default_ciphersuites(void)
|
||||
{
|
||||
@@ -71,7 +71,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
}
|
||||
--- a/include/openssl/ssl.h.in
|
||||
+++ b/include/openssl/ssl.h.in
|
||||
@@ -195,9 +195,15 @@ extern "C" {
|
||||
@@ -199,9 +199,15 @@ extern "C" {
|
||||
* DEPRECATED IN 3.0.0, in favor of OSSL_default_ciphersuites()
|
||||
* Update both macro and function simultaneously
|
||||
*/
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
|
||||
--- a/engines/e_devcrypto.c
|
||||
+++ b/engines/e_devcrypto.c
|
||||
@@ -905,7 +905,7 @@ static void prepare_digest_methods(void)
|
||||
@@ -906,7 +906,7 @@ static void prepare_digest_methods(void)
|
||||
for (i = 0, known_digest_nids_amount = 0; i < OSSL_NELEM(digest_data);
|
||||
i++) {
|
||||
|
||||
@@ -30,7 +30,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
|
||||
/*
|
||||
* Check that the digest is usable
|
||||
@@ -1119,7 +1119,7 @@ static const ENGINE_CMD_DEFN devcrypto_c
|
||||
@@ -1120,7 +1120,7 @@ static const ENGINE_CMD_DEFN devcrypto_c
|
||||
#ifdef IMPLEMENT_DIGEST
|
||||
{DEVCRYPTO_CMD_DIGESTS,
|
||||
"DIGESTS",
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Signed-off-by: Eneas U de Queiroz <cote2004-github@yahoo.com>
|
||||
|
||||
--- a/engines/e_devcrypto.c
|
||||
+++ b/engines/e_devcrypto.c
|
||||
@@ -211,9 +211,8 @@ static int cipher_init(EVP_CIPHER_CTX *c
|
||||
@@ -212,9 +212,8 @@ static int cipher_init(EVP_CIPHER_CTX *c
|
||||
int ret;
|
||||
|
||||
/* cleanup a previous session */
|
||||
|
||||
@@ -118,7 +118,10 @@ func (u *UIUpdater) downloadUI() error {
|
||||
|
||||
tmpDir := C.Path.Resolve("downloadUI.tmp")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
extractedFolder, err := extract(data, tmpDir)
|
||||
|
||||
os.RemoveAll(tmpDir) // cleanup tmp dir before extract
|
||||
log.Debugln("extractedFolder: %s", tmpDir)
|
||||
err = extract(data, tmpDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't extract compressed file: %w", err)
|
||||
}
|
||||
@@ -136,8 +139,8 @@ func (u *UIUpdater) downloadUI() error {
|
||||
return fmt.Errorf("prepare UI path failed: %w", err)
|
||||
}
|
||||
|
||||
log.Debugln("moveFolder from %s to %s", extractedFolder, u.externalUIPath)
|
||||
err = moveDir(extractedFolder, u.externalUIPath) // move files from tmp to target
|
||||
log.Debugln("moveFolder from %s to %s", tmpDir, u.externalUIPath)
|
||||
err = moveDir(tmpDir, u.externalUIPath) // move files from tmp to target
|
||||
if err != nil {
|
||||
return fmt.Errorf("move UI folder failed: %w", err)
|
||||
}
|
||||
@@ -154,63 +157,19 @@ func (u *UIUpdater) prepareUIPath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unzip(data []byte, dest string) (string, error) {
|
||||
func unzip(data []byte, dest string) error {
|
||||
r, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
// check whether or not only exists singleRoot dir
|
||||
rootDir := ""
|
||||
isSingleRoot := true
|
||||
rootItemCount := 0
|
||||
for _, f := range r.File {
|
||||
parts := strings.Split(strings.Trim(f.Name, "/"), "/")
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
isDir := strings.HasSuffix(f.Name, "/")
|
||||
if !isDir {
|
||||
isSingleRoot = false
|
||||
break
|
||||
}
|
||||
|
||||
if rootDir == "" {
|
||||
rootDir = parts[0]
|
||||
}
|
||||
rootItemCount++
|
||||
}
|
||||
}
|
||||
|
||||
if rootItemCount != 1 {
|
||||
isSingleRoot = false
|
||||
}
|
||||
|
||||
// build the dir of extraction
|
||||
var extractedFolder string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
// if the singleRoot, use it directly
|
||||
log.Debugln("Match the singleRoot")
|
||||
extractedFolder = filepath.Join(dest, rootDir)
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
} else {
|
||||
log.Debugln("Match the multiRoot")
|
||||
extractedFolder = dest
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
}
|
||||
|
||||
for _, f := range r.File {
|
||||
var fpath string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
fpath = filepath.Join(dest, f.Name)
|
||||
} else {
|
||||
fpath = filepath.Join(extractedFolder, f.Name)
|
||||
}
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
|
||||
if !inDest(fpath, dest) {
|
||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||
return fmt.Errorf("invalid file path: %s", fpath)
|
||||
}
|
||||
info := f.FileInfo()
|
||||
if info.IsDir() {
|
||||
@@ -221,128 +180,77 @@ func unzip(data []byte, dest string) (string, error) {
|
||||
continue // disallow symlink
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(outFile, rc)
|
||||
outFile.Close()
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
}
|
||||
return extractedFolder, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func untgz(data []byte, dest string) (string, error) {
|
||||
func untgz(data []byte, dest string) error {
|
||||
gzr, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
rootDir := ""
|
||||
isSingleRoot := true
|
||||
rootItemCount := 0
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parts := strings.Split(cleanTarPath(header.Name), string(os.PathSeparator))
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
isDir := header.Typeflag == tar.TypeDir
|
||||
if !isDir {
|
||||
isSingleRoot = false
|
||||
break
|
||||
}
|
||||
|
||||
if rootDir == "" {
|
||||
rootDir = parts[0]
|
||||
}
|
||||
rootItemCount++
|
||||
}
|
||||
}
|
||||
|
||||
if rootItemCount != 1 {
|
||||
isSingleRoot = false
|
||||
}
|
||||
|
||||
_ = gzr.Reset(bytes.NewReader(data))
|
||||
tr = tar.NewReader(gzr)
|
||||
|
||||
var extractedFolder string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
log.Debugln("Match the singleRoot")
|
||||
extractedFolder = filepath.Join(dest, rootDir)
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
} else {
|
||||
log.Debugln("Match the multiRoot")
|
||||
extractedFolder = dest
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
}
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
var fpath string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
fpath = filepath.Join(dest, cleanTarPath(header.Name))
|
||||
} else {
|
||||
fpath = filepath.Join(extractedFolder, cleanTarPath(header.Name))
|
||||
}
|
||||
fpath := filepath.Join(dest, header.Name)
|
||||
|
||||
if !inDest(fpath, dest) {
|
||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||
return fmt.Errorf("invalid file path: %s", fpath)
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(outFile, tr); err != nil {
|
||||
outFile.Close()
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile.Close()
|
||||
}
|
||||
}
|
||||
return extractedFolder, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func extract(data []byte, dest string) (string, error) {
|
||||
func extract(data []byte, dest string) error {
|
||||
fileType := detectFileType(data)
|
||||
log.Debugln("compression Type: %s", fileType)
|
||||
switch fileType {
|
||||
@@ -351,7 +259,7 @@ func extract(data []byte, dest string) (string, error) {
|
||||
case typeTarGzip:
|
||||
return untgz(data, dest)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown or unsupported file type")
|
||||
return fmt.Errorf("unknown or unsupported file type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +301,15 @@ func moveDir(src string, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dirEntryList) == 1 && dirEntryList[0].IsDir() {
|
||||
src = filepath.Join(src, dirEntryList[0].Name())
|
||||
log.Debugln("match the singleRoot: %s", src)
|
||||
dirEntryList, err = os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, dirEntry := range dirEntryList {
|
||||
err = os.Rename(filepath.Join(src, dirEntry.Name()), filepath.Join(dst, dirEntry.Name()))
|
||||
if err != nil {
|
||||
|
||||
@@ -645,6 +645,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: xxx
|
||||
short-id: xxx # optional
|
||||
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
|
||||
client-fingerprint: chrome # cannot be empty
|
||||
|
||||
- name: "vless-reality-grpc"
|
||||
@@ -664,6 +665,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
short-id: 10f897e26c4b9478
|
||||
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
|
||||
|
||||
- name: "vless-ws"
|
||||
type: vless
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ require (
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
|
||||
github.com/metacubex/sing-mux v0.3.2
|
||||
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336
|
||||
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.3
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
|
||||
+2
-4
@@ -120,10 +120,8 @@ github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxM
|
||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
||||
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336 h1:5BgpaFkTzkePwF1A8rmhCqgyOMG79BLsAhFR8W8SiRo=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250517090120-462e75d27336/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a h1:Ho73vGiB94LmtK5T+tKVwtCNEi/YiHmPjlqpHSAmAVs=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
|
||||
|
||||
+37
-23
@@ -18,7 +18,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
@@ -42,16 +41,19 @@ type DialFn = func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
type Conn struct {
|
||||
initFn func() (io.ReadCloser, netAddr, error)
|
||||
writer io.Writer
|
||||
writer io.Writer // writer must not nil
|
||||
closer io.Closer
|
||||
netAddr
|
||||
|
||||
reader io.ReadCloser
|
||||
once sync.Once
|
||||
closed atomic.Bool
|
||||
err error
|
||||
remain int
|
||||
br *bufio.Reader
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
reader io.ReadCloser
|
||||
br *bufio.Reader
|
||||
remain int
|
||||
|
||||
closeMutex sync.Mutex
|
||||
closed bool
|
||||
|
||||
// deadlines
|
||||
deadline *time.Timer
|
||||
}
|
||||
@@ -65,7 +67,7 @@ type Config struct {
|
||||
func (g *Conn) initReader() {
|
||||
reader, addr, err := g.initFn()
|
||||
if err != nil {
|
||||
g.err = err
|
||||
g.initErr = err
|
||||
if closer, ok := g.writer.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
@@ -73,17 +75,21 @@ func (g *Conn) initReader() {
|
||||
}
|
||||
g.netAddr = addr
|
||||
|
||||
if !g.closed.Load() {
|
||||
g.reader = reader
|
||||
g.br = bufio.NewReader(reader)
|
||||
} else {
|
||||
reader.Close()
|
||||
g.closeMutex.Lock()
|
||||
defer g.closeMutex.Unlock()
|
||||
if g.closed { // if g.Close() be called between g.initFn(), direct close the initFn returned reader
|
||||
_ = reader.Close()
|
||||
g.initErr = net.ErrClosed
|
||||
return
|
||||
}
|
||||
|
||||
g.reader = reader
|
||||
g.br = bufio.NewReader(reader)
|
||||
}
|
||||
|
||||
func (g *Conn) Init() error {
|
||||
g.once.Do(g.initReader)
|
||||
return g.err
|
||||
g.initOnce.Do(g.initReader)
|
||||
return g.initErr
|
||||
}
|
||||
|
||||
func (g *Conn) Read(b []byte) (n int, err error) {
|
||||
@@ -100,8 +106,6 @@ func (g *Conn) Read(b []byte) (n int, err error) {
|
||||
n, err = io.ReadFull(g.br, b[:size])
|
||||
g.remain -= n
|
||||
return
|
||||
} else if g.reader == nil {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
|
||||
// 0x00 grpclength(uint32) 0x0A uleb128 payload
|
||||
@@ -147,8 +151,8 @@ func (g *Conn) Write(b []byte) (n int, err error) {
|
||||
buf.Write(b)
|
||||
|
||||
_, err = g.writer.Write(buf.Bytes())
|
||||
if err == io.ErrClosedPipe && g.err != nil {
|
||||
err = g.err
|
||||
if err == io.ErrClosedPipe && g.initErr != nil {
|
||||
err = g.initErr
|
||||
}
|
||||
|
||||
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||
@@ -170,8 +174,8 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
binary.PutUvarint(header[6:], uint64(dataLen))
|
||||
_, err := g.writer.Write(buffer.Bytes())
|
||||
|
||||
if err == io.ErrClosedPipe && g.err != nil {
|
||||
err = g.err
|
||||
if err == io.ErrClosedPipe && g.initErr != nil {
|
||||
err = g.initErr
|
||||
}
|
||||
|
||||
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||
@@ -186,7 +190,17 @@ func (g *Conn) FrontHeadroom() int {
|
||||
}
|
||||
|
||||
func (g *Conn) Close() error {
|
||||
g.closed.Store(true)
|
||||
g.initOnce.Do(func() { // if initReader not called, it should not be run anymore
|
||||
g.initErr = net.ErrClosed
|
||||
})
|
||||
|
||||
g.closeMutex.Lock()
|
||||
defer g.closeMutex.Unlock()
|
||||
if g.closed {
|
||||
return nil
|
||||
}
|
||||
g.closed = true
|
||||
|
||||
var errorArr []error
|
||||
|
||||
if reader := g.reader; reader != nil {
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
"read": {
|
||||
"file": {
|
||||
"/etc/init.d/ddns-go": [ "exec" ],
|
||||
"/usr/libexec/ddns-go-call": [ "exec" ],
|
||||
"/usr/libexec/ddns-go-call": [ "exec" ],
|
||||
"/bin/pidof": [ "exec" ],
|
||||
"/bin/ps": [ "exec" ],
|
||||
"/bin/ash": [ "exec" ],
|
||||
"/etc/ddns-go/ddns-go-config.yaml": [ "read" ],
|
||||
"/var/log/ddns-go.log": [ "read" ]
|
||||
},
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.0-beta.16
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-beta.15
|
||||
|
||||
* Add DERP service **1**
|
||||
|
||||
@@ -33,7 +33,9 @@ See [Listen Fields](/configuration/shared/listen/) for details.
|
||||
|
||||
==Required==
|
||||
|
||||
A mapping Object from HTTP endpoints to Shadowsocks inbound tags.
|
||||
A mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inbound/shadowsocks) tags.
|
||||
|
||||
Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ require (
|
||||
github.com/sagernet/gomobile v0.1.6
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||
github.com/sagernet/quic-go v0.51.0-beta.5
|
||||
github.com/sagernet/sing v0.6.10-0.20250505040842-ba62fee9470f
|
||||
github.com/sagernet/sing v0.6.10-0.20250520114755-0e0545dd92c4
|
||||
github.com/sagernet/sing-mux v0.3.2
|
||||
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
|
||||
+2
-2
@@ -168,8 +168,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
|
||||
github.com/sagernet/quic-go v0.51.0-beta.5 h1:/mME3sJvQ8k/JKP0oC/9XoWrm0znO7hWXviB5yiipJY=
|
||||
github.com/sagernet/quic-go v0.51.0-beta.5/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.10-0.20250505040842-ba62fee9470f h1:lttLhNtFuMItQcTD29QP6aBS8kR1UhG7zZ+pwzTYkFM=
|
||||
github.com/sagernet/sing v0.6.10-0.20250505040842-ba62fee9470f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.10-0.20250520114755-0e0545dd92c4 h1:mP53fzZ9GIFaT1pZ2brV1BhbaDwN8kG8ZR0OYr11EBc=
|
||||
github.com/sagernet/sing v0.6.10-0.20250520114755-0e0545dd92c4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3 h1:1J+s1yyZ8+YAYaClI+az8YuFgV9NGXUUCZnriKmos6w=
|
||||
|
||||
@@ -214,7 +214,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
if !loaded {
|
||||
return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
|
||||
}
|
||||
ruleSet.IncRef()
|
||||
inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
|
||||
}
|
||||
for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
|
||||
@@ -222,7 +221,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
if !loaded {
|
||||
return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
|
||||
}
|
||||
ruleSet.IncRef()
|
||||
inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
|
||||
}
|
||||
if options.AutoRedirect {
|
||||
@@ -312,7 +310,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
if len(ipSets) == 0 {
|
||||
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
|
||||
}
|
||||
routeRuleSet.DecRef()
|
||||
routeRuleSet.IncRef()
|
||||
t.routeAddressSet = append(t.routeAddressSet, ipSets...)
|
||||
if t.autoRedirect != nil {
|
||||
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
@@ -324,7 +322,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
if len(ipSets) == 0 {
|
||||
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
|
||||
}
|
||||
routeExcludeRuleSet.DecRef()
|
||||
routeExcludeRuleSet.IncRef()
|
||||
t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
|
||||
if t.autoRedirect != nil {
|
||||
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user