feat(review-queue): MITRE update review queue for leads
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- New /techniques/review-queue page: lists all techniques flagged for review after a MITRE ATT&CK sync, grouped by tactic. Leads and admins can mark each one reviewed inline without leaving the page. - Sidebar: 'Review Queue' link (admin/red_lead/blue_lead only) with an amber badge showing the live pending count. - TechniqueDetailPage: amber banner when review_required=true explaining what happened and who can act; 'Mark as Reviewed' button now amber coloured for visual distinction. 'Leads only' chip shown for blue_tech. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
FlaskConical,
|
||||
@@ -19,8 +20,10 @@ import {
|
||||
ShieldCheck,
|
||||
GitCompareArrows,
|
||||
ScrollText,
|
||||
ClipboardCheck,
|
||||
} from "lucide-react";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { getTechniques } from "../api/techniques";
|
||||
|
||||
interface NavItem {
|
||||
to: string;
|
||||
@@ -35,6 +38,7 @@ const mainLinks: NavItem[] = [
|
||||
{ to: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
|
||||
{ to: "/executive-dashboard", label: "Executive Dashboard", icon: Gauge, roles: ["admin", "red_lead", "blue_lead", "viewer"] },
|
||||
{ to: "/matrix", label: "ATT&CK Matrix", icon: Grid3X3 },
|
||||
{ to: "/techniques/review-queue", label: "Review Queue", icon: ClipboardCheck, roles: ["admin", "red_lead", "blue_lead"] },
|
||||
{
|
||||
to: "/tests",
|
||||
label: "Tests",
|
||||
@@ -60,7 +64,7 @@ const systemLinks: NavItem[] = [
|
||||
{ to: "/audit", label: "Audit Log", icon: FileText },
|
||||
];
|
||||
|
||||
function SidebarLink({ item }: { item: NavItem }) {
|
||||
function SidebarLink({ item, badge }: { item: NavItem; badge?: number }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
if (item.children) {
|
||||
@@ -104,6 +108,7 @@ function SidebarLink({ item }: { item: NavItem }) {
|
||||
return (
|
||||
<NavLink
|
||||
to={item.to}
|
||||
end
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${
|
||||
isActive
|
||||
@@ -112,8 +117,13 @@ function SidebarLink({ item }: { item: NavItem }) {
|
||||
}`
|
||||
}
|
||||
>
|
||||
<item.icon className="h-5 w-5" />
|
||||
{item.label}
|
||||
<item.icon className="h-5 w-5 shrink-0" />
|
||||
<span className="flex-1">{item.label}</span>
|
||||
{badge !== undefined && badge > 0 && (
|
||||
<span className="rounded-full bg-amber-500 px-1.5 py-0.5 text-[10px] font-bold text-white leading-none">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
@@ -123,10 +133,22 @@ export default function Sidebar() {
|
||||
const role = user?.role ?? "";
|
||||
const isAdmin = role === "admin";
|
||||
|
||||
const canSeeReviewQueue =
|
||||
isAdmin || role === "red_lead" || role === "blue_lead";
|
||||
|
||||
// Fetch review queue count for the badge (only for roles that can see it)
|
||||
const { data: reviewQueue } = useQuery({
|
||||
queryKey: ["techniques", "review-queue"],
|
||||
queryFn: () => getTechniques({ review_required: true }),
|
||||
enabled: canSeeReviewQueue,
|
||||
staleTime: 5 * 60 * 1000, // 5 min — don't hammer the API
|
||||
});
|
||||
const reviewCount = reviewQueue?.length ?? 0;
|
||||
|
||||
/** Returns true when the current user is allowed to see `item`. */
|
||||
const canSee = (item: NavItem) => {
|
||||
if (!item.roles) return true; // no restriction
|
||||
if (isAdmin) return true; // admin sees everything
|
||||
if (!item.roles) return true;
|
||||
if (isAdmin) return true;
|
||||
return item.roles.includes(role);
|
||||
};
|
||||
|
||||
@@ -143,7 +165,11 @@ export default function Sidebar() {
|
||||
{/* Main nav */}
|
||||
<nav className="flex-1 space-y-1 overflow-y-auto px-3 py-4">
|
||||
{mainLinks.filter(canSee).map((item) => (
|
||||
<SidebarLink key={item.to + item.label} item={item} />
|
||||
<SidebarLink
|
||||
key={item.to + item.label}
|
||||
item={item}
|
||||
badge={item.to === "/techniques/review-queue" ? reviewCount : undefined}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* System / Administration section — admin only */}
|
||||
|
||||
Reference in New Issue
Block a user