mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-30 13:21:16 +08:00
⛄ Sync 2026-06-30 01:11:33
This commit is contained in:
parent
fb3826f4ad
commit
a09508c01e
@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dae
|
||||
PKG_VERSION:=2026.06.14
|
||||
PKG_RELEASE:=12
|
||||
PKG_RELEASE:=13
|
||||
|
||||
PKG_SOURCE:=dae-src-2026.06.14-fa89ea7197a5.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
||||
|
||||
121
dae/patches/010-dns-response-ttl.patch
Normal file
121
dae/patches/010-dns-response-ttl.patch
Normal file
@ -0,0 +1,121 @@
|
||||
--- a/cmd/reload_manager.go
|
||||
+++ b/cmd/reload_manager.go
|
||||
@@ -411,0 +412,3 @@ func dnsConfigFingerprint(dns config.Dns
|
||||
+ b.WriteString("response_ttl=")
|
||||
+ b.WriteString(strconv.Itoa(dns.ResponseTtl))
|
||||
+ b.WriteByte(';')
|
||||
--- a/cmd/run_shutdown_test.go
|
||||
+++ b/cmd/run_shutdown_test.go
|
||||
@@ -605,0 +606 @@ func TestDNSConfigFingerprintCoversAllDn
|
||||
+ "ResponseTtl": {},
|
||||
--- a/config/config.go
|
||||
+++ b/config/config.go
|
||||
@@ -156,0 +157 @@ type Dns struct {
|
||||
+ ResponseTtl int `mapstructure:"response_ttl" default:"0"`
|
||||
--- a/config/desc.go
|
||||
+++ b/config/desc.go
|
||||
@@ -68,0 +69 @@ var DnsDesc = Desc{
|
||||
+ "response_ttl": "Override the TTL returned to downstream DNS clients. Zero keeps dae's default behavior.",
|
||||
--- a/control/control_plane.go
|
||||
+++ b/control/control_plane.go
|
||||
@@ -774,0 +775 @@ func NewControlPlane(
|
||||
+ plane.dnsResponseTtl = dnsConfig.ResponseTtl
|
||||
@@ -1273,0 +1275 @@ func (c *ControlPlane) dnsControllerOption() *DnsControllerOption {
|
||||
+ ResponseTtl: c.dnsResponseTtl,
|
||||
--- a/control/dns_control.go
|
||||
+++ b/control/dns_control.go
|
||||
@@ -80,0 +81 @@ type DnsControllerOption struct {
|
||||
+ ResponseTtl int
|
||||
@@ -96,0 +98 @@ type dnsControllerRuntimeState struct {
|
||||
+ responseTtl int
|
||||
@@ -174,0 +177,3 @@ func normalizeDnsRuntimeBehavior(option *DnsControllerOption) (qtypePrefer uint
|
||||
+ if option.ResponseTtl < 0 {
|
||||
+ return 0, false, 0, 0, fmt.Errorf("response_ttl must be greater than or equal to 0")
|
||||
+ }
|
||||
@@ -424,0 +430 @@ func (c *DnsController) TryUpdateRuntime(routing *dns.Dns, option *DnsControlle
|
||||
+ responseTtl: option.ResponseTtl,
|
||||
@@ -1550 +1556,7 @@ func (c *DnsController) NormalizeAndCacheDnsResp_(msg *dnsmessage.Msg, response
|
||||
- // For A/AAAA records, we set TTL to 0 to prevent downstream caching while we manage it.
|
||||
+ responseTtl := 0
|
||||
+ if rt := c.runtime(); rt != nil {
|
||||
+ responseTtl = rt.responseTtl
|
||||
+ }
|
||||
+
|
||||
+ // For A/AAAA records, we set TTL to 0 by default to prevent downstream caching while we manage it.
|
||||
+ // When response_ttl is set, use it as the downstream-visible TTL.
|
||||
@@ -1553 +1565 @@ func (c *DnsController) NormalizeAndCacheDnsResp_(msg *dnsmessage.Msg, response
|
||||
- msg.Answer[i].Header().Ttl = 0
|
||||
+ msg.Answer[i].Header().Ttl = uint32(responseTtl)
|
||||
--- /dev/null
|
||||
+++ b/control/dns_response_ttl_test.go
|
||||
@@ -0,0 +1,64 @@
|
||||
+/*
|
||||
+ * SPDX-License-Identifier: AGPL-3.0-only
|
||||
+ * Copyright (c) 2022-2026, daeuniverse Organization <dae@v2raya.org>
|
||||
+ */
|
||||
+
|
||||
+package control
|
||||
+
|
||||
+import (
|
||||
+ "net"
|
||||
+ "testing"
|
||||
+ "time"
|
||||
+
|
||||
+ dnsmessage "github.com/miekg/dns"
|
||||
+ "github.com/stretchr/testify/require"
|
||||
+)
|
||||
+
|
||||
+func TestDnsController_ResponseTtlOverride(t *testing.T) {
|
||||
+ for _, tt := range []struct {
|
||||
+ name string
|
||||
+ responseTtl int
|
||||
+ wantTtl uint32
|
||||
+ }{
|
||||
+ {name: "default_zero", responseTtl: 0, wantTtl: 0},
|
||||
+ {name: "override", responseTtl: 60, wantTtl: 60},
|
||||
+ } {
|
||||
+ t.Run(tt.name, func(t *testing.T) {
|
||||
+ ctrl := setTestDnsControllerRuntime(newTestDnsController(), func(rt *dnsControllerRuntimeState) {
|
||||
+ rt.responseTtl = tt.responseTtl
|
||||
+ rt.newCache = func(fqdn string, answers, ns, extra []dnsmessage.RR, deadline time.Time, originalDeadline time.Time) (*DnsCache, error) {
|
||||
+ return &DnsCache{
|
||||
+ Answer: answers,
|
||||
+ NS: ns,
|
||||
+ Extra: extra,
|
||||
+ Deadline: deadline,
|
||||
+ OriginalDeadline: originalDeadline,
|
||||
+ }, nil
|
||||
+ }
|
||||
+ })
|
||||
+
|
||||
+ msg := new(dnsmessage.Msg)
|
||||
+ msg.SetReply(&dnsmessage.Msg{
|
||||
+ Question: []dnsmessage.Question{{
|
||||
+ Name: "example.com.",
|
||||
+ Qtype: dnsmessage.TypeA,
|
||||
+ Qclass: dnsmessage.ClassINET,
|
||||
+ }},
|
||||
+ })
|
||||
+ msg.Answer = []dnsmessage.RR{
|
||||
+ &dnsmessage.A{
|
||||
+ Hdr: dnsmessage.RR_Header{
|
||||
+ Name: "example.com.",
|
||||
+ Rrtype: dnsmessage.TypeA,
|
||||
+ Class: dnsmessage.ClassINET,
|
||||
+ Ttl: 300,
|
||||
+ },
|
||||
+ A: net.IPv4(93, 184, 216, 34),
|
||||
+ },
|
||||
+ }
|
||||
+
|
||||
+ require.NoError(t, ctrl.NormalizeAndCacheDnsResp_(msg, "example.com.:1"))
|
||||
+ require.Equal(t, tt.wantTtl, msg.Answer[0].Header().Ttl)
|
||||
+ })
|
||||
+ }
|
||||
+}
|
||||
--- a/control/dns_runtime.go
|
||||
+++ b/control/dns_runtime.go
|
||||
@@ -22,0 +23 @@ type controlPlaneDNSRuntime struct {
|
||||
+ dnsResponseTtl int
|
||||
@@ -264,0 +266 @@ func (r *controlPlaneDNSRuntime) release() {
|
||||
+ r.dnsResponseTtl = 0
|
||||
@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=daed
|
||||
PKG_VERSION:=2026.06.14
|
||||
PKG_RELEASE:=14
|
||||
PKG_RELEASE:=15
|
||||
|
||||
PKG_SOURCE:=daed-src-2026.06.14-4e215048068a.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
||||
|
||||
@ -2,36 +2,17 @@ From: kenzok8 <kenzok8@noreply>
|
||||
Date: 2026-06-23
|
||||
Subject: [PATCH] daed: detach BPF hooks when stopping from dashboard
|
||||
|
||||
The dashboard "stop" (GraphQL Run(dry:true)) is delivered to dae-wing as a
|
||||
reload to EmptyConfig. The fast-reload path keeps the eBPF hooks attached via
|
||||
EjectBpf() (it only skips bpf.Close(), the TC/clsact filters stay bound). On a
|
||||
genuine stop this leaves the LAN/WAN clsact programs attached with no tproxy
|
||||
behind them, so every TCP/UDP packet (including DNS:53) is steered into a dead
|
||||
tproxy and black-holed; only `network restart`/reboot recovers. ICMP is not
|
||||
hijacked, so the box still pings IPs ("internal works, external dead").
|
||||
|
||||
dae-core already has DetachBpfHooks() for exactly this, but upstream only calls
|
||||
it from the standalone dae SIGTERM handler (dae-core/cmd/run.go), never on
|
||||
dae-wing's stop path. Call it explicitly when reloading to EmptyConfig.
|
||||
|
||||
Detach BPF hooks when the dashboard stops dae through EmptyConfig.
|
||||
Reported: https://github.com/kenzok8/openwrt-daede/issues/13
|
||||
---
|
||||
--- a/dae/run.go
|
||||
+++ b/dae/run.go
|
||||
@@ -138,6 +138,21 @@
|
||||
@@ -138,6 +138,13 @@
|
||||
/* dae-wing start */
|
||||
newConf := newReloadMsg.Config
|
||||
/* dae-wing end */
|
||||
+
|
||||
+ // openwrt-daede patch: a "stop" from the dashboard is delivered as a
|
||||
+ // reload to EmptyConfig. The fast-reload path keeps the eBPF hooks
|
||||
+ // attached via EjectBpf (only bpf.Close() is skipped, the TC filters
|
||||
+ // stay bound). On a real stop that leaves the LAN/WAN clsact programs
|
||||
+ // attached with no tproxy behind them, black-holing all TCP/UDP
|
||||
+ // (incl. DNS) until `network restart`/reboot. Upstream only calls
|
||||
+ // DetachBpfHooks() from the standalone dae SIGTERM handler, never on
|
||||
+ // dae-wing's stop path. Detach this generation's BPF hooks now so the
|
||||
+ // datapath is fully removed when stopping.
|
||||
+ // openwrt-daede: dashboard stop reloads to EmptyConfig; detach hooks.
|
||||
+ if newConf == EmptyConfig {
|
||||
+ if e := c.DetachBpfHooks(); e != nil {
|
||||
+ log.Errorln("[Stop] DetachBpfHooks:", e)
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
From: kenzok8 <kenzok8@noreply>
|
||||
Date: 2026-06-29
|
||||
Subject: [PATCH] daed: use bounded probes for manual latency tests
|
||||
|
||||
Use the existing bounded probe for manual dashboard latency tests.
|
||||
Reported: https://github.com/kenzok8/openwrt-daede/issues/20
|
||||
---
|
||||
--- a/graphql/service/node/latency_mutation_utils.go
|
||||
+++ b/graphql/service/node/latency_mutation_utils.go
|
||||
@@ -108,7 +108,7 @@ func testSingleNodeLatency(option *diale
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
- result, err := d.ProbeLatency()
|
||||
+ result, err := d.ProbeLatencyFast()
|
||||
if err != nil {
|
||||
msg := err.Error()
|
||||
resolver.MessageV = &msg
|
||||
@ -1,8 +1,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.5-20241208
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=1.0.5-r20241208
|
||||
PKG_RELEASE:=2
|
||||
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
|
||||
|
||||
LUCI_TITLE:=Easy Access AP / Modem
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.0-20250610
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.0-r20250610
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for arcadia
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.3-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.3-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for ChineseSubFinder
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.2-20251011
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.2-r20251011
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for CodeServer
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-daede
|
||||
PKG_VERSION:=1.14.7
|
||||
PKG_RELEASE:=24
|
||||
PKG_RELEASE:=25
|
||||
PKG_MAINTAINER:=kenzok8
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ const DEFAULT_TEMPLATE =
|
||||
'\n' +
|
||||
'dns {\n' +
|
||||
' ipversion_prefer: 4\n' +
|
||||
' response_ttl: 0\n' +
|
||||
' upstream {\n' +
|
||||
" cndns: 'udp://dns.alidns.com:53'\n" +
|
||||
" fallbackdns: 'tcp+udp://dns.google:53'\n" +
|
||||
@ -302,6 +303,11 @@ function renderDaeForms(ctx) {
|
||||
_('Resolves everything else.'));
|
||||
o.default = 'tcp+udp://dns.google:53';
|
||||
o.placeholder = 'tcp+udp://dns.google:53';
|
||||
o = s.option(form.Value, 'response_ttl', _('DNS response TTL'),
|
||||
_('TTL returned to LAN DNS clients. 0 keeps dae default behavior.'));
|
||||
o.datatype = 'uinteger';
|
||||
o.default = '0';
|
||||
o.placeholder = '60';
|
||||
|
||||
/* Logging — folded into the main form so the single Save button covers it
|
||||
too (no separate native save bar) */
|
||||
|
||||
@ -845,3 +845,9 @@ msgid ""
|
||||
"%s is running. Deleting the daens netns now will break its networking until "
|
||||
"you restart it. Continue?"
|
||||
msgstr ""
|
||||
|
||||
msgid "DNS response TTL"
|
||||
msgstr ""
|
||||
|
||||
msgid "TTL returned to LAN DNS clients. 0 keeps dae default behavior."
|
||||
msgstr ""
|
||||
|
||||
@ -574,6 +574,12 @@ msgstr "兜底 DNS 上游"
|
||||
msgid "Resolves everything else."
|
||||
msgstr "解析其余域名。"
|
||||
|
||||
msgid "DNS response TTL"
|
||||
msgstr "DNS 响应 TTL"
|
||||
|
||||
msgid "TTL returned to LAN DNS clients. 0 keeps dae default behavior."
|
||||
msgstr "返回给局域网 DNS 客户端的 TTL。0 表示保持 dae 默认行为。"
|
||||
|
||||
msgid "Apply failed: %s"
|
||||
msgstr "应用失败:%s"
|
||||
|
||||
|
||||
@ -574,6 +574,12 @@ msgstr "兜底 DNS 上游"
|
||||
msgid "Resolves everything else."
|
||||
msgstr "解析其余域名。"
|
||||
|
||||
msgid "DNS response TTL"
|
||||
msgstr "DNS 响应 TTL"
|
||||
|
||||
msgid "TTL returned to LAN DNS clients. 0 keeps dae default behavior."
|
||||
msgstr "返回给局域网 DNS 客户端的 TTL。0 表示保持 dae 默认行为。"
|
||||
|
||||
msgid "Apply failed: %s"
|
||||
msgstr "应用失败:%s"
|
||||
|
||||
|
||||
@ -97,6 +97,7 @@ if [ -f /etc/config/dae ]; then
|
||||
uci -q set dae.dns=dns
|
||||
uci -q set dae.dns.cn_upstream='udp://dns.alidns.com:53'
|
||||
uci -q set dae.dns.fallback_upstream='tcp+udp://dns.google:53'
|
||||
uci -q set dae.dns.response_ttl='0'
|
||||
fi
|
||||
# Migrate legacy group filters to the unified source / name_filter fields,
|
||||
# then drop the old keys so the form shows real, editable values (the old
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
# config group name/policy, list source, list name_filter
|
||||
# (old list filter_sub/filter_node still read as fallback)
|
||||
# config routing 'routing' private_direct/cn_direct/block_ads/fallback, list custom
|
||||
# config dns 'dns' cn_upstream/fallback_upstream
|
||||
# config dns 'dns' cn_upstream/fallback_upstream/response_ttl
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
@ -213,7 +213,7 @@ generate() {
|
||||
fi
|
||||
|
||||
# routing / dns singletons
|
||||
local private_direct cn_direct block_ads fallback cn_up fb_up
|
||||
local private_direct cn_direct block_ads fallback cn_up fb_up response_ttl
|
||||
config_get_bool private_direct routing private_direct 1
|
||||
config_get_bool cn_direct routing cn_direct 1
|
||||
config_get_bool block_ads routing block_ads 0
|
||||
@ -222,8 +222,12 @@ generate() {
|
||||
fallback="$(sanitize "$fallback")"
|
||||
config_get cn_up dns cn_upstream "udp://dns.alidns.com:53"
|
||||
config_get fb_up dns fallback_upstream "tcp+udp://dns.google:53"
|
||||
config_get response_ttl dns response_ttl "0"
|
||||
cn_up="$(sanitize "$cn_up")"
|
||||
fb_up="$(sanitize "$fb_up")"
|
||||
case "$response_ttl" in
|
||||
''|*[!0-9]*) response_ttl=0 ;;
|
||||
esac
|
||||
|
||||
{
|
||||
echo "# 本配置由 luci-app-daede 表单自动生成,再次保存表单会覆盖你的手动修改。"
|
||||
@ -258,6 +262,7 @@ generate() {
|
||||
|
||||
echo "dns {"
|
||||
echo " ipversion_prefer: 4"
|
||||
[ "$response_ttl" -gt 0 ] && echo " response_ttl: ${response_ttl}"
|
||||
echo " upstream {"
|
||||
echo " cndns: '${cn_up}'"
|
||||
echo " fallbackdns: '${fb_up}'"
|
||||
|
||||
@ -2,7 +2,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: luci-app-ddns 2.4.0-1\n"
|
||||
"POT-Creation-Date: 2016-01-30 11:07+0100\n"
|
||||
"PO-Revision-Date: 2026-04-20 17:56+0000\n"
|
||||
"PO-Revision-Date: 2026-06-09 01:01+0000\n"
|
||||
"Last-Translator: Mytai20100 <mytai232746@gmail.com>\n"
|
||||
"Language-Team: Vietnamese <https://hosted.weblate.org/projects/openwrt/"
|
||||
"luciapplicationsddns/vi/>\n"
|
||||
@ -11,7 +11,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.17.1-dev\n"
|
||||
"X-Generator: Weblate 2026.6\n"
|
||||
|
||||
#: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:423
|
||||
msgid "\"../\" not allowed in path for Security Reason."
|
||||
@ -208,7 +208,7 @@ msgstr "Kích hoạt giao tiếp an toàn với nhà cung cấp DDNS"
|
||||
#: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:612
|
||||
#: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:1140
|
||||
msgid "Enabled"
|
||||
msgstr "Kích Hoạt"
|
||||
msgstr "Đã bật"
|
||||
|
||||
#: applications/luci-app-ddns/htdocs/luci-static/resources/view/ddns/overview.js:966
|
||||
msgid "Error"
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.2.0-20250625
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.2.0-r20250625
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for demon
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2026-05-22 21:12+0000\n"
|
||||
"PO-Revision-Date: 2026-06-11 08:15+0000\n"
|
||||
"Last-Translator: Pavel Borecki <pavel.borecki@gmail.com>\n"
|
||||
"Language-Team: Czech <https://hosted.weblate.org/projects/openwrt/"
|
||||
"luciapplicationsdockerman/cs/>\n"
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);\n"
|
||||
"X-Generator: Weblate 2026.6.dev0\n"
|
||||
"X-Generator: Weblate 2026.6\n"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505
|
||||
msgid "/mnt/path"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2026-06-05 19:02+0000\n"
|
||||
"PO-Revision-Date: 2026-06-22 22:15+0000\n"
|
||||
"Last-Translator: Franco Castillo <castillofrancodamian@gmail.com>\n"
|
||||
"Language-Team: Spanish <https://hosted.weblate.org/projects/openwrt/"
|
||||
"luciapplicationsdockerman/es/>\n"
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 2026.6\n"
|
||||
"X-Generator: Weblate 2026.7.dev0\n"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505
|
||||
msgid "/mnt/path"
|
||||
@ -2309,7 +2309,7 @@ msgstr "detener"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:756
|
||||
msgid "syslog"
|
||||
msgstr "syslog"
|
||||
msgstr "Registro de eventos (Syslog)"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/dockerman/common.js:364
|
||||
msgid "tag"
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2026-04-18 17:10+0000\n"
|
||||
"Last-Translator: Emin Tufan Çetin <etcetin@gmail.com>\n"
|
||||
"PO-Revision-Date: 2026-06-09 01:01+0000\n"
|
||||
"Last-Translator: Oğuz Ersen <oguz@ersen.moe>\n"
|
||||
"Language-Team: Turkish <https://hosted.weblate.org/projects/openwrt/"
|
||||
"luciapplicationsdockerman/tr/>\n"
|
||||
"Language: tr\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.17.1-dev\n"
|
||||
"X-Generator: Weblate 2026.6\n"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505
|
||||
msgid "/mnt/path"
|
||||
@ -1928,7 +1928,7 @@ msgstr ""
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:45
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/volumes.js:111
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
msgstr "Toplam"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:170
|
||||
msgid "Total Memory"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"PO-Revision-Date: 2026-04-20 17:56+0000\n"
|
||||
"PO-Revision-Date: 2026-06-09 01:01+0000\n"
|
||||
"Last-Translator: Mytai20100 <mytai232746@gmail.com>\n"
|
||||
"Language-Team: Vietnamese <https://hosted.weblate.org/projects/openwrt/"
|
||||
"luciapplicationsdockerman/vi/>\n"
|
||||
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.17.1-dev\n"
|
||||
"X-Generator: Weblate 2026.6\n"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:505
|
||||
msgid "/mnt/path"
|
||||
@ -251,7 +251,7 @@ msgstr ""
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1189
|
||||
msgid "Clear"
|
||||
msgstr ""
|
||||
msgstr "Xóa"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:617
|
||||
msgid "Click to add a new tag to this image"
|
||||
@ -1451,7 +1451,7 @@ msgstr ""
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:396
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1805
|
||||
msgid "Pause"
|
||||
msgstr ""
|
||||
msgstr "Tạm dừng"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/containers.js:207
|
||||
msgid "Pause this container"
|
||||
@ -1726,7 +1726,7 @@ msgstr ""
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:423
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/images.js:599
|
||||
msgid "Size"
|
||||
msgstr "Dung lượng"
|
||||
msgstr "Kích thước"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/configuration.js:82
|
||||
msgid ""
|
||||
@ -1945,7 +1945,7 @@ msgstr ""
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:63
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/events.js:249
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
msgstr "Loại"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1135
|
||||
msgid "Type command here... (Ctrl+D to detach)"
|
||||
@ -1963,7 +1963,7 @@ msgstr ""
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1639
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:1682
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
msgstr "Lỗi không xác định"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container.js:485
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:250
|
||||
@ -2035,7 +2035,7 @@ msgstr ""
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/overview.js:240
|
||||
msgid "Version"
|
||||
msgstr ""
|
||||
msgstr "Phiên bản"
|
||||
|
||||
#: applications/luci-app-dockerman/htdocs/luci-static/resources/view/dockerman/container_new.js:374
|
||||
msgid "Volume (named)"
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
# Updated: 2025-10-09
|
||||
PKG_VERSION:=1.0.0-20251009
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.0-r20251009
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for DPanel
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.0-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.0-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for DrawIO
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.3-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.3-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for Emby
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.0-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.0-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for Excalidraw
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.2-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.2-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for FeiShuVpn
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.3-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.3-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for Gogs
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.1.2-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.1.2-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for heimdall
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.0-20240101
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=1.0.0-r20240101
|
||||
PKG_RELEASE:=2
|
||||
|
||||
LUCI_TITLE:=LuCI support for Hermes
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.1.4-20250321
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.1.4-r20250321
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for homeassistant
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.2-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.2-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for HTReader
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.2-20250321
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.2-r20250321
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for Immich
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.1.7-20241211
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.1.7-r20241211
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for istoredup
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.6-20250406
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.6-r20250406
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for 1Panel
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.4-20250321
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.4-r20250321
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for ITTools
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.1.1-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.1.1-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for jackett
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.2.2-20250321
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.2.2-r20250321
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for jellyfin
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.1-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.1-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for LANraragi
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.2-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.2-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for Memos
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.2-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.2-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for MTPhotos
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -10,8 +10,8 @@ LUCI_TITLE:=multi account Virtual WAN config generator
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DEPENDS:=+kmod-macvlan +luci-app-mwan3
|
||||
|
||||
PKG_VERSION:=2.2-2
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=2.2-r2
|
||||
PKG_RELEASE:=2
|
||||
|
||||
define Package/luci-app-multiaccountdial/conffiles
|
||||
/etc/config/multiaccountdial
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.1-20221119
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=1.0.1-r20221119
|
||||
PKG_RELEASE:=2
|
||||
PKG_MAINTAINER:=xiaobao <xiaobao@linkease.com>
|
||||
|
||||
LUCI_TITLE:=LuCI support for mymind
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.1.3-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.1.3-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for nastools
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.1-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.1-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for Navidrome
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.1.1-20240822
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.1.1-r20240822
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for nextcloud
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.1-20250403
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.1-r20250403
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for OneAPI
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-openclash
|
||||
PKG_VERSION:=0.47.097
|
||||
PKG_RELEASE:=4
|
||||
PKG_VERSION:=0.47.110
|
||||
PKG_RELEASE:=5
|
||||
PKG_MAINTAINER:=vernesong <https://github.com/vernesong/OpenClash>
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
@ -59,28 +59,6 @@ define Build/Prepare
|
||||
po2lmo $(po) $(PKG_BUILD_DIR)/$(patsubst %.po,%.lmo,$(notdir $(po)));)
|
||||
chmod 0755 $(PKG_BUILD_DIR)/root/etc/init.d/openclash
|
||||
chmod -R 0755 $(PKG_BUILD_DIR)/root/usr/share/openclash/
|
||||
mkdir -p $(PKG_BUILD_DIR)/root/etc/openclash/config
|
||||
mkdir -p $(PKG_BUILD_DIR)/root/etc/openclash/rule_provider
|
||||
mkdir -p $(PKG_BUILD_DIR)/root/etc/openclash/proxy_provider
|
||||
mkdir -p $(PKG_BUILD_DIR)/root/etc/openclash/core
|
||||
mkdir -p $(PKG_BUILD_DIR)/root/usr/share/openclash/backup/overwrite
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/config/openclash" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_rules.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_rules.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_rules_2.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_rules_2.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_hosts.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_hosts.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_fake_filter.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_fake_filter.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_domain_dns.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_domain_dns.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_domain_dns_policy.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_domain_dns_policy.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_proxy_server_dns_policy.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_fallback_filter.yaml" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_fallback_filter.yaml" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_sniffer.yaml" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_sniffer.yaml" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_localnetwork_ipv4.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_localnetwork_ipv6.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_chnroute_pass.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_chnroute_pass.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_chnroute6_pass.list" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_chnroute6_pass.list" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_firewall_rules.sh" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_firewall_rules.sh" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/custom/openclash_custom_overwrite.sh" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/openclash_custom_overwrite.sh" >/dev/null 2>&1
|
||||
cp -f "$(PKG_BUILD_DIR)/root/etc/openclash/overwrite/default" "$(PKG_BUILD_DIR)/root/usr/share/openclash/backup/overwrite/default" >/dev/null 2>&1
|
||||
exit 0
|
||||
endef
|
||||
|
||||
@ -144,6 +122,7 @@ define Package/$(PKG_NAME)/postrm
|
||||
rm -rf /tmp/etc/openclash >/dev/null 2>&1
|
||||
rm -rf /tmp/openclash_announcement >/dev/null 2>&1
|
||||
rm -rf /www/luci-static/resources/openclash >/dev/null 2>&1
|
||||
rm -rf /tmp/oix* >/dev/null 2>&1
|
||||
sed -i '/OpenClash Append/,/OpenClash Append End/d' "/usr/lib/lua/luci/model/network.lua" >/dev/null 2>&1
|
||||
sed -i '/.*kB maximum content size*/c\HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size' /usr/lib/lua/luci/http.lua >/dev/null 2>&1
|
||||
sed -i '/.*kB maximum content size*/c\export let HTTP_MAX_CONTENT = 1024*100; // 100 kB maximum content size' /usr/share/ucode/luci/http.uc >/dev/null 2>&1
|
||||
|
||||
@ -95,12 +95,20 @@ function index()
|
||||
entry({"admin", "services", "openclash", "config_file_save"}, call("action_config_file_save"))
|
||||
entry({"admin", "services", "openclash", "upload_config"}, call("action_upload_config"))
|
||||
entry({"admin", "services", "openclash", "add_subscription"}, call("action_add_subscription"))
|
||||
entry({"admin", "services", "openclash", "generate_age_key"}, call("action_generate_age_key"))
|
||||
entry({"admin", "services", "openclash", "cal_age_public_key"}, call("action_cal_age_public_key"))
|
||||
entry({"admin", "services", "openclash", "add_age_config"}, call("action_add_age_config"))
|
||||
entry({"admin", "services", "openclash", "upload_overwrite"}, call("action_upload_overwrite"))
|
||||
entry({"admin", "services", "openclash", "overwrite_subscribe_info"}, call("action_overwrite_subscribe_info"))
|
||||
entry({"admin", "services", "openclash", "overwrite_file_list"}, call("action_overwrite_file_list"))
|
||||
entry({"admin", "services", "openclash", "delete_overwrite_file"}, call("delete_overwrite_file"))
|
||||
entry({"admin", "services", "openclash", "get_subscribe_data"}, call("action_get_subscribe_data"))
|
||||
entry({"admin", "services", "openclash", "get_subscribe_info_data"}, call("action_get_subscribe_info_data"))
|
||||
entry({"admin", "services", "openclash", "oix_info"}, call("oix_info"))
|
||||
entry({"admin", "services", "openclash", "oix_checkin"}, call("oix_checkin"))
|
||||
entry({"admin", "services", "openclash", "oix_logout"}, call("oix_logout"))
|
||||
entry({"admin", "services", "openclash", "oix_login"}, call("oix_login"))
|
||||
entry({"admin", "services", "openclash", "oix_login_info_save"}, call("oix_login_info_save"))
|
||||
end
|
||||
|
||||
local fs = require "luci.openclash"
|
||||
@ -176,22 +184,14 @@ local function startlog()
|
||||
return info
|
||||
end
|
||||
|
||||
local function pkg_type()
|
||||
if fs.access("/usr/bin/apk") then
|
||||
return "apk"
|
||||
else
|
||||
return "opkg"
|
||||
end
|
||||
end
|
||||
|
||||
local function coremodel()
|
||||
if opkg and opkg.info("libc") and opkg.info("libc")["libc"] then
|
||||
return opkg.info("libc")["libc"]["Architecture"]
|
||||
else
|
||||
if pkg_type() == "opkg" then
|
||||
if fs.pkg_type() == "opkg" then
|
||||
return luci.sys.exec("rm -f /var/lock/opkg.lock && opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' 2>/dev/null")
|
||||
else
|
||||
return luci.sys.exec("apk list libc 2>/dev/null |awk '{print $2}'")
|
||||
return luci.sys.exec("rm -f /lib/apk/db/lock && apk list libc 2>/dev/null |awk '{print $2}'")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -220,8 +220,12 @@ end
|
||||
local function corelv()
|
||||
local core_meta_lv = ""
|
||||
local core_smart_enable = fs.uci_get_config("config", "smart_enable") or "0"
|
||||
local core_type = fs.uci_get_config("config", "core_type") or "Meta"
|
||||
local oix_token = fs.uci_get_config("config", "oix_token") or ""
|
||||
if fs.access("/tmp/clash_last_version") then
|
||||
if core_smart_enable == "1" then
|
||||
if core_type == "Oix" or oix_token ~= "" then
|
||||
core_meta_lv = luci.sys.exec("sed -n 1p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
|
||||
elseif core_smart_enable == "1" then
|
||||
core_meta_lv = luci.sys.exec("sed -n 2p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
|
||||
else
|
||||
core_meta_lv = luci.sys.exec("sed -n 1p /tmp/clash_last_version 2>/dev/null |tr -d '\n'")
|
||||
@ -240,10 +244,10 @@ local function opcv()
|
||||
if info and info["luci-app-openclash"] and info["luci-app-openclash"]["Version"] and info["luci-app-openclash"]["Installed-Time"] then
|
||||
v = info["luci-app-openclash"]["Version"]
|
||||
else
|
||||
if pkg_type() == "opkg" then
|
||||
if fs.pkg_type() == "opkg" then
|
||||
v = luci.sys.exec("rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' |tr -d '\n'")
|
||||
else
|
||||
v = luci.sys.exec("apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\\.[0-9]+)*' | head -1 |tr -d '\n'")
|
||||
v = luci.sys.exec("rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\\.[0-9]+)*' | head -1 |tr -d '\n'")
|
||||
end
|
||||
end
|
||||
if v and v ~= "" then
|
||||
@ -394,14 +398,16 @@ function action_restore_config()
|
||||
uci:commit("openclash")
|
||||
luci.sys.call("mkdir -p /etc/openclash/custom >/dev/null 2>&1")
|
||||
luci.sys.call("mkdir -p /etc/openclash/overwrite >/dev/null 2>&1")
|
||||
luci.sys.call("mkdir -p /etc/openclash/rule_provider >/dev/null 2>&1")
|
||||
luci.sys.call("/etc/init.d/openclash stop >/dev/null 2>&1")
|
||||
luci.sys.call("cp /usr/share/openclash/backup/openclash /etc/config/openclash >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp /usr/share/openclash/backup/openclash_custom* /etc/openclash/custom/ >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp /usr/share/openclash/backup/openclash_force_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp /usr/share/openclash/backup/openclash_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp /usr/share/openclash/backup/china_ip_route.ipset /etc/openclash/china_ip_route.ipset >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp /usr/share/openclash/backup/china_ip6_route.ipset /etc/openclash/china_ip6_route.ipset >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp /usr/share/openclash/backup/overwrite/default /etc/openclash/overwrite/default >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/openclash /etc/config/openclash >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/openclash_custom* /etc/openclash/custom/ >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/openclash_force_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/openclash_sniffing* /etc/openclash/custom/ >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/china_ip_route.ipset /etc/openclash/china_ip_route.ipset >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/china_ip6_route.ipset /etc/openclash/china_ip6_route.ipset >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/overwrite/default /etc/openclash/overwrite/default >/dev/null 2>&1 &")
|
||||
luci.sys.call("cp -f /usr/share/openclash/backup/oc-cn-domain.mrs /etc/openclash/rule_provider/oc-cn-domain.mrs >/dev/null 2>&1 &")
|
||||
luci.sys.call("rm -rf /etc/openclash/history/* >/dev/null 2>&1 &")
|
||||
end
|
||||
|
||||
@ -943,12 +949,12 @@ local a={' B/S',' KB/S',' MB/S',' GB/S',' TB/S',' PB/S'}
|
||||
if (e<=1024) then
|
||||
return e..a[1]
|
||||
else
|
||||
repeat
|
||||
e=e/1024
|
||||
t=t+1
|
||||
until(e<=1024)
|
||||
return string.format("%.1f",e)..a[t]
|
||||
end
|
||||
repeat
|
||||
e=e/1024
|
||||
t=t+1
|
||||
until(e<=1024)
|
||||
return string.format("%.1f",e)..a[t]
|
||||
end
|
||||
end
|
||||
|
||||
function action_toolbar_show_sys()
|
||||
@ -1283,7 +1289,7 @@ function action_update_ma()
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
oplv = oplv(),
|
||||
pkg_type = pkg_type(),
|
||||
pkg_type = fs.pkg_type(),
|
||||
corelv = corelv(),
|
||||
corever = corever();
|
||||
})
|
||||
@ -1357,18 +1363,19 @@ function action_refresh_log()
|
||||
local core_pattern = "level=|^time="
|
||||
local limit = 1000
|
||||
local start_line = (log_len > 0 and total_lines > log_len) and (log_len + 1) or 1
|
||||
local read_count = math.max(0, total_lines - start_line + 1)
|
||||
local core_cmd, oc_cmd, core_raw, oc_raw
|
||||
local core_logs = {}
|
||||
local oc_logs = {}
|
||||
|
||||
core_cmd = string.format(
|
||||
"tail -n +%d '%s' | grep -v -E '%s' | grep -E '%s' | tail -n %d",
|
||||
start_line, logfile, exclude_pattern, core_pattern, limit
|
||||
"tail -n +%d '%s' | head -n %d | grep -v -E '%s' | grep -E '%s' | tail -n %d",
|
||||
start_line, logfile, read_count, exclude_pattern, core_pattern, limit
|
||||
)
|
||||
|
||||
oc_cmd = string.format(
|
||||
"tail -n +%d '%s' | grep -v -E '%s' | grep -v -E '%s' | tail -n %d",
|
||||
start_line, logfile, exclude_pattern, core_pattern, limit
|
||||
"tail -n +%d '%s' | head -n %d | grep -v -E '%s' | grep -v -E '%s' | tail -n %d",
|
||||
start_line, logfile, read_count, exclude_pattern, core_pattern, limit
|
||||
)
|
||||
|
||||
if core_refresh then
|
||||
@ -1676,6 +1683,13 @@ function rename_file()
|
||||
uci:set("openclash", s[".name"], "config", new_file_name)
|
||||
end
|
||||
end)
|
||||
|
||||
uci:foreach("openclash", "config_age_secret",
|
||||
function(s)
|
||||
if s.name == fs.filename(old_file_name) and fs.filename(new_file_name) ~= new_file_name then
|
||||
uci:set("openclash", s[".name"], "name", fs.filename(new_file_name))
|
||||
end
|
||||
end)
|
||||
|
||||
uci:commit("openclash")
|
||||
end
|
||||
@ -1801,13 +1815,7 @@ function trans_line(data)
|
||||
end
|
||||
|
||||
function process_status(name)
|
||||
local ps_version = luci.sys.exec("ps --version 2>&1 |grep -c procps-ng |tr -d '\n'")
|
||||
local cmd
|
||||
if ps_version == "1" then
|
||||
cmd = string.format("ps -efw |grep '%s' |grep -v grep", name)
|
||||
else
|
||||
cmd = string.format("ps -w |grep '%s' |grep -v grep", name)
|
||||
end
|
||||
local cmd = string.format("%s |grep '%s' |grep -v grep", fs.ps_cmd(), name)
|
||||
local result = luci.sys.exec(cmd)
|
||||
return result ~= nil and result ~= "" and not result:match("^%s*$")
|
||||
end
|
||||
@ -1868,7 +1876,7 @@ function action_myip_check()
|
||||
url = string.format("http://myip.ipip.net?z=%d", random),
|
||||
parser = function(data)
|
||||
if data and data ~= "" then
|
||||
local ip = string.match(data, "当前 IP:([%d%.]+)")
|
||||
local ip = string.match(data, "当前 IP:([%x:%.]+)")
|
||||
local geo = string.match(data, "来自于:(.+)")
|
||||
|
||||
if ip and geo then
|
||||
@ -2099,6 +2107,8 @@ function latency_test(addr)
|
||||
local urls = {}
|
||||
if addr == "raw.githubusercontent.com" then
|
||||
table.insert(urls, "https://raw.githubusercontent.com/vernesong/OpenClash/dev/img/logo.png")
|
||||
elseif addr:match("jsdelivr%.net$") then
|
||||
table.insert(urls, "https://" .. addr .. "/gh/vernesong/OpenClash@dev/img/logo.png")
|
||||
else
|
||||
table.insert(urls, "https://" .. addr .. "/favicon.ico")
|
||||
end
|
||||
@ -2910,6 +2920,11 @@ local function is_safe_filename(filename)
|
||||
return filename and filename:match("^[%w%._%-]+$") and not filename:match("^%.")
|
||||
end
|
||||
|
||||
local function kill_process()
|
||||
local cmd = string.format("%s |grep -E 'openclash|clash' |grep -v grep |awk '{print $1}' |xargs -r kill -9 >/dev/null 2>&1", fs.ps_cmd())
|
||||
luci.sys.call(cmd)
|
||||
end
|
||||
|
||||
function action_oc_action()
|
||||
local action = luci.http.formvalue("action")
|
||||
local config_file = luci.http.formvalue("config_file")
|
||||
@ -2937,7 +2952,7 @@ function action_oc_action()
|
||||
uci:commit("openclash")
|
||||
end
|
||||
if not is_running() then
|
||||
luci.sys.call("ps | grep openclash | grep -v grep | awk '{print $1}' | xargs -r kill -9 >/dev/null 2>&1")
|
||||
kill_process()
|
||||
luci.sys.call("/etc/init.d/openclash start >/dev/null 2>&1")
|
||||
else
|
||||
luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1")
|
||||
@ -2947,14 +2962,14 @@ function action_oc_action()
|
||||
uci:set("openclash", "config", "enable", "0")
|
||||
uci:commit("openclash")
|
||||
end
|
||||
luci.sys.call("ps | grep openclash | grep -v grep | awk '{print $1}' | xargs -r kill -9 >/dev/null 2>&1")
|
||||
kill_process()
|
||||
luci.sys.call("/etc/init.d/openclash stop >/dev/null 2>&1")
|
||||
elseif action == "restart" then
|
||||
if uci:get("openclash", "config", "enable") ~= "1" then
|
||||
uci:set("openclash", "config", "enable", "1")
|
||||
uci:commit("openclash")
|
||||
end
|
||||
luci.sys.call("ps | grep openclash | grep -v grep | awk '{print $1}' | xargs -r kill -9 >/dev/null 2>&1")
|
||||
kill_process()
|
||||
luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1")
|
||||
else
|
||||
luci.http.status(500, "Invalid action parameter")
|
||||
@ -2967,6 +2982,7 @@ end
|
||||
|
||||
function action_config_file_list()
|
||||
local config_files = {}
|
||||
local age_files = {}
|
||||
local current_config = ""
|
||||
|
||||
local config_path = fs.uci_get_config("config", "config_path")
|
||||
@ -2974,6 +2990,19 @@ function action_config_file_list()
|
||||
current_config = config_path
|
||||
end
|
||||
|
||||
uci:foreach("openclash", "config_age_secret", function(a)
|
||||
if a.name and (a.secret or a.public) then
|
||||
table.insert(age_files, {
|
||||
name = a.name,
|
||||
secret = a.secret or "",
|
||||
public = a.public or ""
|
||||
})
|
||||
else
|
||||
uci:delete("openclash", "config_age_secret", a[".name"])
|
||||
uci:commit("openclash")
|
||||
end
|
||||
end)
|
||||
|
||||
local config_dir = "/etc/openclash/config/"
|
||||
if fs.access(config_dir) then
|
||||
local files = fs.dir(config_dir)
|
||||
@ -2981,13 +3010,29 @@ function action_config_file_list()
|
||||
for _, file in ipairs(files) do
|
||||
local full_path = config_dir .. file
|
||||
local stat = fs.stat(full_path)
|
||||
local name_no_ext = file:match("^(.*)%.ya?ml$")
|
||||
if stat and stat.type == "regular" then
|
||||
if name_no_ext then
|
||||
local cfile = io.open(full_path,"r")
|
||||
if cfile then
|
||||
local content = cfile:read(1024)
|
||||
local age_symbol = content:find("BEGIN AGE ENCRYPTED FILE")
|
||||
for _, age in pairs(age_files) do
|
||||
if age.name == name_no_ext and age.secret and age_symbol then
|
||||
stat.age = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
cfile:close()
|
||||
end
|
||||
if string.match(file, "%.ya?ml$") then
|
||||
table.insert(config_files, {
|
||||
name = file,
|
||||
path = full_path,
|
||||
size = stat.size,
|
||||
mtime = stat.mtime
|
||||
mtime = stat.mtime,
|
||||
age = stat.age or false
|
||||
})
|
||||
end
|
||||
end
|
||||
@ -3114,9 +3159,13 @@ end
|
||||
|
||||
function action_config_file_read()
|
||||
local config_file = luci.http.formvalue("config_file")
|
||||
luci.http.prepare_content("application/json")
|
||||
|
||||
if not config_file then
|
||||
luci.http.status(500, "Missing config_file parameter")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Missing config_file parameter"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
@ -3132,7 +3181,6 @@ function action_config_file_read()
|
||||
end
|
||||
|
||||
if not allow then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Invalid config file path"
|
||||
@ -3141,7 +3189,6 @@ function action_config_file_read()
|
||||
end
|
||||
|
||||
if not fs.access(config_file) then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "success",
|
||||
content = "",
|
||||
@ -3158,7 +3205,6 @@ function action_config_file_read()
|
||||
|
||||
local stat = fs.stat(config_file)
|
||||
if not stat or stat.type ~= "regular" then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Config file is not a regular file"
|
||||
@ -3167,7 +3213,6 @@ function action_config_file_read()
|
||||
end
|
||||
|
||||
if stat.size > 10 * 1024 * 1024 then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Config file too large (max 10MB)"
|
||||
@ -3177,7 +3222,6 @@ function action_config_file_read()
|
||||
|
||||
local content = fs.readfile(config_file)
|
||||
if content == nil then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Failed to read config file"
|
||||
@ -3185,7 +3229,6 @@ function action_config_file_read()
|
||||
return
|
||||
end
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "success",
|
||||
content = content,
|
||||
@ -3202,17 +3245,24 @@ end
|
||||
function action_config_file_save()
|
||||
local config_file = luci.http.formvalue("config_file")
|
||||
local content = luci.http.formvalue("content")
|
||||
luci.http.prepare_content("application/json")
|
||||
if content then
|
||||
content = content:gsub("\r\n", "\n"):gsub("\r", "\n")
|
||||
end
|
||||
|
||||
if not config_file then
|
||||
luci.http.status(500, "Missing config_file parameter")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Missing config_file parameter"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if not content then
|
||||
luci.http.status(500, "Missing content parameter")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Missing content parameter"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
@ -3228,7 +3278,6 @@ function action_config_file_save()
|
||||
end
|
||||
else
|
||||
if not (config_file == "/etc/openclash/custom/openclash_custom_overwrite.sh" or (config_file:match("^/etc/openclash/overwrite/[^/]+$") and not string.find(config_file, "%.%."))) then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Invalid overwrite file path"
|
||||
@ -3238,7 +3287,6 @@ function action_config_file_save()
|
||||
end
|
||||
|
||||
if string.len(content) > 10 * 1024 * 1024 then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Content too large (max 10MB)"
|
||||
@ -3251,7 +3299,6 @@ function action_config_file_save()
|
||||
backup_file = config_file .. ".backup." .. os.time()
|
||||
local backup_success = luci.sys.call(string.format("cp '%s' '%s'", config_file, backup_file))
|
||||
if backup_success ~= 0 then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Failed to create backup file"
|
||||
@ -3266,7 +3313,6 @@ function action_config_file_save()
|
||||
luci.sys.call(string.format("mv '%s' '%s'", backup_file, config_file))
|
||||
end
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Failed to write config file"
|
||||
@ -3280,7 +3326,6 @@ function action_config_file_save()
|
||||
luci.sys.call(string.format("mv '%s' '%s'", backup_file, config_file))
|
||||
end
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "File write verification failed"
|
||||
@ -3316,7 +3361,6 @@ function action_config_file_save()
|
||||
}
|
||||
end
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({
|
||||
status = "success",
|
||||
message = "Config file saved successfully",
|
||||
@ -3332,6 +3376,7 @@ function action_add_subscription()
|
||||
local sub_convert = luci.http.formvalue("sub_convert") or "0"
|
||||
local convert_address = luci.http.formvalue("convert_address") or ""
|
||||
local template = luci.http.formvalue("template") or ""
|
||||
local custom_template_url = luci.http.formvalue("custom_template_url") or ""
|
||||
local emoji = luci.http.formvalue("emoji") or "false"
|
||||
local udp = luci.http.formvalue("udp") or "false"
|
||||
local skip_cert_verify = luci.http.formvalue("skip_cert_verify") or "false"
|
||||
@ -3342,20 +3387,19 @@ function action_add_subscription()
|
||||
local keyword = luci.http.formvalue("keyword") or ""
|
||||
local ex_keyword = luci.http.formvalue("ex_keyword") or ""
|
||||
local de_ex_keyword = luci.http.formvalue("de_ex_keyword") or ""
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
|
||||
if not name or not address then
|
||||
if not name then
|
||||
luci.http.write_json({
|
||||
status = "error",
|
||||
message = "Missing name or address parameter"
|
||||
message = "Missing name parameter"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local is_valid_url = false
|
||||
|
||||
if sub_convert == "1" then
|
||||
if address and address ~= "" and sub_convert == "1" then
|
||||
local prefixed_http_pattern = "^[^,%s]+,https?://.+"
|
||||
local encoded_prefixed_http_pattern = "^[^%%%s]+%%2[Cc]https?%%3[Aa]%%2[Ff]%%2[Ff].+"
|
||||
|
||||
@ -3390,10 +3434,12 @@ function action_add_subscription()
|
||||
is_valid_url = true
|
||||
end
|
||||
end
|
||||
else
|
||||
elseif address and address ~= "" then
|
||||
if string.find(address, "^https?://") and not string.find(address, "\n") and not string.find(address, "|") then
|
||||
is_valid_url = true
|
||||
end
|
||||
else
|
||||
is_valid_url = true
|
||||
end
|
||||
|
||||
if not is_valid_url then
|
||||
@ -3451,15 +3497,25 @@ function action_add_subscription()
|
||||
|
||||
if section_id then
|
||||
uci:set("openclash", section_id, "name", name)
|
||||
uci:set("openclash", section_id, "address", normalized_address)
|
||||
if normalized_address and normalized_address ~= "" then
|
||||
uci:set("openclash", section_id, "address", normalized_address)
|
||||
else
|
||||
uci:delete("openclash", section_id, "address")
|
||||
end
|
||||
uci:set("openclash", section_id, "sub_ua", sub_ua)
|
||||
uci:set("openclash", section_id, "sub_convert", sub_convert)
|
||||
if sub_convert == "1" then
|
||||
uci:set("openclash", section_id, "convert_address", convert_address)
|
||||
uci:set("openclash", section_id, "template", template)
|
||||
if template == "0" then
|
||||
uci:set("openclash", section_id, "custom_template_url", custom_template_url)
|
||||
else
|
||||
uci:delete("openclash", section_id, "custom_template_url")
|
||||
end
|
||||
else
|
||||
uci:delete("openclash", section_id, "convert_address")
|
||||
uci:delete("openclash", section_id, "template")
|
||||
uci:delete("openclash", section_id, "custom_template_url")
|
||||
end
|
||||
uci:set("openclash", section_id, "emoji", emoji)
|
||||
uci:set("openclash", section_id, "udp", udp)
|
||||
@ -3993,6 +4049,19 @@ function action_get_subscribe_data()
|
||||
end
|
||||
end)
|
||||
|
||||
uci:foreach("openclash", "config_age_secret", function(a)
|
||||
if a.name == filename and (not a.hidden or a.hidden ~= "true") then
|
||||
if a.secret then data.config_age_secret = a.secret end
|
||||
if a.public then data.config_age_public = a.public end
|
||||
if a.algo then data.config_age_algo = a.algo end
|
||||
return false
|
||||
end
|
||||
if a.name == filename and a.hidden and a.hidden == "true" then
|
||||
data.config_age_hidden = true
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(data)
|
||||
end
|
||||
@ -4005,4 +4074,294 @@ function action_get_subscribe_info_data()
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(get_sub_url(filename))
|
||||
end
|
||||
|
||||
function action_generate_age_key()
|
||||
local algo = luci.http.formvalue("algo") or "keygen"
|
||||
local cmd = string.format("%s age %s", meta_core_path, (algo == "pq" and "keygen-pq" or "keygen"))
|
||||
local out = luci.sys.exec(cmd .. " 2>/dev/null")
|
||||
local secret = out:match("(AGE%-SECRET%-KEY%-%S+)")
|
||||
local public = out:match("# public key: ([^\n\r]+)")
|
||||
luci.http.prepare_content("application/json")
|
||||
if not secret then
|
||||
luci.http.write_json({status = "error", message = "Failed to generate age key", output = out})
|
||||
return
|
||||
end
|
||||
luci.http.write_json({status = "success", secret = secret, public = public})
|
||||
end
|
||||
|
||||
function action_cal_age_public_key()
|
||||
local secret = luci.http.formvalue("secret") or ""
|
||||
if secret == "" then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({status = "error", message = "Secret key is required"})
|
||||
return
|
||||
end
|
||||
local cmd = string.format("%s age convert %s", meta_core_path, secret)
|
||||
local out = luci.sys.exec(cmd .. " 2>/dev/null")
|
||||
luci.http.prepare_content("application/json")
|
||||
if out and out:match("^age") then
|
||||
luci.http.write_json({status = "success", public = out})
|
||||
else
|
||||
luci.http.write_json({status = "error", message = "Failed to calculate public key, invalid secret key", output = out})
|
||||
end
|
||||
end
|
||||
|
||||
function action_add_age_config()
|
||||
local name = luci.http.formvalue("name")
|
||||
local age_secret = luci.http.formvalue("age_secret") or ""
|
||||
local age_public = luci.http.formvalue("age_public") or ""
|
||||
local age_algo = luci.http.formvalue("age_algo") or ""
|
||||
local age_section_id, age_section_hidden
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
|
||||
if not name or name == "" then
|
||||
luci.http.write_json({status = "error", message = "Missing name parameter"})
|
||||
return
|
||||
end
|
||||
|
||||
uci:foreach("openclash", "config_age_secret", function(s)
|
||||
if s.name == name then
|
||||
age_section_id = s['.name']
|
||||
age_section_hidden = s.hidden and s.hidden == "true"
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
if age_section_hidden then
|
||||
luci.http.write_json({status = "error", message = "Cannot modify hidden age configuration"})
|
||||
return
|
||||
end
|
||||
|
||||
if not age_section_id and (age_secret ~= "" or age_public ~= "" or age_algo ~= "") then
|
||||
age_section_id = uci:add("openclash", "config_age_secret")
|
||||
if age_section_id then
|
||||
uci:set("openclash", age_section_id, "name", name)
|
||||
end
|
||||
end
|
||||
|
||||
if age_section_id then
|
||||
if (age_secret == "" and age_public == "") then
|
||||
uci:delete("openclash", age_section_id)
|
||||
else
|
||||
if age_secret and age_secret ~= "" then
|
||||
uci:set("openclash", age_section_id, "secret", age_secret)
|
||||
else
|
||||
uci:delete("openclash", age_section_id, "secret")
|
||||
end
|
||||
if age_public and age_public ~= "" then
|
||||
uci:set("openclash", age_section_id, "public", age_public)
|
||||
else
|
||||
uci:delete("openclash", age_section_id, "public")
|
||||
end
|
||||
if age_algo and age_algo ~= "" then
|
||||
uci:set("openclash", age_section_id, "algo", age_algo)
|
||||
else
|
||||
uci:delete("openclash", age_section_id, "algo")
|
||||
end
|
||||
end
|
||||
uci:commit("openclash")
|
||||
end
|
||||
|
||||
luci.http.write_json({status = "success"})
|
||||
end
|
||||
|
||||
function oix_login_info_save()
|
||||
uci:set("openclash", "config", "oix_email", luci.http.formvalue("email"))
|
||||
uci:set("openclash", "config", "oix_passwd", luci.http.formvalue("passwd"))
|
||||
uci:set("openclash", "config", "oix_checkin", luci.http.formvalue("checkin"))
|
||||
uci:set("openclash", "config", "oix_checkin_interval", luci.http.formvalue("interval"))
|
||||
if tonumber(luci.http.formvalue("multiple")) > 100 then
|
||||
uci:set("openclash", "config", "oix_checkin_multiple", "100")
|
||||
elseif tonumber(luci.http.formvalue("multiple")) < 1 or not tonumber(luci.http.formvalue("multiple")) then
|
||||
uci:set("openclash", "config", "oix_checkin_multiple", "1")
|
||||
else
|
||||
uci:set("openclash", "config", "oix_checkin_multiple", luci.http.formvalue("multiple"))
|
||||
end
|
||||
uci:commit("openclash")
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({status = "success"})
|
||||
end
|
||||
|
||||
function oix_login()
|
||||
local result, info, token, get_sub, sub_info, sub_key, sub_match, sub_convert, sid, sub_file, SIGNATURE
|
||||
local email = fs.uci_get_config("config", "oix_email")
|
||||
local passwd = fs.uci_get_config("config", "oix_passwd")
|
||||
token = fs.uci_get_config("config", "oix_token")
|
||||
if email and passwd then
|
||||
info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'User-Agent: OpenClash' -d '{\"email\":\"%s\", \"passwd\":\"%s\", \"token_expire\":\"365\" }' -X POST https://oix-api.dler.io/api/v1/login", email, passwd))
|
||||
if info then
|
||||
info = json.parse(info)
|
||||
end
|
||||
if info and info.ret == 200 then
|
||||
-- because of uci cache, need reload after delete if get new
|
||||
if token and token ~= "" then
|
||||
oix_logout(token)
|
||||
end
|
||||
token = info.data.token
|
||||
uci:set("openclash", "config", "oix_token", token)
|
||||
uci:commit("openclash")
|
||||
get_sub = string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/managed/clash", token)
|
||||
sub_info = luci.sys.exec(get_sub)
|
||||
if sub_info then
|
||||
sub_info = json.parse(sub_info)
|
||||
end
|
||||
if sub_info and sub_info.ret == 200 then
|
||||
sub_key = {"openclash"}
|
||||
for _,v in ipairs(sub_key) do
|
||||
while true do
|
||||
sub_match = false
|
||||
sub_convert = false
|
||||
uci:foreach("openclash", "config_subscribe",
|
||||
function(s)
|
||||
if s.name == "oixCloud - smart" and s.address == sub_info[v] then
|
||||
sub_match = true
|
||||
end
|
||||
if s.name == "oixCloud - smart" and s.address ~= sub_info[v] then
|
||||
sub_convert = true
|
||||
sid = s['.name']
|
||||
end
|
||||
end)
|
||||
if sub_match then break end
|
||||
if sub_convert then
|
||||
uci:set("openclash", sid, "address", sub_info[v])
|
||||
elseif sub_info[v] then
|
||||
sid = uci:add("openclash", "config_subscribe")
|
||||
uci:set("openclash", sid, "name", "oixCloud - smart")
|
||||
uci:set("openclash", sid, "address", sub_info[v])
|
||||
end
|
||||
uci:commit("openclash")
|
||||
break
|
||||
end
|
||||
if sub_info[v] then
|
||||
luci.sys.exec(string.format('curl -sL -m 10 --retry 2 --user-agent "clash" "%s" -o "/etc/openclash/config/oixCloud - smart.yaml" >/dev/null 2>&1', sub_info[v]))
|
||||
luci.sys.call("/etc/init.d/openclash restart >/dev/null 2>&1 &")
|
||||
end
|
||||
end
|
||||
end
|
||||
result = info.ret
|
||||
else
|
||||
uci:delete("openclash", "config", "oix_token")
|
||||
uci:commit("openclash")
|
||||
fs.unlink("/tmp/oix_checkin")
|
||||
fs.unlink("/tmp/oix_info")
|
||||
if info and info.msg then
|
||||
result = info.msg
|
||||
else
|
||||
result = "login faild"
|
||||
end
|
||||
end
|
||||
else
|
||||
uci:delete("openclash", "config", "oix_token")
|
||||
uci:commit("openclash")
|
||||
fs.unlink("/tmp/oix_checkin")
|
||||
fs.unlink("/tmp/oix_info")
|
||||
result = "email or passwd is wrong"
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({result = result})
|
||||
end
|
||||
|
||||
function oix_logout(oldtoken)
|
||||
local info, result, token
|
||||
if not oldtoken then
|
||||
token = fs.uci_get_config("config", "oix_token")
|
||||
else
|
||||
token = oldtoken
|
||||
end
|
||||
if token then
|
||||
info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/logout", token))
|
||||
if info then
|
||||
info = json.parse(info)
|
||||
end
|
||||
if info and info.ret == 200 then
|
||||
uci:delete("openclash", "config", "oix_token")
|
||||
if not oldtoken then
|
||||
uci:delete("openclash", "config", "oix_checkin")
|
||||
uci:delete("openclash", "config", "oix_checkin_interval")
|
||||
uci:delete("openclash", "config", "oix_checkin_multiple")
|
||||
end
|
||||
uci:commit("openclash")
|
||||
fs.unlink("/tmp/oix_checkin")
|
||||
fs.unlink("/tmp/oix_info")
|
||||
result = info.ret
|
||||
else
|
||||
if info and info.msg then
|
||||
result = info.msg
|
||||
else
|
||||
result = "logout faild"
|
||||
end
|
||||
end
|
||||
else
|
||||
result = "logout faild"
|
||||
end
|
||||
if not oldtoken then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({result = result})
|
||||
end
|
||||
end
|
||||
|
||||
function oix_info()
|
||||
local info, path, get_info
|
||||
local result = "error"
|
||||
local token = fs.uci_get_config("config", "oix_token")
|
||||
path = "/tmp/oix_info"
|
||||
if token then
|
||||
get_info = string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -X POST https://oix-api.dler.io/api/v1/information -o %s", token, path)
|
||||
if not fs.access(path) then
|
||||
luci.sys.exec(get_info)
|
||||
else
|
||||
if fs.readfile(path) == "" or not fs.readfile(path) then
|
||||
luci.sys.exec(get_info)
|
||||
else
|
||||
if (os.time() - fs.mtime(path) > 900) then
|
||||
luci.sys.exec(get_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
info = fs.readfile(path)
|
||||
if info then
|
||||
info = json.parse(info)
|
||||
end
|
||||
if info and info.ret == 200 and info.data then
|
||||
result = info.data
|
||||
elseif info and info.msg then
|
||||
fs.writefile(path, json.stringify(info))
|
||||
else
|
||||
fs.unlink(path)
|
||||
end
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({result = result})
|
||||
end
|
||||
|
||||
function oix_checkin()
|
||||
local info, result
|
||||
local path = "/tmp/oix_checkin"
|
||||
local token = fs.uci_get_config("config", "oix_token")
|
||||
local multiple = fs.uci_get_config("config", "oix_checkin_multiple") or 1
|
||||
if token then
|
||||
info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '{\"multiple\":\"%s\"}' -X POST https://oix-api.dler.io/api/v1/checkin", token, token, multiple))
|
||||
if info then
|
||||
info = json.parse(info)
|
||||
end
|
||||
if info and info.ret == 200 then
|
||||
fs.unlink("/tmp/oix_info")
|
||||
fs.writefile(path, info)
|
||||
luci.sys.exec(string.format("echo -e %s oixCloud Checkin Successful, Result:【%s】 >> /tmp/openclash.log", os.date("%Y-%m-%d %H:%M:%S"), info.data.checkin))
|
||||
result = info
|
||||
else
|
||||
if info and info.msg then
|
||||
luci.sys.exec(string.format("echo -e %s oixCloud Checkin Failed, Result:【%s】 >> /tmp/openclash.log", os.date("%Y-%m-%d %H:%M:%S"), info.msg))
|
||||
else
|
||||
luci.sys.exec(string.format("echo -e %s oixCloud Checkin Failed! Please Check And Try Again... >> /tmp/openclash.log",os.date("%Y-%m-%d %H:%M:%S")))
|
||||
end
|
||||
result = info
|
||||
end
|
||||
else
|
||||
result = "error"
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({result = result})
|
||||
end
|
||||
@ -13,7 +13,9 @@ m.reset = false
|
||||
m.submit = false
|
||||
|
||||
m:section(SimpleSection).template = "openclash/status"
|
||||
|
||||
if fs.uci_get_config("config", "oix_token") then
|
||||
m:append(Template("openclash/oixcloud"))
|
||||
end
|
||||
m:append(Template("openclash/myip"))
|
||||
m:append(Template("openclash/developer"))
|
||||
m:append(Template("openclash/select_git_cdn"))
|
||||
|
||||
@ -205,14 +205,14 @@ custom_fallback_filter.wrap = "off"
|
||||
custom_fallback_filter:depends("custom_fallback_filter", "1")
|
||||
|
||||
function custom_fallback_filter.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml") or ""
|
||||
end
|
||||
function custom_fallback_filter.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -237,14 +237,14 @@ custom_fake_black.wrap = "off"
|
||||
custom_fake_black:depends("custom_fakeip_filter", "1")
|
||||
|
||||
function custom_fake_black.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_fake_filter.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_fake_filter.list") or ""
|
||||
end
|
||||
function custom_fake_black.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_fake_filter.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_fake_filter.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_fake_filter.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_fake_filter.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -261,14 +261,14 @@ custom_domain_dns_policy.wrap = "off"
|
||||
custom_domain_dns_policy:depends("custom_name_policy", "1")
|
||||
|
||||
function custom_domain_dns_policy.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list") or ""
|
||||
end
|
||||
function custom_domain_dns_policy.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -284,14 +284,14 @@ custom_proxy_server_dns_policy.wrap = "off"
|
||||
custom_proxy_server_dns_policy:depends("custom_proxy_server_policy", "1")
|
||||
|
||||
function custom_proxy_server_dns_policy.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list") or ""
|
||||
end
|
||||
function custom_proxy_server_dns_policy.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -307,14 +307,14 @@ custom_hosts.wrap = "off"
|
||||
custom_hosts:depends("custom_host", "1")
|
||||
|
||||
function custom_hosts.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_hosts.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_hosts.list") or ""
|
||||
end
|
||||
function custom_hosts.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_hosts.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_hosts.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_hosts.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_hosts.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -374,14 +374,14 @@ sniffer_custom.rows = 20
|
||||
sniffer_custom.wrap = "off"
|
||||
|
||||
function sniffer_custom.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_sniffer.yaml") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_sniffer.yaml") or ""
|
||||
end
|
||||
function sniffer_custom.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_sniffer.yaml")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_sniffer.yaml")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_sniffer.yaml", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_sniffer.yaml", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -466,14 +466,14 @@ custom_rules.rows = 20
|
||||
custom_rules.wrap = "off"
|
||||
|
||||
function custom_rules.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_rules.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_rules.list") or ""
|
||||
end
|
||||
function custom_rules.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_rules.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_rules.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_rules.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_rules.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -486,14 +486,14 @@ custom_rules_2.rows = 20
|
||||
custom_rules_2.wrap = "off"
|
||||
|
||||
function custom_rules_2.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_rules_2.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_rules_2.list") or ""
|
||||
end
|
||||
function custom_rules_2.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_rules_2.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_rules_2.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_rules_2.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_rules_2.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -8,6 +8,7 @@ local json = require "luci.jsonc"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local sid = arg[1]
|
||||
local age_section
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
font_off = [[</b>]]
|
||||
@ -59,13 +60,135 @@ end
|
||||
|
||||
---- UA
|
||||
o = s:option(Value, "sub_ua", "User-Agent")
|
||||
o.description = font_red..bold_on..translate("Used for Downloading Subscriptions, Defaults to Clash")..bold_off..font_off
|
||||
o.default = "clash-verge/v2.4.5"
|
||||
o.description = font_red..bold_on..translate("Used for Downloading Subscriptions, Defaults to").." "..o.default..bold_off..font_off
|
||||
o:value("clash-verge/v2.4.5")
|
||||
o:value("clash.meta/1.19.20")
|
||||
o:value("Clash")
|
||||
o.default = "clash-verge/v2.4.5"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(ListValue, "config_age_algo", translate("Age Key Type"))
|
||||
o.description = font_red..bold_on..translate("Age Encryption For Config, Click For More:")..bold_off..font_off.." ".."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://wiki.metacubex.one/config/proxy-providers/?age-secret-key#age-secret-key\")'>"..translate("Age Encryption Introduce").."</a>"
|
||||
o:value("keygen", "x25519")
|
||||
o:value("pq", "PQ (mlkem768-x25519)")
|
||||
o.rmempty = true
|
||||
function o.cfgvalue(self, section)
|
||||
local name = m.uci:get(openclash, section, "name") or section
|
||||
local v = ""
|
||||
m.uci:foreach(openclash, "config_age_secret", function(s)
|
||||
if s.name == name then
|
||||
v = s.algo or ""
|
||||
age_section = s['.name']
|
||||
return false
|
||||
end
|
||||
end)
|
||||
return v
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
local name = m.uci:get(openclash, section, "name") or section
|
||||
m.uci:foreach(openclash, "config_age_secret", function(s)
|
||||
if s.name == name then
|
||||
age_section = s['.name']
|
||||
return false
|
||||
end
|
||||
end)
|
||||
if not age_section and value and value ~= "" then
|
||||
age_section = m.uci:add(openclash, "config_age_secret")
|
||||
if age_section then m.uci:set(openclash, age_section, "name", name) end
|
||||
end
|
||||
if age_section then
|
||||
if value and value ~= "" then
|
||||
m.uci:set(openclash, age_section, "algo", value)
|
||||
else
|
||||
m.uci:delete(openclash, age_section, "algo")
|
||||
end
|
||||
end
|
||||
end
|
||||
function o.remove(self, section)
|
||||
self:write(section, "")
|
||||
end
|
||||
|
||||
o = s:option(Value, "config_age_secret", translate("Secret Key"))
|
||||
o.rmempty = true
|
||||
o.placeholder = "AGE-SECRET-KEY-..."
|
||||
function o.cfgvalue(self, section)
|
||||
local name = m.uci:get(openclash, section, "name") or section
|
||||
local v = ""
|
||||
m.uci:foreach(openclash, "config_age_secret", function(s)
|
||||
if s.name == name then
|
||||
v = s.secret or ""
|
||||
age_section = s['.name']
|
||||
return false
|
||||
end
|
||||
end)
|
||||
return v
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
local name = m.uci:get(openclash, section, "name") or section
|
||||
m.uci:foreach(openclash, "config_age_secret", function(s)
|
||||
if s.name == name then
|
||||
age_section = s['.name']
|
||||
return false
|
||||
end
|
||||
end)
|
||||
if not age_section and value and value ~= "" then
|
||||
age_section = m.uci:add(openclash, "config_age_secret")
|
||||
if age_section then m.uci:set(openclash, age_section, "name", name) end
|
||||
end
|
||||
if age_section then
|
||||
if value and value ~= "" then
|
||||
m.uci:set(openclash, age_section, "secret", value)
|
||||
else
|
||||
m.uci:delete(openclash, age_section, "secret")
|
||||
end
|
||||
end
|
||||
end
|
||||
function o.remove(self, section)
|
||||
self:write(section, "")
|
||||
end
|
||||
|
||||
o = s:option(Value, "config_age_public", translate("Public Key"))
|
||||
o.rmempty = true
|
||||
o.placeholder = "age..."
|
||||
function o.cfgvalue(self, section)
|
||||
local name = m.uci:get(openclash, section, "name") or section
|
||||
local v = ""
|
||||
m.uci:foreach(openclash, "config_age_secret", function(s)
|
||||
if s.name == name then
|
||||
v = s.public or ""
|
||||
age_section = s['.name']
|
||||
return false
|
||||
end
|
||||
end)
|
||||
return v
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
local name = m.uci:get(openclash, section, "name") or section
|
||||
m.uci:foreach(openclash, "config_age_secret", function(s)
|
||||
if s.name == name then
|
||||
age_section = s['.name']
|
||||
return false
|
||||
end
|
||||
end)
|
||||
if not age_section and value and value ~= "" then
|
||||
age_section = m.uci:add(openclash, "config_age_secret")
|
||||
if age_section then m.uci:set(openclash, age_section, "name", name) end
|
||||
end
|
||||
if age_section then
|
||||
if value and value ~= "" then
|
||||
m.uci:set(openclash, age_section, "public", value)
|
||||
else
|
||||
m.uci:delete(openclash, age_section, "public")
|
||||
end
|
||||
end
|
||||
end
|
||||
function o.remove(self, section)
|
||||
self:write(section, "")
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "_generate_age_btn", "")
|
||||
o.template = "openclash/generate_age"
|
||||
|
||||
---- subconverter
|
||||
o = s:option(Flag, "sub_convert", translate("Subscribe Convert Online"))
|
||||
o.description = translate("Convert Subscribe Online With Template")
|
||||
@ -183,6 +306,17 @@ o = a:option(Button,"Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
local to_delete = {}
|
||||
m.uci:foreach(openclash, "config_age_secret", function(s)
|
||||
local pub = m.uci:get(openclash, s['.name'], "public") or ""
|
||||
local sec = m.uci:get(openclash, s['.name'], "secret") or ""
|
||||
if (pub == "" or pub == nil) and (sec == "" or sec == nil) then
|
||||
table.insert(to_delete, s['.name'])
|
||||
end
|
||||
end)
|
||||
for _, n in ipairs(to_delete) do
|
||||
m.uci:delete(openclash, n)
|
||||
end
|
||||
m.uci:commit(openclash)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
@ -192,6 +326,7 @@ o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
m.uci:revert(openclash, age_section)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
|
||||
@ -373,14 +373,14 @@ sev = s:option(TextValue, "user")
|
||||
sev.rows = 40
|
||||
sev.wrap = "off"
|
||||
sev.cfgvalue = function(self, section)
|
||||
return NXFS.readfile(conf) or NXFS.readfile(dconf) or ""
|
||||
return fs.readfile(conf) or fs.readfile(dconf) or ""
|
||||
end
|
||||
sev.write = function(self, section, value)
|
||||
if (CHIF == "0") then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile(conf)
|
||||
local old_value = fs.readfile(conf)
|
||||
if value ~= old_value then
|
||||
NXFS.writefile(conf, value)
|
||||
fs.writefile(conf, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -395,7 +395,7 @@ def.rows = 40
|
||||
def.wrap = "off"
|
||||
def.readonly = true
|
||||
def.cfgvalue = function(self, section)
|
||||
return NXFS.readfile(sconf) or NXFS.readfile(dconf) or ""
|
||||
return fs.readfile(sconf) or fs.readfile(dconf) or ""
|
||||
end
|
||||
def.write = function(self, section, value)
|
||||
end
|
||||
|
||||
@ -24,15 +24,15 @@ o.wrap = "off"
|
||||
function o.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile(file_path)
|
||||
local old_value = fs.readfile(file_path)
|
||||
if value ~= old_value then
|
||||
NXFS.writefile(file_path, value)
|
||||
fs.writefile(file_path, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function o.cfgvalue(self, section)
|
||||
return NXFS.readfile(file_path) or ""
|
||||
return fs.readfile(file_path) or ""
|
||||
end
|
||||
|
||||
local t = {
|
||||
|
||||
@ -185,6 +185,7 @@ o.write = function()
|
||||
end
|
||||
end)
|
||||
m.uci:commit("openclash")
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = b:option(Button,"Delete_Servers", " ")
|
||||
@ -193,6 +194,7 @@ o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:delete_all("openclash", "servers", function(s) return true end)
|
||||
m.uci:commit("openclash")
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = b:option(Button,"Delete_Proxy_Provider", " ")
|
||||
@ -201,6 +203,7 @@ o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:delete_all("openclash", "proxy-provider", function(s) return true end)
|
||||
m.uci:commit("openclash")
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = b:option(Button,"Delete_Groups", " ")
|
||||
@ -209,6 +212,7 @@ o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:delete_all("openclash", "groups", function(s) return true end)
|
||||
m.uci:commit("openclash")
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
local t = {
|
||||
|
||||
@ -79,6 +79,7 @@ s:tab("auto_restart", translate("Auto Restart"))
|
||||
s:tab("version_update", translate("Version Update"))
|
||||
s:tab("developer", translate("Developer Settings"))
|
||||
s:tab("debug", translate("Debug Logs"))
|
||||
s:tab("oixcloud", translate("oixCloud"))
|
||||
|
||||
o = s:taboption("op_mode", ListValue, "en_mode", font_red..bold_on..translate("Select Mode")..bold_off..font_off)
|
||||
o.description = translate("Select Mode For OpenClash Work, Try Flush DNS Cache If Network Error")
|
||||
@ -154,6 +155,51 @@ o:value("2", translate("Firewall Redirect"))
|
||||
o = s:taboption("dns", DummyValue, "flush_dns_cache", translate("Flush DNS"))
|
||||
o.template = "openclash/flush_dns_cache"
|
||||
|
||||
o = s:taboption("dns", Button, "dnsmasq_fix", translate("Dnsmasq Fix"))
|
||||
o.description = translate("If DNS is abnormal after stopping the OpenClash, please try to fix")
|
||||
o.inputtitle = translate("Fix")
|
||||
o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
uci:set("dhcp", "@dnsmasq[0]", "noresolv", "0")
|
||||
uci:set("dhcp", "@dnsmasq[0]", "localuse", "1")
|
||||
local resolv_file = uci:get("dhcp", "@dnsmasq[0]", "resolvfile")
|
||||
local need_fix = false
|
||||
if not resolv_file or resolv_file == "" then
|
||||
need_fix = true
|
||||
elseif not NXFS.access(resolv_file) then
|
||||
need_fix = true
|
||||
else
|
||||
local content = fs.readfile(resolv_file) or ""
|
||||
if not content:find("nameserver") then
|
||||
need_fix = true
|
||||
end
|
||||
end
|
||||
if need_fix then
|
||||
for _, f in ipairs({"/tmp/resolv.conf.d/resolv.conf.auto", "/tmp/resolv.conf.auto"}) do
|
||||
if NXFS.access(f) then
|
||||
local content = fs.readfile(f) or ""
|
||||
if content:find("nameserver") then
|
||||
uci:set("dhcp", "@dnsmasq[0]", "resolvfile", f)
|
||||
resolv_file = f
|
||||
need_fix = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if need_fix then
|
||||
resolv_file = "/tmp/resolv.conf.d/resolv.conf.auto"
|
||||
SYS.call("mkdir -p /tmp/resolv.conf.d")
|
||||
fs.writefile(resolv_file, "# Interface lan\nnameserver 119.29.29.29\nnameserver 8.8.8.8\n")
|
||||
uci:set("dhcp", "@dnsmasq[0]", "resolvfile", resolv_file)
|
||||
end
|
||||
uci:set("openclash", "config", "redirect_dns", "0")
|
||||
uci:commit("dhcp")
|
||||
uci:commit("openclash")
|
||||
SYS.call("/etc/init.d/dnsmasq restart")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "settings"))
|
||||
end
|
||||
|
||||
o = s:taboption("dns", Flag, "enable_custom_domain_dns_server", translate("Enable Specify DNS Server"))
|
||||
o.default = 0
|
||||
o:depends("enable_redirect_dns", "1")
|
||||
@ -173,14 +219,14 @@ custom_domain_dns.wrap = "off"
|
||||
custom_domain_dns:depends{enable_redirect_dns = "1", enable_custom_domain_dns_server = "1"}
|
||||
|
||||
function custom_domain_dns.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_domain_dns.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_domain_dns.list") or ""
|
||||
end
|
||||
function custom_domain_dns.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_domain_dns.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_domain_dns.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_domain_dns.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_domain_dns.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -289,7 +335,7 @@ o.rmempty = true
|
||||
o = s2:option(ListValue, "user", translate("User"))
|
||||
o:value("")
|
||||
o.default = ""
|
||||
local passwd_content = NXFS.readfile("/etc/passwd")
|
||||
local passwd_content = fs.readfile("/etc/passwd")
|
||||
local users = ""
|
||||
if passwd_content then
|
||||
for line in string.gmatch(passwd_content, "[^\n]+") do
|
||||
@ -482,14 +528,14 @@ o.rows = 20
|
||||
o.wrap = "off"
|
||||
|
||||
function o.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list") or ""
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_localnetwork_ipv4.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -503,14 +549,14 @@ o:depends("enable_redirect_dns", "1")
|
||||
o:depends("enable_redirect_dns", "0")
|
||||
|
||||
function o.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_chnroute_pass.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_chnroute_pass.list") or ""
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_chnroute_pass.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_chnroute_pass.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_chnroute_pass.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_chnroute_pass.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1148,7 +1194,7 @@ o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang-CN")..translate("(
|
||||
o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang-CN-CIDR"))
|
||||
o:value("https://fastly.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us-CN-CIDR-fastly-jsdelivr"))
|
||||
o:value("https://testingcf.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us-CN-CIDR-testingcf-jsdelivr"))
|
||||
o:value("https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/direct-list.txt", translate("Loyalsoldier-github-Version"))
|
||||
o:value("https://raw.githubusercontent.com/gaoyifan/china-operator-ip/refs/heads/ip-lists/china.txt", translate("gaoyifan-github-Version"))
|
||||
o.default = "https://ispip.clang.cn/all_cn.txt"
|
||||
|
||||
o = s:taboption("chnr_update", Value, "chnr6_custom_url")
|
||||
@ -1156,6 +1202,7 @@ o.title = translate("Custom Chnroute6 Lists URL")
|
||||
o.rmempty = false
|
||||
o.description = translate("Custom Chnroute6 Lists URL, Click Button Below To Refresh After Edit")
|
||||
o:value("https://ispip.clang.cn/all_cn_ipv6.txt", translate("Clang-CN-IPV6")..translate("(Default)"))
|
||||
o:value("https://raw.githubusercontent.com/gaoyifan/china-operator-ip/refs/heads/ip-lists/china6.txt", translate("gaoyifan-github-Version"))
|
||||
o.default = "https://ispip.clang.cn/all_cn_ipv6.txt"
|
||||
|
||||
o = s:taboption("chnr_update", Button, translate("Chnroute Lists Update"))
|
||||
@ -1309,14 +1356,14 @@ o.wrap = "off"
|
||||
o:depends("ipv6_enable", "1")
|
||||
|
||||
function o.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list") or ""
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_localnetwork_ipv6.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1329,14 +1376,14 @@ o.wrap = "off"
|
||||
o:depends({ipv6_enable = "1", enable_redirect_dns = "1"})
|
||||
|
||||
function o.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_chnroute6_pass.list") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_chnroute6_pass.list") or ""
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_chnroute6_pass.list")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_chnroute6_pass.list")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_chnroute6_pass.list", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_chnroute6_pass.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1353,14 +1400,14 @@ o.rows = 30
|
||||
o.wrap = "off"
|
||||
|
||||
function o.cfgvalue(self, section)
|
||||
return NXFS.readfile("/etc/openclash/custom/openclash_custom_firewall_rules.sh") or ""
|
||||
return fs.readfile("/etc/openclash/custom/openclash_custom_firewall_rules.sh") or ""
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = NXFS.readfile("/etc/openclash/custom/openclash_custom_firewall_rules.sh")
|
||||
local old_value = fs.readfile("/etc/openclash/custom/openclash_custom_firewall_rules.sh")
|
||||
if value ~= old_value then
|
||||
NXFS.writefile("/etc/openclash/custom/openclash_custom_firewall_rules.sh", value)
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_firewall_rules.sh", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1369,6 +1416,54 @@ end
|
||||
o = s:taboption("debug", DummyValue, "", nil)
|
||||
o.template = "openclash/debug"
|
||||
|
||||
---- oixcloud
|
||||
o = s:taboption("oixcloud", Value, "oix_email")
|
||||
o.title = translate("Account Email Address")
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("oixcloud", Value, "oix_passwd")
|
||||
o.title = translate("Account Password")
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
|
||||
if fs.uci_get_config("config", "oix_token") then
|
||||
o = s:taboption("oixcloud", Flag, "oix_checkin")
|
||||
o.title = translate("Checkin")
|
||||
o.default = 0
|
||||
o.rmempty = true
|
||||
end
|
||||
|
||||
o = s:taboption("oixcloud", Value, "oix_checkin_interval")
|
||||
o.title = translate("Checkin Interval (hour)")
|
||||
o:depends("oix_checkin", "1")
|
||||
o.default = "1"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:taboption("oixcloud", Value, "oix_checkin_multiple")
|
||||
o.title = translate("Checkin Multiple")
|
||||
o.datatype = "uinteger"
|
||||
o.default = "1"
|
||||
o:depends("oix_checkin", "1")
|
||||
o.rmempty = true
|
||||
o.description = font_green..bold_on..translate("Multiple Must Be a Positive Integer and No More Than 100")..bold_off..font_off
|
||||
function o.validate(self, value)
|
||||
if tonumber(value) < 1 then
|
||||
return "1"
|
||||
end
|
||||
if tonumber(value) > 100 then
|
||||
return "100"
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:taboption("oixcloud", DummyValue, "oix_login", translate("Account Login"))
|
||||
o.template = "openclash/oix_login"
|
||||
if fs.uci_get_config("config", "oix_token") then
|
||||
o.value = font_green..bold_on..translate("Account logged in")..bold_off..font_off
|
||||
else
|
||||
o.value = font_red..bold_on..translate("Account not logged in")..bold_off..font_off
|
||||
end
|
||||
|
||||
local t = {
|
||||
{Commit, Apply}
|
||||
}
|
||||
|
||||
@ -35,6 +35,10 @@ local HTTP = require "luci.http"
|
||||
|
||||
local type = type
|
||||
local string = string
|
||||
local tostring = tostring
|
||||
local table = table
|
||||
local math = math
|
||||
local b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
--- LuCI filesystem library.
|
||||
module "luci.openclash"
|
||||
@ -83,14 +87,34 @@ end
|
||||
-- @param filename String containing the path of the file to read
|
||||
-- @return String containing the file contents or nil on error
|
||||
-- @return String containing the error message on error
|
||||
readfile = fs.readfile
|
||||
-- Wrapped readfile: if file is age-encrypted, try to decrypt with matching UCI secret first
|
||||
function readfile(filename)
|
||||
local content, err = fs.readfile(filename)
|
||||
if not content then return nil, err end
|
||||
if content:find("BEGIN AGE ENCRYPTED FILE") then
|
||||
local keys = get_age_keys(filename)
|
||||
if keys and keys.secret and keys.secret ~= "" then
|
||||
return age_decrypt(keys.secret, content) or content
|
||||
else
|
||||
return content
|
||||
end
|
||||
end
|
||||
return content
|
||||
end
|
||||
|
||||
--- Write the contents of given string to given file.
|
||||
-- @param filename String containing the path of the file to read
|
||||
-- @param data String containing the data to write
|
||||
-- @return Boolean containing true on success or nil on error
|
||||
-- @return String containing the error message on error
|
||||
writefile = fs.writefile
|
||||
-- Wrapped writefile: if public key exists for filename, encrypt before writing
|
||||
function writefile(filename, data)
|
||||
local keys = get_age_keys(filename)
|
||||
if keys and keys.public and keys.public ~= "" then
|
||||
return fs.writefile(filename, age_encrypt(keys.public, data) or data)
|
||||
end
|
||||
return fs.writefile(filename, data)
|
||||
end
|
||||
|
||||
--- Copies a file.
|
||||
-- @param source Source file
|
||||
@ -356,4 +380,147 @@ function get_file_path_from_request()
|
||||
end
|
||||
|
||||
return file_path
|
||||
end
|
||||
end
|
||||
|
||||
function get_age_keys(file)
|
||||
local name = filename(basename(file))
|
||||
local pub, sec
|
||||
uci:foreach("openclash", "config_age_secret", function(s)
|
||||
if s and s.name then
|
||||
if s.name == name and (not s.hidden or (s.hidden ~= "true")) then
|
||||
if not pub and s.public then pub = s.public end
|
||||
if not sec and s.secret then sec = s.secret end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return { public = pub, secret = sec }
|
||||
end
|
||||
|
||||
function age_encrypt(public, content)
|
||||
if not public or public == "" or not content then return nil end
|
||||
|
||||
local tmp_in = os.tmpname()
|
||||
local tmp_out = os.tmpname()
|
||||
local f = io.open(tmp_in, "w")
|
||||
if not f then return nil end
|
||||
f:write(content)
|
||||
f:close()
|
||||
|
||||
local cmd = string.format(
|
||||
"/etc/openclash/core/clash_meta age encrypt %s %s %s 2>/dev/null",
|
||||
public, tmp_in, tmp_out
|
||||
)
|
||||
os.execute(cmd)
|
||||
|
||||
local out = nil
|
||||
local f_out = io.open(tmp_out, "r")
|
||||
if f_out then
|
||||
out = f_out:read("*a") or ""
|
||||
f_out:close()
|
||||
end
|
||||
|
||||
os.remove(tmp_in)
|
||||
os.remove(tmp_out)
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
function age_decrypt(secret, content)
|
||||
if not secret or secret == "" or not content then return nil end
|
||||
|
||||
local tmp_in = os.tmpname()
|
||||
local tmp_out = os.tmpname()
|
||||
local f = io.open(tmp_in, "w")
|
||||
if not f then return nil end
|
||||
f:write(content)
|
||||
f:close()
|
||||
|
||||
local cmd = string.format(
|
||||
"/etc/openclash/core/clash_meta age decrypt %s %s %s 2>/dev/null",
|
||||
secret, tmp_in, tmp_out
|
||||
)
|
||||
os.execute(cmd)
|
||||
|
||||
local out = nil
|
||||
local f_out = io.open(tmp_out, "r")
|
||||
if f_out then
|
||||
out = f_out:read("*a") or ""
|
||||
f_out:close()
|
||||
end
|
||||
|
||||
os.remove(tmp_in)
|
||||
os.remove(tmp_out)
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
function decode64(data)
|
||||
if not data then return nil end
|
||||
data = data:gsub('[%s]', '')
|
||||
if #data % 4 ~= 0 then return data end
|
||||
|
||||
local out = {}
|
||||
for i = 1, #data, 4 do
|
||||
local c1, c2, c3, c4 = data:byte(i, i+3)
|
||||
local i1 = b64chars:find(string.char(c1), 1, true) - 1
|
||||
local i2 = b64chars:find(string.char(c2), 1, true) - 1
|
||||
local i3 = (c3 == 61) and -1 or (b64chars:find(string.char(c3), 1, true) - 1)
|
||||
local i4 = (c4 == 61) and -1 or (b64chars:find(string.char(c4), 1, true) - 1)
|
||||
|
||||
if not i1 or not i2 or (c3 ~= 61 and not i3) or (c4 ~= 61 and not i4) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local x = i1 * 0x40000 + i2 * 0x1000
|
||||
if i3 >= 0 then x = x + i3 * 0x40 end
|
||||
if i4 >= 0 then x = x + i4 end
|
||||
|
||||
table.insert(out, string.char(math.floor(x / 0x10000) % 0x100))
|
||||
if i3 >= 0 then
|
||||
table.insert(out, string.char(math.floor(x / 0x100) % 0x100))
|
||||
end
|
||||
if i4 >= 0 then
|
||||
table.insert(out, string.char(x % 0x100))
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
--- Returns the appropriate ps command string for the system's ps implementation.
|
||||
-- Detects procps-ng (ps -efw) vs busybox (ps -w).
|
||||
-- @return String containing the ps command prefix
|
||||
function ps_cmd()
|
||||
local ps_version = SYS.exec("ps --version 2>&1 |grep -c procps-ng |tr -d '\n'")
|
||||
if ps_version == "1" then
|
||||
return "ps -efw"
|
||||
else
|
||||
return "ps -w"
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the package manager type (opkg or apk).
|
||||
-- @return String "opkg" or "apk"
|
||||
function pkg_type()
|
||||
if fs.access("/usr/bin/apk") then
|
||||
return "apk"
|
||||
else
|
||||
return "opkg"
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the installed version of luci-app-openclash.
|
||||
-- Supports both opkg and apk package managers.
|
||||
-- @return String containing the version number, or "0" if not found
|
||||
function oc_version()
|
||||
local v
|
||||
if pkg_type() == "opkg" then
|
||||
v = SYS.exec("rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep '^Version:' |awk '{print $2}' |tr -d '\n'")
|
||||
else
|
||||
v = SYS.exec("rm -f /lib/apk/db/lock && apk info luci-app-openclash 2>/dev/null |grep '^luci-app-openclash-[0-9]' |sed 's/luci-app-openclash-//' |tr -d '\n'")
|
||||
end
|
||||
if v == "" then
|
||||
v = "0"
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
@ -1,669 +1,3 @@
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Twemoji Mozilla';
|
||||
src: url('/luci-static/resources/openclash/fonts/TwemojiMozilla-flags-B12sb_Bp.woff2') format('woff2');
|
||||
}
|
||||
.oc[data-darkmode="true"] .config-editor-model .CodeMirror {
|
||||
background: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-gutters {
|
||||
background: var(--bg-gray);
|
||||
border-right: 1px solid var(--border-light);
|
||||
}
|
||||
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-linenumber {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-scrollbar-filler,
|
||||
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-gutter-filler {
|
||||
background: var(--bg-gray);
|
||||
}
|
||||
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab:hover {
|
||||
color: var(--text-primary);
|
||||
background: rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
.oc[data-darkmode="true"] #config-mergeview-container .CodeMirror-merge-gap {
|
||||
background: var(--text-secondary) !important;
|
||||
}
|
||||
.oc[data-darkmode="true"] .oc .config-editor-content {
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-banner {
|
||||
background: rgba(255,80,80,0.18);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-banner svg {
|
||||
stroke: var(--error-color);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-banner svg circle {
|
||||
stroke: var(--error-color);
|
||||
fill: rgba(255,80,80,0.18);
|
||||
}
|
||||
|
||||
.oc .config-editor-model-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 5;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
.oc .config-editor-model-overlay.show {
|
||||
display: flex;
|
||||
}
|
||||
.oc .config-editor-model {
|
||||
background: var(--bg-white);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
width: 90vw;
|
||||
height: 85vh;
|
||||
max-width: 1200px;
|
||||
min-width: 600px;
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-light);
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.oc .config-editor-model.maximized {
|
||||
width: 98vw !important;
|
||||
height: 95vh !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
.oc .config-editor-model.minimized {
|
||||
width: 70vw !important;
|
||||
height: 70vh !important;
|
||||
}
|
||||
.oc .config-editor-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
background: var(--bg-gray);
|
||||
flex-shrink: 0;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
min-width: 0;
|
||||
}
|
||||
.oc .config-editor-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.oc .config-editor-title #editTitle,
|
||||
.oc .config-editor-title #config-file-name {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
.oc .config-editor-title .config-file-name {
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
padding: 0;
|
||||
}
|
||||
.oc .config-editor-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.oc .size-btn {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
min-width: 24px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.oc .size-btn svg {
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
#config-mergeview-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: var(--bg-white);
|
||||
z-index: 2;
|
||||
}
|
||||
.oc .config-editor-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
.oc .config-editor-loading {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
background: var(--bg-white);
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
.oc .loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--border-light);
|
||||
border-top-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.oc .config-editor-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
background: var(--bg-gray);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
.oc .config-editor-status {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 50%;
|
||||
}
|
||||
.oc .config-editor-help {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.8;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 50%;
|
||||
}
|
||||
.oc .config-editor-resize-handle {
|
||||
position: absolute;
|
||||
bottom: 0; right: 0;
|
||||
width: 20px; height: 20px;
|
||||
cursor: nw-resize;
|
||||
background: linear-gradient(-45deg,
|
||||
transparent 0%,
|
||||
transparent 40%,
|
||||
var(--border-color) 40%,
|
||||
var(--border-color) 45%,
|
||||
transparent 45%,
|
||||
transparent 50%,
|
||||
var(--border-color) 50%,
|
||||
var(--border-color) 55%,
|
||||
transparent 55%,
|
||||
transparent 60%,
|
||||
var(--border-color) 60%,
|
||||
var(--border-color) 65%,
|
||||
transparent 65%);
|
||||
}
|
||||
.oc #config-editor-textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
padding: 12px;
|
||||
background: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.oc .config-editor-model .CodeMirror {
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.oc #config-mode-tabs {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
.oc #config-mode-tabs .mode-tabs {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: var(--bg-gray);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.oc #config-mode-tabs .mode-tab {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 0;
|
||||
border: none;
|
||||
border-radius: calc(var(--radius-md) - 2px);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.oc #config-mode-tabs .mode-tab:hover {
|
||||
color: var(--text-primary);
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
.oc #config-mode-tabs .mode-tab.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.overwrite-banner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 20px;
|
||||
background: rgba(255,80,80,0.12);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.overwrite-banner svg {
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
}
|
||||
.overwrite-banner span {
|
||||
flex: unset;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
color: var(--error-color);
|
||||
line-height: 1.5;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.oc .config-editor-model .CodeMirror.zoom-75 { font-size: 10.5px; }
|
||||
.oc .config-editor-model .CodeMirror.zoom-90 { font-size: 12.6px; }
|
||||
.oc .config-editor-model .CodeMirror.zoom-110 { font-size: 15.4px; }
|
||||
.oc .config-editor-model .CodeMirror.zoom-125 { font-size: 17.5px; }
|
||||
.oc .config-editor-model .CodeMirror.zoom-150 { font-size: 21px; }
|
||||
.oc .config-editor-model .CodeMirror.zoom-200 { font-size: 28px; }
|
||||
.oc .config-editor-model .CodeMirror, .oc .config-editor-model .CodeMirror-line {
|
||||
font-family: 'Twemoji Mozilla', "Microsoft Yahei", "sans-serif", "Helvetica Neue", "Helvetica", "Hiragino Sans GB" !important;
|
||||
}
|
||||
.oc .config-editor-model .CodeMirror-hints.log {
|
||||
font-family: 'Twemoji Mozilla', "Open Sans", "PingFangSC-Regular", "Microsoft Yahei", "WenQuanYi Micro Hei", "Helvetica Neue", "Helvetica", "Hiragino Sans GB", "sans-serif" !important;
|
||||
}
|
||||
#config-mergeview-container .CodeMirror-merge,
|
||||
#config-mergeview-container .CodeMirror-merge-pane,
|
||||
#config-mergeview-container .CodeMirror,
|
||||
#config-mergeview-container .CodeMirror-scroll {
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#config-mergeview-container .CodeMirror-merge-gap {
|
||||
height: 100% !important;
|
||||
min-height: 0 !important;
|
||||
}
|
||||
#config-mergeview-container .CodeMirror-scroll {
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
#config-mergeview-container .CodeMirror-merge-pane {
|
||||
overflow: hidden;
|
||||
}
|
||||
#config-mergeview-container .CodeMirror-merge-r-chunk {
|
||||
background: #0095ff2e !important;
|
||||
}
|
||||
#config-mergeview-container .CodeMirror-merge-r-connect {
|
||||
fill: #0095ff2e !important;
|
||||
stroke: #0095ff2e !important;
|
||||
}
|
||||
#config-mergeview-container .CodeMirror-merge {
|
||||
border: none !important;
|
||||
}
|
||||
.oc .update-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.oc .update-row > .form-select-wrapper {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.oc .update-row .form-select {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.oc .overwrite-config-dropdown {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-btn {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.oc .overwrite-config-dropdown.form-select-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-btn.form-select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: calc(100% - 20px);
|
||||
text-align: left;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-arrow {
|
||||
margin-left: 8px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid var(--text-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.oc .overwrite-config-dropdown-panel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-white);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 10003;
|
||||
}
|
||||
.oc .overwrite-config-dropdown.open .overwrite-config-dropdown-panel {
|
||||
display: block;
|
||||
}
|
||||
.oc .overwrite-config-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
background: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.oc .overwrite-config-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.oc .overwrite-config-option:hover {
|
||||
background: var(--primary-color);
|
||||
color: var(--select-hover);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-config-option {
|
||||
background: var(--bg-gray);
|
||||
border-bottom-color: var(--border-light);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-config-option:hover {
|
||||
background: var(--primary-color);
|
||||
color: var(--select-hover);
|
||||
}
|
||||
.oc .overwrite-config-option:hover .overwrite-config-option-state {
|
||||
border-color: var(--select-hover);
|
||||
}
|
||||
.oc[data-darkmode="true"] .overwrite-config-option-state {
|
||||
background: var(--bg-white);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
.oc .overwrite-config-option.disabled-by-all {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
background: var(--bg-gray);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.oc .overwrite-config-option.disabled-by-all:hover {
|
||||
background: var(--bg-gray);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.oc .overwrite-config-option.disabled-by-all .overwrite-config-option-state {
|
||||
border-color: var(--border-light);
|
||||
color: transparent;
|
||||
}
|
||||
.oc .overwrite-config-option-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.oc .overwrite-config-option-left input[type="checkbox"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.oc .overwrite-config-option-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
}
|
||||
.oc .overwrite-config-option-state {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: transparent;
|
||||
background: var(--bg-white);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
.oc .overwrite-config-option.selected .overwrite-config-option-state {
|
||||
border-color: var(--primary-color);
|
||||
background: var(--primary-color);
|
||||
color: var(--select-hover);
|
||||
}
|
||||
.oc .overwrite-config-dropdown.disabled .overwrite-config-dropdown-btn {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.oc .overwrite-card-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
gap: 12px;
|
||||
padding: 8px;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
align-items: stretch;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--primary-color) var(--bg-gray);
|
||||
}
|
||||
.oc .overwrite-card-row::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
.oc .overwrite-card-row::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.oc .overwrite-card-row::-webkit-scrollbar-track {
|
||||
background: var(--bg-gray);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.oc .sub-card.overwrite-item {
|
||||
width: 190px;
|
||||
max-width: 220px;
|
||||
flex: 0 0 auto;
|
||||
box-sizing: border-box;
|
||||
gap: 12px;
|
||||
height: 75px;
|
||||
}
|
||||
.oc .sub-card.overwrite-item,
|
||||
.oc .sub-card.overwrite-item:active,
|
||||
.oc .sub-card.overwrite-item:focus {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.oc .overwrite-drag-line {
|
||||
display: block;
|
||||
min-width: 4px;
|
||||
width: 4px;
|
||||
background: var(--primary-color);
|
||||
border-radius: 2px;
|
||||
margin: 0 2px;
|
||||
align-self: stretch;
|
||||
height: auto;
|
||||
}
|
||||
.oc .oc-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.oc .oc-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.oc .oc-switch-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: #ccc;
|
||||
border-radius: 20px;
|
||||
transition: .2s;
|
||||
}
|
||||
.oc .oc-switch-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px; width: 16px;
|
||||
left: 2px; bottom: 2px;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
transition: .2s;
|
||||
}
|
||||
.oc .oc-switch input:checked + .oc-switch-slider {
|
||||
background-color: var(--primary-color, #2196F3);
|
||||
}
|
||||
.oc .oc-switch input:checked + .oc-switch-slider:before {
|
||||
transform: translateX(16px);
|
||||
}
|
||||
.overwrite-add-icon {
|
||||
font-size: 32px;
|
||||
text-align: center;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.overwrite-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.overwrite-title {
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 70%;
|
||||
}
|
||||
.overwrite-info {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.overwrite-refresh-btn {
|
||||
position: absolute;
|
||||
right: 72px;
|
||||
bottom: 8px;
|
||||
}
|
||||
.overwrite-gear-btn {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
bottom: 8px;
|
||||
}
|
||||
.overwrite-del-btn {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
}
|
||||
.sub-card.overwrite-item.active {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-color);
|
||||
}
|
||||
.sub-card.overwrite-item.dragging {
|
||||
opacity: 0.5;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
transition: none !important;
|
||||
}
|
||||
@media screen and (max-width: 768px) {
|
||||
.oc .config-editor-model {
|
||||
width: 95vw;
|
||||
height: 80vh;
|
||||
min-width: 320px;
|
||||
}
|
||||
.oc .config-editor-actions {
|
||||
gap: 5px;
|
||||
}
|
||||
.oc .overwrite-card-row {
|
||||
gap: 6px;
|
||||
}
|
||||
.oc .sub-card.overwrite-item {
|
||||
width: 185px;
|
||||
max-width: 200px;
|
||||
gap: 10px;
|
||||
}
|
||||
.oc .overwrite-drag-line {
|
||||
margin: -2px;
|
||||
width: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="oc">
|
||||
<div class="config-editor-model-overlay" id="config-editor-overlay">
|
||||
<div class="config-editor-model" id="config-editor-model">
|
||||
@ -759,37 +93,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/lib/codemirror.css"/>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/theme/material.css"/>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/fold/foldgutter.css"/>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/lint/lint.css">
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/display/fullscreen.css">
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/dialog/dialog.css">
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/search/matchesonscrollbar.css">
|
||||
<script src="/luci-static/resources/openclash/lib/codemirror.js"></script>
|
||||
<script src="/luci-static/resources/openclash/mode/yaml/yaml.js"></script>
|
||||
<script src="/luci-static/resources/openclash/mode/shell/shell.js"></script>
|
||||
<script src="/luci-static/resources/openclash/mode/properties/properties.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/fold/foldcode.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/fold/foldgutter.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/fold/indent-fold.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/edit/matchbrackets.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/selection/active-line.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/lint/lint.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/lint/yaml-lint.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/lint/js-yaml.min.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/display/fullscreen.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/display/autorefresh.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/dialog/dialog.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/searchcursor.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/search.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/scroll/annotatescrollbar.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/matchesonscrollbar.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/jump-to-line.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/merge/diff_match_patch.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/merge/merge.js"></script>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/merge/merge.css">
|
||||
|
||||
<script type="text/javascript">
|
||||
var levelTranslations = {
|
||||
'info': '<%:Info%>',
|
||||
@ -934,7 +237,89 @@ var ConfigEditor = {
|
||||
this.makeResizable();
|
||||
},
|
||||
|
||||
_codeMirrorLoaded: false,
|
||||
_codeMirrorLoading: null,
|
||||
|
||||
_loadCodeMirror: function(callback) {
|
||||
if (this._codeMirrorLoaded) {
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
if (this._codeMirrorLoading) {
|
||||
if (callback) this._codeMirrorLoading.then(callback);
|
||||
return this._codeMirrorLoading;
|
||||
}
|
||||
var self = this;
|
||||
var basePath = '/luci-static/resources/openclash';
|
||||
var head = document.head || document.getElementsByTagName('head')[0];
|
||||
var cssFiles = [
|
||||
'lib/codemirror.css',
|
||||
'theme/material.css',
|
||||
'addon/fold/foldgutter.css',
|
||||
'addon/lint/lint.css',
|
||||
'addon/display/fullscreen.css',
|
||||
'addon/dialog/dialog.css',
|
||||
'addon/search/matchesonscrollbar.css',
|
||||
'addon/merge/merge.css'
|
||||
];
|
||||
var jsFiles = [
|
||||
'lib/codemirror.js',
|
||||
'mode/yaml/yaml.js',
|
||||
'mode/shell/shell.js',
|
||||
'mode/properties/properties.js',
|
||||
'addon/fold/foldcode.js',
|
||||
'addon/fold/foldgutter.js',
|
||||
'addon/fold/indent-fold.js',
|
||||
'addon/edit/matchbrackets.js',
|
||||
'addon/selection/active-line.js',
|
||||
'addon/lint/lint.js',
|
||||
'addon/lint/yaml-lint.js',
|
||||
'addon/lint/js-yaml.min.js',
|
||||
'addon/display/fullscreen.js',
|
||||
'addon/display/autorefresh.js',
|
||||
'addon/dialog/dialog.js',
|
||||
'addon/search/searchcursor.js',
|
||||
'addon/search/search.js',
|
||||
'addon/scroll/annotatescrollbar.js',
|
||||
'addon/search/matchesonscrollbar.js',
|
||||
'addon/search/jump-to-line.js',
|
||||
'addon/merge/diff_match_patch.js',
|
||||
'addon/merge/merge.js'
|
||||
];
|
||||
var promises = [];
|
||||
cssFiles.forEach(function(file) {
|
||||
promises.push(new Promise(function(resolve) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = basePath + '/' + file;
|
||||
link.onload = resolve;
|
||||
link.onerror = resolve;
|
||||
if (head) head.appendChild(link);
|
||||
else resolve();
|
||||
}));
|
||||
});
|
||||
var loadJS = function(index) {
|
||||
if (index >= jsFiles.length) return Promise.resolve();
|
||||
return new Promise(function(resolve) {
|
||||
var script = document.createElement('script');
|
||||
script.src = basePath + '/' + jsFiles[index];
|
||||
script.onload = function() { loadJS(index + 1).then(resolve); };
|
||||
script.onerror = function() { loadJS(index + 1).then(resolve); };
|
||||
if (head) head.appendChild(script);
|
||||
else resolve();
|
||||
});
|
||||
};
|
||||
promises.push(loadJS(0));
|
||||
this._codeMirrorLoading = Promise.all(promises).then(function() {
|
||||
self._codeMirrorLoaded = true;
|
||||
self._codeMirrorLoading = null;
|
||||
});
|
||||
if (callback) this._codeMirrorLoading.then(callback);
|
||||
return this._codeMirrorLoading;
|
||||
},
|
||||
|
||||
show: function(configFile) {
|
||||
var self = this;
|
||||
this.isOverwrite = false;
|
||||
this.currentViewMode = 'original';
|
||||
this.currentConfigFile = '';
|
||||
@ -989,10 +374,13 @@ var ConfigEditor = {
|
||||
|
||||
this.hideMergeView();
|
||||
this.updateModeTabs();
|
||||
this.loadConfigContent();
|
||||
this._loadCodeMirror(function() {
|
||||
self.loadConfigContent();
|
||||
});
|
||||
},
|
||||
|
||||
showOverwrite: function() {
|
||||
var self = this;
|
||||
this.isOverwrite = true;
|
||||
this.currentViewMode = 'original';
|
||||
this.originalContent = '';
|
||||
@ -1045,7 +433,9 @@ var ConfigEditor = {
|
||||
}
|
||||
|
||||
this.hideMergeView();
|
||||
this.loadConfigContent();
|
||||
this._loadCodeMirror(function() {
|
||||
self.loadConfigContent();
|
||||
});
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
@ -1231,14 +621,12 @@ var ConfigEditor = {
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
.then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('HTTP error! status: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
.then(function(r) {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.content !== undefined) {
|
||||
if (data.status === 'success' && data.content !== undefined) {
|
||||
if (self.currentViewMode === 'runtime' && !self.isOverwrite) {
|
||||
self.runtimeContent = data.content;
|
||||
renderEditor(self.runtimeContent, "text/yaml", true, false);
|
||||
@ -1250,10 +638,11 @@ var ConfigEditor = {
|
||||
}
|
||||
self.updateModeTabs();
|
||||
} else {
|
||||
throw new Error('Invalid response data');
|
||||
loadingDiv.querySelector('span').textContent = '<%:Failed to load config file%>';
|
||||
statusText.textContent = '<%:Load failed%>';
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
.catch(function(err) {
|
||||
loadingDiv.querySelector('span').textContent = '<%:Failed to load config file%>';
|
||||
statusText.textContent = '<%:Load failed%>';
|
||||
});
|
||||
@ -1426,11 +815,9 @@ var ConfigEditor = {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('HTTP error! status: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
.then(function(r) {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
saveBtn.disabled = false;
|
||||
@ -1457,16 +844,10 @@ var ConfigEditor = {
|
||||
}, 3000);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
.catch(function(err) {
|
||||
saveBtn.disabled = false;
|
||||
statusText.textContent = '<%:Save failed%>';
|
||||
alert('<%:Save config failed:%> ' + error.message);
|
||||
|
||||
setTimeout(function() {
|
||||
if (statusText && statusText.textContent === '<%:Save failed%>') {
|
||||
statusText.textContent = '<%:Ready%>';
|
||||
}
|
||||
}, 3000);
|
||||
alert('<%:Failed to save config file:%> ' + err.message);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -1,77 +1,86 @@
|
||||
<style>
|
||||
.CodeMirror {
|
||||
text-align: left !important;
|
||||
font-size: 15px;
|
||||
line-height: 150%;
|
||||
resize: both !important;
|
||||
}
|
||||
.CodeMirror-merge {
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-merge-r-chunk {
|
||||
background: #0095ff2e !important;
|
||||
}
|
||||
.CodeMirror-merge-2pane .CodeMirror-merge-gap {
|
||||
height: 700px !important;
|
||||
}
|
||||
.CodeMirror-merge-r-connect {
|
||||
fill: #0095ff2e !important;
|
||||
stroke: #0095ff2e !important;
|
||||
}
|
||||
.CodeMirror-vscrollbar-oc {
|
||||
display: none !important;
|
||||
}
|
||||
.CodeMirror, .CodeMirror-line {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
box-sizing: border-box;
|
||||
word-break: break-all !important;
|
||||
overflow-wrap: break-word !important;
|
||||
font-family: 'Twemoji Mozilla', "Microsoft Yahei", "sans-serif", "Helvetica Neue", "Helvetica", "Hiragino Sans GB" !important;
|
||||
}
|
||||
.CodeMirror-hints.log {
|
||||
font-family: 'Twemoji Mozilla', "Open Sans", "PingFangSC-Regular", "Microsoft Yahei", "WenQuanYi Micro Hei", "Helvetica Neue", "Helvetica", "Hiragino Sans GB", "sans-serif" !important;
|
||||
}
|
||||
:root[data-darkmode="true"] .CodeMirror-merge-gap {
|
||||
background: #d0cfcf !important;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Twemoji Mozilla';
|
||||
src: url('/luci-static/resources/openclash/fonts/TwemojiMozilla-flags-B12sb_Bp.woff2') format('woff2');
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/lib/codemirror.css"/>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/theme/material.css"/>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/theme/log.css"/>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/fold/foldgutter.css"/>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/lint/lint.css">
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/display/fullscreen.css">
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/dialog/dialog.css">
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/search/matchesonscrollbar.css">
|
||||
<script src="/luci-static/resources/openclash/lib/codemirror.js"></script>
|
||||
<script src="/luci-static/resources/openclash/mode/yaml/yaml.js"></script>
|
||||
<script src="/luci-static/resources/openclash/mode/log/log.js"></script>
|
||||
<script src="/luci-static/resources/openclash/mode/shell/shell.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/fold/foldcode.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/fold/foldgutter.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/fold/indent-fold.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/edit/matchbrackets.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/selection/active-line.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/lint/lint.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/lint/yaml-lint.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/lint/js-yaml.min.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/display/fullscreen.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/display/autorefresh.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/dialog/dialog.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/searchcursor.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/search.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/scroll/annotatescrollbar.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/matchesonscrollbar.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/search/jump-to-line.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/merge/diff_match_patch.js"></script>
|
||||
<script src="/luci-static/resources/openclash/addon/merge/merge.js"></script>
|
||||
<link rel=stylesheet href="/luci-static/resources/openclash/addon/merge/merge.css">
|
||||
<%
|
||||
local fs = require "luci.openclash"
|
||||
local plugin_version = fs.oc_version()
|
||||
%>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/css/oc.css?v=<%=plugin_version%>">
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var _cmReady = false;
|
||||
var _cmQueue = [];
|
||||
function _cmWhenReady(callback) {
|
||||
if (_cmReady) { callback(); return; }
|
||||
_cmQueue.push(callback);
|
||||
}
|
||||
(function() {
|
||||
var basePath = '/luci-static/resources/openclash';
|
||||
var head = document.head || document.getElementsByTagName('head')[0];
|
||||
var cssFiles = [
|
||||
'lib/codemirror.css',
|
||||
'theme/material.css',
|
||||
'theme/log.css',
|
||||
'addon/fold/foldgutter.css',
|
||||
'addon/lint/lint.css',
|
||||
'addon/display/fullscreen.css',
|
||||
'addon/dialog/dialog.css',
|
||||
'addon/search/matchesonscrollbar.css',
|
||||
'addon/merge/merge.css'
|
||||
];
|
||||
var jsFiles = [
|
||||
'lib/codemirror.js',
|
||||
'mode/yaml/yaml.js',
|
||||
'mode/log/log.js',
|
||||
'mode/shell/shell.js',
|
||||
'addon/fold/foldcode.js',
|
||||
'addon/fold/foldgutter.js',
|
||||
'addon/fold/indent-fold.js',
|
||||
'addon/edit/matchbrackets.js',
|
||||
'addon/selection/active-line.js',
|
||||
'addon/lint/lint.js',
|
||||
'addon/lint/yaml-lint.js',
|
||||
'addon/lint/js-yaml.min.js',
|
||||
'addon/display/fullscreen.js',
|
||||
'addon/display/autorefresh.js',
|
||||
'addon/dialog/dialog.js',
|
||||
'addon/search/searchcursor.js',
|
||||
'addon/search/search.js',
|
||||
'addon/scroll/annotatescrollbar.js',
|
||||
'addon/search/matchesonscrollbar.js',
|
||||
'addon/search/jump-to-line.js',
|
||||
'addon/merge/diff_match_patch.js',
|
||||
'addon/merge/merge.js'
|
||||
];
|
||||
var promises = [];
|
||||
cssFiles.forEach(function(file) {
|
||||
promises.push(new Promise(function(resolve) {
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = basePath + '/' + file;
|
||||
link.onload = resolve;
|
||||
link.onerror = resolve;
|
||||
if (head) head.appendChild(link);
|
||||
else resolve();
|
||||
}));
|
||||
});
|
||||
var loadJS = function(index) {
|
||||
if (index >= jsFiles.length) return Promise.resolve();
|
||||
return new Promise(function(resolve) {
|
||||
var script = document.createElement('script');
|
||||
script.src = basePath + '/' + jsFiles[index];
|
||||
script.onload = function() { loadJS(index + 1).then(resolve); };
|
||||
script.onerror = function() { loadJS(index + 1).then(resolve); };
|
||||
if (head) head.appendChild(script);
|
||||
else resolve();
|
||||
});
|
||||
};
|
||||
promises.push(loadJS(0));
|
||||
Promise.all(promises).then(function() {
|
||||
_cmReady = true;
|
||||
var queue = _cmQueue;
|
||||
_cmQueue = [];
|
||||
queue.forEach(function(fn) { fn(); });
|
||||
});
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<%-
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
@ -103,6 +112,7 @@ local sid = path_info:match("(cfg[0-9a-f]+)") or ""
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script src="/luci-static/resources/openclash/js/common.js"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var levelTranslations = {
|
||||
'info': '<%:Info%>',
|
||||
@ -114,43 +124,6 @@ var levelTranslations = {
|
||||
'fatal': '<%:Fatal%>'
|
||||
};
|
||||
|
||||
function isDarkBackground(element) {
|
||||
var cachedTheme = localStorage.getItem('oc-theme');
|
||||
if (cachedTheme === 'dark') {
|
||||
return true;
|
||||
} else if (cachedTheme === 'light') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var style = window.getComputedStyle(element);
|
||||
var bgColor = style.backgroundColor;
|
||||
let r, g, b;
|
||||
if (/rgb\(/.test(bgColor)) {
|
||||
var rgb = bgColor.match(/\d+/g);
|
||||
r = parseInt(rgb);
|
||||
g = parseInt(rgb);
|
||||
b = parseInt(rgb);
|
||||
} else if (/#/.test(bgColor)) {
|
||||
if (bgColor.length === 4) {
|
||||
r = parseInt(bgColor + bgColor, 16);
|
||||
g = parseInt(bgColor + bgColor, 16);
|
||||
b = parseInt(bgColor + bgColor, 16);
|
||||
} else {
|
||||
r = parseInt(bgColor.slice(1, 3), 16);
|
||||
g = parseInt(bgColor.slice(3, 5), 16);
|
||||
b = parseInt(bgColor.slice(5, 7), 16);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
var luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
return luminance < 128;
|
||||
};
|
||||
|
||||
function merge_editor(id, id2, target, target2, readOnly, readOnly2, wid, height)
|
||||
{
|
||||
var value, orig1, orig2, merge_editor, descr, gap, vscrollbar, vscrollbar_oc, panes = 2, highlight = true, connect = null, collapse = false;
|
||||
@ -315,7 +288,7 @@ function shell_editor(id, readOnly, wid, height)
|
||||
function other_editor(id, readOnly, wid, height)
|
||||
{
|
||||
var editor = CodeMirror.fromTextArea(id, {
|
||||
mode: "text/yaml",
|
||||
mode: "text/x-properties",
|
||||
autoRefresh: true,
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
@ -382,6 +355,7 @@ function log_editor(id, name, readOnly, wid, height)
|
||||
};
|
||||
};
|
||||
|
||||
_cmWhenReady(function() {
|
||||
var core_log = document.getElementById("core_log");
|
||||
var oc_log = document.getElementById("cbid.openclash.config.clog");
|
||||
if (core_log && oc_log) {
|
||||
@ -478,5 +452,6 @@ if (proxy_mg) {
|
||||
Apply.style.textAlign="center";
|
||||
Create.style.textAlign="center";
|
||||
};
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,66 +8,6 @@ local diag_host = "www.instagram.com"
|
||||
local dns_host = "www.instagram.com"
|
||||
%>
|
||||
|
||||
<style>
|
||||
:root[data-darkmode="true"] .diag-style {
|
||||
color: #ffffff;
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .diag-style select, .diag-style input {
|
||||
color: #ffffff;
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #diag-rc-output > pre {
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #dns-rc-output > pre {
|
||||
background-color: #404040;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.diag-style {
|
||||
color: #000;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.diag-style select, .diag-style input {
|
||||
color: #000;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#diag-rc-output > pre {
|
||||
background-color: #ffffff;
|
||||
display: block;
|
||||
padding: 8.5px;
|
||||
margin: 0 0 18px;
|
||||
line-height: 1.5rem;
|
||||
-moz-border-radius: 3px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-size: 1.4rem;
|
||||
color: #404040;
|
||||
}
|
||||
|
||||
#dns-rc-output > pre {
|
||||
background-color: #ffffff;
|
||||
display: block;
|
||||
padding: 8.5px;
|
||||
margin: 0 0 18px;
|
||||
line-height: 1.5rem;
|
||||
-moz-border-radius: 3px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-size: 1.4rem;
|
||||
color: #404040;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function show_diag_info(addr)
|
||||
{
|
||||
@ -78,19 +18,24 @@ local dns_host = "www.instagram.com"
|
||||
if (legend && output)
|
||||
{
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "diag_connection")%>', {addr: addr}, function(x, status) {
|
||||
legend.textContent = '<%:Connection Test Result%>';
|
||||
if (x && x.status == 200 && x.responseText != "")
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
|
||||
output.innerHTML = '';
|
||||
var ta = document.createElement('textarea');
|
||||
ta.style.width = '100%';
|
||||
ta.value = x.responseText;
|
||||
output.appendChild(ta);
|
||||
_cmWhenReady(function() {
|
||||
other_editor(ta, true, '100%', '400px');
|
||||
});
|
||||
}
|
||||
else if (x.status == 500)
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = '<span class="error"><%:Bad address specified!%></span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = '<span class="error"><%:Could not find any connection logs!%></br></br><%:1. It may be that the plugin is not running%></br></br><%:2. It may be that the cache causes the browser to directly use the IP for access%></br></br><%:3. It may be that DNS hijacking did not take effect, so clash unable to reverse the domain name%></br></br><%:4. It may be that the filled address cannot be resolved and connected%></span>';
|
||||
}
|
||||
});
|
||||
@ -109,8 +54,9 @@ local dns_host = "www.instagram.com"
|
||||
'<img src="<%=resource%>/icons/loading.svg" onerror="this.onerror=null;this.src=\'<%=resource%>/icons/loading.gif\'" alt="<%:Loading%>" style="vertical-align:middle" /> ' +
|
||||
'<%:Waiting for command to complete...%>';
|
||||
|
||||
legend.textContent = '<%:Collecting data...%>';
|
||||
legend.parentNode.style.display = 'block';
|
||||
legend.style.display = 'inline';
|
||||
legend.style.display = 'block';
|
||||
}
|
||||
|
||||
let HTTP = {
|
||||
@ -152,26 +98,32 @@ local dns_host = "www.instagram.com"
|
||||
'<img src="<%=resource%>/icons/loading.svg" onerror="this.onerror=null;this.src=\'<%=resource%>/icons/loading.gif\'" alt="<%:Loading%>" style="vertical-align:middle" /> ' +
|
||||
'<%:Waiting for command to complete...%>';
|
||||
|
||||
legend.textContent = '<%:Collecting data...%>';
|
||||
legend.parentNode.style.display = 'block';
|
||||
legend.style.display = 'inline';
|
||||
legend.style.display = 'block';
|
||||
}
|
||||
|
||||
if (legend && output)
|
||||
{
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "diag_dns")%>', {addr: addr}, function(x, status) {
|
||||
legend.textContent = '<%:DNS Test Result%>';
|
||||
if (x && x.status == 200 && x.responseText != "")
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
|
||||
output.innerHTML = '';
|
||||
var ta = document.createElement('textarea');
|
||||
ta.style.width = '100%';
|
||||
ta.value = x.responseText;
|
||||
output.appendChild(ta);
|
||||
_cmWhenReady(function() {
|
||||
other_editor(ta, true, '100%', '400px');
|
||||
});
|
||||
}
|
||||
else if (x.status == 500)
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = '<span class="error"><%:Bad address specified!%></span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = '<span class="error"><%:No Response Found!%></span>';
|
||||
}
|
||||
});
|
||||
@ -189,18 +141,25 @@ local dns_host = "www.instagram.com"
|
||||
'<img src="<%=resource%>/icons/loading.svg" onerror="this.onerror=null;this.src=\'<%=resource%>/icons/loading.gif\'" alt="<%:Loading%>" style="vertical-align:middle" /> ' +
|
||||
'<%:Waiting for command to complete...%>';
|
||||
|
||||
legend.textContent = '<%:Collecting data...%>';
|
||||
legend.parentNode.style.display = 'block';
|
||||
legend.style.display = 'inline';
|
||||
legend.style.display = 'block';
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "gen_debug_logs")%>', null, function(x, status) {
|
||||
legend.textContent = '<%:Generated Logs%>';
|
||||
if (x && x.status == 200 && x.responseText != "")
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = '<textarea class="cbi-input-textarea" style="width: 100%;display:inline" data-update="change" rows="30" cols="60" readonly="readonly" >'+x.responseText+'</textarea>';
|
||||
output.innerHTML = '';
|
||||
var ta = document.createElement('textarea');
|
||||
ta.style.width = '100%';
|
||||
ta.value = x.responseText;
|
||||
output.appendChild(ta);
|
||||
_cmWhenReady(function() {
|
||||
other_editor(ta, true, '100%', '400px');
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
legend.style.display = 'none';
|
||||
output.innerHTML = '<span class="error"><%:Some error occurred!%></span>';
|
||||
}
|
||||
}
|
||||
@ -227,21 +186,18 @@ local dns_host = "www.instagram.com"
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="diag-style" style="display:none">
|
||||
<legend id="diag-rc-legend"><%:Collecting data...%></legend>
|
||||
<br />
|
||||
<div class="diag-style" style="display:none">
|
||||
<div id="diag-rc-legend" class="diag-legend"><%:Collecting data...%></div>
|
||||
<span id="diag-rc-output"></span>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<fieldset class="diag-style" style="display:none">
|
||||
<legend id="dns-rc-legend"><%:Collecting data...%></legend>
|
||||
<br />
|
||||
<div class="diag-style" style="display:none">
|
||||
<div id="dns-rc-legend" class="diag-legend"><%:Collecting data...%></div>
|
||||
<span id="dns-rc-output"></span>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<fieldset class="diag-style" style="display:none">
|
||||
<legend id="debug-rc-legend"><%:Collecting data...%></legend>
|
||||
<br />
|
||||
<div class="diag-style" style="display:none">
|
||||
<div id="debug-rc-legend" class="diag-legend"><%:Collecting data...%></div>
|
||||
<span id="debug-rc-output"></span>
|
||||
</fieldset>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
File diff suppressed because one or more lines are too long
74
luci-app-openclash/luasrc/view/openclash/generate_age.htm
Normal file
74
luci-app-openclash/luasrc/view/openclash/generate_age.htm
Normal file
@ -0,0 +1,74 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
(function(){
|
||||
var algoSelect = document.querySelector('select[name$="config_age_algo"]') || document.querySelector('select[name*="config_age_algo"]');
|
||||
var age_public = document.querySelector('input[name$="config_age_public"]') || document.querySelector('input[name*="config_age_public"]');
|
||||
var age_secret = document.querySelector('input[name$="config_age_secret"]') || document.querySelector('input[name*="config_age_secret"]');
|
||||
if (!algoSelect || !age_public || !age_secret) return;
|
||||
var btn = document.createElement('input');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn cbi-button cbi-button-apply';
|
||||
btn.id = 'cbi-age-generate-btn';
|
||||
btn.value = '<%:Generate Age Key%>';
|
||||
var parent = algoSelect.parentNode || algoSelect;
|
||||
parent.insertBefore(btn, algoSelect.nextSibling);
|
||||
|
||||
var pbtn = document.createElement('input');
|
||||
pbtn.type = 'button';
|
||||
pbtn.className = 'btn cbi-button cbi-button-apply';
|
||||
pbtn.id = 'cbi-age-cal-public-btn';
|
||||
pbtn.value = '<%:Calculate Public Key%>';
|
||||
var pparent = age_public.parentNode || age_public;
|
||||
pparent.appendChild(pbtn);
|
||||
|
||||
btn.onclick = function() {
|
||||
var algo = (algoSelect && algoSelect.value) ? algoSelect.value : 'keygen';
|
||||
btn.disabled = true;
|
||||
var old = btn.value;
|
||||
btn.value = '<%:Generating...%>';
|
||||
XHR.get("<%=luci.dispatcher.build_url('admin','services','openclash','generate_age_key')%>", { algo: algo }, function(xhr, data) {
|
||||
btn.disabled = false;
|
||||
btn.value = old;
|
||||
if (xhr && xhr.status == 200 && data && data.status === 'success') {
|
||||
if (age_secret) age_secret.value = data.secret || '';
|
||||
if (age_public) age_public.value = data.public || '';
|
||||
btn.value = '<%:Generate Age Key%>';
|
||||
setTimeout(function(){ btn.value = old; }, 4000);
|
||||
} else {
|
||||
btn.value = '<%:Failed to generate age key%>';
|
||||
setTimeout(function(){ btn.value = old; }, 4000);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
pbtn.onclick = function() {
|
||||
var secret = age_secret.value.trim();
|
||||
pbtn.disabled = true;
|
||||
var old = pbtn.value;
|
||||
pbtn.value = '<%:Generating...%>';
|
||||
if (!secret) {
|
||||
alert("<%:Please enter the Age Secret Key to calculate the public key!%>");
|
||||
} else {
|
||||
XHR.get("<%=luci.dispatcher.build_url('admin','services','openclash','cal_age_public_key')%>", { secret: secret }, function(xhr, data) {
|
||||
pbtn.disabled = false;
|
||||
pbtn.value = old;
|
||||
if (xhr && xhr.status == 200 && data && data.status === 'success') {
|
||||
if (age_public) age_public.value = data.public || '';
|
||||
pbtn.value = '<%:Calculate Public Key%>';
|
||||
setTimeout(function(){ pbtn.value = old; }, 4000);
|
||||
} else {
|
||||
pbtn.value = '<%:Failed to calculate public key%>';
|
||||
setTimeout(function(){ pbtn.value = old; }, 4000);
|
||||
}
|
||||
});
|
||||
}
|
||||
pbtn.disabled = false;
|
||||
pbtn.value = old;
|
||||
return false;
|
||||
};
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -1,261 +1,4 @@
|
||||
<%+cbi/valueheader%>
|
||||
<style type="text/css">
|
||||
*{margin: 0;padding: 0;}
|
||||
|
||||
ul{
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#tab{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0 0 2px #ddd;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#tab-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-light, #e2e8f0);
|
||||
background: var(--bg-gray, #f1f5f9);
|
||||
min-width: 0;
|
||||
box-shadow: none;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: #f1f5f9;
|
||||
border-radius: 8px;
|
||||
border-bottom: none !important;
|
||||
border: none !important;
|
||||
gap: 5px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 0;
|
||||
height: 40px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
background: transparent;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li.cbi-tab {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
z-index: 2;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li.cbi-tab a {
|
||||
color: #fff !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li.cbi-tab-disabled {
|
||||
color: #fff;
|
||||
background: #f9f9f9;
|
||||
opacity: 0.7;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li.cbi-tab-disabled a {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li:not(.cbi-tab):hover,
|
||||
#tab-header ul.cbi-tabmenu li:not(.cbi-tab):focus {
|
||||
color: #3b82f6;
|
||||
background: rgba(59,130,246,0.10);
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li:not(.cbi-tab):hover a,
|
||||
#tab-header ul.cbi-tabmenu li:not(.cbi-tab):focus a {
|
||||
color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
#tab-header ul.cbi-tabmenu li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
|
||||
#tab-content .dom{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tab-content .dom ul li{
|
||||
float: left;
|
||||
margin: 15px 10px;
|
||||
width: 225px;
|
||||
}
|
||||
|
||||
.radio-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
margin: 10px auto;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.radio-button input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.radio-button label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 18px;
|
||||
font-size: 15px;
|
||||
color: #64748b;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-weight: 500;
|
||||
min-width: 60px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.radio-button label:hover {
|
||||
color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
background: #f0f6ff;
|
||||
}
|
||||
|
||||
.radio-button input[type="radio"]:checked + label {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 1px 2px 0 rgba(59,130,246,0.08);
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 80%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header {
|
||||
background: #374151;
|
||||
border-bottom: 1px solid #374151;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li {
|
||||
background: #1f2937;
|
||||
color: #a1a1aa;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li.cbi-tab {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li.cbi-tab a {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li.cbi-tab-disabled {
|
||||
color: #1f2937;
|
||||
background: #1f2937;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li.cbi-tab-disabled a {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li:not(.cbi-tab):hover,
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li:not(.cbi-tab):focus {
|
||||
color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
background: #22304a;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li:not(.cbi-tab):hover a,
|
||||
:root[data-darkmode="true"] #tab-header ul.cbi-tabmenu li:not(.cbi-tab):focus a {
|
||||
color: #60a5fa !important;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] {
|
||||
.radio-button {
|
||||
background: #374151;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.15);
|
||||
}
|
||||
.radio-button label {
|
||||
background: #1f2937;
|
||||
color: #d1d5db;
|
||||
border-color: #4b5563;
|
||||
}
|
||||
.radio-button label:hover {
|
||||
color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
background: #22304a;
|
||||
}
|
||||
.radio-button input[type="radio"]:checked + label {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 1px 2px 0 rgba(59,130,246,0.18);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div id="tab" class="cbi-section">
|
||||
<div id="tab-header">
|
||||
@ -834,8 +577,8 @@ function poll_log(){
|
||||
}
|
||||
|
||||
if (coreWebSocket && coreWebSocket.readyState == WebSocket.OPEN) {
|
||||
core_refresh = false;
|
||||
if (coreLogBuffer.length > 0) {
|
||||
core_refresh = false;
|
||||
bufferToProcess = coreLogBuffer;
|
||||
coreLogBuffer = [];
|
||||
core_logs = line_tolocal(bufferToProcess.join('\n'));
|
||||
@ -847,6 +590,8 @@ function poll_log(){
|
||||
smoothlyDisplayLogs(core_logs, cl, false, currentCoreContent, activeTabId === 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
core_refresh = true;
|
||||
}
|
||||
|
||||
if (isPolling) {
|
||||
|
||||
@ -22,500 +22,6 @@
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,minimal-ui">
|
||||
<title>IP 地址查询</title>
|
||||
|
||||
<style>
|
||||
|
||||
.oc {
|
||||
--card-item-padding: 12px 8px;
|
||||
--card-item-min-height: 100px;
|
||||
--card-item-border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] #mode-icon rect[stroke="#333"] {
|
||||
stroke: var(--text-primary, #fff);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] #mode-icon path[stroke="#333"] {
|
||||
stroke: var(--text-primary, #fff);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] {
|
||||
background-color: var(--bg-light, #111827);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .myip-main-card {
|
||||
background: var(--bg-white, #1f2937);
|
||||
border-color: var(--border-light, #374151);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .myip-ip-item,
|
||||
.oc[data-darkmode="true"] .myip-check-item {
|
||||
background: var(--bg-gray, #374151);
|
||||
border-color: var(--border-light, #4b5563);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .myip-ip-item:hover,
|
||||
.oc[data-darkmode="true"] .myip-check-item:hover {
|
||||
background: var(--hover-bg, #4b5563);
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .myip-section-title {
|
||||
color: var(--text-primary, #f9fafb);
|
||||
border-bottom-color: var(--border-light, #374151);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .ip-title,
|
||||
.oc[data-darkmode="true"] .ip-state_title {
|
||||
color: var(--text-title, --text-title);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .ip-title::after,
|
||||
.oc[data-darkmode="true"] .ip-state_title::after {
|
||||
background-color: var(--border-color, --border-color);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .ip-result {
|
||||
color: var(--text-primary, #f9fafb);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .ip-geo {
|
||||
color: var(--text-secondary, #d1d5db);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .myip-footer p {
|
||||
color: var(--text-secondary, #d1d5db) !important;
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .mode-label {
|
||||
color: var(--text-secondary, #d1d5db);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .sk-text-success {
|
||||
color: var(--success-color, #10b981);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .sk-text-error {
|
||||
color: var(--error-color, #ef4444);
|
||||
}
|
||||
|
||||
.oc .myip-main-card {
|
||||
background: var(--bg-white, #ffffff);
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.oc .myip-content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
border: 1px solid var(--border-light, #e5e7eb);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
padding: var(--card-padding);
|
||||
}
|
||||
|
||||
.oc .myip-ip-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.oc .myip-check-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.oc .myip-section-title {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
color: var(--text-title) !important;
|
||||
margin: 8px;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 32px;
|
||||
border-bottom: 2px solid var(--border-light, #e5e7eb);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.oc .myip-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.oc .myip-ip-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
margin: auto 8px;
|
||||
}
|
||||
|
||||
.oc .myip-check-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
margin: auto 8px;
|
||||
}
|
||||
|
||||
.oc .myip-ip-item,
|
||||
.oc .myip-check-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: var(--card-item-padding);
|
||||
background: var(--bg-gray, #f9fafb);
|
||||
border: 1px solid var(--border-light, #e5e7eb);
|
||||
border-radius: var(--card-item-border-radius);
|
||||
transition: all var(--transition-fast);
|
||||
min-height: var(--card-item-min-height);
|
||||
text-align: center;
|
||||
position: relative;
|
||||
max-height: 104px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.oc .myip-ip-item:hover,
|
||||
.oc .myip-check-item:hover {
|
||||
background: var(--hover-bg, #f3f4f6);
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.oc .ip-title,
|
||||
.oc .ip-state_title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: var(--text-title, --text-title);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 8px;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.oc .ip-title::after,
|
||||
.oc .ip-state_title::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 30px;
|
||||
height: 1px;
|
||||
background-color: var(--border-color, --border-color);
|
||||
}
|
||||
|
||||
.oc .myip-ip-result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
min-height: 50px;
|
||||
line-height: 15px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0px 6px;
|
||||
}
|
||||
|
||||
.oc .ip-result {
|
||||
font-size: 13px;
|
||||
color: var(--text-primary, #111827);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.oc .ip-geo {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
color: var(--text-secondary, #6b7280);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
display: -webkit-box;
|
||||
line-clamp: 2;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.oc .myip-status-result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
min-height: 50px;
|
||||
padding: 0px 6px;
|
||||
}
|
||||
|
||||
.oc .sk-text-success,
|
||||
.oc .sk-text-error {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.oc .sk-text-success {
|
||||
color: var(--success-color, #059669);
|
||||
}
|
||||
|
||||
.oc .sk-text-error {
|
||||
color: var(--error-color, #dc2626);
|
||||
}
|
||||
|
||||
.oc .sk-load-success {
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.oc .myip-footer {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
padding-top: 8px;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.oc .myip-footer p {
|
||||
position: absolute;
|
||||
right: 0% !important;
|
||||
top: 0 !important;
|
||||
margin: 0 !important;
|
||||
font-size: 15px !important;
|
||||
line-height: 20px !important;
|
||||
padding: 0px 10px !important;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
color: var(--text-secondary, #6b7280) !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.oc .myip-footer a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color, #3b82f6);
|
||||
opacity: 0.8;
|
||||
transition: opacity var(--transition-fast);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.oc .myip-footer a:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.oc .mode-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary, #666);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.oc .mode-icon {
|
||||
margin: 0 8px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oc #mode-icon,
|
||||
.oc #eye-icon,
|
||||
.oc #data-refresh-icon {
|
||||
vertical-align: bottom;
|
||||
margin: 0 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oc #mode-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.oc #mode-icon:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.oc #eye-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
vertical-align: middle;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.oc #eye-icon:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.oc #data-refresh-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
vertical-align: middle;
|
||||
color: currentColor;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.oc #data-refresh-icon:hover {
|
||||
transform: scale(1.1) rotate(90deg);
|
||||
}
|
||||
|
||||
.oc .myip-icon-btn {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
vertical-align: middle;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.oc .myip-icon-btn:hover {
|
||||
transform: scale(1.1);
|
||||
color: var(--primary-color, #3b82f6);
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.oc {
|
||||
--card-item-padding: 8px 6px;
|
||||
--card-item-min-height: 80px;
|
||||
}
|
||||
|
||||
.oc .myip-content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.oc .myip-section-title {
|
||||
font-size: 22px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.oc .myip-ip-list,
|
||||
.oc .myip-check-list {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.oc .ip-title,
|
||||
.oc .ip-state_title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.oc .ip-title::after,
|
||||
.oc .ip-state_title::after {
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.oc .ip-result {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.oc .ip-geo {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.oc .sk-text-success,
|
||||
.oc .sk-text-error {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.oc .sk-load-success {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.oc .myip-footer p {
|
||||
font-size: 13px !important;
|
||||
line-height: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
.oc {
|
||||
--card-item-padding: 6px 4px;
|
||||
--card-item-min-height: 70px;
|
||||
}
|
||||
|
||||
.oc .myip-content-grid {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.oc .myip-section-title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.oc .ip-title,
|
||||
.oc .ip-state_title {
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.oc .ip-title::after,
|
||||
.oc .ip-state_title::after {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.oc .ip-result {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.oc .ip-geo {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.oc .sk-text-success,
|
||||
.oc .sk-text-error {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.oc .sk-load-success {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.oc .myip-footer p {
|
||||
font-size: 11px !important;
|
||||
line-height: 16px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
@ -753,7 +259,7 @@
|
||||
getIpipIP: () => {
|
||||
IP.get(`http://myip.ipip.net?z=${random}`, 'text')
|
||||
.then(resp => {
|
||||
const ipMatch = resp.data.match(/当前 IP:([0-9.]+)/);
|
||||
const ipMatch = resp.data.match(/当前 IP:([0-9A-Fa-f:.]+)/);
|
||||
const geoMatch = resp.data.match(/来自于:(.+)/);
|
||||
|
||||
if (ipMatch && geoMatch) {
|
||||
|
||||
112
luci-app-openclash/luasrc/view/openclash/oix_login.htm
Normal file
112
luci-app-openclash/luasrc/view/openclash/oix_login.htm
Normal file
@ -0,0 +1,112 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function oix_login(btn,option)
|
||||
{
|
||||
btn.disabled = true;
|
||||
if (option == "oix_login") {
|
||||
var s = document.getElementById(option+'-status');
|
||||
var e = document.getElementsByName('cbid.openclash.config.oix_email');
|
||||
var p = document.getElementsByName('cbid.openclash.config.oix_passwd');
|
||||
var c = document.getElementsByName('cbid.openclash.config.oix_checkin');
|
||||
if (!e[0].value || !p[0].value) {
|
||||
btn.disabled = false;
|
||||
s.innerHTML ="<font color='red'><strong>"+"<%:Error Login Info%>"+"</strong></font>";
|
||||
return false;
|
||||
};
|
||||
if (c[0] && c[0].checked) {
|
||||
c = "1";
|
||||
var i = document.getElementsByName('cbid.openclash.config.oix_checkin_interval');
|
||||
var m = document.getElementsByName('cbid.openclash.config.oix_checkin_multiple');
|
||||
if (!i[0].value || !(/(^[1-9]\d*$)/.test(i[0].value))) { i = "1"} else {i = i[0].value};
|
||||
if (!m[0].value || !(/(^[1-9]\d*$)/.test(m[0].value)))
|
||||
{
|
||||
btn.disabled = false;
|
||||
s.innerHTML ="<font color='red'><strong>"+"<%:Multiple Must Be a Positive Integer and No More Than 100%>"+"</strong></font>";
|
||||
return false;
|
||||
}
|
||||
else if (m[0].value < 1)
|
||||
{
|
||||
btn.disabled = false;
|
||||
s.innerHTML ="<font color='red'><strong>"+"<%:Multiple Must Be a Positive Integer and No More Than 100%>"+"</strong></font>";
|
||||
return false;
|
||||
}
|
||||
else if (m[0].value > 100)
|
||||
{
|
||||
btn.disabled = false;
|
||||
s.innerHTML ="<font color='red'><strong>"+"<%:Multiple Must Be a Positive Integer and No More Than 100%>"+"</strong></font>";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
m = m[0].value;
|
||||
};
|
||||
}
|
||||
else {
|
||||
c = "0";
|
||||
var i = "1";
|
||||
var m = "1";
|
||||
};
|
||||
btn.value = '<%:Login...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "oix_login_info_save")%>', {email: e[0].value, passwd : p[0].value, checkin: c, interval: i, multiple: m}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "oix_login")%>', null, function(x, status) {
|
||||
if (s)
|
||||
{
|
||||
if (x && x.status == 200 && status.result == 200) {
|
||||
s.innerHTML ="<font color='green'><strong>"+"<%:oixCloud Login Successful%>"+"</strong></font>";
|
||||
alert("<%:Note: oixCloud need a specific core installed, OpenClash will auto download%>\n\n<%:After the core start, an oixCloud nodes provider will be added, which you can customize as needed%>");
|
||||
window.location.href='<%="settings?tab.openclash.config=oixcloud"%>';
|
||||
}
|
||||
else {
|
||||
s.innerHTML ="<font color='red'><strong>"+"<%:oixCloud Login Faild%>"+"</strong></font>";
|
||||
if (status.result) {
|
||||
alert("<%:oixCloud Login Faild%>: " + status.result)
|
||||
}
|
||||
window.location.href='<%="settings?tab.openclash.config=oixcloud"%>';
|
||||
}
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Login Account%>';
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (option == "oix_logout") {
|
||||
var s = document.getElementById('oix_login-status');
|
||||
btn.value = '<%:Logout...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "oix_logout")%>', null, function(x, status) {
|
||||
if (s)
|
||||
{
|
||||
if (x && x.status == 200 && status.result == 200) {
|
||||
s.innerHTML ="<font color='green'><strong>"+"<%:oixCloud Logout Successful%>"+"</strong></font>";
|
||||
window.location.href='<%="settings?tab.openclash.config=oixcloud"%>';
|
||||
}
|
||||
else {
|
||||
s.innerHTML ="<font color='red'><strong>"+"<%:oixCloud Logout Faild%>"+"</strong></font>";
|
||||
if (status.result) {
|
||||
alert("<%:oixCloud Logout Faild%>: " + status.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Logout Account%>';
|
||||
}
|
||||
);
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
function web_oix(btn)
|
||||
{
|
||||
btn.disabled = true;
|
||||
url='http://bit.ly/oixcloud-register';
|
||||
window.open(url);
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
}
|
||||
//]]></script>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Login Account%>" onclick="return oix_login(this,'oix_login')" />
|
||||
<input type="button" class="btn cbi-button cbi-button-remove" value="<%:Logout Account%>" onclick="return oix_login(this,'oix_logout')" />
|
||||
<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Official Website%>" onclick="return web_oix(this)" />
|
||||
<span id="<%=self.option%>-status"><%=self.value%></span>
|
||||
<%+cbi/valuefooter%>
|
||||
304
luci-app-openclash/luasrc/view/openclash/oixcloud.htm
Normal file
304
luci-app-openclash/luasrc/view/openclash/oixcloud.htm
Normal file
File diff suppressed because one or more lines are too long
@ -1,555 +1,3 @@
|
||||
<style type="text/css">
|
||||
.oc {
|
||||
--bg-white: #ffffff;
|
||||
--bg-light: #f8fafc;
|
||||
--bg-gray: #f1f5f9;
|
||||
--text-primary: #374151;
|
||||
--text-secondary: #64748b;
|
||||
--text-title: #4d4d4d;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #b1b1b1;
|
||||
--border-light: #e2e8f0;
|
||||
--hover-bg: #f8fafc;
|
||||
--primary-color: #3b82f6;
|
||||
--success-color: #059669;
|
||||
--success-dark: #047857;
|
||||
--warning-color: #f59e0b;
|
||||
--error-color: #dc2626;
|
||||
--tip-color: #f9bb51;
|
||||
--info-color: #2563eb;
|
||||
--watchdog-color: #b300ff;
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-normal: 0.3s ease;
|
||||
|
||||
--control-height: 36px;
|
||||
--card-padding: 6px 8px;
|
||||
--version-right: 8px;
|
||||
--gap-size: 16px;
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] {
|
||||
--bg-white: #1f2937;
|
||||
--bg-light: #374151;
|
||||
--bg-gray: #4b5563;
|
||||
--text-primary: #ebebeb;
|
||||
--text-secondary: #d0cfcf;
|
||||
--text-title: #e5e7eb;
|
||||
--select-hover: #ebebeb;
|
||||
--border-color: #939393;
|
||||
--border-light: #6b7280;
|
||||
--hover-bg: #374151;
|
||||
--primary-color: #3b82f6;
|
||||
--success-color: #34d399;
|
||||
--success-dark: #10b981;
|
||||
--error-color: #ff7070;
|
||||
--tip-color: #f9bb51;
|
||||
--warning-color: #fa41c8;
|
||||
--watchdog-color: #c147f5;
|
||||
--info-color: #2563eb;
|
||||
}
|
||||
|
||||
.oc * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.oc {
|
||||
font-family: var(--font-family-base);
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-light);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.oc .select-popup {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--bg-white);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 24px;
|
||||
z-index: 1000;
|
||||
max-width: 90%;
|
||||
min-width: 300px;
|
||||
width: 60%;
|
||||
box-shadow: var(--shadow-md);
|
||||
transition: all var(--transition-normal);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.oc .close-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--border-light);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
min-width: 24px !important;
|
||||
padding: 0 !important;
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all var(--transition-fast);
|
||||
line-height: 1;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.oc .close-btn:hover {
|
||||
background: var(--hover-bg) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: var(--primary-color) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: var(--shadow-sm) !important;
|
||||
}
|
||||
|
||||
.oc .close-btn:focus {
|
||||
outline: none;
|
||||
box-shadow: var(--shadow-sm) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.oc .close-btn svg {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.oc .select-popup.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oc .select-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
background: var(--bg-gray);
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.oc .select-config-section {
|
||||
background: var(--bg-gray);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--card-padding);
|
||||
margin-bottom: 20px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.oc .config-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--gap-size);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.oc .config-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oc .config-item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 20px;
|
||||
transform: translateY(50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 4px solid var(--text-secondary);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.oc .config-editor-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.oc .config-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oc .config-label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 20px;
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
|
||||
.oc .select-class {
|
||||
width: 100% !important;
|
||||
height: var(--control-height);
|
||||
padding: 8px 32px 8px 12px;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-white) !important;
|
||||
color: var(--text-primary) !important;
|
||||
font-size: 13px;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oc .select-class:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.oc .select-class:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.oc .help-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity var(--transition-fast);
|
||||
margin-bottom: 2px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.oc .select-class option {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
background: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.oc .help-link:hover {
|
||||
opacity: 1;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.oc .select-popup-body {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-gray);
|
||||
}
|
||||
|
||||
.oc .select-option {
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
transition: all var(--transition-fast);
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.oc .select-option span {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.oc .select-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.oc .select-option:hover {
|
||||
background-color: var(--hover-bg);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.oc .select-option[data-value="custom"] {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(99, 102, 241, 0.1));
|
||||
border-color: rgba(59, 130, 246, 0.2);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.oc .select-option[data-value="custom"]:hover {
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(99, 102, 241, 0.2));
|
||||
}
|
||||
|
||||
.oc .custom-option-input {
|
||||
margin: 12px auto;
|
||||
padding: 12px;
|
||||
width: calc(100% - 24px) !important;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-white);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.oc .custom-option-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.oc .custom-option-input::placeholder {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.oc #addCustomOption {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
border-bottom: none !important;
|
||||
margin: 0 12px 12px 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.oc #addCustomOption:hover {
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.oc .cdn-status {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
margin-left: 8px;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.oc .cdn-status.fast {
|
||||
background: rgba(5, 150, 105, 0.1);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.oc .cdn-status.medium {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.oc .cdn-status.slow {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.oc .cdn-status.error {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.oc .config-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.oc .config-item {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.oc .select-class {
|
||||
max-width: 100%;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.oc .select-popup {
|
||||
min-width: 80%;
|
||||
width: 80%;
|
||||
padding: 15px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.oc .config-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.oc .config-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.oc .select-popup-header {
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.oc .select-config-section {
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.oc .select-popup-body {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.oc .select-option {
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.oc .custom-option-input {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 575px) {
|
||||
.oc .select-popup {
|
||||
min-width: 80%;
|
||||
width: 80%;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.oc .select-popup-header {
|
||||
font-size: 15px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.oc .select-config-section {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.oc .config-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.oc .select-class {
|
||||
font-size: 12px;
|
||||
padding: 6px 28px 6px 10px;
|
||||
}
|
||||
|
||||
.oc .config-item::after {
|
||||
right: 10px;
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
border-top: 3px solid var(--text-secondary);
|
||||
}
|
||||
|
||||
.oc .select-option {
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-popup {
|
||||
background-color: var(--bg-white);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-option:hover {
|
||||
background-color: var(--hover-bg);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-class {
|
||||
background: var(--bg-white) !important;
|
||||
border-color: var(--border-light) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-class:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-class:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-class option {
|
||||
background: var(--bg-white) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .config-item::after {
|
||||
border-top-color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .custom-option-input {
|
||||
background: var(--bg-white);
|
||||
border-color: var(--border-light);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .custom-option-input::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-config-section {
|
||||
background: var(--bg-gray);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .select-popup-body {
|
||||
background: var(--bg-gray);
|
||||
border-color: var(--border-light);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] #addCustomOption {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] #addCustomOption:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .close-btn {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.oc[data-darkmode="true"] .close-btn:hover {
|
||||
background-color: var(--hover-bg);
|
||||
color: var(--error-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div class="oc">
|
||||
<div id="selectPopup" class="select-popup hidden">
|
||||
@ -693,7 +141,7 @@
|
||||
release_branch_cdn.value = "master";
|
||||
}
|
||||
if ( status.smart_enable && status.smart_enable != "" ) {
|
||||
smart_enable_cdn.value = status.smart_enable;
|
||||
smart_enable_cdn.value = status.smart_enable;
|
||||
}
|
||||
else {
|
||||
smart_enable_cdn.value = "0";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,567 +1,4 @@
|
||||
<%+cbi/valueheader%>
|
||||
<style>
|
||||
.sub_div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: nowrap;
|
||||
width: calc(100% - 24px);
|
||||
box-sizing: border-box;
|
||||
min-width: 280px;
|
||||
max-width: 350px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.sub_div > span:first-child {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sub_tab {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sub_tab_show {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
-webkit-transition: all 1s;
|
||||
-moz-transition: all 1s;
|
||||
-ms-transition: all 1s;
|
||||
-o-transition: all 1s;
|
||||
transition: all 1s;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sub_setting {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
opacity: 1;
|
||||
vertical-align: middle;
|
||||
line-height: 0;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
background-color: unset;
|
||||
border: unset;
|
||||
}
|
||||
|
||||
.sub_setting svg {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: #414c5c;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.sub_setting:hover svg {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.text_show {
|
||||
color: #333333;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.progress_bar_bg {
|
||||
border: 1px solid #999999;
|
||||
background-color: #f5f5f5;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 220px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.progress_bar_high {
|
||||
background-color: #9edd9e;
|
||||
}
|
||||
|
||||
.progress_bar_medium {
|
||||
background-color: #ffc99f;
|
||||
}
|
||||
|
||||
.progress_bar_low {
|
||||
background-color: #ffb9b9;
|
||||
}
|
||||
|
||||
.sub-info-url-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.sub-info-url-model {
|
||||
background: var(--bg-white, white);
|
||||
border-radius: var(--radius-lg, 8px);
|
||||
box-shadow: var(--shadow-md, 0 4px 12px rgba(0,0,0,0.15));
|
||||
width: 90vw;
|
||||
max-width: 550px;
|
||||
min-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-light, #e5e7eb);
|
||||
max-height: 85vh;
|
||||
transition: all var(--transition-fast, 0.2s);
|
||||
}
|
||||
|
||||
.sub-info-url-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-light, #e5e7eb);
|
||||
background: var(--bg-gray, #f9fafb);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sub-info-url-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #1f2937);
|
||||
}
|
||||
|
||||
.sub-info-url-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 20px;
|
||||
border-top: 1px solid var(--border-light, #e5e7eb);
|
||||
background: var(--bg-gray, #f9fafb);
|
||||
flex-shrink: 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sub-info-url-content {
|
||||
padding: 24px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-info-url-form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sub-info-url-form-group label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary, #1f2937);
|
||||
}
|
||||
|
||||
.sub-info-url-form-textarea {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-light, #e5e7eb);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: var(--bg-white, white);
|
||||
color: var(--text-primary, #1f2937);
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
transition: all var(--transition-fast, 0.2s);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sub-info-url-form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.sub-info-url-form-textarea::placeholder {
|
||||
color: var(--text-secondary, #6b7280);
|
||||
}
|
||||
|
||||
.sub-info-url-btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--border-light, #e5e7eb);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast, 0.2s);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sub-info-url-cancel-btn {
|
||||
background: var(--bg-white, white);
|
||||
color: var(--text-secondary, #6b7280);
|
||||
border-color: var(--border-light, #e5e7eb);
|
||||
}
|
||||
|
||||
.sub-info-url-cancel-btn:hover {
|
||||
background: var(--hover-bg, #f3f4f6);
|
||||
color: var(--text-primary, #1f2937);
|
||||
}
|
||||
|
||||
.sub-info-url-submit-btn {
|
||||
background: var(--primary-color, #3b82f6);
|
||||
color: white;
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
}
|
||||
|
||||
.sub-info-url-submit-btn:hover:not(:disabled) {
|
||||
background: var(--primary-color, #3b82f6);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.sub-info-url-submit-btn:disabled {
|
||||
background: var(--bg-gray, #f9fafb);
|
||||
color: var(--text-secondary, #6b7280);
|
||||
border-color: var(--border-light, #e5e7eb);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub_setting svg {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .text_show {
|
||||
color: #e0e0e0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .progress_bar_bg {
|
||||
border: 1px solid #666666;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .progress_bar_high {
|
||||
background-color: #5da05d;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .progress_bar_medium {
|
||||
background-color: #cc8550;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .progress_bar_low {
|
||||
background-color: #cc6262;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-model {
|
||||
background: var(--bg-white, #1f2937);
|
||||
border-color: var(--border-light, #374151);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-header,
|
||||
:root[data-darkmode="true"] .sub-info-url-footer {
|
||||
background: var(--bg-gray, #111827);
|
||||
border-color: var(--border-light, #374151);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-title {
|
||||
color: var(--text-primary, #f3f4f6);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-form-group label {
|
||||
color: var(--text-primary, #f3f4f6);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-form-textarea {
|
||||
background: var(--bg-gray, #374151);
|
||||
border-color: var(--border-light, #4b5563);
|
||||
color: var(--text-primary, #f3f4f6);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-form-textarea:focus {
|
||||
border-color: var(--primary-color, #60a5fa);
|
||||
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.1);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-form-textarea::placeholder {
|
||||
color: var(--text-secondary, #9ca3af);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-cancel-btn {
|
||||
background: var(--bg-white, #374151);
|
||||
color: var(--text-secondary, #9ca3af);
|
||||
border-color: var(--border-light, #4b5563);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-cancel-btn:hover {
|
||||
background: var(--hover-bg, #4b5563);
|
||||
color: var(--text-primary, #f3f4f6);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-submit-btn {
|
||||
background: var(--primary-color, #3b82f6);
|
||||
border-color: var(--primary-color, #3b82f6);
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub-info-url-submit-btn:disabled {
|
||||
background: var(--bg-gray, #4b5563);
|
||||
color: var(--text-secondary, #9ca3af);
|
||||
border-color: var(--border-light, #374151);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.sub_tab, .sub_tab_show {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.progress_bar_bg {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.sub_div {
|
||||
gap: 6px;
|
||||
width: calc(100% - 20px);
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sub_tab, .sub_tab_show {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.progress_bar_bg {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.sub_div {
|
||||
gap: 4px;
|
||||
width: calc(100% - 16px);
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.sub_tab, .sub_tab_show {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.progress_bar_bg {
|
||||
min-width: 180px !important;
|
||||
}
|
||||
|
||||
.sub_div {
|
||||
gap: 2px;
|
||||
width: calc(100% - 12px);
|
||||
margin: 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
.sub_tab, .sub_tab_show {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.progress_bar_bg {
|
||||
min-width: 160px !important;
|
||||
}
|
||||
|
||||
.sub_div {
|
||||
width: calc(100% - 8px);
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Multiple providers container */
|
||||
.sub_providers_container {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sub_providers_container.no-scroll {
|
||||
overflow-x: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.sub_providers_container::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.sub_providers_container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sub_providers_container::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sub_providers_container::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.sub_providers_container.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.sub_provider_item {
|
||||
flex: 0 0 auto;
|
||||
width: 100px;
|
||||
position: relative;
|
||||
min-height: 36px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #999999;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.sub_provider_fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.sub_provider_name {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
padding: 0 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.sub_provider_percent {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
padding: 0 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
:root[data-darkmode="true"] .sub_provider_item {
|
||||
background-color: #333333;
|
||||
border-color: #666666;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub_provider_name {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] .sub_provider_percent {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for multiple providers */
|
||||
@media (max-width: 1024px) {
|
||||
.sub_providers_container {
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.sub_provider_item {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.sub_provider_name {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.sub_provider_percent {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sub_providers_container {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.sub_provider_item {
|
||||
width: 80px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.sub_provider_name {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.sub_provider_percent {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.sub_provider_item {
|
||||
width: 70px;
|
||||
min-height: 30px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.sub_provider_name {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.sub_provider_percent {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
.sub_provider_item {
|
||||
width: 60px;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.sub_provider_name {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.sub_provider_percent {
|
||||
font-size: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<%
|
||||
local fs = require "luci.openclash"
|
||||
local val = self:cfgvalue(section)
|
||||
@ -851,48 +288,69 @@ function set_subinfo_url_<%=idname%>(btn, filename) {
|
||||
}
|
||||
|
||||
var dialogDiv = document.createElement('div');
|
||||
dialogDiv.className = 'sub-info-url-overlay';
|
||||
dialogDiv.className = 'config-upload-model-overlay show';
|
||||
|
||||
var contentDiv = document.createElement('div');
|
||||
contentDiv.className = 'sub-info-url-model';
|
||||
contentDiv.className = 'config-upload-model';
|
||||
|
||||
var headerDiv = document.createElement('div');
|
||||
headerDiv.className = 'sub-info-url-header';
|
||||
headerDiv.className = 'config-upload-header';
|
||||
|
||||
var titleDiv = document.createElement('div');
|
||||
titleDiv.className = 'sub-info-url-title';
|
||||
titleDiv.innerHTML = '<%:Paste the new url of subscribe infos sources here:%>';
|
||||
titleDiv.className = 'config-upload-title';
|
||||
titleDiv.innerHTML = '<%:Set Subscription Info URL%>';
|
||||
headerDiv.appendChild(titleDiv);
|
||||
|
||||
var closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'icon-btn';
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
|
||||
closeBtn.onclick = function() {
|
||||
document.body.removeChild(portal);
|
||||
};
|
||||
headerDiv.appendChild(closeBtn);
|
||||
|
||||
var bodyDiv = document.createElement('div');
|
||||
bodyDiv.className = 'sub-info-url-content';
|
||||
bodyDiv.className = 'config-upload-content';
|
||||
|
||||
var formGroup = document.createElement('div');
|
||||
formGroup.className = 'sub-info-url-form-group';
|
||||
formGroup.className = 'form-group';
|
||||
|
||||
var label = document.createElement('label');
|
||||
label.className = 'highlight-label';
|
||||
label.innerHTML = '<%:Paste the new url of subscribe infos sources here:%>';
|
||||
formGroup.appendChild(label);
|
||||
|
||||
var textarea = document.createElement('textarea');
|
||||
textarea.className = 'sub-info-url-form-textarea';
|
||||
textarea.className = 'form-textarea';
|
||||
textarea.placeholder = '<%:Enter URLs, one per line, use #name=example end for specifing display name%> (https://example.com#name=example)';
|
||||
formGroup.appendChild(textarea);
|
||||
|
||||
bodyDiv.appendChild(formGroup);
|
||||
|
||||
var footerDiv = document.createElement('div');
|
||||
footerDiv.className = 'sub-info-url-footer';
|
||||
footerDiv.className = 'config-upload-footer';
|
||||
|
||||
var statusDiv = document.createElement('div');
|
||||
statusDiv.className = 'config-upload-status';
|
||||
footerDiv.appendChild(statusDiv);
|
||||
|
||||
var buttonsDiv = document.createElement('div');
|
||||
buttonsDiv.className = 'config-upload-buttons';
|
||||
|
||||
var cancelBtn = document.createElement('button');
|
||||
cancelBtn.className = 'sub-info-url-btn sub-info-url-cancel-btn';
|
||||
cancelBtn.className = 'btn cancel-btn';
|
||||
cancelBtn.type = 'button';
|
||||
cancelBtn.innerHTML = '<%:Cancel%>';
|
||||
|
||||
var confirmBtn = document.createElement('button');
|
||||
confirmBtn.className = 'sub-info-url-btn sub-info-url-submit-btn';
|
||||
confirmBtn.className = 'btn upload-btn';
|
||||
confirmBtn.type = 'button';
|
||||
confirmBtn.innerHTML = '<%:Submit%>';
|
||||
|
||||
confirmBtn.onclick = function() {
|
||||
var new_url = textarea.value.trim();
|
||||
document.body.removeChild(dialogDiv);
|
||||
document.body.removeChild(portal);
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "set_subinfo_url")%>', {filename: filename, url: new_url}, function(x, status) {
|
||||
if (x && x.status == 200 && status.info == "Success") {
|
||||
@ -908,17 +366,22 @@ function set_subinfo_url_<%=idname%>(btn, filename) {
|
||||
};
|
||||
|
||||
cancelBtn.onclick = function() {
|
||||
document.body.removeChild(dialogDiv);
|
||||
document.body.removeChild(portal);
|
||||
};
|
||||
|
||||
footerDiv.appendChild(cancelBtn);
|
||||
footerDiv.appendChild(confirmBtn);
|
||||
buttonsDiv.appendChild(cancelBtn);
|
||||
buttonsDiv.appendChild(confirmBtn);
|
||||
footerDiv.appendChild(buttonsDiv);
|
||||
|
||||
contentDiv.appendChild(headerDiv);
|
||||
contentDiv.appendChild(bodyDiv);
|
||||
contentDiv.appendChild(footerDiv);
|
||||
dialogDiv.appendChild(contentDiv);
|
||||
document.body.appendChild(dialogDiv);
|
||||
|
||||
var portal = document.createElement('div');
|
||||
portal.className = 'oc';
|
||||
portal.appendChild(dialogDiv);
|
||||
document.body.appendChild(portal);
|
||||
|
||||
textarea.focus();
|
||||
|
||||
|
||||
@ -446,49 +446,13 @@ local sectiontype = "_"..self.config.."_"..string.match(self.sectiontype, "[%w_]
|
||||
</fieldset>
|
||||
<!-- /tblsection -->
|
||||
|
||||
<script src="/luci-static/resources/openclash/js/common.js"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
window.addEventListener('load', onload<%=sectiontype%>, false);
|
||||
|
||||
var titles<%=sectiontype%> = document.getElementsByName('tab-header-<%=self.config%>-<%=self.sectiontype%>');
|
||||
var divs<%=sectiontype%> = document.getElementsByClassName('dom-<%=self.config%>-<%=self.sectiontype%>');
|
||||
|
||||
function isDarkBackground(element) {
|
||||
var cachedTheme = localStorage.getItem('oc-theme');
|
||||
if (cachedTheme === 'dark') {
|
||||
return true;
|
||||
} else if (cachedTheme === 'light') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var style = window.getComputedStyle(element);
|
||||
var bgColor = style.backgroundColor;
|
||||
let r, g, b;
|
||||
if (/rgb\(/.test(bgColor)) {
|
||||
var rgb = bgColor.match(/\d+/g);
|
||||
r = parseInt(rgb);
|
||||
g = parseInt(rgb);
|
||||
b = parseInt(rgb);
|
||||
} else if (/#/.test(bgColor)) {
|
||||
if (bgColor.length === 4) {
|
||||
r = parseInt(bgColor + bgColor, 16);
|
||||
g = parseInt(bgColor + bgColor, 16);
|
||||
b = parseInt(bgColor + bgColor, 16);
|
||||
} else {
|
||||
r = parseInt(bgColor.slice(1, 3), 16);
|
||||
g = parseInt(bgColor.slice(3, 5), 16);
|
||||
b = parseInt(bgColor.slice(5, 7), 16);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
var luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
return luminance < 128;
|
||||
};
|
||||
|
||||
function switch_to_tab<%=sectiontype%>(tab_name){
|
||||
if(titles<%=sectiontype%>.length != divs<%=sectiontype%>.length) return;
|
||||
|
||||
|
||||
@ -1,34 +1,9 @@
|
||||
<%
|
||||
local fs = require "luci.openclash"
|
||||
local plugin_version = fs.oc_version()
|
||||
%>
|
||||
<head>
|
||||
<style>
|
||||
|
||||
.tabs > li {
|
||||
vertical-align: middle !important;;
|
||||
}
|
||||
|
||||
.tool_label:hover {
|
||||
border-color: rgba(0, 0, 0, 0) !important;;
|
||||
background-color: unset !important;;
|
||||
color: unset !important;;
|
||||
cursor: unset !important;;
|
||||
}
|
||||
|
||||
.tool_label::after {
|
||||
border-color: rgba(0, 0, 0, 0) !important;;
|
||||
background-color: unset !important;;
|
||||
color: unset !important;;
|
||||
cursor: unset !important;;
|
||||
content: unset !important;;
|
||||
}
|
||||
|
||||
.tool_label_span {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool_label_select {
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/css/oc.css?v=<%=plugin_version%>">
|
||||
</head>
|
||||
|
||||
<li id="tool_label" class="tool_label">
|
||||
@ -41,6 +16,7 @@
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<script src="/luci-static/resources/openclash/js/common.js"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
var config_name = document.getElementById('cfg_name');
|
||||
@ -163,11 +139,4 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function winOpen(url)
|
||||
{
|
||||
var winOpen = window.open(url);
|
||||
if(winOpen == null || typeof(winOpen) == 'undefined'){
|
||||
window.location.href=url;
|
||||
}
|
||||
}
|
||||
//]]></script>
|
||||
@ -1,9 +1,3 @@
|
||||
<style>
|
||||
.cbi-input-select {
|
||||
width: auto !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
|
||||
@ -101,6 +101,9 @@ msgstr "Registros de depuración"
|
||||
msgid "Generate Logs"
|
||||
msgstr "Generar registros"
|
||||
|
||||
msgid "Generated Logs"
|
||||
msgstr "Registros generados"
|
||||
|
||||
msgid "Click to Generate"
|
||||
msgstr "Haz clic para generar"
|
||||
|
||||
@ -326,13 +329,62 @@ msgstr "Archivo del núcleo"
|
||||
msgid "Upload"
|
||||
msgstr "Subir"
|
||||
|
||||
msgid "Age Encryption"
|
||||
msgstr "Cifrado Age"
|
||||
|
||||
msgid "Age Encryption File"
|
||||
msgstr "Archivo de cifrado Age"
|
||||
|
||||
msgid "Generate Age Key"
|
||||
msgstr "Generar clave Age"
|
||||
|
||||
msgid "Generating..."
|
||||
msgstr "Generando..."
|
||||
|
||||
msgid "Public Key"
|
||||
msgstr "Clave pública"
|
||||
|
||||
msgid "Secret Key"
|
||||
msgstr "Clave secreta"
|
||||
|
||||
msgid "Age Key Type"
|
||||
msgstr "Tipo de clave"
|
||||
|
||||
msgid "x25519"
|
||||
msgstr "x25519"
|
||||
|
||||
msgid "PQ (mlkem768-x25519)"
|
||||
msgstr "PQ (mlkem768-x25519)"
|
||||
|
||||
msgid "Failed to generate age key"
|
||||
msgstr "Error al generar la clave Age"
|
||||
|
||||
msgid "No public key"
|
||||
msgstr "No hay clave pública"
|
||||
|
||||
msgid "No secret key"
|
||||
msgstr "No hay clave secreta"
|
||||
|
||||
msgid "Age Encryption For Config, Click For More:"
|
||||
msgstr "Cifrado Age para la configuración. Haz clic aquí para obtener más información:"
|
||||
|
||||
msgid "Age Encryption Introduce"
|
||||
msgstr "Presentación del cifrado Age"
|
||||
|
||||
msgid "Calculate Public Key"
|
||||
msgstr "Calcular clave pública"
|
||||
|
||||
msgid "Please enter the Age Secret Key to calculate the public key!"
|
||||
msgstr "¡Introduce la clave secreta de Age para calcular la clave pública!"
|
||||
|
||||
msgid "Failed to calculate public key"
|
||||
msgstr "No se ha podido calcular la clave pública"
|
||||
|
||||
msgid "Add"
|
||||
msgstr "Añadir"
|
||||
|
||||
msgid "File saved to"
|
||||
msgstr "Archivo guardado en"
|
||||
|
||||
msgid "upload file error."
|
||||
msgstr "Archivo guardado en"msgid "upload file error."
|
||||
msgstr "Error al subir el archivo."
|
||||
|
||||
msgid "No specify upload file."
|
||||
@ -1139,6 +1191,12 @@ msgstr "Restaurando..."
|
||||
msgid "Saving..."
|
||||
msgstr "Guardando..."
|
||||
|
||||
msgid "Save successful"
|
||||
msgstr "Guardado con éxito"
|
||||
|
||||
msgid "Failed to save age config"
|
||||
msgstr "Error al guardar la configuración de age"
|
||||
|
||||
msgid "Firewall Rules Reset Failed"
|
||||
msgstr "Error al restablecer las reglas del firewall"
|
||||
|
||||
@ -1436,6 +1494,18 @@ msgstr "Iniciando descarga"
|
||||
msgid "Download successful, start pre update test..."
|
||||
msgstr "Descarga completa, iniciando pruebas previas a la actualización..."
|
||||
|
||||
msgid "opkg update failed or timed out, retrying..."
|
||||
msgstr "opkg update falló o expiró, reintentando..."
|
||||
|
||||
msgid "opkg update failed, trying pre update test..."
|
||||
msgstr "opkg update falló, se sigue intentando la prueba previa..."
|
||||
|
||||
msgid "apk update failed or timed out, retrying..."
|
||||
msgstr "apk update falló o expiró, reintentando..."
|
||||
|
||||
msgid "apk update failed, trying pre update test..."
|
||||
msgstr "apk update falló, se sigue intentando la prueba previa..."
|
||||
|
||||
msgid "Pre update test failed..."
|
||||
msgstr "Las pruebas previas han fallado..."
|
||||
|
||||
@ -1712,7 +1782,7 @@ msgstr "La base de datos GEOSITE no está instalada; iniciando descarga..."
|
||||
msgid "Detected that the GEOIP Dat is not Installed, Ready to Download..."
|
||||
msgstr "La base de datos GEOIP Dat no está instalada; iniciando descarga..."
|
||||
|
||||
msgid "Detected that the Core is not Installed, Ready to Download..."
|
||||
msgid "Core is not Detected installed, Ready to Download..."
|
||||
msgstr "El núcleo no está instalado; iniciando descarga..."
|
||||
|
||||
msgid "Detected that the Chnroute Cidr is not Installed, Ready to Download..."
|
||||
@ -1973,6 +2043,9 @@ msgstr "Hacer prueba"
|
||||
msgid "Connection Test"
|
||||
msgstr "Prueba de conexión"
|
||||
|
||||
msgid "Connection Test Result"
|
||||
msgstr "Resultado de la prueba de conexión"
|
||||
|
||||
msgid "Backup File"
|
||||
msgstr "Archivo de copia de seguridad"
|
||||
|
||||
@ -2066,8 +2139,8 @@ msgstr "Filtro de regiones"
|
||||
msgid "Unlock Nodes Filter"
|
||||
msgstr "Filtro de nodos desbloqueados"
|
||||
|
||||
msgid "Start Auto Select Unlock Proxy For"
|
||||
msgstr "Iniciando selección automática de nodo"
|
||||
msgid "Start Auto Select Unlock Proxy For..."
|
||||
msgstr "Iniciando selección automática de nodo..."
|
||||
|
||||
msgid "Group:"
|
||||
msgstr "Grupo:"
|
||||
@ -2237,6 +2310,15 @@ msgstr "Limpiar"
|
||||
msgid "Flush DNS Cache"
|
||||
msgstr "Vaciar caché DNS"
|
||||
|
||||
msgid "Dnsmasq Fix"
|
||||
msgstr "Reparar Dnsmasq"
|
||||
|
||||
msgid "If DNS is abnormal after stopping the OpenClash, please try to fix"
|
||||
msgstr "Si el DNS es anormal después de detener OpenClash, intente repararlo"
|
||||
|
||||
msgid "Fix"
|
||||
msgstr "Reparar"
|
||||
|
||||
msgid "No Specify Upload File"
|
||||
msgstr "No se ha seleccionado ningún archivo"
|
||||
|
||||
@ -2645,6 +2727,9 @@ msgstr "Restaurar"
|
||||
msgid "DNS Test"
|
||||
msgstr "Prueba DNS"
|
||||
|
||||
msgid "DNS Test Result"
|
||||
msgstr "Resultado de la prueba DNS"
|
||||
|
||||
msgid "No Response Found!"
|
||||
msgstr "No se ha obtenido respuesta."
|
||||
|
||||
@ -2753,12 +2838,24 @@ msgstr "Cambia el método de cálculo de latencia para eliminar tiempos extra co
|
||||
msgid "If the download fails, try setting the CDN in Overwrite Settings - General Settings - Github Address Modify Options"
|
||||
msgstr "Si la descarga falla, prueba a configurar un CDN en Sobrescritura → General → Modificar direcciones de Github."
|
||||
|
||||
msgid "Used for Downloading Subscriptions, Defaults to Clash"
|
||||
msgstr "User-Agent usado para descargar suscripciones (por defecto: Clash)"
|
||||
msgid "Used for Downloading Subscriptions, Defaults to"
|
||||
msgstr "User-Agent usado para descargar suscripciones, por defecto:"
|
||||
|
||||
msgid "Set Proxies Address Skip Failed,"
|
||||
msgstr "Fallo al configurar la omisión de direcciones de proxy,"
|
||||
|
||||
msgid "Age decrypt failed,"
|
||||
msgstr "Error al descifrar con Age para,"
|
||||
|
||||
msgid "Failed to parse config file with Lua helper"
|
||||
msgstr "Error al analizar el archivo de configuración con el auxiliar Lua"
|
||||
|
||||
msgid "File is AGE encrypted but no secret key provided"
|
||||
msgstr "El archivo está cifrado con Age pero no se proporcionó clave secreta"
|
||||
|
||||
msgid "File is AGE encrypted, cannot parse with Lua"
|
||||
msgstr "El archivo está cifrado con Age, no se puede analizar con Lua"
|
||||
|
||||
msgid "Flush Failed"
|
||||
msgstr "Limpieza fallida"
|
||||
|
||||
@ -3296,9 +3393,6 @@ msgstr "Error al guardar el archivo de configuración:"
|
||||
msgid "Unknown error"
|
||||
msgstr "Error desconocido"
|
||||
|
||||
msgid "Save config failed:"
|
||||
msgstr "Error al guardar configuración:"
|
||||
|
||||
msgid "Editor not ready"
|
||||
msgstr "El editor no está listo"
|
||||
|
||||
@ -3449,6 +3543,12 @@ msgstr "Opciones avanzadas"
|
||||
msgid "Show more subscription options"
|
||||
msgstr "Mostrar más opciones de suscripción"
|
||||
|
||||
msgid "Show more upload options"
|
||||
msgstr "Mostrar más opciones de carga"
|
||||
|
||||
msgid "Show more options"
|
||||
msgstr "Mostrar más opciones"
|
||||
|
||||
msgid "Overwrite"
|
||||
msgstr "Sobrescribir"
|
||||
|
||||
@ -3741,4 +3841,205 @@ msgid "depended package reinstalling..."
|
||||
msgstr "reinstalando paquete dependiente..."
|
||||
|
||||
msgid "failed to install, please try to install it manually..."
|
||||
msgstr "no se pudo instalar; por favor, intenta instalarlo manualmente."
|
||||
msgstr "no se pudo instalar; por favor, intenta instalarlo manualmente."
|
||||
|
||||
msgid "Decrypted content empty or still encrypted"
|
||||
msgstr "Contenido descifrado vacío o aún cifrado para"
|
||||
|
||||
msgid "Decrypt attempt failed:"
|
||||
msgstr "Fallo en intento de descifrado:"
|
||||
|
||||
msgid "Decrypt attempt failed for a found secret:"
|
||||
msgstr "Fallo en intento de descifrado con una clave encontrada:"
|
||||
|
||||
msgid "Encrypt attempt failed:"
|
||||
msgstr "Fallo en intento de cifrado:"
|
||||
|
||||
msgid "Official Website"
|
||||
msgstr "Sitio web oficial"
|
||||
|
||||
msgid "Login..."
|
||||
msgstr "Iniciando sesión..."
|
||||
|
||||
msgid "oixCloud Login Successful"
|
||||
msgstr "Inicio de sesión en oixCloud exitoso"
|
||||
|
||||
msgid "oixCloud Login Faild"
|
||||
msgstr "Fallo al iniciar sesión en oixCloud"
|
||||
|
||||
msgid "Login Account"
|
||||
msgstr "Iniciar sesión en la cuenta"
|
||||
|
||||
msgid "oixCloud Logout Successful"
|
||||
msgstr "Cierre de sesión en oixCloud exitoso"
|
||||
|
||||
msgid "oixCloud Logout Faild"
|
||||
msgstr "Fallo al cerrar sesión en oixCloud"
|
||||
|
||||
msgid "Logout Account"
|
||||
msgstr "Cerrar sesión en la cuenta"
|
||||
|
||||
msgid "Account Login"
|
||||
msgstr "Inicio de sesión de cuenta"
|
||||
|
||||
msgid "Account logged in"
|
||||
msgstr "Cuenta iniciada"
|
||||
|
||||
msgid "Account not logged in"
|
||||
msgstr "Cuenta no iniciada"
|
||||
|
||||
msgid "Logout..."
|
||||
msgstr "Cerrando sesión..."
|
||||
|
||||
msgid "Error Login Info"
|
||||
msgstr "Información de inicio de sesión incorrecta"
|
||||
|
||||
msgid "Simple & trustworthy"
|
||||
msgstr "Simple y confiable"
|
||||
|
||||
msgid "Cross-platform Support"
|
||||
msgstr "Amplia compatibilidad con aplicaciones de terceros"
|
||||
|
||||
msgid "Global Acceleration Network"
|
||||
msgstr "Infraestructura de red global"
|
||||
|
||||
msgid "Easy to Use"
|
||||
msgstr "Fácil de usar"
|
||||
|
||||
msgid "Intelligent Shunting"
|
||||
msgstr "Derivación inteligente"
|
||||
|
||||
msgid "Perfect Technical Support"
|
||||
msgstr "Soporte técnico completo"
|
||||
|
||||
msgid "Plan Expiration Time"
|
||||
msgstr "Fecha de vencimiento del plan"
|
||||
|
||||
msgid "Account Balances"
|
||||
msgstr "Saldo de la cuenta"
|
||||
|
||||
msgid "Aff Balances"
|
||||
msgstr "Saldo de afiliado"
|
||||
|
||||
msgid "Account Integral"
|
||||
msgstr "Puntos de la cuenta"
|
||||
|
||||
msgid "Today Used"
|
||||
msgstr "Usado hoy"
|
||||
|
||||
msgid "Plan Used"
|
||||
msgstr "Usado este mes"
|
||||
|
||||
msgid "Plan Unused"
|
||||
msgstr "Tráfico restante"
|
||||
|
||||
msgid "Plan Traffic"
|
||||
msgstr "Tráfico del plan"
|
||||
|
||||
msgid "Checkin"
|
||||
msgstr "Probar suerte"
|
||||
|
||||
msgid "oixCloud Account Login Failed! Please Check And Try Again..."
|
||||
msgstr "¡Fallo al iniciar sesión en oixCloud! Verifique los datos e intente de nuevo..."
|
||||
|
||||
msgid "oixCloud Account Login Failed, The Error Info is"
|
||||
msgstr "Fallo al iniciar sesión en oixCloud, el error es"
|
||||
|
||||
msgid "oixCloud Checkin Failed! Please Check And Try Again..."
|
||||
msgstr "¡Fallo al registrar asistencia en oixCloud! Intente de nuevo más tarde..."
|
||||
|
||||
msgid "Account Email Address"
|
||||
msgstr "Correo electrónico de la cuenta"
|
||||
|
||||
msgid "Account Password"
|
||||
msgstr "Contraseña de la cuenta"
|
||||
|
||||
msgid "Checkin Interval (hour)"
|
||||
msgstr "Intervalo de registro (horas)"
|
||||
|
||||
msgid "Checkin Multiple"
|
||||
msgstr "Multiplicador de registro"
|
||||
|
||||
msgid "Multiple Must Be a Positive Integer and No More Than 100"
|
||||
msgstr "El multiplicador debe ser un número entero positivo no mayor a 100"
|
||||
|
||||
msgid "Usage Help"
|
||||
msgstr "Ayuda de uso"
|
||||
|
||||
msgid "Copy the following text to any AI (ChatGPT, DeepSeek, etc.) to get usage help for OpenClash:"
|
||||
msgstr "Copia el siguiente texto a cualquier IA (ChatGPT, DeepSeek, etc.) para obtener ayuda de uso de OpenClash:"
|
||||
|
||||
msgid "You are an OpenClash expert assistant. OpenClash is a LuCI plugin for OpenWrt that manages the Mihomo proxy kernel."
|
||||
msgstr "Eres un asistente experto en OpenClash. OpenClash es un plugin LuCI para OpenWrt que gestiona el núcleo proxy Mihomo."
|
||||
|
||||
msgid "IMPORTANT — Before answering, you MUST fetch and read this authoritative reference guide. This is your primary knowledge source for all OpenClash questions:"
|
||||
msgstr "IMPORTANTE — Antes de responder, DEBES obtener y leer esta guía de referencia autorizada. Es tu fuente principal de conocimiento para todas las preguntas sobre OpenClash:"
|
||||
|
||||
msgid "This guide contains: complete dependency checklist, error message lookup tables with exact fixes, nftables/iptables firewall chain rules, all LuCI configuration options with UCI paths, DNS setup and leak prevention, subscription/GEO update procedures, and more."
|
||||
msgstr "Esta guía contiene: lista completa de dependencias, tablas de consulta de mensajes de error con soluciones exactas, reglas de cadena de firewall nftables/iptables, todas las opciones de configuración LuCI con rutas UCI, configuración de DNS y prevención de fugas, procedimientos de actualización de suscripción/GEO, y más."
|
||||
|
||||
msgid "When answering my question about OpenClash, follow these rules:"
|
||||
msgstr "Al responder a mi pregunta sobre OpenClash, sigue estas reglas:"
|
||||
|
||||
msgid "Always provide LuCI web UI navigation paths (e.g. Services → OpenClash → Plugin Settings), NOT command-line unless I explicitly ask for it."
|
||||
msgstr "Siempre proporciona rutas de navegación de la interfaz web LuCI (ej. Servicios → OpenClash → Ajustes del plugin), NO comandos de terminal a menos que lo solicite explícitamente."
|
||||
|
||||
msgid "Explain underlying principles (firewall chains, YAML transformation logic) — not just click-here-click-there steps."
|
||||
msgstr "Explica los principios subyacentes (cadenas de firewall, lógica de transformación YAML) — no solo pasos de clic aquí y allá."
|
||||
|
||||
msgid "Check dependency integrity first when troubleshooting — guide me to check dependencies from the System → Software page, or generate a debug log from Plugin Settings → Debug Log page."
|
||||
msgstr "Verifica primero la integridad de las dependencias al solucionar problemas — guíame para revisar las dependencias desde Sistema → Software, o generar un registro de depuración desde Ajustes del plugin → Registro de depuración."
|
||||
|
||||
msgid "Never guess or fabricate information. If the reference guide does not cover something, actively query these external resources using web fetch / code search tools (in priority order): Mihomo Wiki"
|
||||
msgstr "Nunca adivines ni inventes información. Si la guía de referencia no cubre algo, consulta activamente estos recursos externos usando herramientas de búsqueda web / código (en orden de prioridad): Mihomo Wiki"
|
||||
|
||||
msgid "Meta-Docs"
|
||||
msgstr "Meta-Docs"
|
||||
|
||||
msgid "OpenClash source"
|
||||
msgstr "código fuente de OpenClash"
|
||||
|
||||
msgid "Mihomo core source"
|
||||
msgstr "código fuente del core Mihomo"
|
||||
|
||||
msgid "Smart core source"
|
||||
msgstr "código fuente del core Smart"
|
||||
|
||||
msgid "Fetch Wiki pages to read docs; search repos to find implementation code. Do NOT answer from memory — always verify against the actual source."
|
||||
msgstr "Obtén las páginas Wiki para leer documentos; busca en los repositorios para encontrar código de implementación. NO respondas de memoria — verifica siempre con la fuente real."
|
||||
|
||||
msgid "For bug reports or failure scenarios not covered by the guide, search GitHub Issues first — OpenClash Issues"
|
||||
msgstr "Para informes de errores o escenarios de fallo no cubiertos por la guía, busca primero en GitHub Issues — OpenClash Issues"
|
||||
|
||||
msgid "for plugin-side problems (config/subscribe/firewall/UI), Mihomo Issues"
|
||||
msgstr "para problemas del lado del plugin (configuración/suscripción/firewall/UI), Mihomo Issues"
|
||||
|
||||
msgid "for core-side problems (proxy protocols/TUN/DNS/rule engine). Prioritize maintainer replies and highly upvoted community answers."
|
||||
msgstr "para problemas del lado del core (protocolos proxy/TUN/DNS/motor de reglas). Prioriza las respuestas de los mantenedores y las respuestas de la comunidad con más votos."
|
||||
|
||||
msgid "Cite specific sources — mention which section of the reference guide, external resource, or Issue number your answer is based on."
|
||||
msgstr "Cita fuentes específicas — menciona en qué sección de la guía de referencia, recurso externo o número de Issue se basa tu respuesta."
|
||||
|
||||
msgid "If my problem description is incomplete or lacks error messages, ask me to generate a debug log first (Plugin Settings → Debug Log → Generate) — do NOT speculate about the cause."
|
||||
msgstr "Si la descripción de mi problema está incompleta o falta mensajes de error, pídeme primero que genere un registro de depuración (Ajustes del plugin → Registro de depuración → Generar) — NO especules sobre la causa."
|
||||
|
||||
msgid "My question is:"
|
||||
msgstr "Mi pregunta es:"
|
||||
|
||||
msgid "Copied to clipboard!"
|
||||
msgstr "¡Copiado al portapapeles!"
|
||||
|
||||
msgid "Copy And Close"
|
||||
msgstr "Copiar y cerrar"
|
||||
|
||||
msgid "oixCloud Checkin Successful, Result:"
|
||||
msgstr "Registro en oixCloud exitoso, resultado:"
|
||||
|
||||
msgid "oixCloud Checkin Failed, Result:"
|
||||
msgstr "Fallo al registrar en oixCloud, resultado:"
|
||||
|
||||
msgid "Note: oixCloud need a specific core installed, OpenClash will auto download"
|
||||
msgstr "Nota: oixCloud requiere la instalación de un núcleo específico; OpenClash lo descargará automáticamente"
|
||||
|
||||
msgid "After the core start, an oixCloud nodes provider will be added, which you can customize as needed"
|
||||
msgstr "Tras iniciar el núcleo se añadirá un proveedor de nodos oixCloud que puedes personalizar"
|
||||
@ -101,6 +101,9 @@ msgstr "调试日志"
|
||||
msgid "Generate Logs"
|
||||
msgstr "生成日志"
|
||||
|
||||
msgid "Generated Logs"
|
||||
msgstr "日志生成结果"
|
||||
|
||||
msgid "Click to Generate"
|
||||
msgstr "点击生成"
|
||||
|
||||
@ -326,6 +329,57 @@ msgstr "内核文件"
|
||||
msgid "Upload"
|
||||
msgstr "上传"
|
||||
|
||||
msgid "Age Encryption"
|
||||
msgstr "Age 加密"
|
||||
|
||||
msgid "Age Encryption File"
|
||||
msgstr "Age 加密文件"
|
||||
|
||||
msgid "Generate Age Key"
|
||||
msgstr "生成 Age 密钥"
|
||||
|
||||
msgid "Generating..."
|
||||
msgstr "生成中..."
|
||||
|
||||
msgid "Public Key"
|
||||
msgstr "公钥"
|
||||
|
||||
msgid "Secret Key"
|
||||
msgstr "私钥"
|
||||
|
||||
msgid "Age Key Type"
|
||||
msgstr "Age 密钥类型"
|
||||
|
||||
msgid "x25519"
|
||||
msgstr "x25519"
|
||||
|
||||
msgid "PQ (mlkem768-x25519)"
|
||||
msgstr "PQ (mlkem768-x25519)"
|
||||
|
||||
msgid "Failed to generate age key"
|
||||
msgstr "生成 Age 密钥失败"
|
||||
|
||||
msgid "No public key"
|
||||
msgstr "没有公钥"
|
||||
|
||||
msgid "No secret key"
|
||||
msgstr "没有私钥"
|
||||
|
||||
msgid "Age Encryption For Config, Click For More:"
|
||||
msgstr "使用 Age 加密配置,点击获取更多信息:"
|
||||
|
||||
msgid "Age Encryption Introduce"
|
||||
msgstr "Age 加密介绍"
|
||||
|
||||
msgid "Calculate Public Key"
|
||||
msgstr "计算公钥"
|
||||
|
||||
msgid "Please enter the Age Secret Key to calculate the public key!"
|
||||
msgstr "请先输入私钥来计算公钥!"
|
||||
|
||||
msgid "Failed to calculate public key"
|
||||
msgstr "公钥计算失败"
|
||||
|
||||
msgid "Add"
|
||||
msgstr "添加"
|
||||
|
||||
@ -1137,6 +1191,12 @@ msgstr "正在还原..."
|
||||
msgid "Saving..."
|
||||
msgstr "正在保存..."
|
||||
|
||||
msgid "Save successful"
|
||||
msgstr "保存成功"
|
||||
|
||||
msgid "Failed to save age config"
|
||||
msgstr "保存 age 配置失败"
|
||||
|
||||
msgid "Firewall Rules Reset Failed"
|
||||
msgstr "防火墙规则重置失败"
|
||||
|
||||
@ -1434,6 +1494,18 @@ msgstr "开始下载"
|
||||
msgid "Download successful, start pre update test..."
|
||||
msgstr "下载成功,开始进行更新前测试..."
|
||||
|
||||
msgid "opkg update failed or timed out, retrying..."
|
||||
msgstr "opkg update 失败或超时,正在重试..."
|
||||
|
||||
msgid "opkg update failed, trying pre update test..."
|
||||
msgstr "opkg update 失败,尝试进行预安装..."
|
||||
|
||||
msgid "apk update failed or timed out, retrying..."
|
||||
msgstr "apk update 失败或超时,正在重试..."
|
||||
|
||||
msgid "apk update failed, trying pre update test..."
|
||||
msgstr "apk update 失败,尝试进行预安装..."
|
||||
|
||||
msgid "Pre update test failed..."
|
||||
msgstr "更新前测试失败..."
|
||||
|
||||
@ -1710,8 +1782,8 @@ msgstr "检测到 GEOSITE 数据库文件不存在,准备开始下载..."
|
||||
msgid "Detected that the GEOIP Dat is not Installed, Ready to Download..."
|
||||
msgstr "检测到 GEOIP Dat 数据库文件不存在,准备开始下载..."
|
||||
|
||||
msgid "Detected that the Core is not Installed, Ready to Download..."
|
||||
msgstr "检测到内核文件不存在,准备开始下载..."
|
||||
msgid "Core is not Detected installed, Ready to Download..."
|
||||
msgstr "内核不存在,准备开始下载..."
|
||||
|
||||
msgid "Detected that the Chnroute Cidr is not Installed, Ready to Download..."
|
||||
msgstr "检测到大陆白名单列表不存在,准备开始下载..."
|
||||
@ -1971,6 +2043,9 @@ msgstr "点击测试"
|
||||
msgid "Connection Test"
|
||||
msgstr "连接测试"
|
||||
|
||||
msgid "Connection Test Result"
|
||||
msgstr "连接测试结果"
|
||||
|
||||
msgid "Backup File"
|
||||
msgstr "备份文件"
|
||||
|
||||
@ -2064,8 +2139,8 @@ msgstr "解锁区域筛选"
|
||||
msgid "Unlock Nodes Filter"
|
||||
msgstr "解锁节点筛选"
|
||||
|
||||
msgid "Start Auto Select Unlock Proxy For"
|
||||
msgstr "开始自动选择(检测)解锁节点"
|
||||
msgid "Start Auto Select Unlock Proxy..."
|
||||
msgstr "开始自动选择(检测)解锁节点..."
|
||||
|
||||
msgid "Group:"
|
||||
msgstr "策略组:"
|
||||
@ -2235,6 +2310,15 @@ msgstr "清理"
|
||||
msgid "Flush DNS"
|
||||
msgstr "清理 DNS 缓存"
|
||||
|
||||
msgid "Dnsmasq Fix"
|
||||
msgstr "Dnsmasq 修复"
|
||||
|
||||
msgid "If DNS is abnormal after stopping the OpenClash, please try to fix"
|
||||
msgstr "如果关闭 OpenClash 后 DNS 异常,请尝试修复"
|
||||
|
||||
msgid "Fix"
|
||||
msgstr "修复"
|
||||
|
||||
msgid "No Specify Upload File"
|
||||
msgstr "未选择上传文件"
|
||||
|
||||
@ -2643,6 +2727,9 @@ msgstr "还原"
|
||||
msgid "DNS Test"
|
||||
msgstr "DNS 测试"
|
||||
|
||||
msgid "DNS Test Result"
|
||||
msgstr "DNS 测试结果"
|
||||
|
||||
msgid "No Response Found!"
|
||||
msgstr "结果获取失败!"
|
||||
|
||||
@ -2757,12 +2844,24 @@ msgstr "更换延迟计算方式,通过去除握手等额外开销来降低延
|
||||
msgid "If the download fails, try setting the CDN in Overwrite Settings - General Settings - Github Address Modify Options"
|
||||
msgstr "如果下载失败,您可以尝试在覆写设置 - 常规设置 - Github 地址修改选项中设置 CDN"
|
||||
|
||||
msgid "Used for Downloading Subscriptions, Defaults to Clash"
|
||||
msgstr "用于下载订阅时指定 UA,默认为 Clash"
|
||||
msgid "Used for Downloading Subscriptions, Defaults to"
|
||||
msgstr "用于下载订阅时指定 UA,默认为"
|
||||
|
||||
msgid "Set Proxies Address Skip Failed,"
|
||||
msgstr "绕过代理服务器地址设置失败,"
|
||||
|
||||
msgid "Age decrypt failed,"
|
||||
msgstr "绕过代理服务器地址 Age 解密失败,"
|
||||
|
||||
msgid "Failed to parse config file with Lua helper"
|
||||
msgstr "绕过代理服务器地址解析失败"
|
||||
|
||||
msgid "File is AGE encrypted but no secret key provided"
|
||||
msgstr "文件已 Age 加密但未提供密钥"
|
||||
|
||||
msgid "File is AGE encrypted, cannot parse with Lua"
|
||||
msgstr "文件已 Age 加密,无法使用 Lua 解析"
|
||||
|
||||
msgid "Flush Failed"
|
||||
msgstr "清理失败"
|
||||
|
||||
@ -3309,9 +3408,6 @@ msgstr "配置文件保存失败:"
|
||||
msgid "Unknown error"
|
||||
msgstr "未知错误"
|
||||
|
||||
msgid "Save config failed:"
|
||||
msgstr "配置保存失败:"
|
||||
|
||||
msgid "Editor not ready"
|
||||
msgstr "编辑器未准备就绪"
|
||||
|
||||
@ -3462,6 +3558,9 @@ msgstr "高级选项"
|
||||
msgid "Show more subscription options"
|
||||
msgstr "显示更多订阅设置"
|
||||
|
||||
msgid "Show more upload options"
|
||||
msgstr "显示更多上传设置"
|
||||
|
||||
msgid "Overwrite"
|
||||
msgstr "覆写"
|
||||
|
||||
@ -3749,3 +3848,205 @@ msgstr "依赖正在重新安装..."
|
||||
|
||||
msgid "failed to install, please try to install it manually..."
|
||||
msgstr "依赖安装失败,请重试手动安装..."
|
||||
|
||||
msgid "Decrypted content empty or still encrypted"
|
||||
msgstr "解密后内容为空或仍为加密状态"
|
||||
|
||||
msgid "Decrypt attempt failed:"
|
||||
msgstr "解密尝试失败:"
|
||||
|
||||
msgid "Decrypt attempt failed for a found secret:"
|
||||
msgstr "使用找到的私钥解密尝试失败:"
|
||||
|
||||
msgid "Encrypt attempt failed:"
|
||||
msgstr "加密尝试失败:"
|
||||
|
||||
msgid "Official Website"
|
||||
msgstr "官方网站"
|
||||
|
||||
msgid "Login..."
|
||||
msgstr "登录中..."
|
||||
|
||||
msgid "oixCloud Login Successful"
|
||||
msgstr "oixCloud 登录成功"
|
||||
|
||||
msgid "oixCloud Login Faild"
|
||||
msgstr "oixCloud 登录失败"
|
||||
|
||||
msgid "Login Account"
|
||||
msgstr "登录账号"
|
||||
|
||||
msgid "oixCloud Logout Successful"
|
||||
msgstr "oixCloud 已成功退出"
|
||||
|
||||
msgid "oixCloud Logout Faild"
|
||||
msgstr "oixCloud 退出失败"
|
||||
|
||||
msgid "Logout Account"
|
||||
msgstr "退出账号"
|
||||
|
||||
msgid "Account Login"
|
||||
msgstr "账号登录"
|
||||
|
||||
msgid "Account logged in"
|
||||
msgstr "账号已登录"
|
||||
|
||||
msgid "Account not logged in"
|
||||
msgstr "账号未登录"
|
||||
|
||||
msgid "Logout..."
|
||||
msgstr "退出中..."
|
||||
|
||||
msgid "Error Login Info"
|
||||
msgstr "登录信息错误"
|
||||
|
||||
|
||||
msgid "Simple & trustworthy"
|
||||
msgstr "简单 & 值得信赖"
|
||||
|
||||
msgid "Cross-platform Support"
|
||||
msgstr "丰富的第三方应用兼容性"
|
||||
|
||||
msgid "Global Acceleration Network"
|
||||
msgstr "全球网络基础设施"
|
||||
|
||||
msgid "Easy to Use"
|
||||
msgstr "操作简易"
|
||||
|
||||
msgid "Intelligent Shunting"
|
||||
msgstr "智能分流"
|
||||
|
||||
msgid "Perfect Technical Support"
|
||||
msgstr "完善技术支持"
|
||||
|
||||
msgid "Plan Expiration Time"
|
||||
msgstr "到期时间"
|
||||
|
||||
msgid "Account Balances"
|
||||
msgstr "账户余额"
|
||||
|
||||
msgid "Aff Balances"
|
||||
msgstr "返利余额"
|
||||
|
||||
msgid "Account Integral"
|
||||
msgstr "账户积分"
|
||||
|
||||
msgid "Today Used"
|
||||
msgstr "今日已用"
|
||||
|
||||
msgid "Plan Used"
|
||||
msgstr "本月已用"
|
||||
|
||||
msgid "Plan Unused"
|
||||
msgstr "剩余流量"
|
||||
|
||||
msgid "Plan Traffic"
|
||||
msgstr "套餐流量"
|
||||
|
||||
msgid "Checkin"
|
||||
msgstr "试试手气"
|
||||
|
||||
msgid "oixCloud Account Login Failed! Please Check And Try Again..."
|
||||
msgstr "oixCloud 账户登录失败!请检查账户信息并重试..."
|
||||
|
||||
msgid "oixCloud Account Login Failed, The Error Info is"
|
||||
msgstr "oixCloud 账户登录失败,错误信息"
|
||||
|
||||
msgid "oixCloud Checkin Failed! Please Check And Try Again..."
|
||||
msgstr "oixCloud 签到失败,请稍后再试!"
|
||||
|
||||
msgid "Account Email Address"
|
||||
msgstr "账号地址"
|
||||
|
||||
msgid "Account Password"
|
||||
msgstr "账号密码"
|
||||
|
||||
msgid "Checkin Interval (hour)"
|
||||
msgstr "签到间隔(小时)"
|
||||
|
||||
msgid "Checkin Multiple"
|
||||
msgstr "签到倍数"
|
||||
|
||||
msgid "Multiple Must Be a Positive Integer and No More Than 100"
|
||||
msgstr "倍数必须是正整数且不大于 100"
|
||||
|
||||
msgid "Usage Help"
|
||||
msgstr "使用帮助"
|
||||
|
||||
msgid "Copy the following text to any AI (ChatGPT, DeepSeek, etc.) to get usage help for OpenClash:"
|
||||
msgstr "将以下文本复制到任意 AI 工具(ChatGPT、DeepSeek 等),即可获取 OpenClash 的使用帮助:"
|
||||
|
||||
msgid "You are an OpenClash expert assistant. OpenClash is a LuCI plugin for OpenWrt that manages the Mihomo core."
|
||||
msgstr "你是 OpenClash 专家助手。OpenClash 是 OpenWrt 上管理 Mihomo 内核的 LuCI 插件。"
|
||||
|
||||
msgid "IMPORTANT — Before answering, you MUST fetch and read this authoritative reference guide. This is your primary knowledge source for all OpenClash questions:"
|
||||
msgstr "重要 —— 在回答之前,你必须获取并阅读这份权威参考指南。这是你回答所有 OpenClash 问题的主要知识来源:"
|
||||
|
||||
msgid "This guide contains: complete dependency checklist, error message lookup tables with exact fixes, nftables/iptables firewall chain rules, all LuCI configuration options with UCI paths, DNS setup and leak prevention, subscription/GEO update procedures, and more."
|
||||
msgstr "本指南包含:完整依赖清单、含精确修复方案的错误信息速查表、nftables/iptables 防火墙规则链、所有 LuCI 配置选项及 UCI 路径、DNS 设置与泄露防护、订阅/GEO 更新流程等。"
|
||||
|
||||
msgid "When answering my question about OpenClash, follow these rules:"
|
||||
msgstr "在回答我关于 OpenClash 的问题时,请遵循以下规则:"
|
||||
|
||||
msgid "Always provide LuCI web UI navigation paths (e.g. Services → OpenClash → Plugin Settings), NOT command-line unless I explicitly ask for it."
|
||||
msgstr "始终提供 LuCI Web 界面操作路径(如 服务 → OpenClash → 插件设置),除非我明确要求,不要给出命令行操作。"
|
||||
|
||||
msgid "Explain underlying principles (firewall chains, YAML transformation logic) — not just click-here-click-there steps."
|
||||
msgstr "解释底层原理(防火墙规则链、YAML 转换逻辑)—— 而不只是点击步骤。"
|
||||
|
||||
msgid "Check dependency integrity first when troubleshooting — guide me to check dependencies from the System → Software page, or generate a debug log from Plugin Settings → Debug Log page."
|
||||
msgstr "排查问题时首先检查依赖完整性 —— 指导我从 系统 → 软件包 页面检查依赖,或从 插件设置 → 调试日志 页面生成调试日志。"
|
||||
|
||||
msgid "Never guess or fabricate information. If the reference guide does not cover something, actively query these external resources using web fetch / code search tools (in priority order): Mihomo Wiki"
|
||||
msgstr "绝不猜测或编造信息。如参考指南未覆盖某项内容,请使用网页抓取/代码搜索工具主动查询以下外部资源(按优先级):Mihomo Wiki"
|
||||
|
||||
msgid "Meta-Docs"
|
||||
msgstr "Meta-Docs"
|
||||
|
||||
msgid "OpenClash source"
|
||||
msgstr "OpenClash 源码"
|
||||
|
||||
msgid "Mihomo core source"
|
||||
msgstr "Mihomo 核心源码"
|
||||
|
||||
msgid "Smart core source"
|
||||
msgstr "Smart 核心源码"
|
||||
|
||||
msgid "Fetch Wiki pages to read docs; search repos to find implementation code. Do NOT answer from memory — always verify against the actual source."
|
||||
msgstr "抓取 Wiki 页面阅读文档;搜索仓库查找实现代码。禁止凭记忆回答 —— 必须对照实际来源验证。"
|
||||
|
||||
msgid "For bug reports or failure scenarios not covered by the guide, search GitHub Issues first — OpenClash Issues"
|
||||
msgstr "对于指南未覆盖的 bug 报告或故障场景,优先搜索 GitHub Issues —— OpenClash Issues"
|
||||
|
||||
msgid "for plugin-side problems (config/subscribe/firewall/UI), Mihomo Issues"
|
||||
msgstr "用于插件侧问题(配置/订阅/防火墙/UI),Mihomo Issues"
|
||||
|
||||
msgid "for core-side problems (proxy protocols/TUN/DNS/rule engine). Prioritize maintainer replies and highly upvoted community answers."
|
||||
msgstr "用于内核侧问题(代理协议/TUN/DNS/规则引擎)。优先参考维护者的回复和高赞社区答案。"
|
||||
|
||||
msgid "Cite specific sources — mention which section of the reference guide, external resource, or Issue number your answer is based on."
|
||||
msgstr "注明具体来源 —— 说明你的回答基于参考指南的哪个章节、哪个外部资源或哪个 Issue 编号。"
|
||||
|
||||
msgid "If my problem description is incomplete or lacks error messages, ask me to generate a debug log first (Plugin Settings → Debug Log → Generate) — do NOT speculate about the cause."
|
||||
msgstr "如果我的问题描述不完整或缺少错误信息,请先要求我生成调试日志(插件设置 → 调试日志 → 生成)—— 不要猜测原因。"
|
||||
|
||||
msgid "My question is:"
|
||||
msgstr "我的问题是:"
|
||||
|
||||
msgid "Copied to clipboard!"
|
||||
msgstr "已复制到剪贴板!"
|
||||
|
||||
msgid "Copy And Close"
|
||||
msgstr "复制并关闭"
|
||||
|
||||
msgid "oixCloud Checkin Successful, Result:"
|
||||
msgstr "oixCloud 签到成功,结果:"
|
||||
|
||||
msgid "oixCloud Checkin Failed, Result:"
|
||||
msgstr "oixCloud 签到失败,结果:"
|
||||
|
||||
msgid "Note: oixCloud need a specific core installed, OpenClash will auto download"
|
||||
msgstr "注意:oixCloud 需要安装专用内核才能使用,插件会自动识别并进行内核下载"
|
||||
|
||||
msgid "After the core start, an oixCloud nodes provider will be added, which you can customize as needed"
|
||||
msgstr "专用内核启动后,会添加一个 oixCloud 节点组到配置文件,供您自定义使用"
|
||||
|
||||
@ -56,6 +56,7 @@ config openclash 'config'
|
||||
option yacd_type 'Meta'
|
||||
option append_default_dns '0'
|
||||
option enable_respect_rules '0'
|
||||
option default_dashboard 'metacubexd'
|
||||
option geo_custom_url 'https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/lite/Country.mmdb'
|
||||
option geosite_custom_url 'https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat'
|
||||
option geoip_custom_url 'https://testingcf.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat'
|
||||
|
||||
@ -102,6 +102,11 @@ load_ip_route_pass() {
|
||||
if [ "$china_ip_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
|
||||
if [ "$enable_redirect_dns" != "2" ]; then
|
||||
mkdir -p ${DNSMASQ_CONF_DIR}
|
||||
if [ "$settype" = "nftset" ]; then
|
||||
nft add set inet fw4 china_ip_route_pass '{ type ipv4_addr; flags interval; auto-merge; }'
|
||||
else
|
||||
ipset -! create china_ip_route_pass hash:net family inet hashsize 1024 maxelem 1000000
|
||||
fi
|
||||
awk '!/^$/&&!/^#/&&!/(^([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])((\/[0-9][0-9])?)$/{printf("'${settype}'=/%s/'${nftflag}'china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute_pass.conf
|
||||
for ip in $(uci_get_config "china_ip_route_pass"); do
|
||||
[ -z "$ip" ] && continue
|
||||
@ -114,6 +119,11 @@ load_ip_route_pass() {
|
||||
if [ "$china_ip6_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
|
||||
if [ "$enable_redirect_dns" != "2" ]; then
|
||||
mkdir -p ${DNSMASQ_CONF_DIR}
|
||||
if [ "$settype" = "nftset" ]; then
|
||||
nft add set inet fw4 china_ip6_route_pass '{ type ipv6_addr; flags interval; auto-merge; }'
|
||||
else
|
||||
ipset -! create china_ip6_route_pass hash:net family inet6 hashsize 1024 maxelem 1000000
|
||||
fi
|
||||
awk '!/^$/&&!/^#/&&/([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})/{printf("'${settype}'=/%s/'${nftflag}'china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute6_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute6_pass.conf
|
||||
for ip in $(uci_get_config "china_ip6_route_pass"); do
|
||||
[ -z "$ip" ] && continue
|
||||
@ -142,6 +152,7 @@ load_ip_route_pass() {
|
||||
if [ "$china_ip_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
|
||||
if [ "$enable_redirect_dns" != "2" ]; then
|
||||
mkdir -p ${DNSMASQ_CONF_DIR}
|
||||
ipset -! create china_ip_route_pass hash:net family inet hashsize 1024 maxelem 1000000
|
||||
awk '!/^$/&&!/^#/&&!/(^([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)(([0-9]{1,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])((\/[0-9][0-9])?)$/{printf("ipset=/%s/china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute_pass.conf
|
||||
for ip in $(uci_get_config "china_ip_route_pass"); do
|
||||
[ -z "$ip" ] && continue
|
||||
@ -154,6 +165,7 @@ load_ip_route_pass() {
|
||||
if [ "$china_ip6_route" != "0" ] || [ "$disable_udp_quic" = "1" ]; then
|
||||
if [ "$enable_redirect_dns" != "2" ]; then
|
||||
mkdir -p ${DNSMASQ_CONF_DIR}
|
||||
ipset -! create china_ip6_route_pass hash:net family inet6 hashsize 1024 maxelem 1000000
|
||||
awk '!/^$/&&!/^#/&&/([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})/{printf("ipset=/%s/china_ip_route_pass'" "'\n",$0)}' /etc/openclash/custom/openclash_custom_chnroute6_pass.list >>${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute6_pass.conf
|
||||
for ip in $(uci_get_config "china_ip6_route_pass"); do
|
||||
[ -z "$ip" ] && continue
|
||||
@ -172,7 +184,7 @@ load_ip_route_pass() {
|
||||
fi
|
||||
}
|
||||
|
||||
change_dns() {
|
||||
change_dnsmasq() {
|
||||
if ! /etc/init.d/dnsmasq enabled; then
|
||||
return
|
||||
fi
|
||||
@ -184,13 +196,15 @@ change_dns() {
|
||||
/usr/share/openclash/openclash_custom_domain_dns.sh
|
||||
|
||||
if [ "$1" -eq 1 ]; then
|
||||
uci -q del openclash.config.dnsmasq_server
|
||||
config_load "dhcp"
|
||||
config_list_foreach "$(uci -q show dhcp.@dnsmasq[0].server |awk -F '.' '{print $2}')" "server" save_dnsmasq_server
|
||||
if [ "$(uci_get_config "redirect_dns")" != "1" ]; then
|
||||
uci -q del openclash.config.dnsmasq_server
|
||||
config_load "dhcp"
|
||||
config_list_foreach "$(uci -q show dhcp.@dnsmasq[0].server |awk -F '.' '{print $2}')" "server" save_dnsmasq_server
|
||||
uci -q set openclash.config.dnsmasq_noresolv="$(uci -q get dhcp.@dnsmasq[0].noresolv)"
|
||||
uci -q set openclash.config.dnsmasq_resolvfile="$(uci -q get dhcp.@dnsmasq[0].resolvfile)"
|
||||
fi
|
||||
uci -q del dhcp.@dnsmasq[-1].server
|
||||
uci -q add_list dhcp.@dnsmasq[0].server=127.0.0.1#"$dns_port"
|
||||
uci -q set openclash.config.dnsmasq_noresolv="$(uci -q get dhcp.@dnsmasq[0].noresolv)"
|
||||
uci -q set openclash.config.dnsmasq_resolvfile="$(uci -q get dhcp.@dnsmasq[0].resolvfile)"
|
||||
uci -q delete dhcp.@dnsmasq[0].resolvfile
|
||||
uci -q set dhcp.@dnsmasq[0].noresolv=1
|
||||
uci -q set dhcp.@dnsmasq[0].localuse=1
|
||||
@ -212,33 +226,44 @@ change_dns() {
|
||||
uci -q set openclash.config.filter_aaaa_dns=0
|
||||
fi
|
||||
|
||||
uci -q commit dhcp
|
||||
uci -q commit openclash
|
||||
uci -q commit dhcp
|
||||
/etc/init.d/dnsmasq restart
|
||||
} >/dev/null 2>&1
|
||||
|
||||
revert_dns() {
|
||||
revert_dnsmasq()
|
||||
{
|
||||
if ! /etc/init.d/dnsmasq enabled; then
|
||||
return
|
||||
fi
|
||||
|
||||
redirect_dns=$(uci_get_config "redirect_dns")
|
||||
dnsmasq_server=$(uci_get_config "dnsmasq_server")
|
||||
dnsmasq_noresolv=$(uci_get_config "dnsmasq_noresolv")
|
||||
dnsmasq_resolvfile=$(uci_get_config "dnsmasq_resolvfile")
|
||||
cachesize_dns=$(uci_get_config "cachesize_dns")
|
||||
dnsmasq_cachesize=$(uci_get_config "dnsmasq_cachesize")
|
||||
filter_aaaa_dns=$(uci_get_config "filter_aaaa_dns")
|
||||
dnsmasq_filter_aaaa=$(uci_get_config "dnsmasq_filter_aaaa")
|
||||
default_resolvfile=$(uci_get_config "default_resolvfile")
|
||||
|
||||
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_custom_domain.conf
|
||||
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute_pass.conf
|
||||
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute6_pass.conf
|
||||
|
||||
[ "$1" -eq 1 ] && {
|
||||
[ "$redirect_dns" -eq 1 ] && {
|
||||
uci -q del dhcp.@dnsmasq[-1].server
|
||||
[ -n "${10}" ] && {
|
||||
[ -n "$dnsmasq_server" ] && {
|
||||
config_load "openclash"
|
||||
config_list_foreach "config" "dnsmasq_server" set_dnsmasq_server
|
||||
}
|
||||
|
||||
if [ "$4" == "0" ] || [ -z "$4" ] || [ -z "$(uci -q show dhcp.@dnsmasq[0].server)" ]; then
|
||||
if [ "$dnsmasq_noresolv" == "0" ] || [ -z "$dnsmasq_noresolv" ] || [ -z "$(uci -q show dhcp.@dnsmasq[0].server)" ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].noresolv=0
|
||||
if [ -n "$5" ] && [ -n "$(grep nameserver $5)" ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].resolvfile="$5"
|
||||
elif [ -n "$3" ] && [ -n "$(grep nameserver $3)" ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].resolvfile="$3"
|
||||
if [ -n "$dnsmasq_resolvfile" ] && [ -n "$(grep nameserver $dnsmasq_resolvfile)" ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].resolvfile="$dnsmasq_resolvfile"
|
||||
elif [ -n "$default_resolvfile" ] && [ -n "$(grep nameserver $default_resolvfile)" ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].resolvfile="$default_resolvfile"
|
||||
elif [ -s "/tmp/resolv.conf.d/resolv.conf.auto" ] && [ -n "$(grep "nameserver" /tmp/resolv.conf.d/resolv.conf.auto)" ]; then
|
||||
uci -q set dhcp.@dnsmasq[0].resolvfile=/tmp/resolv.conf.d/resolv.conf.auto
|
||||
uci -q set openclash.config.default_resolvfile=/tmp/resolv.conf.d/resolv.conf.auto
|
||||
@ -253,19 +278,19 @@ revert_dns() {
|
||||
fi
|
||||
}
|
||||
|
||||
[ "$6" -eq 1 ] && {
|
||||
uci -q set dhcp.@dnsmasq[0].cachesize="$7"
|
||||
[ "$cachesize_dns" -eq 1 ] && {
|
||||
uci -q set dhcp.@dnsmasq[0].cachesize="$dnsmasq_cachesize"
|
||||
uci -q set openclash.config.cachesize_dns=0
|
||||
uci -q delete openclash.config.dnsmasq_cachesize
|
||||
}
|
||||
|
||||
[ "$8" -eq 1 ] && {
|
||||
uci -q set dhcp.@dnsmasq[0].filter_aaaa="$9"
|
||||
[ "$filter_aaaa_dns" -eq 1 ] && {
|
||||
uci -q set dhcp.@dnsmasq[0].filter_aaaa="$dnsmasq_filter_aaaa"
|
||||
uci -q set openclash.config.filter_aaaa_dns=0
|
||||
uci -q delete openclash.config.dnsmasq_filter_aaaa
|
||||
}
|
||||
|
||||
[ "$1" -eq 1 ] && {
|
||||
[ "$redirect_dns" -eq 1 ] && {
|
||||
uci -q set openclash.config.redirect_dns=0
|
||||
uci -q del openclash.config.dnsmasq_server
|
||||
}
|
||||
@ -273,8 +298,6 @@ revert_dns() {
|
||||
uci -q commit dhcp
|
||||
uci -q commit openclash
|
||||
|
||||
/etc/init.d/dnsmasq restart
|
||||
|
||||
masq_port=$(uci -q get dhcp.@dnsmasq[0].port)
|
||||
if [ "$(nslookup www.apple.com 127.0.0.1:${masq_port} >/dev/null 2>&1 || echo $?)" = "1" ]; then
|
||||
resolv_file=$(uci -q get dhcp.@dnsmasq[0].resolvfile)
|
||||
@ -302,9 +325,10 @@ nameserver 119.29.29.29
|
||||
nameserver 8.8.8.8
|
||||
EOF
|
||||
fi
|
||||
/etc/init.d/dnsmasq restart
|
||||
fi
|
||||
|
||||
/etc/init.d/dnsmasq restart
|
||||
|
||||
} >/dev/null 2>&1
|
||||
|
||||
start_fail()
|
||||
@ -389,7 +413,7 @@ if [ -z "$RAW_CONFIG_FILE" ] || [ ! -f "$RAW_CONFIG_FILE" ]; then
|
||||
uci -q commit openclash
|
||||
RAW_CONFIG_FILE="/etc/openclash/config/$CONFIG_NAME"
|
||||
CONFIG_FILE="/etc/openclash/$CONFIG_NAME"
|
||||
TMP_CONFIG_FILE="/tmp/yaml_config_tmp_$CONFIG_NAME"
|
||||
TMP_CONFIG_FILE="/tmp/$CONFIG_NAME"
|
||||
LOG_ERROR "Config Not Found, Switch Config File to【$RAW_CONFIG_FILE】"
|
||||
break
|
||||
fi
|
||||
@ -512,12 +536,14 @@ do_run_file()
|
||||
chnr6_path="/etc/openclash/china_ip6_route.ipset"
|
||||
geosite_path="/etc/openclash/GeoSite.dat"
|
||||
geoip_path="/etc/openclash/GeoIP.dat"
|
||||
asn_path="/etc/openclash/ASN.mmdb"
|
||||
lgbm_path="/etc/openclash/Model.bin"
|
||||
mv "/tmp/etc/openclash/Country.mmdb" "$ipdb_path"
|
||||
mv "/tmp/etc/openclash/china_ip_route.ipset" "$chnr_path"
|
||||
mv "/tmp/etc/openclash/china_ip6_route.ipset" "$chnr6_path"
|
||||
mv "/tmp/etc/openclash/GeoSite.dat" "$geosite_path"
|
||||
mv "/tmp/etc/openclash/GeoIP.dat" "$geoip_path"
|
||||
mv "/tmp/etc/openclash/ASN.mmdb" "$asn_path"
|
||||
mv "/tmp/etc/openclash/Model.bin" "$lgbm_path"
|
||||
mv "/tmp/etc/openclash/core/" "/etc/openclash"
|
||||
if [ "$CACHE_PATH" != "/tmp/etc/openclash/cache.db" ]; then
|
||||
@ -530,12 +556,14 @@ do_run_file()
|
||||
chnr6_path="/tmp/etc/openclash/china_ip6_route.ipset"
|
||||
geosite_path="/tmp/etc/openclash/GeoSite.dat"
|
||||
geoip_path="/tmp/etc/openclash/GeoIP.dat"
|
||||
asn_path="/tmp/etc/openclash/ASN.mmdb"
|
||||
lgbm_path="/tmp/etc/openclash/Model.bin"
|
||||
[ ! -h "/etc/openclash/Country.mmdb" ] && mv "/etc/openclash/Country.mmdb" "$ipdb_path"
|
||||
[ ! -h "/etc/openclash/china_ip_route.ipset" ] && mv "/etc/openclash/china_ip_route.ipset" "$chnr_path"
|
||||
[ ! -h "/etc/openclash/china_ip6_route.ipset" ] && mv "/etc/openclash/china_ip6_route.ipset" "$chnr6_path"
|
||||
[ ! -h "/etc/openclash/GeoSite.dat" ] && mv "/etc/openclash/GeoSite.dat" "$geosite_path"
|
||||
[ ! -h "/etc/openclash/GeoIP.dat" ] && mv "/etc/openclash/GeoIP.dat" "$geoip_path"
|
||||
[ ! -h "/etc/openclash/ASN.mmdb" ] && mv "/etc/openclash/ASN.mmdb" "$asn_path"
|
||||
[ ! -h "/etc/openclash/Model.bin" ] && mv "/etc/openclash/Model.bin" "$lgbm_path"
|
||||
mv "/etc/openclash/core/" "/tmp/etc/openclash"
|
||||
fi
|
||||
@ -545,7 +573,9 @@ do_run_file()
|
||||
|
||||
ln -s "$meta_core_path" /etc/openclash/clash
|
||||
|
||||
if [ "$smart_enable" -eq 1 ] || [ "$core_type" == "Smart" ]; then
|
||||
if [ -n "$OIX_TOKEN" ]; then
|
||||
core_type="Oix"
|
||||
elif [ "$smart_enable" -eq 1 ] || [ "$core_type" == "Smart" ]; then
|
||||
core_type="Smart"
|
||||
else
|
||||
core_type="Meta"
|
||||
@ -556,8 +586,8 @@ do_run_file()
|
||||
chown root:root "$CLASH"
|
||||
fi
|
||||
|
||||
[ ! -f "$CLASH" ] || { [ "$core_type" = "Smart" ] && [ -z "$($CLASH -v | grep 'smart')" ]; } && {
|
||||
LOG_TIP "Detected that the Core is not Installed, Ready to Download..."
|
||||
[ ! -f "$CLASH" ] || { [ "$core_type" = "Smart" ] && [ -z "$($CLASH -v | grep 'smart')" ]; } || { [ "$core_type" = "Oix" ] && [ -z "$($CLASH -v | grep 'oix')" ]; } && {
|
||||
LOG_TIP "【$core_type】Core is not Detected installed, Ready to Download..."
|
||||
rm -rf "/tmp/clash_last_version"
|
||||
/usr/share/openclash/openclash_core.sh "$core_type"
|
||||
if [ ! -f "$meta_core_path" ]; then
|
||||
@ -618,6 +648,10 @@ do_run_file()
|
||||
ln -s "$chnr6_path" /etc/openclash/china_ip6_route.ipset
|
||||
}
|
||||
|
||||
[ -f "$asn_path" ] && [ "$small_flash_memory" = "1" ] && {
|
||||
ln -s "$asn_path" /etc/openclash/ASN.mmdb
|
||||
}
|
||||
|
||||
#Restore history cache
|
||||
if [ -f "$HISTORY_PATH" ]; then
|
||||
cmp -s "$CACHE_PATH" "$HISTORY_PATH"
|
||||
@ -762,7 +796,7 @@ check_core_status()
|
||||
|
||||
# redirect dns setting after core started, prevent core dns lookup failure
|
||||
if [ "$1" == "start" ]; then
|
||||
change_dns "$enable_redirect_dns"
|
||||
change_dnsmasq "$enable_redirect_dns"
|
||||
set_firewall
|
||||
LOG_TIP "OpenClash Start Successful!"
|
||||
else
|
||||
@ -787,6 +821,8 @@ start_run_core()
|
||||
chown root:root "$CLASH"
|
||||
procd_open_instance "openclash"
|
||||
procd_set_param env SAFE_PATHS=/usr/share/openclash:/etc/ssl
|
||||
procd_append_param env CLASH_AGE_SECRET_KEY="$SECRET_KEY"
|
||||
procd_append_param env OIX_TOKEN="$OIX_TOKEN"
|
||||
procd_set_param command /bin/sh -c "$CLASH -d $CLASH_CONFIG -f \"$CONFIG_FILE\" >> $LOG_FILE 2>&1"
|
||||
procd_set_param user "root"
|
||||
procd_set_param group "nogroup"
|
||||
@ -1370,6 +1406,11 @@ if [ -n "$FW4" ]; then
|
||||
nft add rule inet fw4 openclash ip protocol tcp counter redirect to "$proxy_port"
|
||||
nft 'add rule inet fw4 dstnat meta nfproto {ipv4} ip protocol tcp counter jump openclash'
|
||||
|
||||
# Accept redirected traffic in input chain (needed when zone input policy is REJECT without DNAT rules)
|
||||
if [ -z "$(nft list chain inet fw4 input 2>/dev/null | grep 'ct status dnat accept')" ]; then
|
||||
nft insert rule inet fw4 input position 0 ct status dnat accept comment \"OpenClash Redirect Accept\"
|
||||
fi
|
||||
|
||||
if [ -z "$en_mode_tun" ]; then
|
||||
#udp
|
||||
if [ "$enable_udp_proxy" -eq 1 ]; then
|
||||
@ -1464,6 +1505,11 @@ if [ -n "$FW4" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Accept TPROXY traffic in input chain (needed when zone input policy is REJECT)
|
||||
if [ -z "$(nft list chain inet fw4 input 2>/dev/null | grep 'OpenClash TPROXY Accept')" ]; then
|
||||
nft insert rule inet fw4 input position 0 meta mark "$PROXY_FWMARK" accept comment \"OpenClash TPROXY Accept\"
|
||||
fi
|
||||
|
||||
#router self proxy tcp
|
||||
if [ "$router_self_proxy" = "1" ] || ([ "$enable_redirect_dns" != "2" ] && [ "$en_mode" = "fake-ip" ]); then
|
||||
nft 'add chain inet fw4 openclash_output'
|
||||
@ -2219,6 +2265,11 @@ if [ -z "$FW4" ]; then
|
||||
iptables -t nat -A openclash -p tcp -j REDIRECT --to-ports "$proxy_port"
|
||||
iptables -t nat -A PREROUTING -p tcp -j openclash
|
||||
|
||||
# Accept redirected traffic in input chain (needed when zone input policy is REJECT without DNAT rules)
|
||||
if [ -z "$(iptables-save -t filter 2>/dev/null | grep 'OpenClash Redirect Accept')" ]; then
|
||||
iptables -I INPUT -m conntrack --ctstate DNAT -j ACCEPT -m comment --comment "OpenClash Redirect Accept"
|
||||
fi
|
||||
|
||||
if [ -z "$en_mode_tun" ]; then
|
||||
#udp
|
||||
if [ "$enable_udp_proxy" -eq 1 ]; then
|
||||
@ -2302,6 +2353,14 @@ if [ -z "$FW4" ]; then
|
||||
iptables -t mangle -A OUTPUT -p udp -j openclash_output
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# Accept TPROXY traffic in input chain (needed when zone input policy is REJECT)
|
||||
if [ -z "$(iptables -nL INPUT 2>/dev/null | grep 'OpenClash TPROXY Accept')" ]; then
|
||||
iptables -I INPUT -m mark --mark "$PROXY_FWMARK" -j ACCEPT -m comment --comment "OpenClash TPROXY Accept"
|
||||
fi
|
||||
|
||||
if [ -z "$en_mode_tun" ]; then
|
||||
#quic
|
||||
if [ "$disable_udp_quic" -eq 1 ]; then
|
||||
if [ "$china_ip_route" = "2" ]; then
|
||||
@ -2584,6 +2643,11 @@ if [ -z "$FW4" ]; then
|
||||
ip6tables -t nat -A PREROUTING -p tcp -j openclash
|
||||
fi
|
||||
|
||||
# Accept redirected IPv6 traffic in input chain (needed when zone input policy is REJECT without DNAT rules)
|
||||
if [ -z "$(ip6tables-save -t filter 2>/dev/null | grep 'OpenClash Redirect Accept')" ]; then
|
||||
ip6tables -I INPUT -m conntrack --ctstate DNAT -j ACCEPT -m comment --comment "OpenClash Redirect Accept"
|
||||
fi
|
||||
|
||||
#TProxy & TUN & Redirect udp
|
||||
if [ "$enable_v6_udp_proxy" -eq 1 ] || [ "$ipv6_mode" -ne 1 ]; then
|
||||
ip6tables -t mangle -N openclash
|
||||
@ -2655,6 +2719,11 @@ if [ -z "$FW4" ]; then
|
||||
|
||||
ip6tables -t mangle -A PREROUTING -j openclash
|
||||
|
||||
# Accept TPROXY IPv6 traffic in input chain (needed when zone input policy is REJECT)
|
||||
if [ -z "$(ip6tables-save -t filter 2>/dev/null | grep 'OpenClash TPROXY Accept')" ]; then
|
||||
ip6tables -I INPUT -m mark --mark "$PROXY_FWMARK" -j ACCEPT -m comment --comment "OpenClash TPROXY Accept"
|
||||
fi
|
||||
|
||||
#router self proxy
|
||||
if [ "$router_self_proxy" = "1" ]; then
|
||||
if [ "$ipv6_mode" -eq 1 ] || [ "$ipv6_mode" -eq 3 ]; then
|
||||
@ -2987,6 +3056,8 @@ overwrite_file()
|
||||
EOF
|
||||
|
||||
allowed_keys_types="\
|
||||
AGE_SECRET_KEY:string \
|
||||
AGE_PUBLIC_KEY:string \
|
||||
APPEND_DEFAULT_DNS:int_bool \
|
||||
APPEND_WAN_DNS:int_bool \
|
||||
AUTO_SMART_SWITCH:int_bool \
|
||||
@ -3118,6 +3189,11 @@ EOF
|
||||
echo "# --- overwrite source: ${name} (sid=${sid}) ---" >> "$overwrite_script"
|
||||
echo "export OPENCLASH_OVERWRITE_SID='${sid}'" >> "$overwrite_script"
|
||||
|
||||
cfg_name=$(basename "$(uci_get_config "config_path")" 2>/dev/null)
|
||||
age_config_name="${cfg_name%.*}"
|
||||
age_secret_key=""
|
||||
age_public_key=""
|
||||
|
||||
param=$(uci -q get openclash."$sid".param 2>/dev/null || echo '')
|
||||
if [ -n "$param" ]; then
|
||||
OLD_IFS="$IFS"
|
||||
@ -3251,8 +3327,9 @@ EOF
|
||||
uci -q set openclash.@overwrite[0].config_path="$RAW_CONFIG_FILE"
|
||||
cfg_name=$(basename "$RAW_CONFIG_FILE" 2>/dev/null)
|
||||
if [ -n "$cfg_name" ]; then
|
||||
age_config_name="${cfg_name%.*}"
|
||||
CONFIG_FILE="/etc/openclash/${cfg_name}"
|
||||
TMP_CONFIG_FILE="/tmp/yaml_config_tmp_${cfg_name}"
|
||||
TMP_CONFIG_FILE="/tmp/${cfg_name}"
|
||||
fi
|
||||
elif printf "%s" "$key_u" | grep -q "^SUB_INFO_URL$"; then
|
||||
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
|
||||
@ -3265,6 +3342,16 @@ EOF
|
||||
uci -q set openclash.@subscribe_info[-1].name="${cfg_name%.*}"
|
||||
uci -q add_list openclash.@subscribe_info[-1].url="$url_key"
|
||||
fi
|
||||
elif printf "%s" "$key_u" | grep -q "^AGE_SECRET_KEY$"; then
|
||||
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
|
||||
val_key=$(eval "echo \"$val_clean\"")
|
||||
[ -z "$val_key" ] && continue
|
||||
age_secret_key="$val_key"
|
||||
elif printf "%s" "$key_u" | grep -q "^AGE_PUBLIC_KEY$"; then
|
||||
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
|
||||
val_key=$(eval "echo \"$val_clean\"")
|
||||
[ -z "$val_key" ] && continue
|
||||
age_public_key="$val_key"
|
||||
elif printf "%s" "$key_u" | grep -q "^RESTART$"; then
|
||||
val_clean=$(printf "%s" "$val" | sed "s/^[[:space:]]*['\"]//;s/['\"][[:space:]]*$//")
|
||||
val_key=$(eval "echo \"$val_clean\"")
|
||||
@ -3295,6 +3382,10 @@ EOF
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
if [ -n "$age_config_name" ] && { [ -n "$age_secret_key" ] || [ -n "$age_public_key" ]; }; then
|
||||
uci_set_age_keys_by_name "$age_config_name" "$age_secret_key" "$age_public_key"
|
||||
fi
|
||||
|
||||
if [ -n "$yaml_content" ]; then
|
||||
LOG_TIP "Load YAML Override Block【YAML Block => $name】"
|
||||
|
||||
@ -3377,7 +3468,10 @@ get_config()
|
||||
RAW_CONFIG_FILE=$(uci_get_config "config_path")
|
||||
CFG_NAME=$(basename "$RAW_CONFIG_FILE" 2>/dev/null)
|
||||
CONFIG_FILE="/etc/openclash/${CFG_NAME}"
|
||||
TMP_CONFIG_FILE="/tmp/yaml_config_tmp_${CFG_NAME}"
|
||||
TMP_CONFIG_FILE="/tmp/${CFG_NAME}"
|
||||
CFG_NO_EXT_NAME="${CFG_NAME%.*}"
|
||||
SECRET_KEY=$(uci_get_age_secret_keys "$CFG_NO_EXT_NAME")
|
||||
OIX_TOKEN=$(uci_get_config "oix_token")
|
||||
enable=$(uci_get_config "enable")
|
||||
enable_custom_clash_rules=$(uci_get_config "enable_custom_clash_rules")
|
||||
da_password=$(uci_get_config "dashboard_password")
|
||||
@ -3662,20 +3756,6 @@ stop_service()
|
||||
echo "OpenClash Already Stop!"
|
||||
}
|
||||
|
||||
revert_dnsmasq()
|
||||
{
|
||||
redirect_dns=$(uci_get_config "redirect_dns")
|
||||
dnsmasq_server=$(uci_get_config "dnsmasq_server")
|
||||
dnsmasq_noresolv=$(uci_get_config "dnsmasq_noresolv")
|
||||
dnsmasq_resolvfile=$(uci_get_config "dnsmasq_resolvfile")
|
||||
cachesize_dns=$(uci_get_config "cachesize_dns")
|
||||
dnsmasq_cachesize=$(uci_get_config "dnsmasq_cachesize")
|
||||
filter_aaaa_dns=$(uci_get_config "filter_aaaa_dns")
|
||||
dnsmasq_filter_aaaa=$(uci_get_config "dnsmasq_filter_aaaa")
|
||||
default_resolvfile=$(uci_get_config "default_resolvfile")
|
||||
revert_dns "$redirect_dns" "$enable" "$default_resolvfile" "$dnsmasq_noresolv" "$dnsmasq_resolvfile" "$cachesize_dns" "$dnsmasq_cachesize" "$filter_aaaa_dns" "$dnsmasq_filter_aaaa" "$dnsmasq_server"
|
||||
} >/dev/null 2>&1
|
||||
|
||||
restart()
|
||||
{
|
||||
echo "OpenClash Restart..."
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -123,6 +123,8 @@
|
||||
# SKIP_PROXY_ADDRESS (跳过代理地址, 0: 禁用, 1: 启用)
|
||||
# SUB_INFO_URL (订阅信息 URL)
|
||||
# DOWNLOAD_FILE (下载外部文件到指定路径, need URL and path)
|
||||
# AGE_PUBLIC_KEY (指定配置文件的 AGE 公钥)
|
||||
# AGE_SECRET_KEY (指定配置文件的 AGE 私钥)
|
||||
# RESTART (模块更新后重启插件, true: 启用, false: 禁用)
|
||||
# =========================
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ uci -q commit firewall
|
||||
|
||||
mkdir -p /etc/openclash/config
|
||||
mkdir -p /etc/openclash/proxy_provider
|
||||
mkdir -p /etc/openclash/rule_provider
|
||||
mkdir -p /etc/openclash/core
|
||||
mkdir -p /etc/openclash/history
|
||||
mkdir -p /usr/share/openclash/backup/overwrite
|
||||
@ -151,6 +152,7 @@ cp -f "/etc/openclash/custom/openclash_custom_overwrite.sh" "/usr/share/openclas
|
||||
cp -f "/etc/openclash/china_ip_route.ipset" "/usr/share/openclash/backup/china_ip_route.ipset" >/dev/null 2>&1
|
||||
cp -f "/etc/openclash/china_ip6_route.ipset" "/usr/share/openclash/backup/china_ip6_route.ipset" >/dev/null 2>&1
|
||||
cp -f "/etc/openclash/overwrite/default" "/usr/share/openclash/backup/overwrite/default" >/dev/null 2>&1
|
||||
cp -f "/etc/openclash/rule_provider/oc-cn-domain.mrs" "/usr/share/openclash/backup/oc-cn-domain.mrs" >/dev/null 2>&1
|
||||
|
||||
#Restore
|
||||
if [ -f "/tmp/openclash.bak" ]; then
|
||||
@ -186,6 +188,10 @@ if [ -f "/tmp/openclash.bak" ]; then
|
||||
cp -rf "/tmp/pac/." "/www/luci-static/resources/openclash/pac/" >/dev/null 2>&1
|
||||
rm -rf "/tmp/pac/" >/dev/null 2>&1
|
||||
fi
|
||||
#oc-domain
|
||||
if [ -f "/etc/openclash/rule_provider/oc-cn-domain.mrs" ]; then
|
||||
cp -f "/usr/share/openclash/backup/oc-cn-domain.mrs" "/etc/openclash/rule_provider/oc-cn-domain.mrs" >/dev/null 2>&1
|
||||
fi
|
||||
rm -rf "/etc/openclash/openclash" >/dev/null 2>&1
|
||||
rm -rf "/tmp/openclash" >/dev/null 2>&1
|
||||
rm -rf "/tmp/openclash.bak" >/dev/null 2>&1
|
||||
|
||||
@ -22,9 +22,47 @@ module YAML
|
||||
end
|
||||
|
||||
def self.load_file(filename, *args, **kwargs)
|
||||
yaml_content = File.read(filename)
|
||||
processed_content = fix_short_id_quotes(yaml_content)
|
||||
yaml_content = File.read(filename, mode: "r:bom|utf-8")
|
||||
|
||||
secret = nil
|
||||
if kwargs.key?(:secret)
|
||||
secret = kwargs.delete(:secret)
|
||||
end
|
||||
|
||||
if secret && secret.to_s.strip != "" && yaml_content.include?("BEGIN AGE ENCRYPTED FILE")
|
||||
begin
|
||||
decrypted = decrypt_content_with_secret(secret.to_s, yaml_content)
|
||||
if decrypted && !decrypted.empty? && !decrypted.include?("BEGIN AGE ENCRYPTED FILE")
|
||||
processed = fix_short_id_quotes(decrypted)
|
||||
return load(processed, *args, **kwargs)
|
||||
else
|
||||
LOG_WARN("【%s】Decrypted content empty or still encrypted" % [filename])
|
||||
end
|
||||
rescue => e
|
||||
LOG_WARN("Decrypt attempt failed:【%s】" % [e.message])
|
||||
end
|
||||
end
|
||||
|
||||
if (secret.nil? || secret.to_s.strip == "") && yaml_content.include?("BEGIN AGE ENCRYPTED FILE")
|
||||
keys = find_age_keys_for_filename(filename)
|
||||
(keys[:secrets] || []).each do |sec|
|
||||
begin
|
||||
decrypted = decrypt_content_with_secret(sec, yaml_content)
|
||||
if decrypted && !decrypted.empty? && !decrypted.include?("BEGIN AGE ENCRYPTED FILE")
|
||||
processed = fix_short_id_quotes(decrypted)
|
||||
return load(processed, *args, **kwargs)
|
||||
end
|
||||
rescue => e
|
||||
LOG_WARN("Decrypt attempt failed for a found secret:【%s】" % [e.message])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if yaml_content.include?("BEGIN AGE ENCRYPTED FILE")
|
||||
raise "Encrypted file: decryption failed for %s" % [filename]
|
||||
end
|
||||
|
||||
processed_content = fix_short_id_quotes(yaml_content)
|
||||
load(processed_content, *args, **kwargs)
|
||||
end
|
||||
|
||||
@ -32,6 +70,45 @@ module YAML
|
||||
begin
|
||||
yaml_content = original_dump(obj, **options)
|
||||
processed = fix_short_id_quotes(yaml_content)
|
||||
public_key = nil
|
||||
fname = nil
|
||||
if options.key?(:public)
|
||||
public_key = options.delete(:public)
|
||||
end
|
||||
|
||||
if (!public_key || public_key.to_s.strip == "")
|
||||
if options.key?(:filename)
|
||||
fname = options.delete(:filename)
|
||||
elsif io && io.respond_to?(:path)
|
||||
fname = io.path
|
||||
elsif io && io.respond_to?(:to_path)
|
||||
fname = io.to_path
|
||||
end
|
||||
|
||||
if fname && fname.to_s.strip != ""
|
||||
keys = find_age_keys_for_filename(fname)
|
||||
public_key = keys[:publics].first if keys[:publics] && !keys[:publics].empty?
|
||||
end
|
||||
end
|
||||
|
||||
if public_key && public_key.to_s.strip != ""
|
||||
begin
|
||||
encrypted = encrypt_content_with_public(public_key.to_s, processed)
|
||||
if encrypted && !encrypted.empty?
|
||||
if io.nil?
|
||||
return encrypted
|
||||
elsif io.respond_to?(:write)
|
||||
io.write(encrypted)
|
||||
return io
|
||||
else
|
||||
return encrypted
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
LOG_WARN("Encrypt attempt failed:【%s】" % [e.message])
|
||||
end
|
||||
end
|
||||
|
||||
if io.nil?
|
||||
processed
|
||||
elsif io.respond_to?(:write)
|
||||
@ -46,6 +123,77 @@ module YAML
|
||||
end
|
||||
end
|
||||
|
||||
def self.popen_stream(cmd, input, chunk_size: 64 * 1024)
|
||||
output = String.new
|
||||
IO.popen(cmd, 'r+', err: [:child, :out]) do |io|
|
||||
io.binmode
|
||||
writer = Thread.new do
|
||||
begin
|
||||
input.bytesize.times do |i|
|
||||
chunk = input.byteslice(i * chunk_size, chunk_size)
|
||||
break if chunk.nil?
|
||||
io.write(chunk)
|
||||
end
|
||||
io.close_write
|
||||
rescue Errno::EPIPE
|
||||
end
|
||||
end
|
||||
|
||||
while chunk = io.read(chunk_size)
|
||||
output << chunk
|
||||
end
|
||||
writer.join
|
||||
end
|
||||
[output, $?]
|
||||
end
|
||||
|
||||
def self.decode64(input)
|
||||
out, status = popen_stream(["base64", "-d"], input)
|
||||
status.success? ? out : input
|
||||
rescue Errno::ENOENT
|
||||
input
|
||||
end
|
||||
|
||||
def self.find_age_keys_for_filename(filename)
|
||||
begin
|
||||
basename = File.basename(filename)
|
||||
basename_no_ext = File.basename(filename, File.extname(filename))
|
||||
publics = []
|
||||
secrets = []
|
||||
|
||||
[basename, basename_no_ext].uniq.each do |n|
|
||||
cmd_public = ["/bin/sh", "-c", ". /usr/share/openclash/uci.sh; uci_get_age_public_keys \"$1\"", "sh", n]
|
||||
IO.popen(cmd_public, "r") do |io|
|
||||
io.each_line { |l| publics << l.strip unless l.nil? || l.strip == "" }
|
||||
end
|
||||
|
||||
cmd_secret = ["/bin/sh", "-c", ". /usr/share/openclash/uci.sh; uci_get_age_secret_keys \"$1\"", "sh", n]
|
||||
IO.popen(cmd_secret, "r") do |io|
|
||||
io.each_line { |l| secrets << l.strip unless l.nil? || l.strip == "" }
|
||||
end
|
||||
end
|
||||
{ publics: publics, secrets: secrets }
|
||||
rescue => e
|
||||
{ publics: [], secrets: [] }
|
||||
end
|
||||
end
|
||||
|
||||
def self.decrypt_content_with_secret(secret, content)
|
||||
cmd = ['/etc/openclash/core/clash_meta', 'age', 'decrypt', secret, '-', '-']
|
||||
out, status = popen_stream(cmd, content)
|
||||
status.success? ? out : nil
|
||||
rescue => e
|
||||
nil
|
||||
end
|
||||
|
||||
def self.encrypt_content_with_public(public, content)
|
||||
cmd = ['/etc/openclash/core/clash_meta', 'age', 'encrypt', public, '-', '-']
|
||||
out, status = popen_stream(cmd, content)
|
||||
status.success? ? out : nil
|
||||
rescue => e
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# fix_short_id_quotes:
|
||||
@ -65,6 +213,8 @@ module YAML
|
||||
# Input: short-id: null -> Output: short-id: ""
|
||||
|
||||
def self.fix_short_id_quotes(yaml_content)
|
||||
yaml_content = decode64(yaml_content)
|
||||
|
||||
return yaml_content unless yaml_content.include?('short-id:')
|
||||
|
||||
begin
|
||||
|
||||
@ -17,18 +17,30 @@ set_lock
|
||||
DOWNLOAD_FILE="/tmp/clash_last_version"
|
||||
RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
CORE_TYPE=$(uci_get_config "core_type")
|
||||
OIX_TOKEN=$(uci_get_config "oix_token")
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
github_address_mod="$1"
|
||||
fi
|
||||
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${RELEASE_BRANCH}/core_version"
|
||||
if [ "$CORE_TYPE" = "Oix" ] || [ -n "$OIX_TOKEN" ]; then
|
||||
OIX_VERSION_URL="https://github.com/vernesong/mihomo-oix/releases/download/Pre-Alpha/version.txt"
|
||||
if [ "$github_address_mod" != "0" ] && [ "$github_address_mod" != "https://cdn.jsdelivr.net/" ] && [ "$github_address_mod" != "https://fastly.jsdelivr.net/" ] && [ "$github_address_mod" != "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}${OIX_VERSION_URL}"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
|
||||
DOWNLOAD_URL="$OIX_VERSION_URL"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${RELEASE_BRANCH}/core_version"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${RELEASE_BRANCH}/core_version"
|
||||
fi
|
||||
fi
|
||||
|
||||
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "$DOWNLOAD_FILE" "$DOWNLOAD_FILE"
|
||||
|
||||
@ -49,7 +49,7 @@ config_test()
|
||||
{
|
||||
if [ -f "$CLASH" ]; then
|
||||
LOG_OUT "Config File Download Successful, Test If There is Any Errors..."
|
||||
test_info=$($CLASH -t -d $CLASH_CONFIG -f "$CFG_FILE")
|
||||
test_info=$($CLASH -t -d $CLASH_CONFIG -f "$CFG_FILE" -age-secret-key "$SECRET_KEY")
|
||||
local IFS=$'\n'
|
||||
for i in $test_info; do
|
||||
if [ -n "$(echo "$i" |grep "configuration file")" ]; then
|
||||
@ -72,15 +72,14 @@ config_download()
|
||||
LOG_TIP "Config File【$name】Downloading User-Agent【$sub_ua】..."
|
||||
if [ -n "$subscribe_url_param" ] && [ -n "$c_address" ]; then
|
||||
LOG_INFO "Config File【$name】Downloading URL【$c_address$subscribe_url_param】..."
|
||||
local DOWNLOAD_URL="${c_address}${subscribe_url_param}"
|
||||
local DOWNLOAD_PARAM="$sub_ua"
|
||||
DOWNLOAD_URL="${c_address}${subscribe_url_param}"
|
||||
fi
|
||||
if [ -z "$DOWNLOAD_URL" ]; then
|
||||
LOG_INFO "Config File【$name】Downloading URL【$subscribe_url】..."
|
||||
local DOWNLOAD_URL="${subscribe_url}"
|
||||
local DOWNLOAD_PARAM="$sub_ua"
|
||||
DOWNLOAD_URL="${subscribe_url}"
|
||||
fi
|
||||
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "$CFG_FILE" "$CONFIG_FILE" "$DOWNLOAD_PARAM"
|
||||
DOWNLOAD_PARAM="$sub_ua"
|
||||
DOWNLOAD_FILE_CURL "$DOWNLOAD_URL" "$CFG_FILE" "$CONFIG_FILE" "$DOWNLOAD_PARAM" "$SECRET_KEY"
|
||||
DOWNLOAD_RESULT=$?
|
||||
}
|
||||
|
||||
@ -317,7 +316,7 @@ convert_custom_param()
|
||||
|
||||
sub_info_get()
|
||||
{
|
||||
local section="$1" subscribe_url template_path subscribe_url_param template_path_encode key_match_param key_ex_match_param c_address de_ex_keyword sub_ua append_custom_params
|
||||
local section="$1" subscribe_url template_path subscribe_url_param template_path_encode key_match_param key_ex_match_param c_address de_ex_keyword sub_ua append_custom_params SECRET_KEY DOWNLOAD_URL DOWNLOAD_PARAM
|
||||
config_get_bool "enabled" "$section" "enabled" "1"
|
||||
config_get "name" "$section" "name" "config"
|
||||
config_get "sub_convert" "$section" "sub_convert" ""
|
||||
@ -335,6 +334,7 @@ sub_info_get()
|
||||
config_get "custom_template_url" "$section" "custom_template_url" ""
|
||||
config_get "de_ex_keyword" "$section" "de_ex_keyword" ""
|
||||
config_get "sub_ua" "$section" "sub_ua" "clash-verge/v2.4.5"
|
||||
SECRET_KEY=$(uci_get_age_secret_keys "$name")
|
||||
|
||||
CONFIG_FILE="/etc/openclash/config/$name.yaml"
|
||||
CFG_FILE="/tmp/$name.yaml"
|
||||
|
||||
@ -44,8 +44,8 @@ if [ -z "$CHNR_CUSTOM_URL" ]; then
|
||||
else
|
||||
DOWNLOAD_FILE_CURL "$CHNR_CUSTOM_URL" "/tmp/china_ip_route.txt" "$chnr_path"
|
||||
fi
|
||||
|
||||
if [ "$?" -eq 0 ]; then
|
||||
DOWNLOAD_RESULT=$?
|
||||
if [ "$DOWNLOAD_RESULT" -eq 0 ]; then
|
||||
LOG_OUT "Chnroute Cidr List Download Success, Check Updated..."
|
||||
#预处理
|
||||
if [ -n "$FW4" ]; then
|
||||
@ -69,7 +69,7 @@ if [ "$?" -eq 0 ]; then
|
||||
else
|
||||
LOG_OUT "Updated Chnroute Cidr List No Change, Do Nothing..."
|
||||
fi
|
||||
elif [ "$?" -eq 2 ]; then
|
||||
elif [ "$DOWNLOAD_RESULT" -eq 2 ]; then
|
||||
LOG_OUT "Updated Chnroute Cidr List No Change, Do Nothing..."
|
||||
else
|
||||
LOG_OUT "Chnroute Cidr List Update Error, Please Try Again Later..."
|
||||
|
||||
@ -35,7 +35,9 @@ fi
|
||||
CORE_TYPE="$1"
|
||||
C_CORE_TYPE=$(uci_get_config "core_type")
|
||||
SMART_ENABLE=$(uci_get_config "smart_enable" || echo 0)
|
||||
OIX_TOKEN=$(uci_get_config "oix_token")
|
||||
[ "$SMART_ENABLE" -eq 1 ] && CORE_TYPE="Smart"
|
||||
[ "$CORE_TYPE" = "Oix" ] || [ -n "$OIX_TOKEN" ] && CORE_TYPE="Oix"
|
||||
[ -z "$CORE_TYPE" ] && CORE_TYPE="Meta"
|
||||
small_flash_memory=$(uci_get_config "small_flash_memory")
|
||||
CPU_MODEL=$(uci_get_config "core_version")
|
||||
@ -62,31 +64,45 @@ else
|
||||
fi
|
||||
|
||||
CORE_CV=$($meta_core_path -v 2>/dev/null |awk -F ' ' '{print $3}' |head -1)
|
||||
DOWNLOAD_FILE="/tmp/clash_meta.tar.gz"
|
||||
TMP_FILE="/tmp/clash_meta"
|
||||
TARGET_CORE_PATH="$meta_core_path"
|
||||
|
||||
if [ "$CORE_TYPE" = "Smart" ]; then
|
||||
if [ "$CORE_TYPE" = "Oix" ]; then
|
||||
CORE_URL_PATH=""
|
||||
DOWNLOAD_FILE="/tmp/clash_meta.gz"
|
||||
CORE_LV=$(sed -n 1p /tmp/clash_last_version 2>/dev/null)
|
||||
elif [ "$CORE_TYPE" = "Smart" ]; then
|
||||
CORE_URL_PATH="$RELEASE_BRANCH/smart"
|
||||
DOWNLOAD_FILE="/tmp/clash_meta.tar.gz"
|
||||
CORE_LV=$(sed -n 2p /tmp/clash_last_version 2>/dev/null)
|
||||
else
|
||||
CORE_URL_PATH="$RELEASE_BRANCH/meta"
|
||||
DOWNLOAD_FILE="/tmp/clash_meta.tar.gz"
|
||||
CORE_LV=$(sed -n 1p /tmp/clash_last_version 2>/dev/null)
|
||||
fi
|
||||
|
||||
[ "$C_CORE_TYPE" = "$CORE_TYPE" ] || [ -z "$C_CORE_TYPE" ] && restart=1
|
||||
[ "$C_CORE_TYPE" != "$CORE_TYPE" ] || [ -z "$C_CORE_TYPE" ] && restart=1
|
||||
|
||||
if [ "$CORE_CV" != "$CORE_LV" ] || [ -z "$CORE_CV" ]; then
|
||||
if [ "$CPU_MODEL" != 0 ]; then
|
||||
LOG_TIP "【$CORE_TYPE】Core Downloading, Please Try to Download and Upload Manually If Fails"
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
|
||||
if [ "$CORE_TYPE" = "Oix" ]; then
|
||||
OIX_CORE_URL="https://github.com/vernesong/mihomo-oix/releases/download/Pre-Alpha/mihomo-${CPU_MODEL}-${CORE_LV}.gz"
|
||||
if [ "$github_address_mod" != "0" ] && [ "$github_address_mod" != "https://cdn.jsdelivr.net/" ] && [ "$github_address_mod" != "https://fastly.jsdelivr.net/" ] && [ "$github_address_mod" != "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}${OIX_CORE_URL}"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
|
||||
DOWNLOAD_URL="$OIX_CORE_URL"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
|
||||
if [ "$github_address_mod" != "0" ]; then
|
||||
if [ "$github_address_mod" == "https://cdn.jsdelivr.net/" ] || [ "$github_address_mod" == "https://fastly.jsdelivr.net/" ] || [ "$github_address_mod" == "https://testingcf.jsdelivr.net/" ]; then
|
||||
DOWNLOAD_URL="${github_address_mod}gh/vernesong/OpenClash@core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
|
||||
else
|
||||
DOWNLOAD_URL="${github_address_mod}https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
|
||||
fi
|
||||
else
|
||||
DOWNLOAD_URL="https://raw.githubusercontent.com/vernesong/OpenClash/core/${CORE_URL_PATH}/clash-${CPU_MODEL}.tar.gz"
|
||||
fi
|
||||
fi
|
||||
|
||||
retry_count=0
|
||||
@ -107,8 +123,12 @@ if [ "$CORE_CV" != "$CORE_LV" ] || [ -z "$CORE_CV" ]; then
|
||||
LOG_TIP "【"$CORE_TYPE"】Core Download Successful, Start Update..."
|
||||
extract_success=true
|
||||
[ -s "$DOWNLOAD_FILE" ] && {
|
||||
tar zxvfo "$DOWNLOAD_FILE" -C /tmp >/dev/null 2>&1 || extract_success=false
|
||||
mv /tmp/clash "$TMP_FILE" >/dev/null 2>&1 || extract_success=false
|
||||
if [ "$CORE_TYPE" = "Oix" ]; then
|
||||
gzip -dc "$DOWNLOAD_FILE" > "$TMP_FILE" 2>/dev/null || extract_success=false
|
||||
else
|
||||
tar zxvfo "$DOWNLOAD_FILE" -C /tmp >/dev/null 2>&1 || extract_success=false
|
||||
mv /tmp/clash "$TMP_FILE" >/dev/null 2>&1 || extract_success=false
|
||||
fi
|
||||
rm -rf "$DOWNLOAD_FILE" >/dev/null 2>&1
|
||||
chmod 4755 "$TMP_FILE" >/dev/null 2>&1 || extract_success=false
|
||||
"$TMP_FILE" -v >/dev/null 2>&1 || extract_success=false
|
||||
|
||||
@ -8,28 +8,54 @@ DOWNLOAD_FILE_CURL() {
|
||||
DOWNLOAD_PATH=$2
|
||||
FILE_PATH=$3
|
||||
DOWNLOAD_UA=$4
|
||||
SECRET_KEY=$5
|
||||
[ -z "$DOWNLOAD_UA" ] && DOWNLOAD_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
||||
|
||||
CURL_OUTPUT=$(curl -sLI --connect-timeout 5 -m 10 --speed-time 5 --speed-limit 1 --retry 2 \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" 2>&1)
|
||||
|
||||
NEW_ETAG=$(echo "$CURL_OUTPUT" | grep -i "^etag:" | cut -d' ' -f2- | tr -d '\r\n' | sed 's/^"//;s/"$//')
|
||||
HTTP_CODE=$(echo "$CURL_OUTPUT" | grep -i "^HTTP" | tail -1 | cut -d' ' -f2)
|
||||
HEADER_TMP="/tmp/openclash_curl_header_$$"
|
||||
DOWNLOAD_TMP="${DOWNLOAD_PATH}.download.$$"
|
||||
CACHED_ETAG=$(GET_ETAG_BY_PATH "$FILE_PATH")
|
||||
ETAG_HEADER=""
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ] && [ -n "$NEW_ETAG" ] && [ "$NEW_ETAG" = "$CACHED_ETAG" ] && [ -e "$FILE_PATH" ]; then
|
||||
return 2
|
||||
if [ -n "$CACHED_ETAG" ] && [ -e "$FILE_PATH" ]; then
|
||||
FILE_MTIME=$(date -r "$FILE_PATH" '+%Y-%m-%d %H:%M:%S' 2>/dev/null)
|
||||
LAST_UPDATE=$(GET_ETAG_TIMESTAMP_BY_PATH "$FILE_PATH")
|
||||
if [ -n "$LAST_UPDATE" ] && [ -n "$FILE_MTIME" ] && [ "$LAST_UPDATE" = "$FILE_MTIME" ]; then
|
||||
ETAG_HEADER="If-None-Match: \"${CACHED_ETAG}\""
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
|
||||
if [ "$SHOW_DOWNLOAD_PROGRESS" = "1" ] || [ "$SHOW_DOWNLOAD_PROGRESS" = "true" ]; then
|
||||
TEMP_LOG="/tmp/curl_log_$$"
|
||||
|
||||
LOG_OUT "Downloading:【$(basename "$DOWNLOAD_PATH") - 0%】"
|
||||
|
||||
(
|
||||
curl -# -L --connect-timeout 10 -m 60 --speed-time 20 --speed-limit 1 --retry 2 \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_PATH" 2>"$TEMP_LOG"
|
||||
if [ -n "$SECRET_KEY" ] && [ -n "$ETAG_HEADER" ]; then
|
||||
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
-H "X-Age-Public-Key: ${SECRET_KEY}" \
|
||||
-H "$ETAG_HEADER" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
|
||||
elif [ -n "$SECRET_KEY" ]; then
|
||||
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
-H "X-Age-Public-Key: ${SECRET_KEY}" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
|
||||
elif [ -n "$ETAG_HEADER" ]; then
|
||||
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
-H "$ETAG_HEADER" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
|
||||
else
|
||||
curl -# -L --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>"$TEMP_LOG"
|
||||
fi
|
||||
echo $? > "${TEMP_LOG}.exit"
|
||||
) &
|
||||
|
||||
@ -55,6 +81,7 @@ DOWNLOAD_FILE_CURL() {
|
||||
|
||||
wait $CURL_PID
|
||||
EXIR_CODE=$(cat "${TEMP_LOG}.exit" 2>/dev/null || echo "1")
|
||||
HTTP_CODE=$(grep -i "^HTTP" "$HEADER_TMP" 2>/dev/null | tail -1 | cut -d' ' -f2)
|
||||
|
||||
if [ "$EXIR_CODE" -eq 0 ] && [ "$LAST_PROGRESS" -ne 100 ]; then
|
||||
LOG_OUT "Downloading:【$(basename "$DOWNLOAD_PATH") - 100%】"
|
||||
@ -66,29 +93,82 @@ DOWNLOAD_FILE_CURL() {
|
||||
|
||||
rm -f "$TEMP_LOG" "${TEMP_LOG}.exit"
|
||||
|
||||
if [ "$EXIR_CODE" -ne 0 ]; then
|
||||
if [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "304" ] && [ -e "$FILE_PATH" ]; then
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
return 2
|
||||
fi
|
||||
|
||||
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" != "200" ]; then
|
||||
LOG_OUT "【$DOWNLOAD_PATH】Download Failed:【$OUTPUT】"
|
||||
rm -rf $DOWNLOAD_PATH
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
SLOG_CLEAN
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 60 --speed-time 30 --speed-limit 1 --retry 2 -H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" -o "$DOWNLOAD_PATH" 2>&1)
|
||||
EXIR_CODE=$?
|
||||
HTTP_CODE=$(echo "$CURL_OUTPUT" | tail -n1)
|
||||
DOWNLOAD_TRY=0
|
||||
MAX_DOWNLOAD_RETRIES=3
|
||||
while [ "$DOWNLOAD_TRY" -lt "$MAX_DOWNLOAD_RETRIES" ]; do
|
||||
DOWNLOAD_TRY=$((DOWNLOAD_TRY + 1))
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
if [ -n "$SECRET_KEY" ] && [ -n "$ETAG_HEADER" ]; then
|
||||
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
-H "X-Age-Public-Key: ${SECRET_KEY}" \
|
||||
-H "$ETAG_HEADER" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
|
||||
elif [ -n "$SECRET_KEY" ]; then
|
||||
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
-H "X-Age-Public-Key: ${SECRET_KEY}" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
|
||||
elif [ -n "$ETAG_HEADER" ]; then
|
||||
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" \
|
||||
-H "$ETAG_HEADER" \
|
||||
"$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
|
||||
else
|
||||
CURL_OUTPUT=$(curl -w "\n%{http_code}" -SsL --connect-timeout 30 -m 180 --speed-time 30 --speed-limit 1 --retry 2 \
|
||||
-D "$HEADER_TMP" \
|
||||
-H "User-Agent: ${DOWNLOAD_UA}" "$DOWNLOAD_URL" -o "$DOWNLOAD_TMP" 2>&1)
|
||||
fi
|
||||
EXIR_CODE=$?
|
||||
HTTP_CODE=$(echo "$CURL_OUTPUT" | tail -n1)
|
||||
if { [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "200" ]; } || { [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "304" ] && [ -e "$FILE_PATH" ]; }; then
|
||||
break
|
||||
fi
|
||||
[ "$DOWNLOAD_TRY" -lt "$MAX_DOWNLOAD_RETRIES" ] && sleep 1
|
||||
done
|
||||
|
||||
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" -ne 200 ]; then
|
||||
if [ "$EXIR_CODE" -eq 0 ] && [ "$HTTP_CODE" = "304" ] && [ -e "$FILE_PATH" ]; then
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
return 2
|
||||
fi
|
||||
|
||||
if [ "$EXIR_CODE" -ne 0 ] || [ "$HTTP_CODE" != "200" ]; then
|
||||
OUTPUT=$(echo "$CURL_OUTPUT" | sed '$d' | grep -a 'curl:' | tail -n 1)
|
||||
LOG_OUT "【$DOWNLOAD_PATH】Download Failed:【$OUTPUT】"
|
||||
rm -rf $DOWNLOAD_PATH
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
SLOG_CLEAN
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! mv -f "$DOWNLOAD_TMP" "$DOWNLOAD_PATH"; then
|
||||
LOG_OUT "【$DOWNLOAD_PATH】Download Failed:【Unable to save download file】"
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
SLOG_CLEAN
|
||||
return 1
|
||||
fi
|
||||
NEW_ETAG=$(grep -i "^etag:" "$HEADER_TMP" 2>/dev/null | tail -1 | cut -d' ' -f2- | tr -d '\r\n' | sed 's/^"//;s/"$//')
|
||||
|
||||
if [ -n "$NEW_ETAG" ] && [ "$HTTP_CODE" = "200" ]; then
|
||||
SAVE_ETAG_TO_CACHE "$DOWNLOAD_URL" "$NEW_ETAG" "$FILE_PATH"
|
||||
fi
|
||||
|
||||
rm -f "$HEADER_TMP" "$DOWNLOAD_TMP"
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,9 +16,9 @@ del_lock() {
|
||||
ipk_v()
|
||||
{
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
echo $(opkg status "$1" 2>/dev/null |grep 'Version' |awk -F ': ' '{print $2}' 2>/dev/null)
|
||||
echo $(rm -f /var/lock/opkg.lock && opkg status "$1" 2>/dev/null |grep 'Version' |awk -F ': ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
echo $(apk list "$1" 2>/dev/null |grep 'installed' | grep -oE '\d+(\.\d+)*' | head -1)
|
||||
echo $(rm -f /lib/apk/db/lock && apk list "$1" 2>/dev/null |grep 'installed' | grep -oE '\d+(\.\d+)*' | head -1)
|
||||
fi
|
||||
}
|
||||
|
||||
@ -42,9 +42,9 @@ RAW_CONFIG_FILE=$(uci_get_config "config_path")
|
||||
CONFIG_FILE="/etc/openclash/$(uci_get_config "config_path" |awk -F '/' '{print $5}' 2>/dev/null)"
|
||||
core_model=$(uci_get_config "core_version")
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
cpu_model=$(opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' 2>/dev/null)
|
||||
cpu_model=$(rm -f /var/lock/opkg.lock && opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
cpu_model=$(apk list libc 2>/dev/null|awk '{print $2}')
|
||||
cpu_model=$(rm -f /lib/apk/db/lock && apk list libc 2>/dev/null|awk '{print $2}')
|
||||
fi
|
||||
core_meta_version=$(/etc/openclash/core/clash_meta -v 2>/dev/null |awk -F ' ' '{print $3}' |head -1 2>/dev/null)
|
||||
op_version=$(ipk_v "luci-app-openclash")
|
||||
@ -54,6 +54,19 @@ router_self_proxy=$(uci_get_config "router_self_proxy")
|
||||
core_type=$(uci_get_config "core_type" || echo "Dev")
|
||||
da_password=$(uci_get_config "dashboard_password")
|
||||
cn_port=$(uci_get_config "cn_port")
|
||||
stack_type=$(uci_get_config "stack_type")
|
||||
delay_start=$(uci_get_config "delay_start")
|
||||
log_size=$(uci_get_config "log_size")
|
||||
bypass_gateway_compatible=$(uci_get_config "bypass_gateway_compatible")
|
||||
disable_quic_go_gso=$(uci_get_config "disable_quic_go_gso")
|
||||
small_flash_memory=$(uci_get_config "small_flash_memory")
|
||||
enable_meta_sniffer=$(uci_get_config "enable_meta_sniffer")
|
||||
enable_respect_rules=$(uci_get_config "enable_respect_rules")
|
||||
skip_proxy_address=$(uci_get_config "skip_proxy_address")
|
||||
disable_udp_quic=$(uci_get_config "disable_udp_quic")
|
||||
lan_ac_mode=$(uci_get_config "lan_ac_mode")
|
||||
ipv6_mode=$(uci_get_config "ipv6_mode")
|
||||
china_ip6_route=$(uci_get_config "china_ip6_route")
|
||||
lan_interface_name=$(uci_get_config "lan_interface_name" || echo "0")
|
||||
if [ "$lan_interface_name" = "0" ]; then
|
||||
lan_ip=$(uci -q get network.lan.ipaddr |awk -F '/' '{print $1}' 2>/dev/null || ip address show $(uci -q -p /tmp/state get network.lan.device || uci -q -p /tmp/state get network.lan.device) | grep -w "inet" 2>/dev/null |grep -Eo 'inet [0-9\.]+' | awk '{print $2}' |head -1 || ip addr show 2>/dev/null | grep -w 'inet' | grep 'global' | grep 'brd' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -n 1)
|
||||
@ -122,6 +135,12 @@ cat >> "$DEBUG_LOG" <<-EOF
|
||||
LuCI版本: $(ipk_v "luci")
|
||||
内核版本: $(uname -r 2>/dev/null)
|
||||
处理器架构: $cpu_model
|
||||
系统运行时间: $(uptime 2>/dev/null)
|
||||
|
||||
#磁盘与内存
|
||||
$(df -h / /tmp /etc/openclash 2>/dev/null)
|
||||
$(free -m 2>/dev/null)
|
||||
$(cat /proc/meminfo 2>/dev/null | grep -E '^(MemTotal|MemAvailable|SwapTotal|SwapFree)')
|
||||
|
||||
#此项有值时,如不使用IPv6,建议到网络-接口-lan的设置中禁用IPV6的DHCP
|
||||
IPV6-DHCP: $(uci -q get dhcp.lan.dhcpv6)
|
||||
@ -129,6 +148,8 @@ IPV6-DHCP: $(uci -q get dhcp.lan.dhcpv6)
|
||||
DNS劫持: $(dns_re "$enable_redirect_dns")
|
||||
#DNS劫持为Dnsmasq时,此项结果应仅有配置文件的DNS监听地址
|
||||
Dnsmasq转发设置: $(uci -q get dhcp.@dnsmasq[0].server)
|
||||
Dnsmasq完整配置:
|
||||
$(uci show dhcp.@dnsmasq[0] 2>/dev/null)
|
||||
EOF
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
@ -147,6 +168,8 @@ ruby: $(ts_re "$(ipk_v "ruby")")
|
||||
ruby-yaml: $(ts_re "$(ipk_v "ruby-yaml")")
|
||||
ruby-psych: $(ts_re "$(ipk_v "ruby-psych")")
|
||||
ruby-pstore: $(ts_re "$(ipk_v "ruby-pstore")")
|
||||
ruby版本: $(ruby --version 2>/dev/null || echo "未安装")
|
||||
ruby功能测试: $(ruby -e "require 'yaml'; YAML.load('test: ok'); puts '正常'" 2>/dev/null || echo "异常")
|
||||
kmod-tun(TUN模式): $(ts_re "$(ipk_v "kmod-tun")")
|
||||
luci-compat(Luci >= 19.07): $(ts_re "$(ipk_v "luci-compat")")
|
||||
kmod-inet-diag(PROCESS-NAME): $(ts_re "$(ipk_v "kmod-inet-diag")")
|
||||
@ -166,6 +189,12 @@ kmod-ipt-nat: $(ts_re "$(ipk_v "kmod-ipt-nat")")
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#内核模块加载状态:
|
||||
$(lsmod | grep -E 'tun|tproxy|inet_diag' 2>/dev/null || echo "无相关模块")
|
||||
EOF
|
||||
|
||||
#core
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
@ -219,6 +248,22 @@ fi
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== GEO 数据文件 =====================#
|
||||
|
||||
$(ls -lh /etc/openclash/Country.mmdb /etc/openclash/GeoIP.dat /etc/openclash/GeoSite.dat /etc/openclash/ASN.mmdb 2>/dev/null)
|
||||
|
||||
#===================== 模型、缓存文件状态 =====================#
|
||||
|
||||
Model.bin: $(ls -lh /etc/openclash/Model.bin 2>/dev/null || echo "不存在")
|
||||
cache.db: $(ls -lh /etc/openclash/cache.db 2>/dev/null || echo "不存在")
|
||||
|
||||
#===================== 冲突插件检测 =====================#
|
||||
|
||||
$(ps | grep -E 'passwall|ssr-plus|bypass|helloworld' | grep -v grep 2>/dev/null || echo "未检测到冲突插件")
|
||||
EOF
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== 插件设置 =====================#
|
||||
|
||||
当前配置文件: $RAW_CONFIG_FILE
|
||||
@ -236,11 +281,31 @@ IPV6-DNS解析: $(ts_cf "$ipv6_dns")
|
||||
仅允许常用端口流量: $(ts_cf "$common_ports")
|
||||
绕过中国大陆IP: $(ts_cf "$china_ip_route")
|
||||
路由本机代理: $(ts_cf "$router_self_proxy")
|
||||
TUN堆栈类型: ${stack_type:-system}
|
||||
启动延迟: ${delay_start:-0}秒
|
||||
日志大小: ${log_size:-1024}KB
|
||||
旁路由兼容: $(ts_cf "$bypass_gateway_compatible")
|
||||
禁用quic-go GSO: $(ts_cf "$disable_quic_go_gso")
|
||||
小闪存模式: $(ts_cf "$small_flash_memory")
|
||||
域名嗅探: $(ts_cf "$enable_meta_sniffer")
|
||||
DNS代理: $(ts_cf "$enable_respect_rules")
|
||||
绕过服务器地址: $(ts_cf "$skip_proxy_address")
|
||||
禁用QUIC: $(ts_cf "$disable_udp_quic")
|
||||
访问控制模式: $([ "$lan_ac_mode" = "1" ] && echo "White" || echo "Black")
|
||||
IPv6模式: ${ipv6_mode:-0}
|
||||
绕过IPv6区域: $(ts_cf "$china_ip6_route")
|
||||
|
||||
EOF
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== Cron 定时任务 =====================#
|
||||
|
||||
$(crontab -l 2>/dev/null | grep -i openclash || echo "无 OpenClash 相关 cron 任务")
|
||||
EOF
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== 覆写模块设置 =====================#
|
||||
|
||||
$(uci -q show openclash.@overwrite[0])
|
||||
@ -408,6 +473,14 @@ netstat -nlp |grep clash >> "$DEBUG_LOG" 2>/dev/null
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== 网络接口状态 =====================#
|
||||
|
||||
EOF
|
||||
ip link show >> "$DEBUG_LOG" 2>/dev/null
|
||||
ip addr show | grep -E 'inet |utun' >> "$DEBUG_LOG" 2>/dev/null
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== 测试本机DNS查询(www.baidu.com) =====================#
|
||||
|
||||
EOF
|
||||
@ -462,6 +535,15 @@ else
|
||||
curl -SsIL -m 3 --retry 2 "$VERSION_URL" >> "$DEBUG_LOG" 2>/dev/null
|
||||
fi
|
||||
|
||||
if pidof clash >/dev/null; then
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== Mihomo API 健康检查 =====================#
|
||||
|
||||
EOF
|
||||
curl -Ss -m 3 http://127.0.0.1:${cn_port}/version >> "$DEBUG_LOG" 2>/dev/null
|
||||
fi
|
||||
|
||||
cat >> "$DEBUG_LOG" <<-EOF
|
||||
|
||||
#===================== 最近运行日志 (切换为Debug模式) =====================#
|
||||
|
||||
@ -50,7 +50,7 @@ if [ -z "$CONFIG_FILE" ] || [ ! -f "$CONFIG_FILE" ]; then
|
||||
fi
|
||||
|
||||
if [ -n "$(pidof clash)" ] && [ -f "$CONFIG_FILE" ]; then
|
||||
if [ "$small_flash_memory" == "1" ] || [ -n "$(echo $core_version |grep mips)" ] || [ -n "$(echo $DISTRIB_ARCH |grep mips)" ] || [ -n "$(opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' |grep mips)" ] || [ -n "$(apk list libc 2>/dev/null |grep mips)" ]; then
|
||||
if [ "$small_flash_memory" == "1" ] || [ -n "$(echo $core_version |grep mips)" ] || [ -n "$(echo $DISTRIB_ARCH |grep mips)" ] || [ -n "$(rm -f /var/lock/opkg.lock && opkg status libc 2>/dev/null |grep 'Architecture' |awk -F ': ' '{print $2}' |grep mips)" ] || [ -n "$(rm -f /lib/apk/db/lock && apk list libc 2>/dev/null |grep mips)" ]; then
|
||||
CACHE_PATH="/tmp/etc/openclash/cache.db"
|
||||
if [ -f "$CACHE_PATH" ]; then
|
||||
cmp -s "$CACHE_PATH" "$HISTORY_PATH"
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
require "nixio"
|
||||
require "luci.util"
|
||||
require "luci.sys"
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local json = require "luci.jsonc"
|
||||
|
||||
local function oix_checkin()
|
||||
local info, path, checkin
|
||||
local token = fs.uci_get_config("config", "oix_token")
|
||||
local enable = fs.uci_get_config("config", "oix_checkin") or 0
|
||||
local interval = fs.uci_get_config("config", "oix_checkin_interval") or 1
|
||||
local multiple = fs.uci_get_config("config", "oix_checkin_multiple") or 1
|
||||
path = "/tmp/oix_checkin"
|
||||
if token and enable == "1" then
|
||||
checkin = string.format("curl -sL -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '{\"multiple\":\"%s\"}' -X POST https://oix-api.dler.io/api/v1/checkin -o %s", token, multiple, path)
|
||||
if fs.readfile(path) == "" or not fs.readfile(path) then
|
||||
luci.sys.exec(checkin)
|
||||
else
|
||||
if (os.time() - fs.mtime(path) > interval*3600+1) then
|
||||
fs.unlink(path)
|
||||
luci.sys.exec(checkin)
|
||||
else
|
||||
os.exit(0)
|
||||
end
|
||||
end
|
||||
if fs.readfile(path) == "" or not fs.readfile(path) then
|
||||
fs.writefile(path, " ")
|
||||
end
|
||||
info = fs.readfile(path)
|
||||
if info then
|
||||
info = json.parse(info)
|
||||
end
|
||||
if info and info.ret == 200 then
|
||||
luci.sys.exec(string.format('echo "%s [Info] oixCloud Checkin Successful, Result:【%s】" >> /tmp/openclash.log', os.date("%Y-%m-%d %H:%M:%S"), info.data.checkin))
|
||||
else
|
||||
if info and info.msg then
|
||||
luci.sys.exec(string.format('echo "%s [Info] oixCloud Checkin Failed, Result:【%s】" >> /tmp/openclash.log', os.date("%Y-%m-%d %H:%M:%S"), info.msg))
|
||||
else
|
||||
luci.sys.exec(string.format('echo "%s [Info] oixCloud Checkin Failed! Please Check And Try Again..." >> /tmp/openclash.log', os.date("%Y-%m-%d %H:%M:%S")))
|
||||
end
|
||||
end
|
||||
end
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
oix_checkin()
|
||||
@ -709,7 +709,7 @@ function table_sort_by_cache(t)
|
||||
end
|
||||
end
|
||||
if #tab > 0 then
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] "..type.." Group:".."【"..group_match_name.."】".."Cached Compliant Nodes Number:".."【"..#(tab).."】"..", Cached Non-compliant Nodes Number:".."【"..#(tab_b).."】"..", Prioritize Testing With Cached Compliant Nodes...")
|
||||
print(os.date("%Y-%m-%d %H:%M:%S").." [Info] 【"..group_match_name.."】".."Cached Compliant Nodes Number:".."【"..#(tab).."】"..", Cached Non-compliant Nodes Number:".."【"..#(tab_b).."】"..", Prioritize Testing With Cached Compliant Nodes...")
|
||||
end
|
||||
for k,v in pairs(tab_b) do table.insert(tab, v) end
|
||||
return tab
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
local nixio = require "nixio"
|
||||
local jsonc = require "luci.jsonc"
|
||||
local util = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
|
||||
if not nixio.fs.access("/usr/bin/base64") and not nixio.fs.access("/bin/base64") then
|
||||
os.exit(1)
|
||||
@ -14,31 +15,8 @@ if not file_path or not nixio.fs.access(file_path) then
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local function raw_base64_decode(str)
|
||||
if not str or str == "" then return nil end
|
||||
|
||||
str = str:gsub("-", "+"):gsub("_", "/")
|
||||
|
||||
local padding = #str % 4
|
||||
if padding > 0 then
|
||||
str = str .. string.rep("=", 4 - padding)
|
||||
end
|
||||
|
||||
local cmd = "printf '%s' '" .. str .. "' | base64 -d 2>/dev/null"
|
||||
local f = io.popen(cmd, "r")
|
||||
if not f then return nil end
|
||||
local result = f:read("*a")
|
||||
f:close()
|
||||
|
||||
if result and result ~= "" then
|
||||
return result
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function base64_decode_subscription(str)
|
||||
local result = raw_base64_decode(str)
|
||||
local result = fs.decode64(str)
|
||||
if result and result:find("://") then
|
||||
return result
|
||||
end
|
||||
@ -126,7 +104,7 @@ local function get_server_from_url(line)
|
||||
original_body = original_body:match("([^#]+)") or original_body
|
||||
|
||||
-- base64
|
||||
local decoded = raw_base64_decode(original_body)
|
||||
local decoded = fs.decode64(original_body)
|
||||
if decoded then
|
||||
-- V2RayN JSON
|
||||
local ok, data = pcall(jsonc.parse, decoded)
|
||||
@ -160,7 +138,7 @@ local function get_server_from_url(line)
|
||||
else
|
||||
-- ss://base64
|
||||
original_body = original_body:match("([^#]+)") or original_body
|
||||
local decoded = raw_base64_decode(original_body)
|
||||
local decoded = fs.decode64(original_body)
|
||||
if decoded then
|
||||
server = decoded:match("@([^:/]+)")
|
||||
else
|
||||
@ -170,7 +148,7 @@ local function get_server_from_url(line)
|
||||
|
||||
elseif scheme == "ssr" then
|
||||
original_body = original_body:match("([^#]+)") or original_body
|
||||
local decoded = raw_base64_decode(original_body)
|
||||
local decoded = fs.decode64(original_body)
|
||||
if decoded then
|
||||
-- ssr://host:port:protocol:method:obfs:urlsafebase64pass/?params
|
||||
local before_query = decoded:match("^([^/?]+)")
|
||||
@ -214,7 +192,7 @@ local function get_server_from_url(line)
|
||||
|
||||
-- fallback
|
||||
if not server then
|
||||
local body_to_parse = raw_base64_decode(original_body) or original_body
|
||||
local body_to_parse = fs.decode64(original_body) or original_body
|
||||
server = body_to_parse:match("@([^:/]+)") or -- user@host
|
||||
body_to_parse:match("([^:/]+)") or -- host
|
||||
body_to_parse:match("//([^:/]+)") -- //host
|
||||
|
||||
@ -51,12 +51,31 @@ version_compare() {
|
||||
return 1
|
||||
}
|
||||
|
||||
run_with_timeout() {
|
||||
local timeout_sec="$1"
|
||||
shift
|
||||
"$@" &
|
||||
local _pid=$!
|
||||
(
|
||||
sleep "$timeout_sec"
|
||||
kill $_pid 2>/dev/null
|
||||
sleep 0.5
|
||||
kill -9 $_pid 2>/dev/null
|
||||
) &
|
||||
local _watchdog=$!
|
||||
wait $_pid 2>/dev/null
|
||||
local _ret=$?
|
||||
kill $_watchdog 2>/dev/null
|
||||
wait $_watchdog 2>/dev/null
|
||||
return $_ret
|
||||
}
|
||||
|
||||
LAST_OPVER="/tmp/openclash_last_version"
|
||||
LAST_VER=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
OP_CV=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
OP_CV=$(rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep "installed" | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
fi
|
||||
OP_LV=$(sed -n 1p "$LAST_OPVER" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
|
||||
RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
|
||||
@ -129,27 +148,48 @@ if [ -n "$OP_CV" ] && [ -n "$OP_LV" ] && version_compare "$OP_CV" "$OP_LV" && [
|
||||
LOG_TIP "【$retry_count/$max_retries】【OpenClash - v$LAST_VER】Download successful, start pre update test..."
|
||||
|
||||
pre_test_success=false
|
||||
pkg_update_success=true
|
||||
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
opkg update >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
sleep 2
|
||||
pkg_update_success=false
|
||||
continue
|
||||
fi
|
||||
update_retry=0
|
||||
max_update_retry=2
|
||||
while [ $update_retry -lt $max_update_retry ]; do
|
||||
update_retry=$((update_retry + 1))
|
||||
run_with_timeout 30 opkg update >/dev/null 2>&1
|
||||
opkg_ret=$?
|
||||
rm -f /var/lock/opkg.lock
|
||||
if [ $opkg_ret -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
if [ $update_retry -lt $max_update_retry ]; then
|
||||
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】opkg update failed or timed out, retrying..."
|
||||
sleep 2
|
||||
else
|
||||
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】opkg update failed, trying pre update test..."
|
||||
fi
|
||||
done
|
||||
if [ -s "/tmp/openclash.ipk" ]; then
|
||||
if [ -n "$(opkg install /tmp/openclash.ipk --noaction 2>/dev/null |grep 'Upgrading luci-app-openclash on root' 2>/dev/null)" ]; then
|
||||
pre_test_success=true
|
||||
fi
|
||||
fi
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
apk update >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
sleep 2
|
||||
pkg_update_success=false
|
||||
continue
|
||||
fi
|
||||
update_retry=0
|
||||
max_update_retry=2
|
||||
while [ $update_retry -lt $max_update_retry ]; do
|
||||
update_retry=$((update_retry + 1))
|
||||
run_with_timeout 30 apk update >/dev/null 2>&1
|
||||
apk_ret=$?
|
||||
rm -f /lib/apk/db/lock /tmp/apk.lock
|
||||
if [ $apk_ret -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
if [ $update_retry -lt $max_update_retry ]; then
|
||||
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】apk update failed or timed out, retrying..."
|
||||
sleep 2
|
||||
else
|
||||
LOG_ERROR "【$update_retry/$max_update_retry】【OpenClash - v$LAST_VER】apk update failed, trying pre update test..."
|
||||
fi
|
||||
done
|
||||
if [ -s "/tmp/openclash.apk" ]; then
|
||||
apk add -s -q --force-overwrite --clean-protected --allow-untrusted /tmp/openclash.apk >/dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
|
||||
@ -37,7 +37,7 @@ RELEASE_BRANCH=$(uci_get_config "release_branch" || echo "master")
|
||||
if [ -x "/bin/opkg" ]; then
|
||||
OP_CV=$(rm -f /var/lock/opkg.lock && opkg status luci-app-openclash 2>/dev/null |grep 'Version' |awk -F 'Version: ' '{print $2}' 2>/dev/null)
|
||||
elif [ -x "/usr/bin/apk" ]; then
|
||||
OP_CV=$(apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
OP_CV=$(rm -f /lib/apk/db/lock && apk list luci-app-openclash 2>/dev/null|grep 'installed' | grep -oE '[0-9]+(\.[0-9]+)*' | head -1 2>/dev/null)
|
||||
fi
|
||||
OP_LV=$(sed -n 1p "$DOWNLOAD_FILE" 2>/dev/null |sed "s/^v//g" |tr -d "\n")
|
||||
github_address_mod=$(uci_get_config "github_address_mod" || echo 0)
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
. /usr/share/openclash/uci.sh
|
||||
|
||||
LOG_FILE="/tmp/openclash.log"
|
||||
CLASH="/etc/openclash/clash"
|
||||
CFG_UPDATE_INT=0
|
||||
SKIP_PROXY_ADDRESS=1
|
||||
SKIP_PROXY_ADDRESS_INTERVAL=30
|
||||
@ -29,8 +30,6 @@ begin
|
||||
exit;
|
||||
end
|
||||
|
||||
require 'thread'
|
||||
|
||||
servers_to_process = Array.new
|
||||
|
||||
# Servers from proxies
|
||||
@ -46,20 +45,41 @@ begin
|
||||
if provider.key?('path') and not provider['path'].empty?
|
||||
path = provider['path'].start_with?('./') ? '/etc/openclash/' + provider['path'][2..-1] : provider['path']
|
||||
if File.exist?(path)
|
||||
file_is_age_encrypted = File.read(path, 512).include?('BEGIN AGE ENCRYPTED FILE') rescue false
|
||||
begin
|
||||
provider_config = YAML.load_file(path)
|
||||
if provider.key?('age-secret-key') and not provider['age-secret-key'].to_s.empty?
|
||||
begin
|
||||
provider_config = YAML.load_file(path, secret: provider['age-secret-key']) rescue nil
|
||||
rescue Exception => e
|
||||
YAML.LOG_WARN('Set Proxies Address Skip Failed,【' + path + ': ' + e.message+'】')
|
||||
continue
|
||||
end
|
||||
else
|
||||
if file_is_age_encrypted
|
||||
YAML.LOG_WARN('Set Proxies Address Skip Failed,【' + path + ': File is AGE encrypted but no secret key provided】')
|
||||
next
|
||||
end
|
||||
provider_config = YAML.load_file(path)
|
||||
end
|
||||
|
||||
if provider_config.is_a?(Hash) and provider_config.key?('proxies') and not provider_config['proxies'].nil?
|
||||
provider_config['proxies'].each do |p|
|
||||
servers_to_process.push(p['server']) if p.key?('server')
|
||||
end
|
||||
end
|
||||
rescue Psych::SyntaxError, ArgumentError
|
||||
begin
|
||||
syscall = \"lua /usr/share/openclash/openclash_sub_parser.lua \\\"#{path}\\\"\"
|
||||
sub_servers = IO.popen(syscall).read.split(/\n+/)
|
||||
servers_to_process.concat(sub_servers) if sub_servers
|
||||
rescue Exception => e
|
||||
YAML.LOG_WARN('Failed to parse subscription file with Lua helper ' + path + ': ' + e.message)
|
||||
if not provider.key?('age-secret-key') or provider['age-secret-key'].to_s.empty?
|
||||
if file_is_age_encrypted
|
||||
YAML.LOG_WARN('Failed to parse config file with Lua helper【' + path + ': File is AGE encrypted, cannot parse with Lua】')
|
||||
next
|
||||
end
|
||||
begin
|
||||
syscall = \"lua /usr/share/openclash/openclash_sub_parser.lua \\\"#{path}\\\"\"
|
||||
sub_servers = IO.popen(syscall).read.split(/\n+/)
|
||||
servers_to_process.concat(sub_servers) if sub_servers
|
||||
rescue Exception => e
|
||||
YAML.LOG_WARN('Failed to parse config file with Lua helper【' + path + ': ' + e.message+'】')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -195,8 +215,8 @@ fi
|
||||
## Log File Size Manage:
|
||||
LOGSIZE=`ls -l /tmp/openclash.log |awk '{print int($5/1024)}'`
|
||||
if [ "$LOGSIZE" -gt "$log_size" ]; then
|
||||
: > /tmp/openclash.log
|
||||
LOG_WATCHDOG "Log Size Limit, Clean Up All Log Records..."
|
||||
: > /tmp/openclash.log
|
||||
LOG_WATCHDOG "Log Size Limit, Clean Up All Log Records..."
|
||||
fi
|
||||
|
||||
## 防火墙检查
|
||||
@ -361,6 +381,9 @@ fi
|
||||
fi
|
||||
fi
|
||||
|
||||
##Dler Cloud Checkin
|
||||
/usr/share/openclash/openclash_oix_checkin.lua >/dev/null 2>&1
|
||||
|
||||
## 配置文件循环更新
|
||||
if [ "$cfg_update" -eq 1 ] && [ "$cfg_update_mode" -eq 1 ]; then
|
||||
if [ "$CFG_UPDATE_INT" -ne 0 ]; then
|
||||
@ -374,59 +397,59 @@ fi
|
||||
if [ "$STREAM_AUTO_SELECT" -ne 0 ]; then
|
||||
if [ "$(expr "$STREAM_AUTO_SELECT" % "$stream_auto_select_interval")" -eq 0 ] || [ "$STREAM_AUTO_SELECT" -eq 1 ]; then
|
||||
if [ "$stream_auto_select_netflix" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Netflix】..."
|
||||
LOG_TIP "【Netflix】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Netflix" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_disney" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Disney Plus】..."
|
||||
LOG_TIP "【Disney Plus】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Disney Plus" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_google_not_cn" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Google Not CN】..."
|
||||
LOG_TIP "【Google Not CN】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Google" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_ytb" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【YouTube Premium】..."
|
||||
LOG_TIP "【YouTube Premium】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "YouTube Premium" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_prime_video" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Amazon Prime Video】..."
|
||||
LOG_TIP "【Amazon Prime Video】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Amazon Prime Video" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_hbo_max" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【HBO Max】..."
|
||||
LOG_TIP "【HBO Max】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "HBO Max" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_tvb_anywhere" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【TVB Anywhere+】..."
|
||||
LOG_TIP "【TVB Anywhere+】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "TVB Anywhere+" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_dazn" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【DAZN】..."
|
||||
LOG_TIP "【DAZN】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "DAZN" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_paramount_plus" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Paramount Plus】..."
|
||||
LOG_TIP "【Paramount Plus】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Paramount Plus" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_discovery_plus" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Discovery Plus】..."
|
||||
LOG_TIP "【Discovery Plus】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Discovery Plus" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_bilibili" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Bilibili】..."
|
||||
LOG_TIP "【Bilibili】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Bilibili" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_openai" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【OpenAI】..."
|
||||
LOG_TIP "【OpenAI】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "OpenAI" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_claude" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Claude】..."
|
||||
LOG_TIP "【Claude】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Claude" >> $LOG_FILE
|
||||
fi
|
||||
if [ "$stream_auto_select_gemini" -eq 1 ]; then
|
||||
LOG_TIP "Start Auto Select Unlock Proxy For【Gemini】..."
|
||||
LOG_TIP "【Gemini】Start Auto Select Unlock Proxy..."
|
||||
/usr/share/openclash/openclash_streaming_unlock.lua "Gemini" >> $LOG_FILE
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -1,6 +1,73 @@
|
||||
#!/bin/sh
|
||||
. /lib/functions.sh
|
||||
|
||||
uci_get_config() {
|
||||
local key="$1"
|
||||
uci -q get openclash.@overwrite[0]."$key" || uci -q get openclash.config."$key"
|
||||
}
|
||||
|
||||
uci_get_age_public_keys() {
|
||||
local name="$1"
|
||||
[ -n "$name" ] || return 0
|
||||
|
||||
_print_pub() {
|
||||
local section="$1"
|
||||
config_get cfg_name "$section" name
|
||||
if [ "$cfg_name" = "$name" ]; then
|
||||
config_get pub "$section" public
|
||||
[ -n "$pub" ] && printf '%s\n' "$pub"
|
||||
fi
|
||||
}
|
||||
|
||||
config_load openclash
|
||||
config_foreach _print_pub config_age_secret
|
||||
}
|
||||
|
||||
uci_get_age_secret_keys() {
|
||||
local name="$1"
|
||||
[ -n "$name" ] || return 0
|
||||
|
||||
_print_sec() {
|
||||
local section="$1"
|
||||
config_get cfg_name "$section" name
|
||||
if [ "$cfg_name" = "$name" ]; then
|
||||
config_get sec "$section" secret
|
||||
[ -n "$sec" ] && printf '%s\n' "$sec"
|
||||
fi
|
||||
}
|
||||
|
||||
config_load openclash
|
||||
config_foreach _print_sec config_age_secret
|
||||
}
|
||||
|
||||
uci_set_age_keys_by_name() {
|
||||
local name="$1"
|
||||
local secret="$2"
|
||||
local public="$3"
|
||||
local target_section=""
|
||||
|
||||
[ -n "$name" ] || return 1
|
||||
|
||||
_find_age_section() {
|
||||
local section="$1"
|
||||
local cfg_name
|
||||
config_get cfg_name "$section" name
|
||||
[ "$cfg_name" = "$name" ] && target_section="$section"
|
||||
}
|
||||
|
||||
config_load openclash
|
||||
config_foreach _find_age_section config_age_secret
|
||||
|
||||
if [ -z "$target_section" ]; then
|
||||
target_section=$(uci -q add openclash config_age_secret)
|
||||
fi
|
||||
|
||||
[ -n "$target_section" ] || return 1
|
||||
|
||||
uci -q set openclash."$target_section".name="$name"
|
||||
[ -n "$secret" ] && uci -q set openclash."$target_section".secret="$secret"
|
||||
[ -n "$public" ] && uci -q set openclash."$target_section".public="$public"
|
||||
uci -q commit openclash
|
||||
|
||||
return 0
|
||||
}
|
||||
@ -427,9 +427,9 @@ begin
|
||||
Value['external-ui-url'] = 'https://codeload.github.com/MetaCubeX/Yacd-meta/zip/refs/heads/gh-pages'
|
||||
end
|
||||
when 'metacubexd'
|
||||
Value['external-ui-url'] = 'https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages'
|
||||
Value['external-ui-url'] = 'https://codeload.github.com/MetaCubeX/metacubexd/zip/refs/heads/gh-pages'
|
||||
when 'zashboard'
|
||||
Value['external-ui-url'] = 'https://codeload.github.com/Zephyruso/zashboard/zip/refs/heads/gh-pages-cdn-fonts'
|
||||
Value['external-ui-url'] = 'https://codeload.github.com/Zephyruso/zashboard/zip/refs/heads/gh-pages-cdn-fonts'
|
||||
end
|
||||
if !Value.key?('keep-alive-interval') && !Value.key?('keep-alive-idle')
|
||||
Value['keep-alive-interval'] = 15
|
||||
@ -456,7 +456,7 @@ begin
|
||||
end
|
||||
|
||||
if smart_collect
|
||||
(Value['profile'] ||= {})['smart-collector-size'] = smart_collect_size.to_f
|
||||
(Value['profile'] ||= {})['smart-collector-size'] = smart_collect_size.to_f
|
||||
end
|
||||
|
||||
Value['geox-url'] ||= {}
|
||||
|
||||
@ -22,7 +22,7 @@ yml_other_set()
|
||||
begin
|
||||
thread_pool = [];
|
||||
# GEOIP replace
|
||||
geoip_pattern = /GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
|
||||
geoip_pattern = /^GEOIP,([A-Za-z]{2}),([^,]+)(,.*)?/;
|
||||
match_pattern = /(^MATCH.*|^FINAL.*)/;
|
||||
thread_pool << Thread.new{
|
||||
#BT/P2P DIRECT Rules
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* OpenClash Common JavaScript Utilities
|
||||
* Shared functions used across multiple view templates.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Detects if an element has a dark background by analyzing its computed CSS.
|
||||
* Used by CodeMirror log editor to set dark mode attribute.
|
||||
* @param {HTMLElement} element - The element to check
|
||||
* @returns {boolean} True if the background is dark
|
||||
*/
|
||||
function isDarkBackground(element) {
|
||||
var cachedTheme = localStorage.getItem('oc-theme');
|
||||
if (cachedTheme === 'dark') {
|
||||
return true;
|
||||
} else if (cachedTheme === 'light') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var style = window.getComputedStyle(element);
|
||||
var bgColor = style.backgroundColor;
|
||||
var r, g, b;
|
||||
if (/rgb\(/.test(bgColor)) {
|
||||
var rgb = bgColor.match(/\d+/g);
|
||||
r = parseInt(rgb[0]);
|
||||
g = parseInt(rgb[1]);
|
||||
b = parseInt(rgb[2]);
|
||||
} else if (/#/.test(bgColor)) {
|
||||
if (bgColor.length === 4) {
|
||||
r = parseInt(bgColor[1] + bgColor[1], 16);
|
||||
g = parseInt(bgColor[2] + bgColor[2], 16);
|
||||
b = parseInt(bgColor[3] + bgColor[3], 16);
|
||||
} else {
|
||||
r = parseInt(bgColor.slice(1, 3), 16);
|
||||
g = parseInt(bgColor.slice(3, 5), 16);
|
||||
b = parseInt(bgColor.slice(5, 7), 16);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
var luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
return luminance < 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a URL in a new window. Used for external links (Wiki, GitHub, etc.).
|
||||
* @param {string} url - The URL to open
|
||||
* @returns {boolean} false to prevent default link behavior
|
||||
*/
|
||||
function winOpen(url) {
|
||||
var win = window.open(url);
|
||||
if (win == null || typeof(win) == 'undefined') {
|
||||
window.location.href = url;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=1.0.8-20250207
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=1.0.8-r20250207
|
||||
PKG_RELEASE:=4
|
||||
|
||||
LUCI_TITLE:=LuCI support for OpenWebUI
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user