From 20f4727714c39efd262af2c5346e502f96beae59 Mon Sep 17 00:00:00 2001
From: AskaEth
Date: Wed, 4 Nov 2026 20:10:42 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20M6.44+M6.45=20=E7=8E=B0=E5=9C=BA?=
=?UTF-8?q?=E8=A1=A5=E5=85=A8=20=E2=80=94=20=E8=A1=A5=E9=BD=90=E6=8C=89?=
=?UTF-8?q?=E6=89=A9=E5=B1=95=E5=90=8D=E8=BF=87=E6=BB=A4=E9=81=97=E6=BC=8F?=
=?UTF-8?q?=E7=9A=84=20web=20=E5=89=8D=E7=AB=AF=E4=B8=8E=20license=20?=
=?UTF-8?q?=E5=90=88=E8=A7=84=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
真机 0.8.45 现场组成补全(初次提现场按扩展名过滤漏掉 7 个文本文件),
内容逐字节取自容器现场 /mnt/mb/home/Aska/Project/R3ProII 并 md5 对拍一致:
- patch_v2/meowpanel/web/meowpanel.css (+17) 趋势/App 详情卡样式
- patch_v2/meowpanel/web/mp_pages.js (+289) 性能趋势/功耗曲线/App 详情/蓝牙详情
- patch_v2/meowpanel/web/mp_shared.js (+49) 趋势 sparkline 通用逻辑
- patch_v2/reader_app/.gitignore (+2) seg/chapter smoke host
- patch_v2/reader_app/NOTICE (+23) FreeType 2.14.3 + zlib 条目
- patch_v2/reader_app/resources/licenses/NOTICE (+23) 同 reader_app/NOTICE(md5 一致)
- patch_v2/reader_app/resources/licenses/FTL.txt (新增 6743B) FreeType 许可证全文
面板前端对应已收编的后端 mp_files.c/mp_net.c(文件搜索+蓝牙详情),
NOTICE/FTL.txt 为 LGPL/FreeType 合规产物。仅投运现场内容,未改内容。
---
patch_v2/meowpanel/web/meowpanel.css | 17 +
patch_v2/meowpanel/web/mp_pages.js | 291 ++++++++++++++++--
patch_v2/meowpanel/web/mp_shared.js | 49 ++-
patch_v2/reader_app/.gitignore | 2 +
patch_v2/reader_app/NOTICE | 23 ++
.../reader_app/resources/licenses/FTL.txt | 169 ++++++++++
patch_v2/reader_app/resources/licenses/NOTICE | 23 ++
7 files changed, 551 insertions(+), 23 deletions(-)
create mode 100644 patch_v2/reader_app/resources/licenses/FTL.txt
diff --git a/patch_v2/meowpanel/web/meowpanel.css b/patch_v2/meowpanel/web/meowpanel.css
index 469324a..cde0745 100644
--- a/patch_v2/meowpanel/web/meowpanel.css
+++ b/patch_v2/meowpanel/web/meowpanel.css
@@ -270,6 +270,9 @@ input[type=range]{ width:100%; accent-color:var(--primary); }
.app-card .app-pkg{ font-size:12px; color:var(--on-surface-variant); font-family:var(--mono); }
.app-card .app-meta{ font-size:12px; color:var(--on-surface-variant); }
.app-card .app-actions{ display:flex; gap:6px; margin-top:auto; flex-wrap:wrap; }
+.app-detail-card{ grid-column:1/-1; }
+.app-detail-card .app-detail-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:6px; }
+.app-detail-card .app-detail-head strong{ font-family:var(--mono); }
/* 后台活动列表(批④,与启动器后台管理页观感呼应:MD3 圆角卡 + 行右杀按钮) */
.bg-list{ display:flex; flex-direction:column; gap:8px; }
@@ -457,6 +460,20 @@ input[type=range]{ width:100%; accent-color:var(--primary); }
.lvl-spark .grid{ stroke:var(--outline-variant); stroke-width:1; }
.lvl-note{ color:var(--on-surface-variant); font-size:12px; margin-top:6px; }
+/* 性能趋势 sparkline(F1-① 多序列:CPU/内存/负载/温度)+ 功耗历史曲线(F1-②) */
+.trend-spark polyline{ vector-effect:non-scaling-stroke; }
+.trend-cpu{ stroke:#4a8bf5; }
+.trend-mem{ stroke:#00bfa5; }
+.trend-load{ stroke:#ffb300; }
+.trend-temp{ stroke:#ff7043; }
+.trend-legend{ display:flex; flex-wrap:wrap; gap:8px 16px; margin-top:12px; font-size:12px; }
+.trend-legend .sw{ display:inline-block; width:12px; height:12px; border-radius:3px; margin-right:5px; vertical-align:middle; }
+.trend-legend .seg{ display:inline-flex; align-items:center; }
+.pwr-chart{ display:block; width:100%; height:60px; margin-top:8px; }
+.pwr-chart .grid{ stroke:var(--outline-variant); stroke-width:1; }
+.pwr-mw{ stroke:#ff7043; }
+.pwr-pct{ stroke:#4a8bf5; }
+
/* 置信度徽标(measured/derived/estimated) */
.conf-badge{ display:inline-flex; align-items:center; gap:5px; padding:3px 12px; border-radius:999px; font-size:12px; font-weight:600; }
.conf-badge.measured{ background:rgba(76,175,80,.18); color:var(--success); }
diff --git a/patch_v2/meowpanel/web/mp_pages.js b/patch_v2/meowpanel/web/mp_pages.js
index 7160075..77af2f0 100644
--- a/patch_v2/meowpanel/web/mp_pages.js
+++ b/patch_v2/meowpanel/web/mp_pages.js
@@ -3,7 +3,7 @@
(function () {
'use strict';
var MP = window.MP = window.MP || {};
-const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode, authRequired, barHtml, bindLogin, bindPwDlg, bindSlider, bindThemeUi, browserMode, confirmDlg, dbPct, dbg, dbgErr, debounce, deciToDb, devStyleLabel, devThemeLabel, doLogin, drawSpark, emptyState, errText, esc, fmtBytes, fmtDb, fmtTime, fmtUptime, hideLogin, homeCard, homeKv, homeUnavailable, initTheme, isAvail, levelMeterHtml, loginErr, modePill, passwordDlg, pct, pushSpark, refreshAuthGate, resetSpark, resolveMode, resolveStyle, saveSetting, setConn, showLogin, slur, sparkPeak, sparkRms, sparklineHtml, sparklinePoints, stat, state, storageDel, storageGet, storageSet, storedMode, storedStyle, toast, toastTimer, updateLevelMeter, volTimer } = MP;
+const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode, authRequired, barHtml, bindLogin, bindPwDlg, bindSlider, bindThemeUi, browserMode, confirmDlg, dbPct, dbg, dbgErr, debounce, deciToDb, devStyleLabel, devThemeLabel, doLogin, drawSpark, drawTrendSpark, emptyState, errText, esc, fmtBytes, fmtDb, fmtTime, fmtUptime, hideLogin, homeCard, homeKv, homeUnavailable, initTheme, isAvail, levelMeterHtml, loginErr, modePill, passwordDlg, pct, polylinePoints, pushSpark, pushTrend, refreshAuthGate, resetSpark, resetTrend, resolveMode, resolveStyle, saveSetting, setConn, showLogin, slur, sparkPeak, sparkRms, sparklineHtml, sparklinePoints, stat, state, storageDel, storageGet, storageSet, storedMode, storedStyle, toast, toastTimer, trendCpu, trendLoad, trendMem, trendSvgHtml, trendTemp, updateLevelMeter, volTimer } = MP;
let audioTimer = null;
let audioVolDrag = false; // 音量滑条拖拽中(暂停 1s 重渲染,防打断交互)
@@ -14,6 +14,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
let powerData = null, powerEvents = null, powerHistory = null;
let procTimer = null;
let expandedProc = null; // { pkg, file, lines, truncated }:已展开的 App stdout 尾部(重渲染后重开,防轮询清空)
+ let appDetailPkg = null; // F1-⑤ 当前展开「App 详情四源聚合」的包名
let netTimer = null;
let netEntityFilter = '';
let netLoaded = false; // 是否已成功加载过一次网络状态(用于「首次失败只显示横幅 + 占位」语义)
@@ -92,7 +93,28 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
h += stat('温度', '' + (tmpCpu == null ? '—' : tmpCpu) + '℃',
tmp && tmp.board != null ? '板载 ' + (+tmp.board.toFixed(1)) + ' ℃' : '');
h += '';
+ /* F1-① 趋势(2s 节拍,最近 30 点≈1 分钟):CPU%/内存%/负载(1m/核)/温度(20-90℃ 归一) */
+ const loadN = (cpu.load && cpu.load[0] != null && cpu.cores) ? (cpu.load[0] / cpu.cores) * 100 : null;
+ pushTrend(trendCpu, cpu.usage);
+ pushTrend(trendMem, mem.total ? memP : null);
+ pushTrend(trendLoad, loadN);
+ pushTrend(trendTemp, tmpCpu != null ? (tmpCpu - 20) / 70 * 100 : null);
+ const perfTrend = [
+ { cls: 'trend-cpu', arr: trendCpu },
+ { cls: 'trend-mem', arr: trendMem },
+ { cls: 'trend-load', arr: trendLoad },
+ { cls: 'trend-temp', arr: trendTemp }
+ ];
+ h += '趋势(2s 采样 · 最近 ' + trendCpu.length + ' 点)
' +
+ '
' +
+ 'CPU ' + (cpu.usage == null ? '—' : (+cpu.usage).toFixed(1) + '%') + '' +
+ '内存 ' + (mem.total ? memP.toFixed(1) + '%' : '—') + '' +
+ '负载1m/核 ' + (cpu.load && cpu.load[0] != null ? (+cpu.load[0]).toFixed(2) : '—') + '' +
+ '温度 ' + (tmpCpu == null ? '—' : tmpCpu + '℃') + '' +
+ '
' + trendSvgHtml(perfTrend) +
+ '
温度曲线按 20–90℃ 归一;负载按 load1/核(100%=单核满载)。进页起累积,切页清零。
';
page.innerHTML = h;
+ drawTrendSpark(page, perfTrend);
}
async function fetchPerfOnce() {
@@ -109,6 +131,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
function renderPerf() {
$('#content').innerHTML = '';
if (state.perfTimer) stopPerf();
+ resetTrend(); // 进页重置趋势累积(F1-①)
fetchPerfOnce();
state.perfTimer = setInterval(fetchPerfOnce, 2000);
}
@@ -443,11 +466,21 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
const cpu = perf.cpu || {}, mem = perf.memory || {}, tmp = perf.temp || {};
const cpuU = cpu.usage;
const memP = mem.total ? (mem.used / mem.total) * 100 : 0;
+ /* F1-① 首页迷你趋势(5s overview 节拍,CPU/内存/温度) */
+ pushTrend(trendCpu, cpuU);
+ pushTrend(trendMem, mem.total ? memP : null);
+ pushTrend(trendTemp, tmp.cpu != null ? ((+tmp.cpu) - 20) / 70 * 100 : null);
+ const homeTrend = [
+ { cls: 'trend-cpu', arr: trendCpu },
+ { cls: 'trend-mem', arr: trendMem },
+ { cls: 'trend-temp', arr: trendTemp }
+ ];
return homeCard('性能小条', '' +
'
CPU
' + (cpuU == null ? '—' : (+cpuU).toFixed(1)) + '%
' + barHtml(cpuU || 0) + '
' +
'
内存
' + (mem.total ? fmtBytes(mem.used * 1024) : '—') + '
' + barHtml(mem.used && mem.total ? memP : 0) + '
' +
'
温度
' + (tmp.cpu != null ? (+tmp.cpu).toFixed(1) + '℃' : '—') + '
' +
- '
');
+ '' + trendSvgHtml(homeTrend) +
+ '
趋势 5s 采样 · CPU / 内存 / 温度(20–90℃)
');
}
function powerSummaryCard() {
@@ -483,6 +516,11 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
deviceCard() + batteryCard() + audioCard() + netCard() + btCard() + healthCard() + storageCard() + perfCard() +
'';
body.innerHTML = h;
+ drawTrendSpark(body, [
+ { cls: 'trend-cpu', arr: trendCpu },
+ { cls: 'trend-mem', arr: trendMem },
+ { cls: 'trend-temp', arr: trendTemp }
+ ]); // F1-① 首页迷你趋势(perfCard 内 .trend-spark)
const ad = homeData.audio;
if (isAvail(ad)) updateLevelMeter($('#home-audio-level'), ad);
}
@@ -522,6 +560,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
function renderHome() {
stopHome();
resetSpark(); // 进入首页重置 sparkline 累积
+ resetTrend(); // 进入首页重置性能趋势累积(F1-①)
homeData = null; homeSys = null;
$('#content').innerHTML =
'' +
@@ -645,6 +684,27 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
'
';
}
+ function powerHistoryChart(h) {
+ const samples = (h && h.samples) || []; // [mono, pct, est_mw, chg, scr, bri](30s 环形 ≈4h)
+ if (!samples.length) return '';
+ let maxMw = 0, hasMw = false;
+ samples.forEach(function (s2) { if (s2[2] != null && s2[2] >= 0) { hasMw = true; if (s2[2] > maxMw) maxMw = s2[2]; } });
+ if (!hasMw) return '本窗口无 est_mw 估算采样(只读模式/未知),不画曲线。
';
+ const W = 100, H = 30;
+ const mwVals = samples.map(function (s2) { return (s2[2] != null && s2[2] >= 0 && maxMw > 0) ? s2[2] / maxMw * 100 : null; });
+ const pctVals = samples.map(function (s2) { return (s2[1] != null && s2[1] >= 0) ? s2[1] : null; });
+ return '' +
+ 'est_mw(窗内峰值 ' + maxMw + ' mW)' +
+ '电量 %' +
+ '
' +
+ '' +
+ '30s 采样 · 内存环形约 4h · est_mw 为经验估算(confidence 见「功耗估算」卡),非实测功率;X 轴为采样序(等间隔)。
';
+ }
+
function powerHistoryCard(h) {
if (!h) return '';
if (h.available === false) return '功耗历史
' + homeUnavailable() + '';
@@ -654,6 +714,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
row('起止电量', (h.start_pct != null ? h.start_pct : '—') + '% → ' + (h.end_pct != null ? h.end_pct : '—') + '%') +
row('平均功耗', (h.avg_mw != null ? h.avg_mw + ' mW' : '—')) +
'';
+ body += powerHistoryChart(h); // F1-② est_mw 4h 曲线(纯前端画既有 samples)
return '功耗历史
' + body + '';
}
@@ -1134,18 +1195,44 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
h += row('状态', '未连接');
}
h += '';
- // 蓝牙摘要
- const bt = (d && d.bt) || {};
- h += '蓝牙
';
- if (bt.available) {
- h += row('状态', '' + (bt.powered ? '已开启' : '已关闭') + '');
- h += row('地址', esc(bt.address || '—'));
- h += row('已配对', esc(bt.paired != null ? bt.paired : '—'));
- h += row('DNS', esc(((bt.dns) || []).join(' / ') || '—'));
- } else {
- h += row('状态', '无蓝牙');
+ return h;
+ }
+
+ /* F1-③ 蓝牙详情(GET /api/net/bt 只读聚合:status/paired/connected/a2dp/nearby) */
+ function btDevTable(title, list) {
+ let h = '' + esc(title) + '
';
+ if (!list || !list.length) return h + '无
';
+ h += '| MAC | 名称 |
';
+ list.forEach(function (d) { h += '| ' + esc(d.mac) + ' | ' + esc(d.name || '—') + ' |
'; });
+ return h + '
';
+ }
+
+ function netBtCard(bt) {
+ if (!bt || bt.available === false) return '蓝牙
' + homeUnavailable() + '';
+ let h = '蓝牙
';
+ h += '
';
+ h += row('状态', '' + (bt.powered ? '已开启' : '已关闭') + '');
+ h += row('地址', esc(bt.address || '—'));
+ h += row('已配对 / 已连接', (bt.paired_n != null ? esc(bt.paired_n) : '—') + ' / ' + (bt.connected_n != null ? esc(bt.connected_n) : '—'));
+ h += '
';
+ h += btDevTable('已配对设备', bt.paired);
+ h += btDevTable('已连接设备(内核 ACL)', bt.connected);
+ const a2 = bt.a2dp || [];
+ h += '
A2DP 传输
';
+ if (!a2.length) h += '
无 A2DP 端点
';
+ else {
+ h += '
| 设备 | 编解码 | 采样率 | 声道/位深 | 传输 | 角色 |
';
+ a2.forEach(function (a) {
+ h += '| ' + esc(a.mac) + ' | ' + esc(a.codec || '—') + ' | ' + esc(a.rate || '—') + ' | ' +
+ '' + esc(a.channels || '—') + ' / ' + esc(a.bits || '—') + ' | ' +
+ '' + (a.acquired ? 'RUNNING' : '空闲') + ' | ' +
+ '' + esc(a.role || '—') + ' |
';
+ });
+ h += '
';
}
- h += '
';
+ h += btDevTable('附近设备(扫描缓存)', bt.nearby);
+ h += '只读聚合(netmgrd 0xE6:status/paired/connected/a2dp/devices);配对/连接/开关等写操作走实体机,面板未开放。
';
+ h += '';
return h;
}
@@ -1239,8 +1326,8 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
async function fetchNetOnce() {
if (state.route !== 'net') return;
const stEl = $('#net-status'), cbEl = $('#net-conns-body'), dbEl = $('#net-dns-body'),
- ifEl = $('#net-ifaces'), poEl = $('#net-policy');
- let st, cn, dn, ifd, pol;
+ ifEl = $('#net-ifaces'), poEl = $('#net-policy'), btEl = $('#net-bt');
+ let st, cn, dn, ifd, pol, bt;
try { st = await api('GET', '/api/net/status'); }
catch (e) { netBannerErr(errText(e)); return; }
netLoaded = true;
@@ -1251,9 +1338,11 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
catch (e) { dn = null; }
try { ifd = await api('GET', '/api/net/ifaces'); } catch (e) { ifd = null; }
try { pol = await api('GET', '/api/net/policy'); } catch (e) { pol = null; }
+ try { bt = await api('GET', '/api/net/bt'); } catch (e) { bt = null; } /* F1-③ 蓝牙只读详情 */
if (state.route !== 'net') return;
if (ifEl) ifEl.innerHTML = netIfaceCards(ifd);
if (stEl) stEl.innerHTML = netStatusCards(st);
+ if (btEl) btEl.innerHTML = netBtCard(bt);
if (poEl) poEl.innerHTML = netPolicyCard(pol);
if (cbEl) cbEl.innerHTML = netConnsBody(cn);
if (dbEl) dbEl.innerHTML = netDnsBody(dn);
@@ -1293,6 +1382,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
'' +
'' +
'' +
+ '' +
'' +
'每实体连接表
' +
'
' +
@@ -1425,6 +1515,10 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
'' +
'' +
'
';
+ /* F1-④ 搜索(只读,当前目录起递归限深 4 层) */
+ h += '
' +
+ '' +
+ '
';
h += '
| 名称 | 大小 | 修改时间 | 操作 |
';
if (!d.items.length) h += '| 空目录 |
';
d.items.forEach(function (it) {
@@ -1450,6 +1544,49 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
function renderFiles() { $('#content').innerHTML = ''; navFiles(state.filesRel || '/'); }
+ /* F1-④ 搜索结果视图(GET /api/files/search,只读;下载/进入仍走既有端点) */
+ function searchParentRel(it) {
+ const rel = it.rel || '/';
+ if (it.type === 'dir') return rel;
+ const i = rel.lastIndexOf('/');
+ return i > 0 ? rel.slice(0, i) : '/';
+ }
+
+ async function searchFiles() {
+ const q = ($('#file-search') ? $('#file-search').value : '').trim();
+ if (!q) return toast('请输入搜索关键字');
+ const page = $('#files-page'); if (!page) return;
+ const abs = state.filesAbs || '/';
+ page.innerHTML = '在 ' + esc(state.filesRel || '/') + ' 内搜索「' + esc(q) + '」…
';
+ let d;
+ try { d = await api('GET', '/api/files/search?path=' + encodeURIComponent(abs) + '&name=' + encodeURIComponent(q)); }
+ catch (e) { page.innerHTML = emptyState('搜索失败:' + errText(e), e.code); return; }
+ let h = crumbHtml(segments(d.rel), d.rel);
+ h += '' +
+ '「' + esc(q) + '」在 ' + esc(d.rel) + ' 内命中 ' + (d.count || 0) + ' 条' +
+ (d.truncated ? '(已达上限,结果截断)' : '') + '' +
+ '
';
+ h += '| 名称 | 位置 | 大小 | 修改时间 | 操作 |
';
+ if (!d.items.length) h += '| 无匹配 |
';
+ d.items.forEach(function (it) {
+ const isDir = it.type === 'dir';
+ const icon = isDir ? '📁' : '📄';
+ const parent = searchParentRel(it);
+ h += '| ' + icon + ' ' + esc(it.name) + ' | ' +
+ '' + esc(parent) + ' | ' +
+ '' + (isDir ? '—' : fmtBytes(it.size)) + ' | ' +
+ '' + fmtTime(it.mtime) + ' | ' +
+ '' +
+ (isDir
+ ? ' '
+ : ' ' +
+ ' 下载') +
+ ' |
';
+ });
+ h += '
';
+ page.innerHTML = h;
+ }
+
function absFor(name) { return state.filesAbs ? (state.filesAbs.endsWith('/') ? state.filesAbs + name : state.filesAbs + '/' + name) : null; }
function bindFiles() {
@@ -1460,6 +1597,7 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
const ren = t.closest('[data-ren]'); const del = t.closest('[data-del]');
const dirlink = t.closest('[data-dir]'); const nav = t.closest('[data-nav]');
const crumb = t.closest('[data-rel]');
+ const sopen = t.closest('[data-sopen]'); const sdl = t.closest('[data-sdl]');
const btn = t.closest('button[id]');
if (crumb) { navFiles(crumb.dataset.rel); return; }
if (nav) { navFiles(nav.dataset.nav); return; }
@@ -1469,11 +1607,22 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
doDownload('/api/files/download?path=' + encodeURIComponent(abs), dl.dataset.dl);
return;
}
+ if (sopen) { navFiles(sopen.dataset.sopen); return; } /* F1-④ 搜索结果:打开目录/所在目录 */
+ if (sdl) { /* F1-④ 搜索结果:下载(rel 已是卡根相对) */
+ e.preventDefault();
+ doDownload('/api/files/download?path=' + encodeURIComponent(sdl.dataset.sdl), sdl.dataset.name || sdl.dataset.sdl);
+ return;
+ }
if (ren) { renameFile(ren.dataset.ren); return; }
if (del) { removeFile(del.dataset.del); return; }
if (btn && btn.id === 'file-newdir') { mkdirFile(); return; }
if (btn && btn.id === 'file-upload') { const fi = $('#file-input'); if (fi) { fi.value = ''; fi.click(); } return; }
if (btn && btn.id === 'file-refresh') { navFiles(state.filesRel); return; }
+ if (btn && btn.id === 'file-search-go') { searchFiles(); return; } /* F1-④ */
+ if (btn && btn.id === 'file-search-back') { navFiles(state.filesRel); return; } /* F1-④ */
+ });
+ c.addEventListener('keydown', function (e) {
+ if (e.key === 'Enter' && e.target && e.target.id === 'file-search') { e.preventDefault(); searchFiles(); }
});
c.addEventListener('change', function (e) {
if (e.target.id === 'file-input') uploadFiles(e.target.files);
@@ -1575,9 +1724,11 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
const install = t.closest('#app-install'); const refresh = t.closest('#app-refresh');
const info = t.closest('[data-ainfo]'); const exp = t.closest('[data-aexport]');
const uninst = t.closest('[data-auninst]'); const goPath = t.closest('#app-install-path-go');
+ const aclose = t.closest('[data-aclose]');
if (install) { const fi = $('#app-file'); if (fi) { fi.value = ''; fi.click(); } return; }
if (refresh) { renderApps(); return; }
if (info) { appInfo(info.dataset.ainfo); return; }
+ if (aclose) { appDetailRemove(); return; }
if (exp) {
e.preventDefault();
if (exp.dataset.busy) return; /* 打包期间防连点 */
@@ -1624,14 +1775,110 @@ const { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode
catch (e) { toast('卸载失败:' + errText(e)); }
}
- async function appInfo(pkg) {
+ /* ---- F1-⑤ 单 App 详情四源聚合(FUTURE_MEOWPANEL §2.4,纯前端):apps 页行内展开,
+ * 并发聚合既有端点:①安装域 /api/apps/:pkg ②运行崩溃史 /api/ams/:pkg ③实时连接
+ * /api/net/conns?entity=app: ④stdout 尾部(newestStdout + /api/logs/read)。
+ * 单源失败 → 该子块「—/不可用」,绝不拖垮整卡(overview 同款降级)。 ---- */
+ function appDetailRemove() {
+ const el = document.querySelector('.app-detail-card');
+ if (el) el.remove();
+ appDetailPkg = null;
+ }
+
+ async function appStdoutBlock(pkg) {
try {
- const d = await api('GET', '/api/apps/' + pkg);
- let txt = '包名: ' + d.package + '\n名称: ' + d.name + '\n版本: ' + d.version + '\nexec: ' + d.exec +
- '\nsize: ' + fmtBytes(d.size) + '\n路径: ' + d.path;
- if (d.description) txt += '\n描述: ' + d.description;
- alert(txt);
- } catch (e) { toast('获取详情失败:' + errText(e)); }
+ const file = await newestStdout(pkg);
+ if (!file) return '无 stdout 日志
';
+ const d = await api('GET', '/api/logs/read?file=' + encodeURIComponent(pkg + '/' + file) + '&lines=200');
+ const lines = d.lines || [];
+ return '' + esc(lines.join('\n') || '(空)') +
+ (d.truncated ? '\n…(超出尾部上限,部分被截断)' : '') + '' +
+ '' + esc(pkg + '/' + file) + ' · 末尾 ' + lines.length + ' 行
';
+ } catch (e) { return 'stdout 读取失败:' + esc(errText(e)) + '
'; }
+ }
+
+ async function appDetailBody(pkg) {
+ const r = await Promise.allSettled([
+ api('GET', '/api/apps/' + pkg),
+ api('GET', '/api/ams/' + pkg),
+ api('GET', '/api/net/conns?entity=' + encodeURIComponent('app:' + pkg)),
+ appStdoutBlock(pkg)
+ ]);
+
+ let h = '';
+ /* ① 安装域(app.json) */
+ const ap = r[0].status === 'fulfilled' ? r[0].value : null;
+ if (ap) {
+ h += '' +
+ row('名称', esc(ap.name || '—')) +
+ row('版本', esc(ap.version || '—')) +
+ row('执行', '' + esc(ap.exec || '—') + '') +
+ row('体积', fmtBytes(ap.size)) +
+ row('路径', '' + esc(ap.path || '—') + '') +
+ '
';
+ } else {
+ h += '安装详情不可用' + (r[0].status === 'rejected' ? ':' + esc(errText(r[0].reason)) : '') + '
';
+ }
+
+ /* ② 运行域崩溃/事件史(am history) */
+ h += '崩溃 / 事件历史(amsd)
';
+ const am = r[1].status === 'fulfilled' ? r[1].value : null;
+ const evs = (am && am.available && am.events) || [];
+ if (evs.length) {
+ h += '| 时间 | 类型 | 详情 |
';
+ evs.slice().reverse().slice(0, 10).forEach(function (ev) {
+ h += '| ' + fmtTime(ev.ts) + ' | ' + esc(ev.kind || '—') + ' | ' +
+ '' + esc(ev.detail || '—') + ' |
';
+ });
+ h += '
';
+ } else {
+ h += '无事件记录' + (r[1].status === 'rejected' ? '(' + esc(errText(r[1].reason)) + ')' : '') + '
';
+ }
+
+ /* ③ 实时连接(app:) */
+ h += '实时连接(app:' + esc(pkg) + ')
';
+ const cn = r[2].status === 'fulfilled' ? r[2].value : null;
+ const conns = (cn && cn.conns) || [];
+ if (conns.length) {
+ h += '| 远端 | 本端 | 状态 | 寿命 |
';
+ conns.forEach(function (c) {
+ h += '| ' + esc(c.domain || c.remote || '—') + ' | ' +
+ '' + esc(c.local || '—') + ' | ' +
+ '' + esc(c.state || '—') + ' | ' + fmtDur(c.age) + ' |
';
+ });
+ h += '
';
+ } else {
+ h += '无连接' + (r[2].status === 'rejected' ? '(' + esc(errText(r[2].reason)) + ')' : '') + '
';
+ }
+
+ /* ④ stdout 尾部 */
+ h += 'stdout 尾部
';
+ h += (r[3].status === 'fulfilled' && r[3].value) ? r[3].value : 'stdout 读取失败
';
+
+ return h;
+ }
+
+ async function appInfo(pkg) {
+ if (appDetailPkg === pkg) { appDetailRemove(); return; } // 已展开 → 收起
+ appDetailRemove(); // 收起上一个
+ const card = document.querySelector('.app-card[data-pkg="' + escSafeAttr(pkg) + '"]');
+ if (!card) return;
+ const box = document.createElement('div');
+ box.className = 'card app-detail-card';
+ box.innerHTML = '' + esc(pkg) + '' +
+ '
' +
+ '并发聚合四源详情…
';
+ card.after(box);
+ appDetailPkg = pkg;
+ try {
+ box.innerHTML = '' + esc(pkg) + '' +
+ '
' + (await appDetailBody(pkg));
+ } catch (e) {
+ box.innerHTML = '' + esc(pkg) + '' +
+ '
' +
+ '详情加载失败:' + esc(errText(e)) + '
';
+ appDetailPkg = null;
+ }
}
function renderFontsPage() {
diff --git a/patch_v2/meowpanel/web/mp_shared.js b/patch_v2/meowpanel/web/mp_shared.js
index f67b23b..af1e00e 100644
--- a/patch_v2/meowpanel/web/mp_shared.js
+++ b/patch_v2/meowpanel/web/mp_shared.js
@@ -99,6 +99,53 @@
if (arr.length > SPARK_MAX) arr.shift();
}
+ /* ---- F1-① 性能趋势(FUTURE_MEOWPANEL §2.1):pushSpark 模式泛化到任意 0..100 归一值 ---- */
+ const trendCpu = [], trendMem = [], trendLoad = [], trendTemp = [];
+
+ function pushTrend(arr, v) { // 0..100 归一值入队(<=SPARK_MAX 点);null/NaN 不入队(诚实空缺)
+ if (v == null || isNaN(v)) return;
+ arr.push(Math.max(0, Math.min(100, Number(v))));
+ if (arr.length > SPARK_MAX) arr.shift();
+ }
+
+ function resetTrend() { trendCpu.length = 0; trendMem.length = 0; trendLoad.length = 0; trendTemp.length = 0; }
+
+ /* series: [{cls,arr}],cls 决定颜色(CSS .trend-cpu/.trend-mem/.trend-load/.trend-temp),
+ * 位置稳定(首页 3 序列、perf 页 4 序列共用同一语义色,不靠序号猜色)。 */
+ function trendSvgHtml(series) {
+ let polys = '';
+ series.forEach(function (s) {
+ polys += '';
+ });
+ return '';
+ }
+
+ function drawTrendSpark(rootEl, series) {
+ const svg = rootEl && rootEl.querySelector('.trend-spark');
+ if (!svg) return;
+ series.forEach(function (s) {
+ const p = svg.querySelector('.' + s.cls);
+ if (p) p.setAttribute('points', sparklinePoints(s.arr));
+ });
+ }
+
+ /* 通用折线点串(任意长度数组,值 0..100 归一;null 跳过):x 铺满 w,y 反向映射 h 内(F1-② 功耗 4h 曲线用) */
+ function polylinePoints(vals, w, h) {
+ const n = vals.length;
+ if (!n) return '';
+ const out = [];
+ for (let i = 0; i < n; i++) {
+ const v = vals[i];
+ if (v == null || isNaN(v)) continue;
+ const x = n > 1 ? (i / (n - 1)) * w : w / 2;
+ const y = h - 2 - Math.max(0, Math.min(100, v)) / 100 * (h - 4);
+ out.push(x.toFixed(1) + ',' + y.toFixed(1));
+ }
+ return out.join(' ');
+ }
+
function drawSpark(rootEl) {
const svg = rootEl && rootEl.querySelector('.lvl-spark');
if (!svg) return;
@@ -463,5 +510,5 @@
});
}
- Object.assign(MP, { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode, authRequired, barHtml, bindLogin, bindPwDlg, bindSlider, bindThemeUi, browserMode, confirmDlg, dbPct, dbg, dbgErr, debounce, deciToDb, devStyleLabel, devThemeLabel, doLogin, drawSpark, emptyState, errText, esc, fmtBytes, fmtDb, fmtTime, fmtUptime, hideLogin, homeCard, homeKv, homeUnavailable, initTheme, isAvail, levelMeterHtml, loginErr, modePill, passwordDlg, pct, pushSpark, refreshAuthGate, resetSpark, resolveMode, resolveStyle, saveSetting, setConn, showLogin, slur, sparkPeak, sparkRms, sparklineHtml, sparklinePoints, stat, state, storageDel, storageGet, storageSet, storedMode, storedStyle, toast, toastTimer, updateLevelMeter, volTimer });
+ Object.assign(MP, { $, $$, LOG_GROUP_NAME, SPARK_MAX, api, apiDownload, applyTheme, authMode, authRequired, barHtml, bindLogin, bindPwDlg, bindSlider, bindThemeUi, browserMode, confirmDlg, dbPct, dbg, dbgErr, debounce, deciToDb, devStyleLabel, devThemeLabel, doLogin, drawSpark, drawTrendSpark, emptyState, errText, esc, fmtBytes, fmtDb, fmtTime, fmtUptime, hideLogin, homeCard, homeKv, homeUnavailable, initTheme, isAvail, levelMeterHtml, loginErr, modePill, passwordDlg, pct, polylinePoints, pushSpark, pushTrend, refreshAuthGate, resetSpark, resetTrend, resolveMode, resolveStyle, saveSetting, setConn, showLogin, slur, sparkPeak, sparkRms, sparklineHtml, sparklinePoints, stat, state, storageDel, storageGet, storageSet, storedMode, storedStyle, toast, toastTimer, trendCpu, trendLoad, trendMem, trendSvgHtml, trendTemp, updateLevelMeter, volTimer });
})();
\ No newline at end of file
diff --git a/patch_v2/reader_app/.gitignore b/patch_v2/reader_app/.gitignore
index 483ad59..f79b20a 100644
--- a/patch_v2/reader_app/.gitignore
+++ b/patch_v2/reader_app/.gitignore
@@ -4,6 +4,8 @@
/os.meow.reader.mpk
/test/test_reader_core_host
/test/loaders_smoke_host
+/test/seg_smoke_host
+/test/chapter_smoke_host
/test/rdft_smoke_mipsel
/test/book_smoke_mipsel
/test/formats_smoke_mipsel
diff --git a/patch_v2/reader_app/NOTICE b/patch_v2/reader_app/NOTICE
index a7c6b8d..2446773 100644
--- a/patch_v2/reader_app/NOTICE
+++ b/patch_v2/reader_app/NOTICE
@@ -232,3 +232,26 @@ Library.
project: https://github.com/nothings/stb
Used for image decode + downsample (CBZ pages, covers).
+--------------------------------------------------------------------
+4. FreeType — Copyright (c) 1996-2002, 2006, David Turner, Robert
+ Wilhelm, and Werner Lemberg
+ license: The FreeType Project License (FTL)
+ version: 2.14.3 (fetched by build_reader.sh)
+ project: https://freetype.org
+ Used for glyph loading, glyph measurement, and rasterization
+ of the reader's text layout.
+
+ Portions of this software are copyright © 2006 The FreeType
+ Project (https://freetype.org). All rights reserved.
+
+ Full license text (shipped with the package in
+ resources/licenses/):
+ - The FreeType Project License: resources/licenses/FTL.txt
+
+--------------------------------------------------------------------
+5. zlib — Copyright (c) 1995-2024 Jean-loup Gailly and Mark Adler
+ license: zlib license (permissive)
+ project: https://zlib.net
+ Statically linked for compression support (zlib on the
+ target sysroot; also used by the build toolchain).
+
diff --git a/patch_v2/reader_app/resources/licenses/FTL.txt b/patch_v2/reader_app/resources/licenses/FTL.txt
new file mode 100644
index 0000000..b9e8cbd
--- /dev/null
+++ b/patch_v2/reader_app/resources/licenses/FTL.txt
@@ -0,0 +1,169 @@
+ The FreeType Project LICENSE
+ ----------------------------
+
+ 2006-Jan-27
+
+ Copyright 1996-2002, 2006 by
+ David Turner, Robert Wilhelm, and Werner Lemberg
+
+
+
+Introduction
+============
+
+ The FreeType Project is distributed in several archive packages;
+ some of them may contain, in addition to the FreeType font engine,
+ various tools and contributions which rely on, or relate to, the
+ FreeType Project.
+
+ This license applies to all files found in such packages, and
+ which do not fall under their own explicit license. The license
+ affects thus the FreeType font engine, the test programs,
+ documentation and makefiles, at the very least.
+
+ This license was inspired by the BSD, Artistic, and IJG
+ (Independent JPEG Group) licenses, which all encourage inclusion
+ and use of free software in commercial and freeware products
+ alike. As a consequence, its main points are that:
+
+ o We don't promise that this software works. However, we will be
+ interested in any kind of bug reports. (`as is' distribution)
+
+ o You can use this software for whatever you want, in parts or
+ full form, without having to pay us. (`royalty-free' usage)
+
+ o You may not pretend that you wrote this software. If you use
+ it, or only parts of it, in a program, you must acknowledge
+ somewhere in your documentation that you have used the
+ FreeType code. (`credits')
+
+ We specifically permit and encourage the inclusion of this
+ software, with or without modifications, in commercial products.
+ We disclaim all warranties covering The FreeType Project and
+ assume no liability related to The FreeType Project.
+
+
+ Finally, many people asked us for a preferred form for a
+ credit/disclaimer to use in compliance with this license. We thus
+ encourage you to use the following text:
+
+ """
+ Portions of this software are copyright © The FreeType
+ Project (https://freetype.org). All rights reserved.
+ """
+
+ Please replace with the value from the FreeType version you
+ actually use.
+
+
+Legal Terms
+===========
+
+0. Definitions
+--------------
+
+ Throughout this license, the terms `package', `FreeType Project',
+ and `FreeType archive' refer to the set of files originally
+ distributed by the authors (David Turner, Robert Wilhelm, and
+ Werner Lemberg) as the `FreeType Project', be they named as alpha,
+ beta or final release.
+
+ `You' refers to the licensee, or person using the project, where
+ `using' is a generic term including compiling the project's source
+ code as well as linking it to form a `program' or `executable'.
+ This program is referred to as `a program using the FreeType
+ engine'.
+
+ This license applies to all files distributed in the original
+ FreeType Project, including all source code, binaries and
+ documentation, unless otherwise stated in the file in its
+ original, unmodified form as distributed in the original archive.
+ If you are unsure whether or not a particular file is covered by
+ this license, you must contact us to verify this.
+
+ The FreeType Project is copyright (C) 1996-2000 by David Turner,
+ Robert Wilhelm, and Werner Lemberg. All rights reserved except as
+ specified below.
+
+1. No Warranty
+--------------
+
+ THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO
+ USE, OF THE FREETYPE PROJECT.
+
+2. Redistribution
+-----------------
+
+ This license grants a worldwide, royalty-free, perpetual and
+ irrevocable right and license to use, execute, perform, compile,
+ display, copy, create derivative works of, distribute and
+ sublicense the FreeType Project (in both source and object code
+ forms) and derivative works thereof for any purpose; and to
+ authorize others to exercise some or all of the rights granted
+ herein, subject to the following conditions:
+
+ o Redistribution of source code must retain this license file
+ (`FTL.TXT') unaltered; any additions, deletions or changes to
+ the original files must be clearly indicated in accompanying
+ documentation. The copyright notices of the unaltered,
+ original files must be preserved in all copies of source
+ files.
+
+ o Redistribution in binary form must provide a disclaimer that
+ states that the software is based in part of the work of the
+ FreeType Team, in the distribution documentation. We also
+ encourage you to put an URL to the FreeType web page in your
+ documentation, though this isn't mandatory.
+
+ These conditions apply to any software derived from or based on
+ the FreeType Project, not just the unmodified files. If you use
+ our work, you must acknowledge us. However, no fee need be paid
+ to us.
+
+3. Advertising
+--------------
+
+ Neither the FreeType authors and contributors nor you shall use
+ the name of the other for commercial, advertising, or promotional
+ purposes without specific prior written permission.
+
+ We suggest, but do not require, that you use one or more of the
+ following phrases to refer to this software in your documentation
+ or advertising materials: `FreeType Project', `FreeType Engine',
+ `FreeType library', or `FreeType Distribution'.
+
+ As you have not signed this license, you are not required to
+ accept it. However, as the FreeType Project is copyrighted
+ material, only this license, or another one contracted with the
+ authors, grants you the right to use, distribute, and modify it.
+ Therefore, by using, distributing, or modifying the FreeType
+ Project, you indicate that you understand and accept all the terms
+ of this license.
+
+4. Contacts
+-----------
+
+ There are two mailing lists related to FreeType:
+
+ o freetype@nongnu.org
+
+ Discusses general use and applications of FreeType, as well as
+ future and wanted additions to the library and distribution.
+ If you are looking for support, start in this list if you
+ haven't found anything to help you in the documentation.
+
+ o freetype-devel@nongnu.org
+
+ Discusses bugs, as well as engine internals, design issues,
+ specific licenses, porting, etc.
+
+ Our home page can be found at
+
+ https://freetype.org
+
+
+--- end of FTL.TXT ---
diff --git a/patch_v2/reader_app/resources/licenses/NOTICE b/patch_v2/reader_app/resources/licenses/NOTICE
index a7c6b8d..2446773 100644
--- a/patch_v2/reader_app/resources/licenses/NOTICE
+++ b/patch_v2/reader_app/resources/licenses/NOTICE
@@ -232,3 +232,26 @@ Library.
project: https://github.com/nothings/stb
Used for image decode + downsample (CBZ pages, covers).
+--------------------------------------------------------------------
+4. FreeType — Copyright (c) 1996-2002, 2006, David Turner, Robert
+ Wilhelm, and Werner Lemberg
+ license: The FreeType Project License (FTL)
+ version: 2.14.3 (fetched by build_reader.sh)
+ project: https://freetype.org
+ Used for glyph loading, glyph measurement, and rasterization
+ of the reader's text layout.
+
+ Portions of this software are copyright © 2006 The FreeType
+ Project (https://freetype.org). All rights reserved.
+
+ Full license text (shipped with the package in
+ resources/licenses/):
+ - The FreeType Project License: resources/licenses/FTL.txt
+
+--------------------------------------------------------------------
+5. zlib — Copyright (c) 1995-2024 Jean-loup Gailly and Mark Adler
+ license: zlib license (permissive)
+ project: https://zlib.net
+ Statically linked for compression support (zlib on the
+ target sysroot; also used by the build toolchain).
+