/* cli-sim.jsx — Cisco IOS CLI Simulator component */ function normC(s){ return s.trim().toLowerCase().replace(/\s+/g,' '); } function CLISimulator({ question:q, onComplete, submitted, onLearnTopic, onSkip }) { const [history,setHistory]=React.useState([]); const [input,setInput]=React.useState(''); const [stepIdx,setStepIdx]=React.useState(0); const [done,setDone]=React.useState(false); const [showHint,setShowHint]=React.useState(false); const [cmdHist,setCmdHist]=React.useState([]); const [histPtr,setHistPtr]=React.useState(-1); const termRef=React.useRef(null); const inputRef=React.useRef(null); React.useEffect(()=>{ if(termRef.current) termRef.current.scrollTop=termRef.current.scrollHeight; if(!done && !submitted) inputRef.current?.focus(); },[history,done]); const step=q.steps[stepIdx]; const curPrompt=step?.pb||(q.steps[q.steps.length-1]?.pa||'R1#'); function submit(cmdOverride){ const cmd = cmdOverride!==undefined ? cmdOverride : input.trim(); if(!cmd)return; if(cmdOverride===undefined){ setCmdHist(h=>[cmd,...h].slice(0,30)); setHistPtr(-1); setInput(''); } else { setHistPtr(-1); setInput(''); } if(!step||done)return; if(cmd==='?'){ setHistory(h=>[...h,{prompt:curPrompt,cmd,out:`\n Hint: ${step.hint||'Enter the expected command.'}`,type:'help'}]); return; } // Ctrl-Z aliases → treat as 'end' (IOS standard) const ctrlZAliases = ['^z','ctrl-z','cntl-z','ctrl/z','cntl/z','ctrlz','cntlz','^[z']; const isCtrlZ = ctrlZAliases.includes(cmd.toLowerCase().trim()); const ok = step.accept.some(a=>normC(cmd)===normC(a)) || (isCtrlZ && step.accept.some(a=>['end','exit'].includes(normC(a)))); if(ok){ setHistory(h=>[...h,{prompt:curPrompt,cmd,out:step.out||'',type:'ok'}]); if(stepIdx===q.steps.length-1){ setDone(true); onComplete&&onComplete(true); } else { setStepIdx(i=>i+1); } } else { const spaces=' '.repeat(Math.min(curPrompt.length+cmd.length,36)); setHistory(h=>[...h,{prompt:curPrompt,cmd,out:`${spaces}^\n% Invalid input detected at '^' marker.\n\nType '?' for a list of commands.`,type:'err'}]); } } function keyDown(e){ if(e.key==='Enter'){e.preventDefault();submit();} else if(e.key==='ArrowUp'){e.preventDefault();const i=Math.min(histPtr+1,cmdHist.length-1);setHistPtr(i);setInput(cmdHist[i]||'');} else if(e.key==='ArrowDown'){e.preventDefault();const i=Math.max(histPtr-1,-1);setHistPtr(i);setInput(i===-1?'':cmdHist[i]||'');} else if((e.key==='z'||e.key==='Z')&&(e.ctrlKey||e.metaKey)){e.preventDefault();submit('^Z');} else if(e.key==='Tab'){e.preventDefault(); /* could add tab completion */ } } function resetSim(){setHistory([]);setStepIdx(0);setDone(false);setInput('');setHistPtr(-1);} const pctDone=done?100:Math.round((stepIdx/q.steps.length)*100); return(
{/* Task card */}
▶ Task
{q.task}
{onLearnTopic&&( )}
{q.steps.map((_,i)=>(
))}
{done?'Complete':stepIdx+1+'/'+q.steps.length}
{/* progress bar */}
{/* Terminal window */}
!done&&!submitted&&inputRef.current?.focus()}>
Cisco IOS — Type ? for help, ↑↓ for history
{history.map((h,i)=>(
{h.prompt} {h.cmd}
{h.out&&
{h.out}
}
))} {done&&
All steps complete!
} {!done&&!submitted&&(
{curPrompt} setInput(e.target.value)} onKeyDown={keyDown} style={{width:Math.max(input.length,0)+'ch'}} spellCheck={false} autoComplete="off" autoCorrect="off" autoCapitalize="off"/>
)} {submitted&&!done&&
-- session ended --
}
{/* Bottom bar */}
{!done&&!submitted&&} {!submitted&&} {onSkip&&!done&&!submitted&&}
{showHint&&!done&&step&&(
Step {stepIdx+1} hint {step.hint}
)} {done&&(
✓ Task Complete
{q.exp}
)}
); } Object.assign(window,{CLISimulator});