179 lines
16 KiB
JavaScript
179 lines
16 KiB
JavaScript
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))a(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&a(o)}).observe(document,{childList:!0,subtree:!0});function s(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function a(i){if(i.ep)return;i.ep=!0;const r=s(i);fetch(i.href,r)}})();const N={mpls:20,internet:50,lte:60},E={mpls:.2,internet:.3,lte:.5};function P(e,t={}){return e.map(s=>{const a=t[s.id];return{...s,latencyMs:(a==null?void 0:a.latencyMs)??N[s.id]??100,packetLossPercent:(a==null?void 0:a.packetLossPercent)??E[s.id]??1}})}const C=100,x=1;function D(e){return e.latencyMs<=C&&e.packetLossPercent<=x}function W(e){return e.map(t=>({...t,isValid:D(t)}))}function O(e){return M(e.filter(t=>t.isValid))[0]}function R(e){return M(e)[0]}function M(e){return e.sort((t,s)=>{const a=t.packetLossPercent-s.packetLossPercent;return Math.abs(a)>.001?a:t.latencyMs-s.latencyMs})}function _(e){const{links:t,activeLinkId:s,degradationTicks:a}=e;if(t.filter(c=>c.isValid).length===0){const c=R(t);return{activeLinkId:c?c.id:null,systemState:"degraded",degradationTicks:a+1}}const r=O(t),o=t.find(c=>c.id===s);return!o||!o.isValid?{activeLinkId:r.id,systemState:"normal",degradationTicks:0}:{activeLinkId:o.id,systemState:"normal",degradationTicks:0}}const m=[{text:"What does SLA stand for in the context of SD-WAN?",options:["Service Level Agreement","Secure Link Access","System Latency Analysis","Serial Link Aggregation"],answer:0},{text:"What is the maximum acceptable latency threshold in this application?",options:["50 ms","80 ms","100 ms","150 ms"],answer:2},{text:"What is the maximum acceptable packet loss percentage per the SLA?",options:["0.5%","1%","2%","5%"],answer:1},{text:"How many WAN links does the SD-WAN Traffic Master simulate?",options:["2","3","4","5"],answer:1},{text:"Which WAN link type is the default primary path in the simulation?",options:["LTE","Internet","MPLS","Satellite"],answer:2},{text:"What happens when ALL links violate SLA thresholds?",options:["The application crashes","Traffic is silently dropped","The system enters Degraded Mode","All links are automatically reset"],answer:2},{text:"What is the full meaning of SD-WAN?",options:["Software-Defined Wide Area Network","Secure Digital Wireless Access Network","System-Defined Wide Access Node","Software-Driven Wide Application Network"],answer:0},{text:"MPLS, Internet and LTE are responsible for which type of SD-WAN tunnels?",options:["Underlay","Overlay","BIOS","Secure VPN"],answer:0},{text:"Which company powers this SD-WAN Traffic Master application?",options:["ARUBA","Juniper","Sechpoint","All of the above"],answer:2},{text:"What is the role of Business Intent Overlay in SD-WAN?",options:["Defines network policies based on business requirements and applications","Manages physical hardware connections","Encrypts data packets between endpoints","Monitors device CPU usage"],answer:0}];function S(){return m.length}const y=60;function A(){return{screen:"instructions",userName:"",currentQuestion:0,answers:[],timeRemaining:30,timerId:null,active:!1,attemptCount:0}}function F(e){return m[e]}function Q(e){let t=0;for(let s=0;s<m.length;s++)e[s]===m[s].answer&&t++;return{correct:t,total:m.length,percentage:Math.round(t/m.length*100)}}const $={mpls:"#8B5CF6",lte:"#EF4444",internet:"#F59E0B"};function T(e,t,s){return e<=t?"good":e<=s?"warn":"bad"}function B(e){return T(e,80,100)}function H(e){return T(e,.5,1)}function b(e){const{links:t,activeLinkId:s,systemState:a}=e;document.getElementById("app").innerHTML=`
|
|
${U(a)}
|
|
${V()}
|
|
${a==="degraded"?Y():""}
|
|
${K(t,s)}
|
|
${J(t,s,a)}
|
|
${Z()}
|
|
`,G(e)}function U(e){return`
|
|
<header class="header">
|
|
<div>
|
|
<h1>HPE Aruba SD-WAN Traffic Master</h1>
|
|
<p class="header-subtitle">Real-time network traffic management simulation</p>
|
|
<p class="header-powered">Powered by Sechpoint</p>
|
|
<div class="header-thresholds">
|
|
<span class="threshold-badge">Latency ≤ 100 ms</span>
|
|
<span class="threshold-badge">Packet Loss ≤ 1%</span>
|
|
</div>
|
|
</div>
|
|
<div class="header-badges">
|
|
${e==="normal"?'<span class="badge badge-normal">Normal</span>':'<span class="badge badge-degraded">Degraded Mode</span>'}
|
|
</div>
|
|
</header>`}function V(){return`
|
|
<div class="controls">
|
|
<button class="quiz-btn" data-action="start-quiz">🧠 Take Quiz</button>
|
|
</div>`}function Y(){return`
|
|
<div class="degraded-warning">
|
|
<h3>⚠ Degraded Mode — All links violate SLA thresholds</h3>
|
|
<p>The best available link is kept active, but network performance is degraded. Monitor traffic closely.</p>
|
|
</div>`}function K(e,t){return`<div class="link-cards">${e.map(a=>j(a,t)).join("")}</div>`}function j(e,t){const s=e.id===t;return`
|
|
<div class="${["link-card",s?"active-link":"",!e.isValid&&!s?"blocked":""].filter(Boolean).join(" ")}" data-link-id="${e.id}">
|
|
${s?'<span class="active-badge">Active</span>':""}
|
|
|
|
<div class="link-header">
|
|
<span class="link-dot" style="background:${e.color}"></span>
|
|
<span class="link-name">${e.name}</span>
|
|
<span class="link-status ${e.isValid?"status-healthy":"status-blocked"}">
|
|
${e.isValid?"Healthy":"Blocked"}
|
|
</span>
|
|
</div>
|
|
|
|
${q("Latency",`${e.latencyMs} ms`,"100 ms",e.latencyMs,150,B(e.latencyMs),e.latencyMs>100?" ⚠":"",e.id,"latencyMs")}
|
|
|
|
${q("Packet Loss",`${e.packetLossPercent.toFixed(2)}%`,"1.00%",e.packetLossPercent,5,H(e.packetLossPercent),e.packetLossPercent>1?" ⚠":"",e.id,"packetLossPercent")}
|
|
</div>`}function q(e,t,s,a,i,r,o,c,u){const l=Math.min(a/i*100,100);return`
|
|
<div class="metric">
|
|
<div class="metric-label">
|
|
<span>${e}</span>
|
|
<span class="metric-value ${r}">${t}${o}</span>
|
|
</div>
|
|
<div class="metric-bar">
|
|
<div class="metric-bar-fill ${r}" style="width:${l}%"></div>
|
|
</div>
|
|
<div class="metric-limit">SLA limit: ${s}</div>
|
|
<div class="metric-slider">
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max="${u==="latencyMs"?150:5}"
|
|
step="${u==="latencyMs"?1:.01}"
|
|
value="${a}"
|
|
data-link-id="${c}"
|
|
data-field="${u}"
|
|
/>
|
|
<label class="slider-label">${t}</label>
|
|
</div>
|
|
</div>`}function J(e,t,s){const a=t?e.find(c=>c.id===t):null,i=a?a.name:"—",r="",o=a?`background:${$[a.id]}22;color:${$[a.id]};`:"background:#374151;color:#9ca3af;";return`
|
|
<div class="path-section">
|
|
<h3>Active Path ${s==="degraded"?'<span class="badge badge-degraded" style="margin-left:8px">Degraded</span>':""}</h3>
|
|
<div class="path-visual">
|
|
<div class="path-endpoint">
|
|
<span class="path-endpoint-icon">🏢</span>
|
|
<span>Branch Office</span>
|
|
</div>
|
|
<div class="path-line"></div>
|
|
<div class="path-link-badge ${r}" style="${o}">
|
|
${i}
|
|
</div>
|
|
<div class="path-line"></div>
|
|
<div class="path-endpoint">
|
|
<span class="path-endpoint-icon">☁</span>
|
|
<span>Data Center</span>
|
|
</div>
|
|
</div>
|
|
</div>`}function Z(){return`
|
|
<div class="info-footer">
|
|
<p><strong>SLA Thresholds:</strong> Latency ≤ 100 ms | Packet Loss ≤ 1%</p>
|
|
<p><strong>Path Selection:</strong> Lowest packet loss → Lowest latency</p>
|
|
<p><strong>Auto Failover:</strong> Traffic automatically shifts when active link violates SLA</p>
|
|
<p><strong>Degraded Mode:</strong> When all links fail SLA, the best available link remains active</p>
|
|
</div>`}function G(e){document.querySelectorAll("[data-action]").forEach(t=>{t.addEventListener("click",()=>{t.dataset.action==="start-quiz"&&e.onStartQuiz&&e.onStartQuiz()})}),document.querySelectorAll(".metric-slider input").forEach(t=>{t.addEventListener("input",s=>{const a=s.target.dataset.linkId,i=s.target.dataset.field,r=i==="latencyMs"?parseInt(s.target.value,10):parseFloat(s.target.value);e.onUpdateMetric&&e.onUpdateMetric(a,i,r)})})}function X(e,t){var r,o,c,u;const s=document.querySelector(".quiz-overlay");s&&s.remove();const a=document.createElement("div");a.className="quiz-overlay";const i=document.createElement("div");switch(i.className="quiz-container",e.screen){case"instructions":i.innerHTML=ee();break;case"name":i.innerHTML=te();break;case"quiz":i.innerHTML=ne(e);break;case"results":i.innerHTML=se(e);break}if(a.appendChild(i),document.body.appendChild(a),e.screen==="instructions"){const l=i.querySelector(".btn-primary");l&&l.addEventListener("click",t.onProceedToName),(r=i.querySelector(".btn-secondary"))==null||r.addEventListener("click",t.onCloseQuiz)}if(e.screen==="name"){const l=i.querySelector(".name-field"),v=i.querySelector(".name-error"),f=i.querySelector(".btn-primary"),z=()=>{const h=l.value.trim();if(!h){v.classList.add("visible");return}v.classList.remove("visible"),t.onStartQuiz(h)};f==null||f.addEventListener("click",z),l==null||l.addEventListener("keydown",h=>{h.key==="Enter"&&z()}),(o=i.querySelector(".btn-secondary"))==null||o.addEventListener("click",t.onCloseQuiz)}e.screen==="quiz"&&i.querySelectorAll(".quiz-option").forEach(l=>{l.addEventListener("click",()=>{const v=parseInt(l.dataset.optionIndex,10);t.onAnswerQuestion(e.currentQuestion,v)})}),e.screen==="results"&&((c=i.querySelector(".btn-primary"))==null||c.addEventListener("click",t.onCloseQuiz),(u=i.querySelector(".btn-secondary"))==null||u.addEventListener("click",t.onRestartQuiz))}function ee(){return`
|
|
<div class="quiz-header">
|
|
<h2>SD-WAN Knowledge Quiz</h2>
|
|
<p>Test your understanding of the HPE Aruba SD-WAN Traffic Master</p>
|
|
</div>
|
|
<div class="quiz-body">
|
|
<div class="study-note">
|
|
⚠️ <strong>Important:</strong> Please study the application dashboard carefully
|
|
before starting the quiz. Observe how the simulation works, understand the SLA
|
|
thresholds, failover behavior, and the different link types. You will have
|
|
<strong>30 seconds</strong> to answer each of the <strong>10 questions</strong>.
|
|
</div>
|
|
<div class="quiz-instructions">
|
|
<h3>What you need to know:</h3>
|
|
<ul>
|
|
<li>SLA thresholds for latency and packet loss</li>
|
|
<li>The three WAN link types and their roles</li>
|
|
<li>Automatic path selection and failover rules</li>
|
|
<li>Path selection and failover rules</li>
|
|
<li>What happens during Degraded Mode</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div class="quiz-footer quiz-actions">
|
|
<button class="btn-secondary">Cancel</button>
|
|
<button class="btn-primary">Next →</button>
|
|
</div>
|
|
`}function te(){return`
|
|
<div class="quiz-header">
|
|
<h2>Enter Your Name</h2>
|
|
<p>Your results will be saved under this name</p>
|
|
</div>
|
|
<div class="quiz-body">
|
|
<input
|
|
class="name-field"
|
|
type="text"
|
|
placeholder="Type your name here..."
|
|
maxlength="40"
|
|
autocomplete="off"
|
|
/>
|
|
<div class="name-error">Please enter your name to continue.</div>
|
|
</div>
|
|
<div class="quiz-footer quiz-actions">
|
|
<button class="btn-secondary">Cancel</button>
|
|
<button class="btn-primary">Start Quiz</button>
|
|
</div>
|
|
`}function ne(e){const t=e._questionData;if(!t)return"";const s=e.currentQuestion+1,a=e._totalQuestions,i=e.currentQuestion/a*100,r=e.timeRemaining<=10?" urgent":"",o=["A","B","C","D"];return`
|
|
<div class="quiz-header">
|
|
<h2>Question ${s} of ${a}</h2>
|
|
<div class="quiz-name-badge">Player: ${e.userName}</div>
|
|
</div>
|
|
<div class="quiz-body">
|
|
<div class="quiz-progress">
|
|
<div class="quiz-progress-bar">
|
|
<div class="quiz-progress-fill" style="width:${i}%"></div>
|
|
</div>
|
|
<div class="quiz-timer${r}">${e.timeRemaining}s</div>
|
|
</div>
|
|
<div class="quiz-question-text">${t.text}</div>
|
|
<div class="quiz-options">
|
|
${t.options.map((c,u)=>`
|
|
<div class="quiz-option" data-option-index="${u}">
|
|
<span class="option-letter">${o[u]}</span>
|
|
<span>${c}</span>
|
|
</div>
|
|
`).join("")}
|
|
</div>
|
|
</div>
|
|
`}function se(e){const t=e._results;if(!t)return"";const s=t.percentage>=y,a=!s&&e.attemptCount<2;let i="",r="",o="";return s?(i="Congratulations! You passed the quiz.",r='<div class="quiz-prize">🎉 Come to the <strong>Sechpoint Technologies</strong> booth to claim your prize!</div>'):a?(i=`You scored below ${y}%. You have one more attempt.`,o='<p class="quiz-retry-note">Study the dashboard carefully and try again.</p>'):i=`You scored below ${y}%. No more attempts allowed.`,`
|
|
<div class="quiz-header">
|
|
<h2>Quiz Complete!</h2>
|
|
<div class="quiz-name-badge">${e.userName}</div>
|
|
</div>
|
|
<div class="quiz-body quiz-results">
|
|
<div class="quiz-score-circle ${s?"":"quiz-fail"}">
|
|
<span class="quiz-score-number">${t.percentage}%</span>
|
|
<span class="quiz-score-label">SCORE</span>
|
|
</div>
|
|
<p class="quiz-score-detail">
|
|
${t.correct} out of ${t.total} correct | Pass mark: ${y}% | Attempt ${e.attemptCount} of 2
|
|
</p>
|
|
<p class="quiz-feedback">${i}</p>
|
|
${r}
|
|
${o}
|
|
</div>
|
|
<div class="quiz-footer quiz-actions">
|
|
${a?'<button class="btn-secondary">Retake Quiz</button>':""}
|
|
<button class="btn-primary">Return to Dashboard</button>
|
|
</div>
|
|
`}const ie=[{id:"mpls",name:"MPLS",latencyMs:20,packetLossPercent:.2,color:"#8B5CF6"},{id:"lte",name:"LTE",latencyMs:60,packetLossPercent:.5,color:"#EF4444"},{id:"internet",name:"Internet",latencyMs:50,packetLossPercent:.3,color:"#F59E0B"}];let d={links:structuredClone(ie).map(e=>({...e,isValid:!0})),activeLinkId:"mpls",systemState:"normal",degradationTicks:0,tick:0,metricOverrides:{}},w=null;function I(){let e=P(d.links,d.metricOverrides);e=W(e);const t=_({...d,links:e});d={...d,links:e,activeLinkId:t.activeLinkId,systemState:t.systemState,degradationTicks:t.degradationTicks,tick:d.tick+1},b(k())}function k(){return{...d,onStartQuiz:ce,onUpdateMetric:ae}}function ae(e,t,s){d.metricOverrides={...d.metricOverrides,[e]:{...d.metricOverrides[e],[t]:s}},I()}function re(){w||(w=setInterval(I,1500))}let n=A();function p(){n.active&&requestAnimationFrame(()=>{n.active&&(n._questionData=F(n.currentQuestion),n._totalQuestions=S(),X(n,{onCloseQuiz:le,onProceedToName:()=>{n.screen="name",p()},onStartQuiz:e=>{n.userName=e,n.screen="quiz",n.currentQuestion=0,n.answers=[],n.timeRemaining=30,p(),g()},onAnswerQuestion:(e,t)=>{L(),n.answers[e]=t,n.currentQuestion<S()-1?(n.currentQuestion++,n.timeRemaining=30,p(),g()):(n.attemptCount++,n._results=Q(n.answers),n.screen="results",p())},onRestartQuiz:()=>{L();const e=n.userName,t=n.attemptCount;n=A(),n.active=!0,n.userName=e,n.attemptCount=t,n.screen="quiz",n.currentQuestion=0,n.answers=[],n.timeRemaining=30,p(),g()}}))})}function g(){L();let e=30;n.timeRemaining=e,n.timerId=setInterval(()=>{e--,n.timeRemaining=e,e<=0?(clearInterval(n.timerId),n.timerId=null,n.answers[n.currentQuestion]=-1,n.currentQuestion<S()-1?(n.currentQuestion++,n.timeRemaining=30,p(),g()):(n.attemptCount++,n._results=Q(n.answers),n.screen="results",p())):oe(e)},1e3)}function oe(e){const t=document.querySelector(".quiz-timer");t&&(t.textContent=`${e}s`,e<=10?t.classList.add("urgent"):t.classList.remove("urgent"))}function L(){n.timerId&&(clearInterval(n.timerId),n.timerId=null)}function ce(){n=A(),n.active=!0,n.screen="instructions",b(k()),p()}function le(){L(),n.active=!1;const e=document.querySelector(".quiz-overlay");e&&e.remove(),b(k())}b(k());re();
|