mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 20:48:17 +08:00
⛅ Sync 2026-11-02 22:20:40
This commit is contained in:
1 parent
db798bdf5e
commit
d09231d556
184 files changed
+19918
-3537
No files matched your search
@@ -1,8 +1,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-isp-split
|
||||
PKG_VERSION:=0.3.2
|
||||
PKG_RELEASE:=5
|
||||
PKG_VERSION:=0.4.0
|
||||
PKG_RELEASE:=6
|
||||
PKG_LICENSE:=MIT
|
||||
PKG_LICENSE_FILES:=LICENSE
|
||||
|
||||
@@ -55,6 +55,8 @@ define Package/luci-app-isp-split/install
|
||||
$(1)/www/luci-static/resources/view/isp-split/overview.js
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/htdocs/luci-static/resources/view/isp-split/config.js \
|
||||
$(1)/www/luci-static/resources/view/isp-split/config.js
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/htdocs/luci-static/resources/view/isp-split/wizard.js \
|
||||
$(1)/www/luci-static/resources/view/isp-split/wizard.js
|
||||
if [ -f $(PKG_BUILD_DIR)/htdocs/luci-static/resources/view/isp-split/linecheck.js ]; then \
|
||||
$(INSTALL_DATA) $(PKG_BUILD_DIR)/htdocs/luci-static/resources/view/isp-split/linecheck.js \
|
||||
$(1)/www/luci-static/resources/view/isp-split/linecheck.js; \
|
||||
|
||||
@@ -1298,3 +1298,406 @@
|
||||
}
|
||||
|
||||
/* lc:end */
|
||||
|
||||
/* ---------------------------------------------------------------- 环境横幅(doctor,0.3.3) */
|
||||
|
||||
.isp-doctor {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* fail:红条。与 .is-warn 同一套骨架,只换左边框与标题色——不新增视觉语言。
|
||||
这里没有关闭按钮,也没有折叠:有 fail 项时横幅必须一直在。 */
|
||||
.isp-banner.is-bad {
|
||||
border-left-color: var(--isp-bad);
|
||||
}
|
||||
|
||||
.isp-banner.is-bad .isp-banner-msg strong {
|
||||
color: var(--isp-bad);
|
||||
}
|
||||
|
||||
.isp-banner-actions {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.isp-env-list {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.isp-env-list[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isp-env-item {
|
||||
border-left: 3px solid var(--isp-border-strong);
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.isp-env-title {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: var(--isp-text);
|
||||
}
|
||||
|
||||
.isp-env-adv {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* 全绿:一行,尽量不占版面 */
|
||||
.isp-env-ok {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
margin: 0 0 16px 0;
|
||||
padding: 8px 14px;
|
||||
border: 1px solid var(--isp-border);
|
||||
border-left: 4px solid var(--isp-ok);
|
||||
border-radius: var(--isp-radius);
|
||||
background: var(--isp-card-bg);
|
||||
color: var(--isp-text);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.isp-env-ok .isp-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--isp-ok);
|
||||
}
|
||||
|
||||
.isp-env-sub {
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.isp-env-wait {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- 首启向导(0.3.3)
|
||||
|
||||
沿用 isp-* 那套变量与卡片骨架:向导只新增"步骤条 / 自检行 / 完成态"三块,
|
||||
不复用体检页那套样式类——它可被构建期剥离,向导不能跟着消失。 */
|
||||
|
||||
.wz-steps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.wz-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
background: var(--isp-card-bg);
|
||||
border: 1px solid var(--isp-border);
|
||||
border-radius: var(--isp-radius);
|
||||
color: var(--isp-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wz-step.is-current {
|
||||
border-color: var(--isp-primary);
|
||||
box-shadow: inset 0 0 0 1px var(--isp-primary);
|
||||
color: var(--isp-text);
|
||||
}
|
||||
|
||||
.wz-step.is-done {
|
||||
border-left: 4px solid var(--isp-ok);
|
||||
color: var(--isp-text);
|
||||
}
|
||||
|
||||
.wz-step[disabled] {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.wz-step-no {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.wz-step-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wz-step-hint {
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
|
||||
.wz-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.wz-card {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.wz-card h3 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.wz-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 14px 0 10px 0;
|
||||
}
|
||||
|
||||
.wz-summary-head {
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.wz-doctor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.wz-checks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.wz-check {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--isp-border);
|
||||
border-left: 4px solid var(--isp-border-strong);
|
||||
border-radius: var(--isp-radius);
|
||||
background: var(--isp-card-bg);
|
||||
}
|
||||
|
||||
.wz-check.is-ok {
|
||||
border-left-color: var(--isp-ok);
|
||||
}
|
||||
|
||||
.wz-check.is-warn {
|
||||
border-left-color: var(--isp-warn);
|
||||
}
|
||||
|
||||
.wz-check.is-bad {
|
||||
border-left-color: var(--isp-bad);
|
||||
}
|
||||
|
||||
.wz-check-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.wz-check-title {
|
||||
font-weight: 600;
|
||||
color: var(--isp-text);
|
||||
}
|
||||
|
||||
.wz-check-conf {
|
||||
margin-left: auto;
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.wz-find {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.wz-find-line {
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.wz-advice {
|
||||
margin-top: 6px;
|
||||
color: var(--isp-text);
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.wz-kv {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 3px 0;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.wz-kv-key {
|
||||
flex: 0 0 96px;
|
||||
color: var(--isp-muted);
|
||||
}
|
||||
|
||||
.wz-kv-val {
|
||||
flex: 1 1 auto;
|
||||
color: var(--isp-text);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.wz-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.wz-hint {
|
||||
margin: 8px 0 0 0;
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.wz-meta {
|
||||
margin-top: 8px;
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.wz-note {
|
||||
min-height: 1.2em;
|
||||
margin: 10px 0 6px 0;
|
||||
color: var(--isp-primary);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.wz-note.is-bad {
|
||||
color: var(--isp-bad);
|
||||
}
|
||||
|
||||
.wz-note.is-ok {
|
||||
color: var(--isp-ok);
|
||||
}
|
||||
|
||||
.wz-loading {
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.wz-block {
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--isp-radius);
|
||||
background: var(--isp-card-bg-2);
|
||||
color: var(--isp-text);
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.wz-preview {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.wz-preview-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--isp-muted);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.wz-dry {
|
||||
max-height: 320px;
|
||||
}
|
||||
|
||||
.isp-out.is-bad {
|
||||
border-color: var(--isp-bad);
|
||||
}
|
||||
|
||||
.wz-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.wz-ack {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--isp-text);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.wz-list {
|
||||
margin: 8px 0 0 0;
|
||||
padding-left: 20px;
|
||||
color: var(--isp-text);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.wz-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 14px 0 6px 0;
|
||||
}
|
||||
|
||||
.wz-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.wz-done {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.wz-done-mark {
|
||||
flex: 0 0 auto;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--isp-ok);
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.wz-done-text strong {
|
||||
display: block;
|
||||
font-size: 1.05rem;
|
||||
color: var(--isp-text);
|
||||
}
|
||||
|
||||
.wz-done-banner strong {
|
||||
color: var(--isp-ok);
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.wz-steps {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.wz-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,20 @@
|
||||
var CMD = '/usr/sbin/isp-split';
|
||||
var REFRESH_SEC = 10;
|
||||
|
||||
/*
|
||||
* 环境横幅(0.3.3):页面顶部一条 doctor 结论。
|
||||
*
|
||||
* 为什么带缓存:`doctor` 不是零成本——它要读 nft 规则表、ubus、/proc,在
|
||||
* 小闪存机器上要几百毫秒到几秒。而状态页每 10 秒刷新一次,跟着刷等于拿
|
||||
* 路由器当体检机用。所以结果按 10 分钟缓存:内存一份(页面内切来切去不重跑)
|
||||
* + localStorage 一份(刷新页面也不重跑)。用户点「重新体检」才强制重跑。
|
||||
*
|
||||
* 颜色规矩(W2):fail 红且不能关(TUN 劫持 / DNS 53 这类问题会静默吃掉分流,
|
||||
* 关掉就等于藏了个定时炸弹);warn 橙、可折叠;全绿只留一行,不占地方。
|
||||
*/
|
||||
var DOCTOR_TTL_MS = 600 * 1000;
|
||||
var DOCTOR_CACHE_KEY = 'isp-split.doctor.v1';
|
||||
|
||||
/* ---------------------------------------------------------------- 图标(内联 SVG,无外链) */
|
||||
|
||||
var ICONS = {
|
||||
@@ -246,6 +260,187 @@ return view.extend({
|
||||
});
|
||||
},
|
||||
|
||||
/* ---------------- 环境横幅(doctor) ----------------
|
||||
doctor 的退出码有语义(0 全绿 / 1 有保留 / 2 会静默失效),所以这里不看
|
||||
退出码,只看 stdout 里有没有一份能解析的 JSON。 */
|
||||
|
||||
doctorCacheGet: function() {
|
||||
try {
|
||||
var raw = window.localStorage.getItem(DOCTOR_CACHE_KEY);
|
||||
var o = raw ? JSON.parse(raw) : null;
|
||||
if (o && o.t && o.d && (Date.now() - o.t) < DOCTOR_TTL_MS)
|
||||
return { data: o.d, at: +o.t };
|
||||
} catch (e) { /* 隐私模式 / 配额满:退化成只在内存里缓存 */ }
|
||||
return null;
|
||||
},
|
||||
|
||||
doctorCachePut: function(data) {
|
||||
try {
|
||||
window.localStorage.setItem(DOCTOR_CACHE_KEY,
|
||||
JSON.stringify({ t: Date.now(), d: data }));
|
||||
} catch (e) { /* 同上:缓存写不进去不影响功能 */ }
|
||||
},
|
||||
|
||||
refreshDoctor: function(force) {
|
||||
var self = this;
|
||||
|
||||
if (this._doctorInflight)
|
||||
return this._doctorInflight;
|
||||
|
||||
if (!force) {
|
||||
var c = this.doctorCacheGet();
|
||||
if (c) {
|
||||
this.doctor = c.data;
|
||||
this.doctorAt = c.at;
|
||||
this.paintDoctor();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
this.doctorErr = null;
|
||||
this._doctorInflight = fs.exec(CMD, ['doctor', '--json']).then(function(res) {
|
||||
var d = parseJson(res);
|
||||
if (!d)
|
||||
throw new Error((((res && res.stdout) || '') + ((res && res.stderr) || '')).trim() ||
|
||||
('退出码 ' + ((res && res.code) != null ? res.code : '?')));
|
||||
self.doctor = d;
|
||||
self.doctorAt = Date.now();
|
||||
self.doctorCachePut(d);
|
||||
}).catch(function(err) {
|
||||
self.doctorErr = String(err);
|
||||
}).then(function() {
|
||||
self._doctorInflight = null;
|
||||
self.paintDoctor();
|
||||
});
|
||||
|
||||
return this._doctorInflight;
|
||||
},
|
||||
|
||||
paintDoctor: function() {
|
||||
var box = document.getElementById('isp-doctor');
|
||||
if (!box)
|
||||
return;
|
||||
|
||||
while (box.firstChild)
|
||||
box.removeChild(box.firstChild);
|
||||
|
||||
var self = this;
|
||||
|
||||
if (this.doctorErr) {
|
||||
box.appendChild(E('div', { 'class': 'isp-banner is-warn' }, [
|
||||
E('div', { 'class': 'isp-banner-msg' }, [
|
||||
E('strong', {}, [ _('环境体检不可用') ]),
|
||||
E('div', {}, [ this.doctorErr ]),
|
||||
E('div', { 'class': 'isp-banner-hint' }, [
|
||||
_('不影响下方的运行状态;多半是 rpcd 权限或 isp-split 版本不匹配。')
|
||||
])
|
||||
]),
|
||||
E('button', {
|
||||
'class': 'cbi-button isp-banner-btn',
|
||||
'click': function() { self.refreshDoctor(true); }
|
||||
}, [ _('重试') ])
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.doctor) {
|
||||
box.appendChild(E('div', { 'class': 'isp-env-wait' }, [ _('正在做环境体检(doctor)…') ]));
|
||||
return;
|
||||
}
|
||||
|
||||
var d = this.doctor;
|
||||
var checks = d.checks || [];
|
||||
var sum = d.summary || {};
|
||||
var fails = checks.filter(function(c) { return c.status === 'fail'; });
|
||||
var warns = checks.filter(function(c) { return c.status === 'warn'; });
|
||||
var ageMin = this.doctorAt ? Math.max(0, Math.round((Date.now() - this.doctorAt) / 60000)) : 0;
|
||||
var cacheHint = _('(结果缓存 ') + ageMin + _(' 分钟,10 分钟内不重跑探针)');
|
||||
|
||||
var retryBtn = E('button', {
|
||||
'class': 'cbi-button isp-banner-btn',
|
||||
'title': _('立即重跑 doctor(会跳过 10 分钟缓存)'),
|
||||
'click': function() {
|
||||
retryBtn.disabled = true;
|
||||
self.refreshDoctor(true);
|
||||
}
|
||||
}, [ _('重新体检') ]);
|
||||
|
||||
/* fail:红条,不能关,建议直接摊开 */
|
||||
if (fails.length) {
|
||||
var list = E('div', { 'class': 'isp-env-list' });
|
||||
fails.forEach(function(c) {
|
||||
list.appendChild(E('div', { 'class': 'isp-env-item' }, [
|
||||
E('span', { 'class': 'isp-env-title' }, [ c.title || c.id ]),
|
||||
E('span', { 'class': 'isp-env-adv' }, [ c.advice || '' ])
|
||||
]));
|
||||
});
|
||||
box.appendChild(E('div', { 'class': 'isp-banner is-bad' }, [
|
||||
E('div', { 'class': 'isp-banner-msg' }, [
|
||||
E('strong', {}, [
|
||||
_('环境体检:') + fails.length + _(' 项会让分流静默失效')
|
||||
]),
|
||||
E('div', {}, [ sum.headline || _('按下面的建议处理后再看状态指标。') ]),
|
||||
list,
|
||||
E('div', { 'class': 'isp-banner-hint' }, [
|
||||
_('这条横幅在有 fail 项时不能关闭。') + cacheHint
|
||||
])
|
||||
]),
|
||||
retryBtn
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
/* warn:橙条,可折叠 */
|
||||
if (warns.length) {
|
||||
var detail = E('div', { 'class': 'isp-env-list', 'hidden': '' });
|
||||
warns.forEach(function(c) {
|
||||
detail.appendChild(E('div', { 'class': 'isp-env-item' }, [
|
||||
E('span', { 'class': 'isp-env-title' }, [ c.title || c.id ]),
|
||||
E('span', { 'class': 'isp-env-adv' }, [ c.advice || '' ])
|
||||
]));
|
||||
});
|
||||
|
||||
var toggle = E('button', {
|
||||
'class': 'cbi-button isp-banner-btn',
|
||||
'click': function() {
|
||||
if (detail.hasAttribute('hidden')) {
|
||||
detail.removeAttribute('hidden');
|
||||
toggle.textContent = _('收起建议');
|
||||
} else {
|
||||
detail.setAttribute('hidden', '');
|
||||
toggle.textContent = _('查看建议');
|
||||
}
|
||||
}
|
||||
}, [ _('查看建议') ]);
|
||||
|
||||
box.appendChild(E('div', { 'class': 'isp-banner is-warn' }, [
|
||||
E('div', { 'class': 'isp-banner-msg' }, [
|
||||
E('strong', {}, [ _('环境体检:') + warns.length + _(' 项有保留') ]),
|
||||
E('div', {}, [ sum.headline || '' ]),
|
||||
detail,
|
||||
E('div', { 'class': 'isp-banner-hint' }, [ cacheHint ])
|
||||
]),
|
||||
E('div', { 'class': 'isp-banner-actions' }, [ toggle, retryBtn ])
|
||||
]));
|
||||
return;
|
||||
}
|
||||
|
||||
/* 全绿:一行,不抢版面 */
|
||||
box.appendChild(E('div', { 'class': 'isp-env-ok' }, [
|
||||
E('span', { 'class': 'isp-dot' }),
|
||||
E('span', {}, [ _('环境体检正常(') + checks.length + _(' 项)') ]),
|
||||
E('span', { 'class': 'isp-env-sub' }, [
|
||||
sum.worst && sum.worst !== 'ok'
|
||||
? _('最差项:') + sum.worst + _(' · ') + (sum.headline || '')
|
||||
: cacheHint
|
||||
]),
|
||||
E('button', {
|
||||
'class': 'cbi-button isp-banner-btn',
|
||||
'click': function() { self.refreshDoctor(true); }
|
||||
}, [ _('重新体检') ])
|
||||
]));
|
||||
},
|
||||
|
||||
setOut: function(text) {
|
||||
var out = document.getElementById('isp-out');
|
||||
if (out) {
|
||||
@@ -691,6 +886,10 @@ return view.extend({
|
||||
this.paused = false;
|
||||
this.busy = false;
|
||||
this._inflight = null;
|
||||
this.doctor = null;
|
||||
this.doctorAt = 0;
|
||||
this.doctorErr = null;
|
||||
this._doctorInflight = null;
|
||||
|
||||
var pauseLabel = E('span', {}, [_('暂停刷新')]);
|
||||
var pauseBtn = E('button', {
|
||||
@@ -754,6 +953,7 @@ return view.extend({
|
||||
E('div', { 'class': 'isp-toolbar' }, [ statePill, pauseBtn ])
|
||||
]),
|
||||
|
||||
E('div', { 'id': 'isp-doctor', 'class': 'isp-doctor' }),
|
||||
E('div', { 'id': 'isp-load-error', 'class': 'isp-err', 'style': 'display:none' }),
|
||||
E('div', { 'id': 'isp-cards', 'class': 'isp-cards' }),
|
||||
|
||||
@@ -782,6 +982,8 @@ return view.extend({
|
||||
]);
|
||||
|
||||
this.refresh();
|
||||
/* 环境体检单独走自己的 10 分钟缓存:失败/成功都不影响下面的状态刷新 */
|
||||
this.refreshDoctor(false);
|
||||
|
||||
/* 自带定时器替代 LuCI 的 poll(见文件头注释):暂停即停,页面被换走即收摊。 */
|
||||
this.timer = window.setInterval(function() {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -10,6 +10,14 @@
|
||||
"uci": { "mwan3": true }
|
||||
}
|
||||
},
|
||||
"admin/network/isp-split/wizard": {
|
||||
"title": "引导",
|
||||
"order": 0,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "isp-split/wizard"
|
||||
}
|
||||
},
|
||||
"admin/network/isp-split/overview": {
|
||||
"title": "状态",
|
||||
"order": 1,
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"luci-app-isp-split": {
|
||||
"description": "Grant access to ISP split status and control. WARNING: write = execute ANY isp-split subcommand as root (wire/apply/conf set/uninstall); grant only to fully trusted admins. read = conf + whole state tree (public IP/geo/STUN).",
|
||||
"description": "Grant access to ISP split status and control. WARNING: write = execute ANY isp-split subcommand as root (wire/apply/conf set/uninstall); grant only to fully trusted admins. read = conf + whole state tree (public IP/geo/STUN). wizard = one file write scope only (/etc/isp-split/state/wizard.json), no directory-wide write.",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"file": [ "read", "stat" ]
|
||||
},
|
||||
"file": {
|
||||
"/etc/isp-split.conf": [ "read" ],
|
||||
"/etc/isp-split": [ "read" ],
|
||||
"/etc/isp-split/*": [ "read" ],
|
||||
"/etc/isp-split/state/*": [ "read" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"file": [ "exec" ]
|
||||
"file": [ "exec", "write" ]
|
||||
},
|
||||
"file": {
|
||||
"/usr/sbin/isp-split": [ "exec" ]
|
||||
"/usr/sbin/isp-split": [ "exec" ],
|
||||
"/etc/isp-split/state/wizard.json": [ "write" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user