This commit is contained in:
breitenbach76 2026-06-07 15:57:25 +02:00
parent cda49bbdad
commit ef2c01f69f
2 changed files with 60 additions and 24 deletions

View file

@ -269,6 +269,13 @@
.legend h4{margin:0 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)} .legend h4{margin:0 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
.legend dt{font-weight:700;font-size:13px} .legend dt{font-weight:700;font-size:13px}
.legend dd{margin:2px 0 8px;color:var(--muted);font-size:13px} .legend dd{margin:2px 0 8px;color:var(--muted);font-size:13px}
.lgItem{padding:8px 0;border-top:1px solid var(--line)}
.lgItem:first-of-type{border-top:0;padding-top:2px}
.lgName{font-weight:700;font-size:13px;color:var(--ink)}
.lgIdee{margin:2px 0 4px;font-size:13px;color:var(--ink)}
.lgBed{margin:2px 0 4px;padding-left:18px;color:var(--muted);font-size:12px}
.lgBed li{margin:1px 0}
.lgBsp{font-size:12px;color:var(--muted)}
.hint{font-weight:600;margin:8px 0} .hint{font-weight:600;margin:8px 0}
.hint.bad{color:var(--bad)} .hint.ok{color:var(--ok)} .hint.bad{color:var(--bad)} .hint.ok{color:var(--ok)}
.phaseRow{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin:14px 0} .phaseRow{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;margin:14px 0}
@ -399,12 +406,39 @@ const CHANGE_TYPES = [
"Standard Change", "Standard Change",
"Emergency Change" "Emergency Change"
]; ];
/* Legende-Inhalte, Index parallel zu CHANGE_TYPES (0 Major, 1 Normal, 2 Standard, 3 Emergency).
Quelle: DIGITOM-Definitionen. Emergency wurde nicht mitgeliefert -> bestehende Beschreibung. */
const CHANGE_LEGEND = [ const CHANGE_LEGEND = [
"Strategisch/grundlegend, braucht ein echtes Design — durchläuft den vollen Lebenszyklus ab dem Design. Freigabe in der SOR; reicht deren Ressourcen-/Entscheidungshoheit nicht, wird daraus ein Demand (Routing DPM → Mission Board).", { idee:"Sonderform der Normal Change mit hohem Risiko, hohen Kosten oder breiter Auswirkung — höchste Eskalations- und Genehmigungsstufe.",
"Geplant und dokumentiert, aber nicht strategisch — Einstieg an Gate 1 (Bauen oder Konfigurieren), meist Konfiguration.", bed:["Hohes Risiko und/oder hohe Kosten und/oder weitreichende Auswirkung (viele Nutzer/Services betroffen)",
"Vorab genehmigt und im Katalog hinterlegt — keine Gates, kein Design, direkt im laufenden Betrieb.", "Genehmigung oft durch erweitertes Gremium / Management / Lenkungsebene (nicht nur Standard-CAB)",
"Muss eine Störung sofort beheben — beschleunigt umgesetzt; die formale Freigabe erfolgt nachgelagert." "Vollständige Bewertung, Business Case, ausführliche Planung & Tests, Kommunikationsplan",
"Häufig an Schwellenwerte gebunden (z. B. Kosten über X €, Ausfall kritischer Dienste)"],
bsp:"Rechenzentrumsumzug, Austausch eines Kernsystems, organisationsweite Plattformmigration." },
{ idee:"Der Regelfall für alles, was nicht vorab genehmigt ist und kein Notfall ist. Durchläuft den vollen Bewertungs- und Genehmigungsprozess.",
bed:["RfC (Request for Change) wird erfasst",
"Risiko- und Impact-Bewertung wird durchgeführt",
"Genehmigung durch Change-Manager bzw. CAB (Change Advisory Board) vor Umsetzung",
"Terminplanung (Scheduling), Test, ggf. Rollback-Plan",
"Geringes bis mittleres Risiko (die hochriskanten landen bei „Major“)"],
bsp:"Einführung einer neuen Software-Version, Konfigurationsänderung an einem produktiven System." },
{ idee:"Routine. Vorab genehmigt, weil sie oft vorkommt, das Risiko bekannt und niedrig ist und der Ablauf dokumentiert ist.",
bed:["Es existiert ein vorab freigegebenes Muster/Template für genau diese Änderung",
"Geringes, bekanntes Risiko, Auswirkung vorhersehbar",
"Wiederholbar, klar dokumentierter Ablauf",
"Keine Einzelfall-Genehmigung durch CAB/Change-Manager nötig — die Autorisierung gilt generell"],
bsp:"Standard-Passwort-Reset, Austausch eines defekten Standard-Geräts, Einspielen eines geprüften Routine-Patches." },
{ idee:"Muss eine Störung sofort beheben — beschleunigt umgesetzt; die formale Freigabe erfolgt nachgelagert.",
bed:["Akuter Notfall / drohender oder laufender Ausfall kritischer Dienste",
"Beschleunigtes Verfahren (Emergency-CAB bzw. Notfall-Autorisierung)",
"Umsetzung sofort — Dokumentation und formale Freigabe nachgelagert"],
bsp:"Sofort-Sperrung einer kompromittierten VPN-Zertifikatskette, Notfall-Hotfix einer kritischen Sicherheitslücke." }
]; ];
// Anzeige-Reihenfolge der Change-Arten (Indizes in CHANGE_TYPES): Standard, Emergency, Normal, Major
const CT_ORDER = [2, 3, 1, 0];
// Feste, EINMALIG gemischte Deck-Reihenfolge ([service, change]) — bei jedem Start gleich, nicht gruppiert.
const DECK_ORDER = [[2,1],[0,3],[4,0],[1,2],[5,3],[3,0],[0,1],[2,3],[4,2],[1,0],[5,1],[3,2],
[2,0],[0,2],[4,3],[1,3],[5,0],[3,1],[2,2],[0,0],[4,1],[1,1],[5,2],[3,3]];
const USE_CASES = [ 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.",
@ -1140,20 +1174,16 @@ function renderCardBadge(){
/* ---------- Schritt 1: Action Card ziehen (Raster aller Karten) ---------- */ /* ---------- Schritt 1: Action Card ziehen (Raster aller Karten) ---------- */
function renderDeck(){ function renderDeck(){
let grid = ""; const cards = DECK_ORDER.map(([si,ci])=>{
USE_CASES.forEach((u,si)=>{ const c = USE_CASES[si].changes[ci];
grid += `<div class="deckGroup"><div class="deckSvc">${u.service}</div><div class="deckRow">`; return `<button class="deckCard" data-s="${si}" data-c="${ci}" title="${c.titel}">
u.changes.forEach((c,ci)=>{
grid += `<button class="deckCard" data-s="${si}" data-c="${ci}" title="${c.titel}">
<img src="cards/s${si}-c${ci}.png" alt="${c.titel}" loading="lazy"></button>`; <img src="cards/s${si}-c${ci}.png" alt="${c.titel}" loading="lazy"></button>`;
}); }).join("");
grid += `</div></div>`;
});
$("#panel").innerHTML = ` $("#panel").innerHTML = `
<div class="setupHead">Schritt 1 · Action Card ziehen</div> <div class="setupHead">Schritt 1 · Action Card ziehen</div>
<h2 class="setupTitle">Welche Karte zieht ihr?</h2> <h2 class="setupTitle">Welche Karte habt ihr gezogen?</h2>
<p class="muted">Tippt auf eine Action Card, um sie zu ziehen.</p> <p class="muted">Tippt auf die Action Card, die ihr gezogen habt.</p>
<div class="deck">${grid}</div>`; <div class="deck"><div class="deckRow">${cards}</div></div>`;
$("#panel").querySelectorAll(".deckCard").forEach(el=>{ $("#panel").querySelectorAll(".deckCard").forEach(el=>{
el.onclick=()=>{ S.service=+el.dataset.s; S.change=+el.dataset.c; el.onclick=()=>{ S.service=+el.dataset.s; S.change=+el.dataset.c;
S.classifyDone=false; S.classifyWrong=null; S.entryDone=false; S.entryWrong=null; S.classifyDone=false; S.classifyWrong=null; S.entryDone=false; S.entryWrong=null;
@ -1167,10 +1197,15 @@ function renderClassify(){
const card = acard(S.service,S.change); const card = acard(S.service,S.change);
const cardBig = `<img class="classifyCard" src="cards/s${S.service}-c${S.change}.png" alt="${card.titel}">`; const cardBig = `<img class="classifyCard" src="cards/s${S.service}-c${S.change}.png" alt="${card.titel}">`;
if(!S.classifyDone){ if(!S.classifyDone){
const choices = CHANGE_TYPES.map((t,i)=> const choices = CT_ORDER.map(i=>
`<button class="choice ${S.classifyWrong===i?'bad':''}" data-i="${i}">${t}</button>`).join(""); `<button class="choice ${S.classifyWrong===i?'bad':''}" data-i="${i}">${CHANGE_TYPES[i]}</button>`).join("");
const legend = `<div class="legend"><h4>Legende — Change-Arten</h4><dl>` + const legend = `<div class="legend"><h4>Legende: Change-Arten im DIGITOM</h4>` +
CHANGE_TYPES.map((t,i)=>`<dt>${t}</dt><dd>${CHANGE_LEGEND[i]}</dd>`).join("") + `</dl></div>`; CT_ORDER.map(i=>{ const L = CHANGE_LEGEND[i];
return `<div class="lgItem"><div class="lgName">${CHANGE_TYPES[i]}</div>`
+ `<div class="lgIdee">${L.idee}</div>`
+ `<ul class="lgBed">${L.bed.map(b=>`<li>${b}</li>`).join("")}</ul>`
+ `<div class="lgBsp"><b>Beispiel:</b> ${L.bsp}</div></div>`;
}).join("") + `</div>`;
const hint = S.classifyWrong!=null const hint = S.classifyWrong!=null
? `<div class="hint bad">Nicht ganz — überlegt nochmal und probiert es erneut.</div>` : ``; ? `<div class="hint bad">Nicht ganz — überlegt nochmal und probiert es erneut.</div>` : ``;
$("#panel").innerHTML = ` $("#panel").innerHTML = `
@ -1178,11 +1213,11 @@ function renderClassify(){
<div class="classifyTop"> <div class="classifyTop">
${cardBig} ${cardBig}
<div class="classifyMain"> <div class="classifyMain">
<h2 class="setupTitle" style="margin-top:0">Welche Art von Change ist das?</h2> <h2 class="setupTitle" style="margin-top:0">Welche Art von Change könnte das sein?</h2>
<p class="muted">Überlegt gemeinsam und wählt die passende Change-Art. Die Legende hilft beim Einordnen.</p> <p class="muted">Überlegt gemeinsam und wählt die passende Change-Art. Die Legende hilft beim Einordnen.</p>
${legend}
${hint} ${hint}
<div class="choiceGrid grid2">${choices}</div> <div class="choiceGrid grid2">${choices}</div>
${legend}
</div> </div>
</div> </div>
<div class="actions"><button class="ghost" id="backDeck">← Andere Karte</button></div>`; <div class="actions"><button class="ghost" id="backDeck">← Andere Karte</button></div>`;
@ -1200,7 +1235,8 @@ function renderClassify(){
<div class="classifyMain"> <div class="classifyMain">
<div class="hint ok">✓ Richtig: ${CHANGE_TYPES[correct]}</div> <div class="hint ok">✓ Richtig: ${CHANGE_TYPES[correct]}</div>
<div class="recBox"><h4>Warum?</h4> <div class="recBox"><h4>Warum?</h4>
<p style="margin:0;color:var(--muted)">${CHANGE_LEGEND[correct]}</p></div> <p style="margin:0 0 6px;color:var(--muted)">${CHANGE_LEGEND[correct].idee}</p>
<p style="margin:0;color:var(--muted)"><b>Beispiel:</b> ${CHANGE_LEGEND[correct].bsp}</p></div>
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">

View file

@ -1,5 +1,5 @@
/* Service Worker — SLC-Workshop Companion (App-Shell, offline-first) */ /* Service Worker — SLC-Workshop Companion (App-Shell, offline-first) */
const CACHE = "slc-companion-v15"; const CACHE = "slc-companion-v16";
const SHELL = ["./", "index.html", "manifest.webmanifest", "icon.svg"]; const SHELL = ["./", "index.html", "manifest.webmanifest", "icon.svg"];
// Action-Card-Grafiken (cards/s<service>-c<change>.png) fuer Offline vorab cachen (alle 30). // Action-Card-Grafiken (cards/s<service>-c<change>.png) fuer Offline vorab cachen (alle 30).
const CARDS = []; const CARDS = [];