mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 05:13:56 +08:00
🤞 Sync 2026-09-27 22:16:29
This commit is contained in:
1 parent
8ede4a8320
commit
618f51e9dc
459 files changed
+44817
-2191
No files matched your search
@@ -2,6 +2,15 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [2.2.0-r1] - 节点页批量选择与操作
|
||||
|
||||
- 「节点」页关键词搜索框缩短为原长度的 3/5(`size` 默认 20 → 12)
|
||||
- 节点表格最左新增复选框列:表头复选框全选 / 取消全选,行复选框单独勾选
|
||||
- 「筛选」后新增「刷新」按钮(重载页面,筛选参数随 GET URL 自动保留)与「删除」按钮(勾选批量删除,confirm 确认,返回保留筛选参数)
|
||||
- action_node_delete 的 idx 参数支持逗号分隔多值,倒序 table.remove 避免下标偏移;行内单删路径不变
|
||||
- po/zh-cn 新增 Refresh / Delete selected nodes? / Please select nodes first 三条
|
||||
- 版本号 2.1.3-r6 → 2.2.0-r1;README.md / README.en.md / docs/INSTALL.md 版本与功能描述同步
|
||||
|
||||
## [2.1.3-r6] - 节点分组与单节点编辑
|
||||
|
||||
- 表单导入节点行新增「分组」输入框(`data-k="group"`,parse_local 透传入模型)
|
||||
|
||||
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
|
||||
PKG_NAME:=luci-app-substore
|
||||
# 版本约定:每次提交 PKG_RELEASE +1(2.1.2-r1 ~ r9);
|
||||
# 达到 r10 时 PKG_VERSION 末位 +1(2.1.2 -> 2.1.3),PKG_RELEASE 重置为 1。
|
||||
PKG_VERSION:=2.1.3
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=2.2.0
|
||||
PKG_RELEASE:=2
|
||||
|
||||
LUCI_DEPENDS:=+luci-lua-runtime +luci-compat
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ group them, then re-emit them in a format your client can consume.
|
||||
(optionally with keyword include / exclude and dedup rules) into one combination that
|
||||
has its own name, token and subscription link; combinations are recomputed automatically
|
||||
when a source updates
|
||||
- **Local subscription**: no URL needed — paste node text (mixed YAML / URI / JSON) or
|
||||
enter nodes one by one via a form whose fields adapt to the selected protocol
|
||||
(vmess / vless / ss / ssr / trojan / hysteria2 / tuic / wireguard / socks)
|
||||
|
||||
**Input parsing**
|
||||
- Subscription formats: URI lists, Base64, JSON, Clash YAML, sing-box JSON,
|
||||
@@ -30,7 +33,12 @@ group them, then re-emit them in a format your client can consume.
|
||||
`tuic` / `socks` (and more)
|
||||
|
||||
**Node processing**
|
||||
- Browse nodes, filter by protocol, keyword search, sort
|
||||
- Browse nodes, filter by group / protocol, keyword search, sort
|
||||
- Node grouping: set a node's group inline in the Group column (saved via XHR without
|
||||
reload), filter via the Group dropdown
|
||||
- Per-node edit / delete (Actions column); header checkbox selects all, then the Delete
|
||||
button batch-deletes the selection; the Refresh button reloads the list keeping the
|
||||
current filters
|
||||
- Per-subscription rules applied on every update:
|
||||
- keyword include / exclude (comma-separated, multi-keyword)
|
||||
- deduplication
|
||||
@@ -59,18 +67,18 @@ group them, then re-emit them in a format your client can consume.
|
||||
## Installation
|
||||
|
||||
> The version in the package name must match `PKG_VERSION` / `PKG_RELEASE` in the
|
||||
> [Makefile](Makefile) (currently `2.1.3-r6`).
|
||||
> [Makefile](Makefile) (currently `2.2.0-r1`).
|
||||
|
||||
opkg (OpenWrt / ImmortalWrt 24.10 and earlier):
|
||||
|
||||
```bash
|
||||
opkg install luci-app-substore-2.1.3-r6.ipk
|
||||
opkg install luci-app-substore-2.2.0-r1.ipk
|
||||
```
|
||||
|
||||
apk (OpenWrt / ImmortalWrt 25.12+):
|
||||
|
||||
```bash
|
||||
apk add --allow-untrusted luci-app-substore-2.1.3-r6.apk
|
||||
apk add --allow-untrusted luci-app-substore-2.2.0-r1.apk
|
||||
```
|
||||
|
||||
Then open LuCI: **Services → Subscriptions**.
|
||||
@@ -78,9 +86,12 @@ Then open LuCI: **Services → Subscriptions**.
|
||||
## Usage
|
||||
|
||||
1. **Add a subscription** — paste the subscription URL; optionally set up a
|
||||
per-subscription cron schedule, rules, or a download proxy.
|
||||
per-subscription cron schedule, rules, or a download proxy. No remote source?
|
||||
Use **Add local subscription**: paste node text or enter nodes via the form.
|
||||
2. **Update** — fetch, parse and filter the nodes.
|
||||
3. **Browse nodes** — filter, sort, and probe latency.
|
||||
3. **Browse nodes** — filter (group / protocol / keyword), sort, probe latency; tick
|
||||
checkboxes and hit Delete for batch deletion, edit / delete / regroup in-row, and
|
||||
Refresh reloads the list.
|
||||
4. **Export** — pick one of the 13 output formats, or copy the subscription link
|
||||
to feed a downstream client (Passwall / OpenClash / …).
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
用于订阅源直连失败时
|
||||
- **组合订阅**:勾选任意子集的现有订阅(可叠加关键词包含 / 排除、去重规则)合并成一个组合,
|
||||
拥有独立名称、token 与订阅链接;源订阅更新后组合自动重算
|
||||
- **本地订阅**:不填 URL,直接粘贴节点文本(YAML / URI / JSON 混合)导入,或用「表单导入」
|
||||
按协议动态字段逐条录入节点(vmess / vless / ss / ssr / trojan / hysteria2 / tuic / wireguard / socks)
|
||||
|
||||
**输入解析**
|
||||
- 订阅格式:URI 列表、Base64、JSON、Clash YAML、sing-box JSON、V2Ray / Xray JSON、
|
||||
@@ -26,7 +28,10 @@
|
||||
- 节点协议:`vmess` / `vless` / `trojan` / `shadowsocks` / `ssr` / `hysteria2` / `tuic` / `socks`(及更多)
|
||||
|
||||
**节点处理**
|
||||
- 浏览节点,按协议筛选、关键词搜索、排序
|
||||
- 浏览节点,按分组 / 协议筛选、关键词搜索、排序
|
||||
- 节点分组:「分组」列单元格内直接修改单节点分组(XHR 无刷新保存),配合「分组:」下拉筛选
|
||||
- 单节点编辑 / 删除(行尾「操作」列);表头复选框全选、行复选框勾选后点「删除」批量删除;
|
||||
「刷新」按钮重载列表(保留当前筛选条件)
|
||||
- 每次更新时生效的按订阅规则:
|
||||
- 关键词包含 / 排除(逗号分隔,支持多关键词)
|
||||
- 去重
|
||||
@@ -54,18 +59,18 @@
|
||||
## 安装
|
||||
|
||||
> 包名中的版本号必须与 [Makefile](Makefile) 的 `PKG_VERSION` / `PKG_RELEASE` 保持一致
|
||||
> (当前 `2.1.3-r6`)。
|
||||
> (当前 `2.2.0-r1`)。
|
||||
|
||||
opkg(OpenWrt / ImmortalWrt 24.10 及更早):
|
||||
|
||||
```bash
|
||||
opkg install luci-app-substore-2.1.3-r6.ipk
|
||||
opkg install luci-app-substore-2.2.0-r1.ipk
|
||||
```
|
||||
|
||||
apk(OpenWrt / ImmortalWrt 25.12+):
|
||||
|
||||
```bash
|
||||
apk add --allow-untrusted luci-app-substore-2.1.3-r6.apk
|
||||
apk add --allow-untrusted luci-app-substore-2.2.0-r1.apk
|
||||
```
|
||||
|
||||
然后在 LuCI 菜单打开:**服务 → 订阅**。
|
||||
@@ -73,8 +78,10 @@ apk add --allow-untrusted luci-app-substore-2.1.3-r6.apk
|
||||
## 使用方法
|
||||
|
||||
1. **添加订阅** —— 粘贴订阅 URL;可选的按订阅 cron 定时、规则或下载代理。
|
||||
无订阅源时可「添加本地订阅」:粘贴节点文本或表单逐条录入。
|
||||
2. **更新** —— 下载、解析并过滤节点。
|
||||
3. **浏览节点** —— 筛选、排序、探测延迟。
|
||||
3. **浏览节点** —— 筛选(分组 / 协议 / 关键词)、排序、探测延迟;勾选复选框后「删除」
|
||||
可批量删除,行内可编辑 / 删除 / 改分组,「刷新」重载列表。
|
||||
4. **导出** —— 任选 13 种格式之一,或复制订阅链接供下游客户端(Passwall / OpenClash / …)使用。
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
> NOTE: stage-0 skeleton has not yet been installed/verified on a target device.
|
||||
> This document will be updated once installation is validated.
|
||||
>
|
||||
> 包名中的版本号需与 Makefile 的 `PKG_VERSION` / `PKG_RELEASE` 保持同步(当前 2.1.3-r6)。
|
||||
> 包名中的版本号需与 Makefile 的 `PKG_VERSION` / `PKG_RELEASE` 保持同步(当前 2.2.0-r1)。
|
||||
|
||||
## Install (opkg — OpenWrt / ImmortalWrt 24.10 及更早)
|
||||
|
||||
```bash
|
||||
opkg install luci-app-substore-2.1.3-r6.ipk
|
||||
opkg install luci-app-substore-2.2.0-r1.ipk
|
||||
```
|
||||
|
||||
## Install (apk — OpenWrt / ImmortalWrt 25.12+)
|
||||
|
||||
```bash
|
||||
apk add --allow-untrusted luci-app-substore-2.1.3-r6.apk
|
||||
apk add --allow-untrusted luci-app-substore-2.2.0-r1.apk
|
||||
```
|
||||
|
||||
Refresh LuCI:
|
||||
|
||||
@@ -265,4 +265,14 @@ msgid "Failed to set group"
|
||||
msgstr "分组设置失败"
|
||||
|
||||
msgid "Note: edits to this node are overwritten the next time the subscription is updated or re-saved."
|
||||
msgstr "注意:对该节点的修改会在订阅下次更新或重新保存后被覆盖。"
|
||||
msgstr "注意:对该节点的修改会在订阅下次更新或重新保存后被覆盖。"
|
||||
|
||||
# 节点页:刷新 / 勾选批量删除
|
||||
msgid "Refresh"
|
||||
msgstr "刷新"
|
||||
|
||||
msgid "Delete selected nodes?"
|
||||
msgstr "确定删除选中的节点?"
|
||||
|
||||
msgid "Please select nodes first"
|
||||
msgstr "请先勾选节点"
|
||||
@@ -218,21 +218,32 @@ function action_node_save()
|
||||
back_to_nodes(http)
|
||||
end
|
||||
|
||||
-- 单节点删除
|
||||
-- 节点删除:idx 支持单个(行内删除按钮)或逗号分隔多个(勾选批量删除)
|
||||
function action_node_delete()
|
||||
local http = require("luci.http")
|
||||
local core = require("substore.core")
|
||||
if post_ok() then
|
||||
local id = http.formvalue("id") or ""
|
||||
local idx = tonumber(http.formvalue("idx") or "")
|
||||
local idx_param = http.formvalue("idx") or ""
|
||||
if type(idx_param) == "table" then idx_param = table.concat(idx_param, ",") end
|
||||
local nodes = core.read_nodes(id)
|
||||
if idx and nodes[idx] then
|
||||
table.remove(nodes, idx)
|
||||
if core.write_nodes(id, nodes) then
|
||||
core.save_meta(id, { node_count = #nodes })
|
||||
core.refresh_combos(id)
|
||||
local idxs = {}
|
||||
for s in tostring(idx_param):gmatch("%d+") do
|
||||
idxs[#idxs + 1] = tonumber(s)
|
||||
end
|
||||
-- 倒序删除,避免 table.remove 后下标偏移
|
||||
table.sort(idxs, function(a, b) return a > b end)
|
||||
local removed = false
|
||||
for _, i in ipairs(idxs) do
|
||||
if nodes[i] then
|
||||
table.remove(nodes, i)
|
||||
removed = true
|
||||
end
|
||||
end
|
||||
if removed and core.write_nodes(id, nodes) then
|
||||
core.save_meta(id, { node_count = #nodes })
|
||||
core.refresh_combos(id)
|
||||
end
|
||||
end
|
||||
back_to_nodes(http)
|
||||
end
|
||||
|
||||
@@ -125,7 +125,7 @@ var NODEFORM_I18N = {
|
||||
"defaultOpt": "<%:Default%>"
|
||||
};
|
||||
</script>
|
||||
<script src="/luci-static/resources/substore/nodeform.js?v=2.1.3"></script>
|
||||
<script src="/luci-static/resources/substore/nodeform.js?v=2.2.0"></script>
|
||||
<script>
|
||||
// 切换导入模式:仅切换显示/禁用,不提交表单
|
||||
function substore_local_switch(mode) {
|
||||
|
||||
@@ -73,7 +73,7 @@ var NODEFORM_I18N = {
|
||||
};
|
||||
var NODE_EDIT_DATA = <%=js_node%>;
|
||||
</script>
|
||||
<script src="/luci-static/resources/substore/nodeform.js?v=2.1.3"></script>
|
||||
<script src="/luci-static/resources/substore/nodeform.js?v=2.2.0"></script>
|
||||
<script>
|
||||
function node_edit_prepare() {
|
||||
var arr = collectNodes();
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<% end %>
|
||||
</select>
|
||||
</label>
|
||||
<label><%:Keyword%>: <input type="text" name="keyword" value="<%=keyword%>"/></label>
|
||||
<label><%:Keyword%>: <input type="text" name="keyword" size="12" value="<%=keyword%>"/></label>
|
||||
<label><%:Sort by%>:
|
||||
<select name="sort">
|
||||
<option value="name" <%=sort_by=="name" and "selected" or ""%>>name</option>
|
||||
@@ -69,14 +69,29 @@
|
||||
</label>
|
||||
<label style="margin-left:0.5em"><input type="checkbox" name="desc" value="1" <%=sort_desc and "checked" or ""%>/> <%:Desc%></label>
|
||||
<input type="submit" class="cbi-button cbi-button-apply" value="<%:Filter%>"/>
|
||||
<button type="button" class="cbi-button" onclick="location.reload()"><%:Refresh%></button>
|
||||
<button type="button" class="cbi-button cbi-button-reset" onclick="substore_delete_selected()"><%:Delete%></button>
|
||||
<button type="button" class="cbi-button" onclick="runProbe('ping')">Ping</button>
|
||||
<button type="button" class="cbi-button" onclick="runProbe('tcping')">TCPing</button>
|
||||
<button type="button" class="cbi-button" onclick="runProbe('url')"><%:URL Test%></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 勾选批量删除用的隐藏表单:JS 收集选中下标填入 idx(逗号分隔)后提交 -->
|
||||
<form id="batch_delete_form" method="post" action="<%=luci.dispatcher.build_url('admin/services/substore/node_delete')%>" style="display:none">
|
||||
<input type="hidden" name="token" value="<%=token%>"/>
|
||||
<input type="hidden" name="id" value="<%=id%>"/>
|
||||
<input type="hidden" name="idx" value=""/>
|
||||
<input type="hidden" name="proto" value="<%=proto%>"/>
|
||||
<input type="hidden" name="keyword" value="<%=keyword%>"/>
|
||||
<input type="hidden" name="group" value="<%=group%>"/>
|
||||
<input type="hidden" name="sort" value="<%=sort_by%>"/>
|
||||
<% if sort_desc then %><input type="hidden" name="desc" value="1"/><% end %>
|
||||
</form>
|
||||
|
||||
<table class="cbi-section-table">
|
||||
<tr class="cbi-section-table-titles">
|
||||
<th style="width:1em"><input type="checkbox" onclick="substore_toggle_all(this)"/></th>
|
||||
<th><%:Name%></th>
|
||||
<th><%:Group%></th>
|
||||
<th><%:Protocol%></th>
|
||||
@@ -87,6 +102,7 @@
|
||||
</tr>
|
||||
<% for _, n in ipairs(nodes) do %>
|
||||
<tr class="cbi-section-table-row" data-server="<%=n.server or ""%>" data-port="<%=n.port or ""%>">
|
||||
<td><input type="checkbox" class="node-check" value="<%=n.__idx%>"/></td>
|
||||
<td><%=n.name or ""%></td>
|
||||
<td>
|
||||
<input type="text" class="group-input" style="width:8em" value="<%=luci.util.pcdata(n.group or "")%>"
|
||||
@@ -114,7 +130,7 @@
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if #nodes == 0 then %>
|
||||
<tr class="cbi-section-table-row"><td colspan="7" style="text-align:center;color:#808080"><%:No nodes found.%></td></tr>
|
||||
<tr class="cbi-section-table-row"><td colspan="8" style="text-align:center;color:#808080"><%:No nodes found.%></td></tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<div id="probe_area" style="display:none;margin:0.5em 0">
|
||||
@@ -149,6 +165,20 @@ function substore_set_group(el) {
|
||||
+ '&group=' + encodeURIComponent(el.value)
|
||||
+ '&token=' + encodeURIComponent('<%=token%>'));
|
||||
}
|
||||
function substore_toggle_all(el) {
|
||||
var boxes = document.querySelectorAll('.node-check');
|
||||
for (var i = 0; i < boxes.length; i++) boxes[i].checked = el.checked;
|
||||
}
|
||||
function substore_delete_selected() {
|
||||
var boxes = document.querySelectorAll('.node-check:checked');
|
||||
if (boxes.length === 0) { alert('<%:Please select nodes first%>'); return; }
|
||||
if (!confirm('<%=luci.util.pcdata(luci.i18n.translate('Delete selected nodes?'))%>')) return;
|
||||
var idxs = [];
|
||||
for (var i = 0; i < boxes.length; i++) idxs.push(boxes[i].value);
|
||||
var form = document.getElementById('batch_delete_form');
|
||||
form.elements['idx'].value = idxs.join(',');
|
||||
form.submit();
|
||||
}
|
||||
function runProbe(mode) {
|
||||
var area = document.getElementById('probe_area');
|
||||
var sum = document.getElementById('probe_summary');
|
||||
|
||||
Reference in new issue
Block a user