feat(phase-37): timer pause/resume + professional reporting engine
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Pause/Resume timer:
- Add paused_at, red_paused_seconds, blue_paused_seconds fields to Test model
- Add pause_timer/resume_timer workflow functions with accumulated pause tracking
- Auto-resume on phase submit; subtract paused time from worklog duration
- Add POST /tests/{id}/pause-timer and resume-timer endpoints
- Update LiveTimer component with pause/resume button and paused visual state
- Wire pause/resume mutations through TestDetailPage and TestDetailHeader
Professional Reporting Engine - Fase 2:
- Add ReportEngine service with Jinja2 HTML rendering, WeasyPrint PDF, and docxtpl DOCX
- Add corporate CSS stylesheet with cover page, data tables, stats grid, findings
- Create purple_campaign, coverage_report, and executive_summary HTML templates
- Add report_generation_service collecting domain data for each report type
- Add professional_reports router: GET /reports/generate/purple-campaign/{id}, coverage-summary, executive-summary
- Add analytics router with flat JSON endpoints for PowerBI: /coverage, /tests, /trends, /operators
- Add advanced_metrics router: /coverage-by-tactic, /never-tested, /avg-validation-time, /detection-rate-trend
- Add weasyprint and docxtpl to requirements.txt
- Add REPORT_TEMPLATES_DIR, REPORT_OUTPUT_DIR, COMPANY_NAME, COMPANY_LOGO_PATH to config
This commit is contained in:
@@ -13,6 +13,8 @@ import {
|
||||
validateAsRedLead,
|
||||
validateAsBlueLead,
|
||||
reopenTest,
|
||||
pauseTimer,
|
||||
resumeTimer,
|
||||
getTestTimeline,
|
||||
getRetestChain,
|
||||
} from "../api/tests";
|
||||
@@ -223,6 +225,25 @@ export default function TestDetailPage() {
|
||||
},
|
||||
});
|
||||
|
||||
// Timer pause/resume
|
||||
const pauseTimerMutation = useMutation({
|
||||
mutationFn: () => pauseTimer(testId!),
|
||||
onSuccess: () => {
|
||||
invalidateAll();
|
||||
showToast("Timer paused", "success");
|
||||
},
|
||||
onError: (err: unknown) => showToast(extractError(err), "error"),
|
||||
});
|
||||
|
||||
const resumeTimerMutation = useMutation({
|
||||
mutationFn: () => resumeTimer(testId!),
|
||||
onSuccess: () => {
|
||||
invalidateAll();
|
||||
showToast("Timer resumed", "success");
|
||||
},
|
||||
onError: (err: unknown) => showToast(extractError(err), "error"),
|
||||
});
|
||||
|
||||
// Evidence upload
|
||||
const uploadMutation = useMutation({
|
||||
mutationFn: ({ file, team }: { file: File; team: TeamSide }) =>
|
||||
@@ -351,6 +372,9 @@ export default function TestDetailPage() {
|
||||
onSubmitBlue={() => submitBlueMutation.mutate()}
|
||||
onOpenValidateModal={(side) => setValidationModal({ open: true, side })}
|
||||
onReopen={() => setConfirmReopen(true)}
|
||||
onPauseTimer={() => pauseTimerMutation.mutate()}
|
||||
onResumeTimer={() => resumeTimerMutation.mutate()}
|
||||
isTogglingTimer={pauseTimerMutation.isPending || resumeTimerMutation.isPending}
|
||||
/>
|
||||
|
||||
{/* Content: Tabs + Sidebar */}
|
||||
|
||||
Reference in New Issue
Block a user