@harbinger-ai/harbinger 0.1.1 → 0.1.3

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.
@@ -1,22 +1,23 @@
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 { CrosshairIcon, PlusIcon, TrashIcon, ChevronDownIcon, GlobeIcon, SpinnerIcon, DownloadIcon, CheckIcon } from "./icons.js";
5
6
  import { getPrograms, createProgram, deleteProgram, getTargets, createTarget, deleteTarget, updateTarget, syncTargetsFromPlatform, syncAllTargets, getSyncStatus } from "../../bounty/actions.js";
6
7
  const PLATFORMS = [
7
- { id: "hackerone", label: "HackerOne", color: "bg-purple-500/10 text-purple-500", border: "border-purple-500/20" },
8
- { id: "bugcrowd", label: "Bugcrowd", color: "bg-orange-500/10 text-orange-500", border: "border-orange-500/20" },
9
- { id: "intigriti", label: "Intigriti", color: "bg-blue-500/10 text-blue-500", border: "border-blue-500/20" },
10
- { id: "yeswehack", label: "YesWeHack", color: "bg-teal-500/10 text-teal-500", border: "border-teal-500/20" },
11
- { id: "federacy", label: "Federacy", color: "bg-pink-500/10 text-pink-500", border: "border-pink-500/20" },
12
- { id: "custom", label: "Custom", color: "bg-muted text-muted-foreground", border: "border-border" }
8
+ { id: "hackerone", label: "HackerOne", bg: "bg-purple-500/10", text: "text-purple-500", border: "border-purple-500/20" },
9
+ { id: "bugcrowd", label: "Bugcrowd", bg: "bg-orange-500/10", text: "text-orange-500", border: "border-orange-500/20" },
10
+ { id: "intigriti", label: "Intigriti", bg: "bg-blue-500/10", text: "text-blue-500", border: "border-blue-500/20" },
11
+ { id: "yeswehack", label: "YesWeHack", bg: "bg-teal-500/10", text: "text-teal-500", border: "border-teal-500/20" },
12
+ { id: "federacy", label: "Federacy", bg: "bg-pink-500/10", text: "text-pink-500", border: "border-pink-500/20" },
13
+ { id: "custom", label: "Custom", bg: "bg-white/5", text: "text-muted-foreground", border: "border-white/10" }
13
14
  ];
14
15
  const TARGET_TYPES = ["domain", "wildcard", "ip", "cidr", "url", "api", "mobile"];
15
16
  const STATUS_COLORS = {
16
- in_scope: "bg-green-500/10 text-green-500",
17
- out_of_scope: "bg-red-500/10 text-red-500",
18
- testing: "bg-yellow-500/10 text-yellow-500",
19
- completed: "bg-blue-500/10 text-blue-500"
17
+ in_scope: { bg: "bg-green-500/10", text: "text-green-500", border: "border-green-500/20", dot: "bg-green-500" },
18
+ out_of_scope: { bg: "bg-red-500/10", text: "text-red-500", border: "border-red-500/20", dot: "bg-red-500" },
19
+ testing: { bg: "bg-yellow-500/10", text: "text-yellow-500", border: "border-yellow-500/20", dot: "bg-yellow-500 animate-pulse" },
20
+ completed: { bg: "bg-blue-500/10", text: "text-blue-500", border: "border-blue-500/20", dot: "bg-blue-500" }
20
21
  };
