@harbinger-ai/harbinger 0.1.0
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/LICENSE +21 -0
- package/README.md +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { ShieldIcon, PlusIcon, ChevronDownIcon, SpinnerIcon } from "./icons.js";
|
|
5
|
+
import { getFindings, createFinding, updateFinding, deleteFinding, getFindingCounts } from "../../bounty/actions.js";
|
|
6
|
+
const SEVERITY_CONFIG = {
|
|
7
|
+
critical: { color: "bg-red-600/15 text-red-500 border-red-500/20", dot: "bg-red-500", label: "Critical" },
|
|
8
|
+
high: { color: "bg-orange-500/15 text-orange-500 border-orange-500/20", dot: "bg-orange-500", label: "High" },
|
|
9
|
+
medium: { color: "bg-yellow-500/15 text-yellow-500 border-yellow-500/20", dot: "bg-yellow-500", label: "Medium" },
|
|
10
|
+
low: { color: "bg-blue-500/15 text-blue-500 border-blue-500/20", dot: "bg-blue-500", label: "Low" },
|
|
11
|
+
info: { color: "bg-muted text-muted-foreground border-border", dot: "bg-muted-foreground", label: "Info" }
|
|
12
|
+
};
|
|
13
|
+
const STATUS_FLOW = ["new", "triaging", "confirmed", "reported", "duplicate", "resolved", "bounty_paid"];
|
|
14
|
+
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-muted text-muted-foreground",
|
|
20
|
+
resolved: "bg-emerald-500/10 text-emerald-500",
|
|
21
|
+
bounty_paid: "bg-green-600/10 text-green-600"
|
|
22
|
+
};
|
|
23
|
+
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
|
+
function timeAgo(ts) {
|
|
25
|
+
if (!ts) return "";
|
|
26
|
+
const diff = Date.now() - ts;
|
|
27
|
+
const mins = Math.floor(diff / 6e4);
|
|
28
|
+
if (mins < 60) return `${mins}m ago`;
|
|
29
|
+
const hrs = Math.floor(mins / 60);
|
|
30
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
31
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
32
|
+
}
|
|
33
|
+
function StatsBar({ counts }) {
|
|
34
|
+
if (!counts) return null;
|
|
35
|
+
return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-3 mb-6", children: [
|
|
36
|
+
{ label: "Total", value: counts.total, color: "text-foreground" },
|
|
37
|
+
{ label: "Critical", value: counts.critical, color: "text-red-500" },
|
|
38
|
+
{ label: "High", value: counts.high, color: "text-orange-500" },
|
|
39
|
+
{ label: "Confirmed", value: counts.confirmed, color: "text-green-500" },
|
|
40
|
+
{ label: "Reported", value: counts.reported, color: "text-purple-500" },
|
|
41
|
+
{ label: "Bounty", value: counts.totalBounty > 0 ? `$${counts.totalBounty.toLocaleString()}` : "$0", color: "text-emerald-500" }
|
|
42
|
+
].map((s) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-3", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground uppercase tracking-wide", children: s.label }),
|
|
44
|
+
/* @__PURE__ */ jsx("p", { className: `text-xl font-semibold mt-0.5 ${s.color}`, children: s.value })
|
|
45
|
+
] }, s.label)) });
|
|
46
|
+
}
|
|
47
|
+
function FindingCard({ finding, onUpdate, onDelete }) {
|
|
48
|
+
const [expanded, setExpanded] = useState(false);
|
|
49
|
+
const sev = SEVERITY_CONFIG[finding.severity] || SEVERITY_CONFIG.info;
|
|
50
|
+
return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border bg-card ${sev.color.split(" ")[0].replace("/15", "/5").replace("/10", "/5")}`, children: [
|
|
51
|
+
/* @__PURE__ */ jsxs("button", { onClick: () => setExpanded(!expanded), className: "flex items-center gap-3 w-full text-left p-4 hover:bg-accent/30 rounded-lg", children: [
|
|
52
|
+
/* @__PURE__ */ jsx("div", { className: `shrink-0 w-2 h-2 rounded-full ${sev.dot}` }),
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
54
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: finding.title }),
|
|
55
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
|
|
56
|
+
/* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium border ${sev.color}`, children: sev.label }),
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: finding.type }),
|
|
58
|
+
finding.agentId && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-purple-500/10 px-2 py-0.5 text-[10px] font-medium text-purple-500", children: finding.agentId }),
|
|
59
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: timeAgo(finding.createdAt) })
|
|
60
|
+
] })
|
|
61
|
+
] }),
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(
|
|
64
|
+
"select",
|
|
65
|
+
{
|
|
66
|
+
value: finding.status,
|
|
67
|
+
onClick: (e) => e.stopPropagation(),
|
|
68
|
+
onChange: (e) => {
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
onUpdate(finding.id, { status: e.target.value });
|
|
71
|
+
},
|
|
72
|
+
className: `text-[10px] font-medium rounded-full px-2 py-0.5 border-0 cursor-pointer ${STATUS_COLORS[finding.status] || ""}`,
|
|
73
|
+
children: STATUS_FLOW.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s.replace("_", " ") }, s))
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
/* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
|
|
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
|
+
] });
|
|
107
|
+
}
|
|
108
|
+
function FindingsPage() {
|
|
109
|
+
const [findings_, setFindings] = useState([]);
|
|
110
|
+
const [counts, setCounts] = useState(null);
|
|
111
|
+
const [loading, setLoading] = useState(true);
|
|
112
|
+
const [filter, setFilter] = useState("all");
|
|
113
|
+
const [showAdd, setShowAdd] = useState(false);
|
|
114
|
+
const [newFinding, setNewFinding] = useState({ title: "", severity: "medium", type: "xss", description: "" });
|
|
115
|
+
async function load() {
|
|
116
|
+
const [f, c] = await Promise.all([getFindings(), getFindingCounts()]);
|
|
117
|
+
setFindings(f);
|
|
118
|
+
setCounts(c);
|
|
119
|
+
setLoading(false);
|
|
120
|
+
}
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
load();
|
|
123
|
+
}, []);
|
|
124
|
+
async function handleCreate() {
|
|
125
|
+
if (!newFinding.title) return;
|
|
126
|
+
await createFinding(newFinding);
|
|
127
|
+
setNewFinding({ title: "", severity: "medium", type: "xss", description: "" });
|
|
128
|
+
setShowAdd(false);
|
|
129
|
+
load();
|
|
130
|
+
}
|
|
131
|
+
async function handleUpdate(id, data) {
|
|
132
|
+
await updateFinding(id, data);
|
|
133
|
+
load();
|
|
134
|
+
}
|
|
135
|
+
async function handleDelete(id) {
|
|
136
|
+
await deleteFinding(id);
|
|
137
|
+
load();
|
|
138
|
+
}
|
|
139
|
+
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/50" }, i)) });
|
|
141
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
143
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
144
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Findings" }),
|
|
145
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Vulnerability discoveries across all agents and tools" })
|
|
146
|
+
] }),
|
|
147
|
+
/* @__PURE__ */ jsxs("button", { onClick: () => setShowAdd(!showAdd), className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:opacity-90", children: [
|
|
148
|
+
/* @__PURE__ */ jsx(PlusIcon, { size: 12 }),
|
|
149
|
+
" New Finding"
|
|
150
|
+
] })
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsx(StatsBar, { counts }),
|
|
153
|
+
showAdd && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 mb-4 flex flex-col gap-3", children: [
|
|
154
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-3", children: [
|
|
155
|
+
/* @__PURE__ */ jsx("input", { placeholder: "Title", value: newFinding.title, onChange: (e) => setNewFinding({ ...newFinding, title: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background col-span-1 sm:col-span-3" }),
|
|
156
|
+
/* @__PURE__ */ jsx("select", { value: newFinding.severity, onChange: (e) => setNewFinding({ ...newFinding, severity: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background", children: Object.keys(SEVERITY_CONFIG).map((s) => /* @__PURE__ */ jsx("option", { value: s, children: s }, s)) }),
|
|
157
|
+
/* @__PURE__ */ jsx("select", { value: newFinding.type, onChange: (e) => setNewFinding({ ...newFinding, type: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background", children: FINDING_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t)) })
|
|
158
|
+
] }),
|
|
159
|
+
/* @__PURE__ */ jsx("textarea", { placeholder: "Description...", value: newFinding.description, onChange: (e) => setNewFinding({ ...newFinding, description: e.target.value }), className: "text-sm border rounded-md px-3 py-2 bg-background min-h-[80px]" }),
|
|
160
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
161
|
+
/* @__PURE__ */ jsx("button", { onClick: handleCreate, className: "inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:opacity-90", children: "Save" }),
|
|
162
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setShowAdd(false), className: "text-xs text-muted-foreground hover:text-foreground px-3 py-1.5", children: "Cancel" })
|
|
163
|
+
] })
|
|
164
|
+
] }),
|
|
165
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1 mb-4 overflow-x-auto", children: ["all", ...Object.keys(SEVERITY_CONFIG)].map((f) => /* @__PURE__ */ jsx("button", { onClick: () => setFilter(f), className: `px-3 py-1 rounded-full text-xs font-medium transition-colors ${filter === f ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: f === "all" ? `All (${findings_.length})` : `${SEVERITY_CONFIG[f].label} (${findings_.filter((x) => x.severity === f).length})` }, f)) }),
|
|
166
|
+
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-muted p-4 mb-4", children: /* @__PURE__ */ jsx(ShieldIcon, { size: 24 }) }),
|
|
168
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No findings yet" }),
|
|
169
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground max-w-sm", children: "Findings will appear here as your agents discover vulnerabilities, or add them manually." })
|
|
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)) })
|
|
171
|
+
] });
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
FindingsPage
|
|
175
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { ShieldIcon, PlusIcon, ChevronDownIcon, SpinnerIcon } from './icons.js';
|
|
5
|
+
import { getFindings, createFinding, updateFinding, deleteFinding, getFindingCounts } from '../../bounty/actions.js';
|
|
6
|
+
|
|
7
|
+
const SEVERITY_CONFIG = {
|
|
8
|
+
critical: { color: 'bg-red-600/15 text-red-500 border-red-500/20', dot: 'bg-red-500', label: 'Critical' },
|
|
9
|
+
high: { color: 'bg-orange-500/15 text-orange-500 border-orange-500/20', dot: 'bg-orange-500', label: 'High' },
|
|
10
|
+
medium: { color: 'bg-yellow-500/15 text-yellow-500 border-yellow-500/20', dot: 'bg-yellow-500', label: 'Medium' },
|
|
11
|
+
low: { color: 'bg-blue-500/15 text-blue-500 border-blue-500/20', dot: 'bg-blue-500', label: 'Low' },
|
|
12
|
+
info: { color: 'bg-muted text-muted-foreground border-border', dot: 'bg-muted-foreground', label: 'Info' },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const STATUS_FLOW = ['new', 'triaging', 'confirmed', 'reported', 'duplicate', 'resolved', 'bounty_paid'];
|
|
16
|
+
const STATUS_COLORS = {
|
|
17
|
+
new: 'bg-blue-500/10 text-blue-500',
|
|
18
|
+
triaging: 'bg-yellow-500/10 text-yellow-500',
|
|
19
|
+
confirmed: 'bg-green-500/10 text-green-500',
|
|
20
|
+
reported: 'bg-purple-500/10 text-purple-500',
|
|
21
|
+
duplicate: 'bg-muted text-muted-foreground',
|
|
22
|
+
resolved: 'bg-emerald-500/10 text-emerald-500',
|
|
23
|
+
bounty_paid: 'bg-green-600/10 text-green-600',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const FINDING_TYPES = ['xss', 'sqli', 'ssrf', 'idor', 'rce', 'lfi', 'open_redirect', 'subdomain_takeover', 'info_disclosure', 'misconfig', 'auth_bypass', 'rate_limit', 'cors', 'xxe', 'csrf', 'other'];
|
|
27
|
+
|
|
28
|
+
function timeAgo(ts) {
|
|
29
|
+
if (!ts) return '';
|
|
30
|
+
const diff = Date.now() - ts;
|
|
31
|
+
const mins = Math.floor(diff / 60000);
|
|
32
|
+
if (mins < 60) return `${mins}m ago`;
|
|
33
|
+
const hrs = Math.floor(mins / 60);
|
|
34
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
35
|
+
return `${Math.floor(hrs / 24)}d ago`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function StatsBar({ counts }) {
|
|
39
|
+
if (!counts) return null;
|
|
40
|
+
return (
|
|
41
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-3 mb-6">
|
|
42
|
+
{[
|
|
43
|
+
{ label: 'Total', value: counts.total, color: 'text-foreground' },
|
|
44
|
+
{ label: 'Critical', value: counts.critical, color: 'text-red-500' },
|
|
45
|
+
{ label: 'High', value: counts.high, color: 'text-orange-500' },
|
|
46
|
+
{ label: 'Confirmed', value: counts.confirmed, color: 'text-green-500' },
|
|
47
|
+
{ label: 'Reported', value: counts.reported, color: 'text-purple-500' },
|
|
48
|
+
{ label: 'Bounty', value: counts.totalBounty > 0 ? `$${counts.totalBounty.toLocaleString()}` : '$0', color: 'text-emerald-500' },
|
|
49
|
+
].map(s => (
|
|
50
|
+
<div key={s.label} className="rounded-lg border bg-card p-3">
|
|
51
|
+
<p className="text-[10px] text-muted-foreground uppercase tracking-wide">{s.label}</p>
|
|
52
|
+
<p className={`text-xl font-semibold mt-0.5 ${s.color}`}>{s.value}</p>
|
|
53
|
+
</div>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function FindingCard({ finding, onUpdate, onDelete }) {
|
|
60
|
+
const [expanded, setExpanded] = useState(false);
|
|
61
|
+
const sev = SEVERITY_CONFIG[finding.severity] || SEVERITY_CONFIG.info;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className={`rounded-lg border bg-card ${sev.color.split(' ')[0].replace('/15', '/5').replace('/10', '/5')}`}>
|
|
65
|
+
<button onClick={() => setExpanded(!expanded)} className="flex items-center gap-3 w-full text-left p-4 hover:bg-accent/30 rounded-lg">
|
|
66
|
+
<div className={`shrink-0 w-2 h-2 rounded-full ${sev.dot}`} />
|
|
67
|
+
<div className="flex-1 min-w-0">
|
|
68
|
+
<p className="text-sm font-medium truncate">{finding.title}</p>
|
|
69
|
+
<div className="flex items-center gap-2 mt-0.5 flex-wrap">
|
|
70
|
+
<span className={`inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium border ${sev.color}`}>{sev.label}</span>
|
|
71
|
+
<span className="inline-flex rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground">{finding.type}</span>
|
|
72
|
+
{finding.agentId && <span className="inline-flex rounded-full bg-purple-500/10 px-2 py-0.5 text-[10px] font-medium text-purple-500">{finding.agentId}</span>}
|
|
73
|
+
<span className="text-[10px] text-muted-foreground">{timeAgo(finding.createdAt)}</span>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
77
|
+
<select
|
|
78
|
+
value={finding.status}
|
|
79
|
+
onClick={e => e.stopPropagation()}
|
|
80
|
+
onChange={e => { e.stopPropagation(); onUpdate(finding.id, { status: e.target.value }); }}
|
|
81
|
+
className={`text-[10px] font-medium rounded-full px-2 py-0.5 border-0 cursor-pointer ${STATUS_COLORS[finding.status] || ''}`}
|
|
82
|
+
>
|
|
83
|
+
{STATUS_FLOW.map(s => <option key={s} value={s}>{s.replace('_', ' ')}</option>)}
|
|
84
|
+
</select>
|
|
85
|
+
<span className={`transition-transform ${expanded ? 'rotate-180' : ''}`}><ChevronDownIcon size={14} /></span>
|
|
86
|
+
</div>
|
|
87
|
+
</button>
|
|
88
|
+
|
|
89
|
+
{expanded && (
|
|
90
|
+
<div className="border-t px-4 py-3 flex flex-col gap-3">
|
|
91
|
+
{finding.description && (
|
|
92
|
+
<div>
|
|
93
|
+
<p className="text-xs font-medium text-muted-foreground mb-1">Description</p>
|
|
94
|
+
<p className="text-xs whitespace-pre-wrap">{finding.description}</p>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
{finding.stepsToReproduce && (
|
|
98
|
+
<div>
|
|
99
|
+
<p className="text-xs font-medium text-muted-foreground mb-1">Steps to Reproduce</p>
|
|
100
|
+
<pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">{finding.stepsToReproduce}</pre>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
{finding.impact && (
|
|
104
|
+
<div>
|
|
105
|
+
<p className="text-xs font-medium text-muted-foreground mb-1">Impact</p>
|
|
106
|
+
<p className="text-xs">{finding.impact}</p>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
{finding.rawOutput && (
|
|
110
|
+
<div>
|
|
111
|
+
<p className="text-xs font-medium text-muted-foreground mb-1">Raw Output</p>
|
|
112
|
+
<pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-32">{finding.rawOutput}</pre>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
<div className="flex items-center gap-2">
|
|
116
|
+
{finding.bountyAmount > 0 && <span className="inline-flex rounded-full bg-emerald-500/10 text-emerald-500 px-2 py-0.5 text-[10px] font-medium">${finding.bountyAmount}</span>}
|
|
117
|
+
{finding.reportUrl && <a href={finding.reportUrl} target="_blank" rel="noopener" className="text-[10px] text-blue-500 hover:underline">View Report</a>}
|
|
118
|
+
<div className="flex-1" />
|
|
119
|
+
<button onClick={() => onDelete(finding.id)} className="text-[10px] text-muted-foreground hover:text-destructive">Delete</button>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function FindingsPage() {
|
|
128
|
+
const [findings_, setFindings] = useState([]);
|
|
129
|
+
const [counts, setCounts] = useState(null);
|
|
130
|
+
const [loading, setLoading] = useState(true);
|
|
131
|
+
const [filter, setFilter] = useState('all');
|
|
132
|
+
const [showAdd, setShowAdd] = useState(false);
|
|
133
|
+
const [newFinding, setNewFinding] = useState({ title: '', severity: 'medium', type: 'xss', description: '' });
|
|
134
|
+
|
|
135
|
+
async function load() {
|
|
136
|
+
const [f, c] = await Promise.all([getFindings(), getFindingCounts()]);
|
|
137
|
+
setFindings(f);
|
|
138
|
+
setCounts(c);
|
|
139
|
+
setLoading(false);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
useEffect(() => { load(); }, []);
|
|
143
|
+
|
|
144
|
+
async function handleCreate() {
|
|
145
|
+
if (!newFinding.title) return;
|
|
146
|
+
await createFinding(newFinding);
|
|
147
|
+
setNewFinding({ title: '', severity: 'medium', type: 'xss', description: '' });
|
|
148
|
+
setShowAdd(false);
|
|
149
|
+
load();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function handleUpdate(id, data) { await updateFinding(id, data); load(); }
|
|
153
|
+
async function handleDelete(id) { await deleteFinding(id); load(); }
|
|
154
|
+
|
|
155
|
+
const filtered = filter === 'all' ? findings_ : findings_.filter(f => f.severity === filter);
|
|
156
|
+
|
|
157
|
+
if (loading) return <div className="flex flex-col gap-3">{[...Array(4)].map((_, i) => <div key={i} className="h-16 animate-pulse rounded-lg bg-border/50" />)}</div>;
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
<div className="flex items-center justify-between mb-4">
|
|
162
|
+
<div>
|
|
163
|
+
<h1 className="text-2xl font-semibold">Findings</h1>
|
|
164
|
+
<p className="text-sm text-muted-foreground mt-1">Vulnerability discoveries across all agents and tools</p>
|
|
165
|
+
</div>
|
|
166
|
+
<button onClick={() => setShowAdd(!showAdd)} className="inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:opacity-90">
|
|
167
|
+
<PlusIcon size={12} /> New Finding
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<StatsBar counts={counts} />
|
|
172
|
+
|
|
173
|
+
{/* Add form */}
|
|
174
|
+
{showAdd && (
|
|
175
|
+
<div className="rounded-lg border bg-card p-4 mb-4 flex flex-col gap-3">
|
|
176
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
|
177
|
+
<input placeholder="Title" value={newFinding.title} onChange={e => setNewFinding({ ...newFinding, title: e.target.value })} className="text-sm border rounded-md px-3 py-2 bg-background col-span-1 sm:col-span-3" />
|
|
178
|
+
<select value={newFinding.severity} onChange={e => setNewFinding({ ...newFinding, severity: e.target.value })} className="text-sm border rounded-md px-3 py-2 bg-background">
|
|
179
|
+
{Object.keys(SEVERITY_CONFIG).map(s => <option key={s} value={s}>{s}</option>)}
|
|
180
|
+
</select>
|
|
181
|
+
<select value={newFinding.type} onChange={e => setNewFinding({ ...newFinding, type: e.target.value })} className="text-sm border rounded-md px-3 py-2 bg-background">
|
|
182
|
+
{FINDING_TYPES.map(t => <option key={t} value={t}>{t}</option>)}
|
|
183
|
+
</select>
|
|
184
|
+
</div>
|
|
185
|
+
<textarea placeholder="Description..." value={newFinding.description} onChange={e => setNewFinding({ ...newFinding, description: e.target.value })} className="text-sm border rounded-md px-3 py-2 bg-background min-h-[80px]" />
|
|
186
|
+
<div className="flex gap-2">
|
|
187
|
+
<button onClick={handleCreate} className="inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-xs font-medium bg-foreground text-background hover:opacity-90">Save</button>
|
|
188
|
+
<button onClick={() => setShowAdd(false)} className="text-xs text-muted-foreground hover:text-foreground px-3 py-1.5">Cancel</button>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
{/* Filter tabs */}
|
|
194
|
+
<div className="flex gap-1 mb-4 overflow-x-auto">
|
|
195
|
+
{['all', ...Object.keys(SEVERITY_CONFIG)].map(f => (
|
|
196
|
+
<button key={f} onClick={() => setFilter(f)} className={`px-3 py-1 rounded-full text-xs font-medium transition-colors ${filter === f ? 'bg-foreground text-background' : 'bg-muted text-muted-foreground hover:text-foreground'}`}>
|
|
197
|
+
{f === 'all' ? `All (${findings_.length})` : `${SEVERITY_CONFIG[f].label} (${findings_.filter(x => x.severity === f).length})`}
|
|
198
|
+
</button>
|
|
199
|
+
))}
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{/* Findings list */}
|
|
203
|
+
{filtered.length === 0 ? (
|
|
204
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
205
|
+
<div className="rounded-full bg-muted p-4 mb-4"><ShieldIcon size={24} /></div>
|
|
206
|
+
<p className="text-sm font-medium mb-1">No findings yet</p>
|
|
207
|
+
<p className="text-xs text-muted-foreground max-w-sm">Findings will appear here as your agents discover vulnerabilities, or add them manually.</p>
|
|
208
|
+
</div>
|
|
209
|
+
) : (
|
|
210
|
+
<div className="flex flex-col gap-2">{filtered.map(f => <FindingCard key={f.id} finding={f} onUpdate={handleUpdate} onDelete={handleDelete} />)}</div>
|
|
211
|
+
)}
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
function Greeting() {
|
|
5
|
+
return /* @__PURE__ */ jsxs(
|
|
6
|
+
motion.div,
|
|
7
|
+
{
|
|
8
|
+
initial: { opacity: 0, y: 16 },
|
|
9
|
+
animate: { opacity: 1, y: 0 },
|
|
10
|
+
transition: { duration: 0.5, ease: "easeOut" },
|
|
11
|
+
className: "w-full text-center",
|
|
12
|
+
children: [
|
|
13
|
+
/* @__PURE__ */ jsx("div", { className: "inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-[--primary]/10 border border-[--primary]/20 mb-6 glow-cyan", children: /* @__PURE__ */ jsx("span", { className: "text-2xl font-bold text-[--cyan] font-[--font-display] text-glow-cyan", style: { fontFamily: "var(--font-display)" }, children: "H" }) }),
|
|
14
|
+
/* @__PURE__ */ jsx("div", { className: "font-semibold text-2xl md:text-3xl text-foreground mb-2", style: { fontFamily: "var(--font-display)" }, children: "Harbinger" }),
|
|
15
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground font-mono", children: "Autonomous intelligence. What would you like to explore?" })
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
Greeting
|
|
22
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { motion } from 'framer-motion';
|
|
4
|
+
|
|
5
|
+
export function Greeting() {
|
|
6
|
+
return (
|
|
7
|
+
<motion.div
|
|
8
|
+
initial={{ opacity: 0, y: 16 }}
|
|
9
|
+
animate={{ opacity: 1, y: 0 }}
|
|
10
|
+
transition={{ duration: 0.5, ease: 'easeOut' }}
|
|
11
|
+
className="w-full text-center"
|
|
12
|
+
>
|
|
13
|
+
{/* Logo glow */}
|
|
14
|
+
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-[--primary]/10 border border-[--primary]/20 mb-6 glow-cyan">
|
|
15
|
+
<span className="text-2xl font-bold text-[--cyan] font-[--font-display] text-glow-cyan" style={{ fontFamily: 'var(--font-display)' }}>H</span>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div className="font-semibold text-2xl md:text-3xl text-foreground mb-2" style={{ fontFamily: 'var(--font-display)' }}>
|
|
19
|
+
Harbinger
|
|
20
|
+
</div>
|
|
21
|
+
<div className="text-sm text-muted-foreground font-mono">
|
|
22
|
+
Autonomous intelligence. What would you like to explore?
|
|
23
|
+
</div>
|
|
24
|
+
</motion.div>
|
|
25
|
+
);
|
|
26
|
+
}
|