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 '' + rows + '
类型电量说明
'; } + 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 += '
'; + list.forEach(function (d) { h += ''; }); + return h + '
MAC名称
' + esc(d.mac) + '' + esc(d.name || '—') + '
'; + } + + 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 += '' + + '' + + '' + + ''; + }); + h += '
设备编解码采样率声道/位深传输角色
' + esc(a.mac) + '' + esc(a.codec || '—') + '' + esc(a.rate || '—') + '' + esc(a.channels || '—') + ' / ' + esc(a.bits || '—') + '' + (a.acquired ? 'RUNNING' : '空闲') + '' + esc(a.role || '—') + '
'; } - 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 += '' + + '' + + '' + + '' + + ''; + }); + h += '
名称位置大小修改时间操作
无匹配
' + icon + ' ' + esc(it.name) + '' + esc(parent) + '' + (isDir ? '—' : fmtBytes(it.size)) + '' + fmtTime(it.mtime) + '
' + + (isDir + ? '' + : '' + + '下载') + + '
'; + 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 += '' + + ''; + }); + h += '
时间类型详情
' + fmtTime(ev.ts) + '' + esc(ev.kind || '—') + '' + esc(ev.detail || '—') + '
'; + } 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 += '' + + '' + + ''; + }); + h += '
远端本端状态寿命
' + esc(c.domain || c.remote || '—') + '' + esc(c.local || '—') + '' + esc(c.state || '—') + '' + fmtDur(c.age) + '
'; + } 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). +