style(web): use minimal custom svg icons for theme switch button
This commit is contained in:
@@ -9,3 +9,10 @@ header strong{font-size:18px}header span{margin-left:12px;color:var(--text-muted
|
|||||||
@media(max-width:680px){header{align-items:flex-start;padding-top:12px;padding-bottom:12px}header span{display:none}.brand-wrap{padding-top:2px}.brand-logo{width:32px;height:32px}.header-actions{flex-direction:column;align-items:stretch;width:112px}.header-actions button{width:100%;padding:8px 9px}.devices{grid-template-columns:1fr}.cardhead{flex-direction:column;align-items:stretch;gap:8px}.cardhead h2{margin-bottom:0}.cardhead button,#device{width:100%;min-width:0}.actions button{flex:1 1 calc(50% - 4px)}}
|
@media(max-width:680px){header{align-items:flex-start;padding-top:12px;padding-bottom:12px}header span{display:none}.brand-wrap{padding-top:2px}.brand-logo{width:32px;height:32px}.header-actions{flex-direction:column;align-items:stretch;width:112px}.header-actions button{width:100%;padding:8px 9px}.devices{grid-template-columns:1fr}.cardhead{flex-direction:column;align-items:stretch;gap:8px}.cardhead h2{margin-bottom:0}.cardhead button,#device{width:100%;min-width:0}.actions button{flex:1 1 calc(50% - 4px)}}
|
||||||
@media(max-width:450px){main{padding:9px}.card{padding:13px;border-radius:12px}.actions{display:grid;grid-template-columns:1fr 1fr}.actions button{width:100%;min-width:0}.login form{padding:22px}.header-actions{width:104px}}
|
@media(max-width:450px){main{padding:9px}.card{padding:13px;border-radius:12px}.actions{display:grid;grid-template-columns:1fr 1fr}.actions button{width:100%;min-width:0}.login form{padding:22px}.header-actions{width:104px}}
|
||||||
@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important}}
|
@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important}}
|
||||||
|
|
||||||
|
/* 主题切换极简矢量图标按钮 */
|
||||||
|
.btn-icon{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;border-radius:9px;background:var(--bg-sub);border:1px solid var(--border-strong);color:var(--text-main);cursor:pointer;transition:background .2s,border-color .2s}
|
||||||
|
.btn-icon:hover{filter:brightness(1.1)}
|
||||||
|
.theme-svg{display:none;width:18px;height:18px}
|
||||||
|
:root[data-theme="dark"] .svg-sun{display:block;color:#facc15}
|
||||||
|
:root[data-theme="light"] .svg-moon{display:block;color:#475569}
|
||||||
|
|||||||
@@ -1 +1,57 @@
|
|||||||
const $=s=>document.querySelector(s);(function(){const b=$("#themeToggle");if(!b)return;function sync(){const t=document.documentElement.dataset.theme||"dark";b.textContent=t==="light"?"深色模式":"浅色模式"}b.onclick=()=>{const t=document.documentElement.dataset.theme==="light"?"dark":"light";document.documentElement.dataset.theme=t;localStorage.setItem("vpc_theme",t);sync()};sync()})();async function api(path,opt={}){let r=await fetch("/panel/api"+path,opt);if(r.status===401){location.href="/panel/login";throw Error("unauthorized")}if(!r.ok)throw Error(await r.text());return r.json()}function esc(v){return String(v??"").replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]))}async function loadDevices(){let ds=await api("/devices");$("#devices").innerHTML=ds.map(d=>'<div class="device '+(d.status==="online"?"":"offline")+'"><b><i class="dot"></i>'+esc(d.name)+'</b><div class="status">'+esc(d.status)+' · '+(d.is_local?"local":"remote")+' · '+esc(d.arch||"")+'/'+esc(d.os||"")+'</div></div>').join("");$("#device").innerHTML=ds.map(d=>'<option>'+esc(d.name)+'</option>').join("")}async function loadAudits(){let a=await api("/audits?limit=100");$("#audits").innerHTML=a.map(x=>'<tr><td>'+new Date(x.timestamp*1000).toLocaleString()+'</td><td>'+esc(x.device)+'</td><td>'+esc(x.action)+'</td><td><code>'+esc(x.command)+'</code></td><td>'+x.exit_code+'</td><td>'+x.duration_ms+'ms</td><td>'+esc(x.caller_ip)+'</td></tr>').join("")}$("#refresh").onclick=loadDevices;$("#auditRefresh").onclick=loadAudits;$("#run").onclick=async()=>{let o=$("#output");o.textContent="执行中…";try{let x=await api("/exec",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device:$("#device").value,command:$("#command").value,timeout:30})});o.textContent=`exit_code: ${x.exit_code}\n\nstdout:\n${x.stdout}\n\nstderr:\n${x.stderr}\nduration: ${x.duration_ms}ms`}catch(e){o.textContent=e.message}};$("#copySchema").onclick=()=>navigator.clipboard.writeText(location.origin+"/openapi.json");$("#copyPrompt").onclick=()=>navigator.clipboard.writeText("你是我的私有 VPS 运维助手。执行操作前确认目标设备;优先使用 list_devices 确认节点状态;高风险操作先向用户确认。");$("#rotateKey").onclick=async()=>{if(!confirm("轮换后旧 API Key 会立即失效,确认继续?"))return;try{let x=await api("/key/rotate",{method:"POST"});$("#newKey").textContent="新 API Key(仅本次显示):\n"+x.api_key}catch(e){$("#newKey").textContent=e.message}};$("#logout").onclick=async()=>{await fetch("/panel/logout",{method:"POST"});location.href="/panel/login"};loadDevices().then(loadAudits).catch(console.error);
|
const $=s=>document.querySelector(s);
|
||||||
|
(function(){
|
||||||
|
const b=$("#themeToggle");
|
||||||
|
if(!b)return;
|
||||||
|
b.onclick=()=>{
|
||||||
|
const current=document.documentElement.dataset.theme||"dark";
|
||||||
|
const next=current==="light"?"dark":"light";
|
||||||
|
document.documentElement.dataset.theme=next;
|
||||||
|
localStorage.setItem("vpc_theme",next);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
async function api(path,opt={}){
|
||||||
|
let r=await fetch("/panel/api"+path,opt);
|
||||||
|
if(r.status===401){location.href="/panel/login";throw Error("unauthorized")}
|
||||||
|
if(!r.ok)throw Error(await r.text());
|
||||||
|
return r.json()
|
||||||
|
}
|
||||||
|
function esc(v){
|
||||||
|
return String(v??"").replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]))
|
||||||
|
}
|
||||||
|
async function loadDevices(){
|
||||||
|
let ds=await api("/devices");
|
||||||
|
$("#devices").innerHTML=ds.map(d=>'<div class="device '+(d.status==="online"?"":"offline")+'"><b><i class="dot"></i>'+esc(d.name)+'</b><div class="status">'+esc(d.status)+' · '+(d.is_local?"local":"remote")+' · '+esc(d.arch||"")+'/'+esc(d.os||"")+'</div></div>').join("");
|
||||||
|
$("#device").innerHTML=ds.map(d=>'<option>'+esc(d.name)+'</option>').join("")
|
||||||
|
}
|
||||||
|
async function loadAudits(){
|
||||||
|
let a=await api("/audits?limit=100");
|
||||||
|
$("#audits").innerHTML=a.map(x=>'<tr><td>'+new Date(x.timestamp*1000).toLocaleString()+'</td><td>'+esc(x.device)+'</td><td>'+esc(x.action)+'</td><td><code>'+esc(x.command)+'</code></td><td>'+x.exit_code+'</td><td>'+x.duration_ms+'ms</td><td>'+esc(x.caller_ip)+'</td></tr>').join("")
|
||||||
|
}
|
||||||
|
$("#refresh").onclick=loadDevices;
|
||||||
|
$("#auditRefresh").onclick=loadAudits;
|
||||||
|
$("#run").onclick=async()=>{
|
||||||
|
let o=$("#output");
|
||||||
|
o.textContent="执行中…";
|
||||||
|
try{
|
||||||
|
let x=await api("/exec",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device:$("#device").value,command:$("#command").value,timeout:30})});
|
||||||
|
o.textContent=`exit_code: ${x.exit_code}\n\nstdout:\n${x.stdout}\n\nstderr:\n${x.stderr}\nduration: ${x.duration_ms}ms`
|
||||||
|
}catch(e){
|
||||||
|
o.textContent=e.message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$("#copySchema").onclick=()=>navigator.clipboard.writeText(location.origin+"/openapi.json");
|
||||||
|
$("#copyPrompt").onclick=()=>navigator.clipboard.writeText("你是我的私有 VPS 运维助手。执行操作前确认目标设备;优先使用 list_devices 确认节点状态;高风险操作先向用户确认。");
|
||||||
|
$("#rotateKey").onclick=async()=>{
|
||||||
|
if(!confirm("轮换后旧 API Key 会立即失效,确认继续?"))return;
|
||||||
|
try{
|
||||||
|
let x=await api("/key/rotate",{method:"POST"});
|
||||||
|
$("#newKey").textContent="新 API Key(仅本次显示):\n"+x.api_key
|
||||||
|
}catch(e){
|
||||||
|
$("#newKey").textContent=e.message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$("#logout").onclick=async()=>{
|
||||||
|
await fetch("/panel/logout",{method:"POST"});
|
||||||
|
location.href="/panel/login"
|
||||||
|
};
|
||||||
|
loadDevices().then(loadAudits).catch(console.error);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><script>(function(){try{var t=localStorage.getItem("vpc_theme");if(t!=="light"&&t!=="dark")t=matchMedia("(prefers-color-scheme:light)").matches?"light":"dark";document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme="dark"}})();</script><title>VPS-Commander</title><link rel="stylesheet" href="/static/app.css"></head><body><header><div class="brand-wrap"><img src="/static/icon.png" alt="logo" class="brand-logo"><div><strong>VPS-Commander</strong><span>Private Infrastructure Console</span></div></div><div class="header-actions"><button id="themeToggle" type="button">浅色模式</button><button id="logout" type="button">退出</button></div></header><main><section class="grid"><article class="card wide"><div class="cardhead"><h2>设备看板</h2><button id="refresh" type="button">刷新</button></div><div id="devices" class="devices"></div></article><article class="card"><h2>Actions 向导</h2><p>当前 Hub 的 OpenAPI Schema:</p><div class="actions"><button id="copySchema" type="button">复制 Schema 地址</button><button id="copyPrompt" type="button">复制 System Prompt</button><button id="rotateKey" type="button">轮换 API Key</button></div><pre id="schemaHint">/openapi.json</pre><pre id="newKey"></pre></article><article class="card wide"><div class="cardhead"><h2>快速控制台</h2><select id="device"></select></div><textarea id="command" placeholder="输入 Shell 命令,例如 uname -a"></textarea><div><button id="run" type="button">执行</button></div><pre id="output"></pre></article><article class="card wide"><div class="cardhead"><h2>审计日志</h2><button id="auditRefresh" type="button">刷新</button></div><div class="tablewrap"><table><thead><tr><th>时间</th><th>设备</th><th>动作</th><th>命令/路径</th><th>退出码</th><th>耗时</th><th>来源</th></tr></thead><tbody id="audits"></tbody></table></div></article></section></main><script src="/static/app.js"></script></body></html>
|
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><script>(function(){try{var t=localStorage.getItem("vpc_theme");if(t!=="light"&&t!=="dark")t=matchMedia("(prefers-color-scheme:light)").matches?"light":"dark";document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme="dark"}})();</script><title>VPS-Commander</title><link rel="stylesheet" href="/static/app.css"></head><body><header><div class="brand-wrap"><img src="/static/icon.png" alt="logo" class="brand-logo"><div><strong>VPS-Commander</strong><span>Private Infrastructure Console</span></div></div><div class="header-actions"><button id="themeToggle" type="button" class="btn-icon" title="切换主题" aria-label="切换主题"><svg class="theme-svg svg-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg><svg class="theme-svg svg-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></button><button id="logout" type="button">退出</button></div></header><main><section class="grid"><article class="card wide"><div class="cardhead"><h2>设备看板</h2><button id="refresh" type="button">刷新</button></div><div id="devices" class="devices"></div></article><article class="card"><h2>Actions 向导</h2><p>当前 Hub 的 OpenAPI Schema:</p><div class="actions"><button id="copySchema" type="button">复制 Schema 地址</button><button id="copyPrompt" type="button">复制 System Prompt</button><button id="rotateKey" type="button">轮换 API Key</button></div><pre id="schemaHint">/openapi.json</pre><pre id="newKey"></pre></article><article class="card wide"><div class="cardhead"><h2>快速控制台</h2><select id="device"></select></div><textarea id="command" placeholder="输入 Shell 命令,例如 uname -a"></textarea><div><button id="run" type="button">执行</button></div><pre id="output"></pre></article><article class="card wide"><div class="cardhead"><h2>审计日志</h2><button id="auditRefresh" type="button">刷新</button></div><div class="tablewrap"><table><thead><tr><th>时间</th><th>设备</th><th>动作</th><th>命令/路径</th><th>退出码</th><th>耗时</th><th>来源</th></tr></thead><tbody id="audits"></tbody></table></div></article></section></main><script src="/static/app.js"></script></body></html>
|
||||||
|
|||||||
@@ -9,3 +9,10 @@ header strong{font-size:18px}header span{margin-left:12px;color:var(--text-muted
|
|||||||
@media(max-width:680px){header{align-items:flex-start;padding-top:12px;padding-bottom:12px}header span{display:none}.brand-wrap{padding-top:2px}.brand-logo{width:32px;height:32px}.header-actions{flex-direction:column;align-items:stretch;width:112px}.header-actions button{width:100%;padding:8px 9px}.devices{grid-template-columns:1fr}.cardhead{flex-direction:column;align-items:stretch;gap:8px}.cardhead h2{margin-bottom:0}.cardhead button,#device{width:100%;min-width:0}.actions button{flex:1 1 calc(50% - 4px)}}
|
@media(max-width:680px){header{align-items:flex-start;padding-top:12px;padding-bottom:12px}header span{display:none}.brand-wrap{padding-top:2px}.brand-logo{width:32px;height:32px}.header-actions{flex-direction:column;align-items:stretch;width:112px}.header-actions button{width:100%;padding:8px 9px}.devices{grid-template-columns:1fr}.cardhead{flex-direction:column;align-items:stretch;gap:8px}.cardhead h2{margin-bottom:0}.cardhead button,#device{width:100%;min-width:0}.actions button{flex:1 1 calc(50% - 4px)}}
|
||||||
@media(max-width:450px){main{padding:9px}.card{padding:13px;border-radius:12px}.actions{display:grid;grid-template-columns:1fr 1fr}.actions button{width:100%;min-width:0}.login form{padding:22px}.header-actions{width:104px}}
|
@media(max-width:450px){main{padding:9px}.card{padding:13px;border-radius:12px}.actions{display:grid;grid-template-columns:1fr 1fr}.actions button{width:100%;min-width:0}.login form{padding:22px}.header-actions{width:104px}}
|
||||||
@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important}}
|
@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important}}
|
||||||
|
|
||||||
|
/* 主题切换极简矢量图标按钮 */
|
||||||
|
.btn-icon{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;border-radius:9px;background:var(--bg-sub);border:1px solid var(--border-strong);color:var(--text-main);cursor:pointer;transition:background .2s,border-color .2s}
|
||||||
|
.btn-icon:hover{filter:brightness(1.1)}
|
||||||
|
.theme-svg{display:none;width:18px;height:18px}
|
||||||
|
:root[data-theme="dark"] .svg-sun{display:block;color:#facc15}
|
||||||
|
:root[data-theme="light"] .svg-moon{display:block;color:#475569}
|
||||||
|
|||||||
+57
-1
@@ -1 +1,57 @@
|
|||||||
const $=s=>document.querySelector(s);(function(){const b=$("#themeToggle");if(!b)return;function sync(){const t=document.documentElement.dataset.theme||"dark";b.textContent=t==="light"?"深色模式":"浅色模式"}b.onclick=()=>{const t=document.documentElement.dataset.theme==="light"?"dark":"light";document.documentElement.dataset.theme=t;localStorage.setItem("vpc_theme",t);sync()};sync()})();async function api(path,opt={}){let r=await fetch("/panel/api"+path,opt);if(r.status===401){location.href="/panel/login";throw Error("unauthorized")}if(!r.ok)throw Error(await r.text());return r.json()}function esc(v){return String(v??"").replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]))}async function loadDevices(){let ds=await api("/devices");$("#devices").innerHTML=ds.map(d=>'<div class="device '+(d.status==="online"?"":"offline")+'"><b><i class="dot"></i>'+esc(d.name)+'</b><div class="status">'+esc(d.status)+' · '+(d.is_local?"local":"remote")+' · '+esc(d.arch||"")+'/'+esc(d.os||"")+'</div></div>').join("");$("#device").innerHTML=ds.map(d=>'<option>'+esc(d.name)+'</option>').join("")}async function loadAudits(){let a=await api("/audits?limit=100");$("#audits").innerHTML=a.map(x=>'<tr><td>'+new Date(x.timestamp*1000).toLocaleString()+'</td><td>'+esc(x.device)+'</td><td>'+esc(x.action)+'</td><td><code>'+esc(x.command)+'</code></td><td>'+x.exit_code+'</td><td>'+x.duration_ms+'ms</td><td>'+esc(x.caller_ip)+'</td></tr>').join("")}$("#refresh").onclick=loadDevices;$("#auditRefresh").onclick=loadAudits;$("#run").onclick=async()=>{let o=$("#output");o.textContent="执行中…";try{let x=await api("/exec",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device:$("#device").value,command:$("#command").value,timeout:30})});o.textContent=`exit_code: ${x.exit_code}\n\nstdout:\n${x.stdout}\n\nstderr:\n${x.stderr}\nduration: ${x.duration_ms}ms`}catch(e){o.textContent=e.message}};$("#copySchema").onclick=()=>navigator.clipboard.writeText(location.origin+"/openapi.json");$("#copyPrompt").onclick=()=>navigator.clipboard.writeText("你是我的私有 VPS 运维助手。执行操作前确认目标设备;优先使用 list_devices 确认节点状态;高风险操作先向用户确认。");$("#rotateKey").onclick=async()=>{if(!confirm("轮换后旧 API Key 会立即失效,确认继续?"))return;try{let x=await api("/key/rotate",{method:"POST"});$("#newKey").textContent="新 API Key(仅本次显示):\n"+x.api_key}catch(e){$("#newKey").textContent=e.message}};$("#logout").onclick=async()=>{await fetch("/panel/logout",{method:"POST"});location.href="/panel/login"};loadDevices().then(loadAudits).catch(console.error);
|
const $=s=>document.querySelector(s);
|
||||||
|
(function(){
|
||||||
|
const b=$("#themeToggle");
|
||||||
|
if(!b)return;
|
||||||
|
b.onclick=()=>{
|
||||||
|
const current=document.documentElement.dataset.theme||"dark";
|
||||||
|
const next=current==="light"?"dark":"light";
|
||||||
|
document.documentElement.dataset.theme=next;
|
||||||
|
localStorage.setItem("vpc_theme",next);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
async function api(path,opt={}){
|
||||||
|
let r=await fetch("/panel/api"+path,opt);
|
||||||
|
if(r.status===401){location.href="/panel/login";throw Error("unauthorized")}
|
||||||
|
if(!r.ok)throw Error(await r.text());
|
||||||
|
return r.json()
|
||||||
|
}
|
||||||
|
function esc(v){
|
||||||
|
return String(v??"").replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]))
|
||||||
|
}
|
||||||
|
async function loadDevices(){
|
||||||
|
let ds=await api("/devices");
|
||||||
|
$("#devices").innerHTML=ds.map(d=>'<div class="device '+(d.status==="online"?"":"offline")+'"><b><i class="dot"></i>'+esc(d.name)+'</b><div class="status">'+esc(d.status)+' · '+(d.is_local?"local":"remote")+' · '+esc(d.arch||"")+'/'+esc(d.os||"")+'</div></div>').join("");
|
||||||
|
$("#device").innerHTML=ds.map(d=>'<option>'+esc(d.name)+'</option>').join("")
|
||||||
|
}
|
||||||
|
async function loadAudits(){
|
||||||
|
let a=await api("/audits?limit=100");
|
||||||
|
$("#audits").innerHTML=a.map(x=>'<tr><td>'+new Date(x.timestamp*1000).toLocaleString()+'</td><td>'+esc(x.device)+'</td><td>'+esc(x.action)+'</td><td><code>'+esc(x.command)+'</code></td><td>'+x.exit_code+'</td><td>'+x.duration_ms+'ms</td><td>'+esc(x.caller_ip)+'</td></tr>').join("")
|
||||||
|
}
|
||||||
|
$("#refresh").onclick=loadDevices;
|
||||||
|
$("#auditRefresh").onclick=loadAudits;
|
||||||
|
$("#run").onclick=async()=>{
|
||||||
|
let o=$("#output");
|
||||||
|
o.textContent="执行中…";
|
||||||
|
try{
|
||||||
|
let x=await api("/exec",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({device:$("#device").value,command:$("#command").value,timeout:30})});
|
||||||
|
o.textContent=`exit_code: ${x.exit_code}\n\nstdout:\n${x.stdout}\n\nstderr:\n${x.stderr}\nduration: ${x.duration_ms}ms`
|
||||||
|
}catch(e){
|
||||||
|
o.textContent=e.message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$("#copySchema").onclick=()=>navigator.clipboard.writeText(location.origin+"/openapi.json");
|
||||||
|
$("#copyPrompt").onclick=()=>navigator.clipboard.writeText("你是我的私有 VPS 运维助手。执行操作前确认目标设备;优先使用 list_devices 确认节点状态;高风险操作先向用户确认。");
|
||||||
|
$("#rotateKey").onclick=async()=>{
|
||||||
|
if(!confirm("轮换后旧 API Key 会立即失效,确认继续?"))return;
|
||||||
|
try{
|
||||||
|
let x=await api("/key/rotate",{method:"POST"});
|
||||||
|
$("#newKey").textContent="新 API Key(仅本次显示):\n"+x.api_key
|
||||||
|
}catch(e){
|
||||||
|
$("#newKey").textContent=e.message
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$("#logout").onclick=async()=>{
|
||||||
|
await fetch("/panel/logout",{method:"POST"});
|
||||||
|
location.href="/panel/login"
|
||||||
|
};
|
||||||
|
loadDevices().then(loadAudits).catch(console.error);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><script>(function(){try{var t=localStorage.getItem("vpc_theme");if(t!=="light"&&t!=="dark")t=matchMedia("(prefers-color-scheme:light)").matches?"light":"dark";document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme="dark"}})();</script><title>VPS-Commander</title><link rel="stylesheet" href="/static/app.css"></head><body><header><div class="brand-wrap"><img src="/static/icon.png" alt="logo" class="brand-logo"><div><strong>VPS-Commander</strong><span>Private Infrastructure Console</span></div></div><div class="header-actions"><button id="themeToggle" type="button">浅色模式</button><button id="logout" type="button">退出</button></div></header><main><section class="grid"><article class="card wide"><div class="cardhead"><h2>设备看板</h2><button id="refresh" type="button">刷新</button></div><div id="devices" class="devices"></div></article><article class="card"><h2>Actions 向导</h2><p>当前 Hub 的 OpenAPI Schema:</p><div class="actions"><button id="copySchema" type="button">复制 Schema 地址</button><button id="copyPrompt" type="button">复制 System Prompt</button><button id="rotateKey" type="button">轮换 API Key</button></div><pre id="schemaHint">/openapi.json</pre><pre id="newKey"></pre></article><article class="card wide"><div class="cardhead"><h2>快速控制台</h2><select id="device"></select></div><textarea id="command" placeholder="输入 Shell 命令,例如 uname -a"></textarea><div><button id="run" type="button">执行</button></div><pre id="output"></pre></article><article class="card wide"><div class="cardhead"><h2>审计日志</h2><button id="auditRefresh" type="button">刷新</button></div><div class="tablewrap"><table><thead><tr><th>时间</th><th>设备</th><th>动作</th><th>命令/路径</th><th>退出码</th><th>耗时</th><th>来源</th></tr></thead><tbody id="audits"></tbody></table></div></article></section></main><script src="/static/app.js"></script></body></html>
|
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><script>(function(){try{var t=localStorage.getItem("vpc_theme");if(t!=="light"&&t!=="dark")t=matchMedia("(prefers-color-scheme:light)").matches?"light":"dark";document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme="dark"}})();</script><title>VPS-Commander</title><link rel="stylesheet" href="/static/app.css"></head><body><header><div class="brand-wrap"><img src="/static/icon.png" alt="logo" class="brand-logo"><div><strong>VPS-Commander</strong><span>Private Infrastructure Console</span></div></div><div class="header-actions"><button id="themeToggle" type="button" class="btn-icon" title="切换主题" aria-label="切换主题"><svg class="theme-svg svg-sun" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg><svg class="theme-svg svg-moon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></button><button id="logout" type="button">退出</button></div></header><main><section class="grid"><article class="card wide"><div class="cardhead"><h2>设备看板</h2><button id="refresh" type="button">刷新</button></div><div id="devices" class="devices"></div></article><article class="card"><h2>Actions 向导</h2><p>当前 Hub 的 OpenAPI Schema:</p><div class="actions"><button id="copySchema" type="button">复制 Schema 地址</button><button id="copyPrompt" type="button">复制 System Prompt</button><button id="rotateKey" type="button">轮换 API Key</button></div><pre id="schemaHint">/openapi.json</pre><pre id="newKey"></pre></article><article class="card wide"><div class="cardhead"><h2>快速控制台</h2><select id="device"></select></div><textarea id="command" placeholder="输入 Shell 命令,例如 uname -a"></textarea><div><button id="run" type="button">执行</button></div><pre id="output"></pre></article><article class="card wide"><div class="cardhead"><h2>审计日志</h2><button id="auditRefresh" type="button">刷新</button></div><div class="tablewrap"><table><thead><tr><th>时间</th><th>设备</th><th>动作</th><th>命令/路径</th><th>退出码</th><th>耗时</th><th>来源</th></tr></thead><tbody id="audits"></tbody></table></div></article></section></main><script src="/static/app.js"></script></body></html>
|
||||||
|
|||||||
Reference in New Issue
Block a user