From 3676637de35c5e584d43597970630b62aad94465 Mon Sep 17 00:00:00 2001 From: bryancito1090 Date: Wed, 16 Sep 2026 16:54:37 -0500 Subject: [PATCH] refactor: overhaul incident simulator component for enhanced user experience and functionality - Replaced the existing HTML structure with a new layout focusing on a Cyber-Defense SOC Dashboard. - Streamlined the simulation configuration panel, including operation mode selection and target input. - Updated the simulation execution logic to utilize a more reactive approach with signals. - Removed deprecated logging and simulation stages, simplifying the overall codebase. - Enhanced error handling and loading states for better user feedback during simulations. - Introduced new metrics and forensic evidence display for improved simulation results. --- src/frontend/src/app/app.config.ts | 5 +- .../incident-simulator.component.css | 305 +++++----- .../incident-simulator.component.html | 529 +++++++----------- .../incident-simulator.component.ts | 221 ++------ 4 files changed, 433 insertions(+), 627 deletions(-) diff --git a/src/frontend/src/app/app.config.ts b/src/frontend/src/app/app.config.ts index 1c8ce0a..a9b50b5 100644 --- a/src/frontend/src/app/app.config.ts +++ b/src/frontend/src/app/app.config.ts @@ -6,6 +6,8 @@ import { RetroScorePort } from './domain/ports/retro-score.port'; import { LocalStorageRetroScoreAdapter } from './infrastructure/adapters/local-storage-retro-score.adapter'; import { ChatPort } from './domain/ports/chat.port'; import { N8nChatAdapter } from './infrastructure/adapters/n8n-chat.adapter'; +import { SecurityAgentPort } from './domain/ports/security-agent.port'; +import { SecurityAgentService } from './services/security-agent.service'; export const appConfig: ApplicationConfig = { providers: [ @@ -13,6 +15,7 @@ export const appConfig: ApplicationConfig = { provideRouter(routes, withComponentInputBinding()), provideHttpClient(), { provide: RetroScorePort, useClass: LocalStorageRetroScoreAdapter }, - { provide: ChatPort, useClass: N8nChatAdapter } + { provide: ChatPort, useClass: N8nChatAdapter }, + { provide: SecurityAgentPort, useClass: SecurityAgentService } ] }; diff --git a/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.css b/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.css index 10e4765..f4d3397 100644 --- a/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.css +++ b/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.css @@ -1,126 +1,179 @@ -:host{display:block;width:100%} -.zen-sim-container{max-width:1540px;margin:0 auto;padding:0 24px 40px;display:flex;flex-direction:column;gap:16px;width:100%;box-sizing:border-box} -.sim-section-header,.report-header,.console-titlebar,.arch-metrics-bar{display:flex;align-items:center;justify-content:space-between} -.sim-section-header{width:100%;padding-bottom:8px;border-bottom:1px solid #1e2533;margin-bottom:4px;flex-wrap:wrap;gap:12px} -.header-left,.report-header-left,.email-input-wrapper,.story-step-card,.arch-node,.node-info,.secops-console-frame,.forensic-report-card,.report-metric-box,.metric-card{display:flex;flex-direction:column} -.header-left{gap:4px} -.title-with-badge,.email-label,.action-buttons,.sim-trigger-btn,.mode-btn,.console-cursor-row,.download-report-btn{display:flex;align-items:center} -.title-with-badge{gap:12px} -.section-tag,.step-badge,.node-badge-tag,.report-badge,.box-label,.metric-label,.console-title,.console-pill,.log-row,.status-pill,.step-meta,.node-detail,.node-meta-ip,.node-meta-svc,.email-field,.box-val,.metric-val,.console-prompt{font-family:var(--font-mono,monospace)} -.section-tag{font-size:.6875rem;color:#5e697d;letter-spacing:.08em} -.pulse-indicator{width:10px;height:10px;border-radius:50%;background:#c59b27;box-shadow:0 0 12px #c59b27;flex-shrink:0} -.section-title{margin:0;font-size:clamp(1.6rem,3vw,2.25rem);font-weight:800;color:#f0f3f8;line-height:1.15;text-transform:uppercase} -.header-controls{display:flex;align-items:center;gap:12px;flex-wrap:wrap} -.view-mode-toggle{display:inline-flex;background:#0d1117;border:1px solid #1e2533;border-radius:4px;padding:2px;gap:2px} -.mode-btn{gap:6px;padding:5px 10px;background:0 0;border:none;border-radius:2px;color:#9aa5b8;font-size:.75rem;font-weight:600;cursor:pointer} -.mode-btn.active{background:#131720;color:#c59b27;box-shadow:0 1px 4px rgba(0,0,0,.4)} -.mode-icon{width:13px;height:13px} -.status-pill{gap:8px;background:rgba(30,41,59,.6);border:1px solid rgba(255,255,255,.1);padding:6px 14px;border-radius:999px;font-size:.75rem;font-weight:600;color:#9aa5b8} -.status-beacon{width:8px;height:8px;border-radius:50%;background:#38bdf8;box-shadow:0 0 8px #38bdf8;animation:pulseBeacon 2s infinite ease-in-out} -.status-pill.danger .status-beacon,.dot.red{background:#ef4444} -.status-pill.active .status-beacon,.dot.yellow{background:#eab308} -.status-pill.contained .status-beacon,.dot.green{background:#22c55e} -.zen-sim-board{background:#0a0d14;border:1px solid #1e2533;border-radius:6px;padding:24px;display:flex;flex-direction:column;gap:20px} -.email-control-deck{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;background:#10141d;border:1px solid #1c2331;border-radius:4px;padding:14px 18px;flex-wrap:wrap} -.email-input-wrapper{gap:6px;flex:1;min-width:260px} -.email-label{gap:6px;font-size:.78rem;font-weight:600;color:#9aa5b8} -.email-icon{width:14px;height:14px;color:#c59b27} -.email-field{background:#07090e;border:1px solid #1e2533;border-radius:3px;color:#f0f3f8;font-size:.8125rem;padding:8px 12px;outline:0} -.email-field:focus{border-color:#c59b27;box-shadow:0 0 6px rgba(197,155,39,.25)} -.action-buttons{gap:10px} -.sim-trigger-btn{gap:8px;background:#c59b27;color:#07080b;border:1px solid #e2b94a;border-radius:3px;padding:9px 18px;font-size:.84rem;font-weight:700;cursor:pointer} -.sim-trigger-btn:hover:not(:disabled){background:#d4af37;box-shadow:0 0 12px rgba(197,155,39,.4)} -.sim-trigger-btn:disabled{opacity:.6;cursor:not-allowed} -.sim-reset-btn{background:0 0;color:#9aa5b8;border:1px solid #2d3748;border-radius:3px;padding:8px 14px;font-size:.8125rem;font-weight:600;cursor:pointer} -.story-grid-flow{display:flex;align-items:stretch;justify-content:space-between;gap:8px;width:100%} -.story-step-card{flex:1;background:#11151f;border:1px solid #1e293b;border-radius:4px;padding:16px 14px;gap:8px;transition:all .3s} -.step-badge,.node-badge-tag{font-size:.625rem;font-weight:700;letter-spacing:.08em} -.step-badge{color:#64748b} -.step-icon-wrap{width:36px;height:36px;border-radius:4px;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.04);color:#9aa5b8} -.step-icon-wrap svg{width:20px;height:20px} -.node-badge-tag.threat,.step-icon-wrap.danger{color:#f87171;background:rgba(239,68,68,.1)} -.node-badge-tag.detection,.step-icon-wrap.warning{color:#38bdf8;background:rgba(56,189,248,.1)} -.node-badge-tag.remediation,.step-icon-wrap.action{color:#4ade80;background:rgba(34,197,94,.1)} -.node-badge-tag.notification,.step-icon-wrap.success{color:#e2b94a;background:rgba(197,155,39,.1)} -.step-title,.node-title{font-size:.8125rem;font-weight:700;color:#f0f3f8;margin:0} -.step-title{font-size:.875rem} -.step-desc{font-size:.75rem;color:#9aa5b8;line-height:1.45;margin:0;flex-grow:1} -.step-meta{font-size:.6875rem;color:#64748b;border-top:1px solid #1c2331;padding-top:6px} -.story-step-card.active{border-color:#c59b27;background:#171b26;box-shadow:0 0 16px rgba(197,155,39,.2);transform:translateY(-2px)} -.story-step-card.completed,.arch-node.past{border-color:rgba(34,197,94,.4)} -.story-arrow{display:flex;align-items:center;justify-content:center;color:#2d3748;font-size:1.1rem;font-weight:700;padding:0 4px} -.story-arrow.active{color:#c59b27} -.diagram-canvas{display:grid;grid-template-columns:1fr auto 1fr auto 1fr auto 1fr;align-items:center;gap:8px;width:100%} -.arch-node{background:#11151f;border:1px solid #1e293b;border-radius:4px;padding:12px 10px;gap:6px;position:relative;min-height:125px;box-sizing:border-box} -.node-badge-tag{padding:2px 6px;border-radius:2px;width:fit-content} -.node-icon-frame{width:28px;height:28px;display:flex;align-items:center;justify-content:center;color:#9aa5b8;flex-shrink:0} -.node-svg{width:22px;height:22px;max-width:22px;max-height:22px;display:block} -.node-info{gap:2px} -.node-detail,.node-meta-ip,.node-meta-svc{font-size:.6875rem;color:#9aa5b8} -.node-meta-ip,.node-meta-svc{font-size:.625rem;color:#64748b} -.node-status-indicator{position:absolute;top:8px;right:8px;width:6px;height:6px;border-radius:50%;background:#334155} -.threat-node.active{border-color:#ef4444;background:#1c1316} -.threat-node.active .node-icon-frame,.threat-node.active .node-status-indicator.ping{color:#ef4444;background:#ef4444} -.detection-node.active{border-color:#38bdf8;background:#0f1c29} -.detection-node.active .node-icon-frame,.detection-node.active .node-status-indicator.ping{color:#38bdf8;background:#38bdf8} -.remediation-node.active{border-color:#22c55e;background:#0f2316} -.remediation-node.active .node-icon-frame,.remediation-node.active .node-status-indicator.ping{color:#22c55e;background:#22c55e} -.notification-node.active{border-color:#c59b27;background:#231c0e} -.notification-node.active .node-icon-frame,.notification-node.active .node-status-indicator.ping{color:#c59b27;background:#c59b27} -.arch-node.past .node-status-indicator{background:#22c55e} -.flow-connector{display:flex;align-items:center;justify-content:center;position:relative;height:20px;color:#334155} -.beam-line{position:absolute;top:50%;left:0;right:0;height:2px;background:#1e293b;transform:translateY(-50%)} -.flow-arrow{position:relative;z-index:2;font-size:.625rem;background:#0a0d14;padding:0 2px} -.flow-connector.flowing{color:#38bdf8} -.flow-connector.flowing .beam-line{background:linear-gradient(90deg,#ef4444,#38bdf8,#22c55e)} -.secops-console-frame{background:#06080c;border:1px solid #1e2533;border-radius:4px;overflow:hidden} -.console-titlebar{background:#0f131a;padding:6px 10px;border-bottom:1px solid #1e2533} -.console-dots{display:flex;gap:5px} -.dot{width:7px;height:7px;border-radius:50%} -.console-title{font-size:.6875rem;color:#94a3b8} -.console-pill{font-size:.5625rem;color:#38bdf8;background:rgba(56,189,248,.12);padding:2px 5px;border-radius:2px} -.console-body{padding:8px 10px;min-height:100px;max-height:160px;overflow-y:auto;display:flex;flex-direction:column;gap:4px;font-size:.75rem;line-height:1.4} -.log-row{display:flex;gap:8px;color:#94a3b8} -.log-time{color:#475569;flex-shrink:0} -.log-tag{font-weight:700;flex-shrink:0} -.log-row.warn .log-msg,.log-row.warn .log-tag{color:#f87171} -.log-row.action .log-msg,.log-row.action .log-tag{color:#fde047} -.log-row.success .log-msg,.log-row.success .log-tag{color:#4ade80} -.console-cursor-row{gap:6px;margin-top:3px} -.console-prompt{color:#34d399;font-size:.6875rem} -.cursor-block{width:6px;height:11px;background:#38bdf8} -.forensic-report-card{background:linear-gradient(180deg,#10151f 0%,#0c1017 100%);border:1px solid rgba(197,155,39,.4);border-radius:6px;padding:20px;gap:16px;box-shadow:0 4px 20px rgba(0,0,0,.4);animation:fadeInReport .4s ease-out} -@keyframes fadeInReport{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}} -.report-header{flex-wrap:wrap;gap:12px;border-bottom:1px solid #1e293b;padding-bottom:12px} -.report-header-left{gap:4px} -.report-badge{font-size:.625rem;color:#c59b27;letter-spacing:.1em;font-weight:700} -.report-title{margin:0;font-size:1.05rem;font-weight:800;color:#f0f3f8} -.report-status-pill{background:rgba(34,197,94,.15);border:1px solid rgba(34,197,94,.4);color:#4ade80;font-size:.6875rem;font-weight:700;padding:4px 10px;border-radius:999px} -.report-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px} -.report-metric-box{background:#080b10;border:1px solid #1a2230;border-radius:4px;padding:10px 12px;gap:4px} -.box-label{font-size:.625rem;color:#64748b;text-transform:uppercase} -.box-val{font-size:.8125rem;font-weight:700;color:#f0f3f8} -.box-val.highlight-mail{color:#e2b94a} -.box-val.highlight-time{color:#38bdf8} -.box-val.success{color:#4ade80} -.report-footer{display:flex;justify-content:flex-end} -.download-report-btn{gap:8px;background:0 0;border:1px solid rgba(197,155,39,.5);color:#c59b27;padding:8px 16px;border-radius:3px;font-size:.8125rem;font-weight:600;cursor:pointer} -.download-report-btn:hover{background:rgba(197,155,39,.1);border-color:#c59b27} -.dl-icon{width:15px;height:15px} -.arch-metrics-bar{padding:16px 12px 6px;border-top:1px solid #1a2230;flex-wrap:nowrap;gap:16px;width:100%;box-sizing:border-box} -.metric-card{align-items:center;justify-content:center;gap:4px;text-align:center;flex:1} -.metric-label{font-size:.6875rem;color:#5e697d;text-transform:uppercase;letter-spacing:.04em} -.metric-val{font-size:.875rem;font-weight:700;color:#f0f3f8} -.metric-val.highlight{color:#38bdf8} -.metric-val.success{color:#4ade80} -.metric-divider{width:1px;height:28px;background:#1e293b;flex-shrink:0} -@keyframes pulseBeacon{0%,100%{opacity:.6;transform:scale(1)}50%{opacity:1;transform:scale(1.3)}} -@media (max-width:1024px){ -.story-grid-flow{flex-direction:column} -.story-arrow{transform:rotate(90deg);margin:-4px 0} -.report-grid{grid-template-columns:repeat(2,1fr)} -.diagram-canvas{grid-template-columns:1fr;gap:10px} -.flow-connector{height:16px;width:100%;transform:rotate(90deg)} -.metric-divider{display:none} -.arch-metrics-bar{flex-direction:column;gap:12px} -} \ No newline at end of file +:host { + display: block; + width: 100%; + color: var(--zen-text-primary, #f0f3f8); + font-family: var(--font-sans, 'Plus Jakarta Sans', -apple-system, sans-serif); +} + +.soc-dashboard { + max-width: 1100px; + margin: 0 auto; + padding: 1.5rem 1rem; +} + +.soc-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--zen-border-subtle, #1e293b); + padding-bottom: 1.25rem; + margin-bottom: 1.5rem; + gap: 1rem; + flex-wrap: wrap; +} + +.logo-area { display: flex; align-items: center; gap: 0.85rem; } +.shield-badge { + display: flex; align-items: center; justify-content: center; + width: 44px; height: 44px; + background: rgba(56, 189, 248, 0.12); + border: 1px solid rgba(56, 189, 248, 0.3); + border-radius: var(--zen-radius-sm, 4px); + color: #38bdf8; + flex-shrink: 0; +} +.shield-icon { width: 22px; height: 22px; } +.soc-title { font-size: 1.35rem; font-weight: 700; color: #38bdf8; margin: 0; } +.soc-subtitle { font-size: 0.78rem; color: var(--zen-text-muted, #94a3b8); margin: 0.2rem 0 0; font-family: var(--font-mono, monospace); } +.engine-badge { + background: #111827; color: #94a3b8; padding: 0.35rem 0.75rem; + border-radius: var(--zen-radius-xs, 4px); font-size: 0.78rem; font-family: var(--font-mono, monospace); + border: 1px solid #1e293b; +} + +.control-panel, .results-dashboard { + background: #111827; border: 1px solid #1f2937; + border-radius: var(--zen-radius-sm, 6px); padding: 1.5rem; + margin-bottom: 1.5rem; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); +} + +.panel-tag { display: inline-block; font-family: var(--font-mono, monospace); font-size: 0.72rem; color: #e2b94a; margin-bottom: 0.3rem; } +.panel-title { font-size: 1.15rem; font-weight: 600; color: #f8fafc; margin: 0 0 1.25rem; } + +.form-grid { + display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 1.25rem; margin-bottom: 1.25rem; +} +.form-group label { display: block; font-size: 0.8rem; color: #94a3b8; margin-bottom: 0.4rem; font-weight: 500; } +.form-group input[type="text"], .form-group input[type="email"], .form-group select { + width: 100%; padding: 0.7rem 0.85rem; background: #0f172a; border: 1px solid #334155; + color: #f8fafc; border-radius: var(--zen-radius-xs, 4px); box-sizing: border-box; + font-family: var(--font-mono, monospace); font-size: 0.88rem; transition: border-color 0.15s; +} +.form-group input:focus, .form-group select:focus { outline: none; border-color: #38bdf8; } + +.target-presets { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.45rem; flex-wrap: wrap; } +.preset-label { font-size: 0.72rem; color: #64748b; font-family: var(--font-mono, monospace); } +.preset-btn { + background: #1e293b; border: 1px solid #334155; color: #93c5fd; font-size: 0.7rem; + font-family: var(--font-mono, monospace); padding: 0.15rem 0.45rem; border-radius: 3px; cursor: pointer; +} +.preset-btn:hover { background: #334155; color: #ffffff; } + +.checkbox-group { display: flex; align-items: center; } +.checkbox-label { display: flex; align-items: center; gap: 0.6rem; cursor: pointer; font-size: 0.85rem; color: #cbd5e1; } +.checkbox-label input[type="checkbox"] { width: 18px; height: 18px; accent-color: #0284c7; cursor: pointer; } + +.launch-btn { + width: 100%; padding: 0.85rem 1.25rem; + background: linear-gradient(135deg, #0284c7 0%, #2563eb 100%); + color: #ffffff; border: none; border-radius: var(--zen-radius-sm, 6px); + font-size: 0.95rem; font-weight: 600; cursor: pointer; + display: flex; align-items: center; justify-content: center; gap: 0.5rem; + transition: opacity 0.2s; +} +.launch-btn:hover:not(:disabled) { opacity: 0.92; } +.launch-btn:disabled { opacity: 0.6; cursor: not-allowed; } +.btn-icon { width: 18px; height: 18px; } +.spinner-dot { + width: 10px; height: 10px; border-radius: 50%; background: #38bdf8; + animation: pulse-spin 1s infinite alternate; +} +@keyframes pulse-spin { 0% { transform: scale(0.8); opacity: 0.5; } 100% { transform: scale(1.3); opacity: 1; } } + +.alert-box.error { + background: #450a0a; color: #fca5a5; border: 1px solid #7f1d1d; + padding: 1rem; border-radius: var(--zen-radius-sm, 4px); margin-bottom: 1.5rem; + display: flex; align-items: center; gap: 0.75rem; +} +.alert-icon { width: 20px; height: 20px; flex-shrink: 0; } + +.results-header { + display: flex; justify-content: space-between; align-items: center; + border-bottom: 1px solid #1e293b; padding-bottom: 1rem; margin-bottom: 1.25rem; flex-wrap: wrap; gap: 0.75rem; +} +.results-title-group { display: flex; align-items: center; gap: 0.6rem; } +.live-dot { width: 8px; height: 8px; border-radius: 50%; background: #4ade80; box-shadow: 0 0 8px #4ade80; } +.results-header h2 { font-size: 1.15rem; font-weight: 600; color: #f8fafc; margin: 0; } +.header-badges { display: flex; align-items: center; gap: 0.6rem; } +.status-pill.status-completed { + background: rgba(34, 197, 94, 0.15); color: #4ade80; border: 1px solid rgba(34, 197, 94, 0.4); + padding: 0.3rem 0.65rem; border-radius: var(--zen-radius-xs, 4px); font-size: 0.75rem; + font-family: var(--font-mono, monospace); font-weight: 600; +} +.reset-btn { + background: #1e293b; color: #94a3b8; border: 1px solid #334155; padding: 0.3rem 0.65rem; + border-radius: var(--zen-radius-xs, 4px); font-size: 0.75rem; cursor: pointer; +} +.reset-btn:hover { background: #334155; color: #f8fafc; } + +.metrics-grid { + display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; margin-bottom: 1.5rem; +} +.metric-card { + background: #1e293b; border-radius: var(--zen-radius-xs, 4px); padding: 1.1rem; + text-align: center; border: 1px solid #2d3748; +} +.metric-title { display: block; font-size: 0.75rem; color: #94a3b8; margin-bottom: 0.5rem; text-transform: uppercase; } +.verdict-tag { + display: inline-block; padding: 0.35rem 0.85rem; border-radius: var(--zen-radius-xs, 4px); + font-weight: 700; font-size: 0.85rem; font-family: var(--font-mono, monospace); +} +.verdict-tag.critical_threat { background: #991b1b; color: #fee2e2; border: 1px solid #b91c1c; } +.verdict-tag.attention_required { background: #854d0e; color: #fef08a; border: 1px solid #a16207; } +.verdict-tag.safe { background: #166534; color: #dcfce7; border: 1px solid #15803d; } + +.risk-gauge { font-size: 1.6rem; font-weight: 800; font-family: var(--font-mono, monospace); } +.risk-gauge.high-risk { color: #f87171; } +.risk-gauge.med-risk { color: #facc15; } +.risk-gauge.safe-risk { color: #4ade80; } +.gauge-max { font-size: 0.9rem; color: #64748b; font-weight: 400; } +.metric-value.highlight { font-size: 1.6rem; font-weight: 700; color: #38bdf8; font-family: var(--font-mono, monospace); } +.containment-value { font-size: 0.88rem; font-weight: 600; color: #94a3b8; } +.containment-value.contained { color: #4ade80; } + +.mitre-section { margin-bottom: 1.5rem; } +.mitre-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.6rem; } +.mitre-label { font-size: 0.85rem; color: #cbd5e1; font-weight: 600; } +.mitre-count { font-size: 0.75rem; color: #94a3b8; font-family: var(--font-mono, monospace); } +.mitre-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; } +.chip { + background: #1e293b; border: 1px solid #334155; color: #cbd5e1; + padding: 0.35rem 0.75rem; border-radius: var(--zen-radius-xs, 4px); + font-family: var(--font-mono, monospace); font-size: 0.82rem; display: flex; align-items: center; gap: 0.35rem; +} +.chip-dot { width: 6px; height: 6px; border-radius: 50%; background: #38bdf8; } + +.forensic-box { + background: #090d16; border: 1px dashed #38bdf8; border-radius: var(--zen-radius-xs, 4px); + padding: 1.25rem; margin-top: 1rem; +} +.forensic-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.85rem; flex-wrap: wrap; gap: 0.5rem; } +.forensic-title { font-size: 0.88rem; font-weight: 600; color: #38bdf8; } +.timestamp-tag { font-size: 0.72rem; color: #64748b; font-family: var(--font-mono, monospace); } +.forensic-body p { margin: 0.4rem 0; font-size: 0.82rem; color: #cbd5e1; } +.forensic-body code { + font-family: var(--font-mono, monospace); font-size: 0.82rem; + background: rgba(15, 23, 42, 0.8); padding: 0.15rem 0.4rem; border-radius: 3px; color: #93c5fd; +} +.hash-code { color: #4ade80 !important; word-break: break-all; } +.action-buttons { margin-top: 1rem; } +.report-btn { + display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.65rem 1.25rem; + background: #0284c7; color: #ffffff; text-decoration: none; border-radius: var(--zen-radius-xs, 4px); + font-size: 0.85rem; font-weight: 500; transition: background 0.15s; +} +.report-btn:hover { background: #0369a1; } +.report-icon { width: 16px; height: 16px; } +.external-arrow { font-size: 0.95rem; } \ No newline at end of file diff --git a/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.html b/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.html index 9e34cf7..2f0d5d5 100644 --- a/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.html +++ b/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.html @@ -1,367 +1,222 @@ -
- -
-
-
- -

{{ i18n.t().navSimulator }}

+
+ +
+
+
+ + + +
- -
- -
- -
- - - -
- - -
- - - @if (stage() === 'idle') { - {{ i18n.t().simStatusIdle }} - } @else if (isContained()) { - {{ i18n.t().simStatusContained }} - } @else { - {{ i18n.t().simStatusActive }} - } - +
+

Autonomous Cyber-Defense & Purple Teaming

+

Triage Cognitivo DeepSeek // Orquestador n8n // Evidencia Criptográfica Gotenberg

+ Engine: n8n + DeepSeek + Gotenberg
- -
- -
+ } + diff --git a/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.ts b/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.ts index 02f855a..3d616da 100644 --- a/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.ts +++ b/src/frontend/src/app/presentation/features/incident-simulator/incident-simulator.component.ts @@ -1,188 +1,83 @@ -import { Component, ChangeDetectionStrategy, signal, computed, inject, OnDestroy, OnInit } from '@angular/core'; -import { I18nService } from '../../../application/services/i18n.service'; +import { Component, ChangeDetectionStrategy, signal, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { SecurityAgentService } from '../../../services/security-agent.service'; import { RetroAudioService } from '../../../application/services/retro-audio.service'; - -export type SimulationStage = 'idle' | 'breach' | 'detection' | 'remediation' | 'notification' | 'contained'; -export type SimulatorViewMode = 'story' | 'tech'; - -export interface SecOpsLog { - readonly timestamp: string; - readonly level: 'WARN' | 'INFO' | 'ACTION' | 'SUCCESS'; - readonly tag: string; - readonly message: string; -} +import { + SimulationMode, + SecurityAgentRequest, + SecurityAgentResponse +} from '../../../domain/models/security.model'; @Component({ - selector: 'app-incident-simulator', + selector: 'app-incident-simulator, app-cyber-defense', standalone: true, + imports: [CommonModule, FormsModule], templateUrl: './incident-simulator.component.html', styleUrl: './incident-simulator.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) -export class IncidentSimulatorComponent implements OnInit, OnDestroy { - protected readonly i18n = inject(I18nService); +export class IncidentSimulatorComponent { + private readonly securityService = inject(SecurityAgentService); private readonly audio = inject(RetroAudioService); - protected readonly stage = signal('idle'); - protected readonly viewMode = signal('story'); - protected readonly userEmail = signal(''); - protected readonly logs = signal([]); - protected readonly threatIp = signal('198.51.100.42'); - protected readonly executionTimeMs = signal(184); - protected readonly showReport = signal(false); - protected readonly downloadFeedback = signal(false); + // Parámetros reactivos de configuración + readonly mode = signal('PURPLE_TEAM'); + readonly target = signal('local_sandbox'); + readonly requestedBy = signal('auditor@bryan-bano.com'); + readonly autoIsolate = signal(true); - protected readonly isSimulating = computed(() => { - const s = this.stage(); - return s === 'breach' || s === 'detection' || s === 'remediation' || s === 'notification'; - }); + // Estados de ejecución y telemetría + readonly isLoading = signal(false); + readonly errorMessage = signal(null); + readonly result = signal(null); - protected readonly isContained = computed(() => this.stage() === 'contained'); - - protected readonly targetRecipient = computed(() => { - const email = this.userEmail().trim(); - return email.length > 3 && email.includes('@') ? email : 'secops-team@bryan.dev'; - }); - - private timeoutIds: ReturnType[] = []; - - ngOnInit(): void { - this.initDefaultLogs(); - } - - setViewMode(mode: SimulatorViewMode): void { - if (this.viewMode() === mode) return; + setTargetPreset(value: string): void { this.audio.playClick(); - this.viewMode.set(mode); + this.target.set(value); } - onEmailInput(event: Event): void { - const val = (event.target as HTMLInputElement).value; - this.userEmail.set(val); - } + executeSimulation(): void { + if (this.isLoading()) return; - startSimulation(): void { - if (this.isSimulating()) return; - - this.clearTimeouts(); this.audio.playClick(); - this.logs.set([]); - this.showReport.set(false); - this.downloadFeedback.set(false); + this.isLoading.set(true); + this.errorMessage.set(null); + this.result.set(null); - // Paso 1: Intento de Intrusión (Vector de Amenaza) - this.stage.set('breach'); - this.appendLog({ - level: 'WARN', - tag: 'IAM-ALERT', - message: `Anomalous API request: iam:CreateAccessKey from untrusted IP ${this.threatIp()} for principal "svc-deployer".` + const payload: SecurityAgentRequest = { + mode: this.mode(), + target: this.target().trim() || 'local_sandbox', + requested_by: this.requestedBy().trim() || 'auditor@bryan-bano.com', + parameters: { + auto_isolate: this.autoIsolate(), + scan_depth: 'full' + } + }; + + this.securityService.triggerSimulation(payload).subscribe({ + next: (response) => { + this.result.set(response); + this.isLoading.set(false); + this.audio.playVictory(); + }, + error: (err: Error) => { + this.errorMessage.set(err.message || 'Error al conectar con el orquestador n8n.'); + this.isLoading.set(false); + this.audio.playGameOver(); + } }); - - // Paso 2: Detección Inmediata en EventBridge & CloudTrail (después de 700ms) - const t1 = setTimeout(() => { - this.stage.set('detection'); - this.audio.playCellReveal(); - this.appendLog({ - level: 'INFO', - tag: 'CLOUDTRAIL', - message: 'Management event ingested in us-east-1. EventBridge rule "SecOps-AutoRemediate" triggered synchronously.' - }); - }, 700); - this.timeoutIds.push(t1); - - // Paso 3: Remediación Automatizada Serverless (después de 1500ms) - const t2 = setTimeout(() => { - this.stage.set('remediation'); - this.audio.playFlagToggle(); - this.appendLog({ - level: 'ACTION', - tag: 'LAMBDA', - message: 'Executing fn-iam-incident-response: Revoking active STS sessions & attaching inline DenyAllSecurityPolicy.' - }); - }, 1500); - this.timeoutIds.push(t2); - - // Paso 4: Despacho de Reporte Forense (después de 2300ms) - const t3 = setTimeout(() => { - this.stage.set('notification'); - this.audio.playCellReveal(); - this.appendLog({ - level: 'INFO', - tag: 'SES-ALERT', - message: `Dispatching cryptographic audit report to ${this.targetRecipient()} via Amazon SES.` - }); - }, 2300); - this.timeoutIds.push(t3); - - // Paso 5: Amenaza Contenida & Generación de Reporte (después de 3100ms) - const t4 = setTimeout(() => { - this.stage.set('contained'); - this.audio.playVictory(); - this.showReport.set(true); - this.appendLog({ - level: 'SUCCESS', - tag: 'CONTAINED', - message: `Threat neutralized successfully in ${this.executionTimeMs()}ms. Zero credentials compromised. State: SECURE.` - }); - }, 3100); - this.timeoutIds.push(t4); } resetSimulation(): void { - this.clearTimeouts(); this.audio.playClick(); - this.stage.set('idle'); - this.showReport.set(false); - this.downloadFeedback.set(false); - this.initDefaultLogs(); - } - - simulateDownload(): void { - this.audio.playClick(); - this.downloadFeedback.set(true); - setTimeout(() => { - this.downloadFeedback.set(false); - }, 3000); - } - - private initDefaultLogs(): void { - const time = this.getCurrentTimestamp(); - this.logs.set([ - { - timestamp: time, - level: 'INFO', - tag: 'GUARDDUTY', - message: 'Cloud perimeter telemetry nominal. 0 active security findings in us-east-1.' - }, - { - timestamp: time, - level: 'INFO', - tag: 'EVENTBRIDGE', - message: 'Active rule pattern matched: Rule-SecOps-IAM-AutoRemediation [ENABLED].' - } - ]); - } - - private appendLog(entry: Omit): void { - const timestamp = this.getCurrentTimestamp(); - this.logs.update(prev => [...prev, { ...entry, timestamp }]); - } - - private getCurrentTimestamp(): string { - const now = new Date(); - const h = String(now.getHours()).padStart(2, '0'); - const m = String(now.getMinutes()).padStart(2, '0'); - const s = String(now.getSeconds()).padStart(2, '0'); - const ms = String(now.getMilliseconds()).padStart(3, '0'); - return `${h}:${m}:${s}.${ms}`; - } - - private clearTimeouts(): void { - this.timeoutIds.forEach(id => clearTimeout(id)); - this.timeoutIds = []; - } - - ngOnDestroy(): void { - this.clearTimeouts(); + this.errorMessage.set(null); + this.result.set(null); + this.isLoading.set(false); } } + +export { + IncidentSimulatorComponent as CyberDefenseComponent, + IncidentSimulatorComponent as CyberDefenseDashboardComponent +};