4096 lines
240 KiB
HTML
4096 lines
240 KiB
HTML
<!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> ·
|
||
<span>ETA simulato: <b id="rbEtaSim">—</b></span> ·
|
||
<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 & recovery)</h2>
|
||
<div class="pill"><span class="focus-badge">Focus</span> 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: A B<br>
|
||
A B<br><br>
|
||
Mirror 2: C D<br>
|
||
C 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" > /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> > 0 → settori ricollocati: disco che sta cedendo</li>
|
||
<li><b>Current_Pending_Sector</b> > 0 → settori in attesa di ricollocazione</li>
|
||
<li><b>UDMA_CRC_Error_Count</b> > 0 → errori di trasmissione (spesso cavo)</li>
|
||
<li><b>Temperature_Celsius</b> > 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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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 >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>
|
||
<45°C = ottimale · 45-55°C = accettabile · 55-60°C = attenzione · >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 > 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 ← 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 ← 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 ← 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 sdb sdc sdd ← 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" > /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 10–15s, calcola fattore di accelerazione
|
||
const didacticSec=Math.round(10+Math.random()*5); // 10–15s
|
||
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>
|