diff --git a/backend/alembic/versions/b053_add_execution_detection_times.py b/backend/alembic/versions/b053_add_execution_detection_times.py new file mode 100644 index 0000000..3f0ee8b --- /dev/null +++ b/backend/alembic/versions/b053_add_execution_detection_times.py @@ -0,0 +1,27 @@ +"""Add execution_start_time, execution_end_time, detection_time, containment_time to tests. + +Revision ID: b053 +Revises: b052 +Create Date: 2026-06-24 +""" +from alembic import op +import sqlalchemy as sa + +revision = "b053" +down_revision = "b052" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column("tests", sa.Column("execution_start_time", sa.DateTime(), nullable=True)) + op.add_column("tests", sa.Column("execution_end_time", sa.DateTime(), nullable=True)) + op.add_column("tests", sa.Column("detection_time", sa.DateTime(), nullable=True)) + op.add_column("tests", sa.Column("containment_time", sa.DateTime(), nullable=True)) + + +def downgrade() -> None: + op.drop_column("tests", "containment_time") + op.drop_column("tests", "detection_time") + op.drop_column("tests", "execution_end_time") + op.drop_column("tests", "execution_start_time") diff --git a/backend/app/models/test.py b/backend/app/models/test.py index 928ba29..15a0ca4 100644 --- a/backend/app/models/test.py +++ b/backend/app/models/test.py @@ -70,6 +70,8 @@ class Test(Base): red_summary = Column(Text, nullable=True) # Assign attack_success = Column(Boolean, nullable=True) attack_success = Column(Boolean, nullable=True) + execution_start_time = Column(DateTime, nullable=True) + execution_end_time = Column(DateTime, nullable=True) # Assign red_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) red_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) # Assign red_validated_at = Column(DateTime, nullable=True) @@ -84,6 +86,8 @@ class Test(Base): # Assign detection_result = Column(Enum(TestResult, name="testresult"), nullable=True) detection_result = Column(Enum(TestResult, name="testresult"), nullable=True) containment_result = Column(Enum(ContainmentResult, name="containmentresult"), nullable=True) + detection_time = Column(DateTime, nullable=True) + containment_time = Column(DateTime, nullable=True) # Assign blue_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) blue_validated_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) # Assign blue_validated_at = Column(DateTime, nullable=True) diff --git a/backend/app/schemas/test.py b/backend/app/schemas/test.py index e3bd95d..dd8ef7d 100644 --- a/backend/app/schemas/test.py +++ b/backend/app/schemas/test.py @@ -82,6 +82,8 @@ class TestRedUpdate(BaseModel): attack_success: bool | None = None # Assign red_summary = None red_summary: str | None = None + execution_start_time: datetime | None = None + execution_end_time: datetime | None = None # ── Blue Team update ─────────────────────────────────────────────── @@ -93,6 +95,8 @@ class TestBlueUpdate(BaseModel): # Assign detection_result = None detection_result: TestResult | None = None containment_result: ContainmentResult | None = None + detection_time: datetime | None = None + containment_time: datetime | None = None # Assign blue_summary = None blue_summary: str | None = None @@ -195,6 +199,8 @@ class TestOut(BaseModel): red_summary: str | None = None # Assign attack_success = None attack_success: bool | None = None + execution_start_time: datetime | None = None + execution_end_time: datetime | None = None # Assign red_validated_by = None red_validated_by: uuid.UUID | None = None # Assign red_validated_at = None @@ -209,6 +215,8 @@ class TestOut(BaseModel): # Assign detection_result = None detection_result: TestResult | None = None containment_result: ContainmentResult | None = None + detection_time: datetime | None = None + containment_time: datetime | None = None # Assign blue_validated_by = None blue_validated_by: uuid.UUID | None = None # Assign blue_validated_at = None diff --git a/frontend/src/api/tests.ts b/frontend/src/api/tests.ts index ad8db99..79c8d61 100644 --- a/frontend/src/api/tests.ts +++ b/frontend/src/api/tests.ts @@ -34,11 +34,15 @@ export interface RedUpdatePayload { tool_used?: string; attack_success?: boolean; red_summary?: string; + execution_start_time?: string; + execution_end_time?: string; } export interface BlueUpdatePayload { detection_result?: TestResult; containment_result?: ContainmentResult; + detection_time?: string; + containment_time?: string; blue_summary?: string; } diff --git a/frontend/src/components/test-detail/TeamTabs.tsx b/frontend/src/components/test-detail/TeamTabs.tsx index 8e8a665..477161b 100644 --- a/frontend/src/components/test-detail/TeamTabs.tsx +++ b/frontend/src/components/test-detail/TeamTabs.tsx @@ -83,6 +83,8 @@ interface TeamTabsProps { tool_used: string; attack_success: boolean; red_summary: string; + execution_start_time: string; + execution_end_time: string; }; // Blue Team field handlers @@ -90,6 +92,8 @@ interface TeamTabsProps { blueDraft: { detection_result: TestResult | ""; containment_result: ContainmentResult | ""; + detection_time: string; + containment_time: string; blue_summary: string; }; @@ -230,6 +234,44 @@ export default function TeamTabs({ )} + {/* Execution times */} +
+ {test.execution_start_time ? new Date(test.execution_start_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"} +
+ )} ++ {test.execution_end_time ? new Date(test.execution_end_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"} +
+ )} ++ {test.detection_time ? new Date(test.detection_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"} +
+ )} ++ {test.containment_time ? new Date(test.containment_time + "Z").toUTCString().replace(" GMT", " UTC") : "—"} +
+ )} +