21
22
  function timeAgo(ts) {
22
23
  if (!ts) return "never";
@@ -27,6 +28,9 @@ function timeAgo(ts) {
27
28
  if (hrs < 24) return `${hrs}h ago`;
28
29
  return `${Math.floor(hrs / 24)}d ago`;
29
30
  }
31
+ function getPlatStyle(platformId) {
32
+ return PLATFORMS.find((p) => p.id === platformId) || PLATFORMS[5];
33
+ }
30
34
  function SyncPanel({ onSync, syncStatus }) {
31
35
  const [syncing, setSyncing] = useState(null);
32
36
  const [results, setResults] = useState(null);
@@ -49,183 +53,229 @@ function SyncPanel({ onSync, syncStatus }) {
49
53
  setSyncing(null);
50
54
  }
51
55
  const syncablePlatforms = PLATFORMS.filter((p) => p.id !== "custom");
52
- return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 mb-6", children: [
53
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
54
- /* @__PURE__ */ jsxs("div", { children: [
55
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Sync from Bounty Platforms" }),
56
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5", children: "Import programs and targets from bounty-targets-data (arkadiyt/bounty-targets-data)" })
57
- ] }),
58
- syncStatus?.lastSyncedAt && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
59
- "Last sync: ",
60
- timeAgo(syncStatus.lastSyncedAt)
61
- ] })
62
- ] }),
63
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 mb-3", children: [
64
- syncablePlatforms.map((p) => {
65
- const count = syncStatus?.platformCounts?.[p.id] || 0;
66
- return /* @__PURE__ */ jsxs(
67
- "button",
56
+ return /* @__PURE__ */ jsxs(
57
+ motion.div,
58
+ {
59
+ initial: { opacity: 0, y: 8 },
60
+ animate: { opacity: 1, y: 0 },
61
+ transition: { duration: 0.3 },
62
+ className: "rounded-lg border border-white/[0.06] bg-[--card] p-4 mb-6",
63
+ children: [
64
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
65
+ /* @__PURE__ */ jsxs("div", { children: [
66
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-0.5", children: [
67
+ /* @__PURE__ */ jsx(DownloadIcon, { size: 14 }),
68
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-mono font-medium text-[--cyan]", children: "Sync from Bounty Platforms" })
69
+ ] }),
70
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground font-mono", children: "Import programs and targets from bounty-targets-data" })
71
+ ] }),
72
+ syncStatus?.lastSyncedAt && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground font-mono", children: [
73
+ "Last sync: ",
74
+ timeAgo(syncStatus.lastSyncedAt)
75
+ ] })
76
+ ] }),
77
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 mb-3", children: [
78
+ syncablePlatforms.map((p) => {
79
+ const count = syncStatus?.platformCounts?.[p.id] || 0;
80
+ return /* @__PURE__ */ jsxs(
81
+ "button",
82
+ {
83
+ onClick: () => handleSync(p.id),
84
+ disabled: syncing !== null,
85
+ className: `inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-mono font-medium border transition-colors hover:bg-white/[0.04] disabled:opacity-50 ${p.border}`,
86
+ children: [
87
+ syncing === p.id ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
88
+ p.label,
89
+ count > 0 && /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[9px] font-mono ${p.bg} ${p.text}`, children: count })
90
+ ]
91
+ },
92
+ p.id
93
+ );
94
+ }),
95
+ /* @__PURE__ */ jsxs(
96
+ "button",
97
+ {
98
+ onClick: () => handleSync("all"),
99
+ disabled: syncing !== null,
100
+ 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 disabled:opacity-50 transition-opacity",
101
+ children: [
102
+ syncing === "all" ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
103
+ "Sync All"
104
+ ]
105
+ }
106
+ )
107
+ ] }),
108
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
109
+ /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground font-mono uppercase tracking-wider", children: "Max programs:" }),
110
+ /* @__PURE__ */ jsx(
111
+ "input",
112
+ {
113
+ type: "number",
114
+ value: maxPrograms,
115
+ onChange: (e) => setMaxPrograms(e.target.value),
116
+ className: "w-20 text-xs border border-white/[0.06] rounded-md px-2 py-1 bg-black/20 font-mono focus:outline-none focus:border-[--cyan]/40 transition-colors",
117
+ min: "0",
118
+ placeholder: "0 = all"
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: "(0 = unlimited)" })
122
+ ] }),
123
+ /* @__PURE__ */ jsx(AnimatePresence, { children: results && /* @__PURE__ */ jsx(
124
+ motion.div,
68
125
  {
69
- onClick: () => handleSync(p.id),
70
- disabled: syncing !== null,
71
- className: `inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium border transition-colors hover:bg-accent/50 disabled:opacity-50 ${p.border}`,
72
- children: [
73
- syncing === p.id ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
74
- p.label,
75
- count > 0 && /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[9px] ${p.color}`, children: count })
76
- ]
77
- },
78
- p.id
79
- );
80
- }),
81
- /* @__PURE__ */ jsxs(
82
- "button",
83
- {
84
- onClick: () => handleSync("all"),
85
- disabled: syncing !== null,
86
- 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 disabled:opacity-50",
87
- children: [
88
- syncing === "all" ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
89
- "Sync All"
90
- ]
91
- }
92
- )
93
- ] }),
94
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
95
- /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground", children: "Max programs per platform:" }),
96
- /* @__PURE__ */ jsx(
97
- "input",
98
- {
99
- type: "number",
100
- value: maxPrograms,
101
- onChange: (e) => setMaxPrograms(e.target.value),
102
- className: "w-20 text-xs border rounded-md px-2 py-1 bg-background",
103
- min: "0",
104
- placeholder: "0 = all"
105
- }
106
- ),
107
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "(0 = unlimited)" })
108
- ] }),
109
- results && /* @__PURE__ */ jsx("div", { className: "rounded-md bg-muted/50 p-3 mt-2", children: results.error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: results.error }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: Object.entries(results).map(([platform, stats]) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs", children: [
110
- /* @__PURE__ */ jsx("span", { className: "font-medium w-24", children: platform }),
111
- /* @__PURE__ */ jsxs("span", { className: "text-green-500", children: [
112
- "+",
113
- stats.programsAdded,
114
- " programs"
115
- ] }),
116
- /* @__PURE__ */ jsxs("span", { className: "text-blue-500", children: [
117
- stats.programsUpdated,
118
- " updated"
119
- ] }),
120
- /* @__PURE__ */ jsxs("span", { className: "text-green-500", children: [
121
- "+",
122
- stats.targetsAdded,
123
- " targets"
124
- ] }),
125
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
126
- stats.targetsSkipped,
127
- " skipped"
128
- ] }),
129
- stats.errors?.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
130
- stats.errors.length,
131
- " errors"
132
- ] })
133
- ] }, platform)) }) }),
134
- syncStatus && syncStatus.totalSyncedPrograms > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mt-2 pt-2 border-t", children: [
135
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
136
- syncStatus.totalSyncedPrograms,
137
- " synced programs:"
138
- ] }),
139
- Object.entries(syncStatus.platformCounts || {}).map(([p, count]) => {
140
- const plat = PLATFORMS.find((x) => x.id === p);
141
- return /* @__PURE__ */ jsxs("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[9px] font-medium ${plat?.color || ""}`, children: [
142
- plat?.label || p,
143
- " (",
144
- count,
145
- ")"
146
- ] }, p);
147
- })
148
- ] })
149
- ] });
126
+ initial: { opacity: 0, height: 0 },
127
+ animate: { opacity: 1, height: "auto" },
128
+ exit: { opacity: 0, height: 0 },
129
+ className: "overflow-hidden",
130
+ children: /* @__PURE__ */ jsx("div", { className: "rounded-md bg-black/30 border border-white/[0.04] p-3 mt-2", children: results.error ? /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-[--destructive]", children: results.error }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: Object.entries(results).map(([platform, stats]) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs font-mono", children: [
131
+ /* @__PURE__ */ jsx("span", { className: "font-medium w-24 text-foreground/80", children: platform }),
132
+ /* @__PURE__ */ jsxs("span", { className: "text-green-500", children: [
133
+ "+",
134
+ stats.programsAdded,
135
+ " programs"
136
+ ] }),
137
+ /* @__PURE__ */ jsxs("span", { className: "text-blue-500", children: [
138
+ stats.programsUpdated,
139
+ " updated"
140
+ ] }),
141
+ /* @__PURE__ */ jsxs("span", { className: "text-green-500", children: [
142
+ "+",
143
+ stats.targetsAdded,
144
+ " targets"
145
+ ] }),
146
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
147
+ stats.targetsSkipped,
148
+ " skipped"
149
+ ] }),
150
+ stats.errors?.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[--destructive]", children: [
151
+ stats.errors.length,
152
+ " errors"
153
+ ] })
154
+ ] }, platform)) }) })
155
+ }
156
+ ) }),
157
+ syncStatus && syncStatus.totalSyncedPrograms > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mt-3 pt-3 border-t border-white/[0.06]", children: [
158
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground font-mono", children: [
159
+ syncStatus.totalSyncedPrograms,
160
+ " synced:"
161
+ ] }),
162
+ Object.entries(syncStatus.platformCounts || {}).map(([p, count]) => {
163
+ const plat = getPlatStyle(p);
164
+ return /* @__PURE__ */ jsxs("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[9px] font-mono font-medium border ${plat.bg} ${plat.text} ${plat.border}`, children: [
165
+ plat.label,
166
+ " (",
167
+ count,
168
+ ")"
169
+ ] }, p);
170
+ })
171
+ ] })
172
+ ]
173
+ }
174
+ );
150
175
  }
