@harbinger-ai/harbinger 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chat/actions.js +19 -0
- package/lib/chat/components/app-sidebar.js +17 -1
- package/lib/chat/components/app-sidebar.jsx +19 -1
- package/lib/chat/components/findings-page.js +164 -103
- package/lib/chat/components/findings-page.jsx +156 -101
- package/lib/chat/components/icons.js +22 -0
- package/lib/chat/components/icons.jsx +20 -0
- package/lib/chat/components/index.js +1 -0
- package/lib/chat/components/mission-control.js +490 -0
- package/lib/chat/components/mission-control.jsx +618 -0
- package/lib/chat/components/registry-page.js +267 -133
- package/lib/chat/components/registry-page.jsx +299 -138
- package/lib/chat/components/targets-page.js +269 -200
- package/lib/chat/components/targets-page.jsx +181 -111
- package/package.json +1 -1
package/lib/chat/actions.js
CHANGED
|
@@ -202,6 +202,25 @@ export async function triggerUpgrade() {
|
|
|
202
202
|
return { success: true };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
206
|
+
// Agent profile actions
|
|
207
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get all discovered agent profiles for Mission Control.
|
|
211
|
+
* @returns {Promise<object[]>}
|
|
212
|
+
*/
|
|
213
|
+
export async function getAgentProfiles() {
|
|
214
|
+
await requireAuth();
|
|
215
|
+
try {
|
|
216
|
+
const { discoverAgents } = await import('../agents.js');
|
|
217
|
+
return discoverAgents();
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.error('Failed to discover agents:', err);
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
205
224
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
206
225
|
// API Key actions
|
|
207
226
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon } from "./icons.js";
|
|
4
|
+
import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon, CommandIcon } from "./icons.js";
|
|
5
5
|
import { getUnreadNotificationCount, getAppVersion } from "../actions.js";
|
|
6
6
|
import { SidebarHistory } from "./sidebar-history.js";
|
|
7
7
|
import { SidebarUserNav } from "./sidebar-user-nav.js";
|
|
@@ -112,6 +112,22 @@ function AppSidebar({ user }) {
|
|
|
112
112
|
) }),
|
|
113
113
|
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Swarm" })
|
|
114
114
|
] }) }),
|
|
115
|
+
/* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
116
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
117
|
+
SidebarMenuButton,
|
|
118
|
+
{
|
|
119
|
+
className: collapsed ? "justify-center" : "",
|
|
120
|
+
onClick: () => {
|
|
121
|
+
window.location.href = "/mission-control";
|
|
122
|
+
},
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ jsx(CommandIcon, { size: 16 }),
|
|
125
|
+
!collapsed && /* @__PURE__ */ jsx("span", { children: "Mission Control" })
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
) }),
|
|
129
|
+
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Mission Control" })
|
|
130
|
+
] }) }),
|
|
115
131
|
/* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
116
132
|
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
117
133
|
SidebarMenuButton,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon } from './icons.js';
|
|
4
|
+
import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, CrosshairIcon, ShieldIcon, PackageIcon, CommandIcon } from './icons.js';
|
|
5
5
|
import { getUnreadNotificationCount, getAppVersion } from '../actions.js';
|
|
6
6
|
import { SidebarHistory } from './sidebar-history.js';
|
|
7
7
|
import { SidebarUserNav } from './sidebar-user-nav.js';
|
|
@@ -129,6 +129,24 @@ export function AppSidebar({ user }) {
|
|
|
129
129
|
</Tooltip>
|
|
130
130
|
</SidebarMenuItem>
|
|
131
131
|
|
|
132
|
+
{/* Mission Control */}
|
|
133
|
+
<SidebarMenuItem>
|
|
134
|
+
<Tooltip>
|
|
135
|
+
<TooltipTrigger asChild>
|
|
136
|
+
<SidebarMenuButton
|
|
137
|
+
className={collapsed ? 'justify-center' : ''}
|
|
138
|
+
onClick={() => { window.location.href = '/mission-control'; }}
|
|
139
|
+
>
|
|
140
|
+
<CommandIcon size={16} />
|
|
141
|
+
{!collapsed && <span>Mission Control</span>}
|
|
142
|
+
</SidebarMenuButton>
|
|
143
|
+
</TooltipTrigger>
|
|
144
|
+
{collapsed && (
|
|
145
|
+
<TooltipContent side="right">Mission Control</TooltipContent>
|
|
146
|
+
)}
|
|
147
|
+
</Tooltip>
|
|
148
|
+
</SidebarMenuItem>
|
|
149
|
+
|
|
132
150
|
{/* Targets */}
|
|
133
151
|
<SidebarMenuItem>
|
|
134
152
|
<Tooltip>
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
4
5
|
import { ShieldIcon, PlusIcon, ChevronDownIcon, SpinnerIcon } from "./icons.js";
|
|
5
6
|
import { getFindings, createFinding, updateFinding, deleteFinding, getFindingCounts } from "../../bounty/actions.js";
|
|
6
7
|
const SEVERITY_CONFIG = {
|
|
7
|
-
critical: {
|
|
8
|
-
high: {
|
|
9
|
-
medium: {
|
|
10
|
-
low: {
|
|
11
|
-
info: {
|
|
8
|
+
critical: { bg: "bg-red-600/15", text: "text-red-500", border: "border-red-500/20", dot: "bg-red-500", glow: "shadow-[0_0_20px_oklch(0.6_0.22_25/20%)]", label: "Critical" },
|
|
9
|
+
high: { bg: "bg-orange-500/15", text: "text-orange-500", border: "border-orange-500/20", dot: "bg-orange-500", glow: "shadow-[0_0_20px_oklch(0.7_0.17_55/20%)]", label: "High" },
|
|
10
|
+
medium: { bg: "bg-yellow-500/15", text: "text-yellow-500", border: "border-yellow-500/20", dot: "bg-yellow-500", glow: "", label: "Medium" },
|
|
11
|
+
low: { bg: "bg-blue-500/15", text: "text-blue-500", border: "border-blue-500/20", dot: "bg-blue-500", glow: "", label: "Low" },
|
|
12
|
+
info: { bg: "bg-white/5", text: "text-muted-foreground", border: "border-white/10", dot: "bg-muted-foreground", glow: "", label: "Info" }
|
|
12
13
|
};
|
|
13
14
|
const STATUS_FLOW = ["new", "triaging", "confirmed", "reported", "duplicate", "resolved", "bounty_paid"];
|
|
14
15
|
const STATUS_COLORS = {
|
|
15
|
-
new: "bg-blue-500/10 text-blue-500",
|
|
16
|
-
triaging: "bg-yellow-500/10 text-yellow-500",
|
|
17
|
-
confirmed: "bg-green-500/10 text-green-500",
|
|
18
|
-
reported: "bg-purple-500/10 text-purple-500",
|
|
19
|
-
duplicate: "bg-
|
|
20
|
-
resolved: "bg-emerald-500/10 text-emerald-500",
|
|
21
|
-
bounty_paid: "bg-green-600/10 text-green-600"
|
|
16
|
+
new: { bg: "bg-blue-500/10", text: "text-blue-500", border: "border-blue-500/20" },
|
|
17
|
+
triaging: { bg: "bg-yellow-500/10", text: "text-yellow-500", border: "border-yellow-500/20" },
|
|
18
|
+
confirmed: { bg: "bg-green-500/10", text: "text-green-500", border: "border-green-500/20" },
|
|
19
|
+
reported: { bg: "bg-purple-500/10", text: "text-purple-500", border: "border-purple-500/20" },
|
|
20
|
+
duplicate: { bg: "bg-white/5", text: "text-muted-foreground", border: "border-white/10" },
|
|
21
|
+
resolved: { bg: "bg-emerald-500/10", text: "text-emerald-500", border: "border-emerald-500/20" },
|
|
22
|
+
bounty_paid: { bg: "bg-green-600/10", text: "text-green-600", border: "border-green-600/20" }
|
|
22
23
|
};
|
|
23
24
|
const FINDING_TYPES = ["xss", "sqli", "ssrf", "idor", "rce", "lfi", "open_redirect", "subdomain_takeover", "info_disclosure", "misconfig", "auth_bypass", "rate_limit", "cors", "xxe", "csrf", "other"];
|
|
24
25
|
function timeAgo(ts) {
|
|
@@ -32,78 +33,109 @@ function timeAgo(ts) {
|
|
|
32
33
|
}
|
|
33
34
|
function StatsBar({ counts }) {
|
|
34
35
|
if (!counts) return null;
|
|
35
|
-
|
|
36
|
-
{ label: "Total", value: counts.total,
|
|
37
|
-
{ label: "Critical", value: counts.critical,
|
|
38
|
-
{ label: "High", value: counts.high,
|
|
39
|
-
{ label: "Confirmed", value: counts.confirmed,
|
|
40
|
-
{ label: "Reported", value: counts.reported,
|
|
41
|
-
{ label: "Bounty", value: counts.totalBounty > 0 ? `$${counts.totalBounty.toLocaleString()}` : "$0",
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
const stats = [
|
|
37
|
+
{ label: "Total", value: counts.total, text: "text-[--cyan]", glow: "glow-cyan" },
|
|
38
|
+
{ label: "Critical", value: counts.critical, text: "text-red-500", glow: counts.critical > 0 ? "shadow-[0_0_20px_oklch(0.6_0.22_25/15%)]" : "" },
|
|
39
|
+
{ label: "High", value: counts.high, text: "text-orange-500", glow: counts.high > 0 ? "shadow-[0_0_20px_oklch(0.7_0.17_55/15%)]" : "" },
|
|
40
|
+
{ label: "Confirmed", value: counts.confirmed, text: "text-green-500", glow: "" },
|
|
41
|
+
{ label: "Reported", value: counts.reported, text: "text-purple-500", glow: "" },
|
|
42
|
+
{ label: "Bounty", value: counts.totalBounty > 0 ? `$${counts.totalBounty.toLocaleString()}` : "$0", text: "text-emerald-500", glow: counts.totalBounty > 0 ? "shadow-[0_0_20px_oklch(0.7_0.17_160/15%)]" : "" }
|
|
43
|
+
];
|
|
44
|
+
return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3 mb-6", children: stats.map((s, i) => /* @__PURE__ */ jsxs(
|
|
45
|
+
motion.div,
|
|
46
|
+
{
|
|
47
|
+
initial: { opacity: 0, y: 8 },
|
|
48
|
+
animate: { opacity: 1, y: 0 },
|
|
49
|
+
transition: { duration: 0.25, delay: i * 0.05 },
|
|
50
|
+
className: `rounded-lg border border-white/[0.06] bg-[--card] p-3 ${s.glow}`,
|
|
51
|
+
children: [
|
|
52
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground uppercase tracking-wider font-mono", children: s.label }),
|
|
53
|
+
/* @__PURE__ */ jsx("p", { className: `text-xl font-mono font-semibold mt-0.5 ${s.text}`, children: s.value })
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
s.label
|
|
57
|
+
)) });
|
|
46
58
|
}
|
|
47
|
-
function FindingCard({ finding, onUpdate, onDelete }) {
|
|
59
|
+
function FindingCard({ finding, onUpdate, onDelete, index }) {
|
|
48
60
|
const [expanded, setExpanded] = useState(false);
|
|
49
61
|
const sev = SEVERITY_CONFIG[finding.severity] || SEVERITY_CONFIG.info;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const st = STATUS_COLORS[finding.status] || STATUS_COLORS.new;
|
|
63
|
+
return /* @__PURE__ */ jsxs(
|
|
64
|
+
motion.div,
|
|
65
|
+
{
|
|
66
|
+
initial: { opacity: 0, y: 8 },
|
|
67
|
+
animate: { opacity: 1, y: 0 },
|
|
68
|
+
transition: { duration: 0.25, delay: index * 0.03 },
|
|
69
|
+
className: `rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-all ${sev.glow}`,
|
|
70
|
+
children: [
|
|
71
|
+
/* @__PURE__ */ jsxs("button", { onClick: () => setExpanded(!expanded), className: "flex items-center gap-3 w-full text-left p-4 hover:bg-white/[0.02] rounded-lg transition-colors", children: [
|
|
72
|
+
/* @__PURE__ */ jsx("div", { className: `shrink-0 w-2.5 h-2.5 rounded-full ${sev.dot}` }),
|
|
73
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
74
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: finding.title }),
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
|
|
76
|
+
/* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-mono font-medium border ${sev.bg} ${sev.text} ${sev.border}`, children: sev.label }),
|
|
77
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-white/5 border border-white/10 px-2 py-0.5 text-[10px] font-mono font-medium text-muted-foreground", children: finding.type }),
|
|
78
|
+
finding.agentId && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-purple-500/10 border border-purple-500/20 px-2 py-0.5 text-[10px] font-mono font-medium text-purple-500", children: finding.agentId }),
|
|
79
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground", children: timeAgo(finding.createdAt) })
|
|
80
|
+
] })
|
|
81
|
+
] }),
|
|
82
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
"select",
|
|
85
|
+
{
|
|
86
|
+
value: finding.status,
|
|
87
|
+
onClick: (e) => e.stopPropagation(),
|
|
88
|
+
onChange: (e) => {
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
onUpdate(finding.id, { status: e.target.value });
|
|
91
|
+
},
|
|
92
|
+
className: `text-[10px] font-mono font-medium rounded-full px-2 py-0.5 border cursor-pointer bg-transparent ${st.bg} ${st.text} ${st.border} focus:outline-none`,
|
|
93
|
+
children: STATUS_FLOW.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s.replace("_", " ") }, s))
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
/* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
|
|
97
|
+
] })
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: expanded && /* @__PURE__ */ jsx(
|
|
100
|
+
motion.div,
|
|
65
101
|
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
initial: { height: 0, opacity: 0 },
|
|
103
|
+
animate: { height: "auto", opacity: 1 },
|
|
104
|
+
exit: { height: 0, opacity: 0 },
|
|
105
|
+
transition: { duration: 0.2 },
|
|
106
|
+
className: "overflow-hidden",
|
|
107
|
+
children: /* @__PURE__ */ jsxs("div", { className: "border-t border-white/[0.06] px-4 py-3 flex flex-col gap-3", children: [
|
|
108
|
+
finding.description && /* @__PURE__ */ jsxs("div", { children: [
|
|
109
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Description" }),
|
|
110
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-mono whitespace-pre-wrap text-foreground/80", children: finding.description })
|
|
111
|
+
] }),
|
|
112
|
+
finding.stepsToReproduce && /* @__PURE__ */ jsxs("div", { children: [
|
|
113
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Steps to Reproduce" }),
|
|
114
|
+
/* @__PURE__ */ jsx("pre", { className: "text-[11px] bg-black/30 border border-white/[0.04] rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48 text-foreground/80 scrollbar-thin", children: finding.stepsToReproduce })
|
|
115
|
+
] }),
|
|
116
|
+
finding.impact && /* @__PURE__ */ jsxs("div", { children: [
|
|
117
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Impact" }),
|
|
118
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-foreground/80", children: finding.impact })
|
|
119
|
+
] }),
|
|
120
|
+
finding.rawOutput && /* @__PURE__ */ jsxs("div", { children: [
|
|
121
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono font-medium text-muted-foreground uppercase tracking-wider mb-1.5", children: "Raw Output" }),
|
|
122
|
+
/* @__PURE__ */ jsx("pre", { className: "text-[11px] bg-black/30 border border-white/[0.04] rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-32 text-foreground/80 scrollbar-thin", children: finding.rawOutput })
|
|
123
|
+
] }),
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-1", children: [
|
|
125
|
+
finding.bountyAmount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex rounded-full bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 px-2 py-0.5 text-[10px] font-mono font-medium", children: [
|
|
126
|
+
"$",
|
|
127
|
+
finding.bountyAmount
|
|
128
|
+
] }),
|
|
129
|
+
finding.reportUrl && /* @__PURE__ */ jsx("a", { href: finding.reportUrl, target: "_blank", rel: "noopener", className: "text-[10px] font-mono text-[--cyan] hover:underline", children: "View Report" }),
|
|
130
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
131
|
+
/* @__PURE__ */ jsx("button", { onClick: () => onDelete(finding.id), className: "text-[10px] font-mono text-muted-foreground hover:text-[--destructive] transition-colors", children: "Delete" })
|
|
132
|
+
] })
|
|
133
|
+
] })
|
|
74
134
|
}
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
expanded && /* @__PURE__ */ jsxs("div", { className: "border-t px-4 py-3 flex flex-col gap-3", children: [
|
|
80
|
-
finding.description && /* @__PURE__ */ jsxs("div", { children: [
|
|
81
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Description" }),
|
|
82
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: finding.description })
|
|
83
|
-
] }),
|
|
84
|
-
finding.stepsToReproduce && /* @__PURE__ */ jsxs("div", { children: [
|
|
85
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Steps to Reproduce" }),
|
|
86
|
-
/* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48", children: finding.stepsToReproduce })
|
|
87
|
-
] }),
|
|
88
|
-
finding.impact && /* @__PURE__ */ jsxs("div", { children: [
|
|
89
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Impact" }),
|
|
90
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs", children: finding.impact })
|
|
91
|
-
] }),
|
|
92
|
-
finding.rawOutput && /* @__PURE__ */ jsxs("div", { children: [
|
|
93
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Raw Output" }),
|
|
94
|
-
/* @__PURE__ */ jsx("pre", { className: "text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-32", children: finding.rawOutput })
|
|
95
|
-
] }),
|
|
96
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
97
|
-
finding.bountyAmount > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex rounded-full bg-emerald-500/10 text-emerald-500 px-2 py-0.5 text-[10px] font-medium", children: [
|
|
98
|
-
"$",
|
|
99
|
-
finding.bountyAmount
|
|
100
|
-
] }),
|
|
101
|
-
finding.reportUrl && /* @__PURE__ */ jsx("a", { href: finding.reportUrl, target: "_blank", rel: "noopener", className: "text-[10px] text-blue-500 hover:underline", children: "View Report" }),
|
|
102
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
103
|
-
/* @__PURE__ */ jsx("button", { onClick: () => onDelete(finding.id), className: "text-[10px] text-muted-foreground hover:text-destructive", children: "Delete" })
|
|
104
|
-
] })
|
|
105
|
-
] })
|
|
106
|
-
] });
|
|
135
|
+
) })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
);
|
|
107
139
|
}
|
|
108
140
|
function FindingsPage() {
|
|
109
141
|
const [findings_, setFindings] = useState([]);
|
|
@@ -137,37 +169,66 @@ function FindingsPage() {
|
|
|
137
169
|
load();
|
|
138
170
|
}
|
|
139
171
|
const filtered = filter === "all" ? findings_ : findings_.filter((f) => f.severity === filter);
|
|
140
|
-
if (loading) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-pulse rounded-lg bg-border/
|
|
172
|
+
if (loading) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-pulse rounded-lg bg-white/[0.04] border border-white/[0.06]" }, i)) });
|
|
141
173
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
142
174
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
143
175
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
144
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Findings" }),
|
|
145
|
-
/* @__PURE__ */ jsx("p", { className: "text-
|
|
176
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-mono font-semibold text-[--cyan] text-glow-cyan", children: "Findings" }),
|
|
177
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-1 font-mono", children: "Vulnerability discoveries across all agents and tools" })
|
|
146
178
|
] }),
|
|
147
|
-
/* @__PURE__ */ jsxs(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
179
|
+
/* @__PURE__ */ jsxs(
|
|
180
|
+
"button",
|
|
181
|
+
{
|
|
182
|
+
onClick: () => setShowAdd(!showAdd),
|
|
183
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-mono font-medium bg-[--cyan] text-[--primary-foreground] hover:opacity-90 transition-opacity",
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsx(PlusIcon, { size: 12 }),
|
|
186
|
+
" New Finding"
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
)
|
|
151
190
|
] }),
|
|
152
191
|
/* @__PURE__ */ jsx(StatsBar, { counts }),
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
/* @__PURE__ */
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
192
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: showAdd && /* @__PURE__ */ jsx(
|
|
193
|
+
motion.div,
|
|
194
|
+
{
|
|
195
|
+
initial: { opacity: 0, height: 0 },
|
|
196
|
+
animate: { opacity: 1, height: "auto" },
|
|
197
|
+
exit: { opacity: 0, height: 0 },
|
|
198
|
+
transition: { duration: 0.2 },
|
|
199
|
+
className: "overflow-hidden",
|
|
200
|
+
children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-white/[0.06] bg-[--card] p-4 mb-4 flex flex-col gap-3", children: [
|
|
201
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3", children: [
|
|
202
|
+
/* @__PURE__ */ jsx("input", { placeholder: "Title", value: newFinding.title, onChange: (e) => setNewFinding({ ...newFinding, title: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 col-span-1 sm:col-span-3 font-mono placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors" }),
|
|
203
|
+
/* @__PURE__ */ jsx("select", { value: newFinding.severity, onChange: (e) => setNewFinding({ ...newFinding, severity: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono focus:outline-none focus:border-[--cyan]/40 transition-colors", children: Object.keys(SEVERITY_CONFIG).map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s }, s)) }),
|
|
204
|
+
/* @__PURE__ */ jsx("select", { value: newFinding.type, onChange: (e) => setNewFinding({ ...newFinding, type: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono focus:outline-none focus:border-[--cyan]/40 transition-colors", children: FINDING_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t)) })
|
|
205
|
+
] }),
|
|
206
|
+
/* @__PURE__ */ jsx("textarea", { placeholder: "Description...", value: newFinding.description, onChange: (e) => setNewFinding({ ...newFinding, description: e.target.value }), className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 min-h-[80px] font-mono placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors" }),
|
|
207
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
208
|
+
/* @__PURE__ */ jsx("button", { onClick: handleCreate, className: "inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-xs font-mono font-medium bg-[--cyan] text-[--primary-foreground] hover:opacity-90 transition-opacity", children: "Save" }),
|
|
209
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowAdd(false), className: "text-xs font-mono text-muted-foreground hover:text-foreground px-3 py-1.5 transition-colors", children: "Cancel" })
|
|
210
|
+
] })
|
|
211
|
+
] })
|
|
212
|
+
}
|
|
213
|
+
) }),
|
|
214
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1.5 mb-4 overflow-x-auto scrollbar-thin", children: ["all", ...Object.keys(SEVERITY_CONFIG)].map((f) => {
|
|
215
|
+
const isActive = filter === f;
|
|
216
|
+
const sev = SEVERITY_CONFIG[f];
|
|
217
|
+
return /* @__PURE__ */ jsx(
|
|
218
|
+
"button",
|
|
219
|
+
{
|
|
220
|
+
onClick: () => setFilter(f),
|
|
221
|
+
className: `shrink-0 px-3 py-1 rounded-full text-[10px] font-mono font-medium border transition-colors ${isActive ? f === "all" ? "bg-[--cyan]/10 text-[--cyan] border-[--cyan]/20" : `${sev.bg} ${sev.text} ${sev.border}` : "border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]"}`,
|
|
222
|
+
children: f === "all" ? `All (${findings_.length})` : `${sev.label} (${findings_.filter((x) => x.severity === f).length})`
|
|
223
|
+
},
|
|
224
|
+
f
|
|
225
|
+
);
|
|
226
|
+
}) }),
|
|
166
227
|
filtered.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
|
|
167
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-
|
|
168
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No findings yet" }),
|
|
169
|
-
/* @__PURE__ */ jsx("p", { className: "text-
|
|
170
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: filtered.map((f) => /* @__PURE__ */ jsx(FindingCard, { finding: f, onUpdate: handleUpdate, onDelete: handleDelete }, f.id)) })
|
|
228
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(ShieldIcon, { size: 24 }) }),
|
|
229
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No findings yet" }),
|
|
230
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-mono text-muted-foreground max-w-sm", children: "Findings will appear here as your agents discover vulnerabilities, or add them manually." })
|
|
231
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: filtered.map((f, i) => /* @__PURE__ */ jsx(FindingCard, { finding: f, onUpdate: handleUpdate, onDelete: handleDelete, index: i }, f.id)) })
|
|
171
232
|
] });
|
|
172
233
|
}
|
|
173
234
|
export {
|