Initial commit: SenSu Alpha 0.2.0
- 13-service async plugin framework - Textual TUI with CLI fallback - Plugin hot-reload + permission system - Web management panel (aiohttp) - Bridge-based inter-module communication - 10 regression tests Fixes applied: - PBKDF2-SHA256 auth (was plain SHA256) - Auth bypass removed (was allow-all on fail) - Bare excepts replaced with logged errors - CatFramework/DreamSu -> SenSu naming unified - ServiceManager: health checks + startup_order - Env var credentials (SENSU_ADMIN_PASSWORD etc)
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<div class="terminal">
|
||||
<div class="term-header">💻 FRAMEWORK CONSOLE</div>
|
||||
<div class="term-body" id="cmd-box" style="font-size:13px; color:#ccc;">
|
||||
<div style="color:var(--success)">SenSu Console Ready. Type 'help' or 'status'.</div>
|
||||
</div>
|
||||
<div class="term-input-area">
|
||||
<span style="padding:10px; color:var(--accent)">$</span>
|
||||
<input type="text" id="cmd-input" class="term-input" placeholder="输入命令并回车..." autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
window.ConsoleModule = {
|
||||
init: () => {
|
||||
const box = document.getElementById('cmd-box');
|
||||
const input = document.getElementById('cmd-input');
|
||||
input.focus();
|
||||
|
||||
const append = (text, cls='') => {
|
||||
box.innerHTML += `<div style="margin-top:2px;${cls?'color:'+cls:''}">${text}</div>`;
|
||||
box.scrollTop = box.scrollHeight;
|
||||
};
|
||||
|
||||
input.onkeydown = async (e) => {
|
||||
if(e.key === 'Enter' && input.value.trim()) {
|
||||
const cmd = input.value.trim();
|
||||
append(`$ ${cmd}`, 'var(--accent)');
|
||||
input.value = '';
|
||||
try {
|
||||
const res = await fetch('./api/command', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, credentials:'include',
|
||||
body: JSON.stringify({command: cmd})
|
||||
});
|
||||
const d = await res.json();
|
||||
append(d.success ? d.output : `Error: ${d.error}`, d.success ? '#ccc' : 'var(--error)');
|
||||
} catch(err) { append(`Network Error: ${err.message}`, 'var(--error)'); }
|
||||
}
|
||||
};
|
||||
},
|
||||
destroy: () => {}
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
<div class="dash-layout">
|
||||
<!-- 🟢 左侧:动态监控区 -->
|
||||
<div class="dash-main">
|
||||
<div class="dash-grid">
|
||||
<div class="stat-card">
|
||||
<h3>⏳ 运行时间</h3>
|
||||
<div class="stat-value" id="d-uptime">--</div>
|
||||
<div class="stat-sub" id="d-ver-badge">v?.?.?</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>📦 插件状态</h3>
|
||||
<div class="stat-value" id="d-plugins">--</div>
|
||||
<div class="stat-sub">已加载 / 活跃</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>🧠 内存使用</h3>
|
||||
<div class="stat-value" id="d-mem">--%</div>
|
||||
<canvas id="chart-mem" class="mini-chart"></canvas>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>⚡ CPU 负载</h3>
|
||||
<div class="stat-value" id="d-cpu">--%</div>
|
||||
<canvas id="chart-cpu" class="mini-chart"></canvas>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>🌐 网络接收</h3>
|
||||
<div class="stat-value" id="d-net">--</div>
|
||||
<canvas id="chart-net" class="mini-chart"></canvas>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>💾 进程内存</h3>
|
||||
<div class="stat-value" id="d-proc-mem">--</div>
|
||||
<canvas id="chart-proc-mem" class="mini-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 🟢 右侧:高优先级信息栏 -->
|
||||
<aside class="dash-sidebar">
|
||||
<!-- 1. 硬件平台信息 -->
|
||||
<div class="side-card">
|
||||
<h3>💻 硬件平台</h3>
|
||||
<div class="info-row"><span>操作系统</span><span class="info-value" id="info-os">--</span></div>
|
||||
<div class="info-row"><span>硬件架构</span><span class="info-value" id="info-arch">--</span></div>
|
||||
<div class="info-row"><span>核心数 (L/P)</span><span class="info-value" id="info-cores">--</span></div>
|
||||
<div class="info-row"><span>物理内存</span><span class="info-value" id="info-mem-total">--</span></div>
|
||||
<div class="info-row"><span>运行环境</span><span class="info-value" id="info-env">--</span></div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 框架简易信息 -->
|
||||
<div class="side-card">
|
||||
<h3>🐱 框架状态</h3>
|
||||
<div class="info-row"><span>版本号</span><span class="info-value" id="info-fw-ver">--</span></div>
|
||||
<div class="info-row"><span>访问地址</span><span class="info-value" id="info-addr">--</span></div>
|
||||
<div class="info-row"><span>进程 PID</span><span class="info-value" id="info-pid">--</span></div>
|
||||
<div class="info-row"><span>系统负载 (15m)</span><span class="info-value" id="info-load">--</span></div>
|
||||
<div class="info-row" style="border:none; padding-top:8px;">
|
||||
<span>运行状态</span>
|
||||
<span class="info-value" style="color:var(--success)">🟢 Online</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 快捷操作 (占位) -->
|
||||
<div class="side-card">
|
||||
<h3>🛠️ 快捷操作</h3>
|
||||
<button class="btn-sm" style="width:100%; margin-bottom:6px" onclick="window.PluginsModule?.refresh()">🔄 刷新插件列表</button>
|
||||
<button class="btn-sm" style="width:100%; color:var(--error); border-color:var(--error)" onclick="doLogout()">🚪 退出登录</button>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
@@ -0,0 +1,105 @@
|
||||
window.DashboardModule = {
|
||||
charts: {},
|
||||
init: () => {
|
||||
// 1. 初始化图表实例
|
||||
window.DashboardModule.charts = {
|
||||
mem: new MiniChart('chart-mem', '#9ece6a'),
|
||||
cpu: new MiniChart('chart-cpu', '#7aa2f7'),
|
||||
net: new MiniChart('chart-net', '#e0af68'),
|
||||
proc_mem: new MiniChart('chart-proc-mem', '#f7768e')
|
||||
};
|
||||
|
||||
// 2. 获取并填充右侧固定信息 (只获取一次即可,除非重启)
|
||||
fetchSystemStaticInfo();
|
||||
|
||||
// 3. 启动实时数据轮询
|
||||
fetchDash();
|
||||
window._dashInterval = setInterval(fetchDash, 2000); // 2秒刷新
|
||||
},
|
||||
destroy: () => {
|
||||
clearInterval(window._dashInterval);
|
||||
window.DashboardModule.charts = {};
|
||||
}
|
||||
};
|
||||
|
||||
async function fetchSystemStaticInfo() {
|
||||
try {
|
||||
const sys = await fetch('./api/system', {credentials:'include'}).then(r => r.json());
|
||||
|
||||
// 硬件信息
|
||||
if(sys.platform) {
|
||||
document.getElementById('info-os').textContent = sys.platform.system || '--';
|
||||
document.getElementById('info-arch').textContent = sys.platform.machine || '--';
|
||||
document.getElementById('info-env').textContent = sys.platform.env || 'Standard';
|
||||
}
|
||||
if(sys.cpu) {
|
||||
const c = sys.cpu.cores || 0;
|
||||
document.getElementById('info-cores').textContent = `${c} / ${c}`; // Android下通常逻辑核=物理核
|
||||
}
|
||||
if(sys.memory) {
|
||||
document.getElementById('info-mem-total').textContent = sys.memory.total_gb + ' GB';
|
||||
}
|
||||
|
||||
// 框架信息 (部分需结合 API)
|
||||
const host = window.location.hostname + (window.location.port ? ':'+window.location.port : '');
|
||||
document.getElementById('info-addr').textContent = host;
|
||||
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
async function fetchDash() {
|
||||
try {
|
||||
const fw = await fetch('./api/framework', {credentials:'include'}).then(r => r.json());
|
||||
const sys = await fetch('./api/system', {credentials:'include'}).then(r => r.json());
|
||||
|
||||
// --- 左侧动态数据更新 ---
|
||||
if(fw) {
|
||||
document.getElementById('d-uptime').textContent = formatUptime(fw.uptime || 0);
|
||||
document.getElementById('d-plugins').textContent = fw.plugins || 0;
|
||||
if(document.getElementById('d-ver-badge')) document.getElementById('d-ver-badge').textContent = 'v' + (fw.version||'?');
|
||||
}
|
||||
|
||||
if(sys) {
|
||||
// 内存
|
||||
const m = sys.memory?.percent || 0;
|
||||
document.getElementById('d-mem').textContent = m + '%';
|
||||
window.DashboardModule.charts.mem.update(m);
|
||||
|
||||
// CPU (兼容 Android null 情况)
|
||||
let cpuVal = sys.cpu?.percent;
|
||||
if (cpuVal === null || cpuVal === undefined) {
|
||||
const load = sys.cpu?.load_avg?.[0] || 0;
|
||||
const cores = sys.cpu?.cores || 1;
|
||||
cpuVal = Math.min(100, (load / cores) * 100);
|
||||
}
|
||||
document.getElementById('d-cpu').textContent = Math.round(cpuVal) + '%';
|
||||
window.DashboardModule.charts.cpu.update(cpuVal);
|
||||
|
||||
// 网络 (RX 总量)
|
||||
const netRx = sys.network?.rx || 0;
|
||||
document.getElementById('d-net').textContent = netRx + ' MB';
|
||||
window.DashboardModule.charts.net.update(netRx); // 图表显示总流量趋势
|
||||
|
||||
// 进程内存
|
||||
const pm = sys.process?.memory_mb || 0;
|
||||
document.getElementById('d-proc-mem').textContent = pm + ' MB';
|
||||
window.DashboardModule.charts.proc_mem.update(pm);
|
||||
|
||||
// --- 右侧动态数据更新 ---
|
||||
if(sys.process) {
|
||||
document.getElementById('info-pid').textContent = sys.process.pid || '--';
|
||||
}
|
||||
if(sys.cpu?.load_avg) {
|
||||
document.getElementById('info-load').textContent = sys.cpu.load_avg[2].toFixed(2);
|
||||
}
|
||||
}
|
||||
} catch(e) { console.warn("Dashboard fetch error", e); }
|
||||
}
|
||||
|
||||
// 辅助:秒数转时间格式
|
||||
function formatUptime(seconds) {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
return `${h}h ${m}m ${s}s`;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div class="terminal">
|
||||
<div class="term-header">📡 LIVE LOG STREAM (WebSocket)</div>
|
||||
<div class="term-body" id="log-box" style="font-size:13px;"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
window.LogsModule = {
|
||||
ws: null,
|
||||
init: () => {
|
||||
const box = document.getElementById('log-box');
|
||||
const connect = () => {
|
||||
window.LogsModule.ws = new WebSocket(`ws://${location.host}${window.location.pathname.replace(/\/$/,'')}/api/logs/ws`);
|
||||
window.LogsModule.ws.onopen = () => box.innerHTML += `<div style="color:var(--success)">🟢 Connected</div>`;
|
||||
window.LogsModule.ws.onmessage = e => {
|
||||
try {
|
||||
const d = JSON.parse(e.data);
|
||||
if(d.type === 'log') {
|
||||
const cls = d.level === 'ERROR' ? 'log-ERROR' : d.level === 'WARNING' ? 'log-WARNING' : 'log-INFO';
|
||||
const t = d.timestamp ? new Date(d.timestamp*1000).toLocaleTimeString() : '--';
|
||||
box.innerHTML += `<div class="log-entry"><span style="color:#555;margin-right:5px">${t}</span><span class="${cls}">[${d.level}]</span> ${d.message}</div>`;
|
||||
box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
} catch(e){}
|
||||
};
|
||||
window.LogsModule.ws.onclose = setTimeout(connect, 3000);
|
||||
};
|
||||
connect();
|
||||
},
|
||||
destroy: () => window.LogsModule.ws?.close()
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
|
||||
<h2 style="color:var(--accent);">📦 插件管理</h2>
|
||||
<button class="btn-sm" onclick="window.PluginsModule.refresh()">🔄 刷新</button>
|
||||
</div>
|
||||
<div id="plugin-list" class="plugin-list">加载中...</div>
|
||||
@@ -0,0 +1,36 @@
|
||||
window.PluginsModule = {
|
||||
init: () => window.PluginsModule.refresh(),
|
||||
refresh: async () => {
|
||||
const list = document.getElementById('plugin-list');
|
||||
if(!list) return;
|
||||
list.innerHTML = '加载中...';
|
||||
try {
|
||||
const res = await fetch('./api/plugins', {credentials:'include'});
|
||||
const data = await res.json();
|
||||
if(!data.plugins?.length) { list.innerHTML = '<div style="color:var(--text-dim)">暂无已加载插件</div>'; return; }
|
||||
|
||||
list.innerHTML = data.plugins.map(p => `
|
||||
<div class="plugin-card">
|
||||
<div class="plugin-info">
|
||||
<h4>${p.name} <span class="badge ${p.running?'badge-run':'badge-stop'}">${p.running?'RUNNING':'STOPPED'}</span></h4>
|
||||
<p>v${p.version||'1.0.0'} | ${p.enabled?'已启用':'已禁用'}</p>
|
||||
</div>
|
||||
<div class="plugin-act">
|
||||
${p.running
|
||||
? `<button class="btn-sm" onclick="window.PluginsModule.act('${p.name}','disable')">停用</button>`
|
||||
: `<button class="btn-sm" onclick="window.PluginsModule.act('${p.name}','enable')">启用</button>`
|
||||
}
|
||||
<button class="btn-sm" onclick="window.PluginsModule.act('${p.name}','reload')">重载</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch(e) { list.innerHTML = '加载失败'; }
|
||||
},
|
||||
act: async (name, action) => {
|
||||
try {
|
||||
await fetch(`./api/plugins/${name}/${action}`, {method:'POST', credentials:'include'});
|
||||
setTimeout(window.PluginsModule.refresh, 500);
|
||||
} catch(e) { alert('操作失败'); }
|
||||
},
|
||||
destroy: () => {}
|
||||
};
|
||||
Reference in New Issue
Block a user