151
- function ProgramCard({ program, onSelect, selected, onDelete }) {
152
- const platform = PLATFORMS.find((p) => p.id === program.platform) || PLATFORMS[5];
176
+ function ProgramCard({ program, onSelect, selected, onDelete, index }) {
177
+ const plat = getPlatStyle(program.platform);
153
178
  return /* @__PURE__ */ jsxs(
154
- "button",
179
+ motion.button,
155
180
  {
181
+ initial: { opacity: 0, x: -8 },
182
+ animate: { opacity: 1, x: 0 },
183
+ transition: { duration: 0.25, delay: index * 0.02 },
156
184
  onClick: () => onSelect(program.id),
157
- className: `flex items-center gap-3 w-full text-left p-3 rounded-lg border transition-colors ${selected ? "border-foreground bg-accent/50" : "bg-card hover:bg-accent/30"}`,
185
+ className: `flex items-center gap-3 w-full text-left p-3 rounded-lg border transition-all ${selected ? "border-[--cyan]/40 bg-[--cyan]/5 glow-cyan" : "border-white/[0.06] bg-[--card] hover:border-[--cyan]/20"}`,
158
186
  children: [
159
- /* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(GlobeIcon, { size: 14 }) }),
187
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
188
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
189
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
190
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
191
+ ] }),
160
192
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
161
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: program.name }),
193
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: program.name }),
162
194
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
163
- /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-medium ${platform.color}`, children: platform.label }),
164
- program.maxBounty > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
195
+ /* @__PURE__ */ jsx("span", { className: `inline-flex rounded-full px-2 py-0.5 text-[10px] font-mono font-medium border ${plat.bg} ${plat.text} ${plat.border}`, children: plat.label }),
196
+ program.maxBounty > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-green-500", children: [
165
197
  "Up to $",
166
198
  program.maxBounty.toLocaleString()
167
199
  ] }),
168
- program.syncHandle && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-cyan-500/10 px-1.5 py-0.5 text-[9px] text-cyan-500", children: "synced" })
200
+ program.syncHandle && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-[--cyan]/10 border border-[--cyan]/20 px-1.5 py-0.5 text-[9px] font-mono text-[--cyan]", children: "synced" })
169
201
  ] })
