diff --git a/services/dashboard/ExplainButton.jsx b/services/dashboard/ExplainButton.jsx
new file mode 100644
index 0000000..efdcccc
--- /dev/null
+++ b/services/dashboard/ExplainButton.jsx
@@ -0,0 +1,345 @@
+/**
+ * ExplainButton Component
+ * Reusable inline "Explain" button for configs, logs, and errors
+ * Shows modal/popover with LLM-powered explanation
+ */
+
+import React, { useState } from 'react';
+
+const ExplainButton = ({
+ type = 'config', // config, log, error, scan_result
+ content,
+ context = {},
+ size = 'small',
+ style = {}
+}) => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [showModal, setShowModal] = useState(false);
+ const [explanation, setExplanation] = useState(null);
+ const [error, setError] = useState(null);
+
+ const handleExplain = async () => {
+ setIsLoading(true);
+ setError(null);
+ setShowModal(true);
+
+ try {
+ const response = await fetch('/api/explain', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ type,
+ content,
+ context
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to get explanation');
+ }
+
+ const data = await response.json();
+ setExplanation(data);
+ } catch (err) {
+ console.error('Error getting explanation:', err);
+ setError('Failed to load explanation. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const closeModal = () => {
+ setShowModal(false);
+ setExplanation(null);
+ setError(null);
+ };
+
+ const buttonSizes = {
+ small: { padding: '4px 8px', fontSize: '12px' },
+ medium: { padding: '6px 12px', fontSize: '14px' },
+ large: { padding: '8px 16px', fontSize: '16px' }
+ };
+
+ const buttonStyle = {
+ ...buttonSizes[size],
+ backgroundColor: '#3498DB',
+ color: 'white',
+ border: 'none',
+ borderRadius: '4px',
+ cursor: 'pointer',
+ display: 'inline-flex',
+ alignItems: 'center',
+ gap: '4px',
+ transition: 'background-color 0.2s',
+ ...style
+ };
+
+ const renderExplanation = () => {
+ if (error) {
+ return (
+
+ {error}
+
+ );
+ }
+
+ if (isLoading) {
+ return (
+
+
⏳
+
Generating explanation...
+
+ );
+ }
+
+ if (!explanation) {
+ return null;
+ }
+
+ // Render based on explanation type
+ switch (type) {
+ case 'config':
+ return (
+
+
+ {explanation.config_key || 'Configuration'}
+
+
+
+ Current Value:
+
+ {explanation.current_value}
+
+
+
+
+
What it does:
+
{explanation.description}
+
+
+ {explanation.example && (
+
+
Example:
+
{explanation.example}
+
+ )}
+
+ {explanation.value_analysis && (
+
+ Analysis: {explanation.value_analysis}
+
+ )}
+
+ {explanation.recommendations && explanation.recommendations.length > 0 && (
+
+
Recommendations:
+
+ {explanation.recommendations.map((rec, i) => (
+ - {rec}
+ ))}
+
+
+ )}
+
+
+ {explanation.requires_restart && (
+
⚠️ Changing this setting requires a restart
+ )}
+ {!explanation.safe_to_change && (
+
⚠️ Use caution when changing this setting
+ )}
+
+
+ );
+
+ case 'error':
+ return (
+
+
+ Error Explanation
+
+
+
+
Original Error:
+
+ {explanation.original_error}
+
+
+
+
+
What went wrong:
+
{explanation.plain_english}
+
+
+
+
Likely causes:
+
+ {explanation.likely_causes?.map((cause, i) => (
+ - {cause}
+ ))}
+
+
+
+
+
💡 How to fix it:
+
+ {explanation.suggested_fixes?.map((fix, i) => (
+ - {fix}
+ ))}
+
+
+
+
+ Severity:
+ {(explanation.severity || 'unknown').toUpperCase()}
+
+
+
+ );
+
+ case 'log':
+ return (
+
+
+ Log Entry Explanation
+
+
+
+ {explanation.log_entry}
+
+
+
+ Level:
+
+ {explanation.log_level}
+
+
+
+ {explanation.timestamp && (
+
+ Time: {explanation.timestamp}
+
+ )}
+
+
+
What this means:
+
{explanation.explanation}
+
+
+ {explanation.action_needed && explanation.next_steps && explanation.next_steps.length > 0 && (
+
+
⚠️ Action needed:
+
+ {explanation.next_steps.map((step, i) => (
+ - {step}
+ ))}
+
+
+ )}
+
+ );
+
+ default:
+ return (
+
+
{explanation.explanation || 'No explanation available.'}
+
+ );
+ }
+ };
+
+ return (
+ <>
+
+
+ {showModal && (
+
+
e.stopPropagation()}
+ >
+
+
Explanation
+
+
+
+ {renderExplanation()}
+
+
+ )}
+ >
+ );
+};
+
+export default ExplainButton;
diff --git a/services/dashboard/GuidedWizard.jsx b/services/dashboard/GuidedWizard.jsx
new file mode 100644
index 0000000..9f72fd9
--- /dev/null
+++ b/services/dashboard/GuidedWizard.jsx
@@ -0,0 +1,487 @@
+/**
+ * GuidedWizard Component
+ * Multi-step wizard for onboarding flows
+ * Types: create_operation, onboard_agent, run_scan, first_time_setup
+ */
+
+import React, { useState, useEffect } from 'react';
+
+const GuidedWizard = ({
+ wizardType = 'first_time_setup',
+ onComplete,
+ onCancel,
+ initialData = {}
+}) => {
+ const [currentStep, setCurrentStep] = useState(1);
+ const [formData, setFormData] = useState(initialData);
+ const [stepHelp, setStepHelp] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const wizardConfigs = {
+ create_operation: {
+ title: 'Create New Operation',
+ steps: [
+ {
+ number: 1,
+ title: 'Operation Name and Type',
+ fields: [
+ { name: 'operation_name', label: 'Operation Name', type: 'text', required: true, placeholder: 'Q4 Security Assessment' },
+ { name: 'operation_type', label: 'Operation Type', type: 'select', required: true, options: [
+ { value: 'external', label: 'External Penetration Test' },
+ { value: 'internal', label: 'Internal Network Assessment' },
+ { value: 'webapp', label: 'Web Application Test' },
+ { value: 'wireless', label: 'Wireless Security Assessment' }
+ ]}
+ ]
+ },
+ {
+ number: 2,
+ title: 'Define Target Scope',
+ fields: [
+ { name: 'target_range', label: 'Target Network Range', type: 'text', required: true, placeholder: '192.168.1.0/24' },
+ { name: 'excluded_hosts', label: 'Excluded Hosts (comma-separated)', type: 'text', placeholder: '192.168.1.1, 192.168.1.254' },
+ { name: 'domains', label: 'Target Domains', type: 'textarea', placeholder: 'example.com\napp.example.com' }
+ ]
+ },
+ {
+ number: 3,
+ title: 'Configure Assessment Tools',
+ fields: [
+ { name: 'scan_intensity', label: 'Scan Intensity', type: 'select', required: true, options: [
+ { value: '1', label: 'Stealth (Slowest, least detectable)' },
+ { value: '3', label: 'Balanced (Recommended)' },
+ { value: '5', label: 'Aggressive (Fastest, easily detected)' }
+ ]},
+ { name: 'tools', label: 'Tools to Use', type: 'multiselect', options: [
+ { value: 'nmap', label: 'Nmap (Network Scanning)' },
+ { value: 'nikto', label: 'Nikto (Web Server Scanning)' },
+ { value: 'gobuster', label: 'Gobuster (Directory Enumeration)' },
+ { value: 'sqlmap', label: 'SQLMap (SQL Injection Testing)' }
+ ]}
+ ]
+ }
+ ]
+ },
+ run_scan: {
+ title: 'Run Security Scan',
+ steps: [
+ {
+ number: 1,
+ title: 'Select Scan Tool',
+ fields: [
+ { name: 'tool', label: 'Security Tool', type: 'select', required: true, options: [
+ { value: 'nmap', label: 'Nmap - Network Scanner' },
+ { value: 'nikto', label: 'Nikto - Web Server Scanner' },
+ { value: 'gobuster', label: 'Gobuster - Directory/File Discovery' },
+ { value: 'sqlmap', label: 'SQLMap - SQL Injection' },
+ { value: 'whatweb', label: 'WhatWeb - Technology Detection' }
+ ]}
+ ]
+ },
+ {
+ number: 2,
+ title: 'Specify Target',
+ fields: [
+ { name: 'target', label: 'Target', type: 'text', required: true, placeholder: '192.168.1.0/24 or example.com' },
+ { name: 'ports', label: 'Ports (optional)', type: 'text', placeholder: '80,443,8080 or 1-1000' }
+ ]
+ },
+ {
+ number: 3,
+ title: 'Scan Options',
+ fields: [
+ { name: 'scan_type', label: 'Scan Type', type: 'select', required: true, options: [
+ { value: 'quick', label: 'Quick Scan (Fast, common ports)' },
+ { value: 'full', label: 'Full Scan (Comprehensive, slower)' },
+ { value: 'stealth', label: 'Stealth Scan (Slow, harder to detect)' },
+ { value: 'vuln', label: 'Vulnerability Scan (Checks for known vulns)' }
+ ]},
+ { name: 'timeout', label: 'Timeout (seconds)', type: 'number', placeholder: '300' }
+ ]
+ }
+ ]
+ },
+ first_time_setup: {
+ title: 'Welcome to StrikePackageGPT',
+ steps: [
+ {
+ number: 1,
+ title: 'Welcome',
+ fields: [
+ { name: 'user_name', label: 'Your Name', type: 'text', placeholder: 'John Doe' },
+ { name: 'skill_level', label: 'Security Testing Experience', type: 'select', required: true, options: [
+ { value: 'beginner', label: 'Beginner - Learning the basics' },
+ { value: 'intermediate', label: 'Intermediate - Some experience' },
+ { value: 'advanced', label: 'Advanced - Professional pentester' }
+ ]}
+ ]
+ },
+ {
+ number: 2,
+ title: 'Configure LLM Provider',
+ fields: [
+ { name: 'llm_provider', label: 'LLM Provider', type: 'select', required: true, options: [
+ { value: 'ollama', label: 'Ollama (Local, Free)' },
+ { value: 'openai', label: 'OpenAI (Cloud, Requires API Key)' },
+ { value: 'anthropic', label: 'Anthropic Claude (Cloud, Requires API Key)' }
+ ]},
+ { name: 'api_key', label: 'API Key (if using cloud provider)', type: 'password', placeholder: 'sk-...' }
+ ]
+ },
+ {
+ number: 3,
+ title: 'Review and Finish',
+ fields: []
+ }
+ ]
+ }
+ };
+
+ const config = wizardConfigs[wizardType] || wizardConfigs.first_time_setup;
+ const totalSteps = config.steps.length;
+ const currentStepConfig = config.steps[currentStep - 1];
+
+ useEffect(() => {
+ fetchStepHelp();
+ }, [currentStep]);
+
+ const fetchStepHelp = async () => {
+ try {
+ const response = await fetch(`/api/wizard/help?type=${wizardType}&step=${currentStep}`);
+ if (response.ok) {
+ const data = await response.json();
+ setStepHelp(data);
+ }
+ } catch (err) {
+ console.error('Failed to fetch step help:', err);
+ }
+ };
+
+ const handleFieldChange = (fieldName, value) => {
+ setFormData(prev => ({ ...prev, [fieldName]: value }));
+ };
+
+ const validateCurrentStep = () => {
+ const requiredFields = currentStepConfig.fields.filter(f => f.required);
+ for (const field of requiredFields) {
+ if (!formData[field.name]) {
+ setError(`${field.label} is required`);
+ return false;
+ }
+ }
+ setError(null);
+ return true;
+ };
+
+ const handleNext = () => {
+ if (!validateCurrentStep()) return;
+
+ if (currentStep < totalSteps) {
+ setCurrentStep(prev => prev + 1);
+ } else {
+ handleComplete();
+ }
+ };
+
+ const handleBack = () => {
+ if (currentStep > 1) {
+ setCurrentStep(prev => prev - 1);
+ setError(null);
+ }
+ };
+
+ const handleComplete = async () => {
+ if (!validateCurrentStep()) return;
+
+ setLoading(true);
+ try {
+ if (onComplete) {
+ await onComplete(formData);
+ }
+ } catch (err) {
+ setError('Failed to complete wizard: ' + err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const renderField = (field) => {
+ const commonStyle = {
+ width: '100%',
+ padding: '10px',
+ border: '1px solid #ddd',
+ borderRadius: '4px',
+ fontSize: '14px'
+ };
+
+ switch (field.type) {
+ case 'text':
+ case 'password':
+ case 'number':
+ return (
+ handleFieldChange(field.name, e.target.value)}
+ placeholder={field.placeholder}
+ style={commonStyle}
+ />
+ );
+
+ case 'textarea':
+ return (
+