feat: M6.40批 — 阅读器UI改版(书架list·grid+空态骨架+界面设置+App设置页+header·footer+背景黑默认) + 系统级音量翻页映射 + P1 MD3(select面板28/对话框按钮胶囊) + MeowPanel写端点护栏 + SDK公开仓·文档同步
- reader: app.c/shelf.c/reader.c 全套UI; 新增 reader_conf(reader.conf)/settings(App设置页)/reader_ipc(音量翻页IPC响应); app.json加volume_page - launcher: keys.c meow_volpage_*门 + util.c app_id→fd映射(HELLO记fd/断开清) + app_launcher.c 前台三处接线(进入sink/按键路径volpage_key/离开clear) → 前台声明volume_page的App音量键→MSG_KEY_PAGEUP/PAGEDOWN翻页(非阅读器前台音量照旧) - sdk: meow_select.c 底部面板28 + meow_ui_dialog.c 对话框按钮胶囊(h/2) + test_meow_ui_select.c修采样 - meowpanel: /api/net/policy·/api/power/policy 加防自锁+policy默认关两条护栏(panel.policy_enabled默认0) - 文档: MEOW_APP_SDK.md §12.6 + meowpanel README端点表 0warning;宿主测试全过;二进制留批量批次未入库
This commit is contained in:
1 parent
b364b6371a
commit
2826c1e905
27 files changed
+1704
-129
No files matched your search
@@ -609,21 +609,24 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
|
||||
function powerPolicyCard(pol) {
|
||||
if (!pol) return '<div class="card"><h3 class="card-title">电源策略</h3>' + homeUnavailable() + '</div>';
|
||||
const pwe = !(pol.policy_write_enabled === false); // 缺省视为已开启(兼容旧 powermgr 不带该键)
|
||||
const profiles = ['performance', 'balanced', 'powersave', 'ultra'];
|
||||
const cur = pol.profile || 'balanced';
|
||||
let btns = profiles.map(p => '<button class="btn btn-ghost pol-btn' + (p === cur ? ' pol-on' : '') + '" data-pol="' + esc(p) + '">' + powerProfileLabel(p) + '</button>').join(' ');
|
||||
return '<div class="card"><h3 class="card-title">电源策略</h3>' +
|
||||
'<div class="field-row" style="margin-bottom:12px"><span class="muted">省电档位:</span>' + btns + '</div>' +
|
||||
'<table class="table"><tbody>' +
|
||||
row('低电阈值', '<input type="number" id="pol-low" class="num pol-num" value="' + esc(pol.low_pct) + '" min="1" max="99"> %') +
|
||||
row('临界阈值', '<input type="number" id="pol-crit" class="num pol-num" value="' + esc(pol.critical_pct) + '" min="1" max="99"> %') +
|
||||
row('亮度上限', '<input type="number" id="pol-bri" class="num pol-num" value="' + esc(pol.brightness_cap) + '" min="0" max="101"> <span class="muted">0=不生效</span>') +
|
||||
row('息屏上限', '<input type="number" id="pol-scr" class="num pol-num" value="' + esc(pol.screen_timeout_cap) + '" min="0"> <span class="muted">秒 · 0=不生效</span>') +
|
||||
let h = '<div class="card"><h3 class="card-title">电源策略</h3>';
|
||||
if (!pwe) h += '<div class="page-banner" style="margin-bottom:10px"><span class="ico">🔒</span><span>策略写默认关闭(panel.policy_enabled=0):当前只读,切档/阈值需先开启。</span><button class="btn" id="pol-enable">开启策略写</button></div>';
|
||||
const btns = profiles.map(p => '<button class="btn btn-ghost pol-btn' + (p === cur ? ' pol-on' : '') + '" data-pol="' + esc(p) + '"' + (!pwe ? ' disabled' : '') + '>' + powerProfileLabel(p) + '</button>').join(' ');
|
||||
h += '<div class="field-row" style="margin-bottom:12px"><span class="muted">省电档位:</span>' + btns + '</div>';
|
||||
h += '<table class="table"><tbody>' +
|
||||
row('低电阈值', '<input type="number" id="pol-low" class="num pol-num" value="' + esc(pol.low_pct) + '" min="1" max="99"' + (!pwe ? ' disabled' : '') + '> %') +
|
||||
row('临界阈值', '<input type="number" id="pol-crit" class="num pol-num" value="' + esc(pol.critical_pct) + '" min="1" max="99"' + (!pwe ? ' disabled' : '') + '> %') +
|
||||
row('亮度上限', '<input type="number" id="pol-bri" class="num pol-num" value="' + esc(pol.brightness_cap) + '" min="0" max="101"' + (!pwe ? ' disabled' : '') + '> <span class="muted">0=不生效</span>') +
|
||||
row('息屏上限', '<input type="number" id="pol-scr" class="num pol-num" value="' + esc(pol.screen_timeout_cap) + '" min="0"' + (!pwe ? ' disabled' : '') + '> <span class="muted">秒 · 0=不生效</span>') +
|
||||
row('状态', (pol.enacted ? '<span class="home-chip ok">已手动开启</span>' : '<span class="home-chip mut">默认全关</span>')) +
|
||||
'</tbody></table>' +
|
||||
'<div class="field-row"><button class="btn btn-primary" id="pol-apply">保存阈值</button><span class="muted">切档即时生效(需确认);阈值点「保存阈值」。</span></div>' +
|
||||
'<p class="muted">' + esc(pol.note || '') + '</p>' +
|
||||
'</div>';
|
||||
'</tbody></table>';
|
||||
h += '<div class="field-row"><button class="btn btn-primary" id="pol-apply"' + (!pwe ? ' disabled' : '') + '>保存阈值</button><span class="muted">切档即时生效(需确认);阈值点「保存阈值」。</span></div>';
|
||||
h += '<p class="muted">' + esc(pol.note || '') + '</p>';
|
||||
h += '</div>';
|
||||
return h;
|
||||
}
|
||||
|
||||
function powerEventsCard(ev) {
|
||||
@@ -690,6 +693,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
|
||||
function bindPower() {
|
||||
$('#content').addEventListener('click', async function (e) {
|
||||
if (e.target.closest('#pol-enable')) { enablePolicyWrite(fetchPowerOnce); return; }
|
||||
const pb = e.target.closest('.pol-btn');
|
||||
if (pb) {
|
||||
const p = pb.dataset.pol;
|
||||
@@ -1003,6 +1007,19 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
(i > 0 ? ' <span class="net-entity">' + esc(e.slice(i + 1)) + '</span>' : '');
|
||||
}
|
||||
|
||||
const NET_LOCKEDS = ['dropbear', 'wpa_supplicant', 'udhcpc', 'netmgrd', 'svcd', 'sysui', 'init'];
|
||||
function netLockedEntity(e) { // 与后端 net_locked_entity 同口径(取 `svc:|app:|proc:` 后裸名精确比对)
|
||||
if (!e) return false;
|
||||
const i = e.indexOf(':');
|
||||
return NET_LOCKEDS.indexOf(i > 0 ? e.slice(i + 1) : e) !== -1;
|
||||
}
|
||||
|
||||
async function enablePolicyWrite(fetchFn) { // 开启 panel.policy_enabled(guard③ 手动开启入口)
|
||||
try { await api('PUT', '/api/settings/panel.policy_enabled', { value: 1 }); toast('策略写已开启(panel.policy_enabled=1)'); }
|
||||
catch (err) { toast('开启失败:' + errText(err)); }
|
||||
if (fetchFn) fetchFn();
|
||||
}
|
||||
|
||||
function netStateCls(st) {
|
||||
if (!st) return '';
|
||||
if (st === 'ESTABLISHED') return 'net-state ok';
|
||||
@@ -1109,7 +1126,9 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
if (!pol || pol.available === false) return '<div class="card"><h3 class="card-title">网络策略</h3>' + homeUnavailable() + '</div>';
|
||||
const enforce = pol.enforce || '';
|
||||
const dnsOnly = enforce === 'dns_only';
|
||||
const pwe = !(pol.policy_write_enabled === false); // 缺省视为已开启(兼容旧 powermgr/netmgrd 不带该键)
|
||||
let h = '<div class="card"><h3 class="card-title">网络策略</h3>';
|
||||
if (!pwe) h += '<div class="page-banner" style="margin-bottom:10px"><span class="ico">🔒</span><span>策略写默认关闭(panel.policy_enabled=0):当前只读,阻断/放行/域名规则需先开启。</span><button class="btn" id="pol-enable">开启策略写</button></div>';
|
||||
h += '<div class="page-banner" style="margin-bottom:10px"><span class="ico">' + (dnsOnly ? 'ℹ️' : '⚠️') + '</span><span>' +
|
||||
(dnsOnly ? '执行模式:仅 DNS 层拒绝(enforce=dns_only;本内核 SOCK_DESTROY 为 no-op,不会真正断连)' : '执行模式:' + esc(enforce)) + '</span></div>';
|
||||
const rules = pol.rules || [];
|
||||
@@ -1117,21 +1136,24 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
else {
|
||||
h += '<table class="table"><thead><tr><th>实体</th><th>网络</th><th>白名单域名</th><th>黑名单域名</th><th style="text-align:right">操作</th></tr></thead><tbody>';
|
||||
rules.forEach(r => {
|
||||
const targetNet = r.net === 'block' ? 'allow' : 'block';
|
||||
const locked = targetNet === 'block' && netLockedEntity(r.entity);
|
||||
h += '<tr><td class="mono">' + netBadge(r.entity) + '</td>' +
|
||||
'<td><span class="home-chip ' + (r.net === 'block' ? 'bad' : 'ok') + '">' + (r.net === 'block' ? '阻断' : '放行') + '</span></td>' +
|
||||
'<td class="muted">' + esc((r.allow_hosts || []).join(', ') || '—') + '</td>' +
|
||||
'<td class="muted">' + esc((r.deny_hosts || []).join(', ') || '—') + '</td>' +
|
||||
'<td style="text-align:right"><div class="row-actions">' +
|
||||
'<button class="btn btn-ghost" data-ptoggle="' + esc(r.entity) + '" data-net="' + (r.net === 'block' ? 'allow' : 'block') + '">' + (r.net === 'block' ? '改放行' : '阻断') + '</button>' +
|
||||
'<button class="btn btn-ghost" data-pdel="' + esc(r.entity) + '">删除</button></div></td></tr>';
|
||||
'<button class="btn btn-ghost" data-ptoggle="' + esc(r.entity) + '" data-net="' + targetNet + '"' +
|
||||
' title="' + (locked ? '防自锁:禁止阻断系统关键服务' : '') + '"' + ((!pwe || locked) ? ' disabled' : '') + '>' + (r.net === 'block' ? '改放行' : '阻断') + '</button>' +
|
||||
'<button class="btn btn-ghost" data-pdel="' + esc(r.entity) + '"' + ((!pwe) ? ' disabled' : '') + '>删除</button></div></td></tr>';
|
||||
});
|
||||
h += '</tbody></table>';
|
||||
}
|
||||
h += '<div class="field-row" style="margin-top:12px"><input type="text" id="pol-entity" placeholder="app:os.meow.ncm / svc:x / proc:y" style="max-width:280px">' +
|
||||
'<button class="btn btn-primary" id="pol-block">设为阻断</button>' +
|
||||
'<button class="btn btn-ghost" id="pol-allow">设为放行</button></div>';
|
||||
'<button class="btn btn-primary" id="pol-block"' + (!pwe ? ' disabled' : '') + '>设为阻断</button>' +
|
||||
'<button class="btn btn-ghost" id="pol-allow"' + (!pwe ? ' disabled' : '') + '>设为放行</button></div>';
|
||||
h += '<div class="field-row" style="margin-top:8px"><input type="text" id="pol-domain" placeholder="域名(+白 / -黑,如 +music.example.com)" style="max-width:280px">' +
|
||||
'<button class="btn" id="pol-dom">添加域名规则</button></div>';
|
||||
'<button class="btn" id="pol-dom"' + (!pwe ? ' disabled' : '') + '>添加域名规则</button></div>';
|
||||
h += '<p class="muted">防自锁实体(dropbear/wpa_supplicant/udhcpc/netmgrd/svcd/sysui/init)拒 block。</p>';
|
||||
h += '</div>';
|
||||
return h;
|
||||
@@ -1284,6 +1306,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
flush.disabled = false;
|
||||
fetchNetOnce();
|
||||
}
|
||||
if (e.target.closest('#pol-enable')) { enablePolicyWrite(fetchNetOnce); return; }
|
||||
/* ---- 网络策略写(require_auth + 二次确认;enforce=dns_only 如实显示,不谎称断连) ---- */
|
||||
const tgl = e.target.closest('[data-ptoggle]');
|
||||
if (tgl) {
|
||||
@@ -1309,6 +1332,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
if (blk) {
|
||||
const ent = ($('#pol-entity') ? $('#pol-entity').value.trim() : '');
|
||||
if (!ent) { toast('请输入实体(app:xxx / svc:xxx / proc:xxx)'); return; }
|
||||
if (netLockedEntity(ent)) { toast('防自锁:禁止阻断系统关键服务(dropbear/wpa_supplicant/udhcpc/netmgrd/svcd/sysui/init)'); return; }
|
||||
const ok = await confirmDlg('阻断实体', '确认将 ' + ent + ' 设为阻断(DNS 层拒绝,不会真断连)?', true);
|
||||
if (!ok) { fetchNetOnce(); return; }
|
||||
try { await api('POST', '/api/net/policy', { op: 'set_net', entity: ent, arg: 'block' }); toast('已阻断:' + ent); }
|
||||
@@ -1330,6 +1354,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
|
||||
const ent = ($('#pol-entity') ? $('#pol-entity').value.trim() : '');
|
||||
const hst = ($('#pol-domain') ? $('#pol-domain').value.trim() : '');
|
||||
if (!ent) { toast('请输入实体'); return; }
|
||||
if (netLockedEntity(ent)) { toast('防自锁:禁止为系统关键服务配置域名限制(dropbear/wpa_supplicant/udhcpc/netmgrd/svcd/sysui/init)'); return; }
|
||||
if (!hst || (hst[0] !== '+' && hst[0] !== '-')) { toast('域名须以 +(白)或 -(黑)开头'); return; }
|
||||
const ok = await confirmDlg('域名规则', '确认对 ' + ent + ' ' + (hst[0] === '+' ? '加入白名单' : '加入黑名单') + ' ' + hst.slice(1) + '?', hst[0] === '-');
|
||||
if (!ok) { fetchNetOnce(); return; }
|
||||
|
||||
Reference in new issue
Block a user