OpenClash v0.47.116
Some checks failed
Clean jsDelivr Cache / jsDelivr (push) Has been cancelled
Sync Core and Package to Master When release / Sync_core_to_master (push) Has been cancelled
Compile The New Clash Core / Get-Commit-id (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[abi:1 goarch:loong64 goos:linux output:loong64-abi1 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goamd64:v1 goarch:amd64 goos:linux output:amd64-compatible upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goamd64:v1 goarch:amd64 goos:linux output:amd64-v1 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goamd64:v2 goarch:amd64 goos:linux output:amd64-v2 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goamd64:v3 goarch:amd64 goos:linux output:amd64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goamd64:v3 goarch:amd64 goos:linux output:amd64-v3 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:386 goos:linux output:386 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:arm goarm:5 goos:linux output:armv5 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:arm goarm:6 goos:linux output:armv6 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:arm goarm:7 goos:linux output:armv7 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:arm64 goos:linux output:arm64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:loong64 goos:linux output:loong64-abi2 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:mips gomips:hardfloat goos:linux output:mips-hardfloat upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:mips gomips:softfloat goos:linux output:mips-softfloat upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:mips64 goos:linux output:mips64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:mips64le goos:linux output:mips64le upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:mipsle gomips:hardfloat goos:linux output:mipsle-hardfloat upxflag:--best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:mipsle gomips:softfloat goos:linux output:mipsle-softfloat upxflag:--best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:riscv64 goos:linux output:riscv64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:meta-core goversion:1.26 repo:MetaCubeX/mihomo src:metacubex_src type:meta version_prefix:alpha-g], map[goarch:s390x goos:linux output:s390x upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[abi:1 goarch:loong64 goos:linux output:loong64-abi1 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goamd64:v1 goarch:amd64 goos:linux output:amd64-compatible upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goamd64:v1 goarch:amd64 goos:linux output:amd64-v1 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goamd64:v2 goarch:amd64 goos:linux output:amd64-v2 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goamd64:v3 goarch:amd64 goos:linux output:amd64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goamd64:v3 goarch:amd64 goos:linux output:amd64-v3 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:386 goos:linux output:386 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:arm goarm:5 goos:linux output:armv5 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:arm goarm:6 goos:linux output:armv6 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:arm goarm:7 goos:linux output:armv7 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:arm64 goos:linux output:arm64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:loong64 goos:linux output:loong64-abi2 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:mips gomips:hardfloat goos:linux output:mips-hardfloat upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:mips gomips:softfloat goos:linux output:mips-softfloat upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:mips64 goos:linux output:mips64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:mips64le goos:linux output:mips64le upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:mipsle gomips:hardfloat goos:linux output:mipsle-hardfloat upxflag:--best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:mipsle gomips:softfloat goos:linux output:mipsle-softfloat upxflag:--best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:riscv64 goos:linux output:riscv64 upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Compile_Core (map[artifact:smart-core goversion:1.26 repo:vernesong/mihomo src:vernesong_src type:smart version_prefix:alpha-smart-g], map[goarch:s390x goos:linux output:s390x upxflag:--lzma --best]) (push) Has been cancelled
Compile The New Clash Core / Publish (push) Has been cancelled
Trim Core and Package History / trim (core) (push) Has been cancelled
Trim Core and Package History / trim (package) (push) Has been cancelled
Delete old workflow runs / del_runs (push) Has been cancelled
12
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: vernesong
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
153
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
name: Bug报告
|
||||
description: Create a Bug report to help us improve
|
||||
title: "[Bug] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
|
||||
- type: checkboxes
|
||||
id: verify_step
|
||||
attributes:
|
||||
label: Verify Steps
|
||||
description: |
|
||||
在提交之前,请确认 / Please verify that you've followed these steps.
|
||||
options:
|
||||
- label: Tracker 我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||
required: true
|
||||
- label: Branch 我知道 OpenClash 的 Dev 分支切换开关位于插件设置-版本更新中,或者我会手动下载并安装 Dev 分支的 OpenClash
|
||||
required: true
|
||||
- label: Latest 我已经**使用最新 Dev 版本**测试过,问题依旧存在
|
||||
required: true
|
||||
- label: Relevant 我知道 OpenClash 与 内核(Core)、控制面板(Dashboard)、在线订阅转换(Subconverter)等项目之间**无直接关系**,仅相互调用
|
||||
required: true
|
||||
- label: Definite 这确实是 OpenClash 出现的问题
|
||||
required: true
|
||||
- label: Contributors 我有能力协助 OpenClash 开发并解决此问题
|
||||
required: false
|
||||
- label: Meaningless 我提交的**是无意义的**催促更新或修复请求
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: openclash_version
|
||||
attributes:
|
||||
label: OpenClash Version
|
||||
description: |
|
||||
OpenClash 版本号
|
||||
placeholder: "v0.0.0-beta"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: bug_os
|
||||
attributes:
|
||||
label: Bug on Environment
|
||||
description: |
|
||||
发现问题所在的系统环境 / System Environment
|
||||
multiple: true
|
||||
options:
|
||||
- Official OpenWrt
|
||||
- Lean
|
||||
- Immortalwrt
|
||||
- Istoreos
|
||||
- Docker
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: openwrt_version
|
||||
attributes:
|
||||
label: OpenWrt Version
|
||||
description: |
|
||||
Openwrt 固件版本
|
||||
placeholder: "OpenWrt 0.0.0 r0-0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: bug_platform
|
||||
attributes:
|
||||
label: Bug on Platform
|
||||
description: |
|
||||
发现问题所在的架构平台 / Platform
|
||||
multiple: true
|
||||
options:
|
||||
- Linux-386
|
||||
- Linux-amd64(x86-64)
|
||||
- Linux-amd64-v3(x86-64)
|
||||
- Linux-armv5
|
||||
- Linux-armv6
|
||||
- Linux-armv7
|
||||
- Linux-arm64
|
||||
- Linux-loong64
|
||||
- Linux-mips-hardfloat
|
||||
- Linux-mips-softfloat
|
||||
- Linux-mips64
|
||||
- Linux-mips64le
|
||||
- Linux-mipsle-hardfloat
|
||||
- Linux-mipsle-softfloat
|
||||
- Linux-riscv64
|
||||
- Other
|
||||
- All
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: describe_bug
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
description: |
|
||||
对 Bug 本身清晰而简洁的描述 / Describe the Bug
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce_bug
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: |
|
||||
复现此 Bug 的步骤 / How to reproduce?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: openclash_log
|
||||
attributes:
|
||||
label: OpenClash Log
|
||||
description: |
|
||||
在下方附上 OpenClash 调试日志 / OpenClash Debug Log
|
||||
调试日志在插件设置-调试日志中生成,**并非只有运行日志**,如调试日志过长,可作为附件在最下方上传
|
||||
**隐私提示: 上传此日志前请注意检查、屏蔽公网IP、节点、密码等相关敏感信息**
|
||||
placeholder: "我已知晓缺失日志可能会导致开发者无法了解我的情况并降低本issue的处理优先级"
|
||||
render: log
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: openclash_config
|
||||
attributes:
|
||||
label: OpenClash Config
|
||||
description: |
|
||||
在下方附上与 Bug 相关的系统配置、防火墙规则或环境变量 / System config
|
||||
非 Clash Yaml 文件
|
||||
**非必填项**
|
||||
render: shell
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: excepted_behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: |
|
||||
对预期修复后情况的清晰明了的描述 / Expected behavior
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: |
|
||||
在此处添加其它信息或屏幕截图 / Additional Context
|
||||
validations:
|
||||
required: false
|
||||
47
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
|
||||
- name: 发表Issue前请先在Issues与Discussions中搜索相关内容
|
||||
url: https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md#在提问之前
|
||||
about: 请有意义且描述明确的描述问题症状而非你的猜测 问题解决后加个简短的补充说明
|
||||
|
||||
- name: 启动成功后无法访问个别网站 | 启动成功后个别网站不想走代理流量
|
||||
url: https://www.bing.com/search?q=clash+rule+写+教&ensearch=1
|
||||
about: 想要了解clash规则编写相关的问题
|
||||
|
||||
- name: CLASH CORE | PERIUM CORE & 内核问题 | 节点协议加密方式 | 规则 | 内核功能建议
|
||||
url: https://github.com/Dreamacro/clash/issues/new/choose
|
||||
about: 原版内核 | P核
|
||||
|
||||
- name: META CORE | MIHOMO CORE & 内核问题 | 节点协议加密方式 | 规则 | 内核功能建议
|
||||
url: https://github.com/MetaCubeX/mihomo/issues/new/choose
|
||||
about: M核
|
||||
|
||||
- name: 在线订阅转换问题 | 在线订阅转换增加节点协议加密方式 | 在线订阅转换功能建议
|
||||
url: https://github.com/tindy2013/subconverter/issues/new/choose
|
||||
about: 提供并增加预设订阅转换服务地址请通过上方 功能请求 发表
|
||||
|
||||
- name: DASHBOARD & 控制面板问题 | 面板功能建议
|
||||
url: https://github.com/Dreamacro/clash-dashboard/issues/new/choose
|
||||
about: 面板问题先请尝试 更换近期未使用过面板设备 或 使用浏览器隐私匿名无痕...模式 访问
|
||||
|
||||
- name: YACD & 控制面板问题 | 面板功能建议
|
||||
url: https://github.com/haishanh/yacd/issues/new/choose
|
||||
about: 面板问题先请尝试 更换近期未使用过面板设备 或 使用浏览器隐私匿名无痕...模式 访问
|
||||
|
||||
- name: METACUBEXD & 控制面板问题 | 面板功能建议
|
||||
url: https://github.com/MetaCubeX/metacubexd/issues/new/choose
|
||||
about: 面板问题先请尝试 更换近期未使用过面板设备 或 使用浏览器隐私匿名无痕...模式 访问
|
||||
|
||||
- name: YACD-META & 控制面板问题 | 面板功能建议
|
||||
url: https://github.com/MetaCubeX/Yacd-meta/issues/new/choose
|
||||
about: 面板问题先请尝试 更换近期未使用过面板设备 或 使用浏览器隐私匿名无痕...模式 访问
|
||||
|
||||
- name: DASHBOARD-META & 控制面板问题 | 面板功能建议
|
||||
url: https://github.com/MetaCubeX/Razord-meta/issues/new/choose
|
||||
about: 面板问题先请尝试 更换近期未使用过面板设备 或 使用浏览器隐私匿名无痕...模式 访问
|
||||
|
||||
- name: 我想讨论OpenClash的某个功能 | 我不确定是什么问题 | 我的问题不属于上述选项 | 我的问题无法得到原作者原仓库的帮助
|
||||
url: https://github.com/vernesong/OpenClash/discussions/new/choose
|
||||
about: 礼多人不怪 而且有时还很有帮助
|
||||
40
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: 功能请求
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature] "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
|
||||
- type: checkboxes
|
||||
id: verify_step
|
||||
attributes:
|
||||
label: Verify Steps
|
||||
description: "在提交之前,请确认 / Please verify that you've followed these steps"
|
||||
options:
|
||||
- label: Tracker 我已经在 [Issue Tracker](……/) 中找过我要提出的问题
|
||||
required: true
|
||||
- label: Latest 我已经**使用最新 Dev 版本**查看过,并不包含该功能特性或者还不完善
|
||||
required: true
|
||||
- label: Relevant 我知道 OpenClash 与 内核(Core)、控制面板(Dashboard)、在线订阅转换(Subconverter)等项目之间**无直接关系**,仅相互调用
|
||||
required: true
|
||||
- label: Definite 这确实是 OpenClash 应包含的特性
|
||||
required: true
|
||||
- label: Contributors 我有能力协助 OpenClash 开发或完善此功能特性
|
||||
required: false
|
||||
- label: Meaningless 我提交的**是无意义的**催促更新或修复请求
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Feature
|
||||
description: |
|
||||
清晰明了地描述您的想法. 例如你想实现什么 Feature 特性或功能? 如何实现该功能? 目前 OpenClash 的行为是什么?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe Alternatives
|
||||
description: |
|
||||
对您考虑过的任何替代解决方案或备选功能进行清晰、简洁的描述.
|
||||
validations:
|
||||
required: false
|
||||
3163
.github/skills/openclash-user-guide/SKILL.md
vendored
Normal file
41
.github/workflows/clear_cache.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Clean jsDelivr Cache
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
- core
|
||||
- package
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- 'LICENSE'
|
||||
|
||||
jobs:
|
||||
jsDelivr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install jq
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# https://github.com/marketplace/actions/git-changesets
|
||||
- id: changed_files
|
||||
name: git-changesets
|
||||
uses: collin-miller/git-changesets@v1
|
||||
with:
|
||||
# Default format is 'csv'. Other valid options are 'space-delimited' and 'json'.
|
||||
format: json
|
||||
|
||||
- name: Clean jsDelivr Cache
|
||||
run: |
|
||||
echo '${{ steps.changed_files.outputs.added_modified }}' | jq -r '.[]' | while read file; do
|
||||
# echo ${{ steps.changed_files.outputs.all }}
|
||||
curl -sL --retry 3 "https://purge.jsdelivr.net/gh/vernesong/OpenClash@${{ github.ref_name }}/${file}"
|
||||
done
|
||||
19
.github/workflows/close_issues.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
name: Mark and close stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 8 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
|
||||
days-before-stale: 60
|
||||
days-before-close: 5
|
||||
operations-per-run: 200
|
||||
118
.github/workflows/compile_dev_core.yml
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
|
||||
name: Compile The New Clash Dev Core
|
||||
|
||||
on:
|
||||
#schedule:
|
||||
# - cron: "0 19 * * 1,3,5,6"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Get-Commit-id:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
current_id: ${{ steps.current_id.outputs.current_id }}
|
||||
upstream_id: ${{ steps.upstream_id.outputs.upstream_id }}
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install git
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Get Current Version
|
||||
id: current_id
|
||||
run: |
|
||||
echo "current_id=$(sed -n 1p ./dev/core_version)" >> $GITHUB_OUTPUT
|
||||
echo "current_id: $(sed -n 1p ./dev/core_version)"
|
||||
|
||||
- name: Clone Clash Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Dreamacro/clash
|
||||
ref: dev
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Get Upstream Version
|
||||
id: upstream_id
|
||||
run: |
|
||||
echo "upstream_id=$(git describe --tags)" >> $GITHUB_OUTPUT
|
||||
echo "upstream_id: $(git describe --tags)"
|
||||
|
||||
Compile:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Get-Commit-id
|
||||
if: ${{ needs.Get-Commit-id.outputs.current_id != needs.Get-Commit-id.outputs.upstream_id }}
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install wget git tar
|
||||
|
||||
- name: Setup UPX
|
||||
run: |
|
||||
cd ..
|
||||
wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz
|
||||
tar xf upx-3.95-amd64_linux.tar.xz
|
||||
echo "upx=../upx-3.95-amd64_linux/upx" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Copy Makefile
|
||||
run: |
|
||||
cd ..
|
||||
mkdir tmp
|
||||
mkdir tmp/bin
|
||||
cp ./OpenClash/.github/makefile/dev ./tmp/Makefile
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.21"
|
||||
check-latest: true
|
||||
|
||||
- name: Clone Clash Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Dreamacro/clash
|
||||
ref: dev
|
||||
fetch-depth: '0'
|
||||
|
||||
- name: Compile Dev Clash
|
||||
run: |
|
||||
cp ../tmp/Makefile ./Makefile
|
||||
make releases
|
||||
|
||||
- name: Copy Clash Bin
|
||||
run: |
|
||||
cp -rf "./bin/." "../tmp/bin/"
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Change Version
|
||||
run: |
|
||||
sed -i '1d' ./dev/core_version
|
||||
sed -i "1i ${{ needs.Get-Commit-id.outputs.upstream_id }}" ./dev/core_version
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
rm -rf ./dev/dev/*
|
||||
cp -rf "../tmp/bin/." "./dev/dev/"
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add .
|
||||
git commit -m "Auto update Dev core: ${{ needs.Get-Commit-id.outputs.upstream_id }}"
|
||||
git push
|
||||
333
.github/workflows/compile_meta_core.yml
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
name: Compile The New Clash Core
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 20 * * 1,3,5,6"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Get-Commit-id:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
current_id: ${{ steps.current_id.outputs.current_id }}
|
||||
current_smart_id: ${{ steps.current_id.outputs.current_smart_id }}
|
||||
upstream_id: ${{ steps.upstream_id.outputs.upstream_id }}
|
||||
upstream_smart_id: ${{ steps.upstream_id.outputs.upstream_smart_id }}
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install git
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Get Current Version
|
||||
id: current_id
|
||||
run: |
|
||||
current_meta_ver=$(sed -n '1p' ./dev/core_version 2>/dev/null || echo "null")
|
||||
echo "current_id=$current_meta_ver" >> $GITHUB_OUTPUT
|
||||
echo "current_id: $current_meta_ver"
|
||||
|
||||
current_smart_ver=$(sed -n '2p' ./dev/core_version 2>/dev/null || echo "null")
|
||||
echo "current_smart_id=$current_smart_ver" >> $GITHUB_OUTPUT
|
||||
echo "current_smart_id: $current_smart_ver"
|
||||
|
||||
- name: Get Upstream Version
|
||||
id: upstream_id
|
||||
run: |
|
||||
# MetaCubeX/mihomo
|
||||
git clone --depth=1 --branch=Alpha https://github.com/MetaCubeX/mihomo.git metacubex_mihomo
|
||||
cd metacubex_mihomo
|
||||
echo "upstream_id=alpha-g$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "upstream_id: alpha-g$(git rev-parse --short HEAD)"
|
||||
cd ..
|
||||
# vernesong/mihomo
|
||||
git clone --depth=1 --branch=Alpha https://github.com/vernesong/mihomo.git vernesong_mihomo
|
||||
cd vernesong_mihomo
|
||||
echo "upstream_smart_id=alpha-smart-g$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "upstream_smart_id: alpha-smart-g$(git rev-parse --short HEAD)"
|
||||
|
||||
Compile_Core:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Get-Commit-id
|
||||
if: ${{ needs.Get-Commit-id.outputs.current_id != needs.Get-Commit-id.outputs.upstream_id || needs.Get-Commit-id.outputs.current_smart_id != needs.Get-Commit-id.outputs.upstream_smart_id }}
|
||||
strategy:
|
||||
matrix:
|
||||
core:
|
||||
- { type: meta, repo: MetaCubeX/mihomo, src: metacubex_src, version_prefix: alpha-g, artifact: meta-core, goversion: '1.26' }
|
||||
- { type: smart, repo: vernesong/mihomo, src: vernesong_src, version_prefix: alpha-smart-g, artifact: smart-core, goversion: '1.26' }
|
||||
jobs:
|
||||
- { goos: linux, goarch: '386', output: '386', upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: arm, goarm: '5', output: armv5, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: arm, goarm: '6', output: armv6, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: arm, goarm: '7', output: armv7, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: arm64, output: arm64, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: mips, gomips: softfloat, output: mips-softfloat, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: mips, gomips: hardfloat, output: mips-hardfloat, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: mipsle, gomips: softfloat, output: mipsle-softfloat, upxflag: '--best' }
|
||||
- { goos: linux, goarch: mipsle, gomips: hardfloat, output: mipsle-hardfloat, upxflag: '--best' }
|
||||
- { goos: linux, goarch: mips64, output: mips64, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: mips64le, output: mips64le, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: riscv64, output: riscv64, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: s390x, output: s390x, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: loong64, output: loong64-abi2, upxflag: '--lzma --best' }
|
||||
- { goos: linux, goarch: loong64, abi: '1', output: loong64-abi1, upxflag: '--lzma --best' }
|
||||
steps:
|
||||
- name: Check if build needed
|
||||
id: check_skip
|
||||
run: |
|
||||
if [ "${{ matrix.core.type }}" = "meta" ] && [ "${{ needs.Get-Commit-id.outputs.current_id }}" = "${{ needs.Get-Commit-id.outputs.upstream_id }}" ]; then
|
||||
echo "Meta is up to date, skipping build"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ matrix.core.type }}" = "smart" ] && [ "${{ needs.Get-Commit-id.outputs.current_smart_id }}" = "${{ needs.Get-Commit-id.outputs.upstream_smart_id }}" ]; then
|
||||
echo "Smart is up to date, skipping build"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Apt Update
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' }}
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install wget git tar
|
||||
|
||||
- name: Setup UPX
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' }}
|
||||
run: |
|
||||
wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz
|
||||
tar xf upx-3.95-amd64_linux.tar.xz
|
||||
echo "upx=${{ github.workspace }}/upx-3.95-amd64_linux/upx" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Go
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' && !(matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1') }}
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.core.goversion }}
|
||||
check-latest: true
|
||||
cache: false
|
||||
|
||||
- name: Cache Go loongarch abi1 tarball
|
||||
id: cache_go_abi1
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' && matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ runner.temp }}/go-archives/go1.26.0.linux-amd64-abi1.tar.gz
|
||||
key: go-loongarch-abi1-1.26.0
|
||||
|
||||
- name: Setup Go for loongarch abi1
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' && matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
||||
run: |
|
||||
GO_TAR_FILE="go1.26.0.linux-amd64-abi1.tar.gz"
|
||||
GO_ARCHIVE_DIR="${{ runner.temp }}/go-archives"
|
||||
mkdir -p "$GO_ARCHIVE_DIR"
|
||||
if [[ "${{ steps.cache_go_abi1.outputs.cache-hit }}" != 'true' ]]; then
|
||||
echo "Downloading $GO_TAR_FILE..."
|
||||
wget -q "https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.26.0/$GO_TAR_FILE" \
|
||||
-O "$GO_ARCHIVE_DIR/$GO_TAR_FILE"
|
||||
else
|
||||
echo "Restored $GO_TAR_FILE from cache."
|
||||
fi
|
||||
sudo rm -rf /usr/local/go
|
||||
sudo tar zxf "$GO_ARCHIVE_DIR/$GO_TAR_FILE" -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Clone source (${{ matrix.core.type }})
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' }}
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ matrix.core.repo }}
|
||||
ref: Alpha
|
||||
path: ./${{ matrix.core.src }}
|
||||
|
||||
- name: Update CA
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' }}
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install ca-certificates
|
||||
sudo update-ca-certificates
|
||||
cp -f /etc/ssl/certs/ca-certificates.crt ./${{ matrix.core.src }}/component/ca/ca-certificates.crt
|
||||
|
||||
- name: Build Core (${{ matrix.core.type }}-${{ matrix.jobs.output }})
|
||||
id: build
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' }}
|
||||
env:
|
||||
GOOS: ${{ matrix.jobs.goos }}
|
||||
GOARCH: ${{ matrix.jobs.goarch }}
|
||||
GOAMD64: ${{ matrix.jobs.goamd64 }}
|
||||
GOARM: ${{ matrix.jobs.goarm }}
|
||||
GOMIPS: ${{ matrix.jobs.gomips }}
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
if [ "${{ matrix.core.type }}" = "meta" ] && [ "${{ needs.Get-Commit-id.outputs.current_id }}" = "${{ needs.Get-Commit-id.outputs.upstream_id }}" ]; then
|
||||
echo "Meta is up to date, skipping build"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
if [ "${{ matrix.core.type }}" = "smart" ] && [ "${{ needs.Get-Commit-id.outputs.current_smart_id }}" = "${{ needs.Get-Commit-id.outputs.upstream_smart_id }}" ]; then
|
||||
echo "Smart is up to date, skipping build"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
set -e
|
||||
cd ./${{ matrix.core.src }}
|
||||
VERSION="${{ matrix.core.version_prefix }}$(git rev-parse --short HEAD)"
|
||||
BUILDTIME="$(date -u)"
|
||||
mkdir -p ./bin
|
||||
go build -tags with_gvisor -trimpath \
|
||||
-ldflags "-X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" \
|
||||
-o ./bin/clash
|
||||
chmod +x ./bin/clash
|
||||
${upx} ${{ matrix.jobs.upxflag }} ./bin/clash || true
|
||||
mkdir -p ../build_output
|
||||
tar -zcvf ../build_output/clash-linux-${{ matrix.jobs.output }}.tar.gz -C ./bin clash
|
||||
|
||||
- name: Upload Core Artifact (${{ matrix.core.type }}-${{ matrix.jobs.output }})
|
||||
if: ${{ steps.check_skip.outputs.skip != 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.core.artifact }}-${{ matrix.jobs.output }}
|
||||
path: build_output/clash-linux-${{ matrix.jobs.output }}.tar.gz
|
||||
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [Get-Commit-id, Compile_Core]
|
||||
if: always() && (needs.Get-Commit-id.outputs.current_id != needs.Get-Commit-id.outputs.upstream_id || needs.Get-Commit-id.outputs.current_smart_id != needs.Get-Commit-id.outputs.upstream_smart_id)
|
||||
steps:
|
||||
- name: Checkout OpenClash Repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Download All Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./downloaded-artifacts
|
||||
|
||||
- name: Organize Artifacts, Update Version, and Prepare Commit Data
|
||||
id: organize_commit_data
|
||||
run: |
|
||||
commit_description=""
|
||||
changes_made_to_dev_meta=false
|
||||
changes_made_to_dev_smart=false
|
||||
core_version_updated=false
|
||||
|
||||
mkdir -p ./dev/meta
|
||||
mkdir -p ./dev/smart
|
||||
|
||||
if [ "${{ needs.Get-Commit-id.outputs.current_id }}" != "${{ needs.Get-Commit-id.outputs.upstream_id }}" ]; then
|
||||
echo "Organizing MetaCubeX compiled files..."
|
||||
if [ "${{ needs.Compile_Core.result }}" == "success" ]; then
|
||||
rm -rf ./dev/meta/*
|
||||
for artifact_dir in ./downloaded-artifacts/meta-core-*/; do
|
||||
if [ -d "$artifact_dir" ] && [ -n "$(ls -A "$artifact_dir")" ]; then
|
||||
echo "Copying $artifact_dir files..."
|
||||
cp -rf "$artifact_dir". ./dev/meta/
|
||||
changes_made_to_dev_meta=true
|
||||
fi
|
||||
done
|
||||
if [ "$changes_made_to_dev_meta" = true ]; then
|
||||
git add ./dev/meta
|
||||
commit_description="Meta to ${{ needs.Get-Commit-id.outputs.upstream_id }}"
|
||||
fi
|
||||
else
|
||||
echo "Compile_Core job failed, skipping meta..."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${{ needs.Get-Commit-id.outputs.current_smart_id }}" != "${{ needs.Get-Commit-id.outputs.upstream_smart_id }}" ]; then
|
||||
echo "Organizing vernesong compiled files..."
|
||||
if [ "${{ needs.Compile_Core.result }}" == "success" ]; then
|
||||
rm -rf ./dev/smart/*
|
||||
for artifact_dir in ./downloaded-artifacts/smart-core-*/; do
|
||||
if [ -d "$artifact_dir" ] && [ -n "$(ls -A "$artifact_dir")" ]; then
|
||||
echo "Copying $artifact_dir files..."
|
||||
cp -rf "$artifact_dir". ./dev/smart/
|
||||
changes_made_to_dev_smart=true
|
||||
fi
|
||||
done
|
||||
if [ "$changes_made_to_dev_smart" = true ]; then
|
||||
git add ./dev/smart
|
||||
if [ -n "$commit_description" ]; then
|
||||
commit_description="$commit_description, "
|
||||
fi
|
||||
commit_description="${commit_description}Smart to ${{ needs.Get-Commit-id.outputs.upstream_smart_id }}"
|
||||
fi
|
||||
else
|
||||
echo "Compile_Core job failed, skipping smart..."
|
||||
fi
|
||||
fi
|
||||
|
||||
final_meta_id="${{ needs.Get-Commit-id.outputs.current_id }}"
|
||||
if [ "${{ needs.Get-Commit-id.outputs.current_id }}" != "${{ needs.Get-Commit-id.outputs.upstream_id }}" ] && [ "$changes_made_to_dev_meta" = true ]; then
|
||||
final_meta_id="${{ needs.Get-Commit-id.outputs.upstream_id }}"
|
||||
fi
|
||||
|
||||
final_smart_id="${{ needs.Get-Commit-id.outputs.current_smart_id }}"
|
||||
if [ "${{ needs.Get-Commit-id.outputs.current_smart_id }}" != "${{ needs.Get-Commit-id.outputs.upstream_smart_id }}" ] && [ "$changes_made_to_dev_smart" = true ]; then
|
||||
final_smart_id="${{ needs.Get-Commit-id.outputs.upstream_smart_id }}"
|
||||
fi
|
||||
|
||||
echo "Updating ./dev/core_version with:"
|
||||
echo "Meta: $final_meta_id"
|
||||
echo "Smart: $final_smart_id"
|
||||
|
||||
current_core_version_content=$(cat ./dev/core_version 2>/dev/null || printf "null\nnull")
|
||||
new_core_version_content=$(printf "%s\n%s" "$final_meta_id" "$final_smart_id")
|
||||
|
||||
if [ "$current_core_version_content" != "$new_core_version_content" ]; then
|
||||
echo "$final_meta_id" > ./dev/core_version
|
||||
echo "$final_smart_id" >> ./dev/core_version
|
||||
git add ./dev/core_version
|
||||
core_version_updated=true
|
||||
echo "./dev/core_version updated."
|
||||
else
|
||||
echo "./dev/core_version is already up to date."
|
||||
fi
|
||||
echo "Content of ./dev/core_version after potential update:"
|
||||
cat ./dev/core_version
|
||||
|
||||
overall_changes_exist=false
|
||||
if [ "$changes_made_to_dev_meta" = true ] || [ "$changes_made_to_dev_smart" = true ] || [ "$core_version_updated" = true ]; then
|
||||
overall_changes_exist=true
|
||||
fi
|
||||
|
||||
echo "commit_description_output=$commit_description" >> $GITHUB_OUTPUT
|
||||
echo "overall_changes_exist_output=$overall_changes_exist" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and push
|
||||
env:
|
||||
COMMIT_DESCRIPTION: ${{ steps.organize_commit_data.outputs.commit_description_output }}
|
||||
OVERALL_CHANGES_EXIST: ${{ steps.organize_commit_data.outputs.overall_changes_exist_output }}
|
||||
run: |
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
|
||||
echo "Commit description from output: $COMMIT_DESCRIPTION"
|
||||
echo "Overall changes exist from output: $OVERALL_CHANGES_EXIST"
|
||||
|
||||
if ! git diff --staged --quiet || [ "$OVERALL_CHANGES_EXIST" = "true" ]; then
|
||||
final_commit_message="Auto update"
|
||||
if [ -n "$COMMIT_DESCRIPTION" ]; then
|
||||
final_commit_message="$final_commit_message: $COMMIT_DESCRIPTION"
|
||||
elif [ "$OVERALL_CHANGES_EXIST" = "true" ]; then
|
||||
final_commit_message="$final_commit_message: Update core versions"
|
||||
fi
|
||||
|
||||
echo "Committing with message: $final_commit_message"
|
||||
git commit -m "$final_commit_message"
|
||||
git push
|
||||
else
|
||||
echo "No changes to commit."
|
||||
fi
|
||||
209
.github/workflows/compile_new_ipk.yml
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
name: Compile The New Version OpenClash
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- 'luci-app-openclash/Makefile'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Get-Version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
current_version: ${{ steps.current_version.outputs.version }}
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
|
||||
- name: New Version
|
||||
id: version
|
||||
run: |
|
||||
echo "version=$(grep 'PKG_VERSION:=' ./luci-app-openclash/Makefile |awk -F '=' '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Clone Repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: package
|
||||
|
||||
- name: Current Version
|
||||
id: current_version
|
||||
run: |
|
||||
echo "version=$(sed -n 1p ./${{ github.ref_name }}/version |awk -F 'v' '{print $2}')" >> $GITHUB_OUTPUT
|
||||
|
||||
Compile:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Get-Version
|
||||
if: ${{ needs.Get-Version.outputs.version != needs.Get-Version.outputs.current_version }}
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- { name: ipk, sdk_url: "https://mirrors.pku.edu.cn/openwrt/releases/22.03.0/targets/x86/64/openwrt-sdk-22.03.0-x86-64_gcc-11.2.0_musl.Linux-x86_64.tar.xz", sdk_tar: "SDK.tar.xz", sdk_backup_tar: "SDK.tar.bz2", sdk_dir: "SDK", artifact_pattern: "luci-app-openclash_*.ipk" }
|
||||
- { name: apk, sdk_url: "snapshot", sdk_tar: "SNAPSDK.tar.zst", sdk_backup_tar: "SNAPSDK.tar.zst", sdk_dir: "SNAPSDK", artifact_pattern: "luci-app-openclash-*.apk" }
|
||||
steps:
|
||||
- name: Checkout OpenClash Source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
|
||||
- name: Install Dependencies
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install curl git tar zstd python3 python3-pyelftools python3-setuptools
|
||||
|
||||
- name: Download SDK
|
||||
run: |
|
||||
mkdir -p tmp
|
||||
if [ "${{ matrix.target.name }}" = "ipk" ]; then
|
||||
if ! curl -SLk --connect-timeout 30 --retry 2 "${{ matrix.target.sdk_url }}" -o "./tmp/${{ matrix.target.sdk_tar }}"; then
|
||||
curl -SLk --connect-timeout 30 --retry 2 "https://archive.openwrt.org/chaos_calmer/15.05.1/ar71xx/generic/OpenWrt-SDK-15.05.1-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64.tar.bz2" -o "./tmp/SDK.tar.xz"
|
||||
cd tmp
|
||||
tar xjf ${{ matrix.target.sdk_backup_tar }}
|
||||
mv OpenWrt-SDK-* ${{ matrix.target.sdk_dir }}
|
||||
else
|
||||
cd tmp
|
||||
tar xf ${{ matrix.target.sdk_tar }}
|
||||
mv openwrt-sdk-* ${{ matrix.target.sdk_dir }}
|
||||
fi
|
||||
else
|
||||
BASE_URL="https://downloads.openwrt.org/snapshots/targets/x86/64/"
|
||||
SDK_NAME=$(curl -s $BASE_URL | grep -oE 'openwrt-sdk-x86-64[^"]+\.tar\.zst' | head -n 1)
|
||||
if ! curl -SLk --connect-timeout 30 --retry 2 "$BASE_URL/$SDK_NAME" -o "./tmp/${{ matrix.target.sdk_tar }}"; then
|
||||
BASE_URL2="https://mirrors.pku.edu.cn/files/openwrt/snapshots/targets/x86/64/"
|
||||
SDK_NAME=$(curl -s $BASE_URL2 | grep -oE 'openwrt-sdk-x86-64[^"]+\.tar\.zst' | head -n 1)
|
||||
curl -SLk --connect-timeout 30 --retry 2 "$BASE_URL2/$SDK_NAME" -o "./tmp/${{ matrix.target.sdk_tar }}"
|
||||
fi
|
||||
cd tmp
|
||||
zstd -d ${{ matrix.target.sdk_tar }}
|
||||
tar xf SNAPSDK.tar
|
||||
SDK_DIR=$(tar tf SNAPSDK.tar | head -n 1 | cut -d/ -f1)
|
||||
mv "$SDK_DIR" "${{ matrix.target.sdk_dir }}"
|
||||
fi
|
||||
|
||||
- name: Copy OpenClash Source Codes
|
||||
run: |
|
||||
mkdir -p tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash
|
||||
cp -rf ./luci-app-openclash/. tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/
|
||||
if [ "${{ matrix.target.name }}" = "apk" ]; then
|
||||
find tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash -type f -name "Makefile" -exec sed -i 's/DEPENDS:=+dnsmasq-full/DEPENDS:=+dnsmasq-full +luci-compat/g' {} \;
|
||||
fi
|
||||
|
||||
- name: Update Third-Party Resources
|
||||
run: |
|
||||
CHNR_PATH=tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/root/etc/openclash/china_ip_route.ipset
|
||||
CHNR6_PATH=tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/root/etc/openclash/china_ip6_route.ipset
|
||||
ZASHBOARD_PATH=tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/root/usr/share/openclash/ui/zashboard
|
||||
METACUBEXD_PATH=tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/root/usr/share/openclash/ui/metacubexd
|
||||
GEOSITE_PATH=tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/root/etc/openclash/GeoSite.dat
|
||||
COUNTRY_MMDB_PATH=tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/root/etc/openclash/Country.mmdb
|
||||
|
||||
# 更新 China IP Route
|
||||
mkdir -p tmp
|
||||
curl -sSL https://ispip.clang.cn/all_cn.txt -o tmp/china_ip_route.ipset
|
||||
mkdir -p $(dirname $CHNR_PATH)
|
||||
cp tmp/china_ip_route.ipset $CHNR_PATH
|
||||
curl -sSL https://ispip.clang.cn/all_cn_ipv6.txt -o tmp/china_ip6_route.ipset
|
||||
mkdir -p $(dirname $CHNR6_PATH)
|
||||
cp tmp/china_ip6_route.ipset $CHNR6_PATH
|
||||
|
||||
# 更新 MetaCubeXD UI
|
||||
mkdir -p tmp/metacubexd_zip
|
||||
curl -sSL https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip -o tmp/metacubexd.zip
|
||||
unzip -q -o tmp/metacubexd.zip -d tmp/metacubexd_zip
|
||||
METACUBEXD_UNZIP_PATH="tmp/metacubexd_zip/metacubexd-gh-pages"
|
||||
mkdir -p "$METACUBEXD_PATH"
|
||||
rm -rf "$METACUBEXD_PATH"/*
|
||||
cp -rf "$METACUBEXD_UNZIP_PATH"/* "$METACUBEXD_PATH"
|
||||
|
||||
# 更新 ZashBoard UI
|
||||
mkdir -p tmp/zashboard_zip
|
||||
curl -sSL https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip -o tmp/zashboard.zip
|
||||
unzip -q -o tmp/zashboard.zip -d tmp/zashboard_zip
|
||||
mkdir -p "$ZASHBOARD_PATH"
|
||||
rm -rf "$ZASHBOARD_PATH"/*
|
||||
cp -rf tmp/zashboard_zip/dist/* "$ZASHBOARD_PATH"
|
||||
|
||||
# 更新 GeoSite.dat
|
||||
RULES_RELEASE=$(curl -s https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest | jq -r '.tag_name')
|
||||
curl -sSL "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/${RULES_RELEASE}/geosite.dat" -o tmp/GeoSite.dat
|
||||
mkdir -p $(dirname "$GEOSITE_PATH")
|
||||
cp tmp/GeoSite.dat "$GEOSITE_PATH"
|
||||
|
||||
# 更新 Country.mmdb
|
||||
curl -sSL "https://github.com/alecthw/mmdb_china_ip_list/releases/latest/download/Country-lite.mmdb" -o tmp/Country.mmdb
|
||||
mkdir -p $(dirname "$COUNTRY_MMDB_PATH")
|
||||
cp tmp/Country.mmdb "$COUNTRY_MMDB_PATH"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Minify CSS (lightningcss)
|
||||
run: |
|
||||
CSS_PATH="tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/root/www/luci-static/resources/openclash/css/oc.css"
|
||||
SIZE_BEFORE=$(wc -c < "$CSS_PATH")
|
||||
npx --yes --package lightningcss-cli lightningcss --minify --targets 'iOS >= 12, safari >= 12, android >= 4.4' "$CSS_PATH" -o "$CSS_PATH.tmp"
|
||||
mv "$CSS_PATH.tmp" "$CSS_PATH"
|
||||
SIZE_AFTER=$(wc -c < "$CSS_PATH")
|
||||
echo "CSS minified: ${SIZE_BEFORE} -> ${SIZE_AFTER} bytes ($(( 100 - SIZE_AFTER * 100 / SIZE_BEFORE ))% reduction)"
|
||||
|
||||
- name: Compile po2lmo
|
||||
run: |
|
||||
cd tmp/${{ matrix.target.sdk_dir }}/package/luci-app-openclash/tools/po2lmo
|
||||
make && sudo make install
|
||||
|
||||
- name: Compile OpenClash
|
||||
run: |
|
||||
cd tmp/${{ matrix.target.sdk_dir }}
|
||||
if [ -f ../${{ matrix.target.sdk_tar }} ]; then
|
||||
make defconfig
|
||||
fi
|
||||
make package/luci-app-openclash/compile V=99
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openclash-${{ matrix.target.name }}
|
||||
path: tmp/${{ matrix.target.sdk_dir }}/bin/packages/x86_64/base/${{ matrix.target.artifact_pattern }}
|
||||
|
||||
Post-Process:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [Compile, Get-Version]
|
||||
steps:
|
||||
- name: Checkout package branch
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: package
|
||||
|
||||
- name: Download IPK Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: openclash-ipk
|
||||
path: ./
|
||||
|
||||
- name: Download APK Artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: openclash-apk
|
||||
path: ./
|
||||
|
||||
- name: Commit and Push New Version
|
||||
run: |
|
||||
rm -rf ./${{ github.ref_name }}/luci-app-openclash*
|
||||
echo "v${{ needs.Get-Version.outputs.version }}" > ./${{ github.ref_name }}/version
|
||||
echo "https://img.shields.io/badge/New Release-v${{ needs.Get-Version.outputs.version }}-orange.svg" >> ./${{ github.ref_name }}/version
|
||||
cp ./luci-app-openclash_*.ipk "./${{ github.ref_name }}/"
|
||||
cp ./luci-app-openclash-*.apk "./${{ github.ref_name }}/"
|
||||
rm -f ./luci-app-openclash_*.ipk
|
||||
rm -f ./luci-app-openclash-*.apk
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add .
|
||||
git commit -m "Auto Release: v${{ needs.Get-Version.outputs.version }}"
|
||||
git push
|
||||
95
.github/workflows/compile_premium_core.yml
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
name: Compile The New Clash Premium Core
|
||||
|
||||
on:
|
||||
#schedule:
|
||||
# - cron: "0 21 * * 1,3,5"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Get-Commit-id:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
current_id: ${{ steps.current_id.outputs.current_id }}
|
||||
upstream_id: ${{ steps.upstream_id.outputs.upstream_id }}
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install curl git gzip
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Get Current Version
|
||||
id: current_id
|
||||
run: |
|
||||
echo "current_id=$(sed -n 2p ./dev/core_version)" >> $GITHUB_OUTPUT
|
||||
echo "current_id: $(sed -n 2p ./dev/core_version)"
|
||||
|
||||
- name: Get Upstream Version
|
||||
id: upstream_id
|
||||
run: |
|
||||
cd ..
|
||||
mkdir tmp
|
||||
GZNAME="clash-latest.gz"
|
||||
curl -kSL --retry 2 https://release.dreamacro.workers.dev/latest//clash-linux-amd64-latest.gz -o ./tmp/$GZNAME
|
||||
gzip -d ./tmp/$GZNAME
|
||||
chmod +x ./tmp/clash-latest
|
||||
echo "upstream_id=$(./tmp/clash-latest -v |awk -F ' ' '{print $2}')" >> $GITHUB_OUTPUT
|
||||
echo "upstream_id: $(./tmp/clash-latest -v |awk -F ' ' '{print $2}')"
|
||||
|
||||
Compile:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Get-Commit-id
|
||||
if: ${{ needs.Get-Commit-id.outputs.current_id != needs.Get-Commit-id.outputs.upstream_id }}
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install curl git tar gzip
|
||||
|
||||
- name: Setup UPX
|
||||
run: |
|
||||
cd ..
|
||||
wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz
|
||||
tar xf upx-3.95-amd64_linux.tar.xz
|
||||
echo "upx=../upx-3.95-amd64_linux/upx" >> $GITHUB_ENV
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Copy Scripts
|
||||
run: |
|
||||
cd ..
|
||||
mkdir tmp
|
||||
mkdir tmp/bin
|
||||
cp ./OpenClash/.github/makefile/tun.sh ./tmp/tun.sh
|
||||
|
||||
- name: Compile Premium Clash
|
||||
run: |
|
||||
bash ../tmp/tun.sh
|
||||
rm -rf ../tmp/tun.sh
|
||||
|
||||
- name: Change Version
|
||||
run: |
|
||||
sed -i '2d' ./dev/core_version
|
||||
sed -i "2i ${{ needs.Get-Commit-id.outputs.upstream_id }}" ./dev/core_version
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
rm -rf ./dev/premium/*
|
||||
cp -rf "../tmp/." "./dev/premium/"
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add .
|
||||
git commit -m "Auto update Premium core: ${{ needs.Get-Commit-id.outputs.upstream_id }}"
|
||||
git push
|
||||
17
.github/workflows/delete_old_workflow.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
name: Delete old workflow runs
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 1 * *'
|
||||
# Run monthly, at 00:00 on the 1st day of month.
|
||||
|
||||
jobs:
|
||||
del_runs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete workflow runs
|
||||
uses: Mattraks/delete-workflow-runs@v2
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
repository: ${{ github.repository }}
|
||||
retain_days: 30
|
||||
keep_minimum_runs: 6
|
||||
62
.github/workflows/master_release_sync.yml
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
name: Sync Core and Package to Master When release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Sync_core_to_master:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install git
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: core
|
||||
|
||||
- name: Sync dev core to master
|
||||
run: |
|
||||
if [ -n "$(diff -r ./master/ ./dev/)" ]; then
|
||||
rm -rf ./master/premium
|
||||
rm -rf ./master/dev
|
||||
rm -rf ./master/meta/*
|
||||
rm -rf ./master/smart/*
|
||||
rm -rf ./master/core_version
|
||||
cp -rf "./dev/." "./master/"
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add .
|
||||
git commit -m "Release: Auto sync dev core"
|
||||
git push
|
||||
else
|
||||
echo "master core already sync to dev, exit"
|
||||
fi
|
||||
|
||||
- name: Clone OpenClash Repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: package
|
||||
|
||||
- name: Sync dev package to master
|
||||
run: |
|
||||
if [ -n "$(diff -r ./master/ ./dev/)" ]; then
|
||||
rm -rf ./master/README.md
|
||||
rm -rf ./master/luci-app-openclash*
|
||||
rm -rf ./master/version
|
||||
cp -rf "./dev/." "./master/"
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add .
|
||||
git commit -m "Release: Auto sync dev package"
|
||||
git push
|
||||
else
|
||||
echo "master package already sync to dev, exit"
|
||||
fi
|
||||
|
||||
133
.github/workflows/trim_core_package_history.yml
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
name: Trim Core and Package History
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 18 * * 3"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
trim:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
branch: [core, package]
|
||||
steps:
|
||||
- name: Apt Update
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install git
|
||||
|
||||
- name: Trim History
|
||||
env:
|
||||
REPO_URL: https://github.com/vernesong/OpenClash.git
|
||||
BRANCH: ${{ matrix.branch }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${BRANCH}" = "core" ]; then
|
||||
MAX_COMMITS=20
|
||||
LIMIT_COMMITS=10
|
||||
else
|
||||
MAX_COMMITS=50
|
||||
LIMIT_COMMITS=30
|
||||
fi
|
||||
|
||||
echo "Start full clone for history check (branch: ${BRANCH})..."
|
||||
mkdir -p ../full_clone_tmp
|
||||
cd ../full_clone_tmp
|
||||
git clone --branch "${BRANCH}" --single-branch --no-tags --progress --depth=1 "$REPO_URL" .
|
||||
|
||||
MAX_FETCH_RETRIES=3
|
||||
FETCH_OK=false
|
||||
for i in $(seq 1 $MAX_FETCH_RETRIES); do
|
||||
if git fetch --unshallow; then
|
||||
FETCH_OK=true
|
||||
break
|
||||
else
|
||||
echo "git fetch --unshallow failed (attempt $i/$MAX_FETCH_RETRIES), retrying..." >&2
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
if [ "$FETCH_OK" = false ]; then
|
||||
echo "Warning: git fetch --unshallow failed after $MAX_FETCH_RETRIES attempts, proceeding with available history." >&2
|
||||
fi
|
||||
|
||||
count=$(git rev-list --count HEAD 2>/dev/null || echo 0)
|
||||
echo "Current commit count on ${BRANCH} branch: $count"
|
||||
if [ "$count" -le "$MAX_COMMITS" ]; then
|
||||
echo "No trimming needed (commit count: $count <= $MAX_COMMITS)."
|
||||
cd ..
|
||||
rm -rf full_clone_tmp
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
|
||||
echo "Collecting recent commits..."
|
||||
mapfile -t recent_commits < <(git rev-list --max-count=$LIMIT_COMMITS HEAD)
|
||||
|
||||
echo "Looking for sync commit (grep 'Release: Auto sync')..."
|
||||
sync_commit=$(git log --grep='Release: Auto sync' --format='%H' | tail -n 1 || true)
|
||||
|
||||
keep_commits=("${recent_commits[@]}")
|
||||
if [ -n "$sync_commit" ]; then
|
||||
found=false
|
||||
for c in "${recent_commits[@]}"; do
|
||||
if [ "$c" = "$sync_commit" ]; then
|
||||
found=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$found" = false ]; then
|
||||
keep_commits+=("$sync_commit")
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Rebuilding trimmed branch from kept commits (no extra commits)..."
|
||||
ORIG_HEAD=$(git rev-parse HEAD)
|
||||
LAST_COMMIT=""
|
||||
for c in $(printf "%s\n" "${keep_commits[@]}" | tac); do
|
||||
echo "Processing commit $c ..."
|
||||
git checkout -- . || true
|
||||
git checkout "$c" -- . || true
|
||||
git add -A || true
|
||||
TREE_ID=$(git write-tree)
|
||||
AUTHOR_NAME=$(git show -s --format='%an' $c)
|
||||
AUTHOR_EMAIL=$(git show -s --format='%ae' $c)
|
||||
COMMIT_DATE=$(git show -s --format='%ad' --date=iso-strict $c)
|
||||
COMMIT_MSG=$(git log --format=%B -n 1 $c)
|
||||
echo "Commit message for $c:"
|
||||
printf "%s\n" "$COMMIT_MSG"
|
||||
|
||||
MSG_FILE=$(mktemp)
|
||||
printf "%s\n" "$COMMIT_MSG" > "$MSG_FILE"
|
||||
|
||||
if [ -z "$LAST_COMMIT" ]; then
|
||||
NEW_COMMIT=$(GIT_AUTHOR_NAME="$AUTHOR_NAME" GIT_AUTHOR_EMAIL="$AUTHOR_EMAIL" GIT_AUTHOR_DATE="$COMMIT_DATE" git commit-tree $TREE_ID -F "$MSG_FILE")
|
||||
else
|
||||
NEW_COMMIT=$(GIT_AUTHOR_NAME="$AUTHOR_NAME" GIT_AUTHOR_EMAIL="$AUTHOR_EMAIL" GIT_AUTHOR_DATE="$COMMIT_DATE" git commit-tree $TREE_ID -p "$LAST_COMMIT" -F "$MSG_FILE")
|
||||
fi
|
||||
rm -f "$MSG_FILE"
|
||||
LAST_COMMIT=$NEW_COMMIT
|
||||
done
|
||||
|
||||
if [ -n "$LAST_COMMIT" ]; then
|
||||
git update-ref refs/heads/__tmp_history_trim $LAST_COMMIT
|
||||
echo "Built commit: $LAST_COMMIT"
|
||||
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/vernesong/OpenClash.git"
|
||||
fi
|
||||
|
||||
git push --force --quiet origin "$LAST_COMMIT:refs/heads/${BRANCH}" || true
|
||||
echo "Trim complete. New commit count (remote unchanged locally until fetched): $(git rev-list --count HEAD)"
|
||||
else
|
||||
echo "No kept commits; skipping trim." >&2
|
||||
fi
|
||||
|
||||
cd ..
|
||||
rm -rf full_clone_tmp
|
||||
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-2026 vernesong
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
125
README.md
Normal file
@ -0,0 +1,125 @@
|
||||
<h1 align="center">
|
||||
<img src="https://raw.githubusercontent.com/vernesong/OpenClash/dev/img/logo.png" alt="Clash" width="200">
|
||||
<br>OpenClash<br>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
本插件是一个可运行在 OpenWrt 上的<a href="https://github.com/MetaCubeX/mihomo" target="_blank"> Mihomo(Clash) </a>客户端
|
||||
</p>
|
||||
<p align="center">
|
||||
兼容 Shadowsocks、ShadowsocksR、Vmess、Trojan、Snell 等协议,根据灵活的规则配置实现策略代理
|
||||
</p>
|
||||
|
||||
使用手册
|
||||
---
|
||||
|
||||
|
||||
* [Wiki](https://github.com/vernesong/OpenClash/wiki)
|
||||
|
||||
|
||||
下载地址
|
||||
---
|
||||
|
||||
|
||||
* IPK & APK [前往下载](https://github.com/vernesong/OpenClash/releases)
|
||||
|
||||
|
||||
依赖
|
||||
---
|
||||
|
||||
* luci
|
||||
* luci-base
|
||||
* dnsmasq-full
|
||||
* bash
|
||||
* curl
|
||||
* ca-bundle
|
||||
* ipset
|
||||
* ip-full
|
||||
* ruby
|
||||
* ruby-yaml
|
||||
* unzip
|
||||
* iptables(iptables)
|
||||
* kmod-ipt-nat(iptables)
|
||||
* iptables-mod-tproxy(iptables)
|
||||
* iptables-mod-extra(iptables)
|
||||
* kmod-tun(TUN模式)
|
||||
* luci-compat(Luci >= 19.07)
|
||||
* ip6tables-mod-nat(iptables-ipv6)
|
||||
* kmod-inet-diag(PROCESS-NAME)
|
||||
* kmod-nft-tproxy(Firewall4)
|
||||
|
||||
|
||||
编译
|
||||
---
|
||||
|
||||
|
||||
从 OpenWrt 的 [SDK](https://archive.openwrt.org/chaos_calmer/15.05.1/ar71xx/generic/OpenWrt-SDK-15.05.1-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64.tar.bz2) 编译
|
||||
```bash
|
||||
# 解压下载好的 SDK
|
||||
curl -SLk --connect-timeout 30 --retry 2 "https://archive.openwrt.org/chaos_calmer/15.05.1/ar71xx/generic/OpenWrt-SDK-15.05.1-ar71xx-generic_gcc-4.8-linaro_uClibc-0.9.33.2.Linux-x86_64.tar.bz2" -o "/tmp/SDK.tar.bz2"
|
||||
cd \tmp
|
||||
tar xjf SDK.tar.bz2
|
||||
cd OpenWrt-SDK-15.05.1-*
|
||||
|
||||
# Clone 项目
|
||||
mkdir package/luci-app-openclash
|
||||
cd package/luci-app-openclash
|
||||
git init
|
||||
git remote add -f origin https://github.com/vernesong/OpenClash.git
|
||||
git config core.sparsecheckout true
|
||||
echo "luci-app-openclash" >> .git/info/sparse-checkout
|
||||
git pull --depth 1 origin master
|
||||
git branch --set-upstream-to=origin/master master
|
||||
|
||||
# 编译 po2lmo (如果有po2lmo可跳过)
|
||||
pushd luci-app-openclash/tools/po2lmo
|
||||
make && sudo make install
|
||||
popd
|
||||
|
||||
# 开始编译
|
||||
|
||||
# 先回退到SDK主目录
|
||||
cd ../..
|
||||
make package/luci-app-openclash/luci-app-openclash/compile V=99
|
||||
|
||||
# IPK文件位置
|
||||
./bin/ar71xx/packages/base/luci-app-openclash_*-beta_all.ipk
|
||||
```
|
||||
|
||||
```bash
|
||||
# 同步源码
|
||||
cd package/luci-app-openclash/luci-app-openclash
|
||||
git pull
|
||||
|
||||
# 您也可以直接拷贝 `luci-app-openclash` 文件夹至其他 `OpenWrt` 项目的 `Package` 目录下随固件编译
|
||||
|
||||
make menuconfig
|
||||
# 选择要编译的包 LuCI -> Applications -> luci-app-openclash
|
||||
|
||||
```
|
||||
|
||||
|
||||
许可
|
||||
---
|
||||
|
||||
|
||||
* [MIT License](https://github.com/vernesong/OpenClash/blob/master/LICENSE)
|
||||
* 内核 [Mihomo](https://github.com/MetaCubeX/mihomo) by [MetaCubeX](https://github.com/MetaCubeX)
|
||||
* 本项目代码基于 [Luci For Clash](https://github.com/frainzy1477/luci-app-clash) by [frainzy1477](https://github.com/frainzy1477)
|
||||
* IP检查 [IP](https://ip.skk.moe/) by [SukkaW](https://ip.skk.moe/)
|
||||
* 控制面板 [zashboard](https://github.com/Zephyruso/zashboard) by [Zephyruso](https://github.com/Zephyruso)
|
||||
* 控制面板 [yacd](https://github.com/haishanh/yacd) by [haishanh](https://github.com/haishanh)
|
||||
* 流媒体解锁检测 [RegionRestrictionCheck](https://github.com/lmc999/RegionRestrictionCheck) by [lmc999](https://github.com/lmc999)
|
||||
|
||||
请作者喝杯咖啡
|
||||
---
|
||||
|
||||
* PayPal
|
||||
<p align="left">
|
||||
<a href="https://ko-fi.com/vernesong"><img width="300" src="https://www.ko-fi.com/img/githubbutton_sm.svg"> </a>
|
||||
</p>
|
||||
|
||||
* USDT-BSC
|
||||
<p align="left">
|
||||
<img width="300" src="https://github.com/vernesong/OpenClash/raw/master/img/USDT-Wallet.png">
|
||||
</p>
|
||||
14
announcement
Normal file
@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"zh": "覆写模块从 v0.47.081 开始支持 [YAML] 块来进行配置文件覆写,格式使用 yaml, 从而降低代码覆写编辑的难度, 使用方式请参考 default 文件的示例。",
|
||||
"en": "Overwrite module now supports YAML blocks for configuration file overwriting, using YAML format, which reduces the difficulty of code overwriting. Please refer to the default file example for usage."
|
||||
},
|
||||
{
|
||||
"zh": "覆写模块现已支持远程订阅,更加方便插件的远程管理及统一配置、一键订阅,欢迎各位分享配置。",
|
||||
"en": "Overwrite module now supports subscription, more convenient remote management and configuration, one-click subscription, welcome to share the configuration."
|
||||
},
|
||||
{
|
||||
"zh": "Smart 策略组内核现已支持 policy-priority 参数来自定义调整节点权重,请自行替换测试、使用。",
|
||||
"en": "The Smart policy group core now supports the policy-priority parameter to customize node weights. Please replace it for testing and use."
|
||||
}
|
||||
]
|
||||
BIN
img/BTC-Wallet.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
img/ETH-Wallet.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
img/USDT-Wallet.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
img/config-subscribe.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
img/config-subscribe1.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
img/config.png
Normal file
|
After Width: | Height: | Size: 280 KiB |
BIN
img/game-settings.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
img/game1.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
img/game2.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
img/log.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
img/logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
img/meta.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
img/proxy1.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
img/proxy2.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
img/proxy3.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
img/proxy4.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
img/servers.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
img/set1.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
img/set10.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
img/set11.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
img/set12.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
img/set2.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
img/set3.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
img/set4.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
img/set6.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
img/set7.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
img/set8.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
img/set9.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
img/settings.png
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
img/state.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
img/state1.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
img/state2.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
img/state3.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
img/state4.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
img/state5.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
146
luci-app-openclash/Makefile
Normal file
@ -0,0 +1,146 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-openclash
|
||||
PKG_VERSION:=0.47.110
|
||||
PKG_MAINTAINER:=vernesong <https://github.com/vernesong/OpenClash>
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/$(PKG_NAME)/config
|
||||
config PACKAGE_kmod-inet-diag
|
||||
default y if PACKAGE_$(PKG_NAME)
|
||||
|
||||
config PACKAGE_luci-compat
|
||||
default y if PACKAGE_$(PKG_NAME)
|
||||
|
||||
config PACKAGE_kmod-nft-tproxy
|
||||
default y if PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_kmod-ipt-nat
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_iptables-mod-tproxy
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_iptables-mod-extra
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_dnsmasq_full_ipset
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_dnsmasq_full_nftset
|
||||
default y if PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_ipset
|
||||
default y if ! PACKAGE_firewall4
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)
|
||||
CATEGORY:=LuCI
|
||||
SUBMENU:=3. Applications
|
||||
TITLE:=LuCI support for clash
|
||||
PKGARCH:=all
|
||||
DEPENDS:=+dnsmasq-full +bash +curl +ca-bundle +ip-full \
|
||||
+ruby +ruby-yaml +kmod-tun +unzip
|
||||
MAINTAINER:=vernesong
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/description
|
||||
A LuCI support for clash
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(CP) $(CURDIR)/root $(PKG_BUILD_DIR)
|
||||
$(CP) $(CURDIR)/luasrc $(PKG_BUILD_DIR)
|
||||
$(foreach po,$(wildcard ${CURDIR}/po/zh-cn/*.po), \
|
||||
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/
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Build/Configure
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/preinst
|
||||
#!/bin/sh
|
||||
if [ -f "/etc/config/openclash" ] && [ ! -f "/tmp/openclash.bak" ]; then
|
||||
cp -f "/etc/config/openclash" "/tmp/openclash.bak" >/dev/null 2>&1
|
||||
cp -rf "/etc/openclash" "/tmp/openclash" >/dev/null 2>&1
|
||||
cp -rf "/usr/share/openclash/ui" "/tmp/openclash_ui" >/dev/null 2>&1
|
||||
cp -rf "/www/luci-static/resources/openclash/pac" "/tmp/pac" >/dev/null 2>&1
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/postinst
|
||||
#!/bin/sh
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/prerm
|
||||
#!/bin/sh
|
||||
[ -n "$(pidof clash)" ] && /etc/init.d/openclash stop 2>/dev/null
|
||||
if [ -f "/etc/config/openclash" ] && [ ! -f "/tmp/openclash.bak" ]; then
|
||||
cp -f "/etc/config/openclash" "/tmp/openclash.bak" >/dev/null 2>&1
|
||||
cp -rf "/etc/openclash" "/tmp/openclash" >/dev/null 2>&1
|
||||
cp -rf "/usr/share/openclash/ui" "/tmp/openclash_ui" >/dev/null 2>&1
|
||||
cp -rf "/www/luci-static/resources/openclash/pac" "/tmp/pac" >/dev/null 2>&1
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/postrm
|
||||
#!/bin/sh
|
||||
DEFAULT_DNSMASQ_CFGID="$$(uci -q show "dhcp.@dnsmasq[0]" | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}' 2>/dev/null)"
|
||||
if [ -f "/tmp/etc/dnsmasq.conf.$DEFAULT_DNSMASQ_CFGID" ]; then
|
||||
DNSMASQ_CONF_DIR="$$(awk -F '=' '/^conf-dir=/ {print $2}' "/tmp/etc/dnsmasq.conf.$DEFAULT_DNSMASQ_CFGID" 2>/dev/null)"
|
||||
else
|
||||
DNSMASQ_CONF_DIR="/tmp/dnsmasq.d"
|
||||
fi
|
||||
DNSMASQ_CONF_DIR=$${DNSMASQ_CONF_DIR%*/}
|
||||
rm -rf /etc/openclash >/dev/null 2>&1
|
||||
rm -rf /etc/config/openclash >/dev/null 2>&1
|
||||
rm -rf /tmp/openclash.log >/dev/null 2>&1
|
||||
rm -rf /tmp/openclash_start.log >/dev/null 2>&1
|
||||
rm -rf /tmp/openclash_last_version >/dev/null 2>&1
|
||||
rm -rf /tmp/openclash.change >/dev/null 2>&1
|
||||
rm -rf /tmp/clash_last_version >/dev/null 2>&1
|
||||
rm -rf /usr/share/openclash >/dev/null 2>&1
|
||||
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_custom_domain.conf >/dev/null 2>&1
|
||||
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute_pass.conf >/dev/null 2>&1
|
||||
rm -rf ${DNSMASQ_CONF_DIR}/dnsmasq_openclash_chnroute6_pass.conf >/dev/null 2>&1
|
||||
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
|
||||
uci -q delete firewall.openclash
|
||||
uci -q commit firewall
|
||||
[ -f "/etc/config/ucitrack" ] && {
|
||||
uci -q delete ucitrack.@openclash[-1]
|
||||
uci -q commit ucitrack
|
||||
}
|
||||
rm -rf /tmp/luci-indexcache /tmp/luci-indexcache.* /var/luci-indexcache /var/luci-indexcache.* >/dev/null 2>&1
|
||||
rm -rf /tmp/luci-modulecache /var/luci-modulecache >/dev/null 2>&1
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/install
|
||||
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/*.*.lmo $(1)/usr/lib/lua/luci/i18n/
|
||||
$(CP) $(PKG_BUILD_DIR)/root/* $(1)/
|
||||
$(CP) $(PKG_BUILD_DIR)/luasrc/* $(1)/usr/lib/lua/luci/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
4367
luci-app-openclash/luasrc/controller/openclash.lua
Normal file
26
luci-app-openclash/luasrc/model/cbi/openclash/client.lua
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local UTIL = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
|
||||
m = SimpleForm("openclash",translate("OpenClash"))
|
||||
m.description = translate("A Mihomo(Clash) Client For OpenWrt")
|
||||
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"))
|
||||
m:append(Template("openclash/config_edit"))
|
||||
m:append(Template("openclash/config_upload"))
|
||||
|
||||
return m
|
||||
|
||||
@ -0,0 +1,620 @@
|
||||
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local UTIL = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local json = require "luci.jsonc"
|
||||
local datatype = require "luci.cbi.datatypes"
|
||||
|
||||
-- 优化 CBI UI(新版 LuCI 专用)
|
||||
local function optimize_cbi_ui()
|
||||
HTTP.write([[
|
||||
<script type="text/javascript">
|
||||
// 修正上移、下移按钮名称
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
|
||||
btn.value = "]] .. translate("Move up") .. [[";
|
||||
});
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-down").forEach(function(btn) {
|
||||
btn.value = "]] .. translate("Move down") .. [[";
|
||||
});
|
||||
// 删除控件和说明之间的多余换行
|
||||
document.querySelectorAll("div.cbi-value-description").forEach(function(descDiv) {
|
||||
var prev = descDiv.previousSibling;
|
||||
while (prev && prev.nodeType === Node.TEXT_NODE && prev.textContent.trim() === "") {
|
||||
prev = prev.previousSibling;
|
||||
}
|
||||
if (prev && prev.nodeType === Node.ELEMENT_NODE && prev.tagName === "BR") {
|
||||
prev.remove();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
]])
|
||||
end
|
||||
|
||||
font_green = [[<b style=color:green>]]
|
||||
font_red = [[<b style=color:red>]]
|
||||
font_off = [[</b>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
local op_mode = string.sub(luci.sys.exec('uci get openclash.config.operation_mode 2>/dev/null'),0,-2)
|
||||
if not op_mode then op_mode = "redir-host" end
|
||||
local lan_ip = fs.lanip()
|
||||
m = Map("openclash", translate("Overwrite Settings"))
|
||||
m.pageaction = false
|
||||
m.description = translate("Note: To restore the default configuration, try accessing:").." <a href='javascript:void(0)' onclick='javascript:restore_config(this)'>http://"..lan_ip.."/cgi-bin/luci/admin/services/openclash/restore</a>"..
|
||||
"<br/>"..font_green..translate("For More Useful Meta Core Functions Go Wiki")..": "..font_off.."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://wiki.metacubex.one/\")'>"..translate("https://wiki.metacubex.one/").."</a>"
|
||||
|
||||
s = m:section(TypedSection, "openclash")
|
||||
s.anonymous = true
|
||||
|
||||
s:tab("settings", translate("General Settings"))
|
||||
s:tab("dns", "DNS "..translate("Settings"))
|
||||
s:tab("meta", translate("Meta Settings"))
|
||||
s:tab("smart", translate("Smart Settings"))
|
||||
s:tab("rules", translate("Rules Setting"))
|
||||
|
||||
----- General Settings
|
||||
o = s:taboption("settings", ListValue, "interface_name", translate("Bind Network Interface"))
|
||||
local de_int = SYS.exec("ip route |grep 'default' |awk '{print $5}' 2>/dev/null") or SYS.exec("/usr/share/openclash/openclash_get_network.lua 'dhcp'")
|
||||
o.description = translate("Default Interface Name:").." "..font_green..bold_on..de_int..bold_off..font_off..translate(",Try Enable If Network Loopback")
|
||||
local interfaces = SYS.exec("ls -l /sys/class/net/ 2>/dev/null |awk '{print $9}' 2>/dev/null")
|
||||
for interface in string.gmatch(interfaces, "%S+") do
|
||||
o:value(interface)
|
||||
end
|
||||
o:value("0", translate("Disable"))
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("settings", Value, "tolerance", translate("URL-Test Group Tolerance").."(ms)")
|
||||
o.description = translate("Switch To The New Proxy When The Delay Difference Between Old and The Fastest Currently is Greater Than This Value")
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("100")
|
||||
o:value("150")
|
||||
o.datatype = "uinteger"
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("settings", Value, "urltest_interval_mod", translate("URL-Test Interval Modify").."(s)")
|
||||
o.description = translate("Modify The URL-Test Interval In The Config")
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("180")
|
||||
o:value("300")
|
||||
o.datatype = "uinteger"
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("settings", Value, "urltest_address_mod", translate("URL-Test Address Modify"))
|
||||
o.description = translate("Modify The URL-Test Address In The Config")
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("http://www.gstatic.com/generate_204")
|
||||
o:value("http://cp.cloudflare.com/generate_204")
|
||||
o:value("https://cp.cloudflare.com/generate_204")
|
||||
o:value("http://captive.apple.com/generate_204")
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("settings", Value, "github_address_mod", translate("Github Address Modify"))
|
||||
o.description = translate("Modify The Github Address In The Config And OpenClash With Proxy(CDN) To Prevent File Download Faild. Format Reference:").." ".."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://ghfast.top/\")'>https://ghfast.top/</a>"
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("https://fastly.jsdelivr.net/")
|
||||
o:value("https://testingcf.jsdelivr.net/")
|
||||
o:value("https://cdn.jsdelivr.net/")
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("settings", ListValue, "log_level", translate("Log Level"))
|
||||
o.description = translate("Select Core's Log Level")
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("info", translate("Info Mode"))
|
||||
o:value("warning", translate("Warning Mode"))
|
||||
o:value("error", translate("Error Mode"))
|
||||
o:value("debug", translate("Debug Mode"))
|
||||
o:value("silent", translate("Silent Mode"))
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("settings", Value, "dns_port")
|
||||
o.title = translate("DNS Port")
|
||||
o.default = "7874"
|
||||
o.datatype = "port"
|
||||
o.rmempty = false
|
||||
o.description = translate("Please Make Sure Ports Available")
|
||||
|
||||
o = s:taboption("settings", Value, "proxy_port")
|
||||
o.title = translate("Redir Port")
|
||||
o.default = "7892"
|
||||
o.datatype = "port"
|
||||
o.rmempty = false
|
||||
o.description = translate("Please Make Sure Ports Available")
|
||||
|
||||
o = s:taboption("settings", Value, "tproxy_port")
|
||||
o.title = translate("TProxy Port")
|
||||
o.default = "7895"
|
||||
o.datatype = "port"
|
||||
o.rmempty = false
|
||||
o.description = translate("Please Make Sure Ports Available")
|
||||
|
||||
o = s:taboption("settings", Value, "http_port")
|
||||
o.title = translate("HTTP(S) Port")
|
||||
o.default = "7890"
|
||||
o.datatype = "port"
|
||||
o.rmempty = false
|
||||
o.description = translate("Please Make Sure Ports Available")
|
||||
|
||||
o = s:taboption("settings", Value, "socks_port")
|
||||
o.title = translate("SOCKS5 Port")
|
||||
o.default = "7891"
|
||||
o.datatype = "port"
|
||||
o.rmempty = false
|
||||
o.description = translate("Please Make Sure Ports Available")
|
||||
|
||||
o = s:taboption("settings", Value, "mixed_port")
|
||||
o.title = translate("Mixed Port")
|
||||
o.default = "7893"
|
||||
o.datatype = "port"
|
||||
o.rmempty = false
|
||||
o.description = translate("Please Make Sure Ports Available")
|
||||
|
||||
---- DNS Settings
|
||||
|
||||
o = s:taboption("dns", Flag, "enable_custom_dns", font_red..bold_on..translate("Custom DNS Setting")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("Set OpenClash Upstream DNS Resolve Server")..bold_off..font_off
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("dns", Flag, "enable_respect_rules", font_red..bold_on..translate("Respect Rules").."(respect-rules)"..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("Whether or not The Connection to the DNS Server Follow the Rules in Config")..bold_off..font_off
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("dns", Flag, "append_wan_dns", translate("Append Upstream DNS"))
|
||||
o.description = translate("Append The Upstream Assigned DNS And Gateway IP To The Nameserver")
|
||||
o.default = 1
|
||||
|
||||
o = s:taboption("dns", Flag, "append_default_dns", translate("Append Default DNS"))
|
||||
o.description = translate("Automatically Append Compliant DNS to default-nameserver")
|
||||
o.default = 1
|
||||
|
||||
if op_mode == "fake-ip" then
|
||||
o = s:taboption("dns", Value, "fakeip_range", translate("Fake-IP Range").." (IPv4 Cidr)")
|
||||
o.description = translate("Set Fake-IP Range").." (IPv4 Cidr)"
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("198.18.0.1/16")
|
||||
o.default = "0"
|
||||
o.placeholder = "198.18.0.1/16"
|
||||
function o.validate(self, value)
|
||||
if value == "0" then
|
||||
return "0"
|
||||
end
|
||||
if datatype.cidr4(value) then
|
||||
return value
|
||||
end
|
||||
return "198.18.0.1/16"
|
||||
end
|
||||
|
||||
o = s:taboption("dns", Flag, "store_fakeip", translate("Persistence Fake-IP"))
|
||||
o.description = font_red..bold_on..translate("Cache Fake-IP DNS Resolution Records To File, Improve The Response Speed After Startup")..bold_off..font_off
|
||||
o.default = 1
|
||||
|
||||
end
|
||||
|
||||
o = s:taboption("dns", Flag, "custom_fallback_filter", translate("Fallback-Filter"))
|
||||
o.description = translate("Take Effect If Fallback DNS Setted, Prevent DNS Pollution")
|
||||
o.default = 0
|
||||
|
||||
custom_fallback_filter = s:taboption("dns", Value, "custom_fallback_fil")
|
||||
custom_fallback_filter.template = "cbi/tvalue"
|
||||
custom_fallback_filter.rows = 20
|
||||
custom_fallback_filter.wrap = "off"
|
||||
custom_fallback_filter:depends("custom_fallback_filter", "1")
|
||||
|
||||
function custom_fallback_filter.cfgvalue(self, section)
|
||||
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 = fs.readfile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_fallback_filter.yaml", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if op_mode == "fake-ip" then
|
||||
o = s:taboption("dns", Flag, "custom_fakeip_filter", translate("Fake-IP-Filter"))
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("dns", ListValue, "custom_fakeip_filter_mode", translate("Fake-IP-Filter-Mode"))
|
||||
o.description = translate("Fake-IP is not returned if the matching succeeds when blacklist mode or Fake-IP is returned if the matching succeeds when whitelist mode")
|
||||
o.default = "blacklist"
|
||||
o:value("blacklist", translate("Blacklist Mode"))
|
||||
o:value("whitelist", translate("Whitelist Mode"))
|
||||
o:value("rule", translate("Rule Mode"))
|
||||
o:depends("custom_fakeip_filter", "1")
|
||||
|
||||
custom_fake_black = s:taboption("dns", Value, "custom_fake_filter")
|
||||
custom_fake_black.template = "cbi/tvalue"
|
||||
custom_fake_black.description = translate("Domain Names In The List Do Not Return Fake-IP, One rule per line")
|
||||
custom_fake_black.rows = 20
|
||||
custom_fake_black.wrap = "off"
|
||||
custom_fake_black:depends("custom_fakeip_filter", "1")
|
||||
|
||||
function custom_fake_black.cfgvalue(self, section)
|
||||
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 = fs.readfile("/etc/openclash/custom/openclash_custom_fake_filter.list")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_fake_filter.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:taboption("dns", Flag, "custom_name_policy", translate("Nameserver-Policy"))
|
||||
o.default = 0
|
||||
|
||||
custom_domain_dns_policy = s:taboption("dns", Value, "custom_domain_dns_core")
|
||||
custom_domain_dns_policy.template = "cbi/tvalue"
|
||||
custom_domain_dns_policy.description = translate("Domain Names In The List Use The Custom DNS Server, But Still Return Fake-IP Results, One rule per line")
|
||||
custom_domain_dns_policy.rows = 20
|
||||
custom_domain_dns_policy.wrap = "off"
|
||||
custom_domain_dns_policy:depends("custom_name_policy", "1")
|
||||
|
||||
function custom_domain_dns_policy.cfgvalue(self, section)
|
||||
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 = fs.readfile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_domain_dns_policy.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:taboption("dns", Flag, "custom_proxy_server_policy", translate("Proxy-Server-Nameserver-Policy"))
|
||||
o.default = 0
|
||||
|
||||
custom_proxy_server_dns_policy = s:taboption("dns", Value, "custom_proxy_server_dns_policy")
|
||||
custom_proxy_server_dns_policy.template = "cbi/tvalue"
|
||||
custom_proxy_server_dns_policy.description = translate("Domain Names In The List Use The Custom DNS Server, But Still Return Fake-IP Results, One rule per line")
|
||||
custom_proxy_server_dns_policy.rows = 20
|
||||
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 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 = fs.readfile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_proxy_server_dns_policy.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:taboption("dns", Flag, "custom_host", translate("Hosts"))
|
||||
o.default = 0
|
||||
|
||||
custom_hosts = s:taboption("dns", Value, "custom_hosts")
|
||||
custom_hosts.template = "cbi/tvalue"
|
||||
custom_hosts.description = translate("Custom Hosts Here, You May Need to Turn off The Rebinding Protection Option of Dnsmasq When Hosts Has Set a Reserved Address, For More Go:").." ".."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://lancellc.gitbook.io/clash/clash-config-file/dns/host\")'>https://lancellc.gitbook.io/clash/clash-config-file/dns/host</a>"
|
||||
custom_hosts.rows = 20
|
||||
custom_hosts.wrap = "off"
|
||||
custom_hosts:depends("custom_host", "1")
|
||||
|
||||
function custom_hosts.cfgvalue(self, section)
|
||||
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 = fs.readfile("/etc/openclash/custom/openclash_custom_hosts.list")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_hosts.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Meta
|
||||
o = s:taboption("meta", Flag, "enable_tcp_concurrent", font_red..bold_on..translate("Enable Tcp Concurrent")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("TCP Concurrent Request IPs, Choose The Lowest Latency One To Connection")..bold_off..font_off
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("meta", Flag, "enable_unified_delay", font_red..bold_on..translate("Enable Unified Delay")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("Change The Delay Calculation Method To Remove Extra Delays Such as Handshaking")..bold_off..font_off
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("meta", Value, "global_ua", translate("Global User-Agent"))
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("clash-verge/v2.4.5")
|
||||
o:value("clash.meta/1.19.20")
|
||||
o:value("Clash")
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("meta", ListValue, "find_process_mode", translate("Enable Process Rule"))
|
||||
o.description = translate("Whether to Enable Process Rules, Only Works on Routerself, If You Are Not Sure, Please Choose off Which Useful in Router Environment, Depend on kmod-inet-diag")
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("off", translate("OFF "))
|
||||
o:value("always", translate("Always "))
|
||||
o:value("strict", translate("strict "))
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("meta", ListValue, "geodata_loader", translate("Geodata Loader Mode"))
|
||||
o:value("0", translate("Disable"))
|
||||
o:value("memconservative", translate("Memconservative"))
|
||||
o:value("standard", translate("Standard"))
|
||||
o.default = "0"
|
||||
|
||||
o = s:taboption("meta", Flag, "enable_geoip_dat", translate("Enable GeoIP Dat"))
|
||||
o.description = translate("Replace GEOIP MMDB With GEOIP Dat, Large Size File")..", "..font_red..bold_on..translate("Need Download First")..bold_off..font_off
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("meta", Flag, "enable_meta_sniffer", font_red..bold_on..translate("Enable Sniffer")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("Sniffer Will Prevent Domain Name Proxy and DNS Hijack Failure")..bold_off..font_off
|
||||
o.default = 1
|
||||
|
||||
o = s:taboption("meta", Flag, "enable_meta_sniffer_pure_ip", translate("Forced Sniff Pure IP"))
|
||||
o.description = translate("Forced Sniff Pure IP Connections")
|
||||
o.default = 1
|
||||
o:depends("enable_meta_sniffer", "1")
|
||||
|
||||
o = s:taboption("meta", Flag, "enable_meta_sniffer_custom", translate("Custom Sniffer Settings"))
|
||||
o.description = translate("Custom The Force and Skip Sniffing Doamin Lists")
|
||||
o.default = 0
|
||||
o:depends("enable_meta_sniffer", "1")
|
||||
|
||||
sniffer_custom = s:taboption("meta", Value, "sniffer_custom")
|
||||
sniffer_custom:depends("enable_meta_sniffer_custom", "1")
|
||||
sniffer_custom.template = "cbi/tvalue"
|
||||
sniffer_custom.rows = 20
|
||||
sniffer_custom.wrap = "off"
|
||||
|
||||
function sniffer_custom.cfgvalue(self, section)
|
||||
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 = fs.readfile("/etc/openclash/custom/openclash_custom_sniffer.yaml")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_sniffer.yaml", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Smart Settings
|
||||
o = s:taboption("smart", Flag, "auto_smart_switch", font_red..bold_on..translate("Smart Auto Switch")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("Auto Switch Url-test and Load-balance Group to Smart Group")..bold_off..font_off
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("smart", Value, "smart_policy_priority", translate("Policy Priority"))
|
||||
o.default = ""
|
||||
o.placeholder = "Premium:0.9;SG:1.3"
|
||||
o.description = translate("Nodes Weight Priority, <1 Means Lower Priority, >1 Means Higher Priority, The Default is 1, Pattern Support Regex and String")
|
||||
|
||||
o = s:taboption("smart", Flag, "smart_prefer_asn", font_red..bold_on..translate("Prefer-ASN")..bold_off..font_off)
|
||||
o.description = translate("Select Nodes Force Lookup and Use Target ASN Info First For More Stable Experience")
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("smart", Flag, "smart_enable_lgbm", font_red..bold_on..translate("Enable LightGBM Model")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("Use LightGBM Model To Predict Weight")..bold_off..font_off
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("smart", Flag, "smart_collect", translate("Colletct Training Data"))
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("smart", Value, "smart_collect_size", translate("Data Colletct File Size (MB)"))
|
||||
o.default = 100
|
||||
o:depends("smart_collect", "1")
|
||||
o.description = translate("Limit The File Size of Collected Data, The Default is 100MB")
|
||||
|
||||
o = s:taboption("smart", Value, "smart_collect_rate", translate("Data Colletct Rate"))
|
||||
o.default = 1
|
||||
o:depends("smart_collect", "1")
|
||||
o.description = translate("Data Acquisition Rate, Desirable Values are 0-1, The Default is 1")
|
||||
|
||||
o = s:taboption("smart", Flag, "lgbm_auto_update", translate("Auto Update LightGBM Model"))
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("smart", Value, "lgbm_update_interval", translate("Update Interval(hour)"))
|
||||
o.default = "72"
|
||||
o:depends("lgbm_auto_update", "1")
|
||||
|
||||
o = s:taboption("smart", Value, "lgbm_custom_url")
|
||||
o.title = translate("Custom Model URL")
|
||||
o.rmempty = true
|
||||
o.description = translate("Custom LightGBM Model URL, Click Button Below To Refresh After Edit")
|
||||
o:value("https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model.bin", translate("Light Version").." "..translate("(Default)"))
|
||||
o:value("https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model-middle.bin", translate("Middle Version"))
|
||||
o:value("https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model-large.bin", translate("Large Version"))
|
||||
o.default = "https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model.bin"
|
||||
o:depends("lgbm_auto_update", "1")
|
||||
|
||||
o = s:taboption("smart", Button, translate("Model Update"))
|
||||
o.description = translate("Current Version:").." "..font_green..bold_on..translate(fs.get_resourse_mtime("/etc/openclash/Model.bin"))..bold_off..font_off
|
||||
o.title = translate("Update Model")
|
||||
o.inputtitle = translate("Check And Update")
|
||||
o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
m.uci:set("openclash", "config", "enable", 1)
|
||||
m.uci:commit("openclash")
|
||||
SYS.call("/usr/share/openclash/openclash_lgbm.sh >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
o = s:taboption("smart", DummyValue, "flush_smart_cache", translate("Flush Smart Cache"))
|
||||
o.template = "openclash/flush_smart_cache"
|
||||
|
||||
---- Rules Settings
|
||||
o = s:taboption("rules", Flag, "enable_rule_proxy", translate("Rule Match Proxy Mode"))
|
||||
o.description = translate("Append Some Rules to Config, Allow Only Traffic Proxies that Match the Rule, Prevent BT/P2P Passing")
|
||||
o.default = 0
|
||||
|
||||
o = s:taboption("rules", Flag, "enable_custom_clash_rules", font_red..bold_on..translate("Custom Clash Rules")..bold_off..font_off)
|
||||
o.description = translate("Use Custom Rules")
|
||||
o.default = 0
|
||||
|
||||
custom_rules = s:taboption("rules", Value, "custom_rules")
|
||||
custom_rules:depends("enable_custom_clash_rules", 1)
|
||||
custom_rules.template = "cbi/tvalue"
|
||||
custom_rules.description = font_green..bold_on..translate("(Priority)")..bold_off..font_off.." "..translate("Custom Rules Here, For More Go:").." ".."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://wiki.metacubex.one/config/rules/\")'>https://wiki.metacubex.one/config/rules/</a>".." ,"..translate("IP To CIDR:").." ".."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"http://ip2cidr.com\")'>http://ip2cidr.com</a>"
|
||||
custom_rules.rows = 20
|
||||
custom_rules.wrap = "off"
|
||||
|
||||
function custom_rules.cfgvalue(self, section)
|
||||
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 = fs.readfile("/etc/openclash/custom/openclash_custom_rules.list")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_rules.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
custom_rules_2 = s:taboption("rules", Value, "custom_rules_2")
|
||||
custom_rules_2:depends("enable_custom_clash_rules", 1)
|
||||
custom_rules_2.template = "cbi/tvalue"
|
||||
custom_rules_2.description = font_green..bold_on..translate("(Extended)")..bold_off..font_off.." "..translate("Custom Rules Here, For More Go:").." ".."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://wiki.metacubex.one/config/rules/\")'>https://wiki.metacubex.one/config/rules/</a>".." ,"..translate("IP To CIDR:").." ".."<a href='javascript:void(0)' onclick='javascript:return winOpen(\"http://ip2cidr.com\")'>http://ip2cidr.com</a>"
|
||||
custom_rules_2.rows = 20
|
||||
custom_rules_2.wrap = "off"
|
||||
|
||||
function custom_rules_2.cfgvalue(self, section)
|
||||
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 = fs.readfile("/etc/openclash/custom/openclash_custom_rules_2.list")
|
||||
if value ~= old_value then
|
||||
fs.writefile("/etc/openclash/custom/openclash_custom_rules_2.list", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- [[ Edit Custom DNS ]] --
|
||||
ds = m:section(TypedSection, "dns_servers", translate("Add Custom DNS Servers")..translate("(Take Effect After Choose Above)"))
|
||||
ds.anonymous = true
|
||||
ds.addremove = true
|
||||
ds.sortable = true
|
||||
ds.template = "openclash/tblsection"
|
||||
ds.extedit = DISP.build_url("admin/services/openclash/custom-dns-edit/%s")
|
||||
function ds.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "group", name)
|
||||
end
|
||||
HTTP.redirect(ds.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
---- enable flag
|
||||
o = ds:option(Flag, "enabled", translate("Enable"))
|
||||
o.rmempty = false
|
||||
o.default = o.enabled
|
||||
o.cfgvalue = function(...)
|
||||
return Flag.cfgvalue(...) or "1"
|
||||
end
|
||||
|
||||
---- group
|
||||
o = ds:option(ListValue, "group", translate("DNS Server Group"))
|
||||
o:value("nameserver", translate("nameserver "))
|
||||
o:value("fallback", translate("fallback "))
|
||||
o:value("default", translate("default-nameserver"))
|
||||
o.default = "nameserver"
|
||||
o.rempty = false
|
||||
|
||||
---- IP address
|
||||
o = ds:option(Value, "ip", translate("DNS Server Address"))
|
||||
o.placeholder = translate("Not Null")
|
||||
o.datatype = "or(host, string)"
|
||||
o.rmempty = true
|
||||
|
||||
---- port
|
||||
o = ds:option(Value, "port", translate("DNS Server Port"))
|
||||
o.datatype = "port"
|
||||
o.rempty = true
|
||||
|
||||
---- type
|
||||
o = ds:option(ListValue, "type", translate("DNS Server Type"))
|
||||
o:value("udp", translate("UDP"))
|
||||
o:value("tcp", translate("TCP"))
|
||||
o:value("tls", translate("TLS"))
|
||||
o:value("https", translate("HTTPS"))
|
||||
o:value("quic", translate("QUIC"))
|
||||
o.default = "udp"
|
||||
o.rempty = false
|
||||
|
||||
---- Disable-IPv6
|
||||
o = ds:option(Flag, "disable_ipv6", translate("Disable-IPv6"))
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
-- [[ Edit Authentication ]] --
|
||||
s = m:section(TypedSection, "authentication", translate("Set Authentication of SOCKS5/HTTP(S)"))
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.sortable = false
|
||||
s.template = "cbi/tblsection"
|
||||
s.rmempty = false
|
||||
s.render = function(self, ...)
|
||||
Map.render(self, ...)
|
||||
if type(optimize_cbi_ui) == "function" then
|
||||
optimize_cbi_ui()
|
||||
end
|
||||
end
|
||||
|
||||
---- enable flag
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.rmempty = false
|
||||
o.default = o.enabled
|
||||
o.cfgvalue = function(...)
|
||||
return Flag.cfgvalue(...) or "1"
|
||||
end
|
||||
|
||||
---- username
|
||||
o = s:option(Value, "username", translate("Username"))
|
||||
o.placeholder = translate("Not Null")
|
||||
o.rempty = true
|
||||
|
||||
---- password
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.placeholder = translate("Not Null")
|
||||
o.rmempty = true
|
||||
|
||||
local t = {
|
||||
{Commit, Apply}
|
||||
}
|
||||
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button, "Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
end
|
||||
|
||||
o = a:option(Button, "Apply", " ")
|
||||
o.inputtitle = translate("Apply Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:set("openclash", "config", "enable", 1)
|
||||
m.uci:commit("openclash")
|
||||
SYS.call("/etc/init.d/openclash restart >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
m:append(Template("openclash/config_editor"))
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
return m
|
||||
@ -0,0 +1,334 @@
|
||||
|
||||
local m, s, o
|
||||
local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local sys = require "luci.sys"
|
||||
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>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
|
||||
m = Map(openclash, translate("Config Subscribe Edit"))
|
||||
m.pageaction = false
|
||||
m.description=translate("Convert Subscribe function of Online is Supported By subconverter Written By tindy X") ..
|
||||
"<br/>"..
|
||||
"<br/>"..translate("Subconverter external configuration (subscription conversion template) Description: https://github.com/tindy2013/subconverter#external-configuration-file")..
|
||||
"<br/>"..
|
||||
"<br/>"..translate("If you need to customize the external configuration file (subscription conversion template), please write it according to the instructions, upload it to the accessible location of the external network, and fill in the address correctly when using it")..
|
||||
"<br/>"..
|
||||
"<br/>"..translate("If you have a recommended external configuration file (subscription conversion template), you can modify by following The file format of /usr/share/openclash/res/sub_ini.list and pr")
|
||||
m.redirect = DISP.build_url("admin/services/openclash/config-subscribe")
|
||||
if m.uci:get(openclash, sid) ~= "config_subscribe" then
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Config Subscribe Setting ]]--
|
||||
s = m:section(NamedSection, sid, "config_subscribe")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
---- name
|
||||
o = s:option(Value, "name", translate("Config Alias"))
|
||||
o.description = font_red..bold_on..translate("Name For Distinguishing")..bold_off..font_off
|
||||
o.placeholder = translate("config")
|
||||
o.rmempty = true
|
||||
|
||||
---- address
|
||||
o = s:option(Value, "address", translate("Subscribe Address"))
|
||||
o.template = "cbi/tvalue"
|
||||
o.rows = 10
|
||||
o.wrap = "off"
|
||||
o.description = font_red..bold_on..translate("SS/SSR/Vmess or Other Link And Subscription Address is Supported When Online Subscription Conversion is Enabled, Multiple Links Should be One Per Line or Separated By |")..bold_off..font_off
|
||||
o.placeholder = translate("Not Null")
|
||||
o.rmempty = false
|
||||
function o.validate(self, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
value = value:gsub("%c*$", "")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
---- UA
|
||||
o = s:option(Value, "sub_ua", "User-Agent")
|
||||
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.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")
|
||||
o.default = 0
|
||||
|
||||
---- Convert Address
|
||||
o = s:option(Value, "convert_address", translate("Convert Address"))
|
||||
o.rmempty = true
|
||||
o.description = font_red..bold_on..translate("Note: There is A Risk of Privacy Leakage in Online Convert")..bold_off..font_off
|
||||
o:depends("sub_convert", "1")
|
||||
o:value("https://api.asailor.org/sub", translate("api.asailor.org"))
|
||||
o:value("https://api.wcc.best/sub", translate("api.wcc.best"))
|
||||
o.default = "https://api.asailor.org/sub"
|
||||
o.placeholder = "https://api.asailor.org/sub"
|
||||
|
||||
---- Template
|
||||
o = s:option(ListValue, "template", translate("Template Name"))
|
||||
o.rmempty = true
|
||||
o:depends("sub_convert", "1")
|
||||
file = io.open("/usr/share/openclash/res/sub_ini.list", "r");
|
||||
for l in file:lines() do
|
||||
if l ~= "" and l ~= nil then
|
||||
o:value(string.sub(luci.sys.exec(string.format("echo '%s' |awk -F ',' '{print $1}' 2>/dev/null",l)),1,-2))
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
o:value("0", translate("Custom Template"))
|
||||
|
||||
---- Custom Template
|
||||
o = s:option(Value, "custom_template_url", translate("Custom Template URL"))
|
||||
o.rmempty = true
|
||||
o.placeholder = translate("Not Null")
|
||||
o.datatype = "or(host, string)"
|
||||
o:depends("template", "0")
|
||||
|
||||
---- emoji
|
||||
o = s:option(ListValue, "emoji", translate("Emoji"))
|
||||
o.rmempty = false
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
---- udp
|
||||
o = s:option(ListValue, "udp", translate("UDP Enable"))
|
||||
o.rmempty = false
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
---- skip-cert-verify
|
||||
o = s:option(ListValue, "skip_cert_verify", translate("skip-cert-verify"))
|
||||
o.rmempty = false
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
---- sort
|
||||
o = s:option(ListValue, "sort", translate("Sort"))
|
||||
o.rmempty = false
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
---- node type
|
||||
o = s:option(ListValue, "node_type", translate("Append Node Type"))
|
||||
o.rmempty = false
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
---- rule provider
|
||||
o = s:option(ListValue, "rule_provider", translate("Use Rule Provider"))
|
||||
o.description = font_red..bold_on..translate("Note: Please Make Sure Backend Service Supports This Feature")..bold_off..font_off
|
||||
o.rmempty = false
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
---- custom params
|
||||
o = s:option(DynamicList, "custom_params", translate("Custom Params"))
|
||||
o.description = font_red..bold_on..translate("eg: \"rename=match@replace\" , \"rename=\\s+([2-9])[xX]@ (HIGH:$1)\"")..bold_off..font_off
|
||||
o.rmempty = false
|
||||
o:depends("sub_convert", "1")
|
||||
|
||||
---- key
|
||||
o = s:option(DynamicList, "keyword", font_red..bold_on..translate("Keyword Match")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("eg: hk or tw&bgp")..bold_off..font_off
|
||||
o.rmempty = true
|
||||
|
||||
---- exkey
|
||||
o = s:option(DynamicList, "ex_keyword", font_red..bold_on..translate("Exclude Keyword Match")..bold_off..font_off)
|
||||
o.description = font_red..bold_on..translate("eg: hk or tw&bgp")..bold_off..font_off
|
||||
o.rmempty = true
|
||||
|
||||
---- de_exkey
|
||||
o = s:option(MultiValue, "de_ex_keyword", font_red..bold_on..translate("Exclude Keyword Match Default")..bold_off..font_off)
|
||||
o.rmempty = true
|
||||
o:value(translate("Expire"))
|
||||
o:value(translate("Traffic"))
|
||||
o:value(translate("Plan"))
|
||||
o:value(translate("Official"))
|
||||
|
||||
local t = {
|
||||
{Commit, Back}
|
||||
}
|
||||
a = m:section(Table, t)
|
||||
|
||||
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
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
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
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
return m
|
||||
@ -0,0 +1,175 @@
|
||||
|
||||
local m, s, o
|
||||
local openclash = "openclash"
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local UTIL = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
-- 优化 CBI UI(新版 LuCI 专用)
|
||||
local function optimize_cbi_ui()
|
||||
HTTP.write([[
|
||||
<script type="text/javascript">
|
||||
// 修正上移、下移按钮名称
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
|
||||
btn.value = "]] .. translate("Move up") .. [[";
|
||||
});
|
||||
document.querySelectorAll("input.btn.cbi-button.cbi-button-down").forEach(function(btn) {
|
||||
btn.value = "]] .. translate("Move down") .. [[";
|
||||
});
|
||||
// 删除控件和说明之间的多余换行
|
||||
document.querySelectorAll("div.cbi-value-description").forEach(function(descDiv) {
|
||||
var prev = descDiv.previousSibling;
|
||||
while (prev && prev.nodeType === Node.TEXT_NODE && prev.textContent.trim() === "") {
|
||||
prev = prev.previousSibling;
|
||||
}
|
||||
if (prev && prev.nodeType === Node.ELEMENT_NODE && prev.tagName === "BR") {
|
||||
prev.remove();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
]])
|
||||
end
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
font_off = [[</b>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
m = Map("openclash", translate("Config Subscribe"))
|
||||
m.pageaction = false
|
||||
|
||||
s = m:section(TypedSection, "openclash")
|
||||
s.anonymous = true
|
||||
|
||||
---- update Settings
|
||||
o = s:option(Flag, "auto_update", translate("Auto Update"))
|
||||
o.description = translate("Auto Update Server subscription")
|
||||
o.default = 0
|
||||
|
||||
o = s:option(ListValue, "config_auto_update_mode", translate("Update Mode"))
|
||||
o:depends("auto_update", "1")
|
||||
o:value("0", translate("Appointment Mode"))
|
||||
o:value("1", translate("Loop Mode"))
|
||||
o.default = "0"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(ListValue, "config_update_week_time", translate("Update Time (Every Week)"))
|
||||
o:depends("config_auto_update_mode", "0")
|
||||
o:value("*", translate("Every Day"))
|
||||
o:value("1", translate("Every Monday"))
|
||||
o:value("2", translate("Every Tuesday"))
|
||||
o:value("3", translate("Every Wednesday"))
|
||||
o:value("4", translate("Every Thursday"))
|
||||
o:value("5", translate("Every Friday"))
|
||||
o:value("6", translate("Every Saturday"))
|
||||
o:value("0", translate("Every Sunday"))
|
||||
o.default = "1"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(ListValue, "auto_update_time", translate("Update time (every day)"))
|
||||
o:depends("config_auto_update_mode", "0")
|
||||
for t = 0,23 do
|
||||
o:value(t, t..":00")
|
||||
end
|
||||
o.default = "0"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "config_update_interval", translate("Update Interval(min)"))
|
||||
o.default = "60"
|
||||
o.datatype = "integer"
|
||||
o:depends("config_auto_update_mode", "1")
|
||||
o.rmempty = true
|
||||
|
||||
-- [[ Edit Server ]] --
|
||||
s = m:section(TypedSection, "config_subscribe", translate("Config Subscribe Edit"))
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.sortable = true
|
||||
s.template = "cbi/tblsection"
|
||||
s.extedit = DISP.build_url("admin/services/openclash/config-subscribe-edit/%s")
|
||||
function s.create(...)
|
||||
local sid = TypedSection.create(...)
|
||||
if sid then
|
||||
HTTP.redirect(s.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
s.render = function(self, ...)
|
||||
Map.render(self, ...)
|
||||
if type(optimize_cbi_ui) == "function" then
|
||||
optimize_cbi_ui()
|
||||
end
|
||||
end
|
||||
|
||||
---- enable flag
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.rmempty = false
|
||||
o.default = o.enabled
|
||||
o.cfgvalue = function(...)
|
||||
return Flag.cfgvalue(...) or "1"
|
||||
end
|
||||
|
||||
---- name
|
||||
o = s:option(DummyValue, "name", translate("Config Alias"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("config")
|
||||
end
|
||||
|
||||
---- address
|
||||
o = s:option(TextValue, "address", translate("Subscribe Address"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
|
||||
end
|
||||
function o.validate(self, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
value = value:gsub("%c*$", "")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
---- template
|
||||
o = s:option(DummyValue, "template", translate("Template Name"))
|
||||
function o.cfgvalue(...)
|
||||
if Value.cfgvalue(...) ~= "0" then
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
else
|
||||
return translate("Custom Template")
|
||||
end
|
||||
end
|
||||
|
||||
---- update
|
||||
o = s:option(DummyValue, "name", translate("Update"))
|
||||
o.template = "openclash/update_config"
|
||||
|
||||
local t = {
|
||||
{Commit, Apply}
|
||||
}
|
||||
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button, "Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
end
|
||||
|
||||
o = a:option(Button, "Apply", " ")
|
||||
o.inputtitle = translate("Update Config")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:set("openclash", "config", "enable", 1)
|
||||
m.uci:commit("openclash")
|
||||
SYS.call("/usr/share/openclash/openclash.sh >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
return m
|
||||
433
luci-app-openclash/luasrc/model/cbi/openclash/config.lua
Normal file
@ -0,0 +1,433 @@
|
||||
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local UTIL = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
local CHIF = "0"
|
||||
|
||||
font_green = [[<b style=color:green>]]
|
||||
font_off = [[</b>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
align_mid = [[<p align="center">]]
|
||||
align_mid_off = [[</p>]]
|
||||
|
||||
function IsYamlFile(e)
|
||||
e=e or""
|
||||
local e=string.lower(string.sub(e,-5,-1))
|
||||
return e == ".yaml"
|
||||
end
|
||||
function IsYmlFile(e)
|
||||
e=e or""
|
||||
local e=string.lower(string.sub(e,-4,-1))
|
||||
return e == ".yml"
|
||||
end
|
||||
|
||||
function default_config_set(f)
|
||||
local cf = fs.uci_get_config("config", "config_path")
|
||||
if cf == "/etc/openclash/config/"..f or not cf or cf == "" or not fs.isfile(cf) then
|
||||
if CHIF == "1" and cf == "/etc/openclash/config/"..f then
|
||||
return
|
||||
end
|
||||
local fis = fs.glob("/etc/openclash/config/*")[1]
|
||||
if fis ~= nil then
|
||||
fcf = fs.basename(fis)
|
||||
if fcf then
|
||||
uci:set("openclash", "config", "config_path", "/etc/openclash/config/"..fcf)
|
||||
uci:commit("openclash")
|
||||
end
|
||||
else
|
||||
uci:set("openclash", "config", "config_path", "/etc/openclash/config/config.yaml")
|
||||
uci:commit("openclash")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ful = SimpleForm("upload", translate("Config Manage"), nil)
|
||||
ful.reset = false
|
||||
ful.submit = false
|
||||
|
||||
sul =ful:section(SimpleSection, "")
|
||||
o = sul:option(FileUpload, "")
|
||||
o.template = "openclash/upload"
|
||||
um = sul:option(DummyValue, "", nil)
|
||||
um.template = "openclash/dvalue"
|
||||
|
||||
local dir, fd, clash
|
||||
dir = "/etc/openclash/config/"
|
||||
proxy_pro_dir="/etc/openclash/proxy_provider/"
|
||||
rule_pro_dir="/etc/openclash/rule_provider/"
|
||||
core_dir="/etc/openclash/core/core/"
|
||||
backup_dir="/tmp/"
|
||||
|
||||
HTTP.setfilehandler(
|
||||
function(meta, chunk, eof)
|
||||
local fp = HTTP.formvalue("file_type")
|
||||
if not fd then
|
||||
if not meta then return end
|
||||
|
||||
if fp == "config" then
|
||||
if meta and chunk then fd = nixio.open(dir .. meta.file, "w") end
|
||||
elseif fp == "proxy-provider" then
|
||||
if meta and chunk then fd = nixio.open(proxy_pro_dir .. meta.file, "w") end
|
||||
elseif fp == "rule-provider" then
|
||||
if meta and chunk then fd = nixio.open(rule_pro_dir .. meta.file, "w") end
|
||||
elseif fp == "clash_meta" then
|
||||
create_core_dir=fs.mkdir(core_dir)
|
||||
if meta and chunk then fd = nixio.open(core_dir .. meta.file, "w") end
|
||||
elseif fp == "backup-file" then
|
||||
if meta and chunk then fd = nixio.open(backup_dir .. meta.file, "w") end
|
||||
end
|
||||
|
||||
if not fd then
|
||||
um.value = translate("upload file error.")
|
||||
return
|
||||
end
|
||||
end
|
||||
if chunk and fd then
|
||||
fd:write(chunk)
|
||||
end
|
||||
if eof and fd then
|
||||
fd:close()
|
||||
fd = nil
|
||||
if fp == "config" then
|
||||
CHIF = "1"
|
||||
if IsYamlFile(meta.file) then
|
||||
default_config_set(meta.file)
|
||||
end
|
||||
if IsYmlFile(meta.file) then
|
||||
local ymlname=string.lower(string.sub(meta.file,0,-5))
|
||||
local c=fs.rename(dir .. meta.file,"/etc/openclash/config/".. ymlname .. ".yaml")
|
||||
local yamlname=ymlname .. ".yaml"
|
||||
default_config_set(yamlname)
|
||||
end
|
||||
um.value = translate("File saved to") .. ' "/etc/openclash/config/"'
|
||||
elseif fp == "proxy-provider" then
|
||||
um.value = translate("File saved to") .. ' "/etc/openclash/proxy_provider/"'
|
||||
elseif fp == "rule-provider" then
|
||||
um.value = translate("File saved to") .. ' "/etc/openclash/rule_provider/"'
|
||||
elseif fp == "clash_meta" then
|
||||
local archive_path = core_dir .. meta.file
|
||||
if string.lower(string.sub(meta.file, -7, -1)) == ".tar.gz" then
|
||||
-- tar.gz
|
||||
os.execute(string.format("tar -C '/etc/openclash/core/core' -xzf '%s' >/dev/null 2>&1", archive_path))
|
||||
os.execute(string.format("rm -f '%s' >/dev/null 2>&1", archive_path))
|
||||
local first_file_cmd = "find /etc/openclash/core/core -type f ! -name '*.tar.gz' ! -name '*.tar' ! -name '*.gz' 2>/dev/null | head -1"
|
||||
local first_file = io.popen(first_file_cmd):read("*line")
|
||||
if first_file and first_file ~= "" then
|
||||
os.execute(string.format("mv '%s' '/etc/openclash/core/%s' >/dev/null 2>&1", first_file, fp))
|
||||
end
|
||||
elseif string.lower(string.sub(meta.file, -4, -1)) == ".tar" then
|
||||
-- tar
|
||||
os.execute(string.format("tar -C '/etc/openclash/core/core' -xf '%s' >/dev/null 2>&1", archive_path))
|
||||
os.execute(string.format("rm -f '%s' >/dev/null 2>&1", archive_path))
|
||||
local first_file_cmd = "find /etc/openclash/core/core -type f ! -name '*.tar' ! -name '*.gz' 2>/dev/null | head -1"
|
||||
local first_file = io.popen(first_file_cmd):read("*line")
|
||||
if first_file and first_file ~= "" then
|
||||
os.execute(string.format("mv '%s' '/etc/openclash/core/%s' >/dev/null 2>&1", first_file, fp))
|
||||
end
|
||||
elseif string.lower(string.sub(meta.file, -3, -1)) == ".gz" then
|
||||
-- gz
|
||||
os.execute(string.format("gzip -fd '%s' >/dev/null 2>&1", archive_path))
|
||||
os.execute(string.format("rm -f '%s' >/dev/null 2>&1", archive_path))
|
||||
local first_file_cmd = "find /etc/openclash/core/core -type f ! -name '*.gz' 2>/dev/null | head -1"
|
||||
local first_file = io.popen(first_file_cmd):read("*line")
|
||||
if first_file and first_file ~= "" then
|
||||
os.execute(string.format("mv '%s' '/etc/openclash/core/%s' >/dev/null 2>&1", first_file, fp))
|
||||
end
|
||||
else
|
||||
os.execute(string.format("mv '%s' '/etc/openclash/core/%s' >/dev/null 2>&1", (core_dir .. meta.file), fp))
|
||||
end
|
||||
|
||||
os.execute(string.format("chmod 4755 '/etc/openclash/core/%s' >/dev/null 2>&1", fp))
|
||||
os.execute(string.format("rm -rf %s >/dev/null 2>&1", core_dir))
|
||||
um.value = translate("File saved to") .. ' "/etc/openclash/core/"'
|
||||
elseif fp == "backup-file" then
|
||||
os.execute("tar -C '/etc/openclash/' -xzf %s >/dev/null 2>&1" % (backup_dir .. meta.file))
|
||||
os.execute("mv /etc/openclash/openclash /etc/config/openclash >/dev/null 2>&1")
|
||||
fs.unlink(backup_dir .. meta.file)
|
||||
um.value = translate("Backup File Restore Successful!")
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
if HTTP.formvalue("upload") then
|
||||
if not um.value then
|
||||
um.value = translate("No Specify Upload File")
|
||||
end
|
||||
end
|
||||
|
||||
local e,a={}
|
||||
for t,o in ipairs(fs.glob("/etc/openclash/config/*"))do
|
||||
a=fs.stat(o)
|
||||
if a then
|
||||
e[t]={}
|
||||
e[t].name=fs.basename(o)
|
||||
e[t].mtime=os.date("%Y-%m-%d %H:%M:%S",a.mtime)
|
||||
if fs.uci_get_config("config", "config_path") and string.sub(fs.uci_get_config("config", "config_path"), 23, -1) == e[t].name then
|
||||
e[t].state=translate("Enabled")
|
||||
else
|
||||
e[t].state=translate("Disabled")
|
||||
end
|
||||
e[t].size=fs.filesize(a.size)
|
||||
e[t].remove=0
|
||||
end
|
||||
end
|
||||
|
||||
form=SimpleForm("config_file_list",translate("Config File List"))
|
||||
form.reset=false
|
||||
form.submit=false
|
||||
tb=form:section(Table,e)
|
||||
st=tb:option(DummyValue,"state",translate("State"))
|
||||
nm=tb:option(DummyValue,"name",translate("Config Alias"))
|
||||
sb=tb:option(DummyValue,"name",translate("Subscription Info"))
|
||||
mt=tb:option(DummyValue,"mtime",translate("Update Time"))
|
||||
sz=tb:option(DummyValue,"size",translate("Size"))
|
||||
st.template="openclash/cfg_check"
|
||||
sb.template="openclash/sub_info_show"
|
||||
|
||||
btnis=tb:option(Button,"switch",translate("SwiTch"))
|
||||
btnis.render=function(o,t,a)
|
||||
if not e[t] then return false end
|
||||
if IsYamlFile(e[t].name) or IsYmlFile(e[t].name) then
|
||||
a.display=""
|
||||
else
|
||||
a.display="none"
|
||||
end
|
||||
o.inputstyle="apply"
|
||||
Button.render(o,t,a)
|
||||
end
|
||||
btnis.write=function(a,t)
|
||||
uci:set("openclash", "config", "config_path", "/etc/openclash/config/"..e[t].name)
|
||||
uci:commit("openclash")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
end
|
||||
|
||||
btned=tb:option(Button,"edit",translate("Edit"))
|
||||
btned.inputstyle="apply"
|
||||
btned.write=function(a,t)
|
||||
local file_path = "/etc/openclash/config/" .. fs.basename(e[t].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "config") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
end
|
||||
|
||||
btnrn=tb:option(DummyValue,"/etc/openclash/config/",translate("Rename"))
|
||||
btnrn.template="openclash/input_rename"
|
||||
btnrn.rawhtml = true
|
||||
btnrn.render=function(c,t,a)
|
||||
if not e[t] then return end
|
||||
c.value = e[t].name
|
||||
DummyValue.render(c,t,a)
|
||||
end
|
||||
|
||||
local actions = tb:option(ListValue, "actions", translate("Other"))
|
||||
actions.render = function(self, t, a)
|
||||
if not e[t] then return end
|
||||
self.keylist = {}
|
||||
self.vallist = {}
|
||||
-- Servers manage
|
||||
table.insert(self.keylist, "servers_manage")
|
||||
table.insert(self.vallist, translate("Servers Manage"))
|
||||
|
||||
-- Copy
|
||||
if IsYamlFile(e[t].name) or IsYmlFile(e[t].name) then
|
||||
table.insert(self.keylist, "copy")
|
||||
table.insert(self.vallist, translate("Copy Config"))
|
||||
end
|
||||
|
||||
-- Download
|
||||
table.insert(self.keylist, "download")
|
||||
table.insert(self.vallist, translate("Download Config"))
|
||||
|
||||
-- Download Running
|
||||
if NXFS.access("/etc/openclash/"..e[t].name) then
|
||||
table.insert(self.keylist, "download_run")
|
||||
table.insert(self.vallist, translate("Download Running Config"))
|
||||
end
|
||||
|
||||
-- Remove
|
||||
table.insert(self.keylist, "remove")
|
||||
table.insert(self.vallist, translate("Remove"))
|
||||
|
||||
ListValue.render(self, t, a)
|
||||
end
|
||||
|
||||
local btnapply = tb:option(Button, "apply", translate("Apply"))
|
||||
btnapply.inputstyle = "apply"
|
||||
btnapply.write = function(self, t)
|
||||
if not e[t] then return end
|
||||
local action = self.map:formvalue("cbid." .. self.map.config .. "." .. t .. ".actions")
|
||||
|
||||
if action == "servers_manage" then
|
||||
local file_path = "/etc/openclash/config/" .. fs.basename(e[t].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
elseif action == "copy" then
|
||||
local num = 1
|
||||
while true do
|
||||
num = num + 1
|
||||
if not fs.isfile("/etc/openclash/config/"..fs.filename(e[t].name).."("..num..")"..".yaml") then
|
||||
fs.copy("/etc/openclash/config/"..e[t].name, "/etc/openclash/config/"..fs.filename(e[t].name).."("..num..")"..".yaml")
|
||||
break
|
||||
end
|
||||
end
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
elseif action == "download" then
|
||||
local sPath, sFile, fd, block
|
||||
sPath = "/etc/openclash/config/"..e[t].name
|
||||
sFile = NXFS.basename(sPath)
|
||||
if fs.isdirectory(sPath) then
|
||||
fd = io.popen('tar -C "%s" -cz .' % {sPath}, "r")
|
||||
sFile = sFile .. ".tar.gz"
|
||||
else
|
||||
fd = nixio.open(sPath, "r")
|
||||
end
|
||||
if not fd then return end
|
||||
HTTP.header('Content-Disposition', 'attachment; filename="%s"' % {sFile})
|
||||
HTTP.prepare_content("application/octet-stream")
|
||||
while true do
|
||||
block = fd:read(nixio.const.buffersize)
|
||||
if (not block) or (#block == 0) then break end
|
||||
HTTP.write(block)
|
||||
end
|
||||
fd:close()
|
||||
HTTP.close()
|
||||
elseif action == "download_run" then
|
||||
local sPath, sFile, fd, block
|
||||
sPath = "/etc/openclash/"..e[t].name
|
||||
sFile = NXFS.basename(sPath)
|
||||
if fs.isdirectory(sPath) then
|
||||
fd = io.popen('tar -C "%s" -cz .' % {sPath}, "r")
|
||||
sFile = sFile .. ".tar.gz"
|
||||
else
|
||||
fd = nixio.open(sPath, "r")
|
||||
end
|
||||
if not fd then return end
|
||||
HTTP.header('Content-Disposition', 'attachment; filename="%s"' % {sFile})
|
||||
HTTP.prepare_content("application/octet-stream")
|
||||
while true do
|
||||
block = fd:read(nixio.const.buffersize)
|
||||
if (not block) or (#block == 0) then break end
|
||||
HTTP.write(block)
|
||||
end
|
||||
fd:close()
|
||||
HTTP.close()
|
||||
elseif action == "remove" then
|
||||
fs.unlink("/etc/openclash/history/"..fs.filename(e[t].name)..".db")
|
||||
fs.unlink("/etc/openclash/"..fs.basename(e[t].name))
|
||||
local a=fs.unlink("/etc/openclash/config/"..fs.basename(e[t].name))
|
||||
default_config_set(fs.basename(e[t].name))
|
||||
if a then table.remove(e,t) end
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash","config"))
|
||||
end
|
||||
end
|
||||
|
||||
p = SimpleForm("provider_file_manage",translate("Provider File Manage"))
|
||||
p.reset = false
|
||||
p.submit = false
|
||||
|
||||
local provider_manage = {
|
||||
{proxy_mg, rule_mg}
|
||||
}
|
||||
|
||||
promg = p:section(Table, provider_manage)
|
||||
|
||||
o = promg:option(Button, "proxy_mg", " ")
|
||||
o.inputtitle = translate("Proxy Provider File List")
|
||||
o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "proxy-provider-file-manage"))
|
||||
end
|
||||
|
||||
o = promg:option(Button, "rule_mg", " ")
|
||||
o.inputtitle = translate("Rule Providers File List")
|
||||
o.inputstyle = "reload"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "rule-providers-file-manage"))
|
||||
end
|
||||
|
||||
m = SimpleForm("openclash",translate("Config File Edit"))
|
||||
m.reset = false
|
||||
m.submit = false
|
||||
|
||||
local tab = {
|
||||
{user, default}
|
||||
}
|
||||
|
||||
s = m:section(Table, tab)
|
||||
s.description = align_mid..translate("Support syntax check, press").." "..font_green..bold_on.."F10"..bold_off..font_off.." "..translate("to control diff option, press").." "..font_green..bold_on.."F11"..bold_off..font_off.." "..translate("to enter full screen editing mode")..align_mid_off
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
local conf = fs.uci_get_config("config", "config_path")
|
||||
local dconf = "/usr/share/openclash/res/default.yaml"
|
||||
if not conf then conf = "/etc/openclash/config/config.yaml" end
|
||||
local conf_name = fs.basename(conf)
|
||||
if not conf_name then conf_name = "config.yaml" end
|
||||
local sconf = "/etc/openclash/"..conf_name
|
||||
|
||||
sev = s:option(TextValue, "user")
|
||||
---sev.description = align_mid..translate("Modify Your Config file:").." "..font_green..bold_on..conf_name..bold_off..font_off.." "..translate("Here, Except The Settings That Were Taken Over")..align_mid_off
|
||||
sev.rows = 40
|
||||
sev.wrap = "off"
|
||||
sev.cfgvalue = function(self, section)
|
||||
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 = fs.readfile(conf)
|
||||
if value ~= old_value then
|
||||
fs.writefile(conf, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def = s:option(TextValue, "default")
|
||||
if fs.isfile(sconf) then
|
||||
---def.description = align_mid..translate("Config File Edited By OpenClash For Running")..align_mid_off
|
||||
else
|
||||
---def.description = align_mid..translate("Default Config File With Correct Template")..align_mid_off
|
||||
end
|
||||
def.rows = 40
|
||||
def.wrap = "off"
|
||||
def.readonly = true
|
||||
def.cfgvalue = function(self, section)
|
||||
return fs.readfile(sconf) or fs.readfile(dconf) or ""
|
||||
end
|
||||
def.write = function(self, section, value)
|
||||
end
|
||||
|
||||
local t = {
|
||||
{Commit, Create, Apply}
|
||||
}
|
||||
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button, "Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
uci:commit("openclash")
|
||||
end
|
||||
|
||||
o = a:option(DummyValue, "Create", " ")
|
||||
o.rawhtml = true
|
||||
o.template = "openclash/input_file_name"
|
||||
o.value = "/etc/openclash/config/"
|
||||
|
||||
o = a:option(Button, "Apply", " ")
|
||||
o.inputtitle = translate("Apply Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
uci:set("openclash", "config", "enable", 1)
|
||||
uci:commit("openclash")
|
||||
SYS.call("/etc/init.d/openclash restart >/dev/null 2>&1 &")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash"))
|
||||
end
|
||||
|
||||
m:append(Template("openclash/config_editor"))
|
||||
|
||||
return ful , form , p , m
|
||||
@ -0,0 +1,184 @@
|
||||
local m, s, o
|
||||
local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local SYS = require "luci.sys"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local HTTP = require "luci.http"
|
||||
local sid = arg[1]
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
font_green = [[<b style=color:green>]]
|
||||
font_off = [[</b>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
m = Map(openclash, translate("Add Custom DNS Servers"))
|
||||
m.pageaction = false
|
||||
m.redirect = DISP.build_url("admin/services/openclash/config-overwrite")
|
||||
if m.uci:get(openclash, sid) ~= "dns_servers" then
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Edit Custom DNS ]] --
|
||||
s = m:section(NamedSection, sid, "dns_servers")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
---- group
|
||||
o = s:option(ListValue, "group", translate("DNS Server Group"))
|
||||
o.description = font_red..bold_on..translate("NameServer Group Must Be Set")..bold_off..font_off
|
||||
o:value("nameserver", translate("NameServer "))
|
||||
o:value("fallback", translate("FallBack "))
|
||||
o:value("default", translate("Default-NameServer"))
|
||||
o.default = "nameserver"
|
||||
o.rempty = false
|
||||
|
||||
---- IP address
|
||||
o = s:option(Value, "ip", translate("DNS Server Address"))
|
||||
o.description = translate("Do Not Add Type Ahead")
|
||||
o.placeholder = translate("Not Null")
|
||||
o.datatype = "or(host, string)"
|
||||
o.rmempty = true
|
||||
|
||||
---- port
|
||||
o = s:option(Value, "port", translate("DNS Server Port"))
|
||||
o.description = translate("Require When Use Non-Standard Port")
|
||||
o.datatype = "port"
|
||||
o.rempty = true
|
||||
|
||||
---- type
|
||||
o = s:option(ListValue, "type", translate("DNS Server Type"))
|
||||
o.description = translate("Communication protocol")
|
||||
o:value("udp", translate("UDP"))
|
||||
o:value("tcp", translate("TCP"))
|
||||
o:value("tls", translate("TLS"))
|
||||
o:value("https", translate("HTTPS"))
|
||||
o:value("quic", translate("QUIC"))
|
||||
o.default = "udp"
|
||||
o.rempty = false
|
||||
|
||||
---- interface
|
||||
o = s:option(Value, "interface", translate("Specific Interface"))
|
||||
o.description = translate("DNS Lookup Only Through The Specific Interface")
|
||||
local interfaces = SYS.exec("ls -l /sys/class/net/ 2>/dev/null |awk '{print $9}' 2>/dev/null")
|
||||
for interface in string.gmatch(interfaces, "%S+") do
|
||||
o:value(interface)
|
||||
end
|
||||
o:value("Disable", translate("Disable"))
|
||||
o.default = "Disable"
|
||||
o.rempty = false
|
||||
|
||||
---- direct-nameserver
|
||||
o = s:option(Flag, "direct_nameserver", translate("Direct Nameserver"), translate("Use For Domain Need Direct"))
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- Node Domain Resolve
|
||||
o = s:option(Flag, "node_resolve", translate("Node Domain Resolve"), translate("Use For Node Domain Resolve"))
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- disable-reuse
|
||||
o = s:option(Flag, "disable_reuse", translate("Disable Reuse"), translate("Disable Reuse The Connection"))
|
||||
o:depends("type", "tls")
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- Force HTTP/3
|
||||
o = s:option(Flag, "http3", translate("Force HTTP/3"), translate("Force HTTP/3 to connect"))
|
||||
o:depends("type", "https")
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- Skip-cert-verify
|
||||
o = s:option(Flag, "skip_cert_verify", translate("skip-cert-verify"), translate("skip-cert-verify"))
|
||||
o:depends("type", "https")
|
||||
o:depends("type", "quic")
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- ECS Subnet
|
||||
o = s:option(Value, "ecs_subnet", translate("ECS Subnet"),translate("Specify the ECS Subnet Address"))
|
||||
o:depends("type", "https")
|
||||
o.rmempty = true
|
||||
o.datatype = "ipaddr"
|
||||
o:value("1.1.1.1/24")
|
||||
|
||||
---- ECS Override
|
||||
o = s:option(Flag, "ecs_override", translate("ECS Override"),translate("Override the ECS Subnet Address"))
|
||||
o:depends("type", "https")
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- disable-ipv4
|
||||
o = s:option(Flag, "disable_ipv4", translate("Disable-IPv4"),translate("Drop The Type of DNS Responsed"))
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- disable-ipv6
|
||||
o = s:option(Flag, "disable_ipv6", translate("Disable-IPv6"),translate("Drop The Type of DNS Responsed"))
|
||||
o.rmempty = false
|
||||
o.default = o.disbled
|
||||
|
||||
---- disable-qtype
|
||||
o = s:option(DynamicList, "disable_qtype", translate("Disable-Qtype"),translate("Drop The Type of DNS Responsed"))
|
||||
o.rmempty = true
|
||||
o.datatype = "uinteger"
|
||||
o.default = o.disbled
|
||||
|
||||
---- Proxy group
|
||||
o = s:option(Value, "specific_group", translate("Specific Group (Support Regex)"))
|
||||
o.description = translate("Group Use For Proxy The DNS")
|
||||
o:depends("group", "nameserver")
|
||||
o:depends("group", "fallback")
|
||||
local groupnames,filename
|
||||
filename = m.uci:get(openclash, "config", "config_path")
|
||||
if filename then
|
||||
groupnames = SYS.exec(string.format('ruby -ryaml -rYAML -I "/usr/share/openclash" -E UTF-8 -e "YAML.load_file(\'%s\')[\'proxy-groups\'].each do |i| puts i[\'name\']+\'##\' end" 2>/dev/null',filename))
|
||||
if groupnames then
|
||||
for groupname in string.gmatch(groupnames, "([^'##\n']+)##") do
|
||||
if groupname ~= nil and groupname ~= "" then
|
||||
o:value(groupname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
m.uci:foreach("openclash", "groups",
|
||||
function(s)
|
||||
if s.name ~= "" and s.name ~= nil then
|
||||
o:value(s.name)
|
||||
end
|
||||
end)
|
||||
|
||||
o:value("DIRECT")
|
||||
o:value("GLOBAL")
|
||||
o:value("Disable", translate("Disable"))
|
||||
o.default = "Disable"
|
||||
o.rempty = false
|
||||
|
||||
local t = {
|
||||
{Commit, Back}
|
||||
}
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button,"Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit(openclash)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
return m
|
||||
333
luci-app-openclash/luasrc/model/cbi/openclash/groups-config.lua
Normal file
@ -0,0 +1,333 @@
|
||||
|
||||
local m, s, o
|
||||
local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local sys = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local sid = arg[1]
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers"))
|
||||
return
|
||||
end
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
font_off = [[</b>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
function IsYamlFile(e)
|
||||
e=e or""
|
||||
local e=string.lower(string.sub(e,-5,-1))
|
||||
return e == ".yaml"
|
||||
end
|
||||
function IsYmlFile(e)
|
||||
e=e or""
|
||||
local e=string.lower(string.sub(e,-4,-1))
|
||||
return e == ".yml"
|
||||
end
|
||||
|
||||
m = Map(openclash, translate("Edit Group"))
|
||||
m.pageaction = false
|
||||
m.redirect = DISP.build_url("admin/services/openclash/servers") .. "?file=" .. HTTP.urlencode(file_path)
|
||||
if m.uci:get(openclash, sid) ~= "groups" then
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Groups Setting ]]--
|
||||
s = m:section(NamedSection, sid, "groups")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
o = s:option(ListValue, "config", translate("Config File"))
|
||||
o:value("all", translate("Use For All Config File"))
|
||||
local e,a={}
|
||||
for t,f in ipairs(fs.glob("/etc/openclash/config/*"))do
|
||||
a=fs.stat(f)
|
||||
if a then
|
||||
e[t]={}
|
||||
e[t].name=fs.basename(f)
|
||||
if IsYamlFile(e[t].name) or IsYmlFile(e[t].name) then
|
||||
o:value(e[t].name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "type", translate("Group Type"))
|
||||
o.rmempty = false
|
||||
o.description = translate("Choose The Operation Mode")
|
||||
o:value("select", translate("Manual-Select"))
|
||||
o:value("smart", translate("Smart-Select"))
|
||||
o:value("url-test", translate("URL-Test"))
|
||||
o:value("fallback", translate("Fallback"))
|
||||
o:value("load-balance", translate("Load-Balance"))
|
||||
|
||||
o = s:option(Value, "name", translate("Group Name"))
|
||||
o.rmempty = false
|
||||
o.default = "Group - "..sid
|
||||
|
||||
o = s:option(ListValue, "disable_udp", translate("Disable UDP"))
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "test_url", translate("Test URL"))
|
||||
o:value("http://cp.cloudflare.com/generate_204")
|
||||
o:value("http://www.gstatic.com/generate_204")
|
||||
o:value("https://cp.cloudflare.com/generate_204")
|
||||
o.rmempty = true
|
||||
o:depends("type", "url-test")
|
||||
o:depends("type", "fallback")
|
||||
o:depends("type", "load-balance")
|
||||
o:depends("type", "smart")
|
||||
|
||||
o = s:option(Value, "test_interval", translate("Test Interval(s)"))
|
||||
o.default = "300"
|
||||
o.rmempty = true
|
||||
o:depends("type", "url-test")
|
||||
o:depends("type", "fallback")
|
||||
o:depends("type", "load-balance")
|
||||
o:depends("type", "smart")
|
||||
|
||||
o = s:option(ListValue, "strategy", translate("Strategy Type"))
|
||||
o.rmempty = true
|
||||
o.description = translate("Choose The Load-Balance's Strategy Type")
|
||||
o:value("round-robin", translate("Round-robin"))
|
||||
o:value("consistent-hashing", translate("Consistent-hashing"))
|
||||
o:value("sticky-sessions", translate("Sticky-sessions"))
|
||||
o:depends("type", "load-balance")
|
||||
|
||||
o = s:option(ListValue, "uselightgbm", translate("Uselightgbm"))
|
||||
o.description = translate("Use LightGBM Model For Smart Group Weight Prediction")
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o.rmempty = true
|
||||
o:depends("type", "smart")
|
||||
|
||||
o = s:option(ListValue, "collectdata", translate("Collectdata"))
|
||||
o.description = translate("Collect Datas For Smart Group Model Training")
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = "false"
|
||||
o.rmempty = true
|
||||
o:depends("type", "smart")
|
||||
|
||||
o = s:option(Value, "policy_priority", translate("Policy Priority"))
|
||||
o.description = translate("The Priority Of The Nodes, The Higher Than 1, The More Likely It Is To Be Selected, The Default Is 1, Support Regex")
|
||||
o.rmempty = true
|
||||
o.placeholder = "Premium:0.9;SG:1.3"
|
||||
o.rmempty = true
|
||||
o:depends("type", "smart")
|
||||
|
||||
o = s:option(Value, "tolerance", translate("Tolerance(ms)"))
|
||||
o.default = "150"
|
||||
o.rmempty = true
|
||||
o:depends("type", "url-test")
|
||||
|
||||
o = s:option(Value, "policy_filter", translate("Provider Filter"))
|
||||
o.rmempty = true
|
||||
o.placeholder = "bgp|sg"
|
||||
|
||||
o = s:option(Value, "icon", translate("Icon"))
|
||||
o.rmempty = true
|
||||
|
||||
-- [[ other-setting ]]--
|
||||
o = s:option(Value, "other_parameters", translate("Other Parameters"))
|
||||
o.template = "cbi/tvalue"
|
||||
o.rows = 20
|
||||
o.wrap = "off"
|
||||
o.description = font_red..bold_on..translate("Edit Your Other Parameters Here")..bold_off..font_off
|
||||
o.rmempty = true
|
||||
function o.cfgvalue(self, section)
|
||||
if self.map:get(section, "other_parameters") == nil then
|
||||
return "# Example:\n"..
|
||||
"# Only support YAML, four spaces need to be reserved at the beginning of each line to maintain formatting alignment\n"..
|
||||
"# 示例:\n"..
|
||||
"# 仅支持 YAML, 每行行首需要多保留四个空格以使脚本处理后能够与上方配置保持格式对齐\n"..
|
||||
"# type: select\n"..
|
||||
"# proxies:\n"..
|
||||
"# - DIRECT\n"..
|
||||
"# - ss\n"..
|
||||
"# use:\n"..
|
||||
"# - provider1\n"..
|
||||
"# - provider1\n"..
|
||||
"# url: 'https://www.gstatic.com/generate_204'\n"..
|
||||
"# interval: 300\n"..
|
||||
"# lazy: true\n"..
|
||||
"# timeout: 5000\n"..
|
||||
"# max-failed-times: 5\n"..
|
||||
"# disable-udp: true\n"..
|
||||
"# interface-name: en0\n"..
|
||||
"# routing-mark: 11451\n"..
|
||||
"# include-all: false\n"..
|
||||
"# include-all-proxies: false\n"..
|
||||
"# include-all-providers: false\n"..
|
||||
"# filter: \"(?i)港|hk|hongkong|hong kong\"\n"..
|
||||
"# exclude-filter: \"美|日\"\n"..
|
||||
"# exclude-type: \"Shadowsocks|Http\"\n"..
|
||||
"# expected-status: 204\n"..
|
||||
"# hidden: true\n"..
|
||||
"# icon: xxx"
|
||||
else
|
||||
return Value.cfgvalue(self, section)
|
||||
end
|
||||
end
|
||||
function o.validate(self, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
value = value:gsub("%c*$", "")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "other_group", translate("Other Group (Support Regex)"))
|
||||
o.description = font_red..bold_on..translate("The Added Proxy Groups Must Exist Except 'DIRECT' & 'REJECT' & 'REJECT-DROP' & 'PASS' & 'GLOBAL'")..bold_off..font_off
|
||||
o:value("all", translate("All Groups"))
|
||||
uci:foreach("openclash", "groups",
|
||||
function(s)
|
||||
if s.name ~= "" and s.name ~= nil and s.name ~= m.uci:get(openclash, sid, "name") and (s.config == m.uci:get(openclash, sid, "config") or s.config == "all") then
|
||||
o:value(s.name)
|
||||
end
|
||||
end)
|
||||
o:value("DIRECT")
|
||||
o:value("REJECT")
|
||||
o:value("REJECT-DROP")
|
||||
o:value("PASS")
|
||||
o:value("GLOBAL")
|
||||
o.rmempty = true
|
||||
|
||||
local function sync_group_name(section, old_name, new_name)
|
||||
if old_name == "" or old_name == nil or old_name == new_name then
|
||||
return
|
||||
end
|
||||
|
||||
local function matches(value, pattern)
|
||||
if not value or not pattern then
|
||||
return false
|
||||
end
|
||||
|
||||
local ok, result = pcall(function() return string.match(value, pattern) end)
|
||||
if ok and result then
|
||||
return true
|
||||
end
|
||||
return value == pattern
|
||||
end
|
||||
|
||||
-- servers 的 groups 列表
|
||||
uci:foreach(openclash, "servers", function(s)
|
||||
local groups = uci:get(openclash, s[".name"], "groups")
|
||||
if groups then
|
||||
local new_groups = {}
|
||||
local changed = false
|
||||
for _, item in ipairs(groups) do
|
||||
if matches(old_name, item) then
|
||||
table.insert(new_groups, new_name)
|
||||
changed = true
|
||||
else
|
||||
table.insert(new_groups, item)
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
uci:delete(openclash, s[".name"], "groups")
|
||||
uci:set_list(openclash, s[".name"], "groups", new_groups)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- proxy-provider 的 groups 列表
|
||||
uci:foreach(openclash, "proxy-provider", function(s)
|
||||
local groups = uci:get(openclash, s[".name"], "groups")
|
||||
if groups then
|
||||
local new_groups = {}
|
||||
local changed = false
|
||||
for _, item in ipairs(groups) do
|
||||
if matches(old_name, item) then
|
||||
table.insert(new_groups, new_name)
|
||||
changed = true
|
||||
else
|
||||
table.insert(new_groups, item)
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
uci:delete(openclash, s[".name"], "groups")
|
||||
uci:set_list(openclash, s[".name"], "groups", new_groups)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- groups 的 other_group 列表
|
||||
uci:foreach(openclash, "groups", function(s)
|
||||
if s[".name"] ~= section then
|
||||
local other_group = uci:get(openclash, s[".name"], "other_group")
|
||||
if other_group then
|
||||
local new_other = {}
|
||||
local changed = false
|
||||
for _, item in ipairs(other_group) do
|
||||
if matches(old_name, item) then
|
||||
table.insert(new_other, new_name)
|
||||
changed = true
|
||||
else
|
||||
table.insert(new_other, item)
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
uci:delete(openclash, s[".name"], "other_group")
|
||||
uci:set_list(openclash, s[".name"], "other_group", new_other)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- dns_servers 的 specific_group 选项
|
||||
uci:foreach(openclash, "dns_servers", function(s)
|
||||
local specific_group = uci:get(openclash, s[".name"], "specific_group")
|
||||
if matches(old_name, specific_group) then
|
||||
uci:set(openclash, s[".name"], "specific_group", new_name)
|
||||
end
|
||||
end)
|
||||
|
||||
-- servers 的 dialer_proxy 选项
|
||||
uci:foreach(openclash, "servers", function(s)
|
||||
local dialer_proxy = uci:get(openclash, s[".name"], "dialer_proxy")
|
||||
if matches(old_name, dialer_proxy) then
|
||||
uci:set(openclash, s[".name"], "dialer_proxy", new_name)
|
||||
end
|
||||
end)
|
||||
|
||||
uci:commit(openclash)
|
||||
end
|
||||
|
||||
local t = {
|
||||
{Commit, Back}
|
||||
}
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button,"Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
local old_name = m.uci:get(openclash, sid, "old_name") or ""
|
||||
local new_name = HTTP.formvalue("cbid.openclash." .. sid .. ".name") or m.uci:get(openclash, sid, "name")
|
||||
sync_group_name(sid, old_name, new_name)
|
||||
m.uci:set(openclash, sid, "old_name", new_name)
|
||||
m.uci:commit(openclash)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
m:append(Template("openclash/config_editor"))
|
||||
return m
|
||||
22
luci-app-openclash/luasrc/model/cbi/openclash/log.lua
Normal file
@ -0,0 +1,22 @@
|
||||
--
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
|
||||
m = Map("openclash", translate("Server Logs"))
|
||||
s = m:section(TypedSection, "openclash")
|
||||
m.pageaction = false
|
||||
s.anonymous = true
|
||||
s.addremove=false
|
||||
|
||||
log = s:option(TextValue, "clog")
|
||||
log.readonly=true
|
||||
log.pollcheck=true
|
||||
log.template="openclash/log"
|
||||
log.description = translate("")
|
||||
log.rows = 29
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
m:append(Template("openclash/config_editor"))
|
||||
|
||||
return m
|
||||
@ -0,0 +1,60 @@
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local fs = require "luci.openclash"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "%s") % arg[1])
|
||||
return
|
||||
end
|
||||
|
||||
m = Map("openclash", translate("File Edit"))
|
||||
m.pageaction = false
|
||||
m.redirect = DISP.build_url("admin", "services", "openclash", "other-file-edit", "%s") % arg[1].."?file="..HTTP.urlencode(file_path)
|
||||
s = m:section(TypedSection, "openclash")
|
||||
s.anonymous = true
|
||||
s.addremove=false
|
||||
|
||||
o = s:option(TextValue, "edit_file")
|
||||
o.rows = 50
|
||||
o.wrap = "off"
|
||||
|
||||
function o.write(self, section, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
local old_value = fs.readfile(file_path)
|
||||
if value ~= old_value then
|
||||
fs.writefile(file_path, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function o.cfgvalue(self, section)
|
||||
return fs.readfile(file_path) or ""
|
||||
end
|
||||
|
||||
local t = {
|
||||
{Commit, Back}
|
||||
}
|
||||
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button, "Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "%s") % arg[1])
|
||||
end
|
||||
|
||||
m:append(Template("openclash/config_editor"))
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
return m
|
||||
@ -0,0 +1,197 @@
|
||||
|
||||
local m, s, o
|
||||
local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local sys = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local sid = arg[1]
|
||||
local fs = require "luci.openclash"
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "servers"))
|
||||
return
|
||||
end
|
||||
|
||||
font_red = [[<b style=color:red>]]
|
||||
font_off = [[</b>]]
|
||||
bold_on = [[<strong>]]
|
||||
bold_off = [[</strong>]]
|
||||
|
||||
function IsYamlFile(e)
|
||||
e=e or""
|
||||
local e=string.lower(string.sub(e,-5,-1))
|
||||
return e == ".yaml"
|
||||
end
|
||||
function IsYmlFile(e)
|
||||
e=e or""
|
||||
local e=string.lower(string.sub(e,-4,-1))
|
||||
return e == ".yml"
|
||||
end
|
||||
|
||||
m = Map(openclash, translate("Edit Proxy-Provider"))
|
||||
m.pageaction = false
|
||||
m.redirect = DISP.build_url("admin/services/openclash/servers") .. "?file=" .. HTTP.urlencode(file_path)
|
||||
if m.uci:get(openclash, sid) ~= "proxy-provider" then
|
||||
HTTP.redirect(m.redirect)
|
||||
return
|
||||
end
|
||||
|
||||
-- [[ Provider Setting ]]--
|
||||
s = m:section(NamedSection, sid, "proxy-provider")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
o = s:option(ListValue, "config", translate("Config File"))
|
||||
o:value("all", translate("Use For All Config File"))
|
||||
local e,a={}
|
||||
for t,f in ipairs(fs.glob("/etc/openclash/config/*"))do
|
||||
a=fs.stat(f)
|
||||
if a then
|
||||
e[t]={}
|
||||
e[t].name=fs.basename(f)
|
||||
if IsYamlFile(e[t].name) or IsYmlFile(e[t].name) then
|
||||
o:value(e[t].name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "type", translate("Provider Type"))
|
||||
o.rmempty = true
|
||||
o.description = translate("Choose The Provider Type")
|
||||
o:value("http")
|
||||
o:value("file")
|
||||
o:value("inline")
|
||||
|
||||
o = s:option(Value, "name", translate("Provider Name"))
|
||||
o.rmempty = false
|
||||
o.default = "Proxy-provider - "..sid
|
||||
|
||||
o = s:option(ListValue, "path", translate("Provider Path"))
|
||||
o.description = translate("Update Your Proxy Provider File From Config Luci Page")
|
||||
local p,h={}
|
||||
for t,f in ipairs(fs.glob("/etc/openclash/proxy_provider/*"))do
|
||||
h=fs.stat(f)
|
||||
if h then
|
||||
p[t]={}
|
||||
p[t].name=fs.basename(f)
|
||||
if IsYamlFile(p[t].name) or IsYmlFile(p[t].name) then
|
||||
o:value("./proxy_provider/"..p[t].name)
|
||||
end
|
||||
end
|
||||
end
|
||||
o.rmempty = false
|
||||
o:depends("type", "file")
|
||||
|
||||
o = s:option(Value, "provider_url", translate("Provider URL"))
|
||||
o.rmempty = false
|
||||
o:depends("type", "http")
|
||||
|
||||
o = s:option(Value, "provider_filter", translate("Provider Filter"))
|
||||
o.rmempty = true
|
||||
o.placeholder = "bgp|sg"
|
||||
|
||||
o = s:option(Value, "provider_interval", translate("Provider Interval(s)"))
|
||||
o.default = "3600"
|
||||
o.rmempty = false
|
||||
o:depends("type", "http")
|
||||
|
||||
o = s:option(ListValue, "health_check", translate("Provider Health Check"))
|
||||
o:value("false", translate("Disable"))
|
||||
o:value("true", translate("Enable"))
|
||||
o.default = true
|
||||
|
||||
o = s:option(Value, "health_check_url", translate("Health Check URL"))
|
||||
o:value("http://cp.cloudflare.com/generate_204")
|
||||
o:value("http://www.gstatic.com/generate_204")
|
||||
o:value("https://cp.cloudflare.com/generate_204")
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "health_check_interval", translate("Health Check Interval(s)"))
|
||||
o.default = "300"
|
||||
o.rmempty = false
|
||||
|
||||
-- [[ other-setting ]]--
|
||||
o = s:option(Value, "other_parameters", translate("Other Parameters"))
|
||||
o.template = "cbi/tvalue"
|
||||
o.rows = 20
|
||||
o.wrap = "off"
|
||||
o.description = font_red..bold_on..translate("Edit Your Other Parameters Here")..bold_off..font_off
|
||||
o.rmempty = true
|
||||
function o.cfgvalue(self, section)
|
||||
if self.map:get(section, "other_parameters") == nil then
|
||||
return "# Example:\n"..
|
||||
"# Only support YAML, four spaces need to be reserved at the beginning of each line to maintain formatting alignment\n"..
|
||||
"# 示例:\n"..
|
||||
"# 仅支持 YAML, 每行行首需要多保留四个空格以使脚本处理后能够与上方配置保持格式对齐\n"..
|
||||
"# header:\n"..
|
||||
"# User-Agent:\n"..
|
||||
"# - \"Clash/v1.18.0\"\n"..
|
||||
"# - \"mihomo/1.18.3\"\n"..
|
||||
"# Authorization:\n"..
|
||||
"# - \"token 1231231\"\n"..
|
||||
"# override:\n"..
|
||||
"# skip-cert-verify: true\n"..
|
||||
"# additional-prefix: \"provider1 prefix |\"\n"..
|
||||
"# additional-suffix: \"| provider1 suffix\"\n"..
|
||||
"# proxy-name:\n"..
|
||||
"# - pattern: \"IPLC-(.*?)倍\"\n"..
|
||||
"# target: \"iplc x $1\"\n"..
|
||||
"# exclude-type: \"ss|http\"\n"..
|
||||
"\n"..
|
||||
"# inline Example:\n"..
|
||||
"# payload:\n"..
|
||||
"# - name: \"ss1\"\n"..
|
||||
"# type: ss\n"..
|
||||
"# server: server\n"..
|
||||
"# port: 443\n"..
|
||||
"# cipher: chacha20-ietf-poly1305\n"..
|
||||
"# password: \"password\""
|
||||
else
|
||||
return Value.cfgvalue(self, section)
|
||||
end
|
||||
end
|
||||
function o.validate(self, value)
|
||||
if value then
|
||||
value = value:gsub("\r\n?", "\n")
|
||||
value = value:gsub("%c*$", "")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
o = s:option(DynamicList, "groups", translate("Proxy Group (Support Regex)"))
|
||||
o.description = font_red..bold_on..translate("The added Proxy Groups Must Exist")..bold_off..font_off
|
||||
o.rmempty = true
|
||||
o:value("all", translate("All Groups"))
|
||||
m.uci:foreach("openclash", "groups",
|
||||
function(s)
|
||||
if s.name ~= "" and s.name ~= nil and (s.config == m.uci:get(openclash, sid, "config") or s.config == "all") then
|
||||
o:value(s.name)
|
||||
end
|
||||
end)
|
||||
|
||||
local t = {
|
||||
{Commit, Back}
|
||||
}
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button,"Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit(openclash)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:revert(openclash, sid)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
m:append(Template("openclash/config_editor"))
|
||||
return m
|
||||
@ -0,0 +1,122 @@
|
||||
|
||||
local proxy_form
|
||||
local openclash = "openclash"
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local UTIL = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
local p,r={}
|
||||
for x,y in ipairs(fs.glob("/etc/openclash/proxy_provider/*"))do
|
||||
r=fs.stat(y)
|
||||
if r then
|
||||
p[x]={}
|
||||
p[x].num=string.format(x)
|
||||
p[x].name=fs.basename(y)
|
||||
p[x].mtime=os.date("%Y-%m-%d %H:%M:%S",r.mtime)
|
||||
p[x].size=fs.filesize(r.size)
|
||||
p[x].remove=0
|
||||
p[x].enable=false
|
||||
end
|
||||
end
|
||||
|
||||
proxy_form=SimpleForm("proxy_provider_file_list",translate("Proxy Provider File List"))
|
||||
proxy_form.reset=false
|
||||
proxy_form.submit=false
|
||||
tb1=proxy_form:section(Table,p)
|
||||
nu1=tb1:option(DummyValue,"num",translate("Serial Number"))
|
||||
nm1=tb1:option(DummyValue,"name",translate("File Name"))
|
||||
mt1=tb1:option(DummyValue,"mtime",translate("Update Time"))
|
||||
sz1=tb1:option(DummyValue,"size",translate("Size"))
|
||||
|
||||
btned1=tb1:option(Button,"edit",translate("Edit"))
|
||||
btned1.render=function(p,x,r)
|
||||
p.inputstyle="apply"
|
||||
Button.render(p,x,r)
|
||||
end
|
||||
btned1.write=function(r,x)
|
||||
local file_path = "/etc/openclash/proxy_provider/" .. fs.basename(p[x].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "proxy-provider-file-manage") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
end
|
||||
|
||||
btndl1 = tb1:option(Button,"download1",translate("Download Config"))
|
||||
btndl1.render=function(y,x,r)
|
||||
y.inputstyle="remove"
|
||||
Button.render(y,x,r)
|
||||
end
|
||||
btndl1.write = function (r,x)
|
||||
local sPath, sFile, fd, block
|
||||
sPath = "/etc/openclash/proxy_provider/"..p[x].name
|
||||
sFile = NXFS.basename(sPath)
|
||||
if fs.isdirectory(sPath) then
|
||||
fd = io.popen('tar -C "%s" -cz .' % {sPath}, "r")
|
||||
sFile = sFile .. ".tar.gz"
|
||||
else
|
||||
fd = nixio.open(sPath, "r")
|
||||
end
|
||||
if not fd then
|
||||
return
|
||||
end
|
||||
HTTP.header('Content-Disposition', 'attachment; filename="%s"' % {sFile})
|
||||
HTTP.prepare_content("application/octet-stream")
|
||||
while true do
|
||||
block = fd:read(nixio.const.buffersize)
|
||||
if (not block) or (#block ==0) then
|
||||
break
|
||||
else
|
||||
HTTP.write(block)
|
||||
end
|
||||
end
|
||||
fd:close()
|
||||
HTTP.close()
|
||||
end
|
||||
|
||||
btnrm1=tb1:option(Button,"remove1",translate("Remove"))
|
||||
btnrm1.render=function(p,x,r)
|
||||
p.inputstyle="reset"
|
||||
Button.render(p,x,r)
|
||||
end
|
||||
btnrm1.write=function(r,x)
|
||||
local r=fs.unlink("/etc/openclash/proxy_provider/"..luci.openclash.basename(p[x].name))
|
||||
if r then table.remove(p,x)end
|
||||
return r
|
||||
end
|
||||
|
||||
local t = {
|
||||
{Refresh, Create, Delete_all, Apply}
|
||||
}
|
||||
|
||||
a = proxy_form:section(Table, t)
|
||||
|
||||
o = a:option(Button, "Refresh", " ")
|
||||
o.inputtitle = translate("Refresh Page")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "proxy-provider-file-manage"))
|
||||
end
|
||||
|
||||
o = a:option(DummyValue, "Create", " ")
|
||||
o.rawhtml = true
|
||||
o.template = "openclash/input_file_name"
|
||||
o.value = "/etc/openclash/proxy_provider/"
|
||||
|
||||
o = a:option(Button, "Delete_all", " ")
|
||||
o.inputtitle = translate("Delete All File")
|
||||
o.inputstyle = "remove"
|
||||
o.write = function()
|
||||
luci.sys.call("rm -rf /etc/openclash/proxy_provider/* >/dev/null 2>&1")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "proxy-provider-file-manage"))
|
||||
end
|
||||
|
||||
o = a:option(Button, "Apply", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
end
|
||||
|
||||
proxy_form:append(Template("openclash/toolbar_show"))
|
||||
return proxy_form
|
||||
@ -0,0 +1,122 @@
|
||||
|
||||
local rule_form
|
||||
local openclash = "openclash"
|
||||
local NXFS = require "nixio.fs"
|
||||
local SYS = require "luci.sys"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local UTIL = require "luci.util"
|
||||
local fs = require "luci.openclash"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
local g,h={}
|
||||
for n,m in ipairs(fs.glob("/etc/openclash/rule_provider/*"))do
|
||||
h=fs.stat(m)
|
||||
if h then
|
||||
g[n]={}
|
||||
g[n].num=string.format(n)
|
||||
g[n].name=fs.basename(m)
|
||||
g[n].mtime=os.date("%Y-%m-%d %H:%M:%S",h.mtime)
|
||||
g[n].size=fs.filesize(h.size)
|
||||
g[n].remove=0
|
||||
g[n].enable=false
|
||||
end
|
||||
end
|
||||
|
||||
rule_form=SimpleForm("rule_provider_file_list",translate("Rule Providers File List"))
|
||||
rule_form.reset=false
|
||||
rule_form.submit=false
|
||||
tb2=rule_form:section(Table,g)
|
||||
nu2=tb2:option(DummyValue,"num",translate("Serial Number"))
|
||||
nm2=tb2:option(DummyValue,"name",translate("File Name"))
|
||||
mt2=tb2:option(DummyValue,"mtime",translate("Update Time"))
|
||||
sz2=tb2:option(DummyValue,"size",translate("Size"))
|
||||
|
||||
btned1=tb2:option(Button,"edit",translate("Edit"))
|
||||
btned1.render=function(g,n,h)
|
||||
g.inputstyle="apply"
|
||||
Button.render(g,n,h)
|
||||
end
|
||||
btned1.write=function(h,n)
|
||||
local file_path = "/etc/openclash/rule_provider/" .. fs.basename(g[n].name)
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "other-file-edit", "rule-providers-file-manage") .. "?file=" .. HTTP.urlencode(file_path))
|
||||
end
|
||||
|
||||
btndl2 = tb2:option(Button,"download2",translate("Download Config"))
|
||||
btndl2.render=function(m,n,h)
|
||||
m.inputstyle="remove"
|
||||
Button.render(m,n,h)
|
||||
end
|
||||
btndl2.write = function (h,n)
|
||||
local sPath, sFile, fd, block
|
||||
sPath = "/etc/openclash/rule_provider/"..g[n].name
|
||||
sFile = NXFS.basename(sPath)
|
||||
if fs.isdirectory(sPath) then
|
||||
fd = io.popen('tar -C "%s" -cz .' % {sPath}, "r")
|
||||
sFile = sFile .. ".tar.gz"
|
||||
else
|
||||
fd = nixio.open(sPath, "r")
|
||||
end
|
||||
if not fd then
|
||||
return
|
||||
end
|
||||
HTTP.header('Content-Disposition', 'attachment; filename="%s"' % {sFile})
|
||||
HTTP.prepare_content("application/octet-stream")
|
||||
while true do
|
||||
block = fd:read(nixio.const.buffersize)
|
||||
if (not block) or (#block ==0) then
|
||||
break
|
||||
else
|
||||
HTTP.write(block)
|
||||
end
|
||||
end
|
||||
fd:close()
|
||||
HTTP.close()
|
||||
end
|
||||
|
||||
btnrm2=tb2:option(Button,"remove2",translate("Remove"))
|
||||
btnrm2.render=function(g,n,h)
|
||||
g.inputstyle="reset"
|
||||
Button.render(g,n,h)
|
||||
end
|
||||
btnrm2.write=function(h,n)
|
||||
local h=fs.unlink("/etc/openclash/rule_provider/"..luci.openclash.basename(g[n].name))
|
||||
if h then table.remove(g,n)end
|
||||
return h
|
||||
end
|
||||
|
||||
local t = {
|
||||
{Refresh, Create, Delete_all, Apply}
|
||||
}
|
||||
|
||||
a = rule_form:section(Table, t)
|
||||
|
||||
o = a:option(Button, "Refresh", " ")
|
||||
o.inputtitle = translate("Refresh Page")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "rule-providers-file-manage"))
|
||||
end
|
||||
|
||||
o = a:option(DummyValue, "Create", " ")
|
||||
o.rawhtml = true
|
||||
o.template = "openclash/input_file_name"
|
||||
o.value = "/etc/openclash/rule_provider/"
|
||||
|
||||
o = a:option(Button, "Delete_all", " ")
|
||||
o.inputtitle = translate("Delete All File")
|
||||
o.inputstyle = "remove"
|
||||
o.write = function()
|
||||
luci.sys.call("rm -rf /etc/openclash/rule_provider/* >/dev/null 2>&1")
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "rule-providers-file-manage"))
|
||||
end
|
||||
|
||||
o = a:option(Button, "Apply", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
end
|
||||
|
||||
rule_form:append(Template("openclash/toolbar_show"))
|
||||
return rule_form
|
||||
1311
luci-app-openclash/luasrc/model/cbi/openclash/servers-config.lua
Normal file
259
luci-app-openclash/luasrc/model/cbi/openclash/servers.lua
Normal file
@ -0,0 +1,259 @@
|
||||
|
||||
local m, s, o
|
||||
local openclash = "openclash"
|
||||
local uci = luci.model.uci.cursor()
|
||||
local fs = require "luci.openclash"
|
||||
local HTTP = require "luci.http"
|
||||
local DISP = require "luci.dispatcher"
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
|
||||
if not file_path then
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
return
|
||||
end
|
||||
|
||||
m = Map(openclash, translate("Servers & Groups manage"))
|
||||
m.pageaction = false
|
||||
m.redirect = DISP.build_url("admin/services/openclash/servers") .. "?file=" .. HTTP.urlencode(file_path)
|
||||
m.description=translate("Attention:")..
|
||||
"<br/>"..translate("1. Before modifying the configuration file, please click the button below to read the configuration file")..
|
||||
"<br/>"..translate("2. Proxy-providers address can be directly filled in the subscription link")..
|
||||
"<br/>"..
|
||||
"<br/>"..translate("Introduction to proxy usage:").." <a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://wiki.metacubex.one/config/proxies/\")'>"..translate("https://wiki.metacubex.one/config/proxies/").."</a>"..
|
||||
"<br/>"..translate("Introduction to proxy-provider usage:").." <a href='javascript:void(0)' onclick='javascript:return winOpen(\"https://wiki.metacubex.one/config/proxy-providers/\")'>"..translate("https://wiki.metacubex.one/config/proxy-providers/").."</a>"
|
||||
|
||||
-- [[ Groups Manage ]]--
|
||||
gs = m:section(TypedSection, "groups", translate("Proxy Groups"))
|
||||
gs.anonymous = true
|
||||
gs.addremove = true
|
||||
gs.sortable = true
|
||||
gs.template = "openclash/tblsection"
|
||||
gs.extedit = DISP.build_url("admin/services/openclash/groups-config/%s").."?file="..file_path
|
||||
function gs.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "config", name)
|
||||
end
|
||||
HTTP.redirect(gs.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
---- enable flag
|
||||
o = gs:option(Flag, "enabled", translate("Enable"))
|
||||
o.rmempty = false
|
||||
o.default = o.enabled
|
||||
o.cfgvalue = function(...)
|
||||
return Flag.cfgvalue(...) or "1"
|
||||
end
|
||||
|
||||
o = gs:option(DummyValue, "config", translate("Config File"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("all")
|
||||
end
|
||||
|
||||
o = gs:option(DummyValue, "type", translate("Group Type"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = gs:option(DummyValue, "name", translate("Group Name"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
-- [[ Proxy-Provider Manage ]]--
|
||||
ps = m:section(TypedSection, "proxy-provider", translate("Proxy-Provider"))
|
||||
ps.anonymous = true
|
||||
ps.addremove = true
|
||||
ps.sortable = true
|
||||
ps.template = "openclash/tblsection"
|
||||
ps.extedit = DISP.build_url("admin/services/openclash/proxy-provider-config/%s").."?file="..file_path
|
||||
function ps.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "config", name)
|
||||
end
|
||||
HTTP.redirect(ps.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
o = ps:option(Flag, "enabled", translate("Enable"))
|
||||
o.rmempty = false
|
||||
o.default = o.enabled
|
||||
o.cfgvalue = function(...)
|
||||
return Flag.cfgvalue(...) or "1"
|
||||
end
|
||||
|
||||
o = ps:option(DummyValue, "config", translate("Config File"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("all")
|
||||
end
|
||||
|
||||
o = ps:option(DummyValue, "type", translate("Provider Type"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = ps:option(DummyValue, "name", translate("Provider Name"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
-- [[ Servers Manage ]]--
|
||||
ss = m:section(TypedSection, "servers", translate("Proxies"))
|
||||
ss.anonymous = true
|
||||
ss.addremove = true
|
||||
ss.sortable = true
|
||||
ss.template = "openclash/tblsection"
|
||||
ss.extedit = DISP.build_url("admin/services/openclash/servers-config/%s").."?file="..file_path
|
||||
function ss.create(self, section)
|
||||
local sid = TypedSection.create(self, section)
|
||||
if sid then
|
||||
local name = HTTP.formvalue("cbi.cts.tagname.".. self.config .. "." .. self.sectiontype)
|
||||
if name and #name > 0 then
|
||||
self.map.uci:set("openclash", sid, "config", name)
|
||||
end
|
||||
HTTP.redirect(ss.extedit % sid)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
---- enable flag
|
||||
o = ss:option(Flag, "enabled", translate("Enable"))
|
||||
o.rmempty = false
|
||||
o.default = o.enabled
|
||||
o.cfgvalue = function(...)
|
||||
return Flag.cfgvalue(...) or "1"
|
||||
end
|
||||
|
||||
o = ss:option(DummyValue, "config", translate("Config File"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("all")
|
||||
end
|
||||
|
||||
o = ss:option(DummyValue, "type", translate("Type"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = ss:option(DummyValue, "name", translate("Server Alias"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = ss:option(DummyValue, "server", translate("Server Address"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = ss:option(DummyValue, "port", translate("Server Port"))
|
||||
function o.cfgvalue(...)
|
||||
return Value.cfgvalue(...) or translate("None")
|
||||
end
|
||||
|
||||
o = ss:option(DummyValue, "udp", translate("UDP Support"))
|
||||
function o.cfgvalue(...)
|
||||
if Value.cfgvalue(...) == "true" then
|
||||
return translate("Enable")
|
||||
elseif Value.cfgvalue(...) == "false" then
|
||||
return translate("Disable")
|
||||
else
|
||||
return translate("None")
|
||||
end
|
||||
end
|
||||
|
||||
local tt = {
|
||||
{Delete_Unused_Servers, Delete_Servers, Delete_Proxy_Provider, Delete_Groups}
|
||||
}
|
||||
|
||||
b = m:section(Table, tt)
|
||||
|
||||
o = b:option(Button,"Delete_Unused_Servers", " ")
|
||||
o.inputtitle = translate("Delete Unused Servers")
|
||||
o.inputstyle = "reset"
|
||||
o.write = function()
|
||||
m.uci:foreach("openclash", "servers",
|
||||
function(s)
|
||||
if s.enabled ~= "1" then
|
||||
m.uci:delete("openclash", s[".name"])
|
||||
end
|
||||
end)
|
||||
m.uci:commit("openclash")
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = b:option(Button,"Delete_Servers", " ")
|
||||
o.inputtitle = translate("Delete Servers")
|
||||
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", " ")
|
||||
o.inputtitle = translate("Delete Proxy Providers")
|
||||
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", " ")
|
||||
o.inputtitle = translate("Delete Groups")
|
||||
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 = {
|
||||
{Load_Config, Commit, Apply, Back}
|
||||
}
|
||||
|
||||
a = m:section(Table, t)
|
||||
|
||||
o = a:option(Button,"Load_Config", " ")
|
||||
o.inputtitle = translate("Read Config")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
luci.sys.call("/usr/share/openclash/yml_groups_get.sh \"%s\" 2>/dev/null" % file_path)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button, "Commit", " ")
|
||||
o.inputtitle = translate("Commit Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button, "Apply", " ")
|
||||
o.inputtitle = translate("Apply Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
m.uci:commit("openclash")
|
||||
luci.sys.call("/usr/share/openclash/yml_groups_set.sh \"%s\" >/dev/null 2>&1 &" % file_path)
|
||||
HTTP.redirect(m.redirect)
|
||||
end
|
||||
|
||||
o = a:option(Button,"Back", " ")
|
||||
o.inputtitle = translate("Back Settings")
|
||||
o.inputstyle = "apply"
|
||||
o.write = function()
|
||||
HTTP.redirect(DISP.build_url("admin", "services", "openclash", "config"))
|
||||
end
|
||||
|
||||
m:append(Template("openclash/toolbar_show"))
|
||||
|
||||
return m
|
||||
1508
luci-app-openclash/luasrc/model/cbi/openclash/settings.lua
Normal file
526
luci-app-openclash/luasrc/openclash.lua
Normal file
@ -0,0 +1,526 @@
|
||||
--[[
|
||||
LuCI - Filesystem tools
|
||||
|
||||
Description:
|
||||
A module offering often needed filesystem manipulation functions
|
||||
|
||||
FileId:
|
||||
$Id$
|
||||
|
||||
License:
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
]]--
|
||||
|
||||
local io = require "io"
|
||||
local os = require "os"
|
||||
local ltn12 = require "luci.ltn12"
|
||||
local fs = require "nixio.fs"
|
||||
local nutil = require "nixio.util"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local SYS = require "luci.sys"
|
||||
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"
|
||||
|
||||
--- Test for file access permission on given path.
|
||||
-- @class function
|
||||
-- @name access
|
||||
-- @param str String value containing the path
|
||||
-- @return Number containing the return code, 0 on sucess or nil on error
|
||||
-- @return String containing the error description (if any)
|
||||
-- @return Number containing the os specific errno (if any)
|
||||
access = fs.access
|
||||
|
||||
--- Evaluate given shell glob pattern and return a table containing all matching
|
||||
-- file and directory entries.
|
||||
-- @class function
|
||||
-- @name glob
|
||||
-- @param filename String containing the path of the file to read
|
||||
-- @return Table containing file and directory entries or nil if no matches
|
||||
-- @return String containing the error description (if no matches)
|
||||
-- @return Number containing the os specific errno (if no matches)
|
||||
function glob(...)
|
||||
local iter, code, msg = fs.glob(...)
|
||||
if iter then
|
||||
return nutil.consume(iter)
|
||||
else
|
||||
return nil, code, msg
|
||||
end
|
||||
end
|
||||
|
||||
--- Checks wheather the given path exists and points to a regular file.
|
||||
-- @param filename String containing the path of the file to test
|
||||
-- @return Boolean indicating wheather given path points to regular file
|
||||
function isfile(filename)
|
||||
return fs.stat(filename, "type") == "reg"
|
||||
end
|
||||
|
||||
--- Checks wheather the given path exists and points to a directory.
|
||||
-- @param dirname String containing the path of the directory to test
|
||||
-- @return Boolean indicating wheather given path points to directory
|
||||
function isdirectory(dirname)
|
||||
return fs.stat(dirname, "type") == "dir"
|
||||
end
|
||||
|
||||
--- Read the whole content of the given file into memory.
|
||||
-- @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
|
||||
-- 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
|
||||
-- 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
|
||||
-- @param dest Destination
|
||||
-- @return Boolean containing true on success or nil on error
|
||||
copy = fs.datacopy
|
||||
|
||||
--- Renames a file.
|
||||
-- @param source Source file
|
||||
-- @param dest Destination
|
||||
-- @return Boolean containing true on success or nil on error
|
||||
rename = fs.move
|
||||
|
||||
--- Get the last modification time of given file path in Unix epoch format.
|
||||
-- @param path String containing the path of the file or directory to read
|
||||
-- @return Number containing the epoch time or nil on error
|
||||
-- @return String containing the error description (if any)
|
||||
-- @return Number containing the os specific errno (if any)
|
||||
function mtime(path)
|
||||
return fs.stat(path, "mtime")
|
||||
end
|
||||
|
||||
--- Set the last modification time of given file path in Unix epoch format.
|
||||
-- @param path String containing the path of the file or directory to read
|
||||
-- @param mtime Last modification timestamp
|
||||
-- @param atime Last accessed timestamp
|
||||
-- @return 0 in case of success nil on error
|
||||
-- @return String containing the error description (if any)
|
||||
-- @return Number containing the os specific errno (if any)
|
||||
function utime(path, mtime, atime)
|
||||
return fs.utimes(path, atime, mtime)
|
||||
end
|
||||
|
||||
--- Return the last element - usually the filename - from the given path with
|
||||
-- the directory component stripped.
|
||||
-- @class function
|
||||
-- @name basename
|
||||
-- @param path String containing the path to strip
|
||||
-- @return String containing the base name of given path
|
||||
-- @see dirname
|
||||
basename = fs.basename
|
||||
|
||||
--- Return the directory component of the given path with the last element
|
||||
-- stripped of.
|
||||
-- @class function
|
||||
-- @name dirname
|
||||
-- @param path String containing the path to strip
|
||||
-- @return String containing the directory component of given path
|
||||
-- @see basename
|
||||
dirname = fs.dirname
|
||||
|
||||
--- Return a table containing all entries of the specified directory.
|
||||
-- @class function
|
||||
-- @name dir
|
||||
-- @param path String containing the path of the directory to scan
|
||||
-- @return Table containing file and directory entries or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
function dir(...)
|
||||
local iter, code, msg = fs.dir(...)
|
||||
if iter then
|
||||
local t = nutil.consume(iter)
|
||||
t[#t+1] = "."
|
||||
t[#t+1] = ".."
|
||||
return t
|
||||
else
|
||||
return nil, code, msg
|
||||
end
|
||||
end
|
||||
|
||||
--- Create a new directory, recursively on demand.
|
||||
-- @param path String with the name or path of the directory to create
|
||||
-- @param recursive Create multiple directory levels (optional, default is true)
|
||||
-- @return Number with the return code, 0 on sucess or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
function mkdir(path, recursive)
|
||||
return recursive and fs.mkdirr(path) or fs.mkdir(path)
|
||||
end
|
||||
|
||||
--- Remove the given empty directory.
|
||||
-- @class function
|
||||
-- @name rmdir
|
||||
-- @param path String containing the path of the directory to remove
|
||||
-- @return Number with the return code, 0 on sucess or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
rmdir = fs.rmdir
|
||||
|
||||
local stat_tr = {
|
||||
reg = "regular",
|
||||
dir = "directory",
|
||||
lnk = "link",
|
||||
chr = "character device",
|
||||
blk = "block device",
|
||||
fifo = "fifo",
|
||||
sock = "socket"
|
||||
}
|
||||
--- Get information about given file or directory.
|
||||
-- @class function
|
||||
-- @name stat
|
||||
-- @param path String containing the path of the directory to query
|
||||
-- @return Table containing file or directory properties or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
function stat(path, key)
|
||||
local data, code, msg = fs.stat(path)
|
||||
if data then
|
||||
data.mode = data.modestr
|
||||
data.type = stat_tr[data.type] or "?"
|
||||
end
|
||||
return key and data and data[key] or data, code, msg
|
||||
end
|
||||
|
||||
--- Set permissions on given file or directory.
|
||||
-- @class function
|
||||
-- @name chmod
|
||||
-- @param path String containing the path of the directory
|
||||
-- @param perm String containing the permissions to set ([ugoa][+-][rwx])
|
||||
-- @return Number with the return code, 0 on sucess or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
chmod = fs.chmod
|
||||
|
||||
--- Create a hard- or symlink from given file (or directory) to specified target
|
||||
-- file (or directory) path.
|
||||
-- @class function
|
||||
-- @name link
|
||||
-- @param path1 String containing the source path to link
|
||||
-- @param path2 String containing the destination path for the link
|
||||
-- @param symlink Boolean indicating wheather to create a symlink (optional)
|
||||
-- @return Number with the return code, 0 on sucess or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
function link(src, dest, sym)
|
||||
return sym and fs.symlink(src, dest) or fs.link(src, dest)
|
||||
end
|
||||
|
||||
--- Remove the given file.
|
||||
-- @class function
|
||||
-- @name unlink
|
||||
-- @param path String containing the path of the file to remove
|
||||
-- @return Number with the return code, 0 on sucess or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
unlink = fs.unlink
|
||||
|
||||
--- Retrieve target of given symlink.
|
||||
-- @class function
|
||||
-- @name readlink
|
||||
-- @param path String containing the path of the symlink to read
|
||||
-- @return String containing the link target or nil on error
|
||||
-- @return String containing the error description on error
|
||||
-- @return Number containing the os specific errno on error
|
||||
readlink = fs.readlink
|
||||
|
||||
function filename(str)
|
||||
if not str then
|
||||
return nil
|
||||
end
|
||||
local idx = str:match(".+()%.%w+$")
|
||||
if idx then
|
||||
return str:sub(1, idx-1)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
function filesize(e)
|
||||
local t=0
|
||||
local a={' KB',' MB',' GB',' TB',' PB'}
|
||||
if e < 0 then
|
||||
e = -e
|
||||
end
|
||||
repeat
|
||||
e=e/1024
|
||||
t=t+1
|
||||
until(e<=1024)
|
||||
return string.format("%.1f",e)..a[t] or "0.0 KB"
|
||||
end
|
||||
|
||||
function lanip()
|
||||
local lan_int_name = uci:get("openclash", "@overwrite[0]", "lan_interface_name") or uci:get("openclash", "config", "lan_interface_name") or "0"
|
||||
local lan_ip
|
||||
if lan_int_name == "0" then
|
||||
lan_ip = SYS.exec("uci -q get network.lan.ipaddr 2>/dev/null |awk -F '/' '{print $1}' 2>/dev/null |tr -d '\n'")
|
||||
else
|
||||
lan_ip = SYS.exec(string.format("ip address show %s 2>/dev/null | grep -w 'inet' 2>/dev/null | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -1 | tr -d '\n'", lan_int_name))
|
||||
end
|
||||
if not lan_ip or lan_ip == "" then
|
||||
lan_ip = SYS.exec("ip address show $(uci -q -p /tmp/state get network.lan.ifname || 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 | tr -d '\n'")
|
||||
end
|
||||
if not lan_ip or lan_ip == "" then
|
||||
lan_ip = SYS.exec("ip addr show 2>/dev/null | grep -w 'inet' | grep 'global' | grep 'brd' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -n 1 | tr -d '\n'")
|
||||
end
|
||||
return lan_ip
|
||||
end
|
||||
|
||||
function find_case_insensitive_path(path)
|
||||
local dir = dirname(path)
|
||||
local base = basename(path)
|
||||
local files = dir and fs.dir(dir)
|
||||
if not files then
|
||||
return nil
|
||||
end
|
||||
|
||||
for f in files do
|
||||
if f:lower() == base:lower() then
|
||||
return dir .. "/" .. f
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function get_resourse_mtime(path)
|
||||
local real_path = path
|
||||
if not fs.access(path) then
|
||||
local found = find_case_insensitive_path(path)
|
||||
if found then
|
||||
real_path = found
|
||||
else
|
||||
return "File Not Exist"
|
||||
end
|
||||
end
|
||||
local file = fs.readlink(real_path) or real_path
|
||||
local resourse_etag_version = SYS.exec(string.format("source /usr/share/openclash/openclash_etag.sh && GET_ETAG_TIMESTAMP_BY_PATH '%s'", real_path))
|
||||
if resourse_etag_version and resourse_etag_version ~= "" then
|
||||
return resourse_etag_version
|
||||
end
|
||||
local resourse_version = os.date("%Y-%m-%d %H:%M:%S", mtime(real_path))
|
||||
if resourse_version and resourse_version ~= "" then
|
||||
return resourse_version
|
||||
end
|
||||
return "Unknown"
|
||||
end
|
||||
|
||||
function uci_get_config(section, key)
|
||||
local val
|
||||
if section == "config" then
|
||||
val = uci:get("openclash", "@overwrite[0]", key)
|
||||
end
|
||||
if val == nil then
|
||||
val = uci:get("openclash", section, key)
|
||||
end
|
||||
return val
|
||||
end
|
||||
|
||||
function get_file_path_from_request()
|
||||
local file_path
|
||||
local referer = HTTP.getenv("HTTP_REFERER")
|
||||
if referer then
|
||||
local _, _, file_value = referer:find("file=([^&]*)$")
|
||||
if file_value and file_value ~= "" then
|
||||
file_path = HTTP.urldecode(file_value)
|
||||
end
|
||||
end
|
||||
|
||||
if not file_path or file_path == "/" then
|
||||
file_path = HTTP.formvalue("file")
|
||||
if not file_path then
|
||||
file_path = HTTP.urldecode(file_path)
|
||||
end
|
||||
end
|
||||
|
||||
return file_path
|
||||
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
|
||||
18
luci-app-openclash/luasrc/view/openclash/cfg_check.htm
Normal file
@ -0,0 +1,18 @@
|
||||
<%+cbi/valueheader%>
|
||||
<div>
|
||||
<%
|
||||
local val = self:cfgvalue(section)
|
||||
if val == translate("Enable") or val == translate("Enabled") or val == translate("Config Normal") or val == translate("Exist") then
|
||||
%>
|
||||
<div style="color: green; font-weight:bold;">
|
||||
<%
|
||||
else
|
||||
%>
|
||||
<div style="color: red; font-weight:bold;">
|
||||
<%
|
||||
end
|
||||
write(pcdata(val))
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
<%+cbi/valuefooter%>
|
||||
2889
luci-app-openclash/luasrc/view/openclash/config_edit.htm
Normal file
457
luci-app-openclash/luasrc/view/openclash/config_editor.htm
Normal file
@ -0,0 +1,457 @@
|
||||
<%
|
||||
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()
|
||||
local fs = require "luci.openclash"
|
||||
local conf = fs.uci_get_config("config", "config_path")
|
||||
if not conf then conf = "/etc/openclash/config/config.yaml" end
|
||||
local conf_name = fs.basename(conf)
|
||||
if not conf_name then conf_name = "config.yaml" end
|
||||
local sconf = "/etc/openclash/"..conf_name
|
||||
local path_info = os.getenv("PATH_INFO") or ""
|
||||
local sid = path_info:match("(cfg[0-9a-f]+)") or ""
|
||||
-%>
|
||||
|
||||
<table id="my_editor_div" style="width: 100%; text-align: center; display: none;">
|
||||
<tr>
|
||||
<td style="width: 50%; overflow: hidden;"><%:Modify Your Config file:%> <b style=color:green><%=conf_name%></b> <%:Here, Except The Settings That Were Taken Over%></td>
|
||||
|
||||
<%-
|
||||
if fs.isfile(sconf) then
|
||||
-%>
|
||||
<td style="width: 50%; overflow: hidden;"><%:Config File Edited By OpenClash For Running%></td>
|
||||
<%-
|
||||
else
|
||||
-%>
|
||||
<td style="width: 50%; overflow: hidden;"><%:Default Config File With Correct Template%></td>
|
||||
<%-
|
||||
end
|
||||
-%>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script src="/luci-static/resources/openclash/js/common.js"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var levelTranslations = {
|
||||
'info': '<%:Info%>',
|
||||
'warning': '<%:Warning%>',
|
||||
'error': '<%:Error%>',
|
||||
'debug': '<%:Debug%>',
|
||||
'tip': '<%:Tip%>',
|
||||
'watchdog': '<%:Watchdog%>',
|
||||
'fatal': '<%:Fatal%>'
|
||||
};
|
||||
|
||||
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;
|
||||
descr = document.getElementById("my_editor_div");
|
||||
target.parentNode.parentNode.parentNode.insertBefore(descr, target.parentNode.parentNode);
|
||||
descr.style.display="inline-table"
|
||||
value = id.value;
|
||||
if (value == null) {
|
||||
editor(id, readOnly, wid, height);
|
||||
editor(id2, readOnly2, wid, height);
|
||||
}
|
||||
else {
|
||||
target2.parentNode.style.display="none";
|
||||
orig1 = '';
|
||||
orig2 = id2.value;
|
||||
target.style.height= "700px"
|
||||
id.style.display = "none";
|
||||
id2.style.display = "none";
|
||||
merge_editor = CodeMirror.MergeView(target, {
|
||||
mode: "text/yaml",
|
||||
value: value,
|
||||
origLeft: panes == 3 ? orig1 : null,
|
||||
orig: orig2,
|
||||
theme: "material",
|
||||
lineNumbers: true,
|
||||
autoRefresh: true,
|
||||
styleActiveLine: true,
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
foldGutter: true,
|
||||
lint: true,
|
||||
highlightDifferences: highlight,
|
||||
connect: connect,
|
||||
collapseIdentical: collapse,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
|
||||
extraKeys: {
|
||||
"F10": function() {
|
||||
merge_editor.setShowDifferences(highlight = !highlight);
|
||||
},
|
||||
"F11": function(cm) {
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
gap = document.getElementsByClassName("CodeMirror-merge-gap")[0];
|
||||
vscrollbar = document.getElementsByClassName("CodeMirror-vscrollbar")[0];
|
||||
vscrollbar_oc = document.getElementsByClassName("CodeMirror-vscrollbar-oc")[0];
|
||||
if (cm.getOption("fullScreen")) {
|
||||
console.log(gap.style.display)
|
||||
gap.style.display = "none";
|
||||
vscrollbar.className = "CodeMirror-vscrollbar-oc";
|
||||
}
|
||||
else {
|
||||
gap.style.display = "";
|
||||
vscrollbar_oc.className = "CodeMirror-vscrollbar";
|
||||
};
|
||||
},
|
||||
"Esc": function(cm) {
|
||||
gap = document.getElementsByClassName("CodeMirror-merge-gap")[0];
|
||||
vscrollbar = document.getElementsByClassName("CodeMirror-vscrollbar")[0];
|
||||
vscrollbar_oc = document.getElementsByClassName("CodeMirror-vscrollbar-oc")[0];
|
||||
if (cm.getOption("fullScreen")) {
|
||||
cm.setOption("fullScreen", false);
|
||||
gap.style.display = "";
|
||||
vscrollbar_oc.className = "CodeMirror-vscrollbar";
|
||||
};
|
||||
},
|
||||
"Tab": function(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add')
|
||||
} else {
|
||||
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ")
|
||||
cm.replaceSelection(spaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
merge_editor.edit.on("change", function(cm) {
|
||||
id.value = cm.getValue();
|
||||
});
|
||||
|
||||
if (wid && height) {
|
||||
merge_editor.editor().setSize(wid, height);
|
||||
merge_editor.rightOriginal().setSize(wid, height);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function editor(id, readOnly, wid, height)
|
||||
{
|
||||
var editor = CodeMirror.fromTextArea(id, {
|
||||
mode: "text/yaml",
|
||||
autoRefresh: true,
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
theme: "material",
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
foldGutter: true,
|
||||
lint: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
|
||||
extraKeys: {
|
||||
"F11": function(cm) {
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
"Esc": function(cm) {
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
},
|
||||
"Tab": function(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add')
|
||||
} else {
|
||||
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ")
|
||||
cm.replaceSelection(spaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (readOnly) {
|
||||
editor.setOption("readOnly","true");
|
||||
};
|
||||
|
||||
if (wid && height) {
|
||||
editor.setSize(wid, height);
|
||||
};
|
||||
};
|
||||
|
||||
function shell_editor(id, readOnly, wid, height)
|
||||
{
|
||||
var editor = CodeMirror.fromTextArea(id, {
|
||||
mode: "shell",
|
||||
autoRefresh: true,
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
theme: "material",
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
extraKeys: {
|
||||
"F11": function(cm) {
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
"Esc": function(cm) {
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
},
|
||||
"Tab": function(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add')
|
||||
} else {
|
||||
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ")
|
||||
cm.replaceSelection(spaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (wid && height) {
|
||||
editor.setSize(wid, height);
|
||||
};
|
||||
};
|
||||
|
||||
function other_editor(id, readOnly, wid, height)
|
||||
{
|
||||
var editor = CodeMirror.fromTextArea(id, {
|
||||
mode: "text/x-properties",
|
||||
autoRefresh: true,
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
theme: "material",
|
||||
lineWrapping: true,
|
||||
matchBrackets: true,
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
extraKeys: {
|
||||
"F11": function(cm) {
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
"Esc": function(cm) {
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
},
|
||||
"Tab": function(cm) {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add')
|
||||
} else {
|
||||
var spaces = Array(cm.getOption("indentUnit") + 1).join(" ")
|
||||
cm.replaceSelection(spaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (wid && height) {
|
||||
editor.setSize(wid, height);
|
||||
};
|
||||
};
|
||||
|
||||
function log_editor(id, name, readOnly, wid, height)
|
||||
{
|
||||
window['editor_' + name] = CodeMirror.fromTextArea(id, {
|
||||
mode: "log",
|
||||
autoRefresh: true,
|
||||
styleActiveLine: true,
|
||||
lineNumbers: true,
|
||||
theme: "log",
|
||||
lineWrapping: true,
|
||||
matchBrackets: false,
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
extraKeys: {
|
||||
"F11": function(cm) {
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
"Esc": function(cm) {
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (readOnly) {
|
||||
window['editor_' + name].setOption("readOnly","true");
|
||||
};
|
||||
|
||||
if (wid && height) {
|
||||
window['editor_' + name].setSize(wid, height);
|
||||
};
|
||||
|
||||
if (isDarkBackground(document.body)) {
|
||||
document.documentElement.setAttribute('data-darkmode', 'true');
|
||||
};
|
||||
};
|
||||
|
||||
_cmWhenReady(function() {
|
||||
var core_log = document.getElementById("core_log");
|
||||
var oc_log = document.getElementById("cbid.openclash.config.clog");
|
||||
if (core_log && oc_log) {
|
||||
log_editor(oc_log, 'oc', true, '100%', '540px');
|
||||
log_editor(core_log, 'core', true, '100%', '540px');
|
||||
};
|
||||
|
||||
var custom_cfg_value = document.getElementById("cbid.openclash.<%=sid%>.other_parameters");
|
||||
if (custom_cfg_value) {
|
||||
editor(custom_cfg_value, false, '100%', '300px');
|
||||
};
|
||||
|
||||
var custom_firewall = document.getElementById("cbid.openclash.config.firewall_custom");
|
||||
if (custom_firewall) {
|
||||
shell_editor(custom_firewall, false, "100%", '300px');
|
||||
};
|
||||
|
||||
var myEditor_use = document.getElementById("cbid.table.1.user");
|
||||
var myEditor_def = document.getElementById("cbid.table.1.default");
|
||||
|
||||
if (myEditor_use && myEditor_def) {
|
||||
var myEditor_div_use = document.getElementById("cbi-table-1-user");
|
||||
var myEditor_div_def = document.getElementById("cbi-table-1-default");
|
||||
|
||||
myEditor_div_def.parentNode.parentNode.style.cssText="text-align: left !important"
|
||||
myEditor_div_def.parentNode.className='cbi-value';
|
||||
myEditor_div_def.parentNode.style.display="revert"
|
||||
myEditor_div_def.parentNode.style.width="100%"
|
||||
|
||||
myEditor_div_use.parentNode.className='cbi-value';
|
||||
myEditor_div_use.parentNode.style.display="revert"
|
||||
myEditor_div_use.parentNode.style.width="100%"
|
||||
merge_editor(myEditor_use, myEditor_def, myEditor_div_use, myEditor_div_def, true, false, 'auto', '700px');
|
||||
};
|
||||
|
||||
var myEditor_hosts = document.getElementById("cbid.openclash.config.custom_hosts");
|
||||
var myEditor_fall_fil = document.getElementById("cbid.openclash.config.custom_fallback_fil");
|
||||
var myEditor_name_pol = document.getElementById("cbid.openclash.config.custom_domain_dns_core");
|
||||
var myEditor_name_cus_r1 = document.getElementById("cbid.openclash.config.custom_rules_2");
|
||||
var myEditor_name_cus_r2 = document.getElementById("cbid.openclash.config.custom_rules");
|
||||
var myEditor_fake_filter = document.getElementById("cbid.openclash.config.custom_fake_filter");
|
||||
var myEditor_custom_domain_dns = document.getElementById("cbid.openclash.config.custom_domain_dns");
|
||||
var myEditor_edit_file = document.getElementById("cbid.openclash.config.edit_file");
|
||||
var myEditor_custom_sniffer = document.getElementById("cbid.openclash.config.sniffer_custom");
|
||||
var myEditor_proxy_name_pol = document.getElementById("cbid.openclash.config.custom_proxy_server_dns_policy");
|
||||
|
||||
if (myEditor_hosts) {
|
||||
editor(myEditor_hosts, false, '100%', '300px');
|
||||
};
|
||||
|
||||
if (myEditor_edit_file) {
|
||||
editor(myEditor_edit_file, false, '100%', '700px');
|
||||
};
|
||||
|
||||
if (myEditor_fall_fil) {
|
||||
editor(myEditor_fall_fil, false, '100%', '300px');
|
||||
};
|
||||
|
||||
if (myEditor_name_pol) {
|
||||
editor(myEditor_name_pol, false, '100%', '300px');
|
||||
};
|
||||
|
||||
if (myEditor_proxy_name_pol) {
|
||||
editor(myEditor_proxy_name_pol, false, '100%', '300px');
|
||||
};
|
||||
|
||||
if (myEditor_name_cus_r1) {
|
||||
editor(myEditor_name_cus_r1, false, '100%', '300px');
|
||||
editor(myEditor_name_cus_r2, false, '100%', '300px');
|
||||
};
|
||||
|
||||
if (myEditor_fake_filter) {
|
||||
other_editor(myEditor_fake_filter, false, '100%', '300px');
|
||||
};
|
||||
|
||||
if (myEditor_custom_domain_dns) {
|
||||
other_editor(myEditor_custom_domain_dns, false, '100%', '300px');
|
||||
};
|
||||
|
||||
if (myEditor_custom_sniffer) {
|
||||
other_editor(myEditor_custom_sniffer, false, '100%', '300px');
|
||||
};
|
||||
|
||||
var proxy_mg = document.getElementById('cbi-table-1-proxy_mg');
|
||||
var rule_mg = document.getElementById('cbi-table-1-rule_mg');
|
||||
var Commit = document.getElementById('cbi-table-1-Commit');
|
||||
var Apply = document.getElementById('cbi-table-1-Apply');
|
||||
var Create = document.getElementById('cbi-table-1-Create');
|
||||
|
||||
if (proxy_mg) {
|
||||
proxy_mg.style.textAlign="center";
|
||||
rule_mg.style.textAlign="center";
|
||||
Commit.style.textAlign="center";
|
||||
Apply.style.textAlign="center";
|
||||
Create.style.textAlign="center";
|
||||
};
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
1377
luci-app-openclash/luasrc/view/openclash/config_upload.htm
Normal file
203
luci-app-openclash/luasrc/view/openclash/debug.htm
Normal file
@ -0,0 +1,203 @@
|
||||
<%#
|
||||
Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%
|
||||
local diag_host = "www.instagram.com"
|
||||
local dns_host = "www.instagram.com"
|
||||
%>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function show_diag_info(addr)
|
||||
{
|
||||
var addr = addr;
|
||||
var legend = document.getElementById('diag-rc-legend');
|
||||
var output = document.getElementById('diag-rc-output');
|
||||
|
||||
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 != "")
|
||||
{
|
||||
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)
|
||||
{
|
||||
output.innerHTML = '<span class="error"><%:Bad address specified!%></span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
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>';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function update_status(field)
|
||||
{
|
||||
var addr = field.value;
|
||||
var legend = document.getElementById('diag-rc-legend');
|
||||
var output = document.getElementById('diag-rc-output');
|
||||
|
||||
if (legend && output)
|
||||
{
|
||||
output.innerHTML =
|
||||
'<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 = 'block';
|
||||
}
|
||||
|
||||
let HTTP = {
|
||||
checker: (domain) => {
|
||||
let img = new Image;
|
||||
let timeout = setTimeout(() => {
|
||||
img.onerror = img.onload = null;
|
||||
show_diag_info(addr);
|
||||
}, 10000);
|
||||
|
||||
img.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
show_diag_info(addr);
|
||||
}
|
||||
|
||||
img.onload = () => {
|
||||
clearTimeout(timeout);
|
||||
show_diag_info(addr);
|
||||
}
|
||||
|
||||
img.src = `https://${domain}/favicon.ico?${+(new Date)}`
|
||||
},
|
||||
runcheck: () => {
|
||||
HTTP.checker(addr);
|
||||
}
|
||||
};
|
||||
HTTP.runcheck();
|
||||
}
|
||||
|
||||
function diag_dns(field)
|
||||
{
|
||||
var addr = field.value;
|
||||
var legend = document.getElementById('dns-rc-legend');
|
||||
var output = document.getElementById('dns-rc-output');
|
||||
|
||||
if (legend && output)
|
||||
{
|
||||
output.innerHTML =
|
||||
'<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 = '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 != "")
|
||||
{
|
||||
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)
|
||||
{
|
||||
output.innerHTML = '<span class="error"><%:Bad address specified!%></span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
output.innerHTML = '<span class="error"><%:No Response Found!%></span>';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function gen_debug_logs()
|
||||
{
|
||||
var legend = document.getElementById('debug-rc-legend');
|
||||
var output = document.getElementById('debug-rc-output');
|
||||
|
||||
if (legend && output)
|
||||
{
|
||||
output.innerHTML =
|
||||
'<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 = '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 != "")
|
||||
{
|
||||
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
|
||||
{
|
||||
output.innerHTML = '<span class="error"><%:Some error occurred!%></span>';
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
//]]></script>
|
||||
|
||||
<form>
|
||||
<fieldset class="diag-style">
|
||||
<div style="width:33%; float: left; text-align: center; white-space: nowrap;">
|
||||
<%:Connection Test%>
|
||||
<input type="text" value="<%=diag_host%>" name="diag" />
|
||||
<input type="button" value="<%:Click to Test%>" class="btn cbi-button cbi-button-apply" onclick="update_status(this.form.diag)" />
|
||||
</div>
|
||||
<div style="width:33%; float: left; text-align: center; white-space: nowrap;">
|
||||
<%:DNS Test%>
|
||||
<input type="text" value="<%=dns_host%>" name="dns_host" />
|
||||
<input type="button" value="<%:Click to Test%>" class="btn cbi-button cbi-button-apply" onclick="diag_dns(this.form.dns_host)" />
|
||||
</div>
|
||||
<div style="width:33%; float: left; text-align: center; white-space: nowrap;">
|
||||
<%:Generate Logs%>
|
||||
<input type="button" value="<%:Click to Generate%>" class="btn cbi-button cbi-button-apply" onclick="gen_debug_logs(this)" />
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="diag-style" style="display:none">
|
||||
<div id="diag-rc-legend" class="diag-legend"><%:Collecting data...%></div>
|
||||
<span id="diag-rc-output"></span>
|
||||
</div>
|
||||
|
||||
<div class="diag-style" style="display:none">
|
||||
<div id="dns-rc-legend" class="diag-legend"><%:Collecting data...%></div>
|
||||
<span id="dns-rc-output"></span>
|
||||
</div>
|
||||
|
||||
<div class="diag-style" style="display:none">
|
||||
<div id="debug-rc-legend" class="diag-legend"><%:Collecting data...%></div>
|
||||
<span id="debug-rc-output"></span>
|
||||
</div>
|
||||
</form>
|
||||
132
luci-app-openclash/luasrc/view/openclash/developer.htm
Normal file
13
luci-app-openclash/luasrc/view/openclash/dvalue.htm
Normal file
@ -0,0 +1,13 @@
|
||||
<%+cbi/valueheader%>
|
||||
<div style="text-align: center; margin:2% auto; display: block; width: 100%; text-overflow: ellipsis;">
|
||||
<div>
|
||||
<%:Note: Please Upload File According To File Type, File Will Be Saved To The Prompt Path%>
|
||||
</div>
|
||||
<div style="color: green; margin-top: 1%;">
|
||||
<%
|
||||
local val = self:cfgvalue(section) or self.default or ""
|
||||
write(pcdata(val))
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
<%+cbi/valuefooter%>
|
||||
37
luci-app-openclash/luasrc/view/openclash/flush_dns_cache.htm
Normal file
@ -0,0 +1,37 @@
|
||||
<%+cbi/valueheader%>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
function act_flush_dns_cache(btn)
|
||||
{
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Flushing...%> ';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash","flush_dns_cache")%>',
|
||||
null,
|
||||
function(x,status)
|
||||
{
|
||||
if ( x && x.status == 200 ) {
|
||||
if(status.flush_status == "0" || status.flush_status != "")
|
||||
{
|
||||
btn.value = '<%:Flush Failed%>';
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
btn.value = '<%:Flush Successful%>';
|
||||
}
|
||||
}
|
||||
else {
|
||||
btn.value = '<%:Flush Timeout%>';
|
||||
}
|
||||
}
|
||||
);
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
};
|
||||
|
||||
//]]></script>
|
||||
|
||||
|
||||
<input type="button" class="btn cbi-button cbi-input-reload" value="<%:Flush%>" onclick="return act_flush_dns_cache(this)" />
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,37 @@
|
||||
<%+cbi/valueheader%>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
function act_flush_smart_cache(btn)
|
||||
{
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Flushing...%> ';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash","flush_smart_cache")%>',
|
||||
null,
|
||||
function(x,status)
|
||||
{
|
||||
if ( x && x.status == 200 ) {
|
||||
if(status.flush_status == "0" || status.flush_status != "")
|
||||
{
|
||||
btn.value = '<%:Flush Failed%>';
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
btn.value = '<%:Flush Successful%>';
|
||||
}
|
||||
}
|
||||
else {
|
||||
btn.value = '<%:Flush Timeout%>';
|
||||
}
|
||||
}
|
||||
);
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
};
|
||||
|
||||
//]]></script>
|
||||
|
||||
|
||||
<input type="button" class="btn cbi-button cbi-input-reload" value="<%:Flush%>" onclick="return act_flush_smart_cache(this)" />
|
||||
<%+cbi/valuefooter%>
|
||||
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%>
|
||||
19
luci-app-openclash/luasrc/view/openclash/input_file_name.htm
Normal file
@ -0,0 +1,19 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function import_file_name(btn, option, file_path) {
|
||||
var file_name = prompt("<%:Paste the name of the new file (including the extension) here:%>", "");
|
||||
if (file_name == null || file_name == "") {
|
||||
return false;
|
||||
}
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "create_file")%>', {filename: file_name, filepath: file_path}, function(x, status) {
|
||||
if (x && x.status == 200)
|
||||
{
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Create File%>" onclick="return import_file_name(this, '<%=self.option%>', '<%=self.value%>')" />
|
||||
<%+cbi/valuefooter%>
|
||||
23
luci-app-openclash/luasrc/view/openclash/input_rename.htm
Normal file
@ -0,0 +1,23 @@
|
||||
<%+cbi/valueheader%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function import_new_file_name(btn, file_path, file_name) {
|
||||
var new_file_name = prompt("<%:Paste the new file name (including the extension) here:%>", "");
|
||||
if (new_file_name == null || new_file_name == "") {
|
||||
return false;
|
||||
}
|
||||
if (file_path == "/etc/openclash/config/" && new_file_name.split('.').pop() != "yaml" && new_file_name.split('.').pop() != "yml") {
|
||||
alert('<%:Illegal extension!%>')
|
||||
return false;
|
||||
}
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "rename_file")%>', {new_file_name: new_file_name, file_path: file_path, file_name: file_name}, function(x, status) {
|
||||
if (x && x.status == 200)
|
||||
{
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
<input type="button" class="btn cbi-button cbi-input-apply" value="<%:Rename%>" onclick="return import_new_file_name(this, '<%=self.option%>', '<%=self.value%>')" />
|
||||
<%+cbi/valuefooter%>
|
||||
693
luci-app-openclash/luasrc/view/openclash/log.htm
Normal file
@ -0,0 +1,693 @@
|
||||
<%+cbi/valueheader%>
|
||||
<body>
|
||||
<div id="tab" class="cbi-section">
|
||||
<div id="tab-header">
|
||||
<ul class="cbi-tabmenu">
|
||||
<li name="tab-header" class="cbi-tab"><a href="#"><%:OpenClash Log%></a></li>
|
||||
<li name="tab-header" class="cbi-tab-disabled"><a href="#"><%:Core Log%></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="tab-content">
|
||||
<div class="dom" style="display: block;">
|
||||
<textarea id="cbid.openclash.config.clog" class="cbi-input-textarea" style="width: 100%;display:inline" data-update="change" rows="32" cols="60" readonly="readonly" ></textarea>
|
||||
</div>
|
||||
<div class="dom">
|
||||
<textarea id="core_log" class="cbi-input-textarea" style="width: 100%;display:inline" data-update="change" rows="32" cols="60" readonly="readonly" ></textarea>
|
||||
<div class="radio-button">
|
||||
<input type="radio" id="info" name="radios" value="info" checked onclick="return switch_log_level(this.value)"/>
|
||||
<label for="info"><%:Info%></label>
|
||||
<input type="radio" id="warning" name="radios" value="warning" onclick="return switch_log_level(this.value)"/>
|
||||
<label for="warning"><%:Warning%></label>
|
||||
<input type="radio" id="error" name="radios" value="error" onclick="return switch_log_level(this.value)"/>
|
||||
<label for="error"><%:Error%></label>
|
||||
<input type="radio" id="debug" name="radios" value="debug" onclick="return switch_log_level(this.value)"/>
|
||||
<label for="debug"><%:Debug%></label>
|
||||
<input type="radio" id="silent" name="radios" value="silent" onclick="return switch_log_level(this.value)"/>
|
||||
<label for="silent"><%:Silent%></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset style="text-align: center; width: 100%" class="cbi-section">
|
||||
<div class="btn-group">
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" id="stop_refresh_button" value="<%:Stop Refresh%>" onclick=" return stop_refresh() "/>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" id="start_refresh_button" value="<%:Start Refresh%>" onclick=" return start_refresh() "/>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" id="del_log_button" value="<%:Clean%>" onclick=" return del_log() " />
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" id="down_log_button" value="<%:Download Log%>" onclick=" return download_log() " />
|
||||
</div>
|
||||
</fieldset>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var r;
|
||||
var s;
|
||||
var log_len = 0;
|
||||
var lv = document.getElementById('cbid.openclash.config.clog');
|
||||
var cl = document.getElementById('core_log');
|
||||
var animatingOC = false;
|
||||
var animatingCore = false;
|
||||
var coreWebSocket = null;
|
||||
var coreLogBuffer = [];
|
||||
var currentLogLevel = 'info';
|
||||
var wsRetryDelay = 2000;
|
||||
var wsRetryTimer = null;
|
||||
var isPolling = false;
|
||||
var core_refresh = true;
|
||||
|
||||
function setLogTab(log_level) {
|
||||
var radio = document.getElementsByName("radios");
|
||||
for (i=0; i<radio.length; i++) {
|
||||
if (radio[i].value == log_level && ! radio[i].checked) {
|
||||
radio[i].checked = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function get_log_level() {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "log_level")%>', null, function(x, status) {
|
||||
if (x && x.status == 200 && status.log_level != "") {
|
||||
currentLogLevel = status.log_level;
|
||||
setLogTab(currentLogLevel);
|
||||
coreLogWebSocket();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function switch_log_level(value)
|
||||
{
|
||||
if (!coreWebSocket || coreWebSocket.readyState !== WebSocket.OPEN) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_log")%>', {log_level: value}, function(x, status) {
|
||||
if (!x || x.status != 200) {
|
||||
alert(' <%:Log Level%>: ' + value + ' <%:switching failed!%>');
|
||||
return
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
currentLogLevel = value;
|
||||
setLogTab(currentLogLevel);
|
||||
coreLogWebSocket();
|
||||
};
|
||||
|
||||
function stop_refresh() {
|
||||
clearTimeout(r);
|
||||
coreLogWebSocketStop();
|
||||
return
|
||||
};
|
||||
|
||||
function start_refresh() {
|
||||
clearTimeout(r);
|
||||
coreLogWebSocket();
|
||||
r=setTimeout("poll_log()",1000);
|
||||
return
|
||||
};
|
||||
|
||||
function createAndDownloadFile(fileName, content) {
|
||||
var aTag = document.createElement('a');
|
||||
var blob = new Blob([content]);
|
||||
aTag.download = fileName;
|
||||
aTag.href = URL.createObjectURL(blob);
|
||||
aTag.click();
|
||||
URL.revokeObjectURL(blob);
|
||||
};
|
||||
|
||||
function download_log(){
|
||||
var dt = new Date();
|
||||
var timestamp = dt.getFullYear()+"-"+(dt.getMonth()+1)+"-"+dt.getDate()+"-"+dt.getHours()+"-"+dt.getMinutes()+"-"+dt.getSeconds();
|
||||
|
||||
var oc_content = "";
|
||||
if (typeof editor_oc !== 'undefined' && editor_oc) {
|
||||
oc_content = editor_oc.getValue();
|
||||
} else if (lv && lv.value) {
|
||||
oc_content = lv.value;
|
||||
} else if (lv && lv.innerHTML) {
|
||||
oc_content = lv.innerHTML;
|
||||
}
|
||||
|
||||
var core_content = "";
|
||||
if (typeof editor_core !== 'undefined' && editor_core) {
|
||||
core_content = editor_core.getValue();
|
||||
} else if (cl && cl.value) {
|
||||
core_content = cl.value;
|
||||
} else if (cl && cl.innerHTML) {
|
||||
core_content = cl.innerHTML;
|
||||
}
|
||||
|
||||
oc_content = oc_content.split('\n').filter(function(line) {
|
||||
return line.indexOf("】<%:Downloading URL%>【") === -1;
|
||||
}).join('\n');
|
||||
|
||||
if (!oc_content.trim() && !core_content.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var downloadContent = "";
|
||||
if (oc_content.trim()) {
|
||||
downloadContent += "<%:OpenClash Log%>:\n" + oc_content + "\n\n";
|
||||
}
|
||||
if (core_content.trim()) {
|
||||
downloadContent += "<%:Core Log%>:\n" + core_content;
|
||||
}
|
||||
|
||||
createAndDownloadFile("OpenClash-"+timestamp+".log", downloadContent);
|
||||
return;
|
||||
};
|
||||
|
||||
function del_log() {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "del_log")%>',null,function(x, data){
|
||||
lv.innerHTML="";
|
||||
cl.innerHTML="";
|
||||
coreLogBuffer = [];
|
||||
log_len = 0;
|
||||
editor_oc.setValue(lv.value);
|
||||
editor_core.setValue(cl.value);
|
||||
editor_core.refresh();
|
||||
editor_oc.refresh();
|
||||
});
|
||||
return
|
||||
};
|
||||
|
||||
function p(s) {
|
||||
return s < 10 ? '0' + s: s;
|
||||
};
|
||||
|
||||
function getWebSocketConnectionInfo(callback) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "status")%>', null, function(x, status) {
|
||||
if (x && x.status == 200 && status) {
|
||||
callback(status);
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function coreLogWebSocketStop() {
|
||||
if (wsRetryTimer) {
|
||||
clearTimeout(wsRetryTimer);
|
||||
wsRetryTimer = null;
|
||||
}
|
||||
|
||||
var ws = coreWebSocket;
|
||||
coreWebSocket = null;
|
||||
|
||||
if (!ws) return;
|
||||
|
||||
["onopen", "onmessage", "onerror", "onclose"].forEach(function(event) {
|
||||
try {
|
||||
ws[event] = null;
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
try {
|
||||
if (ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
|
||||
ws.close();
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
coreLogBuffer = [];
|
||||
}
|
||||
|
||||
function coreLogWebSocketRetry() {
|
||||
wsRetryTimer = setTimeout(function() {
|
||||
coreLogWebSocket();
|
||||
}, wsRetryDelay);
|
||||
}
|
||||
|
||||
function coreLogWebSocket() {
|
||||
coreLogWebSocketStop();
|
||||
|
||||
getWebSocketConnectionInfo(function(status) {
|
||||
if (!status || !status.daip || !status.cn_port || !status.clash) {
|
||||
return;
|
||||
}
|
||||
|
||||
var protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||
var host, port;
|
||||
|
||||
if (status.daip && window.location.hostname === status.daip) {
|
||||
host = status.daip;
|
||||
port = status.cn_port;
|
||||
} else if (status.daip && window.location.hostname !== status.daip &&
|
||||
status.db_foward_domain && status.db_foward_port) {
|
||||
host = status.db_foward_domain;
|
||||
port = status.db_foward_port;
|
||||
} else {
|
||||
host = status.daip;
|
||||
port = status.cn_port;
|
||||
}
|
||||
|
||||
var token = status.dase;
|
||||
var level = currentLogLevel;
|
||||
var wsUrl = protocol + host + ":" + port + "/logs?token=" + token + "&level=" + level;
|
||||
|
||||
try {
|
||||
coreWebSocket = new WebSocket(wsUrl);
|
||||
coreWebSocket.onopen = function() {
|
||||
coreLogBuffer = [];
|
||||
};
|
||||
|
||||
coreWebSocket.onmessage = function(event) {
|
||||
if (event.data) {
|
||||
try {
|
||||
var msgData = JSON.parse(event.data);
|
||||
if (msgData && msgData.payload) {
|
||||
var now = new Date();
|
||||
var timestamp = now.getFullYear() + "-" + p(now.getMonth() + 1) + "-" + p(now.getDate()) + " " +
|
||||
p(now.getHours()) + ":" + p(now.getMinutes()) + ":" + p(now.getSeconds());
|
||||
var type = msgData.type || 'info';
|
||||
var translatedType = levelTranslations[type] || type;
|
||||
coreLogBuffer.push(timestamp + " [" + translatedType + "] " + msgData.payload);
|
||||
}
|
||||
} catch (e) {
|
||||
coreLogWebSocketRetry();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
coreWebSocket.onerror = function(error) {
|
||||
coreLogWebSocketRetry();
|
||||
};
|
||||
|
||||
coreWebSocket.onclose = function(event) {
|
||||
coreWebSocket = null;
|
||||
if (!event.wasClean) {
|
||||
coreLogWebSocketRetry();
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
coreLogWebSocketRetry();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function line_tolocal(str) {
|
||||
var trans_local = new Array();
|
||||
var local_count = 0;
|
||||
|
||||
str.trim().split('\n').forEach(function(v, i) {
|
||||
var regex = /(time=)"([^"]*)"/g;
|
||||
var res = regex.exec(v);
|
||||
|
||||
try {
|
||||
if (res) {
|
||||
var dt = new Date(res[2]);
|
||||
|
||||
if (!isNaN(dt.getTime())) {
|
||||
var timestamp = dt.getFullYear() + "-" + p(dt.getMonth() + 1) + "-" + p(dt.getDate()) + " " +
|
||||
p(dt.getHours()) + ":" + p(dt.getMinutes()) + ":" + p(dt.getSeconds());
|
||||
|
||||
var levelMatch = v.match(/level=(\w+)/);
|
||||
var level = levelMatch ? levelMatch[1] : 'info';
|
||||
var translatedLevel = levelTranslations[level] || level;
|
||||
var msgMatch = v.match(/msg="(.*)"/);
|
||||
var msg = msgMatch ? msgMatch[1] : '';
|
||||
trans_local[local_count] = timestamp + " [" + translatedLevel + "] " + msg;
|
||||
local_count++;
|
||||
} else {
|
||||
trans_local[local_count] = v;
|
||||
local_count++;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
var dtt = new Date(v.substring(0, 19));
|
||||
|
||||
if (!isNaN(dtt.getTime()) && v.substring(0, 19).match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)) {
|
||||
trans_local[local_count] = v;
|
||||
local_count++;
|
||||
} else {
|
||||
trans_local[local_count] = v;
|
||||
local_count++;
|
||||
}
|
||||
} catch (e) {
|
||||
trans_local[local_count] = v;
|
||||
local_count++;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
trans_local[local_count] = v;
|
||||
local_count++;
|
||||
}
|
||||
});
|
||||
|
||||
return trans_local;
|
||||
};
|
||||
|
||||
function smoothlyDisplayLogs(newLines, target, isEditor, currentContent, isActiveTab) {
|
||||
var scrollPosition = null;
|
||||
var isAtTop = false;
|
||||
var cursorPos = null;
|
||||
var selectionRange = null;
|
||||
|
||||
var isFirstLoad = !currentContent || currentContent.trim() === "";
|
||||
|
||||
if (isEditor && target) {
|
||||
scrollPosition = target.getScrollInfo();
|
||||
isAtTop = (scrollPosition.top < 20);
|
||||
|
||||
if (target.hasFocus()) {
|
||||
cursorPos = target.getCursor();
|
||||
|
||||
if (cursorPos.line === 0) {
|
||||
cursorPos = null;
|
||||
selectionRange = null;
|
||||
} else if (target.somethingSelected()) {
|
||||
selectionRange = {
|
||||
from: target.getCursor(true),
|
||||
to: target.getCursor(false)
|
||||
};
|
||||
} else {
|
||||
selectionRange = null;
|
||||
}
|
||||
} else {
|
||||
cursorPos = null;
|
||||
selectionRange = null;
|
||||
}
|
||||
} else if (!isEditor && target) {
|
||||
scrollPosition = target.scrollTop;
|
||||
isAtTop = (target.scrollTop < 20);
|
||||
}
|
||||
|
||||
if ((target === editor_oc && animatingOC) || (target === editor_core && animatingCore) || !isActiveTab) {
|
||||
var content = "";
|
||||
var lines = newLines.slice().reverse();
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
content += lines[i] + "\n";
|
||||
}
|
||||
content = content + (currentContent || "");
|
||||
|
||||
var allLines = content.split("\n");
|
||||
if (allLines.length > 2000) {
|
||||
allLines = allLines.slice(0, 1999);
|
||||
allLines.push("...");
|
||||
content = allLines.join("\n");
|
||||
}
|
||||
|
||||
if (isEditor) {
|
||||
var addedLines = lines.length;
|
||||
target.setValue(content);
|
||||
|
||||
if (!isAtTop && scrollPosition) {
|
||||
if (cursorPos) {
|
||||
cursorPos.line += addedLines;
|
||||
target.setCursor(cursorPos);
|
||||
|
||||
target.scrollIntoView({line: cursorPos.line, ch: cursorPos.ch}, 300);
|
||||
} else {
|
||||
target.scrollTo(scrollPosition.left, scrollPosition.top);
|
||||
}
|
||||
|
||||
if (selectionRange) {
|
||||
selectionRange.from.line += addedLines;
|
||||
selectionRange.to.line += addedLines;
|
||||
target.setSelection(selectionRange.from, selectionRange.to);
|
||||
|
||||
target.scrollIntoView({
|
||||
from: selectionRange.from,
|
||||
to: selectionRange.to
|
||||
}, 300);
|
||||
}
|
||||
} else if (isAtTop) {
|
||||
target.scrollTo(0, 0);
|
||||
if (isFirstLoad) {
|
||||
target.setCursor({line: 0, ch: 0});
|
||||
}
|
||||
}
|
||||
|
||||
target.refresh();
|
||||
} else {
|
||||
var oldScrollTop = scrollPosition;
|
||||
target.innerHTML = content;
|
||||
if (!isAtTop && oldScrollTop) {
|
||||
target.scrollTop = oldScrollTop;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (target === editor_oc || target === lv) {
|
||||
animatingOC = true;
|
||||
} else {
|
||||
animatingCore = true;
|
||||
}
|
||||
|
||||
var totalLines = newLines.length;
|
||||
var batchSize, interval;
|
||||
|
||||
if (totalLines <= 10) {
|
||||
batchSize = 2;
|
||||
interval = 90;
|
||||
} else if (totalLines <= 50) {
|
||||
batchSize = 10;
|
||||
interval = 60;
|
||||
} else if (totalLines <= 200) {
|
||||
batchSize = 30;
|
||||
interval = 35;
|
||||
} else if (totalLines <= 500) {
|
||||
batchSize = 60;
|
||||
interval = 25;
|
||||
} else if (totalLines <= 1000) {
|
||||
batchSize = 120;
|
||||
interval = 15;
|
||||
} else if (totalLines <= 1500) {
|
||||
batchSize = 180;
|
||||
interval = 10;
|
||||
} else {
|
||||
batchSize = 250;
|
||||
interval = 5;
|
||||
}
|
||||
|
||||
var displayedContent = currentContent || "";
|
||||
var logLines = newLines.slice();
|
||||
var currentBatchCount = 0;
|
||||
var accumulatedContent = "";
|
||||
|
||||
function displayNextBatch() {
|
||||
if (currentBatchCount >= logLines.length) {
|
||||
if (target === editor_oc || target === lv) {
|
||||
animatingOC = false;
|
||||
} else {
|
||||
animatingCore = false;
|
||||
}
|
||||
|
||||
if (isEditor && !isAtTop && cursorPos) {
|
||||
cursorPos.line += logLines.length;
|
||||
target.setCursor(cursorPos);
|
||||
|
||||
if (selectionRange) {
|
||||
selectionRange.from.line += logLines.length;
|
||||
selectionRange.to.line += logLines.length;
|
||||
target.setSelection(selectionRange.from, selectionRange.to);
|
||||
|
||||
target.scrollIntoView({
|
||||
from: selectionRange.from,
|
||||
to: selectionRange.to
|
||||
}, 300);
|
||||
} else {
|
||||
target.scrollIntoView({line: cursorPos.line, ch: cursorPos.ch}, 300);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var nextBatchSize = Math.min(batchSize, logLines.length - currentBatchCount);
|
||||
|
||||
var batchLines = logLines.slice(currentBatchCount, currentBatchCount + nextBatchSize).reverse();
|
||||
currentBatchCount += nextBatchSize;
|
||||
|
||||
if (accumulatedContent) {
|
||||
accumulatedContent = batchLines.join("\n") + "\n" + accumulatedContent;
|
||||
} else {
|
||||
accumulatedContent = batchLines.join("\n");
|
||||
}
|
||||
|
||||
var content = accumulatedContent + (displayedContent ? "\n" + displayedContent : "");
|
||||
|
||||
var contentLines = content.split("\n");
|
||||
if (contentLines.length > 2000) {
|
||||
contentLines = contentLines.slice(0, 1999);
|
||||
contentLines.push("...");
|
||||
content = contentLines.join("\n");
|
||||
}
|
||||
|
||||
if (isEditor) {
|
||||
var currentScrollInfo = isAtTop ? null : target.getScrollInfo();
|
||||
target.setValue(content);
|
||||
|
||||
if (!isAtTop && currentScrollInfo) {
|
||||
target.scrollTo(currentScrollInfo.left, currentScrollInfo.top);
|
||||
}
|
||||
target.refresh();
|
||||
} else {
|
||||
var currentScrollTop = isAtTop ? null : target.scrollTop;
|
||||
target.innerHTML = content;
|
||||
if (!isAtTop && currentScrollTop !== null) {
|
||||
target.scrollTop = currentScrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentBatchCount < logLines.length) {
|
||||
setTimeout(displayNextBatch, interval);
|
||||
} else {
|
||||
if (target === editor_oc || target === lv) {
|
||||
animatingOC = false;
|
||||
} else {
|
||||
animatingCore = false;
|
||||
}
|
||||
|
||||
if (isEditor && !isAtTop && cursorPos) {
|
||||
cursorPos.line += logLines.length;
|
||||
target.setCursor(cursorPos);
|
||||
|
||||
if (selectionRange) {
|
||||
selectionRange.from.line += logLines.length;
|
||||
selectionRange.to.line += logLines.length;
|
||||
target.setSelection(selectionRange.from, selectionRange.to);
|
||||
|
||||
target.scrollIntoView({
|
||||
from: selectionRange.from,
|
||||
to: selectionRange.to
|
||||
}, 300);
|
||||
} else {
|
||||
target.scrollIntoView({line: cursorPos.line, ch: cursorPos.ch}, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayNextBatch();
|
||||
}
|
||||
|
||||
function poll_log(){
|
||||
var activeTabId = 0;
|
||||
var titles = document.getElementsByName('tab-header');
|
||||
var core_logs = [];
|
||||
var oc_logs = [];
|
||||
var currentCoreContent = "";
|
||||
var currentContent = "";
|
||||
var bufferToProcess = [];
|
||||
|
||||
clearTimeout(r);
|
||||
|
||||
for(var i=0; i<titles.length; i++){
|
||||
if(titles[i].className === 'cbi-tab') {
|
||||
activeTabId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (coreWebSocket && coreWebSocket.readyState == WebSocket.OPEN) {
|
||||
core_refresh = false;
|
||||
if (coreLogBuffer.length > 0) {
|
||||
bufferToProcess = coreLogBuffer;
|
||||
coreLogBuffer = [];
|
||||
core_logs = line_tolocal(bufferToProcess.join('\n'));
|
||||
if (editor_core) {
|
||||
currentCoreContent = editor_core.getValue();
|
||||
smoothlyDisplayLogs(core_logs, editor_core, true, currentCoreContent, activeTabId === 1);
|
||||
} else if (cl) {
|
||||
currentCoreContent = cl.innerHTML;
|
||||
smoothlyDisplayLogs(core_logs, cl, false, currentCoreContent, activeTabId === 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
core_refresh = true;
|
||||
}
|
||||
|
||||
if (isPolling) {
|
||||
r = setTimeout("poll_log()", 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
isPolling = true;
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "refresh_log")%>',
|
||||
{
|
||||
log_len: log_len, core_refresh: core_refresh
|
||||
},
|
||||
function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
if (status) {
|
||||
if (!status.update) {
|
||||
isPolling = false;
|
||||
r = setTimeout("poll_log()", 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.len) {
|
||||
log_len = status.len;
|
||||
}
|
||||
|
||||
if (status.oc_log && status.oc_log !== "") {
|
||||
oc_logs = line_tolocal(status.oc_log);
|
||||
|
||||
if (oc_logs.length > 0) {
|
||||
if (editor_oc) {
|
||||
currentContent = editor_oc.getValue();
|
||||
smoothlyDisplayLogs(oc_logs, editor_oc, true, currentContent, activeTabId === 0);
|
||||
} else if (lv) {
|
||||
currentContent = lv.innerHTML;
|
||||
smoothlyDisplayLogs(oc_logs, lv, false, currentContent, activeTabId === 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status.core_log && status.core_log !== "") {
|
||||
core_logs = line_tolocal(status.core_log);
|
||||
|
||||
if (core_logs.length > 0) {
|
||||
if (editor_core) {
|
||||
currentCoreContent = editor_core.getValue();
|
||||
smoothlyDisplayLogs(core_logs, editor_core, true, currentCoreContent, activeTabId === 1);
|
||||
} else if (cl) {
|
||||
currentCoreContent = cl.innerHTML;
|
||||
smoothlyDisplayLogs(core_logs, cl, false, currentCoreContent, activeTabId === 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
isPolling = false;
|
||||
r = setTimeout("poll_log()", 1000);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
window.onload = function(){
|
||||
var titles = document.getElementsByName('tab-header');
|
||||
var divs = document.getElementsByClassName('dom');
|
||||
if(titles.length != divs.length) return;
|
||||
for(var i=0; i<titles.length; i++){
|
||||
var li = titles[i];
|
||||
li.id = i;
|
||||
function handleTabSwitch(tab) {
|
||||
return function(e) {
|
||||
if (tab.className === 'cbi-tab') return;
|
||||
for(var j=0; j<titles.length; j++){
|
||||
titles[j].className = 'cbi-tab-disabled';
|
||||
divs[j].style.display = 'none';
|
||||
}
|
||||
tab.className = 'cbi-tab';
|
||||
divs[tab.id].style.display = 'block';
|
||||
|
||||
if(tab.id == 0 && typeof editor_oc !== 'undefined') {
|
||||
setTimeout(function(){
|
||||
editor_oc.refresh();
|
||||
}, 10);
|
||||
} else if(tab.id == 1 && typeof editor_core !== 'undefined') {
|
||||
setTimeout(function(){
|
||||
editor_core.refresh();
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
li.onclick = handleTabSwitch(li);
|
||||
li.ontouchstart = handleTabSwitch(li);
|
||||
}
|
||||
get_log_level();
|
||||
poll_log();
|
||||
};
|
||||
//]]>
|
||||
</script>
|
||||
<%+cbi/valuefooter%>
|
||||
813
luci-app-openclash/luasrc/view/openclash/myip.htm
Normal file
@ -0,0 +1,813 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-dns-prefetch-control" content="on">
|
||||
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
|
||||
<link rel="dns-prefetch" href="//whois.pconline.com.cn">
|
||||
<link rel="dns-prefetch" href="//pubstatic.b0.upaiyun.com">
|
||||
<link rel="dns-prefetch" href="//api.ip.sb">
|
||||
<link rel="dns-prefetch" href="//api.ipify.org">
|
||||
<link rel="dns-prefetch" href="//api.ttt.sh">
|
||||
<link rel="dns-prefetch" href="//myip.ipip.net">
|
||||
<link rel="dns-prefetch" href="//qqwry.api.skk.moe">
|
||||
<link rel="dns-prefetch" href="//d.skk.moe">
|
||||
<link rel="preconnect" href="https://pubstatic.b0.upaiyun.com">
|
||||
<link rel="preconnect" href="https://whois.pconline.com.cn">
|
||||
<link rel="preconnect" href="https://api.ip.sb">
|
||||
<link rel="preconnect" href="https://api.ipify.org">
|
||||
<link rel="preconnect" href="https://api.ttt.sh">
|
||||
<link rel="preconnect" href="https://qqwry.api.skk.moe">
|
||||
<link rel="preconnect" href="https://d.skk.moe">
|
||||
<link rel="preconnect" href="http://myip.ipip.net">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,minimal-ui">
|
||||
<title>IP 地址查询</title>
|
||||
</head>
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="oc">
|
||||
<div class="myip-main-card">
|
||||
<div class="myip-content-grid">
|
||||
<div class="myip-ip-section">
|
||||
<p class="myip-section-title">
|
||||
<%:IP Address%>
|
||||
<span class="myip-toolbar">
|
||||
<svg id="eye-icon" class="myip-icon-btn" width="20" height="20" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg" onclick="return privacy_my_ip(this)">
|
||||
<title><%:Hide IP%></title>
|
||||
<path id="eye-open" d="M128,56C48,56,16,128,16,128s32,72,112,72,112-72,112-72S208,56,128,56Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"></path>
|
||||
<circle id="eye-open-circle" cx="128" cy="128" r="40" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"></circle>
|
||||
<g id="eye-closed" style="display: none;">
|
||||
<line x1="48" y1="40" x2="208" y2="216" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"></line>
|
||||
<path d="M154.9,157.6A39.6,39.6,0,0,1,128,168a40,40,0,0,1-26.9-69.6" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"></path>
|
||||
<path d="M74,68.6C33.2,89.2,16,128,16,128s32,72,112,72a117.9,117.9,0,0,0,54-12.6" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"></path>
|
||||
<path d="M208.6,169.1C230.4,149.6,240,128,240,128S208,56,128,56a123.9,123.9,0,0,0-20.7,1.7" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"></path>
|
||||
<path d="M135.5,88.7a39.9,39.9,0,0,1,32.3,35.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="myip-ip-list">
|
||||
<div class="myip-ip-item">
|
||||
<span class="ip-title">UpaiYun</span>
|
||||
<div class="myip-ip-result">
|
||||
<span class="ip-result" id="ip-upaiyun"></span>
|
||||
<span class="ip-geo" id="ip-upaiyun-geo"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-ip-item">
|
||||
<span class="ip-title">IPIP.NET</span>
|
||||
<div class="myip-ip-result">
|
||||
<span class="ip-result" id="ip-ipip"></span>
|
||||
<span class="ip-geo" id="ip-ipip-geo"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-ip-item">
|
||||
<span class="ip-title">IP.SB</span>
|
||||
<div class="myip-ip-result">
|
||||
<span class="ip-result" id="ip-ipsb"></span>
|
||||
<span class="ip-geo" id="ip-ipsb-geo"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-ip-item">
|
||||
<span class="ip-title">IPIFY</span>
|
||||
<div class="myip-ip-result">
|
||||
<span class="ip-result" id="ip-ipify"></span>
|
||||
<span class="ip-geo" id="ip-ipify-geo"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-check-section">
|
||||
<p class="myip-section-title">
|
||||
<%:Access Check%>
|
||||
<span class="myip-toolbar">
|
||||
<svg id="mode-icon" class="mode-icon myip-icon-btn" width="22" height="22" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" onclick="return toggle_mode_by_icon(this)">
|
||||
<title><%:Router Mode%></title>
|
||||
<rect x="4" y="28" width="40" height="14" rx="2" fill="#2F88FF" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 35L22 35" stroke="#FFF" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="30" y="33" width="4" height="4" rx="2" fill="#FFF"/>
|
||||
<path d="M12 28L12 8" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 28V8" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg id="data-refresh-icon" class="myip-icon-btn" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" onclick="return refresh_myip(this)">
|
||||
<title><%:Refresh%></title>
|
||||
<path d="M23 4v6h-6"></path>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="myip-check-list">
|
||||
<div class="myip-check-item">
|
||||
<span class="ip-state_title"><%:Baidu Search%></span>
|
||||
<div class="myip-status-result">
|
||||
<span id="http-baidu"></span>
|
||||
<span id="ldtime-baidu"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-check-item">
|
||||
<span class="ip-state_title"><%:NetEase Music%></span>
|
||||
<div class="myip-status-result">
|
||||
<span id="http-163"></span>
|
||||
<span id="ldtime-163"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-check-item">
|
||||
<span class="ip-state_title">GitHub</span>
|
||||
<div class="myip-status-result">
|
||||
<span id="http-github"></span>
|
||||
<span id="ldtime-github"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-check-item">
|
||||
<span class="ip-state_title">YouTube</span>
|
||||
<div class="myip-status-result">
|
||||
<span id="http-youtube"></span>
|
||||
<span id="ldtime-youtube"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="myip-footer">
|
||||
<p>Powered by <a onclick="return ip_skk()" href="javascript:void(0);">ip.skk.moe</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
|
||||
<script>
|
||||
function addTitleOnOverflow() {
|
||||
document.querySelectorAll('.ip-result, .ip-geo').forEach(function (span) {
|
||||
if (span.scrollWidth > span.clientWidth && localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
span.setAttribute('title', span.textContent);
|
||||
} else {
|
||||
span.removeAttribute('title');
|
||||
}
|
||||
});
|
||||
}
|
||||
function ip_skk()
|
||||
{
|
||||
url2='https://ip.skk.moe';
|
||||
window.open(url2);
|
||||
}
|
||||
const $$ = document;
|
||||
var ip_ipip_ip;
|
||||
var ip_ipsb_ip;
|
||||
var ip_upaiyun_ip;
|
||||
var ip_ipify_ip;
|
||||
var refresh_http;
|
||||
var refresh_ip;
|
||||
$$.getElementById('ip-ipip').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-ipify').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '<%:Querying...%>';
|
||||
let random = parseInt(Math.random() * 100000000);
|
||||
let IP = {
|
||||
get: (url, type) =>
|
||||
fetch(url, { method: 'GET' }).then((resp) => {
|
||||
if (type === 'text')
|
||||
return Promise.all([resp.ok, resp.status, resp.text(), resp.headers]);
|
||||
else {
|
||||
return Promise.all([resp.ok, resp.status, resp.json(), resp.headers]);
|
||||
}
|
||||
}).then(([ok, status, data, headers]) => {
|
||||
if (ok) {
|
||||
let json = {
|
||||
ok,
|
||||
status,
|
||||
data,
|
||||
headers
|
||||
}
|
||||
return json;
|
||||
} else {
|
||||
throw new Error(JSON.stringify(json.error));
|
||||
}
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
}),
|
||||
parseIPIpip: (ip, elID) => {
|
||||
const v4 = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}';
|
||||
const v4Exact = new RegExp(`^${v4}$`);
|
||||
const anonymizedIp = (() => {
|
||||
if (v4Exact.test(ip)) {
|
||||
const [a, b, c] = ip.split('.');
|
||||
return `${a}.${b}.${c}.0`;
|
||||
}
|
||||
return ip;
|
||||
})();
|
||||
|
||||
fetch(`https://api.ip.sb/geoip/${anonymizedIp}`, {
|
||||
referrerPolicy: 'no-referrer-when-downgrade',
|
||||
}).then(r => r.json())
|
||||
.then(resp => {
|
||||
if ( resp.country && resp.country != '' && resp.isp && resp.isp != '' ) {
|
||||
$$.getElementById(elID).innerHTML = resp.country + ' ' + resp.isp;
|
||||
}
|
||||
else {
|
||||
fetch(`https://qqwry.api.skk.moe/${anonymizedIp}`, {
|
||||
referrerPolicy: 'no-referrer-when-downgrade',
|
||||
}).then(r => r.json())
|
||||
.then(resp => {
|
||||
if ( resp.geo.indexOf('skk.moe') == -1 ) {
|
||||
$$.getElementById(elID).innerHTML = resp.geo;
|
||||
}
|
||||
else {
|
||||
$$.getElementById(elID).innerHTML = 'Unknown';
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getPcolIP: () => {
|
||||
fetch(`https://whois.pconline.com.cn/ipJson.jsp?z=${random}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
let pcisp = data.addr.split(' ');
|
||||
if (localStorage.getItem('privacy_my_ip') != 'true') {
|
||||
$$.getElementById('ip-pcol').innerHTML = data.ip;
|
||||
};
|
||||
$$.getElementById('ip-pcol-geo').innerHTML = `${data.pro} ${data.city} ${data.region} ${pcisp[1]}`;
|
||||
addTitleOnOverflow();
|
||||
})
|
||||
},
|
||||
getUpaiIP: () => {
|
||||
IP.get(`https://pubstatic.b0.upaiyun.com/?_upnode&z=${random}`, 'json')
|
||||
.then(resp => {
|
||||
if (localStorage.getItem('privacy_my_ip') != 'true') {
|
||||
$$.getElementById('ip-upaiyun').innerHTML = resp.data.remote_addr;
|
||||
};
|
||||
$$.getElementById('ip-upaiyun-geo').innerHTML = resp.data.remote_addr_location.country + ' ' + resp.data.remote_addr_location.province + ' ' + resp.data.remote_addr_location.city + ' ' + resp.data.remote_addr_location.isp;
|
||||
addTitleOnOverflow();
|
||||
})
|
||||
},
|
||||
getIpipIP: () => {
|
||||
IP.get(`http://myip.ipip.net?z=${random}`, 'text')
|
||||
.then(resp => {
|
||||
const ipMatch = resp.data.match(/当前 IP:([0-9A-Fa-f:.]+)/);
|
||||
const geoMatch = resp.data.match(/来自于:(.+)/);
|
||||
|
||||
if (ipMatch && geoMatch) {
|
||||
if (localStorage.getItem('privacy_my_ip') != 'true') {
|
||||
$$.getElementById('ip-ipip').innerHTML = ipMatch[1];
|
||||
}
|
||||
$$.getElementById('ip-ipip-geo').innerHTML = geoMatch[1].trim();
|
||||
addTitleOnOverflow();
|
||||
}
|
||||
})
|
||||
},
|
||||
getIpifyIP: () => {
|
||||
IP.get(`https://api.ipify.org/?format=json&z=${random}`, 'json')
|
||||
.then(resp => {
|
||||
if (localStorage.getItem('privacy_my_ip') != 'true') {
|
||||
$$.getElementById('ip-ipify').innerHTML = resp.data.ip;
|
||||
};
|
||||
return resp.data.ip;
|
||||
})
|
||||
.then(ip => {
|
||||
IP.parseIPIpip(ip, 'ip-ipify-geo');
|
||||
addTitleOnOverflow();
|
||||
})
|
||||
},
|
||||
getIpsbIP: () => {
|
||||
fetch(`https://api.ip.sb/geoip?z=${random}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (localStorage.getItem('privacy_my_ip') != 'true') {
|
||||
$$.getElementById('ip-ipsb').innerHTML = data.ip;
|
||||
};
|
||||
$$.getElementById('ip-ipsb-geo').innerHTML = `${data.country} ${data.isp}`;
|
||||
addTitleOnOverflow();
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
$$.getElementById('http-baidu').innerHTML = '<span><%:Testing...%></span>';
|
||||
$$.getElementById('http-163').innerHTML = '<span><%:Testing...%></span>';
|
||||
$$.getElementById('http-github').innerHTML = '<span><%:Testing...%></span>';
|
||||
$$.getElementById('http-youtube').innerHTML = '<span><%:Testing...%></span>';
|
||||
let HTTP = {
|
||||
checker: (domain, cbElID, cbLoID) => {
|
||||
if (use_router_mode) {
|
||||
HTTP.checker_router(domain, cbElID, cbLoID);
|
||||
} else {
|
||||
HTTP.checker_browser(domain, cbElID, cbLoID);
|
||||
}
|
||||
},
|
||||
checker_browser: (domain, cbElID, cbLoID) => {
|
||||
let img = new Image;
|
||||
let img_start_time = (+new Date());
|
||||
let timeout = setTimeout(() => {
|
||||
img.onerror = img.onload = null;
|
||||
$$.getElementById(cbLoID).style.display = 'none';
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-error"><%:Access Timed Out%></span>'
|
||||
}, 5000);
|
||||
|
||||
img.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
$$.getElementById(cbLoID).style.display = 'none';
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-error"><%:Access Denied%></span>'
|
||||
}
|
||||
|
||||
img.onload = () => {
|
||||
clearTimeout(timeout);
|
||||
let img_load_time = (new Date())- img_start_time;
|
||||
if ($$.getElementById(cbLoID).style.display == 'none') {
|
||||
$$.getElementById(cbLoID).style.display = '';
|
||||
}
|
||||
if (img_load_time <= 500) {
|
||||
$$.getElementById(cbLoID).innerHTML = '<span class="sk-load-success">' + img_load_time + ' ms</span>'
|
||||
$$.getElementById(cbLoID).style.color = '#32b643';
|
||||
}
|
||||
else if (img_load_time > 500 && img_load_time <= 1000) {
|
||||
$$.getElementById(cbLoID).innerHTML = '<span class="sk-load-success">' + img_load_time + ' ms</span>'
|
||||
$$.getElementById(cbLoID).style.color = 'var(--warning-color)';
|
||||
}
|
||||
else {
|
||||
$$.getElementById(cbLoID).innerHTML = '<span class="sk-load-success">' + img_load_time + ' ms</span>'
|
||||
$$.getElementById(cbLoID).style.color = '#e85600';
|
||||
}
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-success"><%:Access Normal%></span>'
|
||||
}
|
||||
|
||||
img.src = `https://${domain}/favicon.ico?${+(new Date)}`
|
||||
},
|
||||
checker_router: (domain, cbElID, cbLoID) => {
|
||||
let start_time = (+new Date());
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/cgi-bin/luci/admin/services/openclash/website_check?domain=' + encodeURIComponent(domain), true);
|
||||
xhr.timeout = 10000;
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
let response_time = (new Date()) - start_time;
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
if ($$.getElementById(cbLoID).style.display == 'none') {
|
||||
$$.getElementById(cbLoID).style.display = '';
|
||||
}
|
||||
|
||||
let load_time = response.response_time || response_time;
|
||||
if (load_time <= 500) {
|
||||
$$.getElementById(cbLoID).innerHTML = '<span class="sk-load-success">' + load_time + ' ms</span>'
|
||||
$$.getElementById(cbLoID).style.color = '#32b643';
|
||||
}
|
||||
else if (load_time > 500 && load_time <= 1000) {
|
||||
$$.getElementById(cbLoID).innerHTML = '<span class="sk-load-success">' + load_time + ' ms</span>'
|
||||
$$.getElementById(cbLoID).style.color = 'var(--warning-color)';
|
||||
}
|
||||
else {
|
||||
$$.getElementById(cbLoID).innerHTML = '<span class="sk-load-success">' + load_time + ' ms</span>'
|
||||
$$.getElementById(cbLoID).style.color = '#e85600';
|
||||
}
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-success"><%:Access Normal%></span>'
|
||||
} else {
|
||||
$$.getElementById(cbLoID).style.display = 'none';
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-error"><%:Access Denied%></span>'
|
||||
}
|
||||
} catch (e) {
|
||||
$$.getElementById(cbLoID).style.display = 'none';
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-error"><%:Access Denied%></span>'
|
||||
}
|
||||
} else {
|
||||
$$.getElementById(cbLoID).style.display = 'none';
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-error"><%:Access Denied%></span>'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
$$.getElementById(cbLoID).style.display = 'none';
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-error"><%:Access Timed Out%></span>'
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
$$.getElementById(cbLoID).style.display = 'none';
|
||||
$$.getElementById(cbElID).innerHTML = '<span class="sk-text-error"><%:Access Denied%></span>'
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
},
|
||||
runcheck: () => {
|
||||
HTTP.checker('www.baidu.com', 'http-baidu', 'ldtime-baidu');
|
||||
HTTP.checker('s1.music.126.net/style', 'http-163', 'ldtime-163');
|
||||
HTTP.checker('github.com', 'http-github', 'ldtime-github');
|
||||
HTTP.checker('www.youtube.com', 'http-youtube', 'ldtime-youtube');
|
||||
}
|
||||
};
|
||||
|
||||
function myip_Load()
|
||||
{
|
||||
//IP.getPcolIP();
|
||||
IP.getUpaiIP();
|
||||
IP.getIpipIP();
|
||||
IP.getIpifyIP();
|
||||
IP.getIpsbIP();
|
||||
};
|
||||
|
||||
function show_my_ip() {
|
||||
if (localStorage.getItem('privacy_my_ip') == 'true') {
|
||||
update_eye_icon(false);
|
||||
ip_ipip_ip = $$.getElementById('ip-ipip').innerHTML;
|
||||
ip_ipsb_ip = $$.getElementById('ip-ipsb').innerHTML;
|
||||
ip_upaiyun_ip = $$.getElementById('ip-upaiyun').innerHTML;
|
||||
ip_ipify_ip = $$.getElementById('ip-ipify').innerHTML;
|
||||
$$.getElementById('ip-ipip').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipify').innerHTML = '***.***.***.***';
|
||||
addTitleOnOverflow();
|
||||
}
|
||||
else {
|
||||
if (!refresh_http) {
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
}
|
||||
if (!refresh_ip) {
|
||||
if (use_router_mode) {
|
||||
refresh_ip = setInterval("get_router_ip_info()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
} else {
|
||||
refresh_ip = setInterval("myip_Load()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function update_eye_icon(isOpen) {
|
||||
var eyeIcon = document.getElementById('eye-icon');
|
||||
var eyeOpen = document.getElementById('eye-open');
|
||||
var eyeOpenCircle = document.getElementById('eye-open-circle');
|
||||
var eyeClosed = document.getElementById('eye-closed');
|
||||
var titleElement = eyeIcon.querySelector('title');
|
||||
|
||||
if (isOpen) {
|
||||
eyeOpen.style.display = '';
|
||||
eyeOpenCircle.style.display = '';
|
||||
eyeClosed.style.display = 'none';
|
||||
if (titleElement) titleElement.textContent = '<%:Hide IP%>';
|
||||
} else {
|
||||
eyeOpen.style.display = 'none';
|
||||
eyeOpenCircle.style.display = 'none';
|
||||
eyeClosed.style.display = '';
|
||||
if (titleElement) titleElement.textContent = '<%:Show IP%>';
|
||||
}
|
||||
};
|
||||
|
||||
function privacy_my_ip(svgElement) {
|
||||
var isCurrentlyOpen = document.getElementById('eye-open').style.display !== 'none';
|
||||
|
||||
if (isCurrentlyOpen) {
|
||||
clearInterval(refresh_ip);
|
||||
refresh_ip = null;
|
||||
localStorage.setItem('privacy_my_ip', 'true');
|
||||
update_eye_icon(false);
|
||||
|
||||
ip_ipip_ip = $$.getElementById('ip-ipip').innerHTML;
|
||||
ip_ipsb_ip = $$.getElementById('ip-ipsb').innerHTML;
|
||||
ip_upaiyun_ip = $$.getElementById('ip-upaiyun').innerHTML;
|
||||
ip_ipify_ip = $$.getElementById('ip-ipify').innerHTML;
|
||||
$$.getElementById('ip-ipip').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipify').innerHTML = '***.***.***.***';
|
||||
addTitleOnOverflow();
|
||||
} else {
|
||||
update_eye_icon(true);
|
||||
localStorage.removeItem('privacy_my_ip');
|
||||
|
||||
if (ip_ipip_ip && ip_ipsb_ip && ip_upaiyun_ip && ip_ipify_ip) {
|
||||
$$.getElementById('ip-ipip').innerHTML = ip_ipip_ip;
|
||||
$$.getElementById('ip-ipsb').innerHTML = ip_ipsb_ip;
|
||||
$$.getElementById('ip-upaiyun').innerHTML = ip_upaiyun_ip;
|
||||
$$.getElementById('ip-ipify').innerHTML = ip_ipify_ip;
|
||||
} else {
|
||||
if (use_router_mode) {
|
||||
get_router_ip_info();
|
||||
} else {
|
||||
myip_Load();
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh_ip) {
|
||||
clearInterval(refresh_ip);
|
||||
refresh_ip = null;
|
||||
}
|
||||
if (use_router_mode) {
|
||||
refresh_ip = setInterval(get_router_ip_info, Math.floor(Math.random() * (40 - 15 + 1) + 15) * 1000);
|
||||
} else {
|
||||
refresh_ip = setInterval(myip_Load, Math.floor(Math.random() * (40 - 15 + 1) + 15) * 1000);
|
||||
}
|
||||
addTitleOnOverflow();
|
||||
};
|
||||
};
|
||||
|
||||
var use_router_mode = true;
|
||||
|
||||
function toggle_mode_by_icon(svgElement) {
|
||||
var rect = svgElement.querySelector('rect[fill="#2F88FF"]');
|
||||
if (rect && rect.getAttribute('fill') === '#2F88FF') {
|
||||
use_router_mode = false;
|
||||
localStorage.setItem('myip_check_mode', 'false');
|
||||
update_mode_icon();
|
||||
clearAllIntervals();
|
||||
init_browser_mode();
|
||||
} else {
|
||||
use_router_mode = true;
|
||||
localStorage.setItem('myip_check_mode', 'true');
|
||||
update_mode_icon();
|
||||
clearAllIntervals();
|
||||
init_router_mode();
|
||||
}
|
||||
}
|
||||
|
||||
function update_mode_icon() {
|
||||
var modeIcon = document.getElementById('mode-icon');
|
||||
var rect = modeIcon.querySelector('rect[x="4"]');
|
||||
var paths = modeIcon.querySelectorAll('path');
|
||||
var smallRect = modeIcon.querySelector('rect[x="30"]');
|
||||
var titleElement = modeIcon.querySelector('title');
|
||||
|
||||
if (use_router_mode) {
|
||||
rect.setAttribute('fill', '#2F88FF');
|
||||
rect.setAttribute('stroke', '#333');
|
||||
paths[0].setAttribute('stroke', '#FFF');
|
||||
paths[1].setAttribute('stroke', '#333');
|
||||
paths[2].setAttribute('stroke', '#333');
|
||||
smallRect.setAttribute('fill', '#FFF');
|
||||
if (titleElement) titleElement.textContent = '<%:Router Mode%>';
|
||||
} else {
|
||||
rect.setAttribute('fill', '#666');
|
||||
rect.setAttribute('stroke', '#999');
|
||||
paths[0].setAttribute('stroke', '#CCC');
|
||||
paths[1].setAttribute('stroke', '#999');
|
||||
paths[2].setAttribute('stroke', '#999');
|
||||
smallRect.setAttribute('fill', '#CCC');
|
||||
if (titleElement) titleElement.textContent = '<%:Browser Mode%>';
|
||||
}
|
||||
}
|
||||
|
||||
function init_router_mode() {
|
||||
if (refresh_ip) {
|
||||
clearInterval(refresh_ip);
|
||||
refresh_ip = null;
|
||||
}
|
||||
|
||||
if (localStorage.getItem('privacy_my_ip') === 'true') {
|
||||
$$.getElementById('ip-ipip').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipify').innerHTML = '***.***.***.***';
|
||||
}
|
||||
|
||||
get_router_ip_info();
|
||||
HTTP.runcheck();
|
||||
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("get_router_ip_info()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
}
|
||||
|
||||
function init_browser_mode() {
|
||||
if (localStorage.getItem('privacy_my_ip') === 'true') {
|
||||
$$.getElementById('ip-ipip').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipify').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipip-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipsb-geo').innerHTML = '';
|
||||
$$.getElementById('ip-upaiyun-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipify-geo').innerHTML = '';
|
||||
} else {
|
||||
reset_display();
|
||||
}
|
||||
|
||||
myip_Load();
|
||||
HTTP.runcheck();
|
||||
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("myip_Load()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
}
|
||||
|
||||
function get_router_ip_info() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/cgi-bin/luci/admin/services/openclash/myip_check', true);
|
||||
xhr.timeout = 15000;
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response && (response.upaiyun || response.pcol || response.ipsb || response.ipify)) {
|
||||
update_ip_display(response);
|
||||
} else {
|
||||
if (!has_ip_cache()) show_querying_state();
|
||||
}
|
||||
} catch (e) {
|
||||
if (!has_ip_cache()) show_querying_state();
|
||||
}
|
||||
} else {
|
||||
if (!has_ip_cache()) show_querying_state();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
if (!has_ip_cache()) show_querying_state();
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
if (!has_ip_cache()) show_querying_state();
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function has_ip_cache() {
|
||||
const ip_ids = ['ip-ipip', 'ip-ipsb', 'ip-upaiyun', 'ip-ipify'];
|
||||
for (let id of ip_ids) {
|
||||
let val = document.getElementById(id).innerHTML;
|
||||
if (
|
||||
val &&
|
||||
val !== '<%:Querying...%>' &&
|
||||
val !== '***.***.***.***'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function show_querying_state() {
|
||||
if (localStorage.getItem('privacy_my_ip') === 'true') {
|
||||
$$.getElementById('ip-ipip').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipify').innerHTML = '***.***.***.***';
|
||||
} else {
|
||||
$$.getElementById('ip-ipip').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-ipify').innerHTML = '<%:Querying...%>';
|
||||
}
|
||||
$$.getElementById('ip-ipip-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipsb-geo').innerHTML = '';
|
||||
$$.getElementById('ip-upaiyun-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipify-geo').innerHTML = '';
|
||||
}
|
||||
|
||||
function update_ip_display(data) {
|
||||
const ipFields = [
|
||||
{ id: 'ip-upaiyun', geo: 'ip-upaiyun-geo', key: 'upaiyun' },
|
||||
{ id: 'ip-ipip', geo: 'ip-ipip-geo', key: 'ipip' },
|
||||
{ id: 'ip-ipsb', geo: 'ip-ipsb-geo', key: 'ipsb' },
|
||||
{ id: 'ip-ipify', geo: 'ip-ipify-geo', key: 'ipify' }
|
||||
];
|
||||
|
||||
if (localStorage.getItem('privacy_my_ip') === 'true') {
|
||||
ipFields.forEach(f => {
|
||||
$$.getElementById(f.id).innerHTML = '***.***.***.***';
|
||||
});
|
||||
} else {
|
||||
ipFields.forEach(f => {
|
||||
let ipVal = '';
|
||||
if (data[f.key] && data[f.key].ip) {
|
||||
ipVal = data[f.key].ip;
|
||||
} else {
|
||||
let curVal = $$.getElementById(f.id).innerHTML;
|
||||
if (curVal && curVal !== '<%:Querying...%>') {
|
||||
ipVal = curVal;
|
||||
} else {
|
||||
ipVal = '<%:Querying...%>';
|
||||
}
|
||||
}
|
||||
$$.getElementById(f.id).innerHTML = ipVal;
|
||||
|
||||
let geoVal = '';
|
||||
if (data[f.key] && data[f.key].geo) {
|
||||
geoVal = data[f.key].geo;
|
||||
} else {
|
||||
let curGeo = $$.getElementById(f.geo).innerHTML;
|
||||
if (curGeo && curGeo !== '' && ipVal == curVal && curVal !== '<%:Querying...%>') {
|
||||
geoVal = curGeo;
|
||||
} else {
|
||||
geoVal = '';
|
||||
}
|
||||
}
|
||||
$$.getElementById(f.geo).innerHTML = geoVal;
|
||||
});
|
||||
}
|
||||
addTitleOnOverflow();
|
||||
}
|
||||
|
||||
function reset_display() {
|
||||
$$.getElementById('ip-ipip').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-ipify').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '<%:Querying...%>';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '<%:Querying...%>';
|
||||
|
||||
$$.getElementById('ip-ipip-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipify-geo').innerHTML = '';
|
||||
$$.getElementById('ip-upaiyun-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipsb-geo').innerHTML = '';
|
||||
}
|
||||
|
||||
function clearAllIntervals() {
|
||||
if (refresh_http) {
|
||||
clearInterval(refresh_http);
|
||||
refresh_http = null;
|
||||
}
|
||||
if (refresh_ip) {
|
||||
clearInterval(refresh_ip);
|
||||
refresh_ip = null;
|
||||
}
|
||||
}
|
||||
|
||||
function refresh_myip(svgElement) {
|
||||
svgElement.style.transform = 'rotate(360deg)';
|
||||
setTimeout(function() {
|
||||
svgElement.style.transform = '';
|
||||
}, 500);
|
||||
|
||||
clearAllIntervals();
|
||||
|
||||
if (use_router_mode) {
|
||||
get_router_ip_info();
|
||||
HTTP.runcheck();
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("get_router_ip_info()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
} else {
|
||||
if (localStorage.getItem('privacy_my_ip') === 'true') {
|
||||
$$.getElementById('ip-ipip-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipsb-geo').innerHTML = '';
|
||||
$$.getElementById('ip-upaiyun-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipify-geo').innerHTML = '';
|
||||
$$.getElementById('ip-ipip').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipsb').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-upaiyun').innerHTML = '***.***.***.***';
|
||||
$$.getElementById('ip-ipify').innerHTML = '***.***.***.***';
|
||||
}
|
||||
|
||||
myip_Load();
|
||||
HTTP.runcheck();
|
||||
|
||||
refresh_http = setInterval("HTTP.runcheck()", Math.floor(Math.random()*(20-5+1)+5)*1000);
|
||||
if (localStorage.getItem('privacy_my_ip') !== 'true') {
|
||||
refresh_ip = setInterval("myip_Load()", Math.floor(Math.random()*(40-15+1)+15)*1000);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function init_page() {
|
||||
var saved_mode = localStorage.getItem('myip_check_mode');
|
||||
if (saved_mode === 'true' || saved_mode === null) {
|
||||
use_router_mode = true;
|
||||
update_mode_icon();
|
||||
init_router_mode();
|
||||
} else {
|
||||
use_router_mode = false;
|
||||
update_mode_icon();
|
||||
init_browser_mode();
|
||||
}
|
||||
|
||||
if (localStorage.getItem('privacy_my_ip') === 'true') {
|
||||
update_eye_icon(false);
|
||||
} else {
|
||||
update_eye_icon(true);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
init_page();
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', function() {
|
||||
clearAllIntervals();
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
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
@ -0,0 +1,88 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local stream_id_name = string.gsub(self.value, "%W", "")
|
||||
%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
function act_manual_unlock_test_<%=stream_id_name%>(btn, type)
|
||||
{
|
||||
var manual_<%=stream_id_name%>_type_legend = document.getElementById('manual-<%=stream_id_name%>-test-state');
|
||||
var manual_<%=stream_id_name%>_type_output = document.getElementById('manual-<%=stream_id_name%>-test-output');
|
||||
var r = confirm("<%:Network instability may occur during testing, Are you sure want to start test?%>")
|
||||
if (r == true) {
|
||||
if (manual_<%=stream_id_name%>_type_legend && manual_<%=stream_id_name%>_type_output)
|
||||
{
|
||||
manual_<%=stream_id_name%>_type_output.innerHTML =
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' +
|
||||
'<%:Waiting for command to complete...%>';
|
||||
|
||||
manual_<%=stream_id_name%>_type_legend.parentNode.style.display = 'block';
|
||||
manual_<%=stream_id_name%>_type_legend.style.display = 'inline';
|
||||
};
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "manual_stream_unlock_test")%>', {type: type}, function(x, status) {
|
||||
if (x && x.status == 200 && x.responseText != "")
|
||||
{
|
||||
manual_<%=stream_id_name%>_type_legend.style.display = 'none';
|
||||
manual_<%=stream_id_name%>_type_output.innerHTML = '<textarea id="manual_<%=stream_id_name%>_type_output" class="cbi-input-textarea" style="width: 100%;display:inline" data-update="change" rows="10" cols="50" readonly="readonly" >'+x.responseText+'</textarea>';
|
||||
log_editor(document.getElementById("manual_<%=stream_id_name%>_type_output"), 'manual_stream_unlock_test', 'true', manual_<%=stream_id_name%>_type_output.offsetWidth, '250px');
|
||||
}
|
||||
else
|
||||
{
|
||||
manual_<%=stream_id_name%>_type_legend.style.display = 'none';
|
||||
manual_<%=stream_id_name%>_type_output.innerHTML = '<span class="error"><%:Something Wrong While Testing...%></span>';
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
function act_all_proxies_test_<%=stream_id_name%>(btn, type)
|
||||
{
|
||||
var all_<%=stream_id_name%>_legend = document.getElementById('all-<%=stream_id_name%>-test-state');
|
||||
var all_<%=stream_id_name%>_output = document.getElementById('all-<%=stream_id_name%>-test-output');
|
||||
var r = confirm("<%:Network instability may occur during testing, Are you sure want to start test?%>")
|
||||
if (r == true) {
|
||||
if (all_<%=stream_id_name%>_legend && all_<%=stream_id_name%>_output)
|
||||
{
|
||||
all_<%=stream_id_name%>_output.innerHTML =
|
||||
'<img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /> ' +
|
||||
'<%:Waiting for command to complete...%>';
|
||||
|
||||
all_<%=stream_id_name%>_legend.parentNode.style.display = 'block';
|
||||
all_<%=stream_id_name%>_legend.style.display = 'inline';
|
||||
};
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "all_proxies_stream_test")%>', {type: type}, function(x, status) {
|
||||
if (x && x.status == 200 && x.responseText != "")
|
||||
{
|
||||
all_<%=stream_id_name%>_legend.style.display = 'none';
|
||||
all_<%=stream_id_name%>_output.innerHTML = '<textarea id="all_<%=stream_id_name%>_output" class="cbi-input-textarea" style="width: 100%;display:inline" data-update="change" rows="10" cols="50" readonly="readonly" >'+x.responseText+'</textarea>';
|
||||
log_editor(document.getElementById("all_<%=stream_id_name%>_output"), 'all_proxies_stream_test', true, all_<%=stream_id_name%>_output.offsetWidth, '250px');
|
||||
}
|
||||
else
|
||||
{
|
||||
all_<%=stream_id_name%>_legend.style.display = 'none';
|
||||
all_<%=stream_id_name%>_output.innerHTML = '<span class="error"><%:Something Wrong While Testing...%></span>';
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
//]]></script>
|
||||
|
||||
|
||||
<input type="button" class="btn cbi-button cbi-input-reload" value="<%:Unlock Test(Select)%>" onclick="return act_manual_unlock_test_<%=stream_id_name%>(this, '<%=self.value%>')" />
|
||||
<input type="button" class="btn cbi-button cbi-input-reload" value="<%:All Proxies Test%>" onclick="return act_all_proxies_test_<%=stream_id_name%>(this, '<%=self.value%>')" />
|
||||
<fieldset style="display: none;margin: 0 auto;">
|
||||
<legend id="manual-<%=stream_id_name%>-test-state"><%:Collecting data...%></legend>
|
||||
<br />
|
||||
<span id="manual-<%=stream_id_name%>-test-output"></span>
|
||||
</fieldset>
|
||||
<fieldset style="display: none;margin: 0 auto;">
|
||||
<legend id="all-<%=stream_id_name%>-test-state"><%:Collecting data...%></legend>
|
||||
<br />
|
||||
<span id="all-<%=stream_id_name%>-test-output"></span>
|
||||
</fieldset>
|
||||
<%+cbi/valuefooter%>
|
||||
357
luci-app-openclash/luasrc/view/openclash/select_git_cdn.htm
Normal file
@ -0,0 +1,357 @@
|
||||
<body>
|
||||
<div class="oc">
|
||||
<div id="selectPopup" class="select-popup hidden">
|
||||
<div class="select-popup-header">
|
||||
<div class="config-editor-title">
|
||||
<%:Check Update%>
|
||||
</div>
|
||||
<div class="config-editor-actions">
|
||||
<button type="button" class="icon-btn close-btn" onclick="closeSelectPopup()" title="<%:Close%>">
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select-config-section">
|
||||
<div class="config-grid">
|
||||
<div class="config-item">
|
||||
<div class="config-label"><%:Compiled Version%></div>
|
||||
<select class="select-class" id="CORE_VERSION_CDN">
|
||||
<option value="linux-386"><%:linux-386%></option>
|
||||
<option value="linux-amd64-v1"><%:linux-amd64-v1(x86-64)%></option>
|
||||
<option value="linux-amd64-v2"><%:linux-amd64-v2(x86-64)%></option>
|
||||
<option value="linux-amd64-v3"><%:linux-amd64-v3(x86-64)%></option>
|
||||
<option value="linux-armv5"><%:linux-armv5%></option>
|
||||
<option value="linux-armv6"><%:linux-armv6%></option>
|
||||
<option value="linux-armv7"><%:linux-armv7%></option>
|
||||
<option value="linux-arm64"><%:linux-arm64(armv8)%></option>
|
||||
<option value="linux-loong64-abi1"><%:linux-loong64-abi1%></option>
|
||||
<option value="linux-loong64-abi2"><%:linux-loong64-abi2%></option>
|
||||
<option value="linux-riscv64"><%:linux-riscv64%></option>
|
||||
<option value="linux-s390x"><%:linux-s390x%></option>
|
||||
<option value="linux-mips-hardfloat"><%:linux-mips-hardfloat%></option>
|
||||
<option value="linux-mips-softfloat"><%:linux-mips-softfloat%></option>
|
||||
<option value="linux-mips64"><%:linux-mips64%></option>
|
||||
<option value="linux-mips64le"><%:linux-mips64le%></option>
|
||||
<option value="linux-mipsle-softfloat"><%:linux-mipsle-softfloat%></option>
|
||||
<option value="linux-mipsle-hardfloat"><%:linux-mipsle-hardfloat%></option>
|
||||
<option value="0"><%:Not Set%></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-label"><%:Release Branch%></div>
|
||||
<select class="select-class" id="RELEASE_BRANCH_CDN">
|
||||
<option value="master">Master</option>
|
||||
<option value="dev">Developer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="config-item">
|
||||
<div class="config-label">
|
||||
<%:Smart Core%>
|
||||
<a href="javascript:void(0);" onclick="window.open('https://github.com/vernesong/mihomo/releases', '_blank');" class="help-link" title="<%:View core infos that support smart group%>">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<select class="select-class" id="SMART_ENABLE_CDN">
|
||||
<option value="0"><%:Disabled%></option>
|
||||
<option value="1"><%:Enable%></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="select-popup-body">
|
||||
<div class="select-option" data-value="https://raw.githubusercontent.com/">
|
||||
<span>https://raw.githubusercontent.com/ (<%:RAW address%>)</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://fastly.jsdelivr.net/">
|
||||
<span>https://fastly.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://testingcf.jsdelivr.net/">
|
||||
<span>https://testingcf.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="https://cdn.jsdelivr.net/">
|
||||
<span>https://cdn.jsdelivr.net/</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="custom">
|
||||
<span><%:Custom Your CDN URL%></span>
|
||||
<span style="display: flex; align-items: center; height: 100%;">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none" style="display: block;">
|
||||
<polygon points="6,9 18,9 12,15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<input type="text" id="customOptionInput" class="custom-option-input" value="https://ghfast.top/" placeholder="<%:Type CDN URL, Format Like%> https://ghfast.top/" style="display: none;">
|
||||
<div class="select-option" id="addCustomOption" style="display: none;"><%:Add%></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<*)\\.)+[a-z]{2,}|'+ // domain name
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
|
||||
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
|
||||
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
|
||||
'(\\#[-a-z\\d_]*)?'+'\/$','i'); // fragment locator
|
||||
return !!pattern.test(str);
|
||||
}
|
||||
|
||||
function HTTP_delay(domain, ID) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/cgi-bin/luci/admin/services/openclash/website_check?domain=' + encodeURIComponent(domain), true);
|
||||
xhr.timeout = 5000;
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
let status, text, response_time = 0;
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var resp = JSON.parse(xhr.responseText);
|
||||
response_time = resp.response_time || 0;
|
||||
if (resp.success) {
|
||||
if (response_time <= 500) {
|
||||
status = 'fast';
|
||||
text = response_time + ' ms';
|
||||
} else if (response_time > 500 && response_time <= 1000) {
|
||||
status = 'medium';
|
||||
text = response_time + ' ms';
|
||||
} else {
|
||||
status = 'slow';
|
||||
text = response_time + ' ms';
|
||||
}
|
||||
} else {
|
||||
status = 'error';
|
||||
text = resp.error || '<%:Access Denied%>';
|
||||
}
|
||||
} catch (e) {
|
||||
status = 'error';
|
||||
text = '<%:Access Denied%>';
|
||||
}
|
||||
} else {
|
||||
status = 'error';
|
||||
text = '<%:Access Timed Out%>';
|
||||
}
|
||||
updateCDNStatus(ID, status, text);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
updateCDNStatus(ID, 'error', '<%:Access Timed Out%>');
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
updateCDNStatus(ID, 'error', '<%:Access Denied%>');
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function updateCDNStatus(element, status, text) {
|
||||
var existingStatus = element.querySelector('.cdn-status');
|
||||
if (existingStatus) {
|
||||
existingStatus.remove();
|
||||
}
|
||||
|
||||
var statusSpan = document.createElement('span');
|
||||
statusSpan.className = 'cdn-status ' + status;
|
||||
statusSpan.textContent = text;
|
||||
element.appendChild(statusSpan);
|
||||
}
|
||||
|
||||
function select_git_cdn(btn, type) {
|
||||
var selectPopup = document.getElementById('selectPopup');
|
||||
get_update_info();
|
||||
selectPopup.classList.remove('hidden');
|
||||
|
||||
var customOptionInput = document.getElementById('customOptionInput');
|
||||
var addCustomOption = document.getElementById('addCustomOption');
|
||||
var selectOptions = document.getElementsByClassName('select-option');
|
||||
|
||||
customOptionInput.style.display = 'none';
|
||||
addCustomOption.style.display = 'none';
|
||||
|
||||
selectPopup.addEventListener('click', function(event) {
|
||||
select_window(event, type);
|
||||
});
|
||||
|
||||
document.addEventListener('click', select_window_close);
|
||||
|
||||
refresh_httpdelay = setInterval(() => {
|
||||
for(var i = 0; i < selectOptions.length; i++) {
|
||||
if (isValidURL(selectOptions[i].dataset.value)) {
|
||||
HTTP_delay(selectOptions[i].dataset.value, selectOptions[i]);
|
||||
}
|
||||
}
|
||||
}, Math.floor(Math.random()*(7-3+1)+3)*1000);
|
||||
}
|
||||
|
||||
function select_window_close(event) {
|
||||
var selectPopup = document.getElementById('selectPopup');
|
||||
if (!selectPopup.contains(event.target)) {
|
||||
selectPopup.classList.add('hidden');
|
||||
if (typeof refresh_httpdelay !== 'undefined') {
|
||||
clearInterval(refresh_httpdelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function select_window(event, type) {
|
||||
var v = core_version_cdn.value;
|
||||
var r = release_branch_cdn.value;
|
||||
var s = smart_enable_cdn.value;
|
||||
var selectOptions = document.getElementsByClassName('select-option');
|
||||
var target = event.target;
|
||||
var selectPopup = document.getElementById('selectPopup');
|
||||
var customOptionInput = document.getElementById('customOptionInput');
|
||||
var addCustomOption = document.getElementById('addCustomOption');
|
||||
|
||||
var optionElem = target.closest ? target.closest('.select-option') : null;
|
||||
|
||||
if (optionElem && optionElem.dataset.value === 'custom' && customOptionInput.style.display === 'block') {
|
||||
customOptionInput.style.display = 'none';
|
||||
addCustomOption.style.display = 'none';
|
||||
} else if (optionElem && optionElem.dataset.value === 'custom') {
|
||||
customOptionInput.style.display = 'block';
|
||||
addCustomOption.style.display = 'block';
|
||||
customOptionInput.focus();
|
||||
} else if (target === customOptionInput) {
|
||||
customOptionInput.style.display = 'block';
|
||||
addCustomOption.style.display = 'block';
|
||||
customOptionInput.focus();
|
||||
} else if (target === addCustomOption) {
|
||||
var newValue = customOptionInput.value.trim();
|
||||
if (newValue !== '' && isValidURL(newValue)) {
|
||||
var newOption = document.createElement('div');
|
||||
newOption.classList.add('select-option');
|
||||
newOption.innerHTML = '<span>' + newValue + '</span>';
|
||||
newOption.dataset.value = newValue;
|
||||
selectPopup.querySelector('.select-popup-body').insertBefore(newOption, selectPopup.querySelector('.select-popup-body').children[selectOptions.length - 2]);
|
||||
customOptionInput.style.display = 'none';
|
||||
addCustomOption.style.display = 'none';
|
||||
}
|
||||
else {
|
||||
alert("<%:Please enter a valid URL!%>");
|
||||
}
|
||||
} else if (optionElem && optionElem != addCustomOption && optionElem != customOptionInput) {
|
||||
customOptionInput.style.display = 'none';
|
||||
addCustomOption.style.display = 'none';
|
||||
if (typeof LogManager !== 'undefined' && LogManager) {
|
||||
LogManager.startLogDisplay('<%:Checking...%>');
|
||||
}
|
||||
if (optionElem.dataset.value === 'https://raw.githubusercontent.com/') {
|
||||
optionElem.dataset.value = '';
|
||||
}
|
||||
if (type === 'core_download') {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {core_ver: v, release_branch: r, smart_enable: s}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "core_download")%>', {url: optionElem.dataset.value}, function(x, status) {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {core_ver: v, release_branch: r, smart_enable: s}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "one_key_update")%>', {url: optionElem.dataset.value}, function(x, status) {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectPopup.classList.add('hidden');
|
||||
if (typeof refresh_httpdelay !== 'undefined') {
|
||||
clearInterval(refresh_httpdelay);
|
||||
}
|
||||
}
|
||||
else if (target != addCustomOption && target != customOptionInput) {
|
||||
customOptionInput.style.display = 'none';
|
||||
addCustomOption.style.display = 'none';
|
||||
}
|
||||
else {
|
||||
if (target != customOptionInput) {
|
||||
selectPopup.classList.add('hidden');
|
||||
if (typeof refresh_httpdelay !== 'undefined') {
|
||||
clearInterval(refresh_httpdelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeSelectPopup() {
|
||||
var selectPopup = document.getElementById('selectPopup');
|
||||
selectPopup.classList.add('hidden');
|
||||
if (typeof refresh_httpdelay !== 'undefined') {
|
||||
clearInterval(refresh_httpdelay);
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
2827
luci-app-openclash/luasrc/view/openclash/server_url.htm
Normal file
4399
luci-app-openclash/luasrc/view/openclash/status.htm
Normal file
420
luci-app-openclash/luasrc/view/openclash/sub_info_show.htm
Normal file
@ -0,0 +1,420 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local fs = require "luci.openclash"
|
||||
local val = self:cfgvalue(section)
|
||||
local filename = fs.filename(val)
|
||||
local idname = math.random(1000)..(string.match(filename, "[%w_]+") or "")
|
||||
%>
|
||||
|
||||
<div class="sub_div">
|
||||
<span id='<%=idname%>' class="sub_tab"></span>
|
||||
<button type="button" class="sub_setting" title='<%:Refresh%>' alt='<%:Refresh%>' onclick='return sub_info_refresh_<%=idname%>(true)'>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M23 4v6h-6"></path>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="sub_setting" title='<%:Specify URL%>' alt='<%:Specify URL%>' onclick='return set_subinfo_url_<%=idname%>(this,"<%=filename%>")'>
|
||||
<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="4">
|
||||
<path d="M4 6H44V36H29L24 41L19 36H4V6Z"/>
|
||||
<path d="M23 21H25.0025" />
|
||||
<path d="M33.001 21H34.9999"/>
|
||||
<path d="M13.001 21H14.9999"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
var retry_<%=idname%> = 0;
|
||||
var s_<%=idname%>;
|
||||
sub_info_refresh_<%=idname%>(false);
|
||||
|
||||
setTimeout(function(){
|
||||
initDarkMode();
|
||||
}, 200);
|
||||
|
||||
function initDarkMode() {
|
||||
if (typeof isDarkBackground === 'undefined') {
|
||||
setTimeout(function() {
|
||||
initDarkMode();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDarkBackground(document.body)) {
|
||||
document.documentElement.setAttribute('data-darkmode', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
function progressbar_<%=idname%>(v, m, pc, np, f, t, tr) {
|
||||
var screenWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
var fontSize = '12px';
|
||||
var minWidth = '220px';
|
||||
var topPosition = tr == "null" ? '6px' : '2px';
|
||||
|
||||
if (screenWidth <= 320) {
|
||||
fontSize = '8px';
|
||||
minWidth = '160px';
|
||||
topPosition = tr == "null" ? '8px' : '4px';
|
||||
} else if (screenWidth <= 480) {
|
||||
fontSize = '9px';
|
||||
minWidth = '180px';
|
||||
topPosition = tr == "null" ? '7px' : '3px';
|
||||
} else if (screenWidth <= 768) {
|
||||
fontSize = '10px';
|
||||
minWidth = '200px';
|
||||
topPosition = tr == "null" ? '6px' : '3px';
|
||||
} else if (screenWidth <= 1024) {
|
||||
fontSize = '11px';
|
||||
minWidth = '220px';
|
||||
topPosition = tr == "null" ? '6px' : '2px';
|
||||
}
|
||||
|
||||
// Build title for native tooltip (format: "v / m (pc%)\nt (Remaining tr days)")
|
||||
var titleText = v + ' / ' + m + ' (' + pc + '%%)';
|
||||
if (t && t !== 'null' && tr && tr !== 'null' && parseInt(tr) > 0) {
|
||||
titleText += '\n' + t + ' (<%:Remaining%> ' + tr + ' <%:days%>)';
|
||||
}
|
||||
|
||||
return String.format(
|
||||
'<div class="progress_bar_bg" title="' + titleText + '" style="position:relative; border-radius: 6px; min-width: %s; display: flex; align-items: center;">' +
|
||||
(pc >= 50 ? '<div class="progress_bar_high" style="width:%d%%; height:34px; border-radius: 6px">' :
|
||||
(pc < 50 && pc >= 20 ? '<div class="progress_bar_medium" style="width:%d%%; height:34px; border-radius: 6px">' :
|
||||
'<div class="progress_bar_low" style="width:%d%%; height:34px; border-radius: 6px">')) +
|
||||
'<div style="position:absolute; left:0; top:50%%; transform: translateY(-50%%); text-align:center; width:100%%; padding:0 4px; box-sizing:border-box; line-height:1.2;">' +
|
||||
'<small class="text_show" style="font-size:%s; line-height:1.2;">%s '+ (f ? f : '/') +' %s ' + (np ? "" : '(%s%%)') +
|
||||
(tr == "null" ? '<div style="visibility: hidden; line-height:1.2;">' : '<div style="visibility: visible; line-height:1.2;">') +
|
||||
'%s (<%:Remaining%> %s <%:days%>)</small>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
minWidth, pc, fontSize, v, m, pc, t, tr
|
||||
);
|
||||
};
|
||||
|
||||
function progressbar_multi_<%=idname%>(providers) {
|
||||
var providerCount = providers.length;
|
||||
var itemWidth;
|
||||
var containerClass = 'sub_providers_container';
|
||||
|
||||
if (providerCount === 2) {
|
||||
itemWidth = 'calc(50% - 2px)';
|
||||
containerClass += ' no-scroll';
|
||||
} else if (providerCount === 3) {
|
||||
itemWidth = 'calc(33.33% - 2.67px)';
|
||||
containerClass += ' no-scroll';
|
||||
} else {
|
||||
itemWidth = '100px';
|
||||
}
|
||||
|
||||
var container = '<div class="' + containerClass + '" id="sub_providers_<%=idname%>">';
|
||||
|
||||
providers.forEach(function(provider) {
|
||||
var percent = parseFloat(provider.percent) || 0;
|
||||
var providerName = provider.provider_name || 'Unknown';
|
||||
var used = provider.used || '0 B';
|
||||
var total = provider.total || 'N/A';
|
||||
var surplus = provider.surplus || used;
|
||||
var expire = provider.expire || 'null';
|
||||
var dayLeft = parseInt(provider.day_left) || 0;
|
||||
|
||||
// Determine background color based on percentage
|
||||
var bgColorClass = percent >= 50 ? 'progress_bar_high' :
|
||||
(percent >= 20 ? 'progress_bar_medium' : 'progress_bar_low');
|
||||
|
||||
// Build title for native tooltip (format: "surplus / total (percent%)\nexpire (Remaining dayLeft days)")
|
||||
var titleText = surplus + ' / ' + total + ' (' + percent + '%)';
|
||||
if (expire !== 'null' && dayLeft > 0) {
|
||||
titleText += '\n' + expire + ' (<%:Remaining%> ' + dayLeft + ' <%:days%>)';
|
||||
}
|
||||
|
||||
container += '<div class="sub_provider_item" style="width: ' + itemWidth + '" title="' + titleText + '">' +
|
||||
'<div class="sub_provider_fill ' + bgColorClass + '" style="width: ' + percent + '%"></div>' +
|
||||
'<span class="sub_provider_name">' + providerName + '</span>' +
|
||||
'<span class="sub_provider_percent">' + ' (' + percent + '%)' + '</span>' +
|
||||
'</div>';
|
||||
});
|
||||
|
||||
container += '</div>';
|
||||
|
||||
if (providerCount > 3) {
|
||||
setTimeout(function() {
|
||||
initDragScroll_<%=idname%>();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return container;
|
||||
};
|
||||
|
||||
function initDragScroll_<%=idname%>() {
|
||||
var container = document.getElementById('sub_providers_<%=idname%>');
|
||||
if (!container) return;
|
||||
|
||||
var isDragging = false;
|
||||
var startX;
|
||||
var scrollLeft;
|
||||
var hasMoved = false;
|
||||
|
||||
container.addEventListener('mousedown', function(e) {
|
||||
if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON') {
|
||||
return;
|
||||
}
|
||||
|
||||
isDragging = true;
|
||||
hasMoved = false;
|
||||
startX = e.pageX - container.offsetLeft;
|
||||
scrollLeft = container.scrollLeft;
|
||||
container.classList.add('dragging');
|
||||
});
|
||||
|
||||
container.addEventListener('mousemove', function(e) {
|
||||
if (!isDragging) return;
|
||||
|
||||
var x = e.pageX - container.offsetLeft;
|
||||
var walk = (x - startX) * 2;
|
||||
|
||||
if (Math.abs(walk) > 5) {
|
||||
hasMoved = true;
|
||||
e.preventDefault();
|
||||
container.scrollLeft = scrollLeft - walk;
|
||||
}
|
||||
});
|
||||
|
||||
container.addEventListener('mouseup', function() {
|
||||
isDragging = false;
|
||||
container.classList.remove('dragging');
|
||||
});
|
||||
|
||||
container.addEventListener('mouseleave', function() {
|
||||
isDragging = false;
|
||||
container.classList.remove('dragging');
|
||||
});
|
||||
|
||||
container.addEventListener('dragstart', function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
function dispaly_progressbar(id, status) {
|
||||
document.getElementById(id).className = "sub_tab_show";
|
||||
if (status && status.providers && status.providers.length > 0) {
|
||||
if (status.providers.length === 1) {
|
||||
var provider = status.providers[0];
|
||||
document.getElementById(id).innerHTML = window["progressbar_" + id](
|
||||
(provider.surplus ? provider.surplus : provider.used),
|
||||
(provider.total),
|
||||
(provider.percent),
|
||||
false, false,
|
||||
(provider.expire),
|
||||
(provider.day_left)
|
||||
);
|
||||
} else {
|
||||
document.getElementById(id).innerHTML = window["progressbar_multi_" + id](status.providers);
|
||||
}
|
||||
} else {
|
||||
document.getElementById(id).innerHTML = "<span><%:No Sub Info Found%></span>";
|
||||
}
|
||||
};
|
||||
|
||||
function sub_info_refresh_<%=idname%>(force) {
|
||||
if (s_<%=idname%>) { clearTimeout(s_<%=idname%>);};
|
||||
|
||||
if (force) {
|
||||
localStorage.removeItem("sub_info_<%=filename%>");
|
||||
}
|
||||
|
||||
var save_info = (localStorage.getItem("sub_info_<%=filename%>")) ? JSON.parse(localStorage.getItem("sub_info_<%=filename%>")) : null;
|
||||
var shouldFetchNew = true;
|
||||
if (!force) {
|
||||
dispaly_progressbar('<%=idname%>', save_info);
|
||||
if (save_info && save_info.get_time) {
|
||||
var currentTime = Math.floor(Date.now() / 1000);
|
||||
var cacheTime = parseInt(save_info.get_time);
|
||||
var timeDiff = currentTime - cacheTime;
|
||||
var halfHourInSeconds = 30 * 60;
|
||||
|
||||
if (timeDiff <= halfHourInSeconds) {
|
||||
shouldFetchNew = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldFetchNew) {
|
||||
s_<%=idname%> = setTimeout("sub_info_refresh_<%=idname%>(false)", 60000*15);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!save_info) {
|
||||
document.getElementById('<%=idname%>').className = "sub_tab_show";
|
||||
document.getElementById('<%=idname%>').innerHTML = "<span><%:Collecting data...%></span>";
|
||||
}
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "sub_info_get")%>', {filename: "<%=filename%>"}, function(x, status) {
|
||||
var needsErrorHandling = false;
|
||||
if (x && x.status == 200 && status.providers.length > 0) {
|
||||
var save_info_length = (save_info && save_info.providers) ? save_info.providers.length : 0;
|
||||
var newProvidersLength = status.providers.length;
|
||||
if (save_info_length !== newProvidersLength && save_info_length > newProvidersLength) {
|
||||
needsErrorHandling = true;
|
||||
}
|
||||
|
||||
if (!needsErrorHandling) {
|
||||
retry_<%=idname%> = 0;
|
||||
localStorage.setItem("sub_info_<%=filename%>", JSON.stringify(status));
|
||||
dispaly_progressbar('<%=idname%>', status);
|
||||
}
|
||||
} else {
|
||||
needsErrorHandling = true;
|
||||
};
|
||||
|
||||
if (needsErrorHandling && retry_<%=idname%> <= 2) {
|
||||
retry_<%=idname%>++;
|
||||
s_<%=idname%> = setTimeout("sub_info_refresh_<%=idname%>(false)", 5000);
|
||||
} else {
|
||||
retry_<%=idname%> = 0;
|
||||
dispaly_progressbar('<%=idname%>', status);
|
||||
if (status && status.providers) {
|
||||
localStorage.setItem("sub_info_<%=filename%>", JSON.stringify(status));
|
||||
}
|
||||
s_<%=idname%> = setTimeout("sub_info_refresh_<%=idname%>(false)", 60000*15);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
function set_subinfo_url_<%=idname%>(btn, filename) {
|
||||
if (filename == null || filename == "") {
|
||||
return false;
|
||||
}
|
||||
|
||||
var dialogDiv = document.createElement('div');
|
||||
dialogDiv.className = 'config-upload-model-overlay show';
|
||||
|
||||
var contentDiv = document.createElement('div');
|
||||
contentDiv.className = 'config-upload-model';
|
||||
|
||||
var headerDiv = document.createElement('div');
|
||||
headerDiv.className = 'config-upload-header';
|
||||
|
||||
var titleDiv = document.createElement('div');
|
||||
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 = 'config-upload-content';
|
||||
|
||||
var formGroup = document.createElement('div');
|
||||
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 = '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 = '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 = 'btn cancel-btn';
|
||||
cancelBtn.type = 'button';
|
||||
cancelBtn.innerHTML = '<%:Cancel%>';
|
||||
|
||||
var confirmBtn = document.createElement('button');
|
||||
confirmBtn.className = 'btn upload-btn';
|
||||
confirmBtn.type = 'button';
|
||||
confirmBtn.innerHTML = '<%:Submit%>';
|
||||
|
||||
confirmBtn.onclick = function() {
|
||||
var new_url = textarea.value.trim();
|
||||
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") {
|
||||
sub_info_refresh_<%=idname%>(true);
|
||||
alert("<%:Specify subscribe infos sources url successfully!%>");
|
||||
} else if (x && x.status == 200 && status.info == "Delete success") {
|
||||
sub_info_refresh_<%=idname%>(true);
|
||||
alert("<%:Delete Specify Subscribe infos sources url successfully!%>");
|
||||
} else {
|
||||
alert("<%:Specify subscribe infos sources url failed:%>\n" + status.info);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cancelBtn.onclick = function() {
|
||||
document.body.removeChild(portal);
|
||||
};
|
||||
|
||||
buttonsDiv.appendChild(cancelBtn);
|
||||
buttonsDiv.appendChild(confirmBtn);
|
||||
footerDiv.appendChild(buttonsDiv);
|
||||
|
||||
contentDiv.appendChild(headerDiv);
|
||||
contentDiv.appendChild(bodyDiv);
|
||||
contentDiv.appendChild(footerDiv);
|
||||
dialogDiv.appendChild(contentDiv);
|
||||
|
||||
var portal = document.createElement('div');
|
||||
portal.className = 'oc';
|
||||
portal.appendChild(dialogDiv);
|
||||
document.body.appendChild(portal);
|
||||
|
||||
textarea.focus();
|
||||
|
||||
var handleSubURL = function(urlresult) {
|
||||
var urls = '';
|
||||
if (urlresult.type && urlresult.type === "multiple") {
|
||||
for (var data in urlresult.providers) {
|
||||
urls += urlresult.providers[data].url + '#name=' + urlresult.providers[data].name + '\n';
|
||||
}
|
||||
}
|
||||
if (urlresult.type && urlresult.type === "single") {
|
||||
urls = urlresult.url;
|
||||
}
|
||||
textarea.value = urls;
|
||||
};
|
||||
|
||||
var existingData = localStorage.getItem('sub_info_' + filename);
|
||||
if (existingData) {
|
||||
existingData = JSON.parse(existingData);
|
||||
if (existingData.url_result) {
|
||||
handleSubURL(existingData.url_result);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "get_subscribe_info_data")%>', {
|
||||
filename: filename
|
||||
}, function(x, status) {
|
||||
if (x && x.status == 200 && status) {
|
||||
handleSubURL(status);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//]]></script>
|
||||
<%+cbi/valuefooter%>
|
||||
110
luci-app-openclash/luasrc/view/openclash/switch_dashboard.htm
Normal file
@ -0,0 +1,110 @@
|
||||
<%+cbi/valueheader%>
|
||||
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 6px;">
|
||||
<div class="cbi-value-field" id="switch_dashboard_<%=self.option%>"><%:Collecting data...%></div>
|
||||
<div class="cbi-value-field" id="delete_dashboard_<%=self.option%>">
|
||||
<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Delete%>" onclick="return delete_dashboard(this, '<%=self.option%>')"/>
|
||||
</div>
|
||||
<div class="cbi-value-field" id="default_dashboard_<%=self.option%>">
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Set to Default%>" onclick="return default_dashboard(this, '<%=self.option%>')"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
(function() {
|
||||
var name = "<%=self.option%>";
|
||||
|
||||
function makeBtn(label, fn, args) {
|
||||
var argStr = args.map(function(a){ return "'" + a + "'"; }).join(', ');
|
||||
return '<input type="button" class="btn cbi-button cbi-button-apply" value="' + label + '" onclick="return ' + fn + '(this, ' + argStr + ')"/>';
|
||||
}
|
||||
|
||||
var switchEl = document.getElementById('switch_dashboard_' + name);
|
||||
var defaultEl = document.getElementById('default_dashboard_' + name);
|
||||
var deleteEl = document.getElementById('delete_dashboard_' + name);
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "dashboard_type")%>', null, function(x, status) {
|
||||
if (!x || x.status != 200) return;
|
||||
|
||||
var switchLabel;
|
||||
if (name == "Metacubexd") {
|
||||
switchLabel = ['<%:Update Metacubexd Version%>', 'Official'];
|
||||
} else if (name == "Zashboard") {
|
||||
switchLabel = ['<%:Update Zashboard Version%>', 'Official'];
|
||||
} else {
|
||||
var isMeta = (name == "Dashboard" ? status.dashboard_type : status.yacd_type) == "Meta";
|
||||
switchLabel = isMeta
|
||||
? ['<%:Switch To Official Version%>', 'Official']
|
||||
: ['<%:Switch To Meta Version%>', 'Meta'];
|
||||
}
|
||||
switchEl.innerHTML = makeBtn(switchLabel[0], 'switch_dashboard', [name, switchLabel[1]]);
|
||||
|
||||
if (status.default_dashboard == name.toLowerCase()) {
|
||||
defaultEl.innerHTML = makeBtn('<%:Default%>', 'default_dashboard', [name]);
|
||||
defaultEl.firstElementChild.disabled = true;
|
||||
}
|
||||
if (!status[name.toLowerCase()]) {
|
||||
defaultEl.firstElementChild.disabled = true;
|
||||
deleteEl.firstElementChild.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
window.switch_dashboard = window.switch_dashboard || function(btn, n, type) {
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Downloading File...%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_dashboard")%>', {name: n, type: type}, function(x, status) {
|
||||
if (!x || x.status != 200) return;
|
||||
if (status.download_state == "0") {
|
||||
var isUpdate = (n == "Metacubexd" || n == "Zashboard");
|
||||
var nextLabel, nextType;
|
||||
if (isUpdate) {
|
||||
nextLabel = '<%:Update Successful%> - ' + (n == "Metacubexd" ? '<%:Update Metacubexd Version%>' : '<%:Update Zashboard Version%>');
|
||||
nextType = 'Official';
|
||||
} else {
|
||||
nextLabel = '<%:Switch Successful%> - ' + (type == "Meta" ? '<%:Switch To Official Version%>' : '<%:Switch To Meta Version%>');
|
||||
nextType = type == "Meta" ? 'Official' : 'Meta';
|
||||
}
|
||||
document.getElementById('switch_dashboard_' + n).innerHTML = makeBtn(nextLabel, 'switch_dashboard', [n, nextType]);
|
||||
document.getElementById('default_dashboard_' + n).firstElementChild.disabled = false;
|
||||
document.getElementById('delete_dashboard_' + n).firstElementChild.disabled = false;
|
||||
} else {
|
||||
btn.value = status.download_state == "2" ? '<%:Unzip Error%>'
|
||||
: (n == "Metacubexd" || n == "Zashboard") ? '<%:Update Failed%>' : '<%:Switch Failed%>';
|
||||
}
|
||||
btn.disabled = false;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
window.delete_dashboard = window.delete_dashboard || function(btn, n) {
|
||||
if (!confirm("<%:Are you sure you want to delete this panel?%>")) return false;
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "delete_dashboard")%>', {name: n}, function(x, status) {
|
||||
if (!x || x.status != 200) { btn.disabled = false; return; }
|
||||
if (status.delete_state == "1") {
|
||||
var defBtn = document.getElementById('default_dashboard_' + n).firstElementChild;
|
||||
if (defBtn.disabled) defBtn.value = '<%:Set to Default%>';
|
||||
defBtn.disabled = true;
|
||||
} else btn.disabled = false;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
window.default_dashboard = window.default_dashboard || function(btn, n) {
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "default_dashboard")%>', {name: n}, function(x, status) {
|
||||
if (!x || x.status != 200) { btn.disabled = false; return; }
|
||||
btn.value = '<%:Default%>';
|
||||
document.querySelectorAll('[id^="default_dashboard_"]').forEach(function(el) {
|
||||
var b = el.firstElementChild;
|
||||
if (b && b !== btn && b.value === '<%:Default%>') {
|
||||
b.disabled = false;
|
||||
b.value = '<%:Set to Default%>';
|
||||
}
|
||||
});
|
||||
});
|
||||
return false;
|
||||
};
|
||||
})();
|
||||
//]]></script>
|
||||
|
||||
<%+cbi/valuefooter%>
|
||||
45
luci-app-openclash/luasrc/view/openclash/switch_mode.htm
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
<fieldset class="cbi-section">
|
||||
<table width="100%">
|
||||
<tr><td width="100%" colspan="4">
|
||||
<p align="center" id="switch_mode">
|
||||
<%:Collecting data...%>
|
||||
</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var switch_mode = document.getElementById('switch_mode');
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "op_mode")%>', null, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( status.op_mode == "redir-host" ) {
|
||||
switch_mode.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch page to Fake-IP mode%>" onclick="return switch_modes(this)"/>';
|
||||
}
|
||||
else {
|
||||
switch_mode.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Switch page to Redir-Host mode%>" onclick="return switch_modes(this)"/>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function switch_modes(btn)
|
||||
{
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_mode")%>', null, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( status.switch_mode == "redir-host" ) {
|
||||
alert('<%:Page has been switched to Fake-IP mode!%>')
|
||||
window.location.href='<%="settings"%>';
|
||||
}
|
||||
else {
|
||||
alert('<%:Page has been switched to Redir-Host mode!%>')
|
||||
window.location.href='<%="settings"%>';
|
||||
}
|
||||
}
|
||||
});
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
//]]></script>
|
||||
|
||||
537
luci-app-openclash/luasrc/view/openclash/tblsection.htm
Normal file
@ -0,0 +1,537 @@
|
||||
<%-
|
||||
local rowcnt = 1
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
local fs = require "luci.openclash"
|
||||
|
||||
function rowstyle()
|
||||
rowcnt = rowcnt + 1
|
||||
return (rowcnt % 2) + 1
|
||||
end
|
||||
|
||||
function width(o)
|
||||
if o.width then
|
||||
if type(o.width) == 'number' then
|
||||
return ' style="width:%dpx"' % o.width
|
||||
end
|
||||
return ' style="width:%s"' % o.width
|
||||
end
|
||||
return ''
|
||||
end
|
||||
|
||||
function table_include(table, value)
|
||||
if table == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
for k, v in pairs(table) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local a={}
|
||||
local e={}
|
||||
if self.sectiontype == "dns_servers" then
|
||||
table.insert(e, {name="nameserver", title="nameserver"})
|
||||
table.insert(e, {name="fallback", title="fallback"})
|
||||
table.insert(e, {name="default", title="default-nameserver"})
|
||||
else
|
||||
for t,o in ipairs(fs.glob("/etc/openclash/config/*"))do
|
||||
a=fs.stat(o)
|
||||
if a then
|
||||
e[t]={}
|
||||
e[t].name=fs.basename(o)
|
||||
end
|
||||
end
|
||||
|
||||
if not table_include(e, "all") then
|
||||
table.insert(e, 1, {name="all"})
|
||||
end
|
||||
end
|
||||
|
||||
local file_path = fs.get_file_path_from_request()
|
||||
local cfg_name = (file_path and fs.basename(file_path)) or nil
|
||||
|
||||
if not cfg_name and uci:get("openclash", "config", "config_path") then
|
||||
cfg_name = fs.basename(uci:get("openclash", "config", "config_path"))
|
||||
end
|
||||
local sectiontype = "_"..self.config.."_"..string.match(self.sectiontype, "[%w_]+") or "self.sectiontype"
|
||||
-%>
|
||||
|
||||
<style type="text/css">
|
||||
*{margin: 0;padding: 0;}
|
||||
|
||||
ul{
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
h3{
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.cbi-value-field .cbi-dropdown, .cbi-value-field .cbi-input-select, .cbi-value input[type="text"], .cbi-value input[type="password"] {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%>{
|
||||
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-<%=self.config%>-<%=self.sectiontype%> ul{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: #f1f5f9;
|
||||
border-radius: 8px;
|
||||
border-bottom: none !important;
|
||||
border: none !important;
|
||||
gap: 5px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li{
|
||||
flex: 1 1 0;
|
||||
min-width: 120px;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px 0;
|
||||
height: 100%;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
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-<%=self.config%>-<%=self.sectiontype%> ul 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-<%=self.config%>-<%=self.sectiontype%> ul li a{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
cursor: inherit;
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
transition: color 0.18s;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li.cbi-tab > a{
|
||||
color: #fff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li.cbi-tab-disabled{
|
||||
color: #fff;
|
||||
background: #f9f9f9;
|
||||
opacity: 0.7;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li.cbi-tab-disabled a{
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):hover,
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):focus{
|
||||
color: #3b82f6;
|
||||
background: rgba(59,130,246,0.10);
|
||||
}
|
||||
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):hover a,
|
||||
#tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):focus a{
|
||||
color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
#tab-content .dom-<%=self.config%>-<%=self.sectiontype%>{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tab-content .dom-<%=self.config%>-<%=self.sectiontype%> ul li{
|
||||
float: left;
|
||||
margin: 15px 10px;
|
||||
width: 225px;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%>{
|
||||
background: #374151;
|
||||
border-bottom: 1px solid #374151;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul{
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li{
|
||||
background: #1f2937;
|
||||
color: #a1a1aa;
|
||||
border-color: #374151;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li.cbi-tab{
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li.cbi-tab a{
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li.cbi-tab-disabled{
|
||||
color: #1f2937;
|
||||
background: #1f2937;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li.cbi-tab-disabled a{
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):hover,
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):focus{
|
||||
color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
background: #22304a;
|
||||
}
|
||||
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):hover a,
|
||||
:root[data-darkmode="true"] #tab-header-<%=self.config%>-<%=self.sectiontype%> ul li:not(.cbi-tab):focus a{
|
||||
color: #60a5fa !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- tblsection -->
|
||||
<fieldset class="cbi-section" id="cbi-<%=self.config%>-<%=self.sectiontype%>">
|
||||
<%- if self.title and #self.title > 0 then -%>
|
||||
<h3><%=self.title%></h3>
|
||||
<%- end -%>
|
||||
<%- if self.sortable then -%>
|
||||
<input type="hidden" id="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" name="cbi.sts.<%=self.config%>.<%=self.sectiontype%>" value="" />
|
||||
<%- end -%>
|
||||
<div class="cbi-section-descr"><%=self.description%></div>
|
||||
<%- local count = 0 -%>
|
||||
<div id="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tabmenu">
|
||||
<ul class="cbi-tabmenu">
|
||||
<%-
|
||||
if self.sectiontype == "dns_servers" then
|
||||
for i, tab in ipairs(e) do
|
||||
if i == 1 then
|
||||
-%>
|
||||
<li name="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tab" data-group="<%=tab.name%>"><a title="<%=tab.name%>"><%=tab.title%></a></li>
|
||||
<%- else -%>
|
||||
<li name="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tab-disabled" data-group="<%=tab.name%>"><a title="<%=tab.name%>"><%=tab.title%></a></li>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<%- elseif #e ~= 1 then
|
||||
for i, tab in ipairs(e) do
|
||||
if tab.name == cfg_name and cfg_name then
|
||||
-%>
|
||||
<li name="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tab" data-group="<%=tab.name%>"><a title="<%=tab.name%>"><%=tab.name%></a></li>
|
||||
<%- elseif not cfg_name and tab.name == "all" then -%>
|
||||
<li name="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tab" data-group="<%=tab.name%>"><a title="All">ALL</a></li>
|
||||
<%- elseif tab.name == "all" then -%>
|
||||
<li name="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tab-disabled" data-group="<%=tab.name%>"><a title="All">ALL</a></li>
|
||||
<%- else -%>
|
||||
<li name="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tab-disabled" data-group="<%=tab.name%>"><a title="<%=tab.name%>"><%=tab.name%></a></li>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<%- else -%>
|
||||
<li name="tab-header-<%=self.config%>-<%=self.sectiontype%>" class="cbi-tab" data-group="all"><a title="All">ALL</a></li>
|
||||
<%- end -%>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="tab-content">
|
||||
<%- for i, tab in ipairs(e) do
|
||||
if self.sectiontype == "dns_servers" then
|
||||
if tab.name == "nameserver" then
|
||||
-%>
|
||||
<div class="dom-<%=self.config%>-<%=self.sectiontype%>" style="display: block;" data-group="<%=tab.name%>">
|
||||
<%- else -%>
|
||||
<div class="dom-<%=self.config%>-<%=self.sectiontype%>" data-group="<%=tab.name%>">
|
||||
<%- end -%>
|
||||
<%- elseif #e ~= 1 and tab.name == cfg_name and cfg_name then -%>
|
||||
<div class="dom-<%=self.config%>-<%=self.sectiontype%>" style="display: block;" data-group="<%=tab.name%>">
|
||||
<%- elseif #e ~= 1 and tab.name == "all" and not cfg_name then -%>
|
||||
<div class="dom-<%=self.config%>-<%=self.sectiontype%>" style="display: block;" data-group="<%=tab.name%>">
|
||||
<%- elseif #e == 1 then -%>
|
||||
<div class="dom-<%=self.config%>-<%=self.sectiontype%>" style="display: block;" data-group="all">
|
||||
<%- else -%>
|
||||
<div class="dom-<%=self.config%>-<%=self.sectiontype%>" data-group="<%=tab.name%>">
|
||||
<%- end -%>
|
||||
<table class="table cbi-section-table">
|
||||
<tr class="tr cbi-section-table-titles">
|
||||
<%- if not self.anonymous then -%>
|
||||
<%- if self.sectionhead then -%>
|
||||
<th class="th cbi-section-table-cell"><%=self.sectionhead%></th>
|
||||
<%- else -%>
|
||||
<th> </th>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<%- for i, k in pairs(self.children) do if not k.optional then -%>
|
||||
<%- if i == 1 then -%>
|
||||
<th class="th cbi-section-table-cell"><%:Serial Number%></th>
|
||||
<%- end -%>
|
||||
<th class="th cbi-section-table-cell"<%=width(k)%>>
|
||||
<%- if k.titleref then -%><a title="<%=self.titledesc or translate('Go to relevant configuration page')%>" class="cbi-title-ref" href="<%=k.titleref%>"><%- end -%>
|
||||
<%-=k.title-%>
|
||||
<%- if k.titleref then -%></a><%- end -%>
|
||||
</th>
|
||||
<%- count = count + 1; end; end; if self.sortable then -%>
|
||||
<th class="th cbi-section-table-cell"><%:Sort%></th>
|
||||
<%- end; if self.extedit or self.addremove then -%>
|
||||
<th class="th cbi-section-table-cell"><%:Edit%></th>
|
||||
<%- count = count + 1; end -%>
|
||||
</tr>
|
||||
<tr class="tr cbi-section-table-descr">
|
||||
<%- if not self.anonymous then -%>
|
||||
<%- if self.sectiondesc then -%>
|
||||
<th class="th cbi-section-table-cell"><%=self.sectiondesc%></th>
|
||||
<%- else -%>
|
||||
<th class="th cbi-section-table-cell"></th>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<%- for i, k in pairs(self.children) do if not k.optional then -%>
|
||||
<%- if i == 1 then -%>
|
||||
<th class="th cbi-section-table-cell"></th>
|
||||
<%- end -%>
|
||||
<th class="th cbi-section-table-cell"<%=width(k)%>><%=k.description%></th>
|
||||
<%- end; end; if self.sortable then -%>
|
||||
<th class="th cbi-section-table-cell"></th>
|
||||
<%- end; if self.extedit or self.addremove then -%>
|
||||
<th class="th cbi-section-table-cell"></th>
|
||||
<%- end -%>
|
||||
</tr>
|
||||
<%- local isempty = true
|
||||
local num = 1
|
||||
for i, k in ipairs(self:cfgsections()) do
|
||||
local section = k
|
||||
local should_render = false
|
||||
local section_name
|
||||
if self.sectiontype == "dns_servers" then
|
||||
section_name = uci:get("openclash", section, "group")
|
||||
else
|
||||
section_name = uci:get("openclash", section, "config")
|
||||
end
|
||||
if section_name == tab.name then
|
||||
should_render = true
|
||||
end
|
||||
if should_render then
|
||||
isempty = false
|
||||
scope = { valueheader = "cbi/cell_valueheader", valuefooter = "cbi/cell_valuefooter" }
|
||||
-%>
|
||||
<tr class="tr cbi-section-table-row<% if self.extedit or self.rowcolors then %> cbi-rowstyle-<%=rowstyle()%><% end %>" id="cbi-<%=self.config%>-<%=section%>">
|
||||
<%- if not self.anonymous then -%>
|
||||
<th><h3><%=(type(self.sectiontitle) == "function") and self:sectiontitle(section) or k%></h3></th>
|
||||
<%- end -%>
|
||||
<td class="td cbi-section-table-cell">
|
||||
<p><%=num%></p>
|
||||
<%- num = num + 1 -%>
|
||||
</td>
|
||||
<%-
|
||||
for k, node in ipairs(self.children) do
|
||||
if not node.optional then
|
||||
node:render(section, scope or {})
|
||||
end
|
||||
end
|
||||
-%>
|
||||
<%- if self.sortable then -%>
|
||||
<td class="tdcbi-section-table-cell">
|
||||
<input class="cbi-button cbi-button-up" type="button" value="<%:∧%>" onclick="return cbi_row_swap(this, true, 'cbi.sts.<%=self.config%>.<%=self.sectiontype%>')" alt="<%:Move up%>" title="<%:Move up%>" />
|
||||
<input class="cbi-button cbi-button-down" type="button" value="<%:∨%>" onclick="return cbi_row_swap(this, false, 'cbi.sts.<%=self.config%>.<%=self.sectiontype%>')" alt="<%:Move down%>" title="<%:Move down%>" />
|
||||
</td>
|
||||
<%- end -%>
|
||||
|
||||
<%- if self.extedit or self.addremove then -%>
|
||||
<td class="td cbi-section-table-cell">
|
||||
<%- if self.extedit then -%>
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>"
|
||||
<%- if type(self.extedit) == "string" then
|
||||
%> onclick="location.href='<%=self.extedit:format(section)%>';return switch_to_tab<%=sectiontype%>()"
|
||||
<%- elseif type(self.extedit) == "function" then
|
||||
%> onclick="location.href='<%=self:extedit(section)%>';return switch_to_tab<%=sectiontype%>()"
|
||||
<%- end
|
||||
%> alt="<%:Edit%>" title="<%:Edit%>" />
|
||||
<%- end; if self.addremove then -%>
|
||||
<%- if file_path then -%>
|
||||
<input class="btn cbi-button cbi-button-remove" type="submit" value="<%:Delete%>"
|
||||
onclick="this.form.cbi_state='del-section'; var redirectUrl = 'servers?file=<%=file_path%>'; var originalSubmit = this.form.onsubmit; this.form.onsubmit = function(e) { var result = true; if (originalSubmit) { result = originalSubmit.call(this, e); } if (result !== false) { setTimeout(function() { window.location.href = redirectUrl; }, 200); } return result; }; return switch_to_tab<%=sectiontype%>()"
|
||||
name="cbi.rts.<%=self.config%>.<%=k%>" alt="<%:Delete%>" title="<%:Delete%>" />
|
||||
<%- else -%>
|
||||
<input class="btn cbi-button cbi-button-remove" type="submit" value="<%:Delete%>" onclick="this.form.cbi_state='del-section'; return switch_to_tab<%=sectiontype%>()" name="cbi.rts.<%=self.config%>.<%=k%>" alt="<%:Delete%>" title="<%:Delete%>" />
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
</td>
|
||||
<%- end -%>
|
||||
<%-
|
||||
end
|
||||
-%>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
|
||||
<%- if isempty then -%>
|
||||
<tr class="tr cbi-section-table-row placeholder">
|
||||
<td class="td" colspan="<%=count%>"><em><%:This section contains no values yet%></em></td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
</table>
|
||||
|
||||
<%- if self.error then -%>
|
||||
<div class="cbi-section-error">
|
||||
<ul><%- for _, c in pairs(self.error) do for _, e in ipairs(c) do -%>
|
||||
<li><%=pcdata(e):gsub("\n","<br />")%></li>
|
||||
<%- end end -%></ul>
|
||||
</div>
|
||||
<%- end -%>
|
||||
|
||||
<%- if self.addremove then -%>
|
||||
<%- if self.template_addremove then include(self.template_addremove) else -%>
|
||||
<div class="cbi-section-create cbi-tblsection-create">
|
||||
<%- if self.anonymous then -%>
|
||||
<input class="btn cbi-button cbi-button-add" type="submit" onclick="return switch_to_tab<%=sectiontype%>('<%=tab.name%>')" value="<%:Add%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" title="<%:Add%>" />
|
||||
<%- else -%>
|
||||
<% if self.invalid_cts then -%><div class="cbi-section-error"><% end %>
|
||||
<input type="text" class="cbi-section-create-name" id="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" name="cbi.cts.<%=self.config%>.<%=self.sectiontype%>.<%=section%>" data-type="uciname" data-optional="true" />
|
||||
<input type="submit" class="cbi-button cbi-button-add" onclick="this.form.cbi_state='add-section'; return switch_to_tab<%=sectiontype%>('<%=tab.name%>')" value="<%:Add%>" />
|
||||
<% if self.invalid_cts then -%>
|
||||
<br /><%:Invalid%></div>
|
||||
<%- end %>
|
||||
<%- end %>
|
||||
</div>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<%- end -%>
|
||||
<%- if self.addremove then -%>
|
||||
<input type="hidden" id="cbi.cts.tagname.<%=self.config%>.<%=self.sectiontype%>" name="cbi.cts.tagname.<%=self.config%>.<%=self.sectiontype%>" value="" />
|
||||
<%- end -%>
|
||||
</div>
|
||||
</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 switch_to_tab<%=sectiontype%>(tab_name){
|
||||
if(titles<%=sectiontype%>.length != divs<%=sectiontype%>.length) return;
|
||||
|
||||
for(var j=0; j < titles<%=sectiontype%>.length; j++){
|
||||
if(titles<%=sectiontype%>[j].className === 'cbi-tab'){
|
||||
localStorage.setItem("titles<%=sectiontype%>", j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tab_name) {
|
||||
var name = document.getElementById('cbi.cts.tagname.<%=self.config%>.<%=self.sectiontype%>');
|
||||
if (name) {
|
||||
name.value = tab_name;
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem("id<%=sectiontype%>",'cbi-<%=self.config%>-<%=self.sectiontype%>');
|
||||
};
|
||||
|
||||
function onload<%=sectiontype%>() {
|
||||
if (isDarkBackground(document.body)) {
|
||||
document.documentElement.setAttribute('data-darkmode', 'true');
|
||||
};
|
||||
|
||||
if(titles<%=sectiontype%>.length != divs<%=sectiontype%>.length) return;
|
||||
|
||||
if (localStorage.getItem("id<%=sectiontype%>")) {
|
||||
if (document.getElementById(localStorage.getItem("id<%=sectiontype%>"))) {
|
||||
document.getElementById(localStorage.getItem("id<%=sectiontype%>")).scrollIntoView();
|
||||
};
|
||||
localStorage.removeItem("id<%=sectiontype%>");
|
||||
};
|
||||
|
||||
if (localStorage.getItem("titles<%=sectiontype%>")) {
|
||||
if (titles<%=sectiontype%>[localStorage.getItem("titles<%=sectiontype%>")]) {
|
||||
for(var j=0; j < titles<%=sectiontype%>.length; j++){
|
||||
titles<%=sectiontype%>[j].className = 'cbi-tab-disabled';
|
||||
divs<%=sectiontype%>[j].style.display = 'none';
|
||||
};
|
||||
titles<%=sectiontype%>[localStorage.getItem("titles<%=sectiontype%>")].className = 'cbi-tab';
|
||||
divs<%=sectiontype%>[localStorage.getItem("titles<%=sectiontype%>")].style.display = 'block';
|
||||
};
|
||||
localStorage.removeItem("titles<%=sectiontype%>");
|
||||
};
|
||||
|
||||
for(var i=0; i < titles<%=sectiontype%>.length; i++){
|
||||
var li<%=sectiontype%> = titles<%=sectiontype%>[i];
|
||||
li<%=sectiontype%>.id = i;
|
||||
|
||||
var a<%=sectiontype%> = li<%=sectiontype%>.querySelector('a');
|
||||
if (a<%=sectiontype%>) {
|
||||
if (a<%=sectiontype%>.scrollWidth > a<%=sectiontype%>.clientWidth) {
|
||||
a<%=sectiontype%>.style.textAlign = 'left';
|
||||
} else {
|
||||
a<%=sectiontype%>.style.textAlign = 'center';
|
||||
}
|
||||
}
|
||||
|
||||
li<%=sectiontype%>.onclick = function(){
|
||||
for(var j=0; j < titles<%=sectiontype%>.length; j++){
|
||||
titles<%=sectiontype%>[j].className = 'cbi-tab-disabled';
|
||||
divs<%=sectiontype%>[j].style.display = 'none';
|
||||
};
|
||||
this.className = 'cbi-tab';
|
||||
divs<%=sectiontype%>[this.id].style.display = 'block';
|
||||
localStorage.setItem("titles<%=sectiontype%>",this.id);
|
||||
};
|
||||
|
||||
li<%=sectiontype%>.onTouchStart = function(){
|
||||
for(var j=0; j < titles<%=sectiontype%>.length; j++){
|
||||
titles<%=sectiontype%>[j].className = 'cbi-tab-disabled';
|
||||
divs<%=sectiontype%>[j].style.display = 'none';
|
||||
};
|
||||
this.className = 'cbi-tab';
|
||||
divs<%=sectiontype%>[this.id].style.display = 'block';
|
||||
localStorage.setItem("titles<%=sectiontype%>",this.id);
|
||||
};
|
||||
};
|
||||
};
|
||||
//]]>
|
||||
</script>
|
||||
142
luci-app-openclash/luasrc/view/openclash/toolbar_show.htm
Normal file
@ -0,0 +1,142 @@
|
||||
<%
|
||||
local fs = require "luci.openclash"
|
||||
local plugin_version = fs.oc_version()
|
||||
%>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/luci-static/resources/openclash/css/oc.css?v=<%=plugin_version%>">
|
||||
</head>
|
||||
|
||||
<li id="tool_label" class="tool_label">
|
||||
<span class="tool_label_span">
|
||||
<%:Current Config File%>:
|
||||
<select class="tool_label_select" id="cfg_name">
|
||||
</select>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value="<%:SwiTch%>" onclick="return switch_config(this)" />
|
||||
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<script src="/luci-static/resources/openclash/js/common.js"></script>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
var config_name = document.getElementById('cfg_name');
|
||||
var tool_label = document.getElementById('tool_label');
|
||||
var Commit = document.getElementById('cbi-table-1-Commit');
|
||||
var Apply = document.getElementById('cbi-table-1-Apply');
|
||||
var Back = document.getElementById('cbi-table-1-Back');
|
||||
var Load_Config = document.getElementById('cbi-table-1-Load_Config');
|
||||
var Delete_Unused_Servers = document.getElementById('cbi-table-1-Delete_Unused_Servers');
|
||||
var Delete_Servers = document.getElementById('cbi-table-1-Delete_Servers');
|
||||
var Delete_Proxy_Provider = document.getElementById('cbi-table-1-Delete_Proxy_Provider');
|
||||
var Delete_Groups = document.getElementById('cbi-table-1-Delete_Groups');
|
||||
var rule_mg = document.getElementById('cbi-table-1-rule_mg');
|
||||
var pro_mg = document.getElementById('cbi-table-1-pro_mg');
|
||||
|
||||
setTimeout("get_header()",100);
|
||||
|
||||
if (Commit) {
|
||||
Commit.style.textAlign="center";
|
||||
}
|
||||
if (Apply) {
|
||||
Apply.style.textAlign="center";
|
||||
}
|
||||
if (Back) {
|
||||
Back.style.textAlign="center";
|
||||
}
|
||||
if (Load_Config) {
|
||||
Load_Config.style.textAlign="center";
|
||||
Delete_Unused_Servers.style.textAlign="center";
|
||||
Delete_Servers.style.textAlign="center";
|
||||
Delete_Proxy_Provider.style.textAlign="center";
|
||||
Delete_Groups.style.textAlign="center";
|
||||
}
|
||||
if (rule_mg) {
|
||||
rule_mg.style.textAlign="center";
|
||||
pro_mg.style.textAlign="center";
|
||||
}
|
||||
|
||||
if (tool_label.style.display != "none") {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "config_name")%>', null, function(x, status) {
|
||||
if (x && x.status == 200 && status.config_name != "") {
|
||||
for(var i in status.config_name){
|
||||
config_name.options.add(new Option(status.config_name[i].name,status.config_name[i].name));
|
||||
}
|
||||
if (status.config_path != "") {
|
||||
config_name.value = status.config_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
config_name.options.add(new Option("<%:Not Select%>",""));
|
||||
config_name.value = "";
|
||||
}
|
||||
}
|
||||
else if (x && x.status == 200 && status.config_path != "") {
|
||||
config_name.options.add(new Option(status.config_path,status.config_path));
|
||||
config_name.value = status.config_path;
|
||||
}
|
||||
else {
|
||||
tool_label.style.display = "none";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function get_header() {
|
||||
var header = document.getElementsByClassName("tabmenu-item-log ")[0];
|
||||
if (header) {
|
||||
insertAfter(tool_label,header);
|
||||
}
|
||||
else {
|
||||
setTimeout("get_header()",100);
|
||||
}
|
||||
}
|
||||
|
||||
function insertAfter(newElement, targetElement) {
|
||||
var parent = targetElement.parentNode;
|
||||
if (parent.lastChild == targetElement) {
|
||||
parent.appendChild(newElement, targetElement);
|
||||
}
|
||||
else {
|
||||
parent.insertBefore(newElement, targetElement.nextSibling);
|
||||
};
|
||||
};
|
||||
|
||||
function switch_config(btn)
|
||||
{
|
||||
if (config_name.value && config_name.value != "") {
|
||||
btn.disabled = true;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_config")%>', {config_name: config_name.value}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
btn.disabled = false;
|
||||
alert(' <%:Config File%>: ' + config_name.value + ' <%:switching succeeded!%>')
|
||||
window.location.href=location.pathname;
|
||||
}
|
||||
else {
|
||||
alert(' <%:Config File%>: ' + config_name.value + ' <%:switching failed!%>')
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function restore_config(btn)
|
||||
{
|
||||
btn.value = '<%:Restore Default%>';
|
||||
btn.disabled = true;
|
||||
var r = confirm("<%:Are you sure want to restore the default config?%>")
|
||||
if (r == true) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "restore")%>', null, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
alert('<%:Restore succeeded!%>')
|
||||
window.location.href='<%="settings"%>';
|
||||
}
|
||||
else {
|
||||
alert('<%:Restore failed!%>')
|
||||
window.location.href='<%="settings"%>';
|
||||
}
|
||||
});
|
||||
}
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
//]]></script>
|
||||
414
luci-app-openclash/luasrc/view/openclash/update.htm
Normal file
@ -0,0 +1,414 @@
|
||||
<fieldset class="cbi-section">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td width="100%" colspan="6">
|
||||
<p align="center" id="update_tip" class="oc" style="background-color: unset;">
|
||||
<b style="color: var(--info-color);"><%:Note: if the update fails, you can manually download and upload%></b>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="16.6%" align="right"><%:Compiled Version%></td>
|
||||
<td width="16.6%" align="left">
|
||||
<select class="cbi-input-select" name="CORE_VERSION" id="CORE_VERSION">
|
||||
<option value="linux-386"><%:linux-386%></option>
|
||||
<option value="linux-amd64-v1"><%:linux-amd64-v1(x86-64)%></option>
|
||||
<option value="linux-amd64-v2"><%:linux-amd64-v2(x86-64)%></option>
|
||||
<option value="linux-amd64-v3"><%:linux-amd64-v3(x86-64)%></option>
|
||||
<option value="linux-armv5"><%:linux-armv5%></option>
|
||||
<option value="linux-armv6"><%:linux-armv6%></option>
|
||||
<option value="linux-armv7"><%:linux-armv7%></option>
|
||||
<option value="linux-arm64"><%:linux-arm64(armv8)%></option>
|
||||
<option value="linux-loong64-abi1"><%:linux-loong64-abi1%></option>
|
||||
<option value="linux-loong64-abi2"><%:linux-loong64-abi2%></option>
|
||||
<option value="linux-riscv64"><%:linux-riscv64%></option>
|
||||
<option value="linux-s390x"><%:linux-s390x%></option>
|
||||
<option value="linux-mips-hardfloat"><%:linux-mips-hardfloat%></option>
|
||||
<option value="linux-mips-softfloat"><%:linux-mips-softfloat%></option>
|
||||
<option value="linux-mips64"><%:linux-mips64%></option>
|
||||
<option value="linux-mips64le"><%:linux-mips64le%></option>
|
||||
<option value="linux-mipsle-softfloat"><%:linux-mipsle-softfloat%></option>
|
||||
<option value="linux-mipsle-hardfloat"><%:linux-mipsle-hardfloat%></option>
|
||||
<option value="0"><%:Not Set%></option>
|
||||
</select>
|
||||
</td>
|
||||
<td width="16.6%" align="right"><%:Release Branch%></td>
|
||||
<td width="16.6%" align="left">
|
||||
<select class="cbi-input-select" name="RELEASE_BRANCH" id="RELEASE_BRANCH">
|
||||
<option value="master">Master</option>
|
||||
<option value="dev">Developer</option>
|
||||
</select>
|
||||
</td>
|
||||
<td width="16.6%" align="right"><%:Smart Core%>
|
||||
<a href="javascript:void(0);" onclick="window.open('https://github.com/vernesong/mihomo/releases', '_blank');" style="color: #0066cc; text-decoration: none;" title="<%:View core infos that support smart group%>">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
<td width="16.6%" align="left">
|
||||
<select class="cbi-input-select" name="SMART_ENABLE" id="SMART_ENABLE">
|
||||
<option value="0"><%:Disabled%></option>
|
||||
<option value="1"><%:Enable%></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
<fieldset class="cbi-section">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td width="100%" colspan="4">
|
||||
<p align="center">
|
||||
<b><%:Core Update%></b>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="25%"><%:CPU Architecture%></td><td width="25%" align="left" id="CPU_MODEL"><%:Collecting data...%></td>
|
||||
<td width="25%"><%:Last Check Update%></td><td width="25%" align="left" id="CHECKTIME"><%:Collecting data...%></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" colspan="4">
|
||||
<p align="center">
|
||||
<b><%:Core path:%>/etc/openclash/core/clash_meta</b>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="25%">[Meta] <%:Current Core%></td><td width="25%" align="left" id="CORE_META_CV"><%:Collecting data...%></td><td width="25%">[Meta] <%:Latest Core%></td><td width="25%" align="left" id="CORE_META_LV"><%:Collecting data...%></td></tr>
|
||||
<tr><td width="25%"><%:Update Core%></td><td width="25%" align="left" id="core_meta_up"><%:Collecting data...%></td><td width="25%"><%:Download Latest Core%></td><td width="25%" align="left" id="ma_core_meta_up"><%:Collecting data...%></td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
<fieldset class="cbi-section">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td width="100%" colspan="4">
|
||||
<p align="center">
|
||||
<b><%:Client Update%></b>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td width="25%"><%:Current Client%></td><td width="25%" align="left" id="OP_CV"><%:Collecting data...%></td><td width="25%"><%:Latest Client%></td><td width="25%" align="left" id="OP_LV"><%:Collecting data...%></td></tr>
|
||||
<tr><td width="25%"><%:Update Client%></td><td width="25%" align="left" id="op_up"><%:Collecting data...%></td><td width="25%"><%:Download Latest Client%></td><td width="25%" align="left" id="ma_op_up"><%:Collecting data...%></td></tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
<fieldset class="cbi-section">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td width="25%" align="center">
|
||||
<div style="display: inline-block; white-space: nowrap;">
|
||||
<select class="cbi-input-select" name="BACKUP_SELECT" id="BACKUP_SELECT">
|
||||
<option value="backup"><%:Backup File%></option>
|
||||
<option value="backup_ex_core"><%:Backup Exclude Cores%></option>
|
||||
<option value="backup_only_core"><%:Backup Core%></option>
|
||||
<option value="backup_only_config"><%:Backup Config%></option>
|
||||
<option value="backup_only_rule"><%:Backup Rule Provider%></option>
|
||||
<option value="backup_only_proxy"><%:Backup Proxy Provider%></option>
|
||||
</select>
|
||||
<span id="backup">
|
||||
<%:Collecting data...%>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td width="25%" align="center" id="restore">
|
||||
<%:Collecting data...%>
|
||||
</td>
|
||||
<td width="25%" align="center" id="remove_core">
|
||||
<%:Collecting data...%>
|
||||
</td>
|
||||
<td width="25%" align="center" id="one_key_update_cdn">
|
||||
<%:Collecting data...%>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var core_version = document.getElementById('CORE_VERSION');
|
||||
var checktime = document.getElementById('CHECKTIME');
|
||||
var cpu_model = document.getElementById('CPU_MODEL');
|
||||
var core_meta_cv = document.getElementById('CORE_META_CV');
|
||||
var core_meta_lv = document.getElementById('CORE_META_LV');
|
||||
var op_cv = document.getElementById('OP_CV');
|
||||
var op_lv = document.getElementById('OP_LV');
|
||||
var core_meta_up = document.getElementById('core_meta_up');
|
||||
var op_up = document.getElementById('op_up');
|
||||
var update_tip = document.getElementById('update_tip');
|
||||
var ma_core_meta_up = document.getElementById('ma_core_meta_up');
|
||||
var ma_op_up = document.getElementById('ma_op_up');
|
||||
var restore = document.getElementById('restore');
|
||||
var backup = document.getElementById('backup');
|
||||
var backup_select = document.getElementById('BACKUP_SELECT');
|
||||
var one_key_update_cdn = document.getElementById('one_key_update_cdn');
|
||||
var remove_core = document.getElementById('remove_core');
|
||||
var release_branch = document.getElementById('RELEASE_BRANCH');
|
||||
var smart_enable = document.getElementById('SMART_ENABLE');
|
||||
core_meta_up.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reload" value="<%:Check And Update%>" onclick="return core_update(this,\'Meta\')"/>';
|
||||
op_up.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reload" value="<%:Check And Update%>" onclick="return op_update(this)"/>';
|
||||
ma_core_meta_up.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reload" value="<%:Download%>" onclick="return ma_core_update(this,\'Meta\')"/>';
|
||||
ma_op_up.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reload" value="<%:Download%>" onclick="return ma_op_update(this)"/>';
|
||||
restore.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Restore Default%>" onclick="return restore_config(this)"/>';
|
||||
one_key_update_cdn.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Check Update%>" onclick="return all_one_key_update_cdn(this)"/>';
|
||||
remove_core.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Remove Core%>" onclick="return remove_all_core(this)"/>';
|
||||
backup.innerHTML = '<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Backup File%>" onclick="return backup_file(this)"/>';
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update_info")%>', null, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( status.corever && status.corever != "0" && status.corever != "" ) {
|
||||
core_version.value = status.corever;
|
||||
}
|
||||
else {
|
||||
core_version.value = "0";
|
||||
}
|
||||
if ( status.release_branch && status.release_branch != "" ) {
|
||||
release_branch.value = status.release_branch;
|
||||
}
|
||||
else {
|
||||
release_branch.value = "master";
|
||||
}
|
||||
if ( status.smart_enable && status.smart_enable != "" ) {
|
||||
smart_enable.value = status.smart_enable;
|
||||
}
|
||||
else {
|
||||
smart_enable.value = "0";
|
||||
}
|
||||
}
|
||||
else {
|
||||
core_version.value = "0";
|
||||
release_branch.value = "master";
|
||||
smart_enable.value = "0";
|
||||
}
|
||||
});
|
||||
|
||||
XHR.poll(300, '<%=luci.dispatcher.build_url("admin", "services", "openclash", "get_last_version")%>', null, function(x, status) {
|
||||
});
|
||||
|
||||
function updateStatus(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
cpu_model.innerHTML = status.coremodel ? "<b style=color:green>"+status.coremodel+"</b>" : "<b style=color:red><%:Model Not Found%></b>";
|
||||
|
||||
if ( status.upchecktime != "1" ) {
|
||||
checktime.innerHTML = "<b style=color:green>"+status.upchecktime+"</b>";
|
||||
} else {
|
||||
checktime.innerHTML = "<b style=color:red><%:Check Failed%></b>";
|
||||
}
|
||||
if ( status.coremetacv == "0" ) {
|
||||
core_meta_cv.innerHTML = "<b style=color:red><%:Unknown%></b>";
|
||||
} else {
|
||||
core_meta_cv.innerHTML = "<b style=color:green>"+status.coremetacv+"</b>";
|
||||
}
|
||||
|
||||
var coremetalvis = status.corelv;
|
||||
|
||||
if (coremetalvis != status.coremetacv && coremetalvis != "" && coremetalvis != "loading...") {
|
||||
core_meta_lv.innerHTML = "<b style=color:green>"+coremetalvis+" <%:<New>%></b>";
|
||||
} else if (coremetalvis != "" && coremetalvis == status.coremetacv && coremetalvis != "loading...") {
|
||||
core_meta_lv.innerHTML = "<b style=color:green>"+coremetalvis+"</b>";
|
||||
} else if (coremetalvis == "loading...") {
|
||||
core_meta_lv.innerHTML = "<b style=color:green><%:Getting Last Version...%></b>";
|
||||
} else {
|
||||
core_meta_lv.innerHTML = "<b style=color:red><%:Unknown%></b>";
|
||||
}
|
||||
|
||||
var oplv = status.oplv;
|
||||
op_cv.innerHTML = status.opcv != "0" ? "<b style=color:green>"+status.opcv+"</b>" : "<b style=color:red><%:Unknown%></b>";
|
||||
|
||||
if (oplv != "" && oplv != "loading..." && status.opcv != "0") {
|
||||
function compareVersions(v1, v2) {
|
||||
var ver1 = v1.replace(/^v/, '').split('.');
|
||||
var ver2 = v2.replace(/^v/, '').split('.');
|
||||
|
||||
var maxLen = Math.max(ver1.length, ver2.length);
|
||||
while (ver1.length < maxLen) ver1.push('0');
|
||||
while (ver2.length < maxLen) ver2.push('0');
|
||||
|
||||
for (var i = 0; i < maxLen; i++) {
|
||||
var num1 = parseInt(ver1[i], 10);
|
||||
var num2 = parseInt(ver2[i], 10);
|
||||
if (num1 > num2) return 1;
|
||||
if (num1 < num2) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (compareVersions(oplv, status.opcv) > 0) {
|
||||
op_lv.innerHTML = "<b style=color:green>"+oplv+" <%:<New>%></b>";
|
||||
} else {
|
||||
op_lv.innerHTML = "<b style=color:green>"+oplv+"</b>";
|
||||
}
|
||||
} else if (oplv != "" && oplv != "loading...") {
|
||||
op_lv.innerHTML = "<b style=color:green>"+oplv+"</b>";
|
||||
} else if (oplv == "loading...") {
|
||||
op_lv.innerHTML = "<b style=color:green><%:Getting Last Version...%></b>";
|
||||
} else {
|
||||
op_lv.innerHTML = "<b style=color:red><%:Unknown%></b>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update")%>', null, updateStatus);
|
||||
|
||||
XHR.poll(10, '<%=luci.dispatcher.build_url("admin", "services", "openclash", "update")%>', null, updateStatus);
|
||||
|
||||
function handleStartLog(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( status.startlog == "\n" || status.startlog == "" || status.startlog == "##FINISH##\n" ) {
|
||||
var rdmdl = Math.floor(Math.random()*3)+1;
|
||||
if(rdmdl == 1) {
|
||||
update_tip.innerHTML = '<b style=color:var(--info-color)><%:Note: if the update fails, you can manually download and upload%></b>';
|
||||
}
|
||||
else if(rdmdl == 2) {
|
||||
update_tip.innerHTML = '<b style=color:var(--info-color)><%:Note: the client may not support update, because the firmware with squashfs format will not release flash space after updating%></b>';
|
||||
}
|
||||
else if(rdmdl == 3) {
|
||||
update_tip.innerHTML = '<b style=color:var(--info-color)><%:Note: options will auto-save when you click to update or download%></b>';
|
||||
}
|
||||
} else if ( status.startlog.match("level=fatal")) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "del_start_log")%>', null, function(x) {});
|
||||
if ( status.startlog.match("level=fatal") ) {
|
||||
alert('<%:OpenClash Start Failed%> :\n\n' + status.startlog.split('msg=')[1]);
|
||||
}
|
||||
} else if ( status.startlog != "\n" && status.startlog != "" && status.startlog != "##FINISH##\n" ) {
|
||||
for (var levelKey in window.levelTranslations) {
|
||||
var translatedText = '[' + window.levelTranslations[levelKey] + ']';
|
||||
if (status.startlog.match(translatedText)) {
|
||||
update_tip.innerHTML = '<b style=color:var(--' + levelKey + '-color)>' + status.startlog + '</b>';
|
||||
return;
|
||||
}
|
||||
}
|
||||
update_tip.innerHTML = '<b style=color:var(--info-color)>' + status.startlog + '</b>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XHR.poll(3, '<%=luci.dispatcher.build_url("admin", "services", "openclash", "startlog")%>', null, handleStartLog);
|
||||
|
||||
function core_update(btn,type) {
|
||||
var v = core_version.value;
|
||||
var r = release_branch.value;
|
||||
var s = smart_enable.value;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {core_ver: v, release_branch: r, smart_enable: s}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "coreupdate")%>', {core_type: type}, function(x, status) {
|
||||
btn.value = '<%:Check And Update%>';
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function op_update(btn) {
|
||||
var v = core_version.value;
|
||||
var r = release_branch.value;
|
||||
var s = smart_enable.value;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {core_ver: v, release_branch: r, smart_enable: s}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "opupdate")%>', null, function(x, status) {
|
||||
btn.value = '<%:Check And Update%>';
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ma_core_update(btn,type) {
|
||||
var v = core_version.value;
|
||||
var r = release_branch.value;
|
||||
var s = smart_enable.value;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {core_ver: v, release_branch: r, smart_enable: s}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
btn.value = '<%:Download%>';
|
||||
btn.disabled = false;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update_ma")%>', status.corever, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
if ( status.corever != "0" ) {
|
||||
if (type == "Meta") {
|
||||
if (s == "1") {
|
||||
url4='https://raw.githubusercontent.com/vernesong/OpenClash/core/'+r+'/smart/clash-'+status.corever+'.tar.gz';
|
||||
window.location.href=url4;
|
||||
} else {
|
||||
url4='https://raw.githubusercontent.com/vernesong/OpenClash/core/'+r+'/meta/clash-'+status.corever+'.tar.gz';
|
||||
window.location.href=url4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert('<%:No Compiled Version is Selected, Please Select on The Top and Try Again!%>')
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ma_op_update(btn) {
|
||||
btn.value = '<%:Download%>';
|
||||
btn.disabled = false;
|
||||
var v = core_version.value;
|
||||
var r = release_branch.value;
|
||||
var s = smart_enable.value;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {core_ver: v, release_branch: r, smart_enable: s}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update_ma")%>', status.oplv, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
var oplv = status.oplv.substring(status.oplv.indexOf("v") + 1);
|
||||
if ( oplv != "" && oplv != "loading..." ) {
|
||||
if (status.pkg_type == "apk") {
|
||||
url2='https://raw.githubusercontent.com/vernesong/OpenClash/package/'+r+'/luci-app-openclash-'+oplv+'.apk';
|
||||
} else {
|
||||
url2='https://raw.githubusercontent.com/vernesong/OpenClash/package/'+r+'/luci-app-openclash_'+oplv+'_all.ipk';
|
||||
}
|
||||
window.location.href=url2;
|
||||
} else {
|
||||
alert('<%:Failed to get the latest version. Please try again later!%>')
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function remove_all_core(btn) {
|
||||
btn.value = '<%:Remove Core%>';
|
||||
btn.disabled = true;
|
||||
var r = confirm("<%:Are you sure want to remove all core files?%>")
|
||||
if (r == true) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "remove_all_core")%>', null, function(x, status) {
|
||||
if ( x && x.status == 200 ) {
|
||||
alert('<%:Remove succeeded!%>')
|
||||
} else {
|
||||
alert('<%:Remove failed!%>')
|
||||
}
|
||||
});
|
||||
}
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
function backup_file(btn) {
|
||||
btn.value = '<%:Backup File%>';
|
||||
btn.disabled = true;
|
||||
window.location.href=backup_select.value;
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
function all_one_key_update_cdn(btn) {
|
||||
var v = core_version.value;
|
||||
var r = release_branch.value;
|
||||
var s = smart_enable.value;
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {core_ver: v, release_branch: r, smart_enable: s}, function(x, status) {
|
||||
if (x && x.status == 200) {
|
||||
btn.value = '<%:Check Update%>';
|
||||
btn.disabled = false;
|
||||
return select_git_cdn("one_key_update");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//]]></script>
|
||||
27
luci-app-openclash/luasrc/view/openclash/update_config.htm
Normal file
@ -0,0 +1,27 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local fs = require "luci.openclash"
|
||||
local filename = fs.filename(self:cfgvalue(section)) or "config"
|
||||
local display = self.display or ""
|
||||
local idname = math.random(1000)..(string.match(filename, "[%w_]+") or "")
|
||||
%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function act_update_config_<%=idname%>(btn)
|
||||
{
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:UpDate%>';
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash","update_config")%>',{filename: "<%=filename%>"}, function(x,status){});
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
};
|
||||
|
||||
//]]></script>
|
||||
|
||||
<%
|
||||
if display == "" then
|
||||
%>
|
||||
<input type="button" class="btn cbi-button cbi-input-reload" value="<%:UpDate%>" onclick="return act_update_config_<%=idname%>(this)" />
|
||||
<%
|
||||
end
|
||||
%>
|
||||
<%+cbi/valuefooter%>
|
||||
28
luci-app-openclash/luasrc/view/openclash/upload.htm
Normal file
@ -0,0 +1,28 @@
|
||||
<%+cbi/valueheader%>
|
||||
<div style="text-align: center; margin:0 auto; display:block; white-space: nowrap;">
|
||||
<label class="cbi-value" style="display:inline-block; width: 100%;" for="ulfiles"><%:Upload File Type%> <%:(Click to select)%>
|
||||
<select name="file_type" style="width:auto">
|
||||
<option value="config" selected="selected"><%:Config File%> (.yaml)</option>
|
||||
<option value="proxy-provider"><%:Proxy Provider File%></option>
|
||||
<option value="rule-provider"><%:Rule Provider File%></option>
|
||||
<option value="clash_meta">[Meta] <%:Core File%> (.tar.gz)</option>
|
||||
<option value="backup-file"><%:Backup File%> (.tar.gz)</option>
|
||||
</select>
|
||||
<input class="cbi-input-file" style="width: 30%" type="file" id="ulfile" name="ulfile" />
|
||||
<input type="submit" class="btn cbi-button cbi-input-reload" name="upload" value="<%:Upload%>" />
|
||||
<input type="submit" class="btn cbi-button cbi-button-reset" value="<%:Backup%>" onclick="return backup_all_file(this)"/>
|
||||
</div>
|
||||
<%+cbi/valuefooter%>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
|
||||
function backup_all_file(btn)
|
||||
{
|
||||
btn.value = '<%:Backup%>';
|
||||
btn.disabled = true;
|
||||
window.location.href='<%="backup"%>';
|
||||
btn.disabled = false;
|
||||
return false;
|
||||
};
|
||||
|
||||
//]]></script>
|
||||