Files
dicampi-raid-simulator/simulatore.html

4096 lines
240 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Simulatore Raid</title>
<style>
:root{
--bg0:#060814; --bg1:#0a1024;
--card: rgba(255,255,255,.06);
--card2: rgba(255,255,255,.09);
--stroke: rgba(255,255,255,.12);
--text: rgba(255,255,255,.92);
--muted: rgba(255,255,255,.72);
--muted2: rgba(255,255,255,.55);
--shadow: 0 18px 60px rgba(0,0,0,.55);
--ok: #4ADE80;
--warn: #FBBF24;
--bad: #FB7185;
--info: #60A5FA;
--accent: #A78BFA;
--accent2: #22D3EE;
--radius: 18px; --radius2: 24px;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
}
*{box-sizing:border-box}
body{
margin:0; color:var(--text); font-family:var(--sans); min-height:100vh;
background:
radial-gradient(1200px 700px at 15% 10%, rgba(167,139,250,.25), transparent 60%),
radial-gradient(900px 600px at 85% 18%, rgba(34,211,238,.22), transparent 60%),
radial-gradient(900px 600px at 50% 110%, rgba(74,222,128,.16), transparent 60%),
linear-gradient(180deg, var(--bg0), var(--bg1));
}
.wrap{max-width:1380px;margin:0 auto;padding:18px 14px 32px}
header{
display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;margin-bottom:12px
}
h1{margin:0;font-size:clamp(22px,3.1vw,36px);letter-spacing:.2px;line-height:1.1}
.subtitle{margin-top:8px;color:var(--muted);font-size:14px;line-height:1.4;max-width:85ch}
.badge{
display:inline-flex;align-items:center;gap:10px;
padding:10px 12px;border-radius:999px;
background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);
box-shadow:0 10px 28px rgba(0,0,0,.28);
backdrop-filter:blur(10px);
font-size:13px;color:var(--muted);
white-space:nowrap;
}
.badge .dot{width:10px;height:10px;border-radius:999px;background:linear-gradient(135deg,var(--accent),var(--accent2));box-shadow:0 0 0 3px rgba(167,139,250,.15)}
.tabs{
display:flex;gap:10px;flex-wrap:wrap;
padding:10px;border-radius:999px;border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
box-shadow:0 12px 40px rgba(0,0,0,.35);
backdrop-filter:blur(10px);
}
.tab{
cursor:pointer; user-select:none;
padding:10px 14px;border-radius:999px;border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
color:rgba(255,255,255,.82);
font-weight:900; letter-spacing:.2px; font-size:13px;
transition:background .2s ease, transform .05s ease, border-color .2s ease;
display:flex;align-items:center;gap:8px;
}
.tab:hover{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.18)}
.tab:active{transform:translateY(1px)}
.tab.active{
background:linear-gradient(135deg, rgba(167,139,250,.65), rgba(34,211,238,.45));
border-color:rgba(255,255,255,.18); color:rgba(255,255,255,.95)
}
.card{
background:linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.045));
border:1px solid rgba(255,255,255,.12);
border-radius:var(--radius2);
box-shadow:var(--shadow);
backdrop-filter:blur(10px);
overflow:hidden;
}
.head{
padding:14px 16px 12px;
display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
border-bottom:1px solid rgba(255,255,255,.08);
background:linear-gradient(180deg, rgba(255,255,255,.05), transparent);
}
.head h2{margin:0;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:10px}
.pill{
display:inline-flex;align-items:center;gap:8px;
padding:7px 10px;border-radius:999px;border:1px solid rgba(255,255,255,.14);
background:rgba(255,255,255,.06);
color:var(--muted);font-size:12px;white-space:nowrap
}
.pill strong{color:rgba(255,255,255,.92);font-weight:900}
.content{padding:16px}
/* Layout pages */
.page{display:none}
.page.active{display:block}
.grid{display:grid;grid-template-columns:1.05fr .95fr;gap:14px}
@media (max-width: 980px){.grid{grid-template-columns:1fr}}
/* Inputs */
label{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--muted)}
select,input[type="number"],input[type="text"]{
height:40px;border-radius:12px;border:1px solid rgba(255,255,255,.14);
background:#0d1220;color:rgba(255,255,255,.92);
padding:0 12px;outline:none;box-shadow:inset 0 0 0 1px rgba(255,255,255,.04);
font-family:var(--sans)
}
select option{background:#0d1220;color:rgba(255,255,255,.92);}
select:focus,input:focus{border-color:rgba(167,139,250,.55);box-shadow:0 0 0 4px rgba(167,139,250,.15)}
.controls{display:grid;grid-template-columns:1.1fr .7fr .7fr;gap:10px}
@media (max-width: 720px){.controls{grid-template-columns:1fr}}
/* Buttons */
.btnrow{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-top:10px}
button{
height:40px;padding:0 14px;border-radius:12px;
border:1px solid rgba(255,255,255,.14);
background:rgba(255,255,255,.07);
color:rgba(255,255,255,.92);
cursor:pointer;
transition:transform .05s ease, background .2s ease, opacity .2s ease, border-color .2s ease;
font-weight:900;letter-spacing:.2px
}
button:hover{background:rgba(255,255,255,.10)}
button:active{transform:translateY(1px)}
button:disabled{opacity:.45;cursor:not-allowed}
.primary{
background:linear-gradient(135deg, rgba(167,139,250,.65), rgba(34,211,238,.45));
border-color:rgba(255,255,255,.18)
}
.primary:hover{background:linear-gradient(135deg, rgba(167,139,250,.78), rgba(34,211,238,.55))}
.danger{background:rgba(251,113,133,.16);border-color:rgba(251,113,133,.35)}
.danger:hover{background:rgba(251,113,133,.24)}
.mutebtn{background:rgba(255,255,255,.05);color:rgba(255,255,255,.86)}
/* Stats */
.split{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px}
@media (max-width: 720px){.split{grid-template-columns:1fr}}
.stat{
padding:12px;border-radius:16px;border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05)
}
.stat .k{font-size:12px;color:var(--muted);margin-bottom:6px}
.stat .v{font-size:18px;font-weight:950;letter-spacing:.2px}
.stat .s{margin-top:6px;font-size:12px;color:var(--muted2);line-height:1.35;font-family:var(--mono)}
.statusLine{
margin-top:12px;padding:12px;border-radius:16px;border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap
}
.bigStatus{display:flex;align-items:center;gap:10px;font-weight:950;letter-spacing:.3px;font-size:14px}
.lamp{width:10px;height:10px;border-radius:999px;box-shadow:0 0 0 4px rgba(255,255,255,.06)}
.lamp.ok{background:var(--ok);box-shadow:0 0 0 4px rgba(74,222,128,.12)}
.lamp.degraded{background:var(--warn);box-shadow:0 0 0 4px rgba(251,191,36,.12)}
.lamp.failed{background:var(--bad);box-shadow:0 0 0 4px rgba(251,113,133,.12)}
.mini{font-family:var(--mono);font-size:12px;color:rgba(255,255,255,.80)}
/* Disks */
.disks{display:grid;grid-template-columns:repeat(6,1fr);gap:10px;margin-top:12px}
@media (max-width: 1100px){.disks{grid-template-columns:repeat(5,1fr)}}
@media (max-width: 860px){.disks{grid-template-columns:repeat(4,1fr)}}
@media (max-width: 540px){.disks{grid-template-columns:repeat(3,1fr)}}
.disk{
position:relative;padding:12px 10px 10px;border-radius:16px;
border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
cursor:pointer;user-select:none;
transition:transform .06s ease, border-color .2s ease, background .2s ease;
overflow:hidden
}
.disk:hover{transform:translateY(-1px);border-color:rgba(255,255,255,.22);background:rgba(255,255,255,.07)}
.disk .top{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px}
.disk .idx{font-family:var(--mono);font-size:12px;color:rgba(255,255,255,.84)}
.disk .state{font-size:11px;padding:4px 8px;border-radius:999px;border:1px solid rgba(255,255,255,.16);
color:rgba(255,255,255,.86);background:rgba(255,255,255,.06);white-space:nowrap;font-family:var(--mono)}
.disk .icon{
width:100%;height:58px;border-radius:12px;border:1px solid rgba(255,255,255,.12);
background:linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.02));
display:flex;align-items:center;justify-content:center;
box-shadow:inset 0 0 0 1px rgba(255,255,255,.04);
position:relative
}
.disk .icon::before{content:"";width:70%;height:10px;border-radius:999px;background:rgba(255,255,255,.12);
position:absolute;bottom:8px;left:15%}
.disk .icon::after{content:"";width:22%;height:10px;border-radius:999px;background:rgba(255,255,255,.16);
position:absolute;top:10px;left:14px}
.disk.ok{border-color:rgba(74,222,128,.25)}
.disk.ok .state{border-color:rgba(74,222,128,.35);background:rgba(74,222,128,.10)}
.disk.ok .icon{border-color:rgba(74,222,128,.30)}
.disk.failed{border-color:rgba(251,113,133,.28)}
.disk.failed .state{border-color:rgba(251,113,133,.42);background:rgba(251,113,133,.14)}
.disk.failed .icon{border-color:rgba(251,113,133,.34)}
.disk.removed{border-color:rgba(255,255,255,.18);opacity:.72}
.disk.removed .state{border-color:rgba(255,255,255,.2);background:rgba(255,255,255,.05)}
.disk.removed .icon{border-color:rgba(255,255,255,.18);filter:grayscale(1) saturate(.2);opacity:.75}
.disk.spare{border-color:rgba(96,165,250,.28)}
.disk.spare .state{border-color:rgba(96,165,250,.45);background:rgba(96,165,250,.14)}
.disk.spare .icon{border-color:rgba(96,165,250,.34)}
.disk.rebuilding{border-color:rgba(251,191,36,.30);background:rgba(251,191,36,.08)}
.disk.rebuilding .state{border-color:rgba(251,191,36,.45);background:rgba(251,191,36,.14)}
.disk.rebuilding .icon{border-color:rgba(251,191,36,.35)}
.disk.slow{border-color:rgba(96,165,250,.35);background:rgba(96,165,250,.08)}
.disk.slow .state{border-color:rgba(96,165,250,.50);background:rgba(96,165,250,.14)}
.disk.overheat{border-color:rgba(251,191,36,.35);background:rgba(251,191,36,.09)}
.disk.overheat .state{border-color:rgba(251,191,36,.55);background:rgba(251,191,36,.18)}
.disk.crc{border-color:rgba(167,139,250,.35);background:rgba(167,139,250,.09)}
.disk.crc .state{border-color:rgba(167,139,250,.55);background:rgba(167,139,250,.18)}
.bar{position:absolute;left:10px;right:10px;bottom:10px;height:7px;border-radius:999px;background:rgba(255,255,255,.10);
border:1px solid rgba(255,255,255,.12);overflow:hidden;display:none}
.disk.rebuilding .bar{display:block}
.bar>span{display:block;height:100%;width:0%;background:linear-gradient(90deg, rgba(251,191,36,.8), rgba(34,211,238,.7));border-radius:999px}
/* Terminal */
.term{
background:rgba(2,6,23,.85);
border:1px solid rgba(255,255,255,.12);
border-radius:18px;
overflow:hidden;
box-shadow:0 18px 60px rgba(0,0,0,.45)
}
.termTop{
display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;
padding:10px 12px;
background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
border-bottom:1px solid rgba(255,255,255,.08)
}
.left{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-weight:950;letter-spacing:.2px;font-size:13px}
.lights{display:flex;gap:6px}
.light{width:10px;height:10px;border-radius:99px;background:rgba(255,255,255,.2)}
.light.r{background:rgba(251,113,133,.85)}
.light.y{background:rgba(251,191,36,.85)}
.light.g{background:rgba(74,222,128,.85)}
.chip{
display:inline-flex;align-items:center;gap:8px;
padding:6px 10px;border-radius:999px;border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
color:rgba(255,255,255,.78);
font-size:12px;font-family:var(--mono);white-space:nowrap
}
.termBody{height:460px;overflow:auto;padding:12px;font-family:var(--mono);font-size:12.5px;line-height:1.45}
.termLine{white-space:pre-wrap;word-break:break-word;margin:2px 0}
.termLine.dim{color:rgba(255,255,255,.65)}
.termLine.err{color:rgba(251,113,133,.95)}
.termLine.ok{color:rgba(74,222,128,.95)}
.termLine.warn{color:rgba(251,191,36,.95)}
.termLine.info{color:rgba(96,165,250,.95)}
.termInputRow{
display:flex;align-items:center;gap:8px;flex-wrap:wrap;
padding:10px 12px;border-top:1px solid rgba(255,255,255,.08);
background:rgba(255,255,255,.02);font-family:var(--mono)
}
.prompt{color:rgba(255,255,255,.78);user-select:none;white-space:nowrap}
.termInput{
flex:1;min-width:220px;height:36px;border-radius:12px;border:1px solid rgba(255,255,255,.12);
background:rgba(0,0,0,.25);color:rgba(255,255,255,.92);padding:0 10px;outline:none;
font-family:var(--mono);font-size:12.5px
}
.termInput:focus{border-color:rgba(34,211,238,.55);box-shadow:0 0 0 4px rgba(34,211,238,.12)}
.kbd{
font-family:var(--mono);font-size:12px;padding:2px 6px;border-radius:8px;
border:1px solid rgba(255,255,255,.14);
background:rgba(255,255,255,.06);
color:rgba(255,255,255,.86)
}
.note{
margin-top:10px;padding:12px;border-radius:16px;border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.045);
color:rgba(255,255,255,.78);font-size:12.5px;line-height:1.45
}
.scenarioBox{
border:1px dashed rgba(255,255,255,.18);
background:rgba(255,255,255,.03);
border-radius:16px;
padding:12px;
margin-top:12px
}
.scenarioTitle{font-weight:950;letter-spacing:.2px;margin-bottom:6px}
.scenarioGoal{color:rgba(255,255,255,.78);font-size:12.5px;line-height:1.45}
.scenarioGoal code{font-family:var(--mono);font-size:12px}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px}
@media(max-width:720px){.row2{grid-template-columns:1fr}}
.miniCard{
border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
border-radius:16px;
padding:12px
}
.miniCard h3{margin:0 0 6px;font-size:13px;letter-spacing:.2px}
.miniCard .p{color:rgba(255,255,255,.75);font-size:12.5px;line-height:1.45}
.kv{display:grid;grid-template-columns:1fr auto;gap:6px;align-items:center;font-family:var(--mono);font-size:12px;color:rgba(255,255,255,.80)}
.kv span{color:rgba(255,255,255,.62)}
.doc h3{margin:18px 0 8px}
.doc p,.doc li{color:rgba(255,255,255,.78);line-height:1.55}
.doc code{font-family:var(--mono);font-size:12px}
.doc .callout{
border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
padding:12px;border-radius:16px;
}
.small{font-size:12px;color:rgba(255,255,255,.72)}
a.fake{color:rgba(34,211,238,.92);text-decoration:none;border-bottom:1px dashed rgba(34,211,238,.45)}
/* ===== DOC PAGE STYLES ===== */
.doc-tabs{
display:flex;gap:8px;flex-wrap:wrap;
margin-bottom:18px;
}
.doc-tab{
cursor:pointer;user-select:none;
padding:9px 16px;border-radius:999px;
border:1px solid rgba(255,255,255,.14);
background:rgba(255,255,255,.05);
color:rgba(255,255,255,.75);
font-size:13px;font-weight:700;letter-spacing:.2px;
transition:background .2s, border-color .2s, color .2s;
}
.doc-tab:hover{background:rgba(255,255,255,.09);color:rgba(255,255,255,.92)}
.doc-tab.active{
background:linear-gradient(135deg, rgba(167,139,250,.55), rgba(34,211,238,.38));
border-color:rgba(255,255,255,.22);color:#fff;
}
.doc-panel{display:none}
.doc-panel.active{display:block}
/* focus badge */
.focus-badge{
display:inline-flex;align-items:center;gap:6px;
padding:5px 12px;border-radius:999px;
background:rgba(167,139,250,.14);border:1px solid rgba(167,139,250,.30);
color:rgba(167,139,250,.95);font-size:12px;font-weight:700;font-family:var(--mono);
}
/* callout yellow tip */
.tip-callout{
display:flex;gap:12px;align-items:flex-start;
border-left:3px solid var(--warn);
background:rgba(251,191,36,.06);
border-radius:0 14px 14px 0;
padding:12px 14px;
margin-bottom:20px;
}
.tip-callout .tip-icon{font-size:18px;flex-shrink:0;margin-top:1px}
.tip-callout .tip-body{font-size:13px;color:rgba(255,255,255,.85);line-height:1.55}
.tip-callout .tip-body b{color:rgba(255,255,255,.95)}
.tip-callout .tip-body code{font-family:var(--mono);font-size:12px;color:rgba(34,211,238,.9)}
/* code block */
.cmd-block{
background:rgba(2,6,23,.75);
border:1px solid rgba(255,255,255,.12);
border-radius:12px;
padding:11px 16px;
font-family:var(--mono);font-size:13px;
color:rgba(34,211,238,.92);
margin:10px 0 14px;
word-break:break-all;
}
/* step */
.step-block{margin-bottom:28px}
.step-title{
font-size:15px;font-weight:900;letter-spacing:.3px;
margin:0 0 8px;
color:rgba(255,255,255,.95);
}
.step-title span.step-num{
display:inline-flex;align-items:center;justify-content:center;
width:26px;height:26px;border-radius:50%;
background:linear-gradient(135deg, rgba(167,139,250,.6), rgba(34,211,238,.4));
font-size:12px;font-weight:900;margin-right:8px;
color:#fff;flex-shrink:0;
}
.step-desc{color:rgba(255,255,255,.75);font-size:13px;line-height:1.6;margin:0 0 6px}
.step-desc b{color:rgba(255,255,255,.92)}
/* inline code in text */
.doc-panel code,.step-desc code{
font-family:var(--mono);font-size:12px;
background:rgba(255,255,255,.08);
border:1px solid rgba(255,255,255,.12);
border-radius:6px;padding:1px 6px;
color:rgba(34,211,238,.88);
}
/* status colors inline */
.c-ok{color:var(--ok);font-weight:700}
.c-warn{color:var(--warn);font-weight:700}
.c-bad{color:var(--bad);font-weight:700}
/* flow box */
.flow-box{
border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.04);
border-radius:14px;padding:14px 16px;
margin:10px 0 14px;
}
.flow-box .flow-label{font-size:11px;color:rgba(255,255,255,.5);margin-bottom:8px;letter-spacing:.4px;text-transform:uppercase}
.flow-box .flow-cmds{display:flex;flex-direction:column;gap:5px}
.flow-cmd{
display:flex;align-items:center;gap:10px;
font-family:var(--mono);font-size:12.5px;color:rgba(255,255,255,.80);
}
.flow-cmd::before{
content:"";
color:rgba(167,139,250,.7);font-size:14px;flex-shrink:0;
}
/* small horizontal rule */
.doc-divider{border:none;border-top:1px solid rgba(255,255,255,.08);margin:20px 0}
/* grid for RAID types */
.raid-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:10px 0 16px}
@media(max-width:640px){.raid-grid{grid-template-columns:1fr}}
.raid-card{
border:1px solid rgba(255,255,255,.12);
background:rgba(255,255,255,.05);
border-radius:14px;padding:12px;
}
.raid-card h4{margin:0 0 6px;font-size:13px;letter-spacing:.2px;color:rgba(255,255,255,.92)}
.raid-card p{margin:0;font-size:12.5px;color:rgba(255,255,255,.72);line-height:1.5}
.raid-card .tag{
display:inline-block;margin-bottom:6px;
padding:2px 8px;border-radius:999px;font-size:11px;font-weight:700;
}
.tag-ok{background:rgba(74,222,128,.15);border:1px solid rgba(74,222,128,.3);color:var(--ok)}
.tag-warn{background:rgba(251,191,36,.13);border:1px solid rgba(251,191,36,.3);color:var(--warn)}
.tag-bad{background:rgba(251,113,133,.13);border:1px solid rgba(251,113,133,.3);color:var(--bad)}
.tag-info{background:rgba(96,165,250,.13);border:1px solid rgba(96,165,250,.3);color:var(--info)}
/* Teacher toggle improved */
.teacher-toggle{
margin-left:auto;
padding:8px 14px;
border-radius:999px;
border:1px solid rgba(255,255,255,.18);
background:rgba(255,255,255,.08);
color:var(--text);
font-weight:600;
cursor:pointer;
transition:all .25s ease;
white-space:nowrap;
}
.teacher-toggle.on{
background:linear-gradient(90deg,#22c55e,#16a34a);
color:white;
border-color:rgba(0,0,0,.2);
box-shadow:0 0 12px rgba(34,197,94,.6);
}
@media (max-width:700px){
.tabs{ flex-wrap:wrap; gap:6px; }
.teacher-toggle{ margin-left:auto; width:100%; margin-left:0; text-align:center; }
}
/* ===== NUOVI STILI GUIDA COMPLETA RAID ===== */
/* Hero intro RAID */
.raid-hero{
background: linear-gradient(135deg, rgba(167,139,250,.12), rgba(34,211,238,.08));
border: 1px solid rgba(167,139,250,.2);
border-radius: 18px;
padding: 20px 22px;
margin-bottom: 24px;
}
.raid-hero h3{
margin: 0 0 10px;
font-size: 17px;
font-weight: 900;
letter-spacing: .3px;
color: rgba(255,255,255,.96);
}
.raid-hero p{
margin: 0;
font-size: 13.5px;
color: rgba(255,255,255,.78);
line-height: 1.65;
}
/* Concetti fondamentali - layout 3 colonne */
.concept-grid{
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
margin: 14px 0 24px;
}
@media(max-width:760px){ .concept-grid{ grid-template-columns: 1fr; } }
.concept-card{
border-radius: 16px;
padding: 16px;
position: relative;
overflow: hidden;
}
.concept-card.stripe{
background: rgba(34,211,238,.07);
border: 1px solid rgba(34,211,238,.22);
}
.concept-card.mirror{
background: rgba(74,222,128,.07);
border: 1px solid rgba(74,222,128,.22);
}
.concept-card.parity{
background: rgba(167,139,250,.07);
border: 1px solid rgba(167,139,250,.22);
}
.concept-card h4{
margin: 0 0 8px;
font-size: 14px;
font-weight: 900;
letter-spacing: .2px;
}
.concept-card.stripe h4{ color: rgba(34,211,238,.95); }
.concept-card.mirror h4{ color: rgba(74,222,128,.95); }
.concept-card.parity h4{ color: rgba(167,139,250,.95); }
.concept-card p{
margin: 0 0 12px;
font-size: 12.5px;
color: rgba(255,255,255,.72);
line-height: 1.55;
}
/* Diagrammi visivi dischi */
.disk-diagram{
display: flex;
flex-direction: column;
gap: 5px;
margin-top: 10px;
}
.disk-row{
display: flex;
gap: 4px;
align-items: center;
}
.disk-row-label{
font-size: 11px;
font-family: var(--mono);
color: rgba(255,255,255,.45);
width: 28px;
flex-shrink: 0;
text-align: right;
}
.dblock{
height: 22px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 800;
font-family: var(--mono);
flex: 1;
border: 1px solid rgba(255,255,255,.08);
letter-spacing: .3px;
}
/* colori blocchi */
.db-a{ background: rgba(34,211,238,.22); color: rgba(34,211,238,.95); border-color: rgba(34,211,238,.3); }
.db-b{ background: rgba(74,222,128,.18); color: rgba(74,222,128,.95); border-color: rgba(74,222,128,.3); }
.db-c{ background: rgba(251,191,36,.18); color: rgba(251,191,36,.95); border-color: rgba(251,191,36,.3); }
.db-d{ background: rgba(167,139,250,.18); color: rgba(167,139,250,.95); border-color: rgba(167,139,250,.3); }
.db-e{ background: rgba(251,113,133,.16); color: rgba(251,113,133,.95); border-color: rgba(251,113,133,.3); }
.db-p{ background: rgba(255,255,255,.12); color: rgba(255,255,255,.7); border-color: rgba(255,255,255,.2); }
.db-pp{ background: rgba(167,139,250,.30); color: rgba(167,139,250,.98); border-color: rgba(167,139,250,.5); }
.db-empty{ background: rgba(255,255,255,.03); color: rgba(255,255,255,.25); border-color: rgba(255,255,255,.06); }
/* disco fisico label */
.disk-col-label{
display: flex;
gap: 4px;
margin-bottom: 3px;
}
.disk-col-label > div{
flex: 1;
text-align: center;
font-size: 10px;
font-family: var(--mono);
color: rgba(255,255,255,.40);
letter-spacing: .3px;
}
/* RAID section full */
.raid-section{
margin-bottom: 28px;
}
.raid-section-header{
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.raid-section-title{
font-size: 16px;
font-weight: 900;
letter-spacing: .3px;
color: rgba(255,255,255,.95);
margin: 0;
}
.raid-level-badge{
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 800;
font-family: var(--mono);
letter-spacing: .3px;
}
.rlb-0{ background: rgba(251,113,133,.15); border: 1px solid rgba(251,113,133,.35); color: var(--bad); }
.rlb-1{ background: rgba(74,222,128,.12); border: 1px solid rgba(74,222,128,.3); color: var(--ok); }
.rlb-2{ background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.15); color: rgba(255,255,255,.7); }
.rlb-3{ background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.15); color: rgba(255,255,255,.7); }
.rlb-4{ background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.15); color: rgba(255,255,255,.7); }
.rlb-5{ background: rgba(251,191,36,.12); border: 1px solid rgba(251,191,36,.3); color: var(--warn); }
.rlb-6{ background: rgba(74,222,128,.12); border: 1px solid rgba(74,222,128,.3); color: var(--ok); }
.rlb-10{ background: rgba(96,165,250,.12); border: 1px solid rgba(96,165,250,.3); color: var(--info); }
.raid-two-col{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
@media(max-width:700px){ .raid-two-col{ grid-template-columns: 1fr; } }
.raid-desc-box{
border: 1px solid rgba(255,255,255,.10);
background: rgba(255,255,255,.04);
border-radius: 14px;
padding: 14px;
}
.raid-desc-box p{
font-size: 13px;
color: rgba(255,255,255,.75);
line-height: 1.6;
margin: 0 0 12px;
}
.raid-desc-box p:last-child{ margin-bottom: 0; }
.raid-pros-cons{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-top: 10px;
}
.pros-box, .cons-box{
border-radius: 10px;
padding: 10px 12px;
font-size: 12px;
line-height: 1.5;
}
.pros-box{
background: rgba(74,222,128,.07);
border: 1px solid rgba(74,222,128,.18);
}
.pros-box .pc-title{ color: var(--ok); font-weight: 800; margin-bottom: 5px; }
.cons-box{
background: rgba(251,113,133,.07);
border: 1px solid rgba(251,113,133,.18);
}
.cons-box .pc-title{ color: var(--bad); font-weight: 800; margin-bottom: 5px; }
.pros-box li, .cons-box li{
color: rgba(255,255,255,.72);
margin-bottom: 3px;
padding-left: 2px;
}
.pc-list{ list-style: none; padding: 0; margin: 0; }
.pc-list li::before{ content: "• "; }
/* Specs strip */
.specs-strip{
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 10px 0;
}
.spec-chip{
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
border-radius: 999px;
font-size: 11.5px;
font-family: var(--mono);
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.05);
color: rgba(255,255,255,.72);
}
.spec-chip b{ color: rgba(255,255,255,.92); }
/* diagram container */
.diagram-container{
border: 1px solid rgba(255,255,255,.10);
background: rgba(0,0,0,.25);
border-radius: 14px;
padding: 14px;
}
.diagram-title{
font-size: 11px;
text-transform: uppercase;
letter-spacing: .5px;
color: rgba(255,255,255,.4);
margin-bottom: 10px;
}
/* Legenda colori */
.legend{
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 12px 0 6px;
}
.legend-item{
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: rgba(255,255,255,.65);
}
.legend-dot{
width: 10px;
height: 10px;
border-radius: 3px;
}
/* Obsolete badge */
.obsolete-badge{
display: inline-flex;
align-items: center;
padding: 3px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
background: rgba(255,255,255,.06);
border: 1px solid rgba(255,255,255,.14);
color: rgba(255,255,255,.45);
margin-left: 8px;
}
/* Comparison table */
.comparison-table{
width: 100%;
border-collapse: collapse;
font-size: 12.5px;
margin: 14px 0;
}
.comparison-table th{
padding: 9px 12px;
text-align: left;
font-size: 11px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .5px;
color: rgba(255,255,255,.5);
border-bottom: 1px solid rgba(255,255,255,.1);
background: rgba(255,255,255,.03);
}
.comparison-table td{
padding: 9px 12px;
border-bottom: 1px solid rgba(255,255,255,.06);
color: rgba(255,255,255,.78);
vertical-align: middle;
}
.comparison-table tr:last-child td{ border-bottom: none; }
.comparison-table tr:hover td{ background: rgba(255,255,255,.03); }
.comparison-table td:first-child{
font-weight: 800;
color: rgba(255,255,255,.92);
font-family: var(--mono);
}
.ct-ok{ color: var(--ok) !important; font-weight: 700; }
.ct-warn{ color: var(--warn) !important; font-weight: 700; }
.ct-bad{ color: var(--bad) !important; font-weight: 700; }
.ct-info{ color: var(--info) !important; font-weight: 700; }
.ct-muted{ color: rgba(255,255,255,.38) !important; font-style: italic; }
/* ===== PROCEDURE COMPLETE STYLES ===== */
/* Proc navigation buttons */
.proc-nav-btn{
padding:9px 16px;border-radius:999px;
border:1px solid rgba(255,255,255,.14);
background:rgba(255,255,255,.05);
color:rgba(255,255,255,.75);
font-size:12.5px;font-weight:700;
cursor:pointer;
transition:all .2s;height:auto;letter-spacing:.1px;
}
.proc-nav-btn:hover{background:rgba(255,255,255,.09);color:rgba(255,255,255,.92)}
.proc-nav-btn.active{
background:linear-gradient(135deg,rgba(167,139,250,.5),rgba(34,211,238,.35));
border-color:rgba(255,255,255,.22);color:#fff;
}
/* Proc sections */
.proc-section{display:none}
.proc-section.active{display:block}
/* CMD card */
.cmd-card{
border:1px solid rgba(255,255,255,.10);
background:rgba(255,255,255,.03);
border-radius:18px;
overflow:hidden;
margin-bottom:14px;
}
.cmd-card-header{
display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
padding:12px 16px;
background:rgba(255,255,255,.04);
border-bottom:1px solid rgba(255,255,255,.07);
}
.cmd-card-title{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.cmd-main{
font-family:var(--mono);font-size:13.5px;font-weight:700;
color:rgba(34,211,238,.92);
background:rgba(34,211,238,.07);
border:1px solid rgba(34,211,238,.18);
border-radius:8px;padding:4px 10px;
}
.cmd-number{
display:inline-flex;align-items:center;justify-content:center;
width:30px;height:30px;border-radius:50%;flex-shrink:0;
background:linear-gradient(135deg,rgba(167,139,250,.6),rgba(34,211,238,.4));
font-size:12px;font-weight:900;color:#fff;
}
.cmd-badge{
display:inline-flex;align-items:center;
padding:4px 10px;border-radius:999px;font-size:11px;font-weight:800;
white-space:nowrap;
}
.cmd-badge-diag{background:rgba(96,165,250,.13);border:1px solid rgba(96,165,250,.3);color:var(--info)}
.cmd-badge-recovery{background:rgba(251,191,36,.12);border:1px solid rgba(251,191,36,.3);color:var(--warn)}
.cmd-badge-monitor{background:rgba(74,222,128,.11);border:1px solid rgba(74,222,128,.28);color:var(--ok)}
.cmd-badge-fs{background:rgba(167,139,250,.13);border:1px solid rgba(167,139,250,.3);color:var(--accent)}
.cmd-card-body{padding:14px 16px}
.cmd-two-col{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media(max-width:760px){.cmd-two-col{grid-template-columns:1fr}}
.cmd-section-label{
font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;
color:rgba(255,255,255,.42);margin-bottom:5px;font-weight:700;
}
.cmd-desc{font-size:13px;color:rgba(255,255,255,.75);line-height:1.6;margin:0 0 4px}
.cmd-list{
list-style:none;padding:0;margin:0;
display:flex;flex-direction:column;gap:4px;
}
.cmd-list li{
font-size:12.5px;color:rgba(255,255,255,.72);line-height:1.5;
padding-left:14px;position:relative;
}
.cmd-list li::before{content:"";position:absolute;left:0;color:rgba(167,139,250,.7);font-weight:900}
.cmd-list code{font-family:var(--mono);font-size:12px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);border-radius:5px;padding:1px 5px;color:rgba(34,211,238,.85)}
/* Terminal-style output blocks */
.cmd-output{
font-family:var(--mono);font-size:12px;line-height:1.7;
padding:10px 12px;border-radius:10px;
white-space:pre-wrap;word-break:break-word;
}
.ok-output{background:rgba(2,6,23,.8);border:1px solid rgba(255,255,255,.10);color:rgba(255,255,255,.75)}
.warn-output{background:rgba(10,8,2,.8);border:1px solid rgba(251,191,36,.18);color:rgba(255,255,255,.75)}
.err-output{background:rgba(15,2,4,.8);border:1px solid rgba(251,113,133,.18);color:rgba(255,255,255,.75)}
.info-output{background:rgba(2,6,23,.8);border:1px solid rgba(96,165,250,.18);color:rgba(255,255,255,.75)}
/* Use case pills */
.usecase-row{
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
}
.usecase-pill{
padding: 4px 10px;
border-radius: 999px;
font-size: 11.5px;
background: rgba(255,255,255,.06);
border: 1px solid rgba(255,255,255,.12);
color: rgba(255,255,255,.7);
}
/* server example callout */
.server-example{
display: flex;
gap: 10px;
align-items: flex-start;
background: rgba(96,165,250,.06);
border: 1px solid rgba(96,165,250,.18);
border-radius: 12px;
padding: 10px 12px;
margin-top: 10px;
}
.server-example .se-icon{ font-size: 18px; flex-shrink: 0; }
.server-example .se-text{ font-size: 12.5px; color: rgba(255,255,255,.75); line-height: 1.55; }
.server-example .se-text b{ color: rgba(255,255,255,.92); }
/* ── PANNELLO DOCENTE ── */
.teacher-form{
display:flex;flex-direction:column;gap:12px;
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);
border-radius:14px;padding:16px 18px;
}
.tf-row{ display:flex;gap:14px;flex-wrap:wrap; }
.tf-field{ display:flex;flex-direction:column;gap:5px;flex:1;min-width:180px; }
.tf-label{ font-size:11.5px;color:var(--muted2);font-weight:700;letter-spacing:.3px;text-transform:uppercase; }
.tf-input,.tf-select{
background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.16);
border-radius:10px;padding:9px 12px;color:var(--text);font-family:var(--sans);font-size:13px;
outline:none;transition:border-color .2s;
}
.tf-select{ appearance:none;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='rgba(255,255,255,.5)' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;padding-right:32px; }
.tf-input:focus,.tf-select:focus{ border-color:rgba(167,139,250,.55);box-shadow:0 0 0 3px rgba(167,139,250,.12); }
.tf-hint{ font-size:11.5px;color:var(--muted2);line-height:1.45; }
.teacher-code-box{
background:linear-gradient(135deg,rgba(167,139,250,.12),rgba(34,211,238,.08));
border:1px solid rgba(167,139,250,.35);border-radius:14px;padding:16px 18px;
}
.tcb-header{ display:flex;justify-content:space-between;align-items:center;margin-bottom:12px; }
.tcb-label{ font-size:13px;font-weight:700;color:var(--accent); }
.tcb-code{
font-family:var(--mono);font-size:15px;letter-spacing:2px;color:var(--text);
background:rgba(0,0,0,.35);border:1px solid rgba(255,255,255,.12);
border-radius:10px;padding:12px 16px;word-break:break-all;line-height:1.6;
user-select:all;cursor:text;
}
.tcb-meta{ margin-top:8px;font-size:11.5px;color:var(--muted2);line-height:1.5; }
/* section titles inside teacher form */
.tf-section-title{
font-size:12px;font-weight:800;letter-spacing:.6px;text-transform:uppercase;
color:var(--accent);padding:6px 0 4px;border-bottom:1px solid rgba(167,139,250,.2);
margin-bottom:10px;
}
/* array preview chips */
.tf-array-preview{
display:flex;gap:6px;flex-wrap:wrap;margin-top:10px;padding:10px 12px;
background:rgba(0,0,0,.25);border:1px solid rgba(255,255,255,.08);border-radius:10px;
min-height:38px;align-items:center;
}
.tf-disk-chip{
display:inline-flex;align-items:center;gap:5px;
padding:4px 10px;border-radius:999px;font-size:12px;font-weight:700;font-family:var(--mono);
background:rgba(74,222,128,.15);border:1px solid rgba(74,222,128,.3);color:var(--ok);
transition:background .2s,border-color .2s,color .2s;
}
.tf-disk-chip.fault-FAILED{background:rgba(251,113,133,.15);border-color:rgba(251,113,133,.4);color:var(--bad);}
.tf-disk-chip.fault-CRC{background:rgba(251,191,36,.12);border-color:rgba(251,191,36,.35);color:var(--warn);}
.tf-disk-chip.fault-OVERHEAT{background:rgba(251,146,60,.12);border-color:rgba(251,146,60,.35);color:#fb923c;}
.tf-disk-chip.fault-SLOW{background:rgba(96,165,250,.12);border-color:rgba(96,165,250,.3);color:var(--info);}
/* fault configuration grid */
.tf-fault-grid{display:flex;flex-direction:column;gap:6px;}
.tf-fault-row{
display:flex;align-items:center;gap:10px;flex-wrap:wrap;
background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.07);
border-radius:10px;padding:8px 12px;
}
.tf-fault-dev{
font-family:var(--mono);font-size:13px;font-weight:700;color:var(--text);
min-width:52px;flex-shrink:0;
}
.tf-fault-select{
background:#111827;border:1px solid rgba(255,255,255,.18);
border-radius:8px;padding:6px 28px 6px 10px;color:rgba(255,255,255,.92);font-size:12.5px;
font-family:var(--sans);outline:none;cursor:pointer;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='rgba(255,255,255,.55)' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat:no-repeat;background-position:right 9px center;appearance:none;
transition:border-color .2s;
}
.tf-fault-select option{background:#111827;color:rgba(255,255,255,.92);}
.tf-fault-select:focus{border-color:rgba(167,139,250,.6);}
.tf-fault-select.sel-FAILED{border-color:rgba(251,113,133,.6);color:var(--bad);}
.tf-fault-select.sel-FAILED option{color:rgba(255,255,255,.92);}
.tf-fault-select.sel-CRC{border-color:rgba(251,191,36,.6);color:var(--warn);}
.tf-fault-select.sel-CRC option{color:rgba(255,255,255,.92);}
.tf-fault-select.sel-OVERHEAT{border-color:rgba(251,146,60,.6);color:#fb923c;}
.tf-fault-select.sel-OVERHEAT option{color:rgba(255,255,255,.92);}
.tf-fault-select.sel-SLOW{border-color:rgba(96,165,250,.55);color:var(--info);}
.tf-fault-select.sel-SLOW option{color:rgba(255,255,255,.92);}
.tf-fault-desc{font-size:11.5px;color:var(--muted2);flex:1;line-height:1.35;}
/* Rebuild didactic banner */
.rebuild-banner{
margin-top:10px;padding:12px 14px;border-radius:16px;
border:1px solid rgba(251,191,36,.40);
background:linear-gradient(135deg, rgba(251,191,36,.10), rgba(251,146,60,.07));
display:flex;align-items:flex-start;gap:12px;
animation: rb-pulse 2s ease-in-out infinite;
}
@keyframes rb-pulse{0%,100%{border-color:rgba(251,191,36,.35)}50%{border-color:rgba(251,191,36,.70)}}
.rb-icon{font-size:20px;flex-shrink:0;line-height:1}
.rb-body{font-size:12.5px;color:rgba(255,255,255,.88);line-height:1.6}
.rb-body b{color:#FBBF24}
</style>
</head>
<body>
<div class="wrap">
<header>
<div>
<h1>Simulatore Raid <span style="opacity:.7"></span></h1>
<div class="subtitle"></div>
</div>
<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;justify-content:flex-end">
<div class="tabs" role="tablist" aria-label="Tabs">
<div class="tab active" id="tab-lab" role="tab">🧪 LAB</div>
<div class="tab" id="tab-ex" role="tab">🎓 ESERCIZI</div>
<div class="tab" id="tab-doc" role="tab">📚 DOCUMENTAZIONE</div>
<button class="teacher-toggle" id="btnTeacher">Teacher: OFF</button>
</div>
<div class="badge" title="Funziona offline: file unico HTML">
<span class="dot"></span> Offline · Scenari · Score · Report
</div>
</div>
</header>
<!-- PAGE: LAB -->
<div class="page active" id="page-lab">
<div class="grid">
<div class="card">
<div class="head">
<h2>🧪 Storage & Dashboard</h2>
<div class="pill"><strong>Array</strong> <span id="pillArray">/dev/md0</span></div>
</div>
<div class="content">
<div class="controls">
<label>
Livello RAID
<select id="raidLevel">
<option value="0">RAID 0 (striping)</option>
<option value="1">RAID 1 (mirroring)</option>
<option value="5" selected>RAID 5 (parità)</option>
<option value="6">RAID 6 (doppia parità)</option>
<option value="10">RAID 10 (stripe + mirror)</option>
</select>
</label>
<label>
Numero dischi
<input id="diskCount" type="number" min="2" max="12" value="4" />
</label>
<label>
Size disco (GB)
<input id="diskSize" type="number" min="10" max="200000" step="10" value="1000" />
</label>
</div>
<div class="btnrow">
<button class="primary" id="btnApply">Crea / Reset array</button>
<button class="mutebtn" id="btnResetDisks">Reset stati</button>
<button class="danger" id="btnRandomFault">Guasto random</button>
<button class="mutebtn" id="btnStartRandomExercise">Esercizio random</button>
</div>
<div class="split">
<div class="stat">
<div class="k">Capacità utile</div>
<div class="v" id="capValue"></div>
<div class="s" id="capNote"></div>
</div>
<div class="stat">
<div class="k">Fault tolerance</div>
<div class="v" id="ftValue"></div>
<div class="s" id="ftNote"></div>
</div>
</div>
<div class="statusLine">
<div class="bigStatus"><span id="lamp" class="lamp ok"></span><span id="bigStatus">STATUS: —</span></div>
<div class="mini" id="miniStatus"></div>
</div>
<!-- Banner didattico rebuild -->
<div id="rebuildBanner" style="display:none" class="rebuild-banner">
<div class="rb-icon"></div>
<div class="rb-body">
<b>MODALITÀ DIDATTICA — Rebuild accelerato</b><br>
<span id="rbSpeedText">Velocità aumentata di <b id="rbFactor"></b>×</span> &nbsp;·&nbsp;
<span>ETA simulato: <b id="rbEtaSim"></b></span> &nbsp;·&nbsp;
<span>Tempo reale stimato: <b id="rbEtaReal"></b></span>
</div>
</div>
<div class="row2">
<div class="miniCard">
<h3>📈 Telemetria (sim)</h3>
<div class="kv"><span>Temperature avg</span><b id="tempAvg"></b></div>
<div class="kv"><span>IOPS (sim)</span><b id="iops"></b></div>
<div class="kv"><span>CRC errors</span><b id="crc"></b></div>
<div class="kv"><span>Reallocated</span><b id="realloc"></b></div>
<div class="p" style="margin-top:8px">Cambia in base ai guasti (slow/overheat/crc/fail) e durante il rebuild.</div>
</div>
<div class="miniCard">
<h3>⏱ Verifica</h3>
<div class="kv"><span>Time left</span><b id="timeLeft">OFF</b></div>
<div class="kv"><span>Score</span><b id="score">0</b></div>
<div class="kv"><span>Hints used</span><b id="hints">0</b></div>
<div class="btnrow" style="margin-top:8px">
<button class="mutebtn" id="btnTimer5">5 min</button>
<button class="mutebtn" id="btnTimer10">10 min</button>
<button class="mutebtn" id="btnTimerOff">OFF</button>
<button class="mutebtn" id="btnExport">⬇ Scarica report</button>
</div>
</div>
</div>
<div class="disks" id="diskGrid"></div>
<div class="scenarioBox">
<div class="scenarioTitle">🎯 Scenario</div>
<div class="scenarioGoal" id="scenarioText">
Nessuno scenario caricato. Usa <span class="kbd">scenario list</span> o <span class="kbd">Esercizio random</span>.
</div>
<div class="btnrow" style="margin-top:10px">
<button class="mutebtn" data-scn="raid5_1fail">RAID5 1 fail</button>
<button class="mutebtn" data-scn="raid5_2fail">RAID5 2 fail</button>
<button class="mutebtn" data-scn="raid6_2fail">RAID6 2 fail</button>
<button class="mutebtn" data-scn="raid10_pairfail">RAID10 pair fail</button>
<button class="mutebtn" data-scn="crc_errors">CRC errors</button>
<button class="mutebtn" data-scn="overheat">Overheat</button>
<button class="mutebtn" data-scn="rebuild_interrupted">Rebuild interrotto</button>
<button class="mutebtn" data-scn="wrong_size_spare">Spare size errata</button>
</div>
</div>
<!-- CARICA CODICE PROVA (studente o docente in test) -->
<div class="scenarioBox" style="border-color:rgba(167,139,250,.35);background:rgba(167,139,250,.07)">
<div class="scenarioTitle" style="color:var(--accent)">🔑 Carica codice prova</div>
<div class="p" style="margin-bottom:10px;font-size:12.5px">
Hai ricevuto un codice dal docente? Incollalo qui e clicca <b>Avvia prova</b> per caricare lo scenario pianificato.
</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<input id="labCodeInput" type="text" placeholder="Incolla il codice prova..." style="
flex:1;min-width:200px;
background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.18);
border-radius:10px;padding:9px 12px;color:var(--text);font-family:var(--mono);
font-size:13px;outline:none;letter-spacing:.5px;
" />
<button class="primary" id="btnLoadCodeInLab" style="padding:9px 18px;white-space:nowrap">▶ Avvia prova</button>
</div>
<div id="labCodeFeedback" style="margin-top:8px;font-size:12px;color:var(--muted2);min-height:18px"></div>
</div>
<div class="note">
<b>Regola didattica:</b> lo studente deve lavorare col <b>terminale</b>. Il click sui dischi è disabilitato, salvo <b>Teacher ON</b>.
<br><br>
Prova con: <span class="kbd">help</span> · <span class="kbd">scenario list</span> · <span class="kbd">cat /proc/mdstat</span> · <span class="kbd">mdadm --detail /dev/md0</span> ·
<span class="kbd">smartctl -a /dev/sdX</span> · <span class="kbd">dmesg | tail</span> · <span class="kbd">mdadm --remove/--add/--rebuild</span>.
</div>
</div>
</div>
<div class="card">
<div class="head">
<h2>💻 Terminale Linux (simulato)</h2>
<div class="pills">
<div class="pill"><strong>Rebuild effettuati</strong> <span id="pillRebuildCount">727</span></div>
<div class="pill"><strong>Mode</strong> <span id="pillMode">LAB</span></div>
</div>
</div>
<div class="content">
<div class="term">
<div class="termTop">
<div class="left">
<div class="lights"><span class="light r"></span><span class="light y"></span><span class="light g"></span></div>
<span>raidlab@dc1</span>
<span class="chip" id="fsChip">fs: none</span>
<span class="chip" id="exerciseChip">exercise: off</span>
</div>
<div class="chip" id="tipChip">tip: help</div>
</div>
<div class="termBody" id="termBody"></div>
<div class="termInputRow">
<div class="prompt" id="promptText">raidlab(OK)$</div>
<input class="termInput" id="termInput" placeholder="digita 'help'…" autocomplete="off" spellcheck="false" />
<button class="mutebtn" id="btnRun">Invia</button>
</div>
</div>
<div class="scenarioBox">
<div class="scenarioTitle">🧩 Parità RAID5 (visual)</div>
<div class="scenarioGoal"><pre style="margin:0;white-space:pre-wrap" id="parityText">Disponibile quando RAID5 è selezionato.</pre></div>
</div>
<div class="note">
<b>Extra:</b> <span class="kbd">hint</span> (penalità) · <span class="kbd">report</span> / <span class="kbd">export</span> ·
<span class="kbd">powerfail</span> (simula blackout, interrompe rebuild) · <span class="kbd">solution</span> (solo Teacher).
</div>
</div>
</div>
</div>
</div>
<!-- PAGE: EXERCISES -->
<div class="page" id="page-ex">
<!-- ── PANNELLO DOCENTE ─────────────────────────────────────────── -->
<div class="card" style="margin-bottom:16px">
<div class="head">
<h2>👨‍🏫 Pannello Docente — Programmazione verifiche</h2>
<div class="pill" style="background:linear-gradient(135deg,rgba(167,139,250,.25),rgba(34,211,238,.18));border-color:rgba(167,139,250,.35)">
<span style="color:var(--accent)"></span> <strong>Configurazione personalizzata</strong>
</div>
</div>
<div class="content">
<div class="note" style="margin-bottom:16px;border-color:rgba(167,139,250,.3);background:rgba(167,139,250,.07)">
<b>Come funziona:</b> configura l'array RAID esattamente come vuoi → assegna i guasti ai dischi → imposta classe/data/timer →
clicca <b>Genera codice prova</b>. Lo studente incolla il codice nel LAB e la prova parte identica per tutti.
</div>
<div class="teacher-form" id="teacherForm">
<!-- ── SEZIONE 1: CONFIGURAZIONE ARRAY ── -->
<div class="tf-section-title">① Configurazione array RAID</div>
<div class="tf-row">
<div class="tf-field">
<label class="tf-label">Livello RAID</label>
<select id="tfRaidLevel" class="tf-select" onchange="tfUpdateDiskFaultRows()">
<option value="0">RAID 0 — striping (no fault tolerance)</option>
<option value="1">RAID 1 — mirroring (1 disco di tolleranza)</option>
<option value="5" selected>RAID 5 — parità distribuita (1 disco di tolleranza)</option>
<option value="6">RAID 6 — doppia parità (2 dischi di tolleranza)</option>
<option value="10">RAID 10 — stripe + mirror (50% ridondanza)</option>
</select>
</div>
<div class="tf-field">
<label class="tf-label">Numero dischi</label>
<input id="tfDiskCount" class="tf-input" type="number" min="2" max="12" value="4"
oninput="tfUpdateDiskFaultRows()" />
</div>
<div class="tf-field">
<label class="tf-label">Size disco (GB)</label>
<input id="tfDiskSize" class="tf-input" type="number" min="10" max="200000" step="10" value="1000" />
</div>
</div>
<!-- anteprima array -->
<div id="tfArrayPreview" class="tf-array-preview"></div>
<!-- ── SEZIONE 2: GUASTI ── -->
<div class="tf-section-title" style="margin-top:14px">② Guasti da assegnare ai dischi</div>
<div id="tfDiskFaultRows" class="tf-fault-grid"></div>
<div class="tf-hint" style="margin-top:6px">
Lascia "Nessun guasto" per i dischi che devono restare OK. Puoi assegnare guasti multipli su dischi diversi.
</div>
<!-- ── SEZIONE 3: DATI VERIFICA ── -->
<div class="tf-section-title" style="margin-top:14px">③ Dati verifica</div>
<div class="tf-row">
<div class="tf-field">
<label class="tf-label">Classe</label>
<input id="tfClasse" class="tf-input" type="text" placeholder="es. 4A-INF" maxlength="20" />
</div>
<div class="tf-field">
<label class="tf-label">Data verifica</label>
<input id="tfData" class="tf-input" type="date" />
</div>
<div class="tf-field">
<label class="tf-label">Durata (minuti)</label>
<select id="tfDurata" class="tf-select">
<option value="0">Nessun timer</option>
<option value="5">5 minuti</option>
<option value="10" selected>10 minuti</option>
<option value="15">15 minuti</option>
<option value="20">20 minuti</option>
<option value="30">30 minuti</option>
</select>
</div>
</div>
<div class="tf-row">
<div class="tf-field" style="flex:1">
<label class="tf-label">Note per lo studente (opzionale)</label>
<input id="tfNote" class="tf-input" type="text" placeholder="es. Usa solo i comandi visti in laboratorio" maxlength="120" />
</div>
</div>
<!-- ── GENERA ── -->
<div class="tf-row" style="align-items:center;gap:14px;flex-wrap:wrap;margin-top:6px">
<button class="primary" id="btnGenTeacherCode" style="padding:11px 24px;font-size:14px">🔐 Genera codice prova</button>
<div id="tfValidationMsg" class="tf-hint" style="color:var(--bad)"></div>
</div>
</div>
<!-- OUTPUT CODICE DOCENTE -->
<div id="teacherCodeOutput" style="display:none;margin-top:20px">
<div class="teacher-code-box">
<div class="tcb-header">
<span class="tcb-label">📋 Codice prova generato</span>
<button class="mutebtn" id="btnCopyTeacherCode" style="font-size:12px;padding:5px 12px">⎘ Copia</button>
</div>
<div class="tcb-code" id="teacherCodeText"></div>
<div class="tcb-meta" id="teacherCodeMeta"></div>
</div>
<div class="row2" style="margin-top:12px">
<div class="miniCard">
<h3>Riepilogo prova</h3>
<div class="kv"><span>Array</span><b id="tcArray"></b></div>
<div class="kv"><span>Guasti</span><b id="tcFaults"></b></div>
<div class="kv"><span>Classe</span><b id="tcClasse"></b></div>
<div class="kv"><span>Data</span><b id="tcData"></b></div>
<div class="kv"><span>Durata</span><b id="tcDurata"></b></div>
<div class="kv"><span>Obiettivo atteso</span><b id="tcGoal"></b></div>
</div>
<div class="miniCard">
<div class="scenarioBox" style="margin:0">
<div class="scenarioTitle">📌 Istruzioni per lo studente</div>
<div class="scenarioGoal" style="font-size:12.5px">
1) Aprire il simulatore RAID<br>
2) Tab <b>LAB</b> → sezione "Carica codice prova"<br>
3) Incollare il codice e cliccare <b>Avvia prova</b><br>
4) Svolgere la prova entro il tempo indicato<br>
5) Consegnare: codice prova + <span class="kbd">export</span> del report
</div>
</div>
<div class="btnrow" style="margin-top:10px">
<button class="mutebtn" id="btnLoadTeacherInLab">▶ Carica nel LAB (test docente)</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ── PROVA RAPIDA (ex sezione originale) ──────────────────────── -->
<div class="card">
<div class="head">
<h2>🎓 Prova rapida (casuale)</h2>
<div class="pill"><strong>Codice</strong> <span id="exerciseCode"></span></div>
</div>
<div class="content">
<div class="row2">
<div class="miniCard">
<h3>Genera prova casuale</h3>
<div class="p">
Clicca "Genera prova": otterrai un <b>codice prova</b> e uno <b>scenario random</b>.
Lo studente deve consegnare il codice + il report (comando <span class="kbd">export</span> o tasto Export report).
</div>
<div class="btnrow" style="margin-top:10px">
<button class="primary" id="btnGenExercise">Genera prova</button>
<button class="mutebtn" id="btnLoadExercise">Carica questa prova nel LAB</button>
</div>
<div class="note">
<b>Tip:</b> puoi impostare il timer nel LAB (5/10 min) e far fare una verifica "a tempo".
</div>
</div>
<div class="miniCard">
<h3>Dettagli prova</h3>
<div class="kv"><span>Scenario</span><b id="exScenario"></b></div>
<div class="kv"><span>Seed</span><b id="exSeed"></b></div>
<div class="kv"><span>Obiettivo</span><b id="exGoalShort"></b></div>
<div class="p" style="margin-top:8px" id="exGoalLong"></div>
<div class="scenarioBox" style="margin-top:12px">
<div class="scenarioTitle">Consegna consigliata</div>
<div class="scenarioGoal">
1) Codice prova<br>
2) Report (Export report o comando <span class="kbd">export</span>)<br>
3) Screenshot del terminale (opzionale)
</div>
</div>
</div>
</div>
<div class="note">
Se vuoi farli lavorare "alla cieca", lascia Teacher OFF e non dare hint.
Se vuoi guidarli, abilita hint oppure usa DOCUMENTAZIONE.
</div>
</div>
</div>
</div>
<!-- PAGE: DOC -->
<div class="page" id="page-doc">
<div class="card">
<div class="head">
<h2>📚 Documentazione integrata (comandi &amp; recovery)</h2>
<div class="pill"><span class="focus-badge">Focus</span>&nbsp; mdadm + diagnosi + rebuild</div>
</div>
<div class="content">
<!-- Inner doc tabs -->
<div class="doc-tabs">
<div class="doc-tab active" data-doc="guida">Guida rapida di laboratorio</div>
<div class="doc-tab" data-doc="completa">Guida completa RAID</div>
<div class="doc-tab" data-doc="procedure">Procedure complete</div>
</div>
<!-- PANEL: GUIDA RAPIDA -->
<div class="doc-panel active" id="doc-guida">
<div class="tip-callout">
<div class="tip-icon">💡</div>
<div class="tip-body">
<b>Suggerimento di laboratorio</b><br>
Quando si sospetta un problema RAID, il primo comando da eseguire è quasi sempre
<code>cat /proc/mdstat</code>. Questo permette di capire immediatamente se l'array è
<span class="c-ok">clean</span>, <span class="c-warn">degraded</span> oppure in fase di
<span class="c-warn">rebuild</span>.
</div>
</div>
<p style="color:rgba(255,255,255,.78);font-size:13.5px;line-height:1.6;margin:0 0 20px">
Quando si lavora con sistemi RAID su Linux, la prima abilità che deve sviluppare un amministratore di sistema è la capacità di
<b>capire rapidamente lo stato dell'array</b> e individuare eventuali problemi.
Questa guida rapida descrive il flusso logico che normalmente si segue durante una fase di diagnosi.
</p>
<div class="flow-box" style="margin-bottom:20px">
<div class="flow-label">Esempio RAID10</div>
<div style="font-family:var(--mono);font-size:12.5px;color:rgba(255,255,255,.78);line-height:1.8">
Mirror 1: &nbsp; A &nbsp; B<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; A &nbsp; B<br><br>
Mirror 2: &nbsp; C &nbsp; D<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C &nbsp; D<br><br>
Prima i dischi sono organizzati in mirror, poi i dati sono distribuiti tra le coppie.
</div>
</div>
<div class="step-block">
<div class="step-title"><span class="step-num">1</span> Controllare lo stato dell'array RAID</div>
<div class="cmd-block">cat /proc/mdstat</div>
<p class="step-desc">
Questo comando legge il file virtuale <b>/proc/mdstat</b>, che viene generato dal kernel Linux e contiene informazioni sugli array RAID software gestiti dal sistema.
</p>
<p class="step-desc">Gli stati più comuni che potresti vedere sono:</p>
<ul style="margin:0 0 0 16px;padding:0;list-style:disc">
<li class="step-desc" style="margin-bottom:4px"><span class="c-ok">clean</span> l'array funziona correttamente</li>
<li class="step-desc" style="margin-bottom:4px"><span class="c-warn">degraded</span> uno dei dischi non è più operativo</li>
<li class="step-desc" style="margin-bottom:4px"><span class="c-warn">recovery / rebuild</span> il sistema sta ricostruendo i dati</li>
</ul>
</div>
<hr class="doc-divider">
<div class="step-block">
<div class="step-title"><span class="step-num">2</span> Analizzare i dettagli dell'array</div>
<div class="cmd-block">mdadm --detail /dev/md0</div>
<p class="step-desc">
Il comando <b>mdadm</b> è lo strumento principale utilizzato in Linux per gestire RAID software. Con l'opzione <code>--detail</code> è possibile visualizzare una descrizione completa dell'array.
</p>
<p class="step-desc">
Questo passaggio serve soprattutto per <b>identificare quale disco ha causato il problema</b> e verificare se l'array è ancora in grado di garantire ridondanza.
</p>
</div>
<hr class="doc-divider">
<div class="step-block">
<div class="step-title"><span class="step-num">3</span> Visualizzare la struttura dei dischi</div>
<div class="cmd-block">lsblk</div>
<p class="step-desc">
Il comando <b>lsblk</b> mostra la struttura dei dispositivi di storage presenti nel sistema. Guardando questo output è possibile vedere quali dischi partecipano all'array e verificare se qualche dispositivo risulta mancante.
</p>
</div>
<hr class="doc-divider">
<div class="step-block">
<div class="step-title"><span class="step-num">4</span> Controllare i messaggi del kernel</div>
<div class="cmd-block">dmesg | tail</div>
<p class="step-desc">
Il comando <b>dmesg</b> permette di leggere i messaggi prodotti dal kernel Linux. Questi messaggi spesso contengono informazioni preziose su errori hardware, problemi di lettura o malfunzionamenti dei dischi.
</p>
</div>
<hr class="doc-divider">
<div class="step-block">
<div class="step-title"><span class="step-num">5</span> Verificare la salute del disco</div>
<div class="cmd-block">smartctl -a /dev/sdX</div>
<p class="step-desc">
Il comando <b>smartctl</b> utilizza il sistema SMART integrato nei dischi moderni per raccogliere statistiche interne e individuare segnali di degrado prima che il dispositivo smetta completamente di funzionare.
</p>
</div>
</div>
<!-- ============================================================ -->
<!-- PANEL: GUIDA COMPLETA RAID — AMPLIATA -->
<!-- ============================================================ -->
<div class="doc-panel" id="doc-completa">
<!-- ── COS'È UN RAID ─────────────────────────────────────── -->
<div class="raid-hero">
<h3>📦 Cos'è un RAID?</h3>
<p>
<b>RAID</b> sta per <b>Redundant Array of Independent Disks</b> (in origine "Inexpensive"). È una tecnologia che combina
più dischi fisici in un unico sistema logico, gestito dal sistema operativo o da un controller hardware dedicato.
</p>
<p style="margin-top:10px">
Lo scopo può essere uno o più di questi obiettivi:
aumentare le <b>prestazioni</b> distribuendo le operazioni su più dischi in parallelo,
garantire la <b>ridondanza</b> dei dati (fault tolerance) così che il guasto di uno o più dischi non provochi perdita di informazioni,
oppure entrambe le cose insieme.
</p>
<p style="margin-top:10px">
<b>Attenzione:</b> RAID <em>non è un backup</em>. Protegge da guasti hardware dei dischi, non da cancellazioni accidentali, ransomware o disastri fisici che coinvolgono l'intero server. Un sistema di backup separato è sempre necessario.
</p>
</div>
<!-- ── CONCETTI FONDAMENTALI ──────────────────────────────── -->
<div style="font-size:15px;font-weight:900;letter-spacing:.3px;margin:0 0 14px;color:rgba(255,255,255,.95)">
🧠 Concetti fondamentali
</div>
<div class="concept-grid">
<!-- STRIPING -->
<div class="concept-card stripe">
<h4>⚡ Striping</h4>
<p>I dati vengono suddivisi in blocchi e scritti su più dischi in parallelo. Ogni disco riceve una porzione differente del dato ("stripe").</p>
<p><b>Vantaggio:</b> velocità di lettura e scrittura aumentata, poiché più dischi lavorano contemporaneamente.</p>
<p><b>Rischio:</b> se un disco si guasta, tutti i dati dello stripe sono irrecuperabili (nessuna ridondanza da solo).</p>
<div class="diagram-container" style="margin-top:8px">
<div class="diagram-title">Esempio — 3 dischi in striping</div>
<div class="disk-col-label">
<div>sda</div><div>sdb</div><div>sdc</div>
</div>
<div class="disk-diagram">
<div class="disk-row">
<div class="disk-row-label">S1</div>
<div class="dblock db-a">A1</div>
<div class="dblock db-b">A2</div>
<div class="dblock db-c">A3</div>
</div>
<div class="disk-row">
<div class="disk-row-label">S2</div>
<div class="dblock db-b">B1</div>
<div class="dblock db-c">B2</div>
<div class="dblock db-a">B3</div>
</div>
<div class="disk-row">
<div class="disk-row-label">S3</div>
<div class="dblock db-c">C1</div>
<div class="dblock db-a">C2</div>
<div class="dblock db-b">C3</div>
</div>
</div>
</div>
</div>
<!-- MIRRORING -->
<div class="concept-card mirror">
<h4>🪞 Mirroring</h4>
<p>I dati vengono copiati in modo identico su due o più dischi. Ogni scrittura aggiorna tutti i dischi mirror in modo sincrono.</p>
<p><b>Vantaggio:</b> alta ridondanza — finché almeno un disco del mirror funziona, i dati sono accessibili.</p>
<p><b>Svantaggio:</b> la capacità utile è dimezzata (o ridotta al numero di copie), e le scritture sono più lente.</p>
<div class="diagram-container" style="margin-top:8px">
<div class="diagram-title">Esempio — mirror su 2 dischi</div>
<div class="disk-col-label">
<div>sda</div><div>sdb (copia)</div>
</div>
<div class="disk-diagram">
<div class="disk-row">
<div class="disk-row-label">S1</div>
<div class="dblock db-a">A</div>
<div class="dblock db-a">A</div>
</div>
<div class="disk-row">
<div class="disk-row-label">S2</div>
<div class="dblock db-b">B</div>
<div class="dblock db-b">B</div>
</div>
<div class="disk-row">
<div class="disk-row-label">S3</div>
<div class="dblock db-c">C</div>
<div class="dblock db-c">C</div>
</div>
</div>
</div>
</div>
<!-- PARITÀ -->
<div class="concept-card parity">
<h4>🔢 Parità (XOR)</h4>
<p>Un blocco speciale di parità viene calcolato tramite l'operazione logica <b>XOR</b> dei blocchi dati. Non è una copia, ma un'informazione matematica che permette di ricostruire un blocco mancante.</p>
<p><b>Vantaggio:</b> ridondanza con meno spreco di spazio rispetto al mirroring.</p>
<p><b>Esempio:</b> A XOR B = P. Se perdo A, lo ricostruisco: B XOR P = A.</p>
<div class="diagram-container" style="margin-top:8px">
<div class="diagram-title">Parità distribuita — stripe con P</div>
<div class="disk-col-label">
<div>sda</div><div>sdb</div><div>sdc</div><div>sdd</div>
</div>
<div class="disk-diagram">
<div class="disk-row">
<div class="disk-row-label">S1</div>
<div class="dblock db-a">D1</div>
<div class="dblock db-b">D2</div>
<div class="dblock db-c">D3</div>
<div class="dblock db-p">P</div>
</div>
<div class="disk-row">
<div class="disk-row-label">S2</div>
<div class="dblock db-a">D4</div>
<div class="dblock db-b">D5</div>
<div class="dblock db-p">P</div>
<div class="dblock db-c">D6</div>
</div>
<div class="disk-row">
<div class="disk-row-label">S3</div>
<div class="dblock db-p">P</div>
<div class="dblock db-a">D7</div>
<div class="dblock db-b">D8</div>
<div class="dblock db-c">D9</div>
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── LIVELLI RAID ──────────────────────────────────────── -->
<div style="font-size:15px;font-weight:900;letter-spacing:.3px;margin:0 0 18px;color:rgba(255,255,255,.95)">
📊 Livelli RAID
</div>
<!-- LEGENDA -->
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:rgba(34,211,238,.5)"></div> Dati</div>
<div class="legend-item"><div class="legend-dot" style="background:rgba(255,255,255,.35)"></div> Parità P</div>
<div class="legend-item"><div class="legend-dot" style="background:rgba(167,139,250,.55)"></div> Parità Q (doppia)</div>
<div class="legend-item"><div class="legend-dot" style="background:rgba(74,222,128,.45)"></div> Copia mirror</div>
<div class="legend-item"><div class="legend-dot" style="background:rgba(251,113,133,.4)"></div> Disco guasto / perso</div>
</div>
<!-- ── RAID 0 ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 0</h3>
<span class="raid-level-badge rlb-0">Striping puro</span>
<span class="raid-level-badge rlb-0" style="margin-left:4px">No ridondanza</span>
</div>
<div class="raid-two-col">
<div class="raid-desc-box">
<p>I dati vengono distribuiti equamente su tutti i dischi senza alcuna informazione di parità o copia. Non esiste ridondanza: il guasto di <em>qualsiasi</em> disco causa la perdita totale dell'array.</p>
<p>Viene usato quando si vuole la massima velocità e la perdita dei dati è accettabile (es. cache temporanee, rendering, editing video su workstation con backup separato).</p>
<div class="specs-strip">
<span class="spec-chip">📀 Min dischi: <b>2</b></span>
<span class="spec-chip">💾 Capacità: <b>N × size</b></span>
<span class="spec-chip">🛡 Fault tolerance: <b>0</b></span>
<span class="spec-chip">⚡ Lettura: <b>ottima</b></span>
<span class="spec-chip">✍ Scrittura: <b>ottima</b></span>
</div>
<div class="raid-pros-cons">
<div class="pros-box">
<div class="pc-title">✅ Pro</div>
<ul class="pc-list">
<li>Massime prestazioni in lettura e scrittura</li>
<li>Usa il 100% della capacità dei dischi</li>
<li>Semplicità di configurazione</li>
</ul>
</div>
<div class="cons-box">
<div class="pc-title">❌ Contro</div>
<ul class="pc-list">
<li>Zero ridondanza</li>
<li>1 guasto = dati irrecuperabili</li>
<li>Non adatto a produzione critica</li>
</ul>
</div>
</div>
<div class="usecase-row">
<span class="usecase-pill">🎬 Editing video</span>
<span class="usecase-pill">🎮 Gaming temporaneo</span>
<span class="usecase-pill">🧪 Ambienti di test</span>
</div>
</div>
<div class="diagram-container">
<div class="diagram-title">RAID 0 — 4 dischi</div>
<div class="disk-col-label">
<div>sda</div><div>sdb</div><div>sdc</div><div>sdd</div>
</div>
<div class="disk-diagram">
<div class="disk-row"><div class="disk-row-label">S1</div>
<div class="dblock db-a">A1</div><div class="dblock db-b">A2</div>
<div class="dblock db-c">A3</div><div class="dblock db-d">A4</div>
</div>
<div class="disk-row"><div class="disk-row-label">S2</div>
<div class="dblock db-b">B1</div><div class="dblock db-c">B2</div>
<div class="dblock db-d">B3</div><div class="dblock db-a">B4</div>
</div>
<div class="disk-row"><div class="disk-row-label">S3</div>
<div class="dblock db-c">C1</div><div class="dblock db-d">C2</div>
<div class="dblock db-a">C3</div><div class="dblock db-b">C4</div>
</div>
</div>
<div style="margin-top:10px;font-size:11.5px;color:rgba(251,113,133,.85);font-style:italic">
⚠️ Se sda si guasta → stripe A1, B1, C1 persi → array FAILED
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── RAID 1 ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 1</h3>
<span class="raid-level-badge rlb-1">Mirroring puro</span>
<span class="raid-level-badge rlb-1" style="margin-left:4px">Alta ridondanza</span>
</div>
<div class="raid-two-col">
<div class="raid-desc-box">
<p>Ogni dato viene scritto in modo identico su tutti i dischi dell'array. Se un disco si guasta, i dati sono ancora disponibili su quello rimanente. La lettura può essere parallelizzata su più dischi aumentando le performance in read.</p>
<p>È il livello più semplice e affidabile per proteggere i dati. Il costo è che si sfrutta solo la capacità di un disco, indipendentemente da quanti dischi sono nell'array.</p>
<div class="specs-strip">
<span class="spec-chip">📀 Min dischi: <b>2</b></span>
<span class="spec-chip">💾 Capacità: <b>1 × size</b></span>
<span class="spec-chip">🛡 Fault tolerance: <b>N-1 dischi</b></span>
<span class="spec-chip">⚡ Lettura: <b>buona</b></span>
<span class="spec-chip">✍ Scrittura: <b>normale</b></span>
</div>
<div class="raid-pros-cons">
<div class="pros-box">
<div class="pc-title">✅ Pro</div>
<ul class="pc-list">
<li>Massima ridondanza</li>
<li>Recovery immediato da 1 guasto</li>
<li>Lettura veloce (multi-disk)</li>
</ul>
</div>
<div class="cons-box">
<div class="pc-title">❌ Contro</div>
<ul class="pc-list">
<li>Capacità utile = 1 solo disco</li>
<li>Scrittura rallentata dal sync</li>
<li>Costoso per grandi capacità</li>
</ul>
</div>
</div>
<div class="usecase-row">
<span class="usecase-pill">🗄 Database critici</span>
<span class="usecase-pill">💻 OS di boot</span>
<span class="usecase-pill">🏦 Dati finanziari</span>
</div>
</div>
<div class="diagram-container">
<div class="diagram-title">RAID 1 — 2 dischi</div>
<div class="disk-col-label">
<div>sda (originale)</div><div>sdb (copia)</div>
</div>
<div class="disk-diagram">
<div class="disk-row"><div class="disk-row-label">S1</div>
<div class="dblock db-a">A</div><div class="dblock db-a">A</div>
</div>
<div class="disk-row"><div class="disk-row-label">S2</div>
<div class="dblock db-b">B</div><div class="dblock db-b">B</div>
</div>
<div class="disk-row"><div class="disk-row-label">S3</div>
<div class="dblock db-c">C</div><div class="dblock db-c">C</div>
</div>
<div class="disk-row"><div class="disk-row-label">S4</div>
<div class="dblock db-d">D</div><div class="dblock db-d">D</div>
</div>
</div>
<div style="margin-top:10px;font-size:11.5px;color:rgba(74,222,128,.85)">
✅ sda guasto → sdb ancora operativo → dati accessibili
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── RAID 2 (obsoleto) ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 2 <span class="obsolete-badge">⚠ Obsoleto</span></h3>
<span class="raid-level-badge rlb-2">Hamming ECC</span>
</div>
<div class="raid-desc-box">
<p>RAID 2 utilizza la codifica di correzione degli errori di Hamming (ECC) distribuita su dischi dedicati. I dati vengono distribuiti bit per bit (bit-level striping), e i bit di parità ECC vengono scritti su dischi separati dedicati.</p>
<p>Questo livello era studiato per rilevare e correggere errori sui singoli bit direttamente a livello hardware, prima che i dischi moderni integrassero la correzione ECC internamente. <b>Oggi non viene più utilizzato in pratica</b>: l'ECC è gestita dall'elettronica interna di ogni disco, rendendo RAID 2 inutilmente complesso e costoso.</p>
<div class="server-example">
<div class="se-icon">🏛</div>
<div class="se-text"><b>Contesto storico:</b> RAID 2 era usato nei mainframe degli anni '80-'90 prima che i dischi IDE/SCSI integrassero la correzione errori. Richiedeva da 3 a 10+ dischi solo per la parità ECC.</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── RAID 3 (obsoleto) ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 3 <span class="obsolete-badge">⚠ Obsoleto</span></h3>
<span class="raid-level-badge rlb-3">Parità su disco dedicato</span>
</div>
<div class="raid-desc-box">
<p>RAID 3 usa lo striping a livello di byte (byte-level striping) con un disco dedicato esclusivamente alla parità. Tutti i dati sono distribuiti byte per byte tra i dischi dati, e la parità corrispondente viene sempre scritta sullo stesso disco fisso.</p>
<p>Il problema principale è che il disco di parità diventa un <b>collo di bottiglia</b>: ogni singola operazione di lettura/scrittura richiede un accesso a quel disco, che si deteriora prima degli altri. RAID 3 è stato largamente sostituito da RAID 5, che distribuisce la parità su tutti i dischi.</p>
<div class="specs-strip">
<span class="spec-chip">📀 Min dischi: <b>3</b></span>
<span class="spec-chip">💾 Capacità: <b>(N-1) × size</b></span>
<span class="spec-chip">🛡 Fault tolerance: <b>1 disco</b></span>
</div>
<div class="server-example">
<div class="se-icon">⚙️</div>
<div class="se-text"><b>Differenza con RAID 5:</b> in RAID 3 la parità è sempre sullo stesso disco fisso. In RAID 5 la parità ruota tra tutti i dischi (distributed parity), eliminando il collo di bottiglia.</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── RAID 4 (raro) ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 4 <span class="obsolete-badge">Raro</span></h3>
<span class="raid-level-badge rlb-4">Block-level + parità dedicata</span>
</div>
<div class="raid-desc-box">
<p>Simile a RAID 3 ma con striping a livello di blocco anziché byte. I dati vengono distribuiti a blocchi sui dischi dati, con un disco fisso dedicato alla parità. Permette letture parallele di blocchi indipendenti, ma le scritture soffrono ancora del collo di bottiglia del disco di parità.</p>
<p>RAID 4 è rimasto in uso in alcune implementazioni di storage specializzato (es. vecchi NetApp WAFL) ma oggi è quasi completamente soppiantato da RAID 5.</p>
<div class="specs-strip">
<span class="spec-chip">📀 Min dischi: <b>3</b></span>
<span class="spec-chip">💾 Capacità: <b>(N-1) × size</b></span>
<span class="spec-chip">🛡 Fault tolerance: <b>1 disco</b></span>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── RAID 5 ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 5</h3>
<span class="raid-level-badge rlb-5">Parità distribuita</span>
<span class="raid-level-badge rlb-5" style="margin-left:4px">1 guasto tollerato</span>
</div>
<div class="raid-two-col">
<div class="raid-desc-box">
<p>RAID 5 distribuisce i dati e la parità su tutti i dischi dell'array, eliminando il collo di bottiglia di un disco di parità dedicato. Per ogni stripe, la parità (calcolata con XOR) ruota su un disco diverso.</p>
<p>Con un disco guasto, tutti i dati possono essere ricostruiti matematicamente dagli altri. Questo processo si chiama <b>rebuild</b> e può richiedere ore o giorni per array di grandi dimensioni.</p>
<p><b>Attenzione:</b> durante il rebuild l'array è in stato DEGRADED e un secondo guasto causerebbe la perdita totale dei dati. Per questo motivo si consiglia sempre un disco spare hot-standby.</p>
<div class="specs-strip">
<span class="spec-chip">📀 Min dischi: <b>3</b></span>
<span class="spec-chip">💾 Capacità: <b>(N-1) × size</b></span>
<span class="spec-chip">🛡 Fault tolerance: <b>1 disco</b></span>
<span class="spec-chip">⚡ Lettura: <b>ottima</b></span>
<span class="spec-chip">✍ Scrittura: <b>buona</b></span>
</div>
<div class="raid-pros-cons">
<div class="pros-box">
<div class="pc-title">✅ Pro</div>
<ul class="pc-list">
<li>Buon equilibrio capacità/ridondanza</li>
<li>Lettura molto veloce</li>
<li>Parità distribuita (no bottleneck)</li>
</ul>
</div>
<div class="cons-box">
<div class="pc-title">❌ Contro</div>
<ul class="pc-list">
<li>Solo 1 guasto tollerato</li>
<li>Rebuild lento su dischi grandi</li>
<li>Scrittura random più lenta (write penalty)</li>
</ul>
</div>
</div>
<div class="usecase-row">
<span class="usecase-pill">🏢 File server aziendali</span>
<span class="usecase-pill">🖥 NAS domestici</span>
<span class="usecase-pill">📁 Storage archivi</span>
</div>
</div>
<div class="diagram-container">
<div class="diagram-title">RAID 5 — 4 dischi (parità ruotante)</div>
<div class="disk-col-label">
<div>sda</div><div>sdb</div><div>sdc</div><div>sdd</div>
</div>
<div class="disk-diagram">
<div class="disk-row"><div class="disk-row-label">S1</div>
<div class="dblock db-a">D1</div><div class="dblock db-b">D2</div>
<div class="dblock db-c">D3</div><div class="dblock db-p">P</div>
</div>
<div class="disk-row"><div class="disk-row-label">S2</div>
<div class="dblock db-a">D4</div><div class="dblock db-b">D5</div>
<div class="dblock db-p">P</div><div class="dblock db-c">D6</div>
</div>
<div class="disk-row"><div class="disk-row-label">S3</div>
<div class="dblock db-a">D7</div><div class="dblock db-p">P</div>
<div class="dblock db-b">D8</div><div class="dblock db-c">D9</div>
</div>
<div class="disk-row"><div class="disk-row-label">S4</div>
<div class="dblock db-p">P</div><div class="dblock db-a">D10</div>
<div class="dblock db-b">D11</div><div class="dblock db-c">D12</div>
</div>
</div>
<div style="margin-top:10px;font-size:11.5px;color:rgba(251,191,36,.85)">
⚠️ sdb guasto → DEGRADED, rebuild necessario. 2° guasto = FAILED.
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── RAID 6 ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 6</h3>
<span class="raid-level-badge rlb-6">Doppia parità</span>
<span class="raid-level-badge rlb-6" style="margin-left:4px">2 guasti tollerati</span>
</div>
<div class="raid-two-col">
<div class="raid-desc-box">
<p>RAID 6 estende RAID 5 aggiungendo un secondo blocco di parità indipendente per ogni stripe (parità P e parità Q, calcolate con algoritmi diversi — es. Reed-Solomon). Questo permette di tollerare il guasto simultaneo di due dischi qualsiasi.</p>
<p>È particolarmente indicato per array con molti dischi di grande capacità, dove il rischio di un secondo guasto durante il rebuild (che dura ore o giorni) è statisticamente significativo.</p>
<div class="specs-strip">
<span class="spec-chip">📀 Min dischi: <b>4</b></span>
<span class="spec-chip">💾 Capacità: <b>(N-2) × size</b></span>
<span class="spec-chip">🛡 Fault tolerance: <b>2 dischi</b></span>
<span class="spec-chip">⚡ Lettura: <b>ottima</b></span>
<span class="spec-chip">✍ Scrittura: <b>più lenta di RAID5</b></span>
</div>
<div class="raid-pros-cons">
<div class="pros-box">
<div class="pc-title">✅ Pro</div>
<ul class="pc-list">
<li>Sopravvive a 2 guasti simultanei</li>
<li>Ideale per array grandi</li>
<li>Standard nei NAS enterprise</li>
</ul>
</div>
<div class="cons-box">
<div class="pc-title">❌ Contro</div>
<ul class="pc-list">
<li>Perdi 2 dischi di capacità</li>
<li>Scrittura più lenta (doppia parità)</li>
<li>Più CPU/controller necessario</li>
</ul>
</div>
</div>
<div class="usecase-row">
<span class="usecase-pill">🏭 Storage enterprise</span>
<span class="usecase-pill">☁️ Cloud storage</span>
<span class="usecase-pill">📊 Data center</span>
<span class="usecase-pill">🗃 Archivi a lungo termine</span>
</div>
</div>
<div class="diagram-container">
<div class="diagram-title">RAID 6 — 5 dischi (P e Q ruotanti)</div>
<div class="disk-col-label">
<div>sda</div><div>sdb</div><div>sdc</div><div>sdd</div><div>sde</div>
</div>
<div class="disk-diagram">
<div class="disk-row"><div class="disk-row-label">S1</div>
<div class="dblock db-a">D1</div><div class="dblock db-b">D2</div>
<div class="dblock db-c">D3</div><div class="dblock db-p">P</div>
<div class="dblock db-pp">Q</div>
</div>
<div class="disk-row"><div class="disk-row-label">S2</div>
<div class="dblock db-a">D4</div><div class="dblock db-b">D5</div>
<div class="dblock db-p">P</div><div class="dblock db-pp">Q</div>
<div class="dblock db-c">D6</div>
</div>
<div class="disk-row"><div class="disk-row-label">S3</div>
<div class="dblock db-a">D7</div><div class="dblock db-p">P</div>
<div class="dblock db-pp">Q</div><div class="dblock db-b">D8</div>
<div class="dblock db-c">D9</div>
</div>
</div>
<div style="margin-top:10px;font-size:11.5px;color:rgba(74,222,128,.85)">
✅ 2 dischi qualsiasi guasti → DEGRADED ma operativo → rebuild possibile
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── RAID 10 ── -->
<div class="raid-section">
<div class="raid-section-header">
<h3 class="raid-section-title">RAID 10</h3>
<span class="raid-level-badge rlb-10">Stripe di mirror</span>
<span class="raid-level-badge rlb-10" style="margin-left:4px">1 per coppia</span>
</div>
<div class="raid-two-col">
<div class="raid-desc-box">
<p>RAID 10 (o RAID 1+0) combina mirroring e striping. I dischi vengono prima organizzati in coppie mirror, poi le coppie vengono messe in stripe. Il risultato è un array ad altissime prestazioni con ridondanza garantita per ogni coppia.</p>
<p>Un guasto è tollerato per ciascuna coppia mirror (fino a N/2 dischi, ma mai entrambi della stessa coppia). Se entrambi i dischi di una coppia mirror si guastano, l'array è FAILED.</p>
<p>È il livello preferito per database ad alto I/O e applicazioni che richiedono sia velocità che affidabilità, quando il costo di avere metà capacità non è un problema.</p>
<div class="specs-strip">
<span class="spec-chip">📀 Min dischi: <b>4 (pari)</b></span>
<span class="spec-chip">💾 Capacità: <b>(N/2) × size</b></span>
<span class="spec-chip">🛡 Fault tolerance: <b>1 per coppia</b></span>
<span class="spec-chip">⚡ Lettura: <b>eccellente</b></span>
<span class="spec-chip">✍ Scrittura: <b>eccellente</b></span>
</div>
<div class="raid-pros-cons">
<div class="pros-box">
<div class="pc-title">✅ Pro</div>
<ul class="pc-list">
<li>Prestazioni top in lettura e scrittura</li>
<li>Rebuild più rapido (solo mirror)</li>
<li>Ideale per database SQL</li>
</ul>
</div>
<div class="cons-box">
<div class="pc-title">❌ Contro</div>
<ul class="pc-list">
<li>Usa solo il 50% della capacità</li>
<li>Costoso (servono 4+ dischi)</li>
<li>Mirror pair perso = FAILED</li>
</ul>
</div>
</div>
<div class="usecase-row">
<span class="usecase-pill">🗄 MySQL / PostgreSQL</span>
<span class="usecase-pill">🚀 High-performance apps</span>
<span class="usecase-pill">💼 Virtualizzazione</span>
<span class="usecase-pill">🏥 Sistemi critici H24</span>
</div>
</div>
<div class="diagram-container">
<div class="diagram-title">RAID 10 — 4 dischi (2 mirror + stripe)</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<div>
<div style="font-size:10px;color:rgba(74,222,128,.7);font-family:var(--mono);margin-bottom:4px;text-align:center">Mirror 1</div>
<div class="disk-col-label">
<div>sda</div><div>sdb</div>
</div>
<div class="disk-diagram">
<div class="disk-row"><div class="disk-row-label">S1</div>
<div class="dblock db-a">A</div><div class="dblock db-a">A</div>
</div>
<div class="disk-row"><div class="disk-row-label">S2</div>
<div class="dblock db-b">B</div><div class="dblock db-b">B</div>
</div>
<div class="disk-row"><div class="disk-row-label">S3</div>
<div class="dblock db-c">C</div><div class="dblock db-c">C</div>
</div>
</div>
</div>
<div>
<div style="font-size:10px;color:rgba(96,165,250,.7);font-family:var(--mono);margin-bottom:4px;text-align:center">Mirror 2</div>
<div class="disk-col-label">
<div>sdc</div><div>sdd</div>
</div>
<div class="disk-diagram">
<div class="disk-row"><div class="disk-row-label">S1</div>
<div class="dblock db-d">D</div><div class="dblock db-d">D</div>
</div>
<div class="disk-row"><div class="disk-row-label">S2</div>
<div class="dblock db-e">E</div><div class="dblock db-e">E</div>
</div>
<div class="disk-row"><div class="disk-row-label">S3</div>
<div class="dblock db-a">F</div><div class="dblock db-a">F</div>
</div>
</div>
</div>
</div>
<div style="margin-top:10px;font-size:11px;color:rgba(255,255,255,.5);line-height:1.5">
Stripe: Mirror1 + Mirror2 in parallelo.<br>
<span style="color:rgba(74,222,128,.75)">✅ sda guasto → sdb tiene il mirror</span><br>
<span style="color:rgba(251,113,133,.75)">❌ sda+sdb guasti → mirror 1 perso → FAILED</span>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- ── TABELLA DI CONFRONTO ── -->
<div style="font-size:15px;font-weight:900;letter-spacing:.3px;margin:0 0 14px;color:rgba(255,255,255,.95)">
📋 Confronto tra i livelli RAID
</div>
<div style="overflow-x:auto;border-radius:16px;border:1px solid rgba(255,255,255,.10)">
<table class="comparison-table">
<thead>
<tr>
<th>Livello</th>
<th>Min dischi</th>
<th>Capacità utile</th>
<th>Fault tolerance</th>
<th>Lettura</th>
<th>Scrittura</th>
<th>Uso tipico</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<tr>
<td>RAID 0</td>
<td>2</td>
<td>N × size</td>
<td class="ct-bad">0 dischi</td>
<td class="ct-ok">Eccellente</td>
<td class="ct-ok">Eccellente</td>
<td>Cache, editing</td>
<td class="ct-bad">No backup = disastro</td>
</tr>
<tr>
<td>RAID 1</td>
<td>2</td>
<td>1 × size</td>
<td class="ct-ok">N-1 dischi</td>
<td class="ct-ok">Buona</td>
<td class="ct-warn">Normale</td>
<td>OS, database</td>
<td>Costoso per TB</td>
</tr>
<tr>
<td>RAID 2</td>
<td>3+</td>
<td>Variabile</td>
<td class="ct-warn">1 disco</td>
<td class="ct-warn">Media</td>
<td class="ct-warn">Media</td>
<td class="ct-muted">Obsoleto</td>
<td class="ct-muted">Solo teorico</td>
</tr>
<tr>
<td>RAID 3</td>
<td>3</td>
<td>(N-1) × size</td>
<td class="ct-warn">1 disco</td>
<td class="ct-warn">Buona seq.</td>
<td class="ct-bad">Bottleneck P</td>
<td class="ct-muted">Obsoleto</td>
<td class="ct-muted">Parità fissa</td>
</tr>
<tr>
<td>RAID 4</td>
<td>3</td>
<td>(N-1) × size</td>
<td class="ct-warn">1 disco</td>
<td class="ct-warn">Buona</td>
<td class="ct-bad">Bottleneck P</td>
<td class="ct-muted">Raro</td>
<td class="ct-muted">Sostituito da RAID5</td>
</tr>
<tr>
<td>RAID 5</td>
<td>3</td>
<td>(N-1) × size</td>
<td class="ct-warn">1 disco</td>
<td class="ct-ok">Eccellente</td>
<td class="ct-warn">Buona</td>
<td>NAS, file server</td>
<td>Standard di fatto</td>
</tr>
<tr>
<td>RAID 6</td>
<td>4</td>
<td>(N-2) × size</td>
<td class="ct-ok">2 dischi</td>
<td class="ct-ok">Eccellente</td>
<td class="ct-warn">Più lenta di R5</td>
<td>Enterprise, cloud</td>
<td>Doppia parità P+Q</td>
</tr>
<tr>
<td>RAID 10</td>
<td>4 (pari)</td>
<td>(N/2) × size</td>
<td class="ct-ok">1 per coppia</td>
<td class="ct-ok">Eccellente</td>
<td class="ct-ok">Eccellente</td>
<td>DB, virtualiz.</td>
<td>Mirror pair perso = FAILED</td>
</tr>
</tbody>
</table>
</div>
<div class="tip-callout" style="margin-top:20px">
<div class="tip-icon">🎯</div>
<div class="tip-body">
<b>Come scegliere il livello giusto?</b><br>
Hai bisogno di <b>massima velocità</b> e hai un backup? → <b>RAID 0</b>.<br>
Hai 2 dischi e vuoi semplice ridondanza? → <b>RAID 1</b>.<br>
Hai 3-6 dischi e vuoi il bilanciamento tipico per un NAS? → <b>RAID 5</b>.<br>
Hai array grandi (8+ dischi) e temi il rebuild lungo? → <b>RAID 6</b>.<br>
Hai budget, servono prestazioni max E ridondanza per un database critico? → <b>RAID 10</b>.
</div>
</div>
<!-- Errori non fatali -->
<hr class="doc-divider">
<div class="step-block">
<div class="step-title" style="margin-bottom:12px">Errori non fatali — CRC / Overheat / Slow</div>
<div class="flow-box">
<div class="flow-label">Tipi di errore e azioni consigliate</div>
<div style="display:flex;flex-direction:column;gap:10px;margin-top:4px">
<div style="font-size:13px;color:rgba(255,255,255,.82)">
<b style="color:rgba(167,139,250,.95)">CRC errors</b> — Spesso indica un problema di cavo o connessione SATA, non necessariamente il disco. Prima azione: sostituire il cavo e verificare il connettore.
</div>
<div style="font-size:13px;color:rgba(255,255,255,.82)">
<b style="color:var(--warn)">Overheat</b> — Temperatura critica: rischio guasto imminente. Migliorare il raffreddamento, pianificare sostituzione preventiva.
</div>
<div style="font-size:13px;color:rgba(255,255,255,.82)">
<b style="color:var(--info)">Slow</b> — Disco lento: degrada le prestazioni dell'intero array. Monitorare con <code>smartctl</code> e pianificare sostituzione.
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<div class="step-block">
<div class="step-title" style="margin-bottom:12px">Filesystem su RAID (simulato)</div>
<div class="cmd-block">mkfs.ext4 /dev/md0</div>
<div class="cmd-block">mount /dev/md0 /mnt</div>
<p class="step-desc">Una volta montato: <code>ls /mnt</code>, <code>touch /mnt/file</code>, <code>echo "testo" &gt; /mnt/file</code>, <code>cat /mnt/file</code>, <code>rm /mnt/file</code>, <code>df -h</code>.</p>
</div>
</div>
<!-- END PANEL: GUIDA COMPLETA RAID -->
<!-- PANEL: PROCEDURE COMPLETE -->
<div class="doc-panel" id="doc-procedure">
<!-- Inner nav for procedure sections -->
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px" id="proc-nav">
<button class="proc-nav-btn active" data-proc="diagnosi" onclick="showProc('diagnosi',this)">🔍 5.1 Diagnosi RAID</button>
<button class="proc-nav-btn" data-proc="recovery" onclick="showProc('recovery',this)">🔧 5.2 Recovery RAID</button>
<button class="proc-nav-btn" data-proc="errori" onclick="showProc('errori',this)">⚠️ 5.3 Errori non fatali</button>
<button class="proc-nav-btn" data-proc="filesystem" onclick="showProc('filesystem',this)">💾 5.4 Filesystem su RAID</button>
</div>
<!-- ─────────────────────────────────────────────────────── -->
<!-- 5.1 DIAGNOSI RAID -->
<!-- ─────────────────────────────────────────────────────── -->
<div class="proc-section active" id="proc-diagnosi">
<div class="raid-hero" style="margin-bottom:20px">
<h3>🔍 5.1 Diagnosi RAID</h3>
<p>La diagnosi è il primo passo obbligatorio davanti a qualsiasi problema. Prima di toccare qualsiasi cosa, bisogna capire <b>cos'è successo</b>, <b>quale disco è coinvolto</b> e <b>qual è lo stato attuale dell'array</b>. Usare sempre i comandi in questo ordine.</p>
</div>
<!-- CMD 1: cat /proc/mdstat -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">01</span>
<code class="cmd-main">cat /proc/mdstat</code>
</div>
<span class="cmd-badge cmd-badge-diag">Diagnosi</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Legge il file virtuale <b>/proc/mdstat</b> generato in tempo reale dal kernel Linux. Contiene lo stato di tutti gli array RAID software attivi nel sistema. È il <em>primo</em> comando da eseguire in qualsiasi situazione.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Sempre come primo passo: all'avvio del turno, dopo un allarme, dopo una modifica all'array, durante il rebuild per monitorare l'avanzamento.</p>
<div class="cmd-section-label" style="margin-top:10px">Cosa guardare</div>
<ul class="cmd-list">
<li>Lo stato dopo <code>status=</code>: <span class="c-ok">OK</span>, <span class="c-warn">DEGRADED</span>, <span class="c-bad">FAILED</span></li>
<li>I tag tra parentesi quadre: <code>[U]</code> = disco attivo, <code>[_]</code> = disco mancante, <code>[R]</code> = disco in rebuild</li>
<li>La riga <code>recovery =</code> con la percentuale durante il rebuild</li>
</ul>
</div>
<div>
<div class="cmd-section-label">Output tipico — array OK</div>
<div class="cmd-output ok-output">
md0 : active raid5 sda[U] sdb[U] sdc[U] sdd[U]
status=<span style="color:var(--ok)">OK</span> blocks=3000GB [ UUUU ]
</div>
<div class="cmd-section-label" style="margin-top:10px">Output tipico — DEGRADED</div>
<div class="cmd-output warn-output">
md0 : active raid5 sda[U] sdb[F] sdc[U] sdd[U]
status=<span style="color:var(--warn)">DEGRADED</span> blocks=3000GB [ U_UU ]
</div>
<div class="cmd-section-label" style="margin-top:10px">Output tipico — rebuild in corso</div>
<div class="cmd-output info-output">
md0 : active raid5 sda[U] sdb[R] sdc[U] sdd[U]
status=<span style="color:var(--warn)">DEGRADED</span> [ U.UU ]
[==>.................] recovery = 12.3% (ETA ~ 180s)
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
md0 : active raid5 sda[F] sdb[F] sdc[U] sdd[U]
status=<span style="color:var(--bad)">FAILED</span> [ __UU ]
<span style="color:rgba(251,113,133,.7)">→ 2 dischi guasti in RAID5 = array FAILED (dati irrecuperabili via RAID)</span>
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD 2: mdadm --detail -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">02</span>
<code class="cmd-main">mdadm --detail /dev/md0</code>
</div>
<span class="cmd-badge cmd-badge-diag">Diagnosi</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Mostra informazioni dettagliate sull'array: livello RAID, capacità, stato di ogni disco, numero di guasti, spare presenti e stato del rebuild. È il comando di diagnosi <em>più completo</em>.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Dopo <code>cat /proc/mdstat</code> per identificare esattamente <b>quale disco</b> è in stato FAILED, REBUILDING o SPARE. Necessario prima di qualsiasi operazione di recovery.</p>
<div class="cmd-section-label" style="margin-top:10px">Cosa guardare</div>
<ul class="cmd-list">
<li>Colonna <code>State</code> per ogni disco: <span class="c-ok">active sync</span>, <span class="c-bad">faulty</span>, <span class="c-warn">spare rebuilding</span></li>
<li>Riga <code>State :</code> dell'array: <span class="c-ok">clean</span>, <span class="c-warn">degraded</span></li>
<li>Riga <code>Failed Disks :</code> e <code>Spare Disks :</code></li>
<li>Riga <code>Rebuild :</code> con la percentuale se attivo</li>
</ul>
</div>
<div>
<div class="cmd-section-label">Output tipico</div>
<div class="cmd-output warn-output">
/dev/md0:
Raid Level : raid5
Array Size : 3000 GB
Failed Disks : <span style="color:var(--bad)">1</span>
Spare Disks : 0
State : <span style="color:var(--warn)">degraded</span>
Number State Device
0 active sync /dev/sda
1 <span style="color:var(--bad)">faulty</span> /dev/sdb ← disco guasto
2 active sync /dev/sdc
3 active sync /dev/sdd
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">mdadm: cannot open /dev/md0: No such file or directory</span>
→ array non esiste o non è stato creato ancora
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD 3: lsblk -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">03</span>
<code class="cmd-main">lsblk</code>
</div>
<span class="cmd-badge cmd-badge-diag">Diagnosi</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Mostra la struttura ad albero di tutti i dispositivi di blocco (dischi, partizioni, array RAID, volumi LVM). Permette di vedere quali dischi fisici partecipano all'array e quali risultano mancanti o non montati.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Per verificare che tutti i dischi siano visibili al sistema e per controllare se il filesystem è montato. Utile anche per scoprire il nome corretto dei dispositivi (es. <code>sda</code>, <code>sdb</code>…).</p>
<div class="cmd-section-label" style="margin-top:10px">Cosa guardare</div>
<ul class="cmd-list">
<li>La colonna <code>TYPE</code>: <code>disk</code> = disco fisico, <code>raid5</code> = array RAID</li>
<li>La colonna <code>MOUNTPOINT</code>: se l'array è montato e dove</li>
<li>Dischi non elencati = non rilevati dal kernel (guasto hardware grave)</li>
</ul>
</div>
<div>
<div class="cmd-section-label">Output tipico</div>
<div class="cmd-output ok-output">
NAME SIZE TYPE MOUNTPOINT
sda 1000G disk
sdb 1000G disk <span style="color:rgba(251,113,133,.7)">(failed)</span>
sdc 1000G disk
sdd 1000G disk
md0 3000G <span style="color:var(--warn)">raid5</span> /mnt
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)"># sdb non appare nella lista</span>
→ disco fisicamente non rilevato:
controlla cavi SATA, connettori, alimentazione
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD 4: dmesg | tail -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">04</span>
<code class="cmd-main">dmesg | tail</code>
</div>
<span class="cmd-badge cmd-badge-diag">Diagnosi</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Mostra gli ultimi messaggi del buffer del kernel Linux. Il kernel registra in tempo reale tutto ciò che accade: errori di I/O, problemi SATA, errori CRC, temperature, eventi RAID. È la <em>fonte più diretta</em> di informazioni su cosa è andato storto.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Quando un disco risulta FAILED o in stato anomalo, o quando si vuole capire <em>come</em> e <em>quando</em> è avvenuto il guasto. Indispensabile per distinguere un guasto fisico del disco da un problema di cavo/connessione.</p>
<div class="cmd-section-label" style="margin-top:10px">Cosa guardare</div>
<ul class="cmd-list">
<li><code>I/O error</code> su un dispositivo → guasto disco o cavo</li>
<li><code>UDMA CRC error count increased</code> → problema cavo SATA</li>
<li><code>temperature critical</code> → disco in overheat</li>
<li><code>md/raid: md0: Disk failure</code> → RAID ha marcato il disco come guasto</li>
</ul>
</div>
<div>
<div class="cmd-section-label">Output tipico — CRC / cavo</div>
<div class="cmd-output warn-output">
[12345.678] ata2.00: UDMA CRC error count increased
[12346.001] ata2.00: <span style="color:var(--warn)">exception Emask 0x0 SAct 0x0</span>
→ possibile problema cavo SATA su sdb
</div>
<div class="cmd-section-label" style="margin-top:10px">Output tipico — disco guasto</div>
<div class="cmd-output err-output">
[12400.123] <span style="color:var(--bad)">end_request: I/O error</span>, dev sdb, sector 2048
[12400.200] md/raid:md0: <span style="color:var(--bad)">Disk failure on sdb</span>, disabling device.
[12400.210] md/raid:md0: Operation continuing on 3 devices.
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)"># dmesg: read kernel buffer failed: Operation not permitted</span>
→ serve privilegi root: usa sudo dmesg | tail
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD 5: smartctl -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">05</span>
<code class="cmd-main">smartctl -a /dev/sdX</code>
</div>
<span class="cmd-badge cmd-badge-diag">Diagnosi</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Interroga il sistema SMART (<em>Self-Monitoring, Analysis and Reporting Technology</em>) integrato nel disco. Legge attributi interni come settori ricollocati, temperatura, errori CRC, ore di utilizzo e test diagnostici. Permette di prevedere guasti imminenti prima che si manifestino.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Per ogni disco sospetto identificato da <code>mdadm --detail</code> o <code>dmesg</code>. Sostituire <code>sdX</code> con il nome del disco da analizzare (es. <code>sda</code>, <code>sdb</code>…).</p>
<div class="cmd-section-label" style="margin-top:10px">Cosa guardare</div>
<ul class="cmd-list">
<li><b>Reallocated_Sector_Ct</b> &gt; 0 → settori ricollocati: disco che sta cedendo</li>
<li><b>Current_Pending_Sector</b> &gt; 0 → settori in attesa di ricollocazione</li>
<li><b>UDMA_CRC_Error_Count</b> &gt; 0 → errori di trasmissione (spesso cavo)</li>
<li><b>Temperature_Celsius</b> &gt; 55°C → overheat critico</li>
<li>Riga <code>SMART overall-health</code>: <span class="c-ok">PASSED</span> o <span class="c-bad">FAILED!</span></li>
</ul>
</div>
<div>
<div class="cmd-section-label">Output tipico — disco guasto</div>
<div class="cmd-output err-output">
SMART overall-health: <span style="color:var(--bad)">FAILED!</span>
ID# ATTRIBUTE_NAME RAW_VALUE
5 Reallocated_Sector_Ct <span style="color:var(--bad)">214</span>
197 Current_Pending_Sector <span style="color:var(--bad)">18</span>
199 UDMA_CRC_Error_Count 0
194 Temperature_Celsius 38
<span style="color:rgba(251,113,133,.6)">→ disco con molti settori ricollocati: sostituire subito</span>
</div>
<div class="cmd-section-label" style="margin-top:10px">Output tipico — CRC (cavo)</div>
<div class="cmd-output warn-output">
SMART overall-health: <span style="color:var(--ok)">PASSED</span> (but CRC errors)
ID# ATTRIBUTE_NAME RAW_VALUE
5 Reallocated_Sector_Ct 0
199 UDMA_CRC_Error_Count <span style="color:var(--warn)">24</span>
194 Temperature_Celsius 36
<span style="color:rgba(251,191,36,.6)">→ disco probabilmente OK, controllare cavo SATA</span>
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">smartctl: cannot open /dev/sdb: No such device</span>
→ disco non rilevato dal kernel: problema hardware
</div>
</div>
</div>
</div>
</div>
</div><!-- end proc-diagnosi -->
<!-- ─────────────────────────────────────────────────────── -->
<!-- 5.2 RECOVERY RAID -->
<!-- ─────────────────────────────────────────────────────── -->
<div class="proc-section" id="proc-recovery">
<div class="raid-hero" style="margin-bottom:20px">
<h3>🔧 5.2 Recovery RAID</h3>
<p>Il recovery si esegue solo dopo aver completato la diagnosi. L'ordine delle operazioni è preciso e non va invertito: <b>prima rimuovere il disco guasto</b>, poi aggiungere il nuovo, poi avviare il rebuild. Monitorare sempre fino al completamento.</p>
</div>
<!-- CMD: mdadm --fail -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">R1</span>
<code class="cmd-main">mdadm /dev/md0 --fail /dev/sdX</code>
&nbsp;<span style="font-size:11px;color:rgba(255,255,255,.40);font-family:var(--mono)">(o: mdadm --fail /dev/md0 /dev/sdX)</span>
</div>
<span class="cmd-badge cmd-badge-recovery">Recovery</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Forza il kernel a marcare un disco come <b>FAILED</b> all'interno dell'array RAID. Questo non rimuove il disco, ma lo disabilita logicamente. L'array entra in stato DEGRADED.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Quando un disco mostra segni di degrado (overheat, CRC, slow) ma <em>non è ancora entrato in FAILED automaticamente</em>. Permette di controllare la situazione prima della rimozione.</p>
<div class="cmd-section-label" style="margin-top:10px">Nota</div>
<p class="cmd-desc">Se il disco è già in stato FAILED (marcato automaticamente dal kernel), questo comando non è necessario. Procedere direttamente con <code>--remove</code>.</p>
</div>
<div>
<div class="cmd-section-label">Esempio</div>
<div class="cmd-output warn-output">
$ mdadm /dev/md0 --fail /dev/sdb
<span style="color:var(--warn)">mdadm: set /dev/sdb faulty in /dev/md0</span>
→ sdb marcato FAILED, array in DEGRADED
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">mdadm: Cannot set device faulty: Device or resource busy</span>
→ rebuild in corso, attendere il completamento
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD: mdadm --remove -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">R2</span>
<code class="cmd-main">mdadm /dev/md0 --remove /dev/sdX</code>
&nbsp;<span style="font-size:11px;color:rgba(255,255,255,.40);font-family:var(--mono)">(o: mdadm --remove /dev/md0 /dev/sdX)</span>
</div>
<span class="cmd-badge cmd-badge-recovery">Recovery</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Rimuove logicamente un disco FAILED dall'array RAID. Dopo questo comando il disco è in stato REMOVED e può essere fisicamente sostituito. L'array rimane DEGRADED.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Solo dopo che il disco è in stato FAILED (automatico o forzato con <code>--fail</code>). Non è possibile rimuovere un disco attivo senza prima marcarlo come guasto.</p>
<div class="cmd-section-label" style="margin-top:10px">Cosa fare dopo</div>
<p class="cmd-desc">Sostituire fisicamente il disco (o usarne uno nuovo), poi eseguire <code>--add</code> per inserire il nuovo disco come spare e avviare il rebuild.</p>
</div>
<div>
<div class="cmd-section-label">Esempio</div>
<div class="cmd-output warn-output">
$ mdadm /dev/md0 --remove /dev/sdb
<span style="color:var(--warn)">mdadm: hot removed /dev/sdb from /dev/md0</span>
→ sdb rimosso, pronto per sostituzione fisica
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">mdadm: Cannot remove non-faulty device /dev/sdb</span>
→ il disco non è in stato FAILED
usa prima: mdadm /dev/md0 --fail /dev/sdb
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD: mdadm --add -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">R3</span>
<code class="cmd-main">mdadm /dev/md0 --add /dev/sdY</code>
&nbsp;<span style="font-size:11px;color:rgba(255,255,255,.40);font-family:var(--mono)">(o: mdadm --add /dev/md0 /dev/sdY)</span>
</div>
<span class="cmd-badge cmd-badge-recovery">Recovery</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Aggiunge un nuovo disco all'array come <b>spare</b>. Se l'array è in stato DEGRADED, il rebuild può partire automaticamente o può essere avviato manualmente. Il disco deve avere dimensione <em>uguale o maggiore</em> al disco originale.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Dopo aver rimosso il disco guasto e inserito fisicamente il nuovo. Il disco nuovo (<code>sdY</code>) deve essere un disco vuoto, non formattato, non parte di altri array.</p>
<div class="cmd-section-label" style="margin-top:10px">Attenzione</div>
<p class="cmd-desc">Se il nuovo disco è più piccolo del disco originale, il rebuild fallirà con un errore di dimensione. Verificare sempre la capacità con <code>lsblk</code> prima di procedere.</p>
</div>
<div>
<div class="cmd-section-label">Esempio</div>
<div class="cmd-output ok-output">
$ mdadm /dev/md0 --add /dev/sde
<span style="color:var(--ok)">mdadm: added /dev/sde as spare to /dev/md0</span>
→ sde aggiunto come SPARE, rebuild avviato
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore: spare troppo piccolo</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">mdadm: /dev/sde: device too small (500GB) for array</span>
<span style="color:rgba(251,113,133,.7)"> minimum size required: 1000GB</span>
→ usa un disco uguale o più grande di 1000GB
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore: disco già presente</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">mdadm: /dev/sde is already a member of /dev/md0</span>
→ il disco è già nell'array
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD: watch cat /proc/mdstat -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">R4</span>
<code class="cmd-main">watch cat /proc/mdstat</code>
&nbsp;<span style="font-size:11px;padding:2px 8px;border-radius:999px;background:rgba(251,113,133,.15);border:1px solid rgba(251,113,133,.35);color:rgba(251,113,133,.9)">⚠ non disponibile nel simulatore</span>
</div>
<span class="cmd-badge cmd-badge-monitor">Linux reale</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa (su Linux reale)</div>
<p class="cmd-desc">Il comando <b>watch</b> esegue ripetutamente un comando (ogni 2 secondi per default) e aggiorna l'output a schermo. Usato con <code>cat /proc/mdstat</code> crea un monitor in tempo reale del rebuild su un sistema Linux reale, mostrando la percentuale di avanzamento e l'ETA aggiornati ogni 2 secondi.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa (Linux reale)</div>
<p class="cmd-desc">Durante un rebuild attivo su un vero server Linux, per monitorare l'avanzamento senza dover rieseguire manualmente il comando ogni volta. Si esce con <kbd>Ctrl+C</kbd>.</p>
<div class="cmd-section-label" style="margin-top:10px;color:rgba(251,191,36,.85)">⚠ Nel simulatore</div>
<p class="cmd-desc" style="color:rgba(251,191,36,.85)">Il comando <code>watch</code> <b>non è supportato</b> nel terminale simulato. Per monitorare il rebuild, usare ripetutamente <code>cat /proc/mdstat</code>. La barra di avanzamento del rebuild è visibile anche nella dashboard in tempo reale.</p>
</div>
<div>
<div class="cmd-section-label">Output durante rebuild (Linux reale)</div>
<div class="cmd-output warn-output">
md0 : active raid5 sda[U] sde[R] sdc[U] sdd[U]
status=<span style="color:var(--warn)">DEGRADED</span> [ U.UU ]
[=====>.............] recovery = 28.4%
finish=180s speed=55000K/sec
</div>
<div class="cmd-section-label" style="margin-top:10px">Output a rebuild completato (Linux reale)</div>
<div class="cmd-output ok-output">
md0 : active raid5 sda[U] sde[U] sdc[U] sdd[U]
status=<span style="color:var(--ok)">OK</span> blocks=3000GB [ UUUU ]
<span style="color:rgba(74,222,128,.7)">→ rebuild completato, array tornato clean ✅</span>
</div>
<div class="cmd-section-label" style="margin-top:10px">Alternativa nel simulatore</div>
<div class="cmd-output info-output">
<span style="color:var(--info)">cat /proc/mdstat</span> ← ripetere manualmente
<span style="color:rgba(255,255,255,.45)">La dashboard aggiorna la barra % in tempo reale</span>
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- Sequenze complete -->
<div style="font-size:15px;font-weight:900;letter-spacing:.3px;margin:0 0 14px;color:rgba(255,255,255,.95)">
📋 Sequenze di recovery complete
</div>
<div class="raid-two-col" style="margin-bottom:16px">
<div class="flow-box">
<div class="flow-label">RAID5 / RAID1 — 1 disco guasto → OK</div>
<div class="flow-cmds">
<div class="flow-cmd">cat /proc/mdstat <span style="color:rgba(255,255,255,.4);font-size:12px">→ DEGRADED</span></div>
<div class="flow-cmd">mdadm --detail /dev/md0 <span style="color:rgba(255,255,255,.4);font-size:12px">→ identifica sdb FAILED</span></div>
<div class="flow-cmd">smartctl -a /dev/sdb <span style="color:rgba(255,255,255,.4);font-size:12px">→ conferma guasto</span></div>
<div class="flow-cmd">mdadm /dev/md0 --remove /dev/sdb</div>
<div class="flow-cmd">mdadm /dev/md0 --add /dev/sde <span style="color:rgba(255,255,255,.4);font-size:12px">→ nuovo disco</span></div>
<div class="flow-cmd">mdadm /dev/md0 --rebuild</div>
<div class="flow-cmd">cat /proc/mdstat <span style="color:rgba(74,222,128,.6);font-size:12px">→ attendi OK</span></div>
</div>
</div>
<div class="flow-box">
<div class="flow-label">RAID6 — 2 dischi guasti → OK</div>
<div class="flow-cmds">
<div class="flow-cmd">cat /proc/mdstat <span style="color:rgba(255,255,255,.4);font-size:12px">→ DEGRADED (2 FAILED)</span></div>
<div class="flow-cmd">mdadm /dev/md0 --remove /dev/sdb</div>
<div class="flow-cmd">mdadm /dev/md0 --add /dev/sdg</div>
<div class="flow-cmd">mdadm /dev/md0 --rebuild <span style="color:rgba(255,255,255,.4);font-size:12px">→ 1° rebuild</span></div>
<div class="flow-cmd">cat /proc/mdstat <span style="color:rgba(255,255,255,.4);font-size:12px">→ attendi OK, poi ripeti per 2° disco</span></div>
<div class="flow-cmd">Ripeti: --remove → --add → --rebuild</div>
<div class="flow-cmd">cat /proc/mdstat <span style="color:rgba(74,222,128,.6);font-size:12px">→ OK finale</span></div>
</div>
</div>
</div>
<div class="flow-box">
<div class="flow-label">Rebuild interrotto (powerfail) → riavvio</div>
<div class="flow-cmds">
<div class="flow-cmd">cat /proc/mdstat <span style="color:rgba(255,255,255,.4);font-size:12px">→ DEGRADED, rebuild interrotto</span></div>
<div class="flow-cmd">mdadm --detail /dev/md0 <span style="color:rgba(255,255,255,.4);font-size:12px">→ verifica stato spare ancora presente</span></div>
<div class="flow-cmd">mdadm /dev/md0 --rebuild <span style="color:rgba(255,255,255,.4);font-size:12px">→ riprende dall'ultimo checkpoint</span></div>
<div class="flow-cmd">cat /proc/mdstat <span style="color:rgba(74,222,128,.6);font-size:12px">→ attendi OK</span></div>
</div>
</div>
<div class="tip-callout" style="margin-top:16px">
<div class="tip-icon">⚠️</div>
<div class="tip-body">
<b>Array FAILED = dati irrecuperabili via RAID.</b><br>
Questo accade con RAID0 (qualsiasi guasto), RAID5 con 2+ guasti, RAID6 con 3+ guasti, RAID10 con mirror pair perso. In questi casi non esiste un comando di recovery: bisogna ripristinare da backup esterno. Nel simulatore: <code>conclude restore da backup</code>.
</div>
</div>
</div><!-- end proc-recovery -->
<!-- ─────────────────────────────────────────────────────── -->
<!-- 5.3 ERRORI NON FATALI -->
<!-- ─────────────────────────────────────────────────────── -->
<div class="proc-section" id="proc-errori">
<div class="raid-hero" style="margin-bottom:20px">
<h3>⚠️ 5.3 Errori non fatali</h3>
<p>Non tutti i problemi richiedono la sostituzione immediata del disco. Alcuni errori sono segnali di avvertimento che permettono di intervenire in modo preventivo. Ignorarli, però, può portare a un guasto definitivo. Capire la differenza è fondamentale.</p>
</div>
<!-- CRC ERRORS -->
<div class="cmd-card" style="border-color:rgba(167,139,250,.25)">
<div class="cmd-card-header" style="border-bottom-color:rgba(167,139,250,.15)">
<div class="cmd-card-title">
<span class="cmd-number" style="background:linear-gradient(135deg,rgba(167,139,250,.5),rgba(167,139,250,.3))">CRC</span>
<span style="font-size:14px;font-weight:900;color:rgba(167,139,250,.95)">CRC Errors — errori di trasmissione dati</span>
</div>
<span class="cmd-badge" style="background:rgba(167,139,250,.14);border-color:rgba(167,139,250,.35);color:rgba(167,139,250,.95)">Non fatale</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cos'è</div>
<p class="cmd-desc">CRC (<em>Cyclic Redundancy Check</em>) è un meccanismo di controllo dell'integrità dei dati trasmessi sul cavo SATA. Un errore CRC significa che un blocco di dati è arrivato corrotto durante il trasferimento tra il disco e il controller. <b>Non necessariamente il disco è guasto.</b></p>
<div class="cmd-section-label" style="margin-top:10px">Cause più frequenti</div>
<ul class="cmd-list">
<li>Cavo SATA danneggiato, piegato o di scarsa qualità</li>
<li>Connettore SATA allentato (disco o scheda madre)</li>
<li>Alimentazione instabile al disco</li>
<li>Solo raramente: problema interno al disco</li>
</ul>
<div class="cmd-section-label" style="margin-top:10px">Procedura consigliata</div>
<ul class="cmd-list">
<li><span class="step-num" style="width:18px;height:18px;font-size:10px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:linear-gradient(135deg,rgba(167,139,250,.5),rgba(34,211,238,.3));margin-right:4px">1</span> Eseguire <code>dmesg | tail</code> e <code>smartctl -a /dev/sdX</code></li>
<li><span class="step-num" style="width:18px;height:18px;font-size:10px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:linear-gradient(135deg,rgba(167,139,250,.5),rgba(34,211,238,.3));margin-right:4px">2</span> Se solo CRC errors alti (Reallocated=0): sostituire prima il cavo SATA</li>
<li><span class="step-num" style="width:18px;height:18px;font-size:10px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:linear-gradient(135deg,rgba(167,139,250,.5),rgba(34,211,238,.3));margin-right:4px">3</span> Verificare con <code>smartctl</code> se il contatore si azzera o continua a crescere</li>
<li><span class="step-num" style="width:18px;height:18px;font-size:10px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:linear-gradient(135deg,rgba(167,139,250,.5),rgba(34,211,238,.3));margin-right:4px">4</span> Se persiste dopo cambio cavo: pianificare sostituzione del disco</li>
</ul>
</div>
<div>
<div class="cmd-section-label">dmesg tipico</div>
<div class="cmd-output warn-output">
[04231.001] ata2.00: <span style="color:rgba(167,139,250,.95)">UDMA CRC error count increased</span>
[04231.100] ata2.00: exception Emask 0x0 SAct
[04231.200] ata2.00: cmd 60/08:00:... failed
→ probabile problema cavo su /dev/sdb
</div>
<div class="cmd-section-label" style="margin-top:10px">smartctl tipico</div>
<div class="cmd-output warn-output">
SMART overall-health: <span style="color:var(--ok)">PASSED</span>
5 Reallocated_Sector_Ct <span style="color:var(--ok)">0</span>
199 UDMA_CRC_Error_Count <span style="color:rgba(167,139,250,.95)">24</span>
194 Temperature_Celsius 35
→ disco OK, solo CRC errors → cambia cavo SATA
</div>
<div class="tip-callout" style="margin-top:12px;border-left-color:rgba(167,139,250,.7);background:rgba(167,139,250,.06)">
<div class="tip-icon">💡</div>
<div class="tip-body"><b>Regola pratica:</b> CRC alto + Reallocated=0 = cavo. CRC alto + Reallocated>50 = disco da sostituire.</div>
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- OVERHEAT -->
<div class="cmd-card" style="border-color:rgba(251,191,36,.25)">
<div class="cmd-card-header" style="border-bottom-color:rgba(251,191,36,.15)">
<div class="cmd-card-title">
<span class="cmd-number" style="background:linear-gradient(135deg,rgba(251,191,36,.5),rgba(251,113,133,.3))">🌡</span>
<span style="font-size:14px;font-weight:900;color:rgba(251,191,36,.95)">Overheat — temperatura critica</span>
</div>
<span class="cmd-badge" style="background:rgba(251,191,36,.12);border-color:rgba(251,191,36,.35);color:var(--warn)">Urgente</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cos'è</div>
<p class="cmd-desc">Un disco funziona in modo ottimale tra 20°C e 50°C. Sopra i 55°C entra in zona di rischio; sopra i 60°C può registrare errori di lettura/scrittura o ridurre la propria affidabilità in modo permanente. L'overheat è un <b>segnale di rischio imminente</b> ma non è ancora un guasto.</p>
<div class="cmd-section-label" style="margin-top:10px">Cause più frequenti</div>
<ul class="cmd-list">
<li>Ventola del case guasta o ostruita</li>
<li>Dischi troppo vicini senza airflow</li>
<li>Temperatura ambiente elevata (estate, locali non climatizzati)</li>
<li>Carico eccessivo su disco in fase di rebuild</li>
</ul>
<div class="cmd-section-label" style="margin-top:10px">Procedura consigliata</div>
<ul class="cmd-list">
<li>Verificare temperatura con <code>smartctl -a /dev/sdX</code> → attributo <code>194 Temperature_Celsius</code></li>
<li>Migliorare il raffreddamento del case (ventole, spaziatura dischi)</li>
<li>Se temperatura &gt;60°C: pianificare sostituzione preventiva <em>prima</em> del guasto</li>
<li>Non avviare rebuild con un disco in overheat: rallenta e rischia di peggiorare</li>
</ul>
</div>
<div>
<div class="cmd-section-label">smartctl — disco in overheat</div>
<div class="cmd-output warn-output">
SMART overall-health: <span style="color:var(--warn)">PASSED (but temperature high)</span>
5 Reallocated_Sector_Ct 0
194 Temperature_Celsius <span style="color:var(--warn)">61</span> ← critico
199 UDMA_CRC_Error_Count 0
→ raffreddare subito, pianificare sostituzione
</div>
<div class="cmd-section-label" style="margin-top:10px">dmesg tipico</div>
<div class="cmd-output warn-output">
[09821.400] ata3.00: <span style="color:var(--warn)">temperature critical</span>
[09821.500] ata3.00: device reported high temperature
→ kernel avverte del rischio termico
</div>
<div class="tip-callout" style="margin-top:12px;border-left-color:rgba(251,191,36,.7);background:rgba(251,191,36,.05)">
<div class="tip-icon"></div>
<div class="tip-body"><b>Soglie di temperatura HDD/SSD:</b><br>
&lt;45°C = ottimale · 45-55°C = accettabile · 55-60°C = attenzione · &gt;60°C = sostituire</div>
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- SLOW DISK -->
<div class="cmd-card" style="border-color:rgba(96,165,250,.25)">
<div class="cmd-card-header" style="border-bottom-color:rgba(96,165,250,.15)">
<div class="cmd-card-title">
<span class="cmd-number" style="background:linear-gradient(135deg,rgba(96,165,250,.5),rgba(34,211,238,.3))">🐢</span>
<span style="font-size:14px;font-weight:900;color:rgba(96,165,250,.95)">Slow disk — disco lento</span>
</div>
<span class="cmd-badge" style="background:rgba(96,165,250,.12);border-color:rgba(96,165,250,.35);color:var(--info)">Da monitorare</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cos'è</div>
<p class="cmd-desc">Un disco lento non ha ancora smesso di funzionare, ma risponde ai comandi in modo più lento del normale. Questo degrada le <b>prestazioni dell'intero array</b>, poiché tutte le operazioni devono aspettare il disco più lento (in RAID, l'array è veloce quanto il disco più lento).</p>
<div class="cmd-section-label" style="margin-top:10px">Cause più frequenti</div>
<ul class="cmd-list">
<li>Settori in fase di ricollocazione (<code>Current_Pending_Sector &gt; 0</code>)</li>
<li>Disco meccanico (HDD) in fase di degrado meccanico</li>
<li>Overheat che limita le prestazioni come misura di protezione</li>
<li>Carico I/O eccessivo combinato con altri processi</li>
</ul>
<div class="cmd-section-label" style="margin-top:10px">Impatto sull'array</div>
<ul class="cmd-list">
<li>IOPS dell'intero array ridotti del 30-50%</li>
<li>Latenze elevate su tutte le operazioni</li>
<li>Se il disco rallenta ulteriormente, il kernel può marcarlo FAILED</li>
</ul>
<div class="cmd-section-label" style="margin-top:10px">Procedura consigliata</div>
<p class="cmd-desc">Monitorare con <code>smartctl</code>, verificare <code>Current_Pending_Sector</code>. Se il contatore cresce: pianificare sostituzione preventiva prima che il disco passi a FAILED.</p>
</div>
<div>
<div class="cmd-section-label">smartctl — disco lento</div>
<div class="cmd-output info-output">
SMART overall-health: <span style="color:var(--info)">PASSED (but performance degraded)</span>
5 Reallocated_Sector_Ct <span style="color:var(--warn)">8</span>
197 Current_Pending_Sector <span style="color:var(--warn)">6</span> ← attenzione
199 UDMA_CRC_Error_Count 2
194 Temperature_Celsius 42
→ settori in pending: monitorare, probabile degrado
</div>
<div class="cmd-section-label" style="margin-top:10px">Impatto su telemetria array</div>
<div class="cmd-output info-output">
IOPS nominali RAID5 (4 dischi): ~1200
IOPS con 1 disco SLOW: ~<span style="color:var(--info)">720</span> (-40%)
IOPS con rebuild attivo: ~<span style="color:var(--warn)">550</span> (-55%)
→ pianificare manutenzione in orario bassa attività
</div>
<div class="tip-callout" style="margin-top:12px;border-left-color:rgba(96,165,250,.7);background:rgba(96,165,250,.05)">
<div class="tip-icon">📊</div>
<div class="tip-body"><b>Current_Pending_Sector</b> è l'attributo più importante: indica settori che il disco non riesce più a leggere correttamente ma non ha ancora sostituito. Se supera 20, il rischio di FAILED cresce rapidamente.</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- end proc-errori -->
<!-- ─────────────────────────────────────────────────────── -->
<!-- 5.4 FILESYSTEM SU RAID -->
<!-- ─────────────────────────────────────────────────────── -->
<div class="proc-section" id="proc-filesystem">
<div class="raid-hero" style="margin-bottom:20px">
<h3>💾 5.4 Filesystem su RAID</h3>
<p>Il RAID gestisce la <b>ridondanza e la distribuzione dei dati</b> a livello fisico, ma da solo non basta per usare un disco. Sopra il RAID bisogna creare un <b>filesystem</b> (es. ext4) e montarlo in una directory. Solo a quel punto è possibile leggere e scrivere file.</p>
</div>
<!-- Schema a strati -->
<div class="flow-box" style="margin-bottom:20px">
<div class="flow-label">Struttura a strati — dal fisico al file</div>
<div style="display:flex;flex-direction:column;gap:4px;font-family:var(--mono);font-size:12.5px">
<div style="padding:8px 12px;border-radius:8px;background:rgba(34,211,238,.12);border:1px solid rgba(34,211,238,.25);color:rgba(34,211,238,.9)">📂 /mnt/data/file.txt &nbsp;&nbsp;&nbsp; ← file visibile all'utente</div>
<div style="padding:4px 20px;color:rgba(255,255,255,.3);font-size:10px">↑ montaggio (mount)</div>
<div style="padding:8px 12px;border-radius:8px;background:rgba(74,222,128,.10);border:1px solid rgba(74,222,128,.22);color:rgba(74,222,128,.9)">📁 Filesystem ext4 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ← gestisce directory, permessi, inode</div>
<div style="padding:4px 20px;color:rgba(255,255,255,.3);font-size:10px">↑ creazione (mkfs.ext4)</div>
<div style="padding:8px 12px;border-radius:8px;background:rgba(167,139,250,.10);border:1px solid rgba(167,139,250,.22);color:rgba(167,139,250,.9)">🔧 /dev/md0 — Array RAID5 &nbsp; ← gestisce ridondanza e striping</div>
<div style="padding:4px 20px;color:rgba(255,255,255,.3);font-size:10px">↑ costruito da</div>
<div style="padding:8px 12px;border-radius:8px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.12);color:rgba(255,255,255,.7)">💿 sda &nbsp; sdb &nbsp; sdc &nbsp; sdd &nbsp;&nbsp;&nbsp;&nbsp; ← dischi fisici</div>
</div>
</div>
<!-- CMD: mkfs.ext4 -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">F1</span>
<code class="cmd-main">mkfs.ext4 /dev/md0</code>
</div>
<span class="cmd-badge cmd-badge-fs">Filesystem</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Crea un filesystem <b>ext4</b> sull'array RAID. Formatta il volume logico <code>/dev/md0</code> scrivendo le strutture dati del filesystem (superblock, inode table, bitmap). Operazione <b>distruttiva</b>: cancella tutti i dati esistenti.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Una sola volta, dopo aver creato l'array RAID per la prima volta. Non va eseguito dopo un rebuild (i dati sono già presenti sul filesystem).</p>
<div class="cmd-section-label" style="margin-top:10px">Prerequisiti</div>
<ul class="cmd-list">
<li>Array RAID creato e in stato OK (non FAILED, non DEGRADED)</li>
<li>Nessun filesystem già presente (o si accetta di sovrascrivere tutto)</li>
</ul>
</div>
<div>
<div class="cmd-section-label">Output tipico</div>
<div class="cmd-output ok-output">
$ mkfs.ext4 /dev/md0
mke2fs 1.47.0
Creating filesystem with 786432 4k blocks...
Allocating group tables: done
Writing inode tables: done
Creating journal: done
Writing superblocks: done
<span style="color:var(--ok)">→ filesystem ext4 creato su /dev/md0 ✅</span>
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">mkfs.ext4: I/O error: volume FAILED</span>
→ array FAILED, impossibile creare filesystem
risolvere prima il problema RAID
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- CMD: mount -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">F2</span>
<code class="cmd-main">mount /dev/md0 /mnt</code>
</div>
<span class="cmd-badge cmd-badge-fs">Filesystem</span>
</div>
<div class="cmd-card-body">
<div class="cmd-two-col">
<div>
<div class="cmd-section-label">Cosa fa</div>
<p class="cmd-desc">Monta il filesystem dell'array RAID nella directory <code>/mnt</code> (o qualsiasi altra directory esistente). Dopo il mount, tutti i file dell'array diventano accessibili tramite quella directory.</p>
<div class="cmd-section-label" style="margin-top:10px">Quando si usa</div>
<p class="cmd-desc">Dopo aver creato il filesystem con <code>mkfs.ext4</code>. Deve essere eseguito ad ogni riavvio del sistema (oppure aggiunto a <code>/etc/fstab</code> per il mount automatico).</p>
<div class="cmd-section-label" style="margin-top:10px">Per smontare</div>
<div class="cmd-block" style="margin:4px 0">umount /mnt</div>
<p class="cmd-desc">Smonta il filesystem in modo sicuro. Attendere che tutte le operazioni di I/O siano concluse prima di smontare.</p>
</div>
<div>
<div class="cmd-section-label">Output tipico</div>
<div class="cmd-output ok-output">
$ mount /dev/md0 /mnt
<span style="color:var(--ok)"># nessun output = successo</span>
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/md0 3.0T 1.1G 2.9T 1% /mnt
<span style="color:rgba(74,222,128,.7)">→ array montato e accessibile ✅</span>
</div>
<div class="cmd-section-label" style="margin-top:10px">Errore tipico</div>
<div class="cmd-output err-output">
<span style="color:rgba(251,113,133,.7)">mount: /mnt: can't read superblock from /dev/md0</span>
→ filesystem non creato: esegui mkfs.ext4 prima
</div>
</div>
</div>
</div>
</div>
<hr class="doc-divider">
<!-- Operazioni su file -->
<div style="font-size:15px;font-weight:900;letter-spacing:.3px;margin:0 0 14px;color:rgba(255,255,255,.95)">
📁 Lettura, scrittura e verifica del volume
</div>
<div class="raid-two-col">
<div>
<!-- df -h -->
<div class="cmd-card" style="margin-bottom:10px">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">F3</span>
<code class="cmd-main">df -h</code>
</div>
<span class="cmd-badge cmd-badge-fs">Verifica</span>
</div>
<div class="cmd-card-body">
<p class="cmd-desc">Mostra l'utilizzo del disco in formato leggibile. Permette di verificare capacità totale, spazio usato e spazio disponibile. Mostra anche il punto di mount.</p>
<div class="cmd-output ok-output" style="margin-top:8px">
Filesystem Size Used Avail Use% Mounted on
/dev/md0 3.0T 48G 2.9T 2% /mnt
</div>
</div>
</div>
<!-- ls -->
<div class="cmd-card" style="margin-bottom:10px">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">F4</span>
<code class="cmd-main">ls /mnt</code>
</div>
<span class="cmd-badge cmd-badge-fs">Lettura</span>
</div>
<div class="cmd-card-body">
<p class="cmd-desc">Lista il contenuto della directory montata. Se restituisce una lista vuota o <code>(vuoto)</code> significa che il filesystem è montato ma non contiene file.</p>
<div class="cmd-output ok-output" style="margin-top:8px">
$ ls /mnt
documento.txt backup.tar logs/
<span style="color:rgba(74,222,128,.6)"># oppure: (vuoto) se appena formattato</span>
</div>
</div>
</div>
</div>
<div>
<!-- touch -->
<div class="cmd-card" style="margin-bottom:10px">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">F5</span>
<code class="cmd-main">touch /mnt/file</code>
</div>
<span class="cmd-badge cmd-badge-fs">Scrittura</span>
</div>
<div class="cmd-card-body">
<p class="cmd-desc">Crea un file vuoto. Test minimale per verificare che il filesystem sia scrivibile. Se fallisce con I/O error, l'array ha un problema.</p>
<div class="cmd-output ok-output" style="margin-top:8px">
$ touch /mnt/test.txt
<span style="color:var(--ok)"># successo = nessun output</span>
</div>
</div>
</div>
<!-- echo + cat + rm -->
<div class="cmd-card">
<div class="cmd-card-header">
<div class="cmd-card-title">
<span class="cmd-number">F6</span>
<code class="cmd-main">echo / cat / rm</code>
</div>
<span class="cmd-badge cmd-badge-fs">Test completo</span>
</div>
<div class="cmd-card-body">
<p class="cmd-desc">Test completo di scrittura, lettura e cancellazione su file. Sequenza consigliata per verificare la funzionalità del volume dopo un rebuild o una riparazione.</p>
<div class="cmd-output ok-output" style="margin-top:8px">
$ echo "test RAID OK" &gt; /mnt/verifica.txt
$ cat /mnt/verifica.txt
<span style="color:var(--ok)">test RAID OK</span>
$ rm /mnt/verifica.txt
<span style="color:rgba(74,222,128,.6)"># volume funzionante ✅</span>
</div>
<div class="cmd-output err-output" style="margin-top:8px">
<span style="color:rgba(251,113,133,.7)">echo: I/O error: volume FAILED</span>
→ array non accessibile, dati non scrivibili
</div>
</div>
</div>
</div>
</div>
<!-- Comandi facoltativi -->
<hr class="doc-divider">
<div style="font-size:15px;font-weight:900;letter-spacing:.3px;margin:0 0 14px;color:rgba(255,255,255,.95)">
🔧 Comandi facoltativi ma utili
</div>
<div class="raid-grid" style="margin:0">
<div class="raid-card">
<span class="tag tag-info">Diagnostica avanzata</span>
<h4><code style="font-size:12px">mdadm --examine /dev/sdX</code></h4>
<p>Legge i metadati RAID scritti sul disco, anche se non è più parte di un array attivo. Utile per recuperare informazioni su array distrutti o disco rimosso accidentalmente.</p>
</div>
<div class="raid-card">
<span class="tag tag-warn">Controllo filesystem</span>
<h4><code style="font-size:12px">fsck /dev/md0</code></h4>
<p>Controlla e ripara il filesystem ext4 sopra l'array RAID. Va eseguito <em>solo con il filesystem smontato</em>. Utile dopo crash di sistema o powerfail durante scrittura.</p>
</div>
<div class="raid-card">
<span class="tag tag-info">Identificazione</span>
<h4><code style="font-size:12px">blkid</code></h4>
<p>Mostra UUID, tipo di filesystem e label di tutti i dispositivi. Utile per identificare il volume RAID nel file <code>/etc/fstab</code> per il mount automatico al boot.</p>
</div>
<div class="raid-card">
<span class="tag tag-info">Layout dischi</span>
<h4><code style="font-size:12px">fdisk -l</code></h4>
<p>Elenca tutti i dischi e le partizioni con dimensioni. Permette di verificare la dimensione esatta di ogni disco prima di usarlo come spare, evitando l'errore "spare too small".</p>
</div>
</div>
<!-- Tabella comandi simulatore -->
<hr class="doc-divider">
<div style="font-size:15px;font-weight:900;letter-spacing:.3px;margin:0 0 14px;color:rgba(255,255,255,.95)">
📋 Tutti i comandi disponibili nel simulatore
</div>
<div style="overflow-x:auto;border-radius:16px;border:1px solid rgba(255,255,255,.10)">
<table class="comparison-table">
<thead>
<tr>
<th>Comando</th>
<th>Categoria</th>
<th>Funzione</th>
<th>Disponibile</th>
</tr>
</thead>
<tbody>
<tr><td>cat /proc/mdstat</td><td class="ct-info">Diagnosi</td><td>Stato array RAID in tempo reale</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mdadm --detail /dev/md0</td><td class="ct-info">Diagnosi</td><td>Dettaglio completo array e dischi</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>lsblk</td><td class="ct-info">Diagnosi</td><td>Struttura dispositivi di blocco</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>dmesg | tail</td><td class="ct-info">Diagnosi</td><td>Messaggi kernel recenti</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>smartctl -a /dev/sdX</td><td class="ct-info">Diagnosi</td><td>Stato SMART del disco</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mdadm /dev/md0 --fail /dev/sdX</td><td class="ct-warn">Recovery</td><td>Marca disco come FAILED</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mdadm /dev/md0 --remove /dev/sdX</td><td class="ct-warn">Recovery</td><td>Rimuove disco FAILED dall'array</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mdadm /dev/md0 --add /dev/sdY</td><td class="ct-warn">Recovery</td><td>Aggiunge nuovo disco come spare</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mdadm /dev/md0 --rebuild</td><td class="ct-warn">Recovery</td><td>Avvia il rebuild dell'array</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mdadm --stop-rebuild /dev/md0</td><td class="ct-warn">Recovery</td><td>Interrompe il rebuild</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>powerfail</td><td class="ct-warn">Simulazione</td><td>Simula blackout, interrompe rebuild</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mkfs.ext4 /dev/md0</td><td class="ct-ok">Filesystem</td><td>Crea filesystem ext4 sull'array</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>mount /dev/md0 /mnt</td><td class="ct-ok">Filesystem</td><td>Monta il filesystem</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>umount /mnt</td><td class="ct-ok">Filesystem</td><td>Smonta il filesystem</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>df -h</td><td class="ct-ok">Filesystem</td><td>Spazio disco utilizzato</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>ls / touch / echo / cat / rm</td><td class="ct-ok">Filesystem</td><td>Operazioni su file</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>blkid</td><td class="ct-info">Extra</td><td>UUID e tipo filesystem</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>fdisk -l</td><td class="ct-info">Extra</td><td>Layout completo dischi</td><td class="ct-ok">✅ Sì</td></tr>
<tr><td>watch cat /proc/mdstat</td><td class="ct-info">Linux reale</td><td>Monitor continuo (Linux reale, ogni 2s)</td><td class="ct-muted">❌ Non nel simulatore — usa cat</td></tr>
<tr><td>mdadm --examine /dev/sdX</td><td class="ct-info">Avanzato</td><td>Metadati RAID su disco</td><td class="ct-muted">❌ Non disponibile</td></tr>
<tr><td>fsck /dev/md0</td><td class="ct-info">Avanzato</td><td>Controllo/riparazione filesystem</td><td class="ct-muted">❌ Non disponibile</td></tr>
</tbody>
</table>
</div>
</div><!-- end proc-filesystem -->
</div><!-- end doc-procedure panel -->
</div>
</div>
</div>
</div>
<script>
(() => {
const $ = (id) => document.getElementById(id);
const clamp = (n,a,b) => Math.max(a, Math.min(b,n));
const pad2 = (n) => String(n).padStart(2,"0");
const ts = () => { const d=new Date(); return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; };
const tabs = [
{tab:$("tab-lab"), page:$("page-lab")},
{tab:$("tab-ex"), page:$("page-ex")},
{tab:$("tab-doc"), page:$("page-doc")},
];
function showPage(which){
tabs.forEach(x => {
const on = (x.tab.id === which);
x.tab.classList.toggle("active", on);
x.page.classList.toggle("active", on);
});
window.scrollTo({top:0, behavior:"smooth"});
}
$("tab-lab").addEventListener("click", ()=>showPage("tab-lab"));
$("tab-ex").addEventListener("click", ()=>showPage("tab-ex"));
$("tab-doc").addEventListener("click", ()=>showPage("tab-doc"));
document.querySelectorAll(".doc-tab").forEach(btn => {
btn.addEventListener("click", () => {
const target = btn.getAttribute("data-doc");
document.querySelectorAll(".doc-tab").forEach(b => b.classList.remove("active"));
document.querySelectorAll(".doc-panel").forEach(p => p.classList.remove("active"));
btn.classList.add("active");
const panel = document.getElementById("doc-" + target);
if (panel) panel.classList.add("active");
});
});
function fmtGB(gb){
if (!isFinite(gb)) return "—";
if (gb >= 1024){ const tb = gb/1024; return tb.toFixed(tb<10?2:1) + " TB"; }
return gb.toFixed(0) + " GB";
}
function tokenize(cmd){
const out=[]; let cur=""; let q=null;
for (let i=0;i<cmd.length;i++){
const ch=cmd[i];
if (q){ if(ch===q){q=null;continue;} cur+=ch; }
else {
if(ch==="\""||ch==="'"){ q=ch; continue; }
if(/\s/.test(ch)){ if(cur){out.push(cur);cur="";} }
else cur+=ch;
}
}
if(cur) out.push(cur);
return out;
}
const DiskState = Object.freeze({
OK:"OK", FAILED:"FAILED", REMOVED:"REMOVED", SPARE:"SPARE", REBUILDING:"REBUILDING",
SLOW:"SLOW", OVERHEAT:"OVERHEAT", CRC:"CRC"
});
const VolState = Object.freeze({ OK:"OK", DEGRADED:"DEGRADED", FAILED:"FAILED" });
let state = {
arrayName: "/dev/md0",
startupTime: Date.now(),
raidLevel: 5,
diskSizeGB: 1000,
disks: [],
spares: [],
rebuild: { active:false, start:0, etaSec:0, progress:0, realSec:0, speedFactor:1 },
fs: { created:false, mountedAt:null, uuid:null, label:null, files: new Map() },
dmesg: [],
score: 0,
hints: 0,
teacher: false,
mode: "LAB",
rebuildCount: 0,
exerciseOn: false,
timer: { on:false, endTs:0, interval:null },
actions: [],
scenario: { name:null, goalHtml:null, solution:[], checkpoints:{}, done:false, startedAt:null, seed:null },
exgen: { code:null, seed:null, scenario:null, goalShort:null, goalLong:null }
};
const raidLevelEl = $("raidLevel");
const diskCountEl = $("diskCount");
const diskSizeEl = $("diskSize");
const pillArrayEl = $("pillArray");
const pillModeEl = $("pillMode");
const lampEl = $("lamp");
const bigStatusEl = $("bigStatus");
const rebuildBannerEl = $("rebuildBanner");
const rbFactorEl = $("rbFactor");
const rbEtaSimEl = $("rbEtaSim");
const rbEtaRealEl = $("rbEtaReal");
const miniStatusEl = $("miniStatus");
const capValueEl = $("capValue");
const capNoteEl = $("capNote");
const ftValueEl = $("ftValue");
const ftNoteEl = $("ftNote");
const diskGridEl = $("diskGrid");
const scenarioTextEl = $("scenarioText");
const parityTextEl = $("parityText");
const tempAvgEl = $("tempAvg");
const iopsEl = $("iops");
const crcEl = $("crc");
const reallocEl = $("realloc");
const timeLeftEl = $("timeLeft");
const scoreEl = $("score");
const hintsEl = $("hints");
const fsChipEl = $("fsChip");
const exerciseChipEl = $("exerciseChip");
const tipChipEl = $("tipChip");
const promptTextEl = $("promptText");
const exerciseCodeEl = $("exerciseCode");
const exScenarioEl = $("exScenario");
const exSeedEl = $("exSeed");
const exGoalShortEl = $("exGoalShort");
const exGoalLongEl = $("exGoalLong");
const termBody = $("termBody");
const termInput = $("termInput");
const btnRun = $("btnRun");
const pillRebuildCountEl = $("pillRebuildCount");
let history = [];
let histIdx = -1;
function termPrint(text, cls="dim"){
const div = document.createElement("div");
div.className = `termLine ${cls}`;
div.textContent = text;
termBody.appendChild(div);
termBody.scrollTop = termBody.scrollHeight;
}
function termEcho(cmd){
const div = document.createElement("div");
div.className = "termLine";
div.textContent = `${promptTextEl.textContent} ${cmd}`;
termBody.appendChild(div);
termBody.scrollTop = termBody.scrollHeight;
}
function clearTerminal(){ termBody.innerHTML=""; }
function pushAction(cmd){ state.actions.push({ t: ts(), cmd }); if (state.actions.length > 400) state.actions.shift(); }
function addScore(points, reason){ state.score += points; scoreEl.textContent = String(state.score); if (reason) termPrint(`+${points} ${reason}`, "ok"); }
function useHint(){ state.hints += 1; hintsEl.textContent = String(state.hints); state.score = Math.max(0, state.score - 3); scoreEl.textContent = String(state.score); }
function pushDmesg(level, msg){ const line = `[${ts()}] ${level.toUpperCase()}: ${msg}`; state.dmesg.push(line); if (state.dmesg.length > 240) state.dmesg.shift(); }
function raidRules(level){ const L=Number(level); switch(L){ case 0: return {minDisks:2,requiresEven:false}; case 1: return {minDisks:2,requiresEven:false}; case 5: return {minDisks:3,requiresEven:false}; case 6: return {minDisks:4,requiresEven:false}; case 10: return {minDisks:4,requiresEven:true}; default: return {minDisks:2,requiresEven:false}; } }
function normalizeDiskCount(n){ n=clamp(parseInt(n||"4",10),2,12); const rules=raidRules(state.raidLevel); n=Math.max(n,rules.minDisks); if(state.raidLevel===10&&n%2===1)n+=1; return clamp(n,2,12); }
function tolerance(level,n){ const L=Number(level); switch(L){ case 0:return 0; case 1:return Math.max(0,n-1); case 5:return 1; case 6:return 2; case 10:return Math.floor(n/2); default:return 0; } }
function capacityGB(level,n,sizeGB){ const L=Number(level); switch(L){ case 0:return n*sizeGB; case 1:return sizeGB; case 5:return(n-1)*sizeGB; case 6:return(n-2)*sizeGB; case 10:return Math.floor(n/2)*sizeGB; default:return 0; } }
function raid10PairFailed(){ for(let i=0;i<state.disks.length;i+=2){ const a=state.disks[i]?.state,b=state.disks[i+1]?.state; const aBad=(a===DiskState.FAILED||a===DiskState.REMOVED),bBad=(b===DiskState.FAILED||b===DiskState.REMOVED); if(aBad&&bBad)return true; } return false; }
function failedCount(){ return state.disks.filter(d=>d.state===DiskState.FAILED).length; }
function removedCount(){ return state.disks.filter(d=>d.state===DiskState.REMOVED).length; }
function degradedSignals(){ return state.disks.some(d=>[DiskState.SLOW,DiskState.OVERHEAT,DiskState.CRC].includes(d.state)); }
function volumeStatus(){
// Solo i dischi FAILED superano la tolleranza → FAILED.
// I dischi REMOVED non contano verso la soglia: l'array rimane DEGRADED
// finché non viene aggiunto uno spare e avviato il rebuild.
// Flusso corretto: fail → DEGRADED → remove → DEGRADED → add → rebuild → OK
const failed = failedCount();
const removed = removedCount();
const degraded = failed > 0 || removed > 0;
if(state.raidLevel===10){
if(raid10PairFailed())return VolState.FAILED;
if(degraded||state.rebuild.active||degradedSignals())return VolState.DEGRADED;
return VolState.OK;
}
const tol=tolerance(state.raidLevel,state.disks.length);
if(failed>tol)return VolState.FAILED;
if(degraded||state.rebuild.active||degradedSignals())return VolState.DEGRADED;
return VolState.OK;
}
function canAccessData(){ return volumeStatus()!==VolState.FAILED; }
function lampClass(vol){ return vol===VolState.OK?"ok":(vol===VolState.DEGRADED?"degraded":"failed"); }
function devForIndex(i){ return `/dev/sd${String.fromCharCode("a".charCodeAt(0)+i)}`; }
function makeDisk(index,sizeGB){ const dev=devForIndex(index); return {id:index,dev,name:`disk${index+1}`,sizeGB,state:DiskState.OK,progress:0,smart:{temp:31+(index%4),realloc:0,pending:0,crc:0,powerOnHours:1200+index*110,model:"SIMDISK 7K2",serial:"SIM"+(100000+index*777)}}; }
function randUUID(){ const hex=()=>Math.floor(Math.random()*0xffffffff).toString(16).padStart(8,"0"); return `${hex()}-${hex().slice(0,4)}-${hex().slice(0,4)}-${hex().slice(0,4)}-${hex()}${hex().slice(0,4)}`; }
function resetFS(){ state.fs.created=false;state.fs.mountedAt=null;state.fs.uuid=null;state.fs.label=null;state.fs.files=new Map(); }
let rebuildTimer=null;
function rebuildEstimateSec(){
const sizeGB=state.diskSizeGB,n=state.disks.length;
let speed=1.6;
if(state.raidLevel===5)speed=1.1;
if(state.raidLevel===6)speed=0.85;
if(state.raidLevel===10)speed=1.25;
if(state.raidLevel===1)speed=1.35;
if(state.raidLevel===0)speed=2.0;
if(state.disks.some(d=>d.state===DiskState.SLOW))speed*=0.65;
if(state.disks.some(d=>d.state===DiskState.OVERHEAT))speed*=0.75;
speed*=(1+Math.log2(Math.max(2,n))*0.12);
const realSec=Math.max(60,Math.round(sizeGB/speed));
// Modalità didattica: cappato a 1015s, calcola fattore di accelerazione
const didacticSec=Math.round(10+Math.random()*5); // 1015s
state.rebuild.realSec=realSec;
state.rebuild.speedFactor=Math.round(realSec/didacticSec);
return didacticSec;
}
function stopRebuild(silent=false){ state.rebuild.active=false;state.rebuild.progress=0; state.disks.forEach(d=>{ if(d.state===DiskState.REBUILDING){d.state=DiskState.FAILED;d.progress=0;} }); if(rebuildTimer){clearInterval(rebuildTimer);rebuildTimer=null;} if(!silent)pushDmesg("warn","md0: rebuild interrupted"); render(); }
function startRebuild(){
if(state.rebuild.active)return termPrint("Rebuild già in corso.","warn");
if(volumeStatus()===VolState.FAILED)return termPrint("Volume FAILED: rebuild non possibile.","err");
const failedIdx=state.disks.findIndex(d=>d.state===DiskState.FAILED||d.state===DiskState.REMOVED);
if(failedIdx<0)return termPrint("Nessun disco FAILED/REMOVED da ricostruire.","warn");
if(!state.spares.length)return termPrint("Nessuno SPARE presente. Usa: mdadm --add /dev/md0 /dev/sdX","err");
const spare=state.spares[0];
if(spare.sizeGB<state.diskSizeGB){ pushDmesg("err",`${spare.dev}: spare too small (${spare.sizeGB}GB < ${state.diskSizeGB}GB)`); return termPrint(`mdadm: spare troppo piccolo (${spare.sizeGB}GB).`,"err"); }
state.disks[failedIdx].state=DiskState.REBUILDING; state.disks[failedIdx].progress=0; state.spares.shift();
state.rebuild.active=true; state.rebuild.start=Date.now(); state.rebuild.etaSec=rebuildEstimateSec(); state.rebuild.progress=0;
const realMin=Math.round(state.rebuild.realSec/60);
const realHr=(state.rebuild.realSec>=3600)?(` = ${(state.rebuild.realSec/3600).toFixed(1)}h`):"";
pushDmesg("info",`md0: rebuild started (ETA ~ ${state.rebuild.etaSec}s — MODALITÀ DIDATTICA x${state.rebuild.speedFactor})`);
termPrint(`mdadm: rebuild avviato.`,"ok");
termPrint(` ⚡ Velocità aumentata di ${state.rebuild.speedFactor}× per uso didattico`,"warn");
termPrint(` ⏱ ETA simulato : ~${state.rebuild.etaSec}s`,"warn");
termPrint(` 🕐 Tempo reale : ~${realMin} min${realHr} (disco ${state.diskSizeGB} GB, RAID${state.raidLevel})`,"warn");
if(state.scenario.name&&!state.scenario.checkpoints.rebuild){ state.scenario.checkpoints.rebuild=true; addScore(6,"(scenario) rebuild avviato."); }
rebuildTimer=setInterval(()=>{
if(!state.rebuild.active)return;
const elapsed=(Date.now()-state.rebuild.start)/1000;
const pct=clamp((elapsed/state.rebuild.etaSec)*100,0,100);
state.rebuild.progress=pct;
state.disks.forEach(d=>{ if(d.state===DiskState.REBUILDING)d.progress=pct; });
if(pct>=100){
state.rebuild.active=false;state.rebuild.progress=0;
state.disks.forEach(d=>{ if(d.state===DiskState.REBUILDING){d.state=DiskState.OK;d.progress=0;d.smart.realloc=0;d.smart.pending=0;d.smart.crc=0;d.smart.temp=33;} });
pushDmesg("info","md0: rebuild completed");
termPrint("mdadm: rebuild completato. ✅","ok");
state.rebuildCount+=1;
const rf=state.rebuild.speedFactor||1, rs=state.rebuild.realSec||0;
const rm=Math.round(rs/60), rh=(rs>=3600)?` = ${(rs/3600).toFixed(1)}h`:"";
termPrint(` Ricorda: nella realtà questa operazione avrebbe richiesto ~${rm} min${rh}`,"warn");
termPrint(` (accelerato di ${rf}× per uso didattico)`,"warn");
if(state.scenario.name&&!state.scenario.checkpoints.ok&&volumeStatus()===VolState.OK){ state.scenario.checkpoints.ok=true; addScore(8,"(scenario) array OK."); }
clearInterval(rebuildTimer);rebuildTimer=null;
}
render();scenarioCheck();
},220);
render();scenarioCheck();
}
function calcTelemetry(){
const temps=state.disks.map(d=>{ let t=d.smart.temp; if(d.state===DiskState.OVERHEAT)t=56+(d.id%3); if(d.state===DiskState.FAILED)t=0; return t; }).filter(t=>t>0);
const avg=temps.length?Math.round(temps.reduce((a,b)=>a+b,0)/temps.length):0;
let iops=1200; if(state.raidLevel===6)iops=980; if(state.raidLevel===10)iops=1400; if(state.raidLevel===1)iops=1100; if(state.raidLevel===0)iops=1650;
if(failedCount()>0)iops=Math.round(iops*0.72); if(state.rebuild.active)iops=Math.round(iops*0.55); if(state.disks.some(d=>d.state===DiskState.SLOW))iops=Math.round(iops*0.6); if(state.disks.some(d=>d.state===DiskState.OVERHEAT))iops=Math.round(iops*0.75);
const crc=state.disks.reduce((a,d)=>a+(d.state===DiskState.CRC?12:0)+d.smart.crc,0);
const realloc=state.disks.reduce((a,d)=>a+d.smart.realloc,0);
return {avg,iops,crc,realloc};
}
function diskVisualClass(st){ if(st===DiskState.OK)return"ok"; if(st===DiskState.FAILED)return"failed"; if(st===DiskState.REMOVED)return"removed"; if(st===DiskState.SPARE)return"spare"; if(st===DiskState.REBUILDING)return"rebuilding"; if(st===DiskState.SLOW)return"slow"; if(st===DiskState.OVERHEAT)return"overheat"; if(st===DiskState.CRC)return"crc"; return"ok"; }
function parityText(){ if(state.raidLevel!==5)return"Disponibile quando RAID5 è selezionato."; return ["Stripe 1: D1 D2 D3 P","Stripe 2: D4 D5 P D6","Stripe 3: D7 P D8 D9","","Idea rebuild (didattica):","se manca un blocco, lo ricostruisci con XOR degli altri + parità."].join("\n"); }
function setTimerMinutes(min){
if(state.timer.interval){clearInterval(state.timer.interval);state.timer.interval=null;}
if(!min){state.timer.on=false;state.timer.endTs=0;render();return;}
state.timer.on=true; state.timer.endTs=Date.now()+min*60*1000;
state.timer.interval=setInterval(()=>{
if(!state.timer.on)return;
const left=state.timer.endTs-Date.now();
if(left<=0){ state.timer.on=false; clearInterval(state.timer.interval);state.timer.interval=null; termPrint("⏱ Tempo scaduto. Verifica conclusa.","warn"); termPrint(`Score finale: ${state.score}`,"info"); }
render();
},250);
render();
}
function createArray(){
state.raidLevel=Number(raidLevelEl.value); state.diskSizeGB=clamp(parseInt(diskSizeEl.value||"1000",10),10,200000);
const n=normalizeDiskCount(diskCountEl.value); diskCountEl.value=String(n);
state.disks=Array.from({length:n},(_,i)=>makeDisk(i,state.diskSizeGB)); state.spares=[];
resetFS(); if(rebuildTimer){clearInterval(rebuildTimer);rebuildTimer=null;}
state.rebuild={active:false,start:0,etaSec:0,progress:0,realSec:0,speedFactor:1}; state.mode="LAB"; state.exerciseOn=false;
state.scenario={name:null,goalHtml:null,solution:[],checkpoints:{},done:false,startedAt:null,seed:null};
state.score=0;state.hints=0;state.actions=[];
scoreEl.textContent="0";hintsEl.textContent="0";
pushDmesg("info",`md0: created RAID${state.raidLevel} with ${n} disks`);
termPrint("Array creato. Suggerimento: 'scenario list' oppure tab ESERCIZI.","ok");
render();
}
function resetDiskStates(){ stopRebuild(true); state.disks.forEach(d=>{d.state=DiskState.OK;d.progress=0;d.smart.temp=31+(d.id%4);d.smart.realloc=0;d.smart.pending=0;d.smart.crc=0;}); state.spares=[]; pushDmesg("info","All disk states reset"); termPrint("Reset stati completato.","ok"); render(); }
function randomFault(){
if(state.rebuild.active)return termPrint("Guasto random bloccato: rebuild in corso.","warn");
const candidates=state.disks.filter(d=>d.state===DiskState.OK);
if(!candidates.length)return termPrint("Nessun disco OK disponibile.","warn");
const d=candidates[Math.floor(Math.random()*candidates.length)];
const r=Math.random();
if(r<0.45){d.state=DiskState.FAILED;d.smart.realloc=120+Math.floor(Math.random()*90);d.smart.pending=10+Math.floor(Math.random()*20);pushDmesg("warn",`${d.dev}: I/O error, disk FAILED (random)`);termPrint(`Guasto: ${d.dev} -> FAILED`,"warn");}
else if(r<0.68){d.state=DiskState.CRC;d.smart.crc+=10+Math.floor(Math.random()*15);pushDmesg("warn",`${d.dev}: UDMA CRC errors detected (random)`);termPrint(`Guasto: ${d.dev} -> CRC errors`,"warn");}
else if(r<0.85){d.state=DiskState.OVERHEAT;d.smart.temp=58+Math.floor(Math.random()*3);pushDmesg("warn",`${d.dev}: temperature critical (random)`);termPrint(`Guasto: ${d.dev} -> OVERHEAT`,"warn");}
else{d.state=DiskState.SLOW;pushDmesg("info",`${d.dev}: device slow responses (random)`);termPrint(`Guasto: ${d.dev} -> SLOW`,"warn");}
render();scenarioCheck();
}
const scenarios = {
raid0_fail:()=>{ createArray();raidLevelEl.value="0";state.raidLevel=0;diskCountEl.value="3";state.disks=Array.from({length:3},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[1].state=DiskState.FAILED;pushDmesg("err","RAID0: single disk failed => array FAILED (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"raid0_fail",goalHtml:`RAID0: un disco è guasto. Obiettivo: diagnosticare che i dati non sono recuperabili via RAID (<b>FAILED</b>) e concludere con <code>conclude restore da backup</code>.`,solution:["cat /proc/mdstat","mdadm --detail /dev/md0","conclude restore da backup"],checkpoints:{diag:false,explained:false},done:false,startedAt:Date.now(),seed:"raid0_fail"};termPrint("Scenario caricato: RAID0 fail (atteso FAILED)","info");render(); },
raid1_onefail:()=>{ createArray();raidLevelEl.value="1";state.raidLevel=1;diskCountEl.value="2";state.disks=Array.from({length:2},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[0].state=DiskState.FAILED;pushDmesg("warn","RAID1: one disk failed, still OK/DEGRADED (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"raid1_onefail",goalHtml:`RAID1 degradato (1 disco FAILED). Obiettivo: sostituire il disco e ricostruire.`,solution:["cat /proc/mdstat","mdadm --detail /dev/md0","mdadm --remove /dev/md0 /dev/sda","mdadm --add /dev/md0 /dev/sdc","mdadm --rebuild /dev/md0","cat /proc/mdstat"],checkpoints:{diag:false,removed:false,added:false,rebuild:false,ok:false},done:false,startedAt:Date.now(),seed:"raid1_onefail"};termPrint("Scenario caricato: RAID1 one fail","info");render(); },
raid5_1fail:()=>{ createArray();raidLevelEl.value="5";state.raidLevel=5;diskCountEl.value="4";state.disks=Array.from({length:4},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[1].state=DiskState.FAILED;state.disks[1].smart.realloc=140;state.disks[1].smart.pending=18;pushDmesg("warn","/dev/sdb: disk FAILED (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"raid5_1fail",goalHtml:`RAID5 degradato (1 disco FAILED). Obiettivo: tornare a <b>OK</b>.<br>Sequenza: <code>cat /proc/mdstat</code> → <code>mdadm --detail /dev/md0</code> → <code>mdadm --remove /dev/md0 /dev/sdb</code> → <code>mdadm --add /dev/md0 /dev/sde</code> → <code>mdadm --rebuild /dev/md0</code>.`,solution:["cat /proc/mdstat","mdadm --detail /dev/md0","mdadm --remove /dev/md0 /dev/sdb","mdadm --add /dev/md0 /dev/sde","mdadm --rebuild /dev/md0","cat /proc/mdstat"],checkpoints:{diag:false,removed:false,added:false,rebuild:false,ok:false},done:false,startedAt:Date.now(),seed:"raid5_1fail"};termPrint("Scenario caricato: RAID5 1 fail","info");render(); },
raid5_2fail:()=>{ createArray();raidLevelEl.value="5";state.raidLevel=5;diskCountEl.value="5";state.disks=Array.from({length:5},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[0].state=DiskState.FAILED;state.disks[3].state=DiskState.FAILED;pushDmesg("err","RAID5: 2 disks failed => array FAILED (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"raid5_2fail",goalHtml:`RAID5 con 2 dischi FAILED → array <b>FAILED</b>. Obiettivo: riconoscere impossibilità di rebuild e concludere <code>conclude restore da backup</code>.`,solution:["cat /proc/mdstat","mdadm --detail /dev/md0","conclude restore da backup"],checkpoints:{diag:false,explained:false},done:false,startedAt:Date.now(),seed:"raid5_2fail"};termPrint("Scenario caricato: RAID5 2 fail (atteso FAILED)","info");render(); },
raid6_2fail:()=>{ createArray();raidLevelEl.value="6";state.raidLevel=6;diskCountEl.value="6";state.disks=Array.from({length:6},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[1].state=DiskState.FAILED;state.disks[4].state=DiskState.FAILED;pushDmesg("warn","RAID6: 2 disks FAILED, still operational (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"raid6_2fail",goalHtml:`RAID6 degradato con 2 dischi FAILED (tollerato). Obiettivo: sostituire e rebuild (uno alla volta).`,solution:["cat /proc/mdstat","mdadm --detail /dev/md0","mdadm --remove /dev/md0 /dev/sdb","mdadm --add /dev/md0 /dev/sdg","mdadm --rebuild /dev/md0","(ripeti per altro disco FAILED)"],checkpoints:{diag:false,ok:false},done:false,startedAt:Date.now(),seed:"raid6_2fail"};termPrint("Scenario caricato: RAID6 2 fail","info");render(); },
raid10_pairfail:()=>{ createArray();raidLevelEl.value="10";state.raidLevel=10;diskCountEl.value="6";state.disks=Array.from({length:6},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[0].state=DiskState.FAILED;state.disks[1].state=DiskState.FAILED;pushDmesg("err","RAID10: mirror pair lost => array FAILED (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"raid10_pairfail",goalHtml:`RAID10: perso un mirror pair → array <b>FAILED</b>. Obiettivo: diagnostica + conclusione corretta: <code>conclude restore da backup</code>.`,solution:["cat /proc/mdstat","mdadm --detail /dev/md0","conclude restore da backup"],checkpoints:{diag:false,explained:false},done:false,startedAt:Date.now(),seed:"raid10_pairfail"};termPrint("Scenario caricato: RAID10 pair fail (atteso FAILED)","info");render(); },
rebuild_interrupted:()=>{ createArray();raidLevelEl.value="5";state.raidLevel=5;diskCountEl.value="4";state.disks=Array.from({length:4},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[2].state=DiskState.FAILED;pushDmesg("warn","/dev/sdc: disk FAILED (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"rebuild_interrupted",goalHtml:`RAID5 degradato. Obiettivo: sostituire disco, avviare rebuild e interromperlo (blackout) con <code>powerfail</code>, poi riavviarlo.`,solution:["mdadm --remove /dev/md0 /dev/sdc","mdadm --add /dev/md0 /dev/sde","mdadm --rebuild /dev/md0","powerfail","mdadm --rebuild /dev/md0","cat /proc/mdstat"],checkpoints:{started:false,stopped:false,restarted:false,ok:false},done:false,startedAt:Date.now(),seed:"rebuild_interrupted"};termPrint("Scenario caricato: rebuild_interrupted","info");render(); },
wrong_size_spare:()=>{ createArray();raidLevelEl.value="5";state.raidLevel=5;diskCountEl.value="4";state.disks=Array.from({length:4},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[1].state=DiskState.FAILED;pushDmesg("warn","/dev/sdb: disk FAILED (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"wrong_size_spare",goalHtml:`RAID5 degradato. Obiettivo: provare spare più piccolo (errore), poi spare corretto e rebuild.<br>Prova: <code>mdadm --add /dev/md0 /dev/sdz --size 500</code> → rebuild deve fallire.`,solution:["mdadm --add /dev/md0 /dev/sdz --size 500","mdadm --rebuild /dev/md0","mdadm --add /dev/md0 /dev/sde","mdadm --rebuild /dev/md0","cat /proc/mdstat"],checkpoints:{triedSmall:false,addedOk:false,ok:false},done:false,startedAt:Date.now(),seed:"wrong_size_spare"};termPrint("Scenario caricato: wrong_size_spare","info");render(); },
crc_errors:()=>{ createArray();raidLevelEl.value="5";state.raidLevel=5;diskCountEl.value="4";state.disks=Array.from({length:4},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[3].state=DiskState.CRC;state.disks[3].smart.crc=24;pushDmesg("warn","/dev/sdd: UDMA CRC errors detected (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"crc_errors",goalHtml:`CRC errors: probabile cavo/connessione. Obiettivo: diagnosticare con <code>dmesg | tail</code> e <code>smartctl -a /dev/sdd</code> e concludere: <code>conclude sostituire cavo SATA</code>.`,solution:["dmesg | tail","smartctl -a /dev/sdd","conclude sostituire cavo SATA"],checkpoints:{diag:false,explained:false},done:false,startedAt:Date.now(),seed:"crc_errors"};termPrint("Scenario caricato: crc_errors","info");render(); },
overheat:()=>{ createArray();raidLevelEl.value="6";state.raidLevel=6;diskCountEl.value="5";state.disks=Array.from({length:5},(_,i)=>makeDisk(i,1000));diskSizeEl.value="1000";state.diskSizeGB=1000;state.disks[2].state=DiskState.OVERHEAT;state.disks[2].smart.temp=60;pushDmesg("warn","/dev/sdc: temperature critical (scenario)");state.mode="EXERCISE";state.exerciseOn=true;state.scenario={name:"overheat",goalHtml:`Disco in overheat. Obiettivo: diagnosi (<code>smartctl</code> + <code>dmesg | tail</code>) e conclusione: <code>conclude migliorare raffreddamento</code>.`,solution:["smartctl -a /dev/sdc","dmesg | tail","conclude migliorare raffreddamento"],checkpoints:{diag:false,explained:false},done:false,startedAt:Date.now(),seed:"overheat"};termPrint("Scenario caricato: overheat","info");render(); }
};
function loadScenario(name){ const fn=scenarios[name]; if(!fn)return termPrint(`scenario: '${name}' non trovato. Usa 'scenario list'.`,"err"); fn(); scenarioCheck(); }
function randomExercise(){ const keys=Object.keys(scenarios); const pick=keys[Math.floor(Math.random()*keys.length)]; loadScenario(pick); termPrint(`Esercizio random: ${pick}`,"info"); }
function scenarioActionDiag(){ if(!state.scenario.name||state.scenario.done)return; if(!state.scenario.checkpoints.diag){state.scenario.checkpoints.diag=true;addScore(4,"(scenario) diagnosi fatta.");} }
function scenarioCheck(){
if(!state.scenario.name||state.scenario.done)return;
const sc=state.scenario; const vol=volumeStatus();
if(sc.name==="raid5_1fail"||sc.name==="raid1_onefail"){if(vol===VolState.OK)sc.checkpoints.ok=true;if(sc.checkpoints.diag&&sc.checkpoints.removed&&sc.checkpoints.added&&sc.checkpoints.rebuild&&sc.checkpoints.ok){sc.done=true;addScore(25,"(scenario) recovery completato.");}}
if(sc.name==="raid6_2fail"){if(vol===VolState.OK){sc.checkpoints.ok=true;sc.done=true;addScore(22,"(scenario) RAID6 ripristinato.");}}
if(["raid0_fail","raid5_2fail","raid10_pairfail"].includes(sc.name)){if(sc.checkpoints.diag&&sc.checkpoints.explained){sc.done=true;addScore(18,"(scenario) conclusione corretta.");}}
if(sc.name==="rebuild_interrupted"){if(sc.checkpoints.started&&sc.checkpoints.stopped&&sc.checkpoints.restarted&&vol===VolState.OK){sc.done=true;addScore(28,"(scenario) completato.");}}
if(sc.name==="wrong_size_spare"){if(sc.checkpoints.triedSmall&&sc.checkpoints.addedOk&&vol===VolState.OK){sc.done=true;addScore(26,"(scenario) spare size gestita.");}}
if(["crc_errors","overheat"].includes(sc.name)){if(sc.checkpoints.diag&&sc.checkpoints.explained){sc.done=true;addScore(16,"(scenario) diagnosi corretta.");}}
render();
}
function mdstat(){
const vol=volumeStatus(),n=state.disks.length,L=state.raidLevel;
const bar=state.disks.map(d=>{if(d.state===DiskState.OK)return"U";if(d.state===DiskState.REBUILDING)return"R";if([DiskState.SLOW,DiskState.CRC,DiskState.OVERHEAT].includes(d.state))return"U";return"_";}).join("");
const members=state.disks.map(d=>{let tag="U";if(d.state===DiskState.REBUILDING)tag="R";else if(d.state===DiskState.REMOVED)tag="M";else if(d.state===DiskState.FAILED)tag="F";else if(d.state===DiskState.CRC)tag="C";else if(d.state===DiskState.SLOW)tag="S";else if(d.state===DiskState.OVERHEAT)tag="T";return`${d.dev}[${tag}]`;}).join(" ");
let out=[];
out.push("Personalities : [raid0] [raid1] [raid5] [raid6] [raid10]");
out.push(`md0 : active raid${L} ${members}`);
out.push(` status=${vol} blocks=${capacityGB(L,n,state.diskSizeGB)}GB [ ${bar} ]`);
if(state.rebuild.active){const left=Math.max(0,Math.round(state.rebuild.etaSec-(Date.now()-state.rebuild.start)/1000));out.push(` [>....................] recovery = ${state.rebuild.progress.toFixed(1)}% (ETA ~ ${left}s)`);}
out.push(""); out.push("unused devices: <none>");
return out.join("\n");
}
function mdadmDetail(){
const vol=volumeStatus(),n=state.disks.length,tol=tolerance(state.raidLevel,n),cap=capacityGB(state.raidLevel,n,state.diskSizeGB);
let out=[];
out.push("/dev/md0:"); out.push(" Version : 1.2 (sim)"); out.push(` Raid Level : raid${state.raidLevel}`); out.push(` Array Size : ${cap} GB`); out.push(` Device Count : ${n}`); out.push(` Failed Disks : ${failedCount()}`); out.push(` Removed Disks : ${removedCount()}`); out.push(` Spare Disks : ${state.spares.length}`); out.push(` State : ${vol}`); out.push(` Fault Tolerance: ${tol} disk(s) (didattica)`);
if(state.rebuild.active){const left=Math.max(0,Math.round(state.rebuild.etaSec-(Date.now()-state.rebuild.start)/1000));out.push(` Rebuild : active (ETA ~ ${left}s)`);}
out.push(""); out.push(" Number State Device Size");
state.disks.forEach((d,i)=>{let st=d.state;if(d.state===DiskState.REBUILDING)st=`REBUILD ${d.progress.toFixed(1)}%`;out.push(` ${String(i).padStart(6," ")} ${st.padEnd(14," ")} ${d.dev.padEnd(10," ")} ${d.sizeGB}GB`);});
if(state.spares.length){out.push("");out.push(" Spares:");state.spares.forEach(d=>out.push(` - ${d.dev} (${d.sizeGB}GB) state=SPARE`));}
return out.join("\n");
}
function lsblk(){
const cap=capacityGB(state.raidLevel,state.disks.length,state.diskSizeGB),mount=state.fs.mountedAt||"";
let out=["NAME SIZE TYPE MOUNTPOINT"];
state.disks.forEach(d=>{const st=d.state===DiskState.OK?"":`(${d.state.toLowerCase()})`;out.push(`${d.dev.replace("/dev/","")} ${d.sizeGB}G disk${st?` ${st}`:""}`)});
state.spares.forEach(d=>out.push(`${d.dev.replace("/dev/","")} ${d.sizeGB}G disk (spare)`));
out.push(`md0 ${cap}G raid${state.raidLevel} ${mount}`);
return out.join("\n");
}
function fdiskL(){ const cap=capacityGB(state.raidLevel,state.disks.length,state.diskSizeGB); return[`Disk /dev/md0: ${cap} GiB (sim)`,"Device Boot Start End Sectors Size Id Type","/dev/md0p1 2048 2097151 2095104 1024M 83 Linux",`/dev/md0p2 2097152 ${cap*2048} ${(cap*2048)-2097152} ${Math.max(1,cap-1)}G 83 Linux`].join("\n"); }
function blkid(){ if(!state.fs.created)return"(nessun filesystem)"; return`/dev/md0: UUID="${state.fs.uuid}" TYPE="ext4" LABEL="${state.fs.label}"`; }
function dfh(){
if(!state.fs.created)return"df: filesystem non creato. Usa: mkfs.ext4 /dev/md0";
if(!state.fs.mountedAt)return"df: filesystem non montato. Usa: mount /dev/md0 /mnt";
if(!canAccessData())return"df: I/O error: volume FAILED (didattica).";
const cap=capacityGB(state.raidLevel,state.disks.length,state.diskSizeGB),used=Math.min(cap,Math.round((state.fs.files.size*2)+(state.rebuild.active?6:1))),avail=Math.max(0,cap-used),usep=cap?Math.round((used/cap)*100):0;
return["Filesystem Size Used Avail Use% Mounted on",`/dev/md0 ${cap}G ${used}G ${avail}G ${usep}% ${state.fs.mountedAt}`].join("\n");
}
function dmesgTail(){ const lines=state.dmesg.slice(-14); return lines.length?lines.join("\n"):"(nessun messaggio kernel)"; }
function smartctl(dev){
const disk=[...state.disks,...state.spares].find(d=>d.dev===dev);
if(!disk)return`smartctl: cannot open ${dev}: No such device`;
const st=disk.state; let health="PASSED";
if(st===DiskState.FAILED)health="FAILED!"; if(st===DiskState.OVERHEAT)health="PASSED (but temperature high)"; if(st===DiskState.CRC)health="PASSED (but CRC errors)"; if(st===DiskState.SLOW)health="PASSED (but performance degraded)";
const temp=(st===DiskState.OVERHEAT)?disk.smart.temp:(31+(disk.id%6)+(st===DiskState.SLOW?6:0));
const realloc=(st===DiskState.FAILED)?Math.max(120,disk.smart.realloc):disk.smart.realloc;
const pending=(st===DiskState.FAILED)?Math.max(10,disk.smart.pending):disk.smart.pending;
const crc=(st===DiskState.CRC)?Math.max(20,disk.smart.crc):disk.smart.crc;
return[`smartctl 7.4 ${dev} (sim)`,`Device Model: ${disk.smart.model}`,`Serial Number: ${disk.smart.serial}`,`Power_On_Hours: ${disk.smart.powerOnHours}`,"","=== START OF READ SMART DATA SECTION ===",`SMART overall-health self-assessment test result: ${health}`,"","ID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE",` 5 Reallocated_Sector_Ct 0x0033 100 100 010 Pre-fail Always - ${realloc}`,`197 Current_Pending_Sector 0x0012 100 100 000 Old_age Always - ${pending}`,`199 UDMA_CRC_Error_Count 0x003e 200 200 000 Old_age Always - ${crc}`,`194 Temperature_Celsius 0x0022 070 060 000 Old_age Always - ${temp}`].join("\n");
}
function pathUnderMount(path){ if(!state.fs.mountedAt)return false; const m=state.fs.mountedAt; return path===m||path.startsWith(m+"/"); }
function mkfs(){ if(volumeStatus()===VolState.FAILED)return{ok:false,msg:"mkfs.ext4: I/O error: volume FAILED (didattica)."}; state.fs.created=true;state.fs.uuid=randUUID();state.fs.label="RAIDLAB";state.fs.files=new Map();pushDmesg("info","EXT4-fs: filesystem created on /dev/md0");return{ok:true,msg:"mkfs.ext4: filesystem creato su /dev/md0 (sim)."}; }
function mountFs(mp){ if(!state.fs.created)return{ok:false,msg:"mount: nessun filesystem. Esegui mkfs.ext4 /dev/md0"}; if(volumeStatus()===VolState.FAILED)return{ok:false,msg:"mount: I/O error: volume FAILED (didattica)."}; state.fs.mountedAt=mp;pushDmesg("info",`VFS: mounted /dev/md0 on ${mp}`);return{ok:true,msg:`mount: /dev/md0 montato su ${mp} (sim).`}; }
function umountFs(mp){ if(state.fs.mountedAt!==mp)return{ok:false,msg:`umount: ${mp} non montato.`}; state.fs.mountedAt=null;pushDmesg("info",`VFS: unmounted ${mp}`);return{ok:true,msg:`umount: smontato ${mp} (sim).`}; }
function fileTouch(path){ if(!state.fs.mountedAt)return{ok:false,msg:"touch: filesystem non montato."}; if(!pathUnderMount(path))return{ok:false,msg:`touch: accesso negato (usa ${state.fs.mountedAt}/...)`}; if(!canAccessData())return{ok:false,msg:"touch: I/O error: volume FAILED (didattica)."}; state.fs.files.set(path,state.fs.files.get(path)||"");return{ok:true,msg:""}; }
function fileEchoTo(text,path){ if(!state.fs.mountedAt)return{ok:false,msg:"echo: filesystem non montato."}; if(!pathUnderMount(path))return{ok:false,msg:`echo: accesso negato (usa ${state.fs.mountedAt}/...)`}; if(!canAccessData())return{ok:false,msg:"echo: I/O error: volume FAILED (didattica)."}; state.fs.files.set(path,text);return{ok:true,msg:""}; }
function fileCat(path){ if(!state.fs.mountedAt)return{ok:false,msg:"cat: filesystem non montato."}; if(!pathUnderMount(path))return{ok:false,msg:`cat: accesso negato (usa ${state.fs.mountedAt}/...)`}; if(!canAccessData())return{ok:false,msg:"cat: I/O error: volume FAILED (didattica)."}; if(!state.fs.files.has(path))return{ok:false,msg:`cat: ${path}: No such file`};return{ok:true,msg:state.fs.files.get(path)}; }
function fileLs(path){ if(!state.fs.mountedAt)return{ok:false,msg:"ls: filesystem non montato. Usa: mount /dev/md0 /mnt"}; if(!pathUnderMount(path))return{ok:false,msg:`ls: accesso negato (usa ${state.fs.mountedAt}/...)`}; if(!canAccessData())return{ok:false,msg:"ls: I/O error: volume FAILED (didattica)."}; const prefix=(path===state.fs.mountedAt)?(state.fs.mountedAt+"/"):(path.replace(/\/+$/,"")+"/"); const items=[]; for(const k of state.fs.files.keys()){if(k.startsWith(prefix)){const rest=k.slice(prefix.length);if(!rest.includes("/"))items.push(rest);}} return{ok:true,msg:items.length?items.sort().join("\n"):"(vuoto)"}; }
function fileRm(path){ if(!state.fs.mountedAt)return{ok:false,msg:"rm: filesystem non montato."}; if(!pathUnderMount(path))return{ok:false,msg:`rm: accesso negato (usa ${state.fs.mountedAt}/...)`}; if(!canAccessData())return{ok:false,msg:"rm: I/O error: volume FAILED (didattica)."}; if(!state.fs.files.has(path))return{ok:false,msg:`rm: cannot remove '${path}': No such file`}; state.fs.files.delete(path);return{ok:true,msg:""}; }
function mdadmFail(dev){ if(state.rebuild.active)return{ok:false,msg:"mdadm: rebuild in corso, fail bloccato (didattica)."}; const d=state.disks.find(x=>x.dev===dev); if(!d)return{ok:false,msg:`mdadm: ${dev}: non è membro dell'array.`}; d.state=DiskState.FAILED;d.smart.realloc=120+Math.floor(Math.random()*90);d.smart.pending=10+Math.floor(Math.random()*20);pushDmesg("warn",`${dev}: disk FAILED (mdadm --fail)`);return{ok:true,msg:`mdadm: ${dev} marcato FAILED.`}; }
function mdadmRemove(dev){ if(state.rebuild.active)return{ok:false,msg:"mdadm: rebuild in corso, remove bloccato."}; const d=state.disks.find(x=>x.dev===dev); if(!d)return{ok:false,msg:`mdadm: ${dev}: non è membro dell'array.`}; if(!(d.state===DiskState.FAILED||d.state===DiskState.REMOVED))return{ok:false,msg:`mdadm: ${dev}: rimozione solo se FAILED/REMOVED (didattica).`}; d.state=DiskState.REMOVED;pushDmesg("warn",`${dev}: removed from array`); if(state.scenario.name&&!state.scenario.checkpoints.removed){if(["raid5_1fail","raid1_onefail","rebuild_interrupted","wrong_size_spare","raid6_2fail"].includes(state.scenario.name)){state.scenario.checkpoints.removed=true;addScore(5,"(scenario) remove fatto.");}} return{ok:true,msg:`mdadm: ${dev} rimosso (REMOVED).`}; }
function mdadmAdd(dev,sizeOverrideGB=null){ if(state.disks.some(x=>x.dev===dev)||state.spares.some(x=>x.dev===dev))return{ok:false,msg:`mdadm: ${dev}: già presente.`}; let size=sizeOverrideGB??state.diskSizeGB; size=clamp(parseInt(size,10),10,200000); const idx=state.spares.length; const spare=makeDisk(state.disks.length+idx,size); spare.dev=dev;spare.name=`spare${idx+1}`;spare.state=DiskState.SPARE; state.spares.push(spare);pushDmesg("info",`${dev}: added as spare (${size}GB)`); if(state.scenario.name){if(["raid5_1fail","raid1_onefail","rebuild_interrupted","raid6_2fail"].includes(state.scenario.name)&&!state.scenario.checkpoints.added){state.scenario.checkpoints.added=true;addScore(5,"(scenario) spare aggiunto.");}if(state.scenario.name==="wrong_size_spare"){if(size<state.diskSizeGB&&!state.scenario.checkpoints.triedSmall){state.scenario.checkpoints.triedSmall=true;addScore(5,"(scenario) spare piccolo provato.");}else if(size>=state.diskSizeGB&&!state.scenario.checkpoints.addedOk){state.scenario.checkpoints.addedOk=true;addScore(5,"(scenario) spare corretto aggiunto.");}}} return{ok:true,msg:`mdadm: aggiunto ${dev} come SPARE (${size}GB).`}; }
function helpText(){ return["Comandi (simulazione):"," help | clear | report | export"," scenario list | scenario load <n>"," hint | solution (teacher)"," powerfail (simula blackout: interrompe rebuild)","","Sistema:"," uptime (mostra il tempo di esecuzione del simulatore)","","RAID (entrambe le sintassi accettate):"," cat /proc/mdstat"," mdadm --detail /dev/md0"," mdadm --fail /dev/md0 /dev/sdX (oppure: mdadm /dev/md0 --fail /dev/sdX)"," mdadm --remove /dev/md0 /dev/sdX (oppure: mdadm /dev/md0 --remove /dev/sdX)"," mdadm --add /dev/md0 /dev/sdX [--size <GB>]"," mdadm --rebuild /dev/md0"," mdadm --stop-rebuild /dev/md0","","Dischi & OS:"," unplug /dev/sdX"," lsblk | fdisk -l | blkid"," dmesg | tail"," smartctl -a /dev/sdX"," df -h","","Filesystem:"," mkfs.ext4 /dev/md0"," mount /dev/md0 /mnt | umount /mnt"," ls /mnt | touch /mnt/file | echo \"testo\" > /mnt/file | cat /mnt/file | rm /mnt/file","","Conclusioni (scenari FAILED o non-RAID):"," conclude <testo>"].join("\n"); }
function makeReport(){
const vol=volumeStatus(),cap=capacityGB(state.raidLevel,state.disks.length,state.diskSizeGB),lines=[];
lines.push("RAID LAB REPORT");lines.push("===============");lines.push(`Timestamp: ${new Date().toISOString()}`);lines.push(`ExerciseCode: ${state.exgen.code||"(none)"}`);lines.push(`Scenario: ${state.scenario.name||"(none)"}`);lines.push(`Mode: ${state.mode} Teacher: ${state.teacher?"ON":"OFF"}`);lines.push(`RAID: ${state.raidLevel} Members: ${state.disks.length} DiskSizeGB: ${state.diskSizeGB}`);lines.push(`CapacityGB: ${cap} Status: ${vol}`);lines.push(`Score: ${state.score} Hints: ${state.hints}`);lines.push("");lines.push("Disks:");
state.disks.forEach(d=>lines.push(`- ${d.dev} ${d.name} size=${d.sizeGB}GB state=${d.state}`));
if(state.spares.length){lines.push("Spares:");state.spares.forEach(d=>lines.push(`- ${d.dev} ${d.name} size=${d.sizeGB}GB state=SPARE`));}
lines.push("");lines.push("Last dmesg (tail):");lines.push(dmesgTail());lines.push("");lines.push("Actions:");state.actions.slice(-80).forEach(a=>lines.push(`[${a.t}] ${a.cmd}`));
return lines.join("\n");
}
async function exportReport(){
const text=makeReport();
// Download reale come file .txt
try{
const blob=new Blob([text],{type:"text/plain"});
const url=URL.createObjectURL(blob);
const a=document.createElement("a");
a.href=url; a.download=`raid_report_${Date.now()}.txt`; a.click();
URL.revokeObjectURL(url);
termPrint("Report scaricato come file .txt ✅","ok");
}catch(e){
// Fallback: clipboard
try{await navigator.clipboard.writeText(text);termPrint("Download non disponibile: report copiato negli appunti ✅","ok");}
catch(e2){termPrint("Clipboard non disponibile: stampo report qui sotto.","warn");termPrint(text,"dim");}
}
}
function render(){
pillRebuildCountEl.textContent=state.rebuildCount;
pillArrayEl.textContent=state.arrayName;
const n=state.disks.length,L=state.raidLevel,sizeGB=state.diskSizeGB;
capValueEl.textContent=fmtGB(capacityGB(L,n,sizeGB));
capNoteEl.textContent=L===0?"N × size":L===1?"1 × size":L===5?"(N-1) × size":L===6?"(N-2) × size":L===10?"(N/2) × size":"—";
const tol=tolerance(L,n);
if(L===10){ftValueEl.textContent=`fino a ${tol}`;ftNoteEl.textContent="1 guasto per coppia (non nella stessa mirror)";}
else{ftValueEl.textContent=`${tol}`;ftNoteEl.textContent=L===0?"nessuna ridondanza":L===1?"mirroring (tutti tranne 1)":(L===5||L===6)?"parità":"—";}
const vol=volumeStatus();
lampEl.className=`lamp ${lampClass(vol)}`;
bigStatusEl.textContent=`STATUS: ${vol}`;
miniStatusEl.textContent=`RAID${L} · members=${n} · failed=${failedCount()} · removed=${removedCount()} · spares=${state.spares.length} · rebuild=${state.rebuild.active?state.rebuild.progress.toFixed(1)+"%":"no"}`;
// Banner didattico rebuild
if(state.rebuild.active){
const leftSim=Math.max(0,Math.round(state.rebuild.etaSec-(Date.now()-state.rebuild.start)/1000));
const realSec=state.rebuild.realSec||0;
const realMin=Math.round(realSec/60);
const realStr=realSec>=3600?`~${(realSec/3600).toFixed(1)} ore`:`~${realMin} min`;
rbFactorEl.textContent=state.rebuild.speedFactor||"?";
rbEtaSimEl.textContent=`${leftSim}s (${state.rebuild.progress.toFixed(0)}%)`;
rbEtaRealEl.textContent=`${realStr} (disco ${state.diskSizeGB} GB, RAID${L})`;
rebuildBannerEl.style.display="flex";
} else {
rebuildBannerEl.style.display="none";
}
pillModeEl.textContent=state.mode;
promptTextEl.textContent=`raidlab(${vol})$`;
if(!state.fs.created)fsChipEl.textContent="fs: none";
else if(!state.fs.mountedAt)fsChipEl.textContent="fs: unmounted";
else fsChipEl.textContent=`fs: mounted@${state.fs.mountedAt}`;
exerciseChipEl.textContent=`exercise: ${state.exerciseOn?"on":"off"}`;
tipChipEl.textContent=state.teacher?"tip: solution":"tip: help";
if(!state.timer.on)timeLeftEl.textContent="OFF";
else{const leftMs=Math.max(0,state.timer.endTs-Date.now()),s=Math.ceil(leftMs/1000),mm=Math.floor(s/60),ss=s%60;timeLeftEl.textContent=`${mm}:${String(ss).padStart(2,"0")}`;}
scoreEl.textContent=String(state.score);hintsEl.textContent=String(state.hints);
const tel=calcTelemetry();tempAvgEl.textContent=tel.avg?`${tel.avg}°C`:"—";iopsEl.textContent=String(tel.iops);crcEl.textContent=String(tel.crc);reallocEl.textContent=String(tel.realloc);
if(!state.scenario.name){scenarioTextEl.innerHTML=`Nessuno scenario caricato. Usa <span class="kbd">scenario list</span> o <span class="kbd">Esercizio random</span>.`;}
else{const done=state.scenario.done?" ✅ RISOLTO":"";const teacher=state.teacher?`<br><br><b>Soluzione (Teacher):</b><br><code>${state.scenario.solution.join("</code> <br><code>")}</code>`:"";scenarioTextEl.innerHTML=`<b>${state.scenario.name}</b>${done}<br>${state.scenario.goalHtml}${teacher}`;}
parityTextEl.textContent=parityText();
diskGridEl.innerHTML="";
[...state.disks,...state.spares].forEach((d,idx)=>{
const isSpare=idx>=state.disks.length;
const div=document.createElement("div");
const st=isSpare?DiskState.SPARE:d.state;
div.className=`disk ${diskVisualClass(st)}`;
let label=isSpare?"SPARE":d.state;
if(!isSpare&&d.state===DiskState.REBUILDING)label=`REBUILD ${d.progress.toFixed(0)}%`;
div.innerHTML=`<div class="top"><div class="idx">${d.dev} (${d.name})</div><div class="state">${label}</div></div><div class="icon" aria-hidden="true"></div><div class="bar"><span style="width:${clamp(d.progress||0,0,100)}%"></span></div>`;
div.addEventListener("click",()=>{
if(!state.teacher)return;
if(isSpare){termPrint("(teacher) spare selezionato. Usa mdadm --add per inserirlo.","dim");return;}
if(state.rebuild.active)return termPrint("Operazione bloccata: rebuild in corso.","warn");
const order=[DiskState.OK,DiskState.SLOW,DiskState.CRC,DiskState.OVERHEAT,DiskState.FAILED];
const cur=order.indexOf(d.state);d.state=order[(cur+1)%order.length];
if(d.state===DiskState.FAILED){d.smart.realloc=120+Math.floor(Math.random()*80);d.smart.pending=10+Math.floor(Math.random()*15);pushDmesg("warn",`${d.dev}: I/O error, disk FAILED (teacher)`);}
else if(d.state===DiskState.CRC){d.smart.crc+=8+Math.floor(Math.random()*8);pushDmesg("warn",`${d.dev}: ata CRC errors rising (teacher)`);}
else if(d.state===DiskState.OVERHEAT){d.smart.temp=58;pushDmesg("warn",`${d.dev}: temperature critical (teacher)`);}
else if(d.state===DiskState.SLOW){pushDmesg("info",`${d.dev}: device responding slow (teacher)`);}
render();scenarioCheck();
});
diskGridEl.appendChild(div);
});
exerciseCodeEl.textContent=state.exgen.code||"—";exScenarioEl.textContent=state.exgen.scenario||"—";exSeedEl.textContent=state.exgen.seed||"—";exGoalShortEl.textContent=state.exgen.goalShort||"—";exGoalLongEl.textContent=state.exgen.goalLong||"—";
}
function randCode(){ const a="ABCDEFGHJKLMNPQRSTUVWXYZ23456789";let s="";for(let i=0;i<8;i++)s+=a[Math.floor(Math.random()*a.length)];return s; }
function generateExercise(){
const code=randCode(),keys=Object.keys(scenarios),pick=keys[Math.floor(Math.random()*keys.length)],seed=`${Date.now().toString(36)}-${Math.floor(Math.random()*9999)}`;
state.exgen.code=code;state.exgen.seed=seed;state.exgen.scenario=pick;
const goalMap={raid0_fail:["Diagnosi + backup","Capire che RAID0 con un guasto è FAILED e serve restore da backup."],raid1_onefail:["Sostituzione + rebuild","Sostituire un disco guasto in RAID1 e completare rebuild."],raid5_1fail:["Recovery RAID5","Sostituire 1 disco guasto e completare rebuild fino a OK."],raid5_2fail:["Diagnosi + backup","RAID5 con 2 guasti è FAILED: concludere restore da backup."],raid6_2fail:["Recovery RAID6","Sostituire i dischi guasti (uno alla volta) e tornare a OK."],raid10_pairfail:["Diagnosi + backup","RAID10 con mirror pair perso è FAILED: restore da backup."],rebuild_interrupted:["Rebuild interrotto","Avviare rebuild, simulare powerfail, poi riavviare fino a OK."],wrong_size_spare:["Gestire spare errato","Provare spare piccolo (errore) poi spare corretto e rebuild."],crc_errors:["CRC / cablaggio","Diagnosticare CRC errors e concludere sostituzione cavo."],overheat:["Surriscaldamento","Diagnosticare temperatura critica e concludere intervento di raffreddamento."]};
const[short,long]=goalMap[pick]||["Esercizio RAID","Ripristinare l'array seguendo diagnosi e azioni corrette."];
state.exgen.goalShort=short;state.exgen.goalLong=`Scenario: ${pick}. Seed: ${seed}. Obiettivo: ${long}`;
render();
}
function loadGeneratedExerciseIntoLab(){ if(!state.exgen.scenario)return; loadScenario(state.exgen.scenario); termPrint(`EXERCISE CODE: ${state.exgen.code} (seed=${state.exgen.seed})`,"info"); showPage("tab-lab"); render(); }
function runCommand(raw){
const cmd=raw.trim(); if(!cmd)return;
history.push(cmd);histIdx=history.length;
termEcho(cmd);pushAction(cmd);
if(cmd==="help"){termPrint(helpText(),"dim");return;}
if(cmd==="clear"){clearTerminal();return;}
if(cmd==="scenario list"){termPrint("Scenari:\n "+Object.keys(scenarios).join("\n "),"dim");return;}
if(cmd.startsWith("scenario load ")){loadScenario(cmd.slice("scenario load ".length).trim());return;}
if(cmd==="hint"){ useHint(); if(!state.scenario.name)return termPrint("Nessuno scenario attivo. Usa 'scenario list' o tab ESERCIZI.","warn"); const vol=volumeStatus(); if(vol===VolState.FAILED)return termPrint("Hint: volume FAILED → prima capire perché (mdstat/detail).","info"); if(vol===VolState.DEGRADED)return termPrint("Hint: trova disco problematico (mdstat/detail/smartctl/dmesg).","info"); return termPrint("Hint: verifica filesystem e dati (df -h, mount, ls/cat).","info"); }
if(cmd==="solution"){if(!state.teacher)return termPrint("solution: disponibile solo in Teacher mode.","err");if(!state.scenario.name)return termPrint("Nessuno scenario attivo.","warn");termPrint("Soluzione:","info");state.scenario.solution.forEach(s=>termPrint(s,"dim"));return;}
if(cmd==="report"){termPrint(makeReport(),"dim");return;}
if(cmd==="export"){exportReport();return;}
if(cmd==="powerfail"){if(state.rebuild.active){stopRebuild(false);termPrint("⚡ POWER FAIL: rebuild interrotto.","warn");if(state.scenario.name==="rebuild_interrupted"&&!state.scenario.checkpoints.stopped){state.scenario.checkpoints.stopped=true;addScore(6,"(scenario) rebuild interrotto.");}}else termPrint("POWER FAIL: nessun rebuild attivo.","dim");render();scenarioCheck();return;}
if(cmd==="dmesg | tail"){termPrint(dmesgTail(),"dim");scenarioActionDiag();render();scenarioCheck();return;}
const t=tokenize(cmd),c0=t[0];
if(cmd==="uptime"){let uptime=new Date(Date.now()-state.startupTime);termPrint("Uptime: "+twoDigits((uptime.getUTCDate()-1))+":"+twoDigits(uptime.getUTCHours())+":"+twoDigits(uptime.getUTCMinutes())+":"+twoDigits(uptime.getUTCSeconds()),"dim");return;}
if(c0==="cat"&&t[1]==="/proc/mdstat"){termPrint(mdstat(),"dim");scenarioActionDiag();render();scenarioCheck();return;}
if(c0==="mdadm"){
// Supporta entrambe le sintassi: "mdadm --op /dev/md0 /dev/sdX" e "mdadm /dev/md0 --op /dev/sdX"
let mdOp=t[1],mdArray=t[2],mdDisk=t[3];
if(t[1]==="/dev/md0"){mdOp=t[2];mdArray=t[1];mdDisk=t[3];}
if(mdOp==="--detail"&&mdArray==="/dev/md0"){termPrint(mdadmDetail(),"dim");scenarioActionDiag();render();scenarioCheck();return;}
if(mdOp==="--fail"&&mdArray==="/dev/md0"&&mdDisk){const r=mdadmFail(mdDisk);termPrint(r.msg,r.ok?"ok":"err");render();scenarioCheck();return;}
if(mdOp==="--remove"&&mdArray==="/dev/md0"&&mdDisk){const r=mdadmRemove(mdDisk);termPrint(r.msg,r.ok?"ok":"err");render();scenarioCheck();return;}
if(mdOp==="--add"&&mdArray==="/dev/md0"&&mdDisk){const sizeFlag=t.indexOf("--size");const size=(sizeFlag>=0&&t[sizeFlag+1])?t[sizeFlag+1]:null;const r=mdadmAdd(mdDisk,size);termPrint(r.msg,r.ok?"ok":"err");render();scenarioCheck();return;}
if((mdOp==="--rebuild"&&mdArray==="/dev/md0")||(t[1]==="--rebuild"&&t[2]==="/dev/md0")){if(state.scenario.name==="rebuild_interrupted"){if(state.scenario.checkpoints.started&&state.scenario.checkpoints.stopped&&!state.scenario.checkpoints.restarted){state.scenario.checkpoints.restarted=true;addScore(6,"(scenario) rebuild riavviato.");}if(!state.scenario.checkpoints.started){state.scenario.checkpoints.started=true;addScore(6,"(scenario) rebuild avviato.");}}if(state.scenario.name==="raid5_1fail"||state.scenario.name==="raid1_onefail"){if(!state.scenario.checkpoints.rebuild)state.scenario.checkpoints.rebuild=true;}startRebuild();render();scenarioCheck();return;}
if((mdOp==="--stop-rebuild"&&mdArray==="/dev/md0")||(t[1]==="--stop-rebuild"&&t[2]==="/dev/md0")){if(!state.rebuild.active)return termPrint("mdadm: nessun rebuild attivo.","warn");stopRebuild(false);termPrint("mdadm: rebuild interrotto (didattica).","warn");if(state.scenario.name==="rebuild_interrupted"&&!state.scenario.checkpoints.stopped){state.scenario.checkpoints.stopped=true;addScore(6,"(scenario) rebuild interrotto.");}render();scenarioCheck();return;}
return termPrint("mdadm: opzione non supportata. Digita 'help'.","err");
}
if(c0==="unplug"){
const dev=t[1];
if(!dev){
return termPrint("unplug: specifica il disco spare da rimuovere (simulazione). Esempio: unplug /dev/sdX","err");
}
const spareIndex=state.spares.findIndex(d=>d.dev===dev);
if(spareIndex===-1){
return termPrint(`unplug: ${dev} non è uno spare o non esiste.`,`err`);
}
const spare=state.spares[spareIndex];
spare.state=DiskState.REMOVED;
state.spares.splice(spareIndex,1);
pushDmesg("warn",`${dev}: unplugged dallo spare`);
termPrint(`unplug: ${dev} rimosso.`,`ok`);
render();
scenarioCheck();
return;
}
if(c0==="lsblk"){termPrint(lsblk(),"dim");scenarioActionDiag();render();scenarioCheck();return;}
if(c0==="fdisk"&&t[1]==="-l"){termPrint(fdiskL(),"dim");scenarioActionDiag();render();scenarioCheck();return;}
if(c0==="blkid"){termPrint(blkid(),"dim");scenarioActionDiag();render();scenarioCheck();return;}
if(c0==="df"&&t[1]==="-h"){termPrint(dfh(),"dim");scenarioActionDiag();render();scenarioCheck();return;}
if(c0==="smartctl"&&t[1]==="-a"&&t[2]){termPrint(smartctl(t[2]),"dim");scenarioActionDiag();render();scenarioCheck();return;}
if(c0==="dmesg"){termPrint("Suggerimento: usa 'dmesg | tail' (simulato).","dim");return;}
if(c0==="mkfs.ext4"&&t[1]==="/dev/md0"){const r=mkfs();termPrint(r.msg,r.ok?"ok":"err");render();scenarioCheck();return;}
if(c0==="mount"&&t[1]==="/dev/md0"&&t[2]){const r=mountFs(t[2]);termPrint(r.msg,r.ok?"ok":"err");render();scenarioCheck();return;}
if((c0==="umount"||c0==="unmount")&&t[1]){const r=umountFs(t[1]);termPrint(r.msg,r.ok?"ok":"err");render();scenarioCheck();return;}
if(c0==="ls"&&t[1]){const r=fileLs(t[1]);termPrint(r.msg,r.ok?"dim":"err");render();scenarioCheck();return;}
if(c0==="touch"&&t[1]){const r=fileTouch(t[1]);if(r.msg)termPrint(r.msg,r.ok?"dim":"err");render();scenarioCheck();return;}
if(c0==="rm"&&t[1]){const r=fileRm(t[1]);if(r.msg)termPrint(r.msg,r.ok?"dim":"err");render();scenarioCheck();return;}
if(c0==="cat"&&t[1]&&t[1]!=="/proc/mdstat"){const r=fileCat(t[1]);termPrint(r.msg,r.ok?"dim":"err");render();scenarioCheck();return;}
if(c0==="echo"){const gt=t.indexOf(">");if(gt<0)return termPrint(t[1],"dim");const text=t.slice(1,gt).join(" ");const path=t[gt+1];if(!path)return termPrint("echo: manca destinazione.","err");const r=fileEchoTo(text,path);if(r.msg)termPrint(r.msg,r.ok?"dim":"err");render();scenarioCheck();return;}
if(c0==="conclude"){
const text=t.slice(1).join(" ");if(!text)return termPrint("Uso: conclude <testo spiegazione>","err");
termPrint("Annotazione registrata (didattica).","ok");
if(["raid0_fail","raid5_2fail","raid10_pairfail"].includes(state.scenario.name)&&!state.scenario.checkpoints.explained){state.scenario.checkpoints.explained=true;addScore(6,"(scenario) conclusione registrata.");}
if(state.scenario.name==="crc_errors"&&!state.scenario.checkpoints.explained){state.scenario.checkpoints.explained=true;addScore(6,"(scenario) conclusione registrata.");}
if(state.scenario.name==="overheat"&&!state.scenario.checkpoints.explained){state.scenario.checkpoints.explained=true;addScore(6,"(scenario) conclusione registrata.");}
scenarioCheck();render();return;
}
termPrint(`${c0}: comando non riconosciuto. Digita 'help'.`,"err");
}
// ── CODICE DOCENTE: encode / decode ────────────────────────────────
function teacherEncode(payload){
const json=JSON.stringify(payload);
const b64=btoa(unescape(encodeURIComponent(json))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");
const alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let cs=0; for(let i=0;i<b64.length;i++)cs=(cs+b64.charCodeAt(i))%1296;
const c1=alpha[Math.floor(cs/36)], c2=alpha[cs%36];
return `TC-${b64}-${c1}${c2}`;
}
function teacherDecode(raw){
try{
const s=raw.trim().toUpperCase().replace(/\s/g,"");
if(!s.startsWith("TC-"))return null;
const inner=s.slice(3);
const lastDash=inner.lastIndexOf("-");
if(lastDash<0)return null;
const b64=inner.slice(0,lastDash);
const cs2=inner.slice(lastDash+1);
const alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let cs=0; for(let i=0;i<b64.length;i++)cs=(cs+b64.charCodeAt(i))%1296;
const c1=alpha[Math.floor(cs/36)], c2=alpha[cs%36];
if(cs2!==c1+c2)return null;
const json=decodeURIComponent(escape(atob(b64.replace(/-/g,"+").replace(/_/g,"/"))));
return JSON.parse(json);
}catch(e){return null;}
}
// ── TEACHER FORM: UI dinamica ───────────────────────────────────────
const devNames=["sda","sdb","sdc","sdd","sde","sdf","sdg","sdh","sdi","sdj","sdk","sdl"];
const faultLabels={
"":"Nessun guasto (OK)",
"FAILED":"FAILED — disco guasto (I/O error)",
"CRC":"CRC — errori di comunicazione (cavo/porta)",
"OVERHEAT":"OVERHEAT — temperatura critica",
"SLOW":"SLOW — risposte lente (pre-guasto)"
};
const faultDescriptions={
"":"Disco funzionante, nessun problema.",
"FAILED":"Disco non risponde. Se la tolleranza del RAID è superata l'array diventa FAILED.",
"CRC":"Errori sul cavo/connettore SATA. Diagnosticabili con dmesg e smartctl.",
"OVERHEAT":"Temperatura sopra soglia critica. Rilevabile con smartctl -a.",
"SLOW":"Disco funziona ma lento. Segnale di guasto imminente, rilevabile da dmesg."
};
function tfRaidMinDisks(level){
const L=Number(level);
if(L===0||L===1)return 2;
if(L===5)return 3;
if(L===6||L===10)return 4;
return 2;
}
function tfNormDiskCount(n,level){
const L=Number(level);
n=Math.max(n,tfRaidMinDisks(L));
if(L===10&&n%2===1)n+=1;
return Math.min(n,12);
}
function tfCapacityText(level,n,size){
const L=Number(level); let cap=0;
if(L===0)cap=n*size;
else if(L===1)cap=size;
else if(L===5)cap=(n-1)*size;
else if(L===6)cap=(n-2)*size;
else if(L===10)cap=Math.floor(n/2)*size;
const ft={0:"nessuna",1:`${n-1} disco/i`,5:"1 disco",6:"2 dischi",10:"1 disco per coppia"}[L]||"—";
return `Capacità utile: ${cap} GB · Tolleranza: ${ft}`;
}
function tfUpdateDiskFaultRows(){
const level=parseInt($("tfRaidLevel").value)||5;
let n=parseInt($("tfDiskCount").value)||4;
n=tfNormDiskCount(n,level);
$("tfDiskCount").value=String(n);
// aggiorna anteprima
const preview=$("tfArrayPreview");
preview.innerHTML="";
const capSpan=document.createElement("span");
capSpan.style.cssText="font-size:11.5px;color:var(--muted2);margin-right:6px;white-space:nowrap";
capSpan.textContent=tfCapacityText(level,n,parseInt($("tfDiskSize").value)||1000)+" —";
preview.appendChild(capSpan);
// salva selezioni precedenti
const existing={};
$("tfDiskFaultRows").querySelectorAll(".tf-fault-select").forEach(sel=>{
existing[sel.dataset.dev]=sel.value;
});
// genera chip preview e righe fault
const faultGrid=$("tfDiskFaultRows");
faultGrid.innerHTML="";
for(let i=0;i<n;i++){
const dev="/dev/"+devNames[i];
const chip=document.createElement("span");
chip.className="tf-disk-chip";
chip.id=`tf-chip-${i}`;
chip.textContent=`/dev/${devNames[i]}`;
preview.appendChild(chip);
// riga fault
const row=document.createElement("div");
row.className="tf-fault-row";
const devLabel=document.createElement("span");
devLabel.className="tf-fault-dev";
devLabel.textContent=dev;
const sel=document.createElement("select");
sel.className="tf-fault-select";
sel.dataset.dev=dev;
sel.dataset.idx=String(i);
Object.entries(faultLabels).forEach(([val,label])=>{
const opt=document.createElement("option");
opt.value=val; opt.textContent=label;
sel.appendChild(opt);
});
// ripristina selezione precedente se stesso disco
if(existing[dev])sel.value=existing[dev];
const desc=document.createElement("span");
desc.className="tf-fault-desc";
desc.textContent=faultDescriptions[sel.value]||"";
sel.addEventListener("change",()=>{
desc.textContent=faultDescriptions[sel.value]||"";
// aggiorna colore select
sel.className="tf-fault-select"+(sel.value?` sel-${sel.value}`:"");
// aggiorna chip preview
const chipEl=$(`tf-chip-${i}`);
if(chipEl)chipEl.className="tf-disk-chip"+(sel.value?` fault-${sel.value}`:"");
});
row.appendChild(devLabel);
row.appendChild(sel);
row.appendChild(desc);
faultGrid.appendChild(row);
}
}
// ── GENERA CODICE CUSTOM ────────────────────────────────────────────
function generateTeacherCode(){
const level=parseInt($("tfRaidLevel").value)||5;
let n=parseInt($("tfDiskCount").value)||4;
n=tfNormDiskCount(n,level);
const size=Math.max(10,parseInt($("tfDiskSize").value)||1000);
const classe=$("tfClasse").value.trim()||"—";
const data=$("tfData").value||"—";
const durata=parseInt($("tfDurata").value)||0;
const note=$("tfNote").value.trim()||"";
// raccogli guasti
const faults=[];
$("tfDiskFaultRows").querySelectorAll(".tf-fault-select").forEach(sel=>{
if(sel.value) faults.push({idx:parseInt(sel.dataset.idx),dev:sel.dataset.dev,type:sel.value});
});
// validazione
const vmsg=$("tfValidationMsg");
const L=Number(level);
const failedCount=faults.filter(f=>f.type==="FAILED").length;
let warning="";
if(L===0&&failedCount>=1) warning="⚠ RAID0: anche 1 FAILED porta l'array a FAILED — scenario didatticamente corretto.";
if(L===5&&failedCount>=2) warning="⚠ RAID5 con 2+ FAILED → array FAILED. L'obiettivo sarà restore da backup.";
if(L===1&&failedCount>=n) warning="⚠ Tutti i dischi guasti: nessuna tolleranza disponibile.";
if(L===6&&failedCount>=3) warning="⚠ RAID6 con 3+ FAILED → array FAILED.";
vmsg.textContent=warning;
const ts=Date.now();
// payload custom (v=2 = custom)
const payload={v:2,level,n,size,faults,classe,data,durata,note,ts};
const code=teacherEncode(payload);
$("teacherCodeText").textContent=code;
$("teacherCodeMeta").innerHTML=`Generato il ${new Date(ts).toLocaleString("it-IT")} · RAID${level}/${n} dischi/${size}GB`;
$("tcArray").textContent=`RAID${level} · ${n} dischi · ${size} GB/disco`;
$("tcFaults").textContent=faults.length?faults.map(f=>`${f.dev}${f.type}`).join(", "):"nessun guasto preimpostato";
$("tcClasse").textContent=classe;
$("tcData").textContent=data;
$("tcDurata").textContent=durata?`${durata} minuti`:"Nessun timer";
// obiettivo automatico in base ai guasti
let goal="Analisi array RAID e diagnosi stato.";
if(faults.some(f=>f.type==="FAILED")){
const canRebuild=(L===5&&failedCount===1)||(L===6&&failedCount<=2)||(L===1&&failedCount<n)||(L===10&&failedCount<n/2);
goal=canRebuild?"Rimuovere disco/i guasti e completare rebuild fino a stato OK.":"Diagnosticare array FAILED e concludere con restore da backup.";
} else if(faults.some(f=>f.type==="CRC")) goal="Diagnosticare errori CRC, identificare il disco, concludere con sostituzione cavo SATA.";
else if(faults.some(f=>f.type==="OVERHEAT")) goal="Diagnosticare temperatura critica con smartctl e dmesg, concludere con intervento di raffreddamento.";
else if(faults.some(f=>f.type==="SLOW")) goal="Identificare disco lento, valutare sostituzione preventiva prima del guasto.";
$("tcGoal").textContent=goal;
$("teacherCodeOutput").style.display="block";
state._teacherPayload=payload;
}
// ── CARICA PAYLOAD CUSTOM NEL LAB ──────────────────────────────────
function loadTeacherPayloadInLab(payload){
if(!payload)return;
const L=Number(payload.level||5);
const n=Number(payload.n||4);
const size=Number(payload.size||1000);
const faults=payload.faults||[];
// reset array con parametri custom
raidLevelEl.value=String(L);
state.raidLevel=L;
diskCountEl.value=String(n);
diskSizeEl.value=String(size);
state.diskSizeGB=size;
state.disks=Array.from({length:n},(_,i)=>makeDisk(i,size));
state.spares=[];
if(rebuildTimer){clearInterval(rebuildTimer);rebuildTimer=null;}
state.rebuild={active:false,start:0,etaSec:0,progress:0,realSec:0,speedFactor:1};
state.score=0;state.hints=0;state.actions=[];
state.mode="EXERCISE";state.exerciseOn=true;
// applica guasti
faults.forEach(f=>{
const d=state.disks[f.idx];
if(!d)return;
if(f.type==="FAILED"){d.state=DiskState.FAILED;d.smart.realloc=130;d.smart.pending=14;pushDmesg("warn",`${d.dev}: I/O error, disk FAILED (docente)`);}
else if(f.type==="CRC"){d.state=DiskState.CRC;d.smart.crc=22;pushDmesg("warn",`${d.dev}: UDMA CRC errors (docente)`);}
else if(f.type==="OVERHEAT"){d.state=DiskState.OVERHEAT;d.smart.temp=60;pushDmesg("warn",`${d.dev}: temperature critical (docente)`);}
else if(f.type==="SLOW"){d.state=DiskState.SLOW;pushDmesg("info",`${d.dev}: device responding slow (docente)`);}
});
// scenario generico custom
const faultSummary=faults.length?faults.map(f=>`${f.dev}${f.type}`).join(", "):"nessun guasto preimpostato";
state.scenario={
name:"custom_docente",
goalHtml:`Prova docente: RAID${L} · ${n} dischi · ${size}GB/disco.<br>Guasti: <b>${faultSummary}</b>.<br>${payload.note?`<i>${payload.note}</i>`:""}`,
solution:[],checkpoints:{diag:false,ok:false},done:false,startedAt:Date.now(),seed:`custom-${payload.ts}`
};
if(payload.durata>0)setTimerMinutes(payload.durata);
termPrint(`▶ PROVA DOCENTE caricata — RAID${L} · ${n} dischi · ${size}GB`,"info");
termPrint(` Guasti : ${faultSummary}`,"dim");
termPrint(` Classe : ${payload.classe||"—"}`,"dim");
termPrint(` Data : ${payload.data||"—"}`,"dim");
termPrint(` Durata : ${payload.durata?payload.durata+" min":"nessun timer"}`,"dim");
if(payload.note)termPrint(` Note : ${payload.note}`,"dim");
pushDmesg("info",`md0: created RAID${L} with ${n} disks (docente)`);
showPage("tab-lab");
render();scenarioCheck();
}
function loadCodeFromLabInput(){
const raw=$("labCodeInput").value;
const fb=$("labCodeFeedback");
if(!raw.trim()){fb.style.color="var(--warn)";fb.textContent="⚠ Incolla un codice prima di procedere.";return;}
const payload=teacherDecode(raw);
if(!payload){fb.style.color="var(--bad)";fb.textContent="✗ Codice non valido o corrotto. Controlla di averlo copiato per intero.";return;}
const label=payload.v===2?`RAID${payload.level}·${payload.n}dischi·${payload.size}GB`:(payload.scn||"?");
fb.style.color="var(--ok)";fb.textContent=`✓ Codice valido — ${label}${payload.classe&&payload.classe!=="—"?" · "+payload.classe:""}`;
setTimeout(()=>{ $("labCodeInput").value="";fb.textContent=""; },2000);
// supporta sia payload v1 (scenario predefinito) che v2 (custom)
if(payload.v===2) loadTeacherPayloadInLab(payload);
else {
if(payload.scn) loadScenario(payload.scn);
if(payload.durata>0)setTimerMinutes(payload.durata);
showPage("tab-lab");render();
}
}
// Init form dinamico
$("btnGenTeacherCode").addEventListener("click",generateTeacherCode);
$("btnLoadTeacherInLab").addEventListener("click",()=>{
if(state._teacherPayload)loadTeacherPayloadInLab(state._teacherPayload);
else termPrint("Genera prima un codice prova.","warn");
});
$("btnCopyTeacherCode").addEventListener("click",()=>{
const t=$("teacherCodeText").textContent;
if(t&&t!=="—"){navigator.clipboard.writeText(t).then(()=>{$("btnCopyTeacherCode").textContent="✓ Copiato!";setTimeout(()=>$("btnCopyTeacherCode").textContent="⎘ Copia",2000)});}
});
$("btnLoadCodeInLab").addEventListener("click",loadCodeFromLabInput);
$("labCodeInput").addEventListener("keydown",(e)=>{if(e.key==="Enter")loadCodeFromLabInput();});
$("tfDiskSize").addEventListener("input",tfUpdateDiskFaultRows);
// pre-fill today & init rows
(()=>{ $("tfData").value=new Date().toISOString().slice(0,10); tfUpdateDiskFaultRows(); })();
$("btnApply").addEventListener("click",createArray);
$("btnResetDisks").addEventListener("click",resetDiskStates);
$("btnRandomFault").addEventListener("click",randomFault);
$("btnStartRandomExercise").addEventListener("click",randomExercise);
$("btnTeacher").addEventListener("click",()=>{
state.teacher=!state.teacher;
$("btnTeacher").textContent=`Teacher: ${state.teacher?"ON":"OFF"}`;
if(state.teacher)$("btnTeacher").classList.add("on");
else $("btnTeacher").classList.remove("on");
render();
});
$("btnTimer5").addEventListener("click",()=>setTimerMinutes(5));
$("btnTimer10").addEventListener("click",()=>setTimerMinutes(10));
$("btnTimerOff").addEventListener("click",()=>setTimerMinutes(0));
$("btnExport").addEventListener("click",exportReport);
document.querySelectorAll("[data-scn]").forEach(btn=>btn.addEventListener("click",()=>loadScenario(btn.getAttribute("data-scn"))));
$("btnGenExercise").addEventListener("click",generateExercise);
$("btnLoadExercise").addEventListener("click",loadGeneratedExerciseIntoLab);
function submit(){ const cmd=termInput.value; termInput.value=""; runCommand(cmd); render(); }
btnRun.addEventListener("click",submit);
termInput.addEventListener("keydown",(e)=>{
if(e.key==="Enter"){e.preventDefault();submit();}
else if(e.key==="ArrowUp"){e.preventDefault();if(!history.length)return;histIdx=Math.max(0,histIdx-1);termInput.value=history[histIdx]||"";}
else if(e.key==="ArrowDown"){e.preventDefault();if(!history.length)return;histIdx=Math.min(history.length,histIdx+1);termInput.value=history[histIdx]||"";}
});
termPrint("RAID LAB ULTIMATE — Offline","info");
termPrint("Suggerimento: 'scenario list' oppure tab ESERCIZI → Genera prova.","dim");
termPrint("Teacher mode (opzionale): abilita click dischi + comando 'solution'.","dim");
createArray();
render();
})();
// Procedure panel navigation (global scope)
function showProc(id, btn){
document.querySelectorAll(".proc-section").forEach(s=>s.classList.remove("active"));
document.querySelectorAll(".proc-nav-btn").forEach(b=>b.classList.remove("active"));
const section=document.getElementById("proc-"+id);
if(section)section.classList.add("active");
if(btn)btn.classList.add("active");
}
function twoDigits(n){return n.toString().padStart(2,"0");}
</script>
</body>
</html>