170
202
  ] }),
171
203
  !program.syncHandle && /* @__PURE__ */ jsx("button", { onClick: (e) => {
172
204
  e.stopPropagation();
173
205
  onDelete(program.id);
174
- }, className: "shrink-0 p-1 text-muted-foreground hover:text-destructive rounded", children: /* @__PURE__ */ jsx(TrashIcon, { size: 12 }) })
206
+ }, className: "shrink-0 p-1 text-muted-foreground hover:text-[--destructive] rounded transition-colors", children: /* @__PURE__ */ jsx(TrashIcon, { size: 12 }) })
175
207
  ]
176
208
  }
177
209
  );
178
210
  }
179
- function TargetRow({ target, onDelete, onStatusChange }) {
180
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3 rounded-lg border bg-card", children: [
181
- /* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(CrosshairIcon, { size: 14 }) }),
182
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
183
- /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: target.value }),
184
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
185
- /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: target.type }),
186
- target.syncSource && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-cyan-500/10 px-1.5 py-0.5 text-[9px] text-cyan-500", children: target.syncSource }),
187
- target.technologies && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground truncate", children: JSON.parse(target.technologies).join(", ") })
188
- ] })
189
- ] }),
190
- /* @__PURE__ */ jsxs(
191
- "select",
192
- {
193
- value: target.status,
194
- onChange: (e) => onStatusChange(target.id, e.target.value),
195
- className: `text-[10px] font-medium rounded-full px-2 py-0.5 border-0 cursor-pointer ${STATUS_COLORS[target.status] || ""}`,
196
- children: [
197
- /* @__PURE__ */ jsx("option", { value: "in_scope", children: "in scope" }),
198
- /* @__PURE__ */ jsx("option", { value: "testing", children: "testing" }),
199
- /* @__PURE__ */ jsx("option", { value: "completed", children: "completed" }),
200
- /* @__PURE__ */ jsx("option", { value: "out_of_scope", children: "out of scope" })
201
- ]
202
- }
203
- ),
204
- /* @__PURE__ */ jsx("button", { onClick: () => onDelete(target.id), className: "shrink-0 p-1 text-muted-foreground hover:text-destructive rounded", children: /* @__PURE__ */ jsx(TrashIcon, { size: 12 }) })
205
- ] });
211
+ function TargetRow({ target, onDelete, onStatusChange, index }) {
212
+ const st = STATUS_COLORS[target.status] || STATUS_COLORS.in_scope;
213
+ return /* @__PURE__ */ jsxs(
214
+ motion.div,
215
+ {
216
+ initial: { opacity: 0, y: 6 },
217
+ animate: { opacity: 1, y: 0 },
218
+ transition: { duration: 0.2, delay: index * 0.02 },
219
+ className: "flex items-center gap-3 p-3 rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors",
220
+ children: [
221
+ /* @__PURE__ */ jsx("div", { className: `shrink-0 w-2 h-2 rounded-full ${st.dot}` }),
222
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
223
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: target.value }),
224
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 flex-wrap", children: [
225
+ /* @__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: target.type }),
226
+ target.syncSource && /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-full bg-[--cyan]/10 border border-[--cyan]/20 px-1.5 py-0.5 text-[9px] font-mono text-[--cyan]", children: target.syncSource }),
227
+ target.technologies && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground truncate", children: JSON.parse(target.technologies).join(", ") })
228
+ ] })
229
+ ] }),
230
+ /* @__PURE__ */ jsxs(
231
+ "select",
232
+ {
233
+ value: target.status,
234
+ onChange: (e) => onStatusChange(target.id, e.target.value),
235
+ 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`,
236
+ children: [
237
+ /* @__PURE__ */ jsx("option", { value: "in_scope", children: "in scope" }),
238
+ /* @__PURE__ */ jsx("option", { value: "testing", children: "testing" }),
239
+ /* @__PURE__ */ jsx("option", { value: "completed", children: "completed" }),
240
+ /* @__PURE__ */ jsx("option", { value: "out_of_scope", children: "out of scope" })
241
+ ]
242
+ }
243
+ ),
244
+ /* @__PURE__ */ jsx("button", { onClick: () => onDelete(target.id), className: "shrink-0 p-1 text-muted-foreground hover:text-[--destructive] rounded transition-colors", children: /* @__PURE__ */ jsx(TrashIcon, { size: 12 }) })
245
+ ]
246
+ }
247
+ );
206
248
  }
207
249
  function AddForm({ fields, onSubmit }) {
208
250
  const [values, setValues] = useState({});
209
251
  const [open, setOpen] = useState(false);
210
- if (!open) return /* @__PURE__ */ jsxs("button", { onClick: () => setOpen(true), className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium border border-dashed hover:bg-accent/50 transition-colors text-muted-foreground hover:text-foreground", children: [
252
+ if (!open) return /* @__PURE__ */ jsxs("button", { onClick: () => setOpen(true), className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-mono font-medium border border-dashed border-white/[0.12] hover:bg-white/[0.04] transition-colors text-muted-foreground hover:text-foreground", children: [
211
253
  /* @__PURE__ */ jsx(PlusIcon, { size: 12 }),
212
254
  " Add"
213
255
  ] });
214
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-2 p-3 rounded-lg border bg-card", children: [
215
- fields.map((f) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
216
- /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground", children: f.label }),
217
- f.type === "select" ? /* @__PURE__ */ jsx("select", { value: values[f.name] || "", onChange: (e) => setValues({ ...values, [f.name]: e.target.value }), className: "text-xs border rounded-md px-2 py-1.5 bg-background", children: f.options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o)) }) : /* @__PURE__ */ jsx("input", { type: f.type || "text", placeholder: f.placeholder, value: values[f.name] || "", onChange: (e) => setValues({ ...values, [f.name]: e.target.value }), className: "text-xs border rounded-md px-2 py-1.5 bg-background min-w-[120px]" })
218
- ] }, f.name)),
219
- /* @__PURE__ */ jsx("button", { onClick: () => {
220
- onSubmit(values);
221
- setValues({});
222
- setOpen(false);
223
- }, 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" }),
224
- /* @__PURE__ */ jsx("button", { onClick: () => {
225
- setValues({});
226
- setOpen(false);
227
- }, className: "text-xs text-muted-foreground hover:text-foreground px-2 py-1.5", children: "Cancel" })
228
- ] });
256
+ return /* @__PURE__ */ jsxs(
257
+ motion.div,
258
+ {
259
+ initial: { opacity: 0, scale: 0.98 },
260
+ animate: { opacity: 1, scale: 1 },
261
+ className: "flex flex-wrap items-end gap-2 p-3 rounded-lg border border-white/[0.06] bg-[--card]",
262
+ children: [
263
+ fields.map((f) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
264
+ /* @__PURE__ */ jsx("label", { className: "text-[10px] text-muted-foreground font-mono uppercase tracking-wider", children: f.label }),
265
+ f.type === "select" ? /* @__PURE__ */ jsx("select", { value: values[f.name] || "", onChange: (e) => setValues({ ...values, [f.name]: e.target.value }), className: "text-xs border border-white/[0.06] rounded-md px-2 py-1.5 bg-black/20 font-mono focus:outline-none focus:border-[--cyan]/40 transition-colors", children: f.options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o)) }) : /* @__PURE__ */ jsx("input", { type: f.type || "text", placeholder: f.placeholder, value: values[f.name] || "", onChange: (e) => setValues({ ...values, [f.name]: e.target.value }), className: "text-xs border border-white/[0.06] rounded-md px-2 py-1.5 bg-black/20 font-mono min-w-[120px] placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 transition-colors" })
266
+ ] }, f.name)),
267
+ /* @__PURE__ */ jsx("button", { onClick: () => {
268
+ onSubmit(values);
269
+ setValues({});
270
+ setOpen(false);
271
+ }, 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" }),
272
+ /* @__PURE__ */ jsx("button", { onClick: () => {
273
+ setValues({});
274
+ setOpen(false);
275
+ }, className: "text-xs font-mono text-muted-foreground hover:text-foreground px-2 py-1.5 transition-colors", children: "Cancel" })
276
+ ]
277
+ }
278
+ );
229
279
  }
230
280
  function TargetsPage() {
231
281
  const [programs_, setPrograms] = useState([]);
@@ -289,39 +339,58 @@ function TargetsPage() {
289
339
  loadTargets();
290
340
  }
291
341
  const filteredPrograms = platformFilter === "all" ? programs_ : programs_.filter((p) => p.platform === platformFilter);
292
- if (loading) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-pulse rounded-lg bg-border/50" }, i)) });
342
+ const inScopeCount = targets_.filter((t) => t.status === "in_scope").length;
343
+ if (loading) return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 animate-pulse rounded-lg bg-white/[0.04] border border-white/[0.06]" }, i)) });
293
344
  return /* @__PURE__ */ jsxs(Fragment, { children: [
294
- /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
295
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Targets" }),
296
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: [
297
- programs_.length,
298
- " program",
299
- programs_.length !== 1 ? "s" : "",
300
- ", ",
301
- targets_.length,
302
- " target",
303
- targets_.length !== 1 ? "s" : "",
304
- " in scope"
345
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-6", children: [
346
+ /* @__PURE__ */ jsxs("div", { children: [
347
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-mono font-semibold text-[--cyan] text-glow-cyan", children: "Targets" }),
348
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-1 font-mono", children: "Bug bounty program and target management" })
349
+ ] }),
350
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
351
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-[10px] font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20", children: [
352
+ programs_.length,
353
+ " programs"
354
+ ] }),
355
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-[10px] font-mono font-medium bg-green-500/10 text-green-500 border border-green-500/20", children: [
356
+ inScopeCount,
357
+ " in scope"
358
+ ] })
305
359
  ] })
306
360
  ] }),
307
361
  /* @__PURE__ */ jsx(SyncPanel, { onSync: handleSync, syncStatus }),
308
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1 mb-4 overflow-x-auto pb-1", children: [
309
- /* @__PURE__ */ jsxs("button", { onClick: () => setPlatformFilter("all"), className: `shrink-0 px-3 py-1 rounded-full text-xs font-medium transition-colors ${platformFilter === "all" ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: [
310
- "All (",
311
- programs_.length,
312
- ")"
313
- ] }),
314
- PLATFORMS.filter((p) => programs_.some((prog) => prog.platform === p.id)).map((p) => /* @__PURE__ */ jsxs("button", { onClick: () => setPlatformFilter(p.id), className: `shrink-0 px-3 py-1 rounded-full text-xs font-medium transition-colors ${platformFilter === p.id ? "bg-foreground text-background" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: [
315
- p.label,
316
- " (",
317
- programs_.filter((prog) => prog.platform === p.id).length,
318
- ")"
319
- ] }, p.id))
362
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5 mb-4 overflow-x-auto pb-1 scrollbar-thin", children: [
363
+ /* @__PURE__ */ jsxs(
364
+ "button",
365
+ {
366
+ onClick: () => setPlatformFilter("all"),
367
+ className: `shrink-0 px-3 py-1 rounded-full text-[10px] font-mono font-medium border transition-colors ${platformFilter === "all" ? "bg-[--cyan]/10 text-[--cyan] border-[--cyan]/20" : "border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]"}`,
368
+ children: [
369
+ "All (",
370
+ programs_.length,
371
+ ")"
372
+ ]
373
+ }
374
+ ),
375
+ PLATFORMS.filter((p) => programs_.some((prog) => prog.platform === p.id)).map((p) => /* @__PURE__ */ jsxs(
376
+ "button",
377
+ {
378
+ onClick: () => setPlatformFilter(p.id),
379
+ className: `shrink-0 px-3 py-1 rounded-full text-[10px] font-mono font-medium border transition-colors ${platformFilter === p.id ? `${p.bg} ${p.text} ${p.border}` : "border-white/[0.06] text-muted-foreground hover:text-foreground hover:border-white/[0.12]"}`,
380
+ children: [
381
+ p.label,
382
+ " (",
383
+ programs_.filter((prog) => prog.platform === p.id).length,
384
+ ")"
385
+ ]
386
+ },
387
+ p.id
388
+ ))
320
389
  ] }),
321
390
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-6", children: [
322
391
  /* @__PURE__ */ jsxs("div", { className: "lg:col-span-1", children: [
323
392
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
324
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "Programs" }),
393
+ /* @__PURE__ */ jsx("h2", { className: "text-[10px] font-mono font-medium uppercase tracking-wider text-muted-foreground", children: "Programs" }),
325
394
  /* @__PURE__ */ jsx(
326
395
  AddForm,
327
396
  {
@@ -335,15 +404,15 @@ function TargetsPage() {
335
404
  }
336
405
  )
337
406
  ] }),
338
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 max-h-[600px] overflow-y-auto", children: filteredPrograms.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-8 text-center", children: [
339
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-3 mb-3", children: /* @__PURE__ */ jsx(GlobeIcon, { size: 20 }) }),
340
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No programs yet" }),
341
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground mt-1", children: "Sync from platforms or add manually" })
342
- ] }) : filteredPrograms.map((p) => /* @__PURE__ */ jsx(ProgramCard, { program: p, selected: selectedProgram === p.id, onSelect: setSelectedProgram, onDelete: handleDeleteProgram }, p.id)) })
407
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 max-h-[600px] overflow-y-auto scrollbar-thin", children: filteredPrograms.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-8 text-center", children: [
408
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-3 mb-3", children: /* @__PURE__ */ jsx(GlobeIcon, { size: 20 }) }),
409
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-mono text-muted-foreground", children: "No programs yet" }),
410
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-mono text-muted-foreground mt-1", children: "Sync from platforms or add manually" })
411
+ ] }) : filteredPrograms.map((p, i) => /* @__PURE__ */ jsx(ProgramCard, { program: p, selected: selectedProgram === p.id, onSelect: setSelectedProgram, onDelete: handleDeleteProgram, index: i }, p.id)) })
343
412
  ] }),
344
413
  /* @__PURE__ */ jsxs("div", { className: "lg:col-span-2", children: [
345
414
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
346
- /* @__PURE__ */ jsxs("h2", { className: "text-sm font-medium", children: [
415
+ /* @__PURE__ */ jsxs("h2", { className: "text-[10px] font-mono font-medium uppercase tracking-wider text-muted-foreground", children: [
347
416
  "Targets ",
348
417
  selectedProgram && `\u2014 ${programs_.find((p) => p.id === selectedProgram)?.name || ""}`
349
418
  ] }),
@@ -358,15 +427,15 @@ function TargetsPage() {
358
427
  }
359
428
  )
360
429
  ] }),
361
- !selectedProgram ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-12 text-center", children: [
362
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(CrosshairIcon, { size: 24 }) }),
363
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "Select a program" }),
364
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Choose a program to manage its targets" })
365
- ] }) : targets_.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-12 text-center", children: [
366
- /* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(CrosshairIcon, { size: 24 }) }),
367
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No targets yet" }),
368
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Add domains, IPs, and URLs to start hunting" })
369
- ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 max-h-[600px] overflow-y-auto", children: targets_.map((t) => /* @__PURE__ */ jsx(TargetRow, { target: t, onDelete: handleDeleteTarget, onStatusChange: handleStatusChange }, t.id)) })
430
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: !selectedProgram ? /* @__PURE__ */ jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "flex flex-col items-center py-12 text-center", children: [
431
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(CrosshairIcon, { size: 24 }) }),
432
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "Select a program" }),
433
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-mono text-muted-foreground", children: "Choose a program to manage its targets" })
434
+ ] }, "empty") : targets_.length === 0 ? /* @__PURE__ */ jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "flex flex-col items-center py-12 text-center", children: [
435
+ /* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(CrosshairIcon, { size: 24 }) }),
436
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No targets yet" }),
437
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-mono text-muted-foreground", children: "Add domains, IPs, and URLs to start hunting" })
438
+ ] }, "no-targets") : /* @__PURE__ */ jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "flex flex-col gap-2 max-h-[600px] overflow-y-auto scrollbar-thin", children: targets_.map((t, i) => /* @__PURE__ */ jsx(TargetRow, { target: t, onDelete: handleDeleteTarget, onStatusChange: handleStatusChange, index: i }, t.id)) }, "targets") })
370
439
  ] })
371
440
  ] })
372
441
  ] });