summaryrefslogtreecommitdiff
path: root/frontend/src/components/ContractDetail.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/ContractDetail.tsx')
-rw-r--r--frontend/src/components/ContractDetail.tsx139
1 files changed, 135 insertions, 4 deletions
diff --git a/frontend/src/components/ContractDetail.tsx b/frontend/src/components/ContractDetail.tsx
index 72527ce..a9dd550 100644
--- a/frontend/src/components/ContractDetail.tsx
+++ b/frontend/src/components/ContractDetail.tsx
@@ -12,6 +12,8 @@ interface TaskSummary {
id: string
name: string
status: string
+ is_supervisor?: boolean
+ is_red_team?: boolean
}
interface ContractRepository {
@@ -30,6 +32,24 @@ interface Contract {
status: string
version: number
created_at: string
+ red_team_enabled?: boolean
+ red_team_prompt?: string
+}
+
+interface RedTeamNotification {
+ id: string
+ contract_id: string
+ red_team_task_id: string
+ related_task_id?: string
+ message: string
+ severity: 'info' | 'warning' | 'critical'
+ file_path?: string
+ context?: string
+ delivered: boolean
+ delivered_at?: string
+ acknowledged: boolean
+ acknowledged_at?: string
+ created_at: string
}
interface ContractWithRelations {
@@ -39,7 +59,7 @@ interface ContractWithRelations {
tasks: TaskSummary[]
}
-type Tab = 'overview' | 'files' | 'tasks' | 'repositories'
+type Tab = 'overview' | 'files' | 'tasks' | 'repositories' | 'red-team'
export function ContractDetail() {
const { id } = useParams<{ id: string }>()
@@ -47,6 +67,8 @@ export function ContractDetail() {
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<Tab>('overview')
+ const [notifications, setNotifications] = useState<RedTeamNotification[]>([])
+ const [notificationsLoading, setNotificationsLoading] = useState(false)
useEffect(() => {
async function fetchContract() {
@@ -70,6 +92,28 @@ export function ContractDetail() {
fetchContract()
}, [id])
+ // Fetch red team notifications when viewing red-team tab
+ useEffect(() => {
+ async function fetchNotifications() {
+ if (!id || activeTab !== 'red-team' || !data?.contract.red_team_enabled) return
+
+ try {
+ setNotificationsLoading(true)
+ const response = await fetch(`/api/v1/contracts/${id}/red-team/notifications`)
+ if (response.ok) {
+ const notificationData = await response.json()
+ setNotifications(notificationData.notifications || [])
+ }
+ } catch (err) {
+ console.error('Failed to fetch red team notifications:', err)
+ } finally {
+ setNotificationsLoading(false)
+ }
+ }
+
+ fetchNotifications()
+ }, [id, activeTab, data?.contract.red_team_enabled])
+
if (loading) {
return (
<div className="contract-detail-container">
@@ -108,7 +152,14 @@ export function ContractDetail() {
<Link to="/contracts" className="back-link">
Back to Contracts
</Link>
- <h1 className="contract-title">{contract.name}</h1>
+ <h1 className="contract-title">
+ {contract.name}
+ {contract.red_team_enabled && (
+ <span className="red-team-badge" title="Red Team monitoring enabled">
+ đŸ›Ąī¸ Red Team
+ </span>
+ )}
+ </h1>
{contract.description && (
<p className="contract-description">{contract.description}</p>
)}
@@ -144,6 +195,14 @@ export function ContractDetail() {
>
Repositories ({repositories.length})
</button>
+ {contract.red_team_enabled && (
+ <button
+ className={`tab-button red-team-tab ${activeTab === 'red-team' ? 'active' : ''}`}
+ onClick={() => setActiveTab('red-team')}
+ >
+ đŸ›Ąī¸ Red Team
+ </button>
+ )}
</div>
<div className="contract-tab-content">
@@ -194,8 +253,12 @@ export function ContractDetail() {
) : (
<ul className="task-list">
{tasks.map((task) => (
- <li key={task.id} className="task-item">
- <h3>{task.name}</h3>
+ <li key={task.id} className={`task-item ${task.is_red_team ? 'red-team-task' : ''}`}>
+ <h3>
+ {task.is_red_team && <span className="task-badge red-team">đŸ›Ąī¸</span>}
+ {task.is_supervisor && <span className="task-badge supervisor">👔</span>}
+ {task.name}
+ </h3>
<span className={`task-status status-${task.status}`}>
{task.status}
</span>
@@ -226,6 +289,74 @@ export function ContractDetail() {
)}
</div>
)}
+
+ {activeTab === 'red-team' && contract.red_team_enabled && (
+ <div className="tab-panel red-team-panel">
+ <h2>đŸ›Ąī¸ Red Team Monitoring</h2>
+
+ {contract.red_team_prompt && (
+ <div className="red-team-prompt">
+ <h3>Review Criteria</h3>
+ <p>{contract.red_team_prompt}</p>
+ </div>
+ )}
+
+ <div className="red-team-status">
+ <h3>Red Team Task</h3>
+ {(() => {
+ const redTeamTask = tasks.find(t => t.is_red_team)
+ if (redTeamTask) {
+ return (
+ <div className="red-team-task-info">
+ <span className="task-name">{redTeamTask.name}</span>
+ <span className={`task-status status-${redTeamTask.status}`}>
+ {redTeamTask.status}
+ </span>
+ </div>
+ )
+ }
+ return <p className="no-task">Red team task not yet spawned</p>
+ })()}
+ </div>
+
+ <div className="red-team-notifications">
+ <h3>Alerts ({notifications.length})</h3>
+ {notificationsLoading ? (
+ <p>Loading notifications...</p>
+ ) : notifications.length === 0 ? (
+ <p className="no-alerts">No alerts from red team</p>
+ ) : (
+ <ul className="notification-list">
+ {notifications.map((notification) => (
+ <li
+ key={notification.id}
+ className={`notification-item severity-${notification.severity}`}
+ >
+ <div className="notification-header">
+ <span className={`severity-badge ${notification.severity}`}>
+ {notification.severity === 'critical' && '🚨'}
+ {notification.severity === 'warning' && 'âš ī¸'}
+ {notification.severity === 'info' && 'â„šī¸'}
+ {notification.severity.toUpperCase()}
+ </span>
+ <span className="notification-time">
+ {new Date(notification.created_at).toLocaleString()}
+ </span>
+ </div>
+ <p className="notification-message">{notification.message}</p>
+ {notification.file_path && (
+ <span className="notification-file">File: {notification.file_path}</span>
+ )}
+ {notification.context && (
+ <pre className="notification-context">{notification.context}</pre>
+ )}
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
+ </div>
+ )}
</div>
</div>
)