feat: implement Phase 2 (Go Agent) and Phase 3 (React Frontend MVP)

Phase 2 - Go Agent Core:
- gRPC client with exponential backoff reconnect logic
- Command executor (PowerShell/sh cross-platform)
- Proto stubs regenerated with module= option (correct output path)
- gRPC upgraded to v1.79.3 (BidiStreamingClient support)

Phase 3 - React Frontend MVP:
- Vite + React 18 + TypeScript setup with Tailwind CSS v4
- TanStack Query for data fetching, API client + TypeScript types
- Dashboard page: stats cards (agents/status/tickets) + sortable agents table
- Agent detail page: CPU/RAM charts (Recharts), disk usage, shell command executor
- Tickets page: CRUD with modals, filters, sortable table
- Dark mode with CSS custom properties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-19 12:42:52 +01:00
parent 51052261f5
commit 418fc5b6d5
30 changed files with 7670 additions and 24 deletions

View File

@@ -0,0 +1,50 @@
import type {
Agent,
AgentMetric,
TaskItem,
Ticket,
CreateTaskRequest,
CreateTicketRequest,
UpdateTicketRequest,
} from './types'
const BASE_URL = '/api/v1'
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
headers: { 'Content-Type': 'application/json', ...options?.headers },
...options,
})
if (!res.ok) {
const text = await res.text()
throw new Error(`HTTP ${res.status}: ${text}`)
}
if (res.status === 204) return undefined as T
return res.json() as Promise<T>
}
// Agents
export const agentsApi = {
list: () => request<Agent[]>('/agents'),
get: (id: string) => request<Agent>(`/agents/${id}`),
getMetrics: (id: string, limit = 100) =>
request<AgentMetric[]>(`/agents/${id}/metrics?limit=${limit}`),
}
// Tasks
export const tasksApi = {
create: (data: CreateTaskRequest) =>
request<TaskItem>('/tasks', { method: 'POST', body: JSON.stringify(data) }),
listForAgent: (agentId: string) =>
request<TaskItem[]>(`/tasks?agentId=${agentId}`),
}
// Tickets
export const ticketsApi = {
list: () => request<Ticket[]>('/tickets'),
get: (id: number) => request<Ticket>(`/tickets/${id}`),
create: (data: CreateTicketRequest) =>
request<Ticket>('/tickets', { method: 'POST', body: JSON.stringify(data) }),
update: (id: number, data: UpdateTicketRequest) =>
request<Ticket>(`/tickets/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
}

95
Frontend/src/api/types.ts Normal file
View File

@@ -0,0 +1,95 @@
export type AgentStatus = 'Online' | 'Offline' | 'Degraded' | 'Pending'
export type OsType = 'Windows' | 'Linux'
export type TaskStatus = 'Pending' | 'InProgress' | 'Completed' | 'Failed' | 'Cancelled'
export type TaskType = 'Shell' | 'InstallSoftware' | 'UninstallSoftware' | 'UpdateAgent' | 'NetworkScan'
export type TicketStatus = 'Open' | 'InProgress' | 'Resolved' | 'Closed'
export type TicketPriority = 'Low' | 'Medium' | 'High' | 'Critical'
export type AlertSeverity = 'Info' | 'Warning' | 'Critical'
export interface Agent {
id: string
hostname: string
osType: OsType
osVersion: string
ipAddress: string
macAddress: string
agentVersion: string
status: AgentStatus
lastSeen: string
tags: string[]
enrolledAt: string
}
export interface DiskInfo {
mountPoint: string
totalBytes: number
freeBytes: number
filesystem: string
}
export interface NetworkInterfaceInfo {
name: string
ipAddress: string
macAddress: string
bytesSent: number
bytesRecv: number
}
export interface SystemMetrics {
cpuUsagePercent: number
memoryUsagePercent: number
memoryTotalBytes: number
memoryAvailableBytes: number
disks: DiskInfo[]
networkInterfaces: NetworkInterfaceInfo[]
uptimeSeconds: number
}
export interface AgentMetric {
id: number
agentId: string
timestamp: string
metrics: SystemMetrics
}
export interface TaskItem {
id: string
agentId: string
type: TaskType
status: TaskStatus
payload: Record<string, unknown> | null
result: Record<string, unknown> | null
createdAt: string
completedAt: string | null
}
export interface Ticket {
id: number
title: string
description: string
status: TicketStatus
priority: TicketPriority
agentId: string | null
createdAt: string
updatedAt: string
}
export interface CreateTaskRequest {
agentId: string
type: TaskType
payload?: Record<string, unknown>
}
export interface CreateTicketRequest {
title: string
description: string
priority: TicketPriority
agentId?: string
}
export interface UpdateTicketRequest {
title?: string
description?: string
status?: TicketStatus
priority?: TicketPriority
}