summaryrefslogblamecommitdiff
path: root/frontend/src/components/ContractDetail.tsx
blob: a9dd55039ff7fbe3d3dfc06768e7b3ce576a9261 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                                  

                         

















                              

















                                           








                                    
                                                                       






                                                                      

                                                                               






















                                                                             





















                                                                                      





































                                                           







                                                                                 


































                                                                                  







                                                                                             

















































                                                                                        





                                                                                                       





























                                                                                         



































































                                                                                                 



            
import React, { useEffect, useState } from 'react'
import { useParams, Link } from 'react-router-dom'

interface FileSummary {
  id: string
  name: string
  description?: string
  contract_phase?: string
}

interface TaskSummary {
  id: string
  name: string
  status: string
  is_supervisor?: boolean
  is_red_team?: boolean
}

interface ContractRepository {
  id: string
  name: string
  source_type: string
  is_primary: boolean
}

interface Contract {
  id: string
  name: string
  description?: string
  contract_type: string
  phase: string
  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 {
  contract: Contract
  repositories: ContractRepository[]
  files: FileSummary[]
  tasks: TaskSummary[]
}

type Tab = 'overview' | 'files' | 'tasks' | 'repositories' | 'red-team'

export function ContractDetail() {
  const { id } = useParams<{ id: string }>()
  const [data, setData] = useState<ContractWithRelations | null>(null)
  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() {
      if (!id) return

      try {
        setLoading(true)
        const response = await fetch(`/api/v1/contracts/${id}`)
        if (!response.ok) {
          throw new Error(`Failed to fetch contract: ${response.statusText}`)
        }
        const contractData = await response.json()
        setData(contractData)
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Unknown error')
      } finally {
        setLoading(false)
      }
    }

    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">
        <div className="loading">Loading contract...</div>
      </div>
    )
  }

  if (error) {
    return (
      <div className="contract-detail-container">
        <div className="error">Error: {error}</div>
        <Link to="/contracts" className="back-link">
          Back to Contracts
        </Link>
      </div>
    )
  }

  if (!data) {
    return (
      <div className="contract-detail-container">
        <div className="not-found">Contract not found</div>
        <Link to="/contracts" className="back-link">
          Back to Contracts
        </Link>
      </div>
    )
  }

  const { contract, repositories, files, tasks } = data

  return (
    <div className="contract-detail-container">
      <div className="contract-detail-header">
        <Link to="/contracts" className="back-link">
          Back to Contracts
        </Link>
        <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>
        )}
        <div className="contract-meta">
          <span>Phase: {contract.phase}</span>
          <span>Status: {contract.status}</span>
          <span>Version: {contract.version}</span>
        </div>
      </div>

      <div className="contract-tabs">
        <button
          className={`tab-button ${activeTab === 'overview' ? 'active' : ''}`}
          onClick={() => setActiveTab('overview')}
        >
          Overview
        </button>
        <button
          className={`tab-button ${activeTab === 'files' ? 'active' : ''}`}
          onClick={() => setActiveTab('files')}
        >
          Files ({files.length})
        </button>
        <button
          className={`tab-button ${activeTab === 'tasks' ? 'active' : ''}`}
          onClick={() => setActiveTab('tasks')}
        >
          Tasks ({tasks.length})
        </button>
        <button
          className={`tab-button ${activeTab === 'repositories' ? 'active' : ''}`}
          onClick={() => setActiveTab('repositories')}
        >
          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">
        {activeTab === 'overview' && (
          <div className="tab-panel">
            <h2>Contract Overview</h2>
            <dl className="overview-list">
              <dt>Type</dt>
              <dd>{contract.contract_type}</dd>
              <dt>Phase</dt>
              <dd>{contract.phase}</dd>
              <dt>Status</dt>
              <dd>{contract.status}</dd>
              <dt>Created</dt>
              <dd>{new Date(contract.created_at).toLocaleString()}</dd>
            </dl>
          </div>
        )}

        {activeTab === 'files' && (
          <div className="tab-panel">
            <h2>Files</h2>
            {files.length === 0 ? (
              <p>No files in this contract</p>
            ) : (
              <ul className="file-list">
                {files.map((file) => (
                  <li key={file.id} className="file-item">
                    <Link to={`/contracts/${contract.id}/files/${file.id}`}>
                      <h3>{file.name}</h3>
                      {file.description && <p>{file.description}</p>}
                      {file.contract_phase && (
                        <span className="file-phase">Phase: {file.contract_phase}</span>
                      )}
                    </Link>
                  </li>
                ))}
              </ul>
            )}
          </div>
        )}

        {activeTab === 'tasks' && (
          <div className="tab-panel">
            <h2>Tasks</h2>
            {tasks.length === 0 ? (
              <p>No tasks in this contract</p>
            ) : (
              <ul className="task-list">
                {tasks.map((task) => (
                  <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>
                  </li>
                ))}
              </ul>
            )}
          </div>
        )}

        {activeTab === 'repositories' && (
          <div className="tab-panel">
            <h2>Repositories</h2>
            {repositories.length === 0 ? (
              <p>No repositories linked to this contract</p>
            ) : (
              <ul className="repository-list">
                {repositories.map((repo) => (
                  <li key={repo.id} className="repository-item">
                    <h3>
                      {repo.name}
                      {repo.is_primary && <span className="primary-badge">Primary</span>}
                    </h3>
                    <span className="repo-type">{repo.source_type}</span>
                  </li>
                ))}
              </ul>
            )}
          </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>
  )
}