/** * PlaybookManager - Investigation playbook workflow wizard. * Create/load playbooks from templates, track step completion, navigate to target views. */ import React, { useState, useEffect, useCallback } from 'react'; import { Box, Typography, Paper, CircularProgress, Alert, Button, Chip, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Checkbox, Dialog, DialogTitle, DialogContent, DialogActions, TextField, LinearProgress, IconButton, Divider, Tooltip, } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import DeleteIcon from '@mui/icons-material/Delete'; import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { useSnackbar } from 'notistack'; import { playbooks, PlaybookSummary, PlaybookDetail, PlaybookTemplate, } from '../api/client'; export default function PlaybookManager() { const { enqueueSnackbar } = useSnackbar(); const [loading, setLoading] = useState(false); const [pbList, setPbList] = useState([]); const [active, setActive] = useState(null); const [templates, setTemplates] = useState([]); const [showCreate, setShowCreate] = useState(false); const [newName, setNewName] = useState(''); const [newDesc, setNewDesc] = useState(''); const loadList = useCallback(async () => { setLoading(true); try { const data = await playbooks.list(); setPbList(data.playbooks); } catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); } finally { setLoading(false); } }, [enqueueSnackbar]); const loadTemplates = useCallback(async () => { try { const data = await playbooks.templates(); setTemplates(data.templates); } catch {} }, []); useEffect(() => { loadList(); loadTemplates(); }, [loadList, loadTemplates]); const selectPlaybook = async (id: string) => { try { const d = await playbooks.get(id); setActive(d); } catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); } }; const toggleStep = async (stepId: number, current: boolean) => { if (!active) return; try { await playbooks.updateStep(stepId, { is_completed: !current }); const d = await playbooks.get(active.id); setActive(d); loadList(); } catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); } }; const createFromTemplate = async (tpl: PlaybookTemplate) => { try { const pb = await playbooks.create({ name: tpl.name, description: tpl.description, steps: tpl.steps.map((s, i) => ({ title: s.title, description: s.description, step_type: 'task', target_route: s.target_route || undefined, })), }); enqueueSnackbar('Playbook created from template', { variant: 'success' }); loadList(); setActive(pb); } catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); } }; const createCustom = async () => { if (!newName.trim()) return; try { const pb = await playbooks.create({ name: newName, description: newDesc, steps: [{ title: 'First step', description: 'Describe what to do' }], }); enqueueSnackbar('Playbook created', { variant: 'success' }); setShowCreate(false); setNewName(''); setNewDesc(''); loadList(); setActive(pb); } catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); } }; const deletePlaybook = async (id: string) => { try { await playbooks.delete(id); enqueueSnackbar('Playbook deleted', { variant: 'success' }); if (active?.id === id) setActive(null); loadList(); } catch (e: any) { enqueueSnackbar(e.message, { variant: 'error' }); } }; const completedCount = active?.steps.filter(s => s.is_completed).length || 0; const totalSteps = active?.steps.length || 1; const progress = Math.round((completedCount / totalSteps) * 100); return ( {/* Left sidebar - playbook list */} Playbooks setShowCreate(true)}> {/* Templates section */} TEMPLATES {templates.map(t => ( createFromTemplate(t)} sx={{ borderRadius: 1, mb: 0.5 }}> ))} MY PLAYBOOKS {loading && } {pbList.map(p => ( selectPlaybook(p.id)} sx={{ borderRadius: 1, mb: 0.5 }}> { e.stopPropagation(); deletePlaybook(p.id); }}> ))} {!loading && pbList.length === 0 && ( No playbooks yet. Start from a template or create one. )} {/* Right panel - active playbook */} {!active ? ( Select or create a playbook Use templates for common investigation workflows, or build your own step-by-step checklist. ) : ( {active.name} {active.description && {active.description}} {progress}% {active.steps .sort((a, b) => a.order_index - b.order_index) .map(step => ( toggleStep(step.id, step.is_completed)} sx={{ borderRadius: 1, border: '1px solid', borderColor: step.is_completed ? 'success.main' : 'divider', bgcolor: step.is_completed ? 'success.main' : 'transparent', opacity: step.is_completed ? 0.7 : 1 }}> {step.target_route && ( { e.stopPropagation(); window.location.hash = step.target_route!; }}> )} ))} )} {/* Create dialog */} setShowCreate(false)} maxWidth="sm" fullWidth> Create Custom Playbook setNewName(e.target.value)} sx={{ mt: 1, mb: 2 }} /> setNewDesc(e.target.value)} /> ); }