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:
50
Frontend/src/api/client.ts
Normal file
50
Frontend/src/api/client.ts
Normal 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
95
Frontend/src/api/types.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user