From 1a69712994644b71bff7801b0de5f3630f0e49b6 Mon Sep 17 00:00:00 2001 From: jbergner Date: Sat, 22 Aug 2026 18:47:14 +0200 Subject: [PATCH] Bugfix --- CHANGELOG.md | 6 ++++++ README.md | 9 +++++++-- internal/agent/agent.go | 2 +- internal/master/master.go | 2 +- internal/master/ui.go | 6 +++--- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a3b32..d4b4241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.3 + +- Fix: Master-WebUI definiert und aktualisiert `agentCache`, bevor die Farm-Mitglieder berechnet werden. +- Behebt `ReferenceError: agentCache is not defined` in der Farm-/Broker-Ansicht. +- Die Farm-Agent-Anzahl verwendet weiterhin dieselbe Membership-Logik wie der Broker (`farm.agent_ids`, `agent.farm_ids`, `required_tags`). + ## 0.3.2 - Fixed Farm member count in the Master WebUI. The table now uses the same membership rules as the broker: explicit `farm.agent_ids`, agent-side `agent.farm_ids`, and `required_tags`. diff --git a/README.md b/README.md index 3e1ce76..37be3a2 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,15 @@ SessionGuard is a Go-based **RDS control plane** for Windows Remote Desktop Session Hosts. It is designed to complement Apache Guacamole: Guacamole remains the HTML5/RDP gateway, while SessionGuard provides Citrix-like broker, Director, policy, profile-lifecycle and operations functions. -**Current development version: 0.3.1 (Broker & Director production candidate)** +**Current development version: 0.3.3 (Broker & Director production candidate)** > SessionGuard is not an ICA/HDX implementation and does not replace the Windows RDS runtime. It deliberately reuses standard RDP/WTS, Guacamole and PocketID/OIDC. +## 0.3.3 Farm UI regression fix + +The master UI now maintains an explicit `agentCache` from the dashboard response before rendering farm membership. This fixes `ReferenceError: agentCache is not defined` introduced by the v0.3.2 farm-count display correction. + + ## 0.3.1 UI stability fix - Live dashboard refresh no longer recreates editable Master forms. @@ -283,4 +288,4 @@ The current source tree contains unit tests for configuration, templates, profil ## Production-candidate status -The design intentionally fails closed around destructive profile operations and broker farm boundaries. Nevertheless, v0.3.2 should be introduced as a canary before broad production rollout. In particular, validate native WTS behavior, SMB failure/recovery, PostgreSQL backup/restore, Guacamole extension loading and your exact PocketID group claims in your environment. +The design intentionally fails closed around destructive profile operations and broker farm boundaries. Nevertheless, v0.3.3 should be introduced as a canary before broad production rollout. In particular, validate native WTS behavior, SMB failure/recovery, PostgreSQL backup/restore, Guacamole extension loading and your exact PocketID group claims in your environment. diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 28130ce..bc410b6 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -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 diff --git a/internal/master/master.go b/internal/master/master.go index b0f279e..8193d2c 100644 --- a/internal/master/master.go +++ b/internal/master/master.go @@ -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 diff --git a/internal/master/ui.go b/internal/master/ui.go index 7c5424a..ba3422d 100644 --- a/internal/master/ui.go +++ b/internal/master/ui.go @@ -2,12 +2,12 @@ package master const masterHTML = `SessionGuard Master
SessionGuard
Master Console · v0.3.2 · Broker & Director
Server
Online
Aktiv
Getrennt
Profil-Jobs
Cleanup
Farms
Alerts

Terminalserver

Server auswählen

Links einen Agent auswählen.

Farms & Broker

Published Resources

Session-Historie

Policy-Historie & Rollback

Alerts

Audit-Log

` +
SessionGuard
Master Console · v0.3.3 · Broker & Director
Server
Online
Aktiv
Getrennt
Profil-Jobs
Cleanup
Farms
Alerts

Terminalserver

Server auswählen

Links einen Agent auswählen.

Farms & Broker

Published Resources

Session-Historie

Policy-Historie & Rollback

Alerts

Audit-Log

` 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&&i1?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&&i1?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=''+a.map(x=>'').join('')+'
StatusServerModeHealthSitzungenJobs
'+(x.online?'Online':'Offline')+''+esc(x.name)+'
'+esc(x.snapshot.agent_version||'–')+'
'+esc(x.maintenance_mode||'online')+''+esc((x.snapshot.health||{}).score??'–')+''+x.active_sessions+' aktiv / '+x.total_sessions+''+((x.snapshot.profile_jobs||[]).length)+' P / '+((x.snapshot.pending_cleanup||[]).length)+' C
';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='
Policy-Historie erfordert Policy-Admin/Admin-Rolle.
'}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='
Audit-Log erfordert Auditor/Admin-Rolle.
'}}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='
Policy-Historie erfordert Policy-Admin/Admin-Rolle.
'}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='
Audit-Log erfordert Auditor/Admin-Rolle.
'}}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?''+rows.map(x=>'').join('')+'
ZeitZielRevisionAkteur
'+when(x.created_at)+''+esc(x.target)+''+esc(x.revision)+''+esc(x.actor||'–')+'
':'
Noch keine Policy-Versionen.
'} function renderAudit(rows){rows=(rows||[]).slice().reverse().slice(0,250);$('audit').innerHTML=rows.length?''+rows.map(x=>'').join('')+'
ZeitAkteurAktionZielErgebnis
'+esc(when(x.time))+''+esc(x.actor)+''+esc(x.action)+''+esc(x.target||'–')+''+esc(x.result)+(x.details?'
'+esc(x.details)+'':'')+'
':'
Noch keine Audit-Einträge.
'}