Bugfix
This commit is contained in:
@@ -25,7 +25,7 @@ import (
|
||||
"github.com/example/sessionguard/internal/windowsx"
|
||||
)
|
||||
|
||||
const Version = "0.3.1"
|
||||
const Version = "0.3.3"
|
||||
|
||||
type App struct {
|
||||
cfg config.Agent
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/example/sessionguard/internal/model"
|
||||
)
|
||||
|
||||
const Version = "0.3.1"
|
||||
const Version = "0.3.3"
|
||||
|
||||
type App struct {
|
||||
cfg config.Master
|
||||
|
||||
@@ -2,12 +2,12 @@ package master
|
||||
|
||||
const masterHTML = `<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SessionGuard Master</title><style>
|
||||
:root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;color-scheme:dark;background:#0b1020;color:#e8edf7}*{box-sizing:border-box}body{margin:0;background:linear-gradient(135deg,#0b1020,#111a31);min-height:100vh}.wrap{max-width:1600px;margin:auto;padding:28px}.top{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.brand{display:flex;gap:11px;align-items:center}.logo{width:42px;height:42px;border-radius:12px;background:#5b8cff;display:grid;place-items:center;font-weight:900}.muted{color:#95a3be}.grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:12px}.card,.panel{background:rgba(18,27,49,.88);border:1px solid #263654;border-radius:16px}.card{padding:16px}.value{font-size:24px;font-weight:800;margin-top:5px}.split{display:grid;grid-template-columns:minmax(350px,.9fr) minmax(650px,1.6fr);gap:16px}.panel{margin-top:16px;overflow:hidden}.panel h2{font-size:16px;margin:0;padding:15px 18px;border-bottom:1px solid #263654}.table{width:100%;border-collapse:collapse}.table th,.table td{padding:10px 12px;border-bottom:1px solid #22314e;text-align:left;font-size:13px;vertical-align:top}.table th{color:#94a5c4}.row{cursor:pointer}.row:hover,.row.selected{background:#182642}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#5ee08b;margin-right:7px}.dot.off{background:#65728a}.form{padding:16px;display:grid;gap:13px}.cols{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.two{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}label{display:grid;gap:5px;font-size:13px;color:#c8d4e7}input,textarea,select{width:100%;background:#0d1629;color:#e8edf7;border:1px solid #314463;border-radius:9px;padding:9px}textarea{min-height:88px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.check{display:flex;gap:8px;align-items:center}.check input{width:auto}.check label{display:block}button{background:#5b8cff;color:white;border:0;border-radius:9px;padding:8px 11px;font-weight:650;cursor:pointer}button.secondary{background:#263654}button.danger{background:#743b46}.actions{display:flex;gap:5px;flex-wrap:wrap}.empty{padding:20px;color:#8190aa}.section-title{font-weight:800;padding:12px 16px;border-top:1px solid #263654;border-bottom:1px solid #263654}.content{max-height:320px;overflow:auto}.toolbar{display:flex;justify-content:space-between;align-items:center;gap:10px}.list{display:grid;gap:10px}.item{border:1px solid #2b3d5c;background:#0f192c;border-radius:12px;padding:12px;display:grid;gap:9px}.item-head{display:flex;justify-content:space-between;align-items:center}.note{padding:10px;border:1px solid #334866;background:#101b31;border-radius:10px;color:#9fb0ca;font-size:12px}.good{color:#6fe39a}.bad{color:#ff8d8d}.warning,.event-warning,.event-dry-run{color:#ffd166}.event-error{color:#ffaaaa}.toast{position:fixed;right:22px;bottom:22px;background:#1f3155;border:1px solid #3d5785;padding:11px 14px;border-radius:10px;display:none;z-index:10}@media(max-width:1100px){.grid{grid-template-columns:repeat(3,1fr)}.split{grid-template-columns:1fr}.cols,.two{grid-template-columns:1fr}}@media(max-width:650px){.grid{grid-template-columns:repeat(2,1fr)}}
|
||||
</style></head><body><div class="wrap"><div class="top"><div class="brand"><div class="logo">SG</div><div><strong>SessionGuard</strong><div class="muted">Master Console · v0.3.2 · Broker & Director</div></div></div><form action="/logout" method="post"><button class="secondary">Abmelden</button></form></div><div class="grid"><div class="card"><div class="muted">Server</div><div class="value" id="mServers">–</div></div><div class="card"><div class="muted">Online</div><div class="value" id="mOnline">–</div></div><div class="card"><div class="muted">Aktiv</div><div class="value" id="mActive">–</div></div><div class="card"><div class="muted">Getrennt</div><div class="value" id="mDisc">–</div></div><div class="card"><div class="muted">Profil-Jobs</div><div class="value" id="mProfile">–</div></div><div class="card"><div class="muted">Cleanup</div><div class="value" id="mCleanup">–</div></div><div class="card"><div class="muted">Farms</div><div class="value" id="mFarms">–</div></div><div class="card"><div class="muted">Alerts</div><div class="value" id="mAlerts">–</div></div></div><div class="split"><section class="panel"><h2>Terminalserver</h2><div id="agents"></div></section><section class="panel"><h2 id="detailTitle">Server auswählen</h2><div id="detail" class="empty">Links einen Agent auswählen.</div></section></div><section class="panel"><h2>Farms & Broker</h2><div id="farms"></div></section><section class="panel"><h2>Published Resources</h2><div id="resources"></div></section><section class="panel"><h2>Session-Historie</h2><div id="history" class="content"></div></section><section class="panel"><h2>Policy-Historie & Rollback</h2><div id="policyHistory" class="content"></div></section><section class="panel"><h2>Alerts</h2><div id="alerts" class="content"></div></section><section class="panel"><h2>Audit-Log</h2><div id="audit" class="content"></div></section></div><div class="toast" id="toast"></div><script src="/app.js?v=0.3.2"></script></body></html>`
|
||||
</style></head><body><div class="wrap"><div class="top"><div class="brand"><div class="logo">SG</div><div><strong>SessionGuard</strong><div class="muted">Master Console · v0.3.3 · Broker & Director</div></div></div><form action="/logout" method="post"><button class="secondary">Abmelden</button></form></div><div class="grid"><div class="card"><div class="muted">Server</div><div class="value" id="mServers">–</div></div><div class="card"><div class="muted">Online</div><div class="value" id="mOnline">–</div></div><div class="card"><div class="muted">Aktiv</div><div class="value" id="mActive">–</div></div><div class="card"><div class="muted">Getrennt</div><div class="value" id="mDisc">–</div></div><div class="card"><div class="muted">Profil-Jobs</div><div class="value" id="mProfile">–</div></div><div class="card"><div class="muted">Cleanup</div><div class="value" id="mCleanup">–</div></div><div class="card"><div class="muted">Farms</div><div class="value" id="mFarms">–</div></div><div class="card"><div class="muted">Alerts</div><div class="value" id="mAlerts">–</div></div></div><div class="split"><section class="panel"><h2>Terminalserver</h2><div id="agents"></div></section><section class="panel"><h2 id="detailTitle">Server auswählen</h2><div id="detail" class="empty">Links einen Agent auswählen.</div></section></div><section class="panel"><h2>Farms & Broker</h2><div id="farms"></div></section><section class="panel"><h2>Published Resources</h2><div id="resources"></div></section><section class="panel"><h2>Session-Historie</h2><div id="history" class="content"></div></section><section class="panel"><h2>Policy-Historie & Rollback</h2><div id="policyHistory" class="content"></div></section><section class="panel"><h2>Alerts</h2><div id="alerts" class="content"></div></section><section class="panel"><h2>Audit-Log</h2><div id="audit" class="content"></div></section></div><div class="toast" id="toast"></div><script src="/app.js?v=0.3.3"></script></body></html>`
|
||||
|
||||
const masterJS = `
|
||||
let selected=null,current=null,editorAgent=null,policyTemplates=[],profileFolders=[],farmCache=[],resourceCache=[],brokerLeases=[],policyHistory=[],me=null;const $=id=>document.getElementById(id);const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));function when(v){if(!v)return'–';let d=new Date(v);return Number.isNaN(d.getTime())||d.getFullYear()<2000?'–':d.toLocaleString('de-DE')}function bytes(n){if(!n)return'–';let u=['B','KB','MB','GB','TB'],i=0;while(n>=1024&&i<u.length-1){n/=1024;i++}return n.toFixed(i>1?1:0)+' '+u[i]}function toast(t){let e=$('toast');e.textContent=t;e.style.display='block';setTimeout(()=>e.style.display='none',3000)}async function api(u,o){let r=await fetch(u,o);if(r.status===401){location='/login';return}let j=await r.json().catch(()=>({}));if(!r.ok)throw new Error(j.error||r.statusText);return j}function lines(id){return $(id).value.split('\n').map(x=>x.trim()).filter(Boolean)}
|
||||
let selected=null,current=null,editorAgent=null,policyTemplates=[],profileFolders=[],agentCache=[],farmCache=[],resourceCache=[],brokerLeases=[],policyHistory=[],me=null;const $=id=>document.getElementById(id);const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));function when(v){if(!v)return'–';let d=new Date(v);return Number.isNaN(d.getTime())||d.getFullYear()<2000?'–':d.toLocaleString('de-DE')}function bytes(n){if(!n)return'–';let u=['B','KB','MB','GB','TB'],i=0;while(n>=1024&&i<u.length-1){n/=1024;i++}return n.toFixed(i>1?1:0)+' '+u[i]}function toast(t){let e=$('toast');e.textContent=t;e.style.display='block';setTimeout(()=>e.style.display='none',3000)}async function api(u,o){let r=await fetch(u,o);if(r.status===401){location='/login';return}let j=await r.json().catch(()=>({}));if(!r.ok)throw new Error(j.error||r.statusText);return j}function lines(id){return $(id).value.split('\n').map(x=>x.trim()).filter(Boolean)}
|
||||
function renderAgentTable(a){$('agents').innerHTML='<table class="table"><thead><tr><th>Status</th><th>Server</th><th>Mode</th><th>Health</th><th>Sitzungen</th><th>Jobs</th></tr></thead><tbody>'+a.map(x=>'<tr class="row '+(x.id===selected?'selected':'')+'" data-id="'+esc(x.id)+'"><td><i class="dot '+(x.online?'':'off')+'"></i>'+(x.online?'Online':'Offline')+'</td><td><strong>'+esc(x.name)+'</strong><br><span class="muted">'+esc(x.snapshot.agent_version||'–')+'</span></td><td>'+esc(x.maintenance_mode||'online')+'</td><td>'+esc((x.snapshot.health||{}).score??'–')+'</td><td>'+x.active_sessions+' aktiv / '+x.total_sessions+'</td><td>'+((x.snapshot.profile_jobs||[]).length)+' P / '+((x.snapshot.pending_cleanup||[]).length)+' C</td></tr>').join('')+'</tbody></table>';document.querySelectorAll('.row').forEach(r=>r.onclick=()=>selectAgent(r.dataset.id,false,false))}
|
||||
async function refresh(){try{let d=await api('/api/v1/dashboard'),a=d.agents||[];$('mServers').textContent=a.length;$('mOnline').textContent=a.filter(x=>x.online).length;$('mActive').textContent=a.reduce((n,x)=>n+x.active_sessions,0);$('mDisc').textContent=a.reduce((n,x)=>n+(x.disconnected_sessions||0),0);$('mProfile').textContent=a.reduce((n,x)=>n+(x.snapshot.profile_jobs||[]).length,0);$('mCleanup').textContent=a.reduce((n,x)=>n+(x.snapshot.pending_cleanup||[]).length,0);$('mFarms').textContent=d.farms||0;$('mAlerts').textContent=d.active_alerts||0;renderAgentTable(a);if(selected)await selectAgent(selected,true,true);let fr=await api('/api/v1/farms');farmCache=fr.farms||[];let rr=await api('/api/v1/resources');resourceCache=rr.resources||[];let lr=await api('/api/v1/leases');brokerLeases=lr.leases||[];renderFarms();renderResources();let hi=await api('/api/v1/history?limit=500');renderHistory(hi.history||[]);try{let ph=await api('/api/v1/policy/history');policyHistory=ph.history||[];renderPolicyHistory()}catch(e){$('policyHistory').innerHTML='<div class="empty">Policy-Historie erfordert Policy-Admin/Admin-Rolle.</div>'}let al=await api('/api/v1/alerts');renderAlerts(al.alerts||[]);if(!me)me=await api('/api/v1/me');try{let au=await api('/api/v1/audit');renderAudit(au.audit||[])}catch(e){$('audit').innerHTML='<div class="empty">Audit-Log erfordert Auditor/Admin-Rolle.</div>'}}catch(e){toast(e.message)}}
|
||||
async function refresh(){try{let d=await api('/api/v1/dashboard'),a=d.agents||[];agentCache=a;$('mServers').textContent=a.length;$('mOnline').textContent=a.filter(x=>x.online).length;$('mActive').textContent=a.reduce((n,x)=>n+x.active_sessions,0);$('mDisc').textContent=a.reduce((n,x)=>n+(x.disconnected_sessions||0),0);$('mProfile').textContent=a.reduce((n,x)=>n+(x.snapshot.profile_jobs||[]).length,0);$('mCleanup').textContent=a.reduce((n,x)=>n+(x.snapshot.pending_cleanup||[]).length,0);$('mFarms').textContent=d.farms||0;$('mAlerts').textContent=d.active_alerts||0;renderAgentTable(a);if(selected)await selectAgent(selected,true,true);let fr=await api('/api/v1/farms');farmCache=fr.farms||[];let rr=await api('/api/v1/resources');resourceCache=rr.resources||[];let lr=await api('/api/v1/leases');brokerLeases=lr.leases||[];renderFarms();renderResources();let hi=await api('/api/v1/history?limit=500');renderHistory(hi.history||[]);try{let ph=await api('/api/v1/policy/history');policyHistory=ph.history||[];renderPolicyHistory()}catch(e){$('policyHistory').innerHTML='<div class="empty">Policy-Historie erfordert Policy-Admin/Admin-Rolle.</div>'}let al=await api('/api/v1/alerts');renderAlerts(al.alerts||[]);if(!me)me=await api('/api/v1/me');try{let au=await api('/api/v1/audit');renderAudit(au.audit||[])}catch(e){$('audit').innerHTML='<div class="empty">Audit-Log erfordert Auditor/Admin-Rolle.</div>'}}catch(e){toast(e.message)}}
|
||||
async function selectAgent(id,quiet,preserve){let same=selected===id;selected=id;try{current=await api('/api/v1/agents/'+encodeURIComponent(id));renderDetail(current,!!preserve&&same)}catch(e){if(!quiet)toast(e.message)}}
|
||||
function renderPolicyHistory(){let rows=(policyHistory||[]).slice().reverse().slice(0,250);$('policyHistory').innerHTML=rows.length?'<table class="table"><thead><tr><th>Zeit</th><th>Ziel</th><th>Revision</th><th>Akteur</th><th></th></tr></thead><tbody>'+rows.map(x=>'<tr><td>'+when(x.created_at)+'</td><td>'+esc(x.target)+'</td><td><code>'+esc(x.revision)+'</code></td><td>'+esc(x.actor||'–')+'</td><td><button class="secondary" data-global-action="rollback-policy" data-target="'+esc(x.target)+'" data-revision="'+esc(x.revision)+'">Rollback</button></td></tr>').join('')+'</tbody></table>':'<div class="empty">Noch keine Policy-Versionen.</div>'}
|
||||
function renderAudit(rows){rows=(rows||[]).slice().reverse().slice(0,250);$('audit').innerHTML=rows.length?'<table class="table"><thead><tr><th>Zeit</th><th>Akteur</th><th>Aktion</th><th>Ziel</th><th>Ergebnis</th></tr></thead><tbody>'+rows.map(x=>'<tr><td>'+esc(when(x.time))+'</td><td>'+esc(x.actor)+'</td><td>'+esc(x.action)+'</td><td>'+esc(x.target||'–')+'</td><td class="'+(x.result==='error'?'bad':x.result==='success'?'good':'')+'">'+esc(x.result)+(x.details?'<br><span class="muted">'+esc(x.details)+'</span>':'')+'</td></tr>').join('')+'</tbody></table>':'<div class="empty">Noch keine Audit-Einträge.</div>'}
|
||||
|
||||
Reference in New Issue
Block a user