Companion-App: 30 Action-Card-Texte (Titel + Szenario) eingebaut

- USE_CASES.changes von Strings auf {titel, text} umgestellt (6 Services x 5 =
  30 Action Cards, Stil wie das Freiburg-Action-Card-PDF).
- UI zeigt Titel (fett) + Text + "Was passiert an welchen Stellen?";
  Karten-Screen, Run-Token, Tour-Banner und Debrief (MD+JSON) nachgezogen.
- Helfer acard()/cardHtml(); JS-Syntax + Datenform verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
breitenbach76 2026-06-05 17:01:28 +02:00
parent 7f0e847561
commit 668367a241

View file

@ -291,56 +291,56 @@ const USE_CASES = [
{ service:"Zentrale VDI (Virtual-Desktop-Infrastructure)", { service:"Zentrale VDI (Virtual-Desktop-Infrastructure)",
desc:"Bereitstellung von virtuellen Windows-Desktops über das interne Rechenzentrum.", desc:"Bereitstellung von virtuellen Windows-Desktops über das interne Rechenzentrum.",
changes:[ changes:[
"Strategische IT-Richtlinie auf Geheiß des OB, die den Umstieg von einer proprietären VDI-Lösung auf eine Open-Source-Alternative (z. B. OpenStack + Thin-Client) vorschreibt.", {titel:"Open Source von oben!", text:"Der OB gibt die Richtung vor: Die proprietäre VDI-Lösung soll auf eine Open-Source-Alternative (OpenStack + Thin-Client) umgestellt werden."},
"Das Bauamt fordert die Einführung einer neuen Fachsoftware (z. B. ein GIS-Tool), die auf dem virtuellen Desktop zugänglich sein soll.", {titel:"Bauamt will mehr!", text:"Das Bauamt fordert ein neues GIS-Fachverfahren, das künftig direkt auf dem virtuellen Desktop laufen soll."},
"Rebranding des Logos der Stadt Freiburg: Anpassung der Desktop-Hintergrundgrafik erforderlich.", {titel:"Tapetenwechsel", text:"Die Stadt bekommt ein neues Logo — der Desktop-Hintergrund aller virtuellen Arbeitsplätze muss angepasst werden."},
"Quartalsweises Update der VDI-Host-Hypervisor-Firmware im Standard-Change-Katalog verankert.", {titel:"Quartalspflege", text:"Das turnusmäßige Firmware-Update der VDI-Host-Hypervisoren steht an — im Standard-Change-Katalog längst hinterlegt."},
"Stromausfall: Ausfall eines VDI-Host-Clusters, der die sofortige Migration der Sitzungen auf ein Backup-Cluster erfordert." {titel:"Blackout!", text:"Ein Stromausfall reißt ein ganzes VDI-Host-Cluster aus dem Betrieb — die Sitzungen müssen sofort auf ein Backup-Cluster migriert werden."}
]}, ]},
{ service:"Managed VPN-Access Service", { service:"Managed VPN-Access Service",
desc:"Zentral verwalteter VPN-Dienst, der Mitarbeitenden der Fachämter sicheren Remote-Zugriff auf interne Systeme (Intranet, Fachanwendungen, Datenbanken) ermöglicht.", desc:"Zentral verwalteter VPN-Dienst, der Mitarbeitenden der Fachämter sicheren Remote-Zugriff auf interne Systeme (Intranet, Fachanwendungen, Datenbanken) ermöglicht.",
changes:[ changes:[
"EU-weite neue Datenschutz- oder IT-Sicherheitsverordnung, die die gesamte VPN-Architektur neu gestaltet.", {titel:"Brüssel ruft!", text:"Eine neue EU-weite IT-Sicherheitsverordnung zwingt dazu, die gesamte VPN-Architektur neu aufzustellen."},
"HPA fordert die Einführung einer neuen Authentifizierungsmethode (z. B. verpflichtende Nutzung von FIDO2-Security-Keys).", {titel:"Schlüsselpflicht", text:"Das Hauptpersonalamt verlangt eine neue Authentifizierung: FIDO2-Security-Keys werden für alle verpflichtend."},
"HPA möchte eine interne Anwendung (z. B. ein neues Intranet-Portal) in die Split-Tunnel-Liste ergänzen, weil Nutzer nun von zu Hause darauf zugreifen müssen.", {titel:"Heimvorteil", text:"Ein neues Intranet-Portal soll in die Split-Tunnel-Liste, damit Mitarbeitende auch aus dem Homeoffice darauf zugreifen."},
"Regelmäßige (monatliche) Firmware-Updates der VPN-Appliance, bereits im Change-Katalog als Standard-Change definiert.", {titel:"Monatsroutine", text:"Das monatliche Firmware-Update der VPN-Appliance steht an — als Standard-Change bereits freigegeben."},
"Erfolgreicher Phishing-Angriff: Eine VPN-Benutzerzertifikatskette ist kompromittiert sofortige Sperrung und Neu-Ausstellung der Zertifikate erforderlich." {titel:"Gephisht!", text:"Ein erfolgreicher Phishing-Angriff hat eine VPN-Zertifikatskette kompromittiert — sofort sperren und neu ausstellen."}
]}, ]},
{ service:"Online-Bürgerportal für Meldungen & Anträge", { service:"Online-Bürgerportal für Meldungen & Anträge",
desc:"Zentrales Web-Portal, über das Bürger*innen Meldungen (z. B. Straßenreparatur, Lärm) und Anträge (z. B. Baugenehmigung, Personalausweis) digital einreichen und den Status verfolgen.", desc:"Zentrales Web-Portal, über das Bürger*innen Meldungen (z. B. Straßenreparatur, Lärm) und Anträge (z. B. Baugenehmigung, Personalausweis) digital einreichen und den Status verfolgen.",
changes:[ changes:[
"Neues Landesgesetz zur digitalen Bürgerbeteiligung (z. B. verpflichtende Online-Beteiligungs-Tools für größere Baumaßnahmen) → komplette Erweiterung des Portals um Beteiligungs-Module.", {titel:"Mitreden, Pflicht!", text:"Ein neues Landesgesetz schreibt digitale Bürgerbeteiligung vor — das Portal muss um komplette Beteiligungs-Module erweitert werden."},
"Neuer Gemeinderat fordert Einführung eines neuen Meldetyps (z. B. „Klimaschutz-Meldungen“) durch das Umweltamt neue Formulare und Workflows erforderlich.", {titel:"Ratsbeschluss!", text:"Der Gemeinderat will einen neuen Meldetyp „Klimaschutz“ — das Umweltamt braucht dafür eigene Formulare und Workflows."},
"Bürgerservice meldet Rechtschreibfehler in einem statischen Hinweis-Text, der geändert werden muss.", {titel:"Rotstift gefragt", text:"Der Bürgerservice meldet einen Rechtschreibfehler in einem statischen Hinweistext, der korrigiert werden muss."},
"Monatliches Sicherheits-Patch-Update des Web-Servers (Apache/Nginx) bereits im Change-Katalog definiert.", {titel:"Patchday", text:"Das monatliche Sicherheits-Patch des Webservers (Apache/Nginx) steht an — im Change-Katalog definiert."},
"Entdeckung einer kritischen XSS-Lücke in einem Eingabe-Formular, die eine sofortige Hot-Fix-Implementierung nötig macht." {titel:"Lücke im Formular!", text:"In einem Eingabe-Formular wird eine kritische XSS-Schwachstelle entdeckt — ein Hotfix muss sofort raus."}
]}, ]},
{ service:"Zentrales Dokumenten-Management-System (DMS)", { service:"Zentrales Dokumenten-Management-System (DMS)",
desc:"Elektronisches Archiv, das alle ein- und ausgehenden Dokumente (PDF, Scans, E-Mails) zentral speichert, versioniert und revisionssicher archiviert.", desc:"Elektronisches Archiv, das alle ein- und ausgehenden Dokumente (PDF, Scans, E-Mails) zentral speichert, versioniert und revisionssicher archiviert.",
changes:[ changes:[
"EU-DSGVO-Ergänzung (z. B. neue Vorgaben zur Daten-Minimierung) → komplette Neugestaltung des Metadaten-Modells und der Archivierungs-Policies.", {titel:"Datendiät", text:"Eine DSGVO-Ergänzung zur Datenminimierung erzwingt die komplette Neugestaltung von Metadaten-Modell und Archivierungs-Policies."},
"Einführung eines neuen Fachverfahrens (z. B. „Bürgerbeteiligungs-Verfahren“), das eigene Dokumenten-Klassen und Workflows erfordert.", {titel:"Akten-Zuwachs", text:"Ein neues Fachverfahren zieht ein und braucht eigene Dokumentenklassen und Workflows im DMS."},
"Rückmeldung aus dem Finanzamt: Ein Metadaten-Feld (z. B. neues Dropdown-Feld „Kostenstelle“) muss angepasst werden.", {titel:"Dropdown-Wunsch", text:"Das Finanzamt bittet um ein neues Metadaten-Feld „Kostenstelle“ als Auswahlliste."},
"Quartalsweises Update der DMS-Software-Version (Sicherheits- und Funktions-Patches) im Standard-Change-Katalog.", {titel:"Versionspflege", text:"Das quartalsweise Update der DMS-Software (Sicherheits- und Funktions-Patches) steht an."},
"Ransomware-Alarm: Verdächtige Verschlüsselung von Dokumenten sofortige Isolation des Storage-Systems und Wiederherstellung aus letzten Snapshots." {titel:"Ransomware!", text:"Alarm: Dokumente werden verdächtig verschlüsselt — Storage sofort isolieren und aus den letzten Snapshots wiederherstellen."}
]}, ]},
{ service:"Kommunales GIS-Portal (Geoinformations-System)", { service:"Kommunales GIS-Portal (Geoinformations-System)",
desc:"Web-basiertes Karten- und Analyse-Portal, das Fachämtern (Bau, Umwelt, Verkehr) räumliche Daten (Flurstücke, Infrastruktur, Umweltzonen) und bearbeitbare Layer bereitstellt.", desc:"Web-basiertes Karten- und Analyse-Portal, das Fachämtern (Bau, Umwelt, Verkehr) räumliche Daten (Flurstücke, Infrastruktur, Umweltzonen) und bearbeitbare Layer bereitstellt.",
changes:[ changes:[
"Bundesweite Vorgabe zur Nutzung von EU-Standards → komplette Migration des GIS-Stacks zu konformen Services und Datenmodellen.", {titel:"Norm-Zwang", text:"Eine bundesweite Vorgabe zu EU-Standards erzwingt die komplette Migration des GIS-Stacks auf konforme Services und Datenmodelle."},
"Das Umweltamt möchte ein neues Analyse-Modul (z. B. „Klimarisiko-Analyse“) einführen, das neue Daten-Layer und Rechen-Ressourcen erfordert.", {titel:"Klimarisiko im Blick", text:"Das Umweltamt will ein neues Analyse-Modul „Klimarisiko“ — mit neuen Daten-Layern und Rechen-Ressourcen."},
"Rückmeldung aus dem Bauamt: Korrektur einer falschen Beschriftung eines Karten-Layers.", {titel:"Falsch beschriftet", text:"Das Bauamt meldet eine falsche Beschriftung eines Karten-Layers, die korrigiert werden muss."},
"Monatliches Update der GIS-Software (z. B. GeoServer 2.23 → 2.24) im Standard-Change-Katalog definiert.", {titel:"GeoServer-Update", text:"Das monatliche Update der GIS-Software (GeoServer 2.23 → 2.24) steht an — im Standard-Change-Katalog."},
"Entdeckung einer kritischen Schwachstelle an einer Schnittstelle, die unautorisierten Datenzugriff ermöglicht → sofortige Deaktivierung des Dienstes und Patch-Roll-out." {titel:"Schnittstelle offen!", text:"An einer Schnittstelle wird eine kritische Schwachstelle entdeckt, die unautorisierten Datenzugriff erlaubt — Dienst sofort abschalten und patchen."}
]}, ]},
{ service:"Beschaffungs- und Vertrags-System für Fachämter", { service:"Beschaffungs- und Vertrags-System für Fachämter",
desc:"Web-Applikation, über die Fachämter interne Beschaffungen (Material, Dienstleistungen) anlegen, prüfen und Verträge digital verwalten.", desc:"Web-Applikation, über die Fachämter interne Beschaffungen (Material, Dienstleistungen) anlegen, prüfen und Verträge digital verwalten.",
changes:[ changes:[
"Neue EU-Vergaberichtlinie zwingt zur Einführung von e-Invoicing und erweiterten Transparenz-Reports.", {titel:"Vergabe neu!", text:"Eine neue EU-Vergaberichtlinie zwingt zur Einführung von E-Invoicing und erweiterten Transparenz-Reports."},
"Einführung eines neuen Lieferanten-Portals (z. B. für Badenova), das neue API-Schnittstellen und Authentifizierungs-Flows erfordert.", {titel:"Portal für Partner", text:"Ein neues Lieferanten-Portal (z. B. für Badenova) soll andocken — mit neuen API-Schnittstellen und Authentifizierungs-Flows."},
"Rückmeldung aus dem Finanzamt: Anpassung eines Formular-Labels (z. B. „Kostenstelle“ → „Kostenstelle (4-stellig)“).", {titel:"Vierstellig, bitte", text:"Das Finanzamt wünscht eine kleine Anpassung: aus dem Label „Kostenstelle“ wird „Kostenstelle (4-stellig)“."},
"Quartalsweises Sicherheits-Patch-Update des Anwendungs-Servers bereits im Change-Katalog.", {titel:"Patch-Quartal", text:"Das quartalsweise Sicherheits-Patch des Anwendungsservers steht an — bereits im Change-Katalog."},
"Entdeckung einer kritischen Schwachstelle im Vertrags-Upload-Modul, die das Hochladen von Schadcode ermöglicht → sofortige Deaktivierung des Upload-Endpoints und Hot-Fix." {titel:"Upload-Falle!", text:"Im Vertrags-Upload wird eine kritische Lücke entdeckt, über die sich Schadcode hochladen lässt — Endpoint sofort sperren, Hotfix einspielen."}
]} ]}
]; ];
@ -852,6 +852,11 @@ const $ = s => document.querySelector(s);
const cur = () => STATIONEN[S.index]; const cur = () => STATIONEN[S.index];
function pkey(sid, qi){ return sid+"#"+qi; } function pkey(sid, qi){ return sid+"#"+qi; }
function roleLabel(id){ return ROLLEN[id] || id; } function roleLabel(id){ return ROLLEN[id] || id; }
function acard(si, ci){ return USE_CASES[si].changes[ci]; } // {titel, text}
function cardHtml(si, ci){ const c = acard(si, ci);
return `<div style="font-weight:800;font-size:18px;color:var(--ink);margin-bottom:6px">${c.titel}</div>`
+ `<div>${c.text}</div>`
+ `<div style="color:var(--muted);font-style:italic;margin-top:8px">Was passiert an welchen Stellen?</div>`; }
/* ====================== RENDER: SIDEBAR ====================== */ /* ====================== RENDER: SIDEBAR ====================== */
function renderList(){ function renderList(){
@ -906,7 +911,7 @@ function renderCardScreen(){
<label>Service<select id="serviceSel"></select></label> <label>Service<select id="serviceSel"></select></label>
<label>Change-Typ<select id="changeSel"></select></label> <label>Change-Typ<select id="changeSel"></select></label>
</div> </div>
<div class="ctText" id="cardTrigger">${USE_CASES[S.service].changes[S.change]}</div> <div class="ctText" id="cardTrigger">${cardHtml(S.service,S.change)}</div>
<div class="actions"> <div class="actions">
<button class="ghost" id="randomCard">🎲 Zufällig ziehen</button> <button class="ghost" id="randomCard">🎲 Zufällig ziehen</button>
<button class="ghost" id="tourBtn">📘 Geführtes Beispiel</button> <button class="ghost" id="tourBtn">📘 Geführtes Beispiel</button>
@ -918,7 +923,7 @@ function renderCardScreen(){
svc.innerHTML = USE_CASES.map((u,i)=>`<option value="${i}">${u.service}</option>`).join(""); svc.innerHTML = USE_CASES.map((u,i)=>`<option value="${i}">${u.service}</option>`).join("");
ch.innerHTML = CHANGE_TYPES.map((c,i)=>`<option value="${i}">${c}</option>`).join(""); ch.innerHTML = CHANGE_TYPES.map((c,i)=>`<option value="${i}">${c}</option>`).join("");
svc.value=S.service; ch.value=S.change; svc.value=S.service; ch.value=S.change;
const refresh=()=>{ $("#cardTrigger").textContent=USE_CASES[S.service].changes[S.change]; }; const refresh=()=>{ $("#cardTrigger").innerHTML=cardHtml(S.service,S.change); };
svc.onchange=()=>{ S.service=+svc.value; save(); refresh(); }; svc.onchange=()=>{ S.service=+svc.value; save(); refresh(); };
ch.onchange=()=>{ S.change=+ch.value; save(); refresh(); }; ch.onchange=()=>{ S.change=+ch.value; save(); refresh(); };
$("#randomCard").onclick=()=>{ S.service=Math.floor(Math.random()*USE_CASES.length); S.change=Math.floor(Math.random()*CHANGE_TYPES.length); save(); draw(); }; $("#randomCard").onclick=()=>{ S.service=Math.floor(Math.random()*USE_CASES.length); S.change=Math.floor(Math.random()*CHANGE_TYPES.length); save(); draw(); };
@ -943,7 +948,7 @@ function renderTour(){
const last = S.tourIndex === STATIONEN.length-1; const last = S.tourIndex === STATIONEN.length-1;
$("#panel").innerHTML = ` $("#panel").innerHTML = `
<div class="tourBanner">📘 Geführtes Beispiel · <b>${USE_CASES[TOUR.service].service}</b> — ${CHANGE_TYPES[TOUR.change]}<br> <div class="tourBanner">📘 Geführtes Beispiel · <b>${USE_CASES[TOUR.service].service}</b> — ${CHANGE_TYPES[TOUR.change]}<br>
<span style="color:var(--muted)">${USE_CASES[TOUR.service].changes[TOUR.change]}</span></div> <span style="color:var(--muted)"><b>${acard(TOUR.service,TOUR.change).titel}</b> — ${acard(TOUR.service,TOUR.change).text}</span></div>
<div class="tourProg">Station ${S.tourIndex+1} von ${STATIONEN.length}</div> <div class="tourProg">Station ${S.tourIndex+1} von ${STATIONEN.length}</div>
${chip} ${chip}
<div class="stationName">${st.name}</div> <div class="stationName">${st.name}</div>
@ -1046,7 +1051,7 @@ function renderRun(){
<div class="stationId">${st.id}</div> <div class="stationId">${st.id}</div>
<div class="token">Action Card: <b>${USE_CASES[S.service].service}</b> <div class="token">Action Card: <b>${USE_CASES[S.service].service}</b>
<span class="ctChip">${CHANGE_TYPES[S.change]}</span> <span class="ctChip">${CHANGE_TYPES[S.change]}</span>
<div class="ctText">${USE_CASES[S.service].changes[S.change]}</div> <div class="ctText"><b>${acard(S.service,S.change).titel}</b> — ${acard(S.service,S.change).text}</div>
</div> </div>
`; `;
@ -1183,7 +1188,7 @@ function openDebrief(){
let md = `# SLC-Workshop — Debrief\n\n`; let md = `# SLC-Workshop — Debrief\n\n`;
md += `**Service:** ${USE_CASES[S.service].service}\n`; md += `**Service:** ${USE_CASES[S.service].service}\n`;
md += `**Change-Typ:** ${CHANGE_TYPES[S.change]}\n`; md += `**Change-Typ:** ${CHANGE_TYPES[S.change]}\n`;
md += `**Auslöser:** ${USE_CASES[S.service].changes[S.change]}\n`; md += `**Action Card:** „${acard(S.service,S.change).titel}“ — ${acard(S.service,S.change).text}\n`;
md += `**Stationen bearbeitet:** ${doneN}/${STATIONEN.length}\n`; md += `**Stationen bearbeitet:** ${doneN}/${STATIONEN.length}\n`;
md += `**Quiz:** ${correct}/${total} richtig\n\n`; md += `**Quiz:** ${correct}/${total} richtig\n\n`;
md += `## Als unklar markiert\n`; md += `## Als unklar markiert\n`;
@ -1199,7 +1204,8 @@ function openDebrief(){
erzeugt: stamp, erzeugt: stamp,
service: USE_CASES[S.service].service, service: USE_CASES[S.service].service,
changeTyp: CHANGE_TYPES[S.change], changeTyp: CHANGE_TYPES[S.change],
ausloeser: USE_CASES[S.service].changes[S.change], actionCard: acard(S.service,S.change).titel,
ausloeser: acard(S.service,S.change).text,
stationenBearbeitet: doneN, stationenBearbeitet: doneN,
stationenGesamt: STATIONEN.length, stationenGesamt: STATIONEN.length,
quiz: { richtig: correct, gesamt: total }, quiz: { richtig: correct, gesamt: total },