const API_BASE = '/api'; export interface DashboardStats { total_clients: number; online_clients: number; offline_clients: number; total_jobs: number; backups_today_count: number; backups_today_success: number; backups_today_failed: number; active_uploads_count: number; storage: { total_bytes: number; used_bytes: number; free_bytes: number; usage_percent: number; storage_root: string; }; } export interface ClientItem { id: number; client_code: string; name: string; alias?: string; hostname?: string; os_info?: string; ip_address?: string; agent_version: string; status: string; storage_used_bytes: number; storage_quota_bytes: number; last_seen_at?: string; last_backup_at?: string; is_active: boolean; created_at: string; } export interface BackupJobItem { id: number; job_code: string; client_id: number; name: string; source_path: string; file_patterns: string; schedule_cron: string; is_active: boolean; keep_daily: number; keep_weekly: number; keep_monthly: number; min_stable_time_seconds: number; status: string; last_run_at?: string; next_run_at?: string; created_at: string; } export interface BackupFileItem { id: number; client_id: number; job_id?: number; session_id?: number; filename: string; relative_path: string; file_size: number; sha256: string; retention_tag: string; is_active: boolean; created_at: string; } export interface EventLogItem { id: number; timestamp: string; event_type: string; severity: string; client_id?: number; job_id?: number; user_email?: string; ip_address?: string; message: string; } export interface SystemSettingsResponse { storage_root: string; global_quota_gb: number; default_client_quota_gb: number; default_keep_daily: number; default_keep_weekly: number; default_keep_monthly: number; } export const getAuthToken = (): string | null => { return localStorage.getItem('oed_token'); }; export const getCurrentUser = (): any | null => { const saved = localStorage.getItem('oed_user'); if (saved) { try { return JSON.parse(saved); } catch { return null; } } return null; }; export const setAuthToken = (token: string | null) => { if (token) { localStorage.setItem('oed_token', token); } else { localStorage.removeItem('oed_token'); localStorage.removeItem('oed_user'); } }; const request = async (endpoint: string, options: RequestInit = {}): Promise => { const token = getAuthToken(); const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record), }; if (token) { headers['Authorization'] = `Bearer ${token}`; } const response = await fetch(`${API_BASE}${endpoint}`, { ...options, headers, }); if (response.status === 401) { setAuthToken(null); window.dispatchEvent(new Event('oed_unauthorized')); const errorData = await response.json().catch(() => ({ detail: 'Authentication required' })); throw new Error(errorData.detail || 'Authentication required'); } if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'Network error' })); throw new Error(errorData.detail || `Request failed with status ${response.status}`); } return response.json(); }; export const api = { // Auth login: (email: string, password: string) => request<{ access_token: string; user: any }>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), }), // Stats getStats: () => request('/stats'), // Clients getClients: () => request('/clients'), createRegistrationCode: (clientNameHint?: string) => request<{ code: string; expires_at: string }>('/clients/registration-code', { method: 'POST', body: JSON.stringify({ client_name_hint: clientNameHint, expires_in_hours: 48 }), }), revokeClient: (clientId: number) => request<{ message: string }>(`/clients/${clientId}/revoke`, { method: 'POST' }), generateReRegisterCode: (clientId: number) => request<{ code: string; expires_at: string; client_id: number }>(`/clients/${clientId}/re-register`, { method: 'POST' }), deleteClient: (clientId: number) => request<{ message: string }>(`/clients/${clientId}`, { method: 'DELETE' }), updateClient: (clientId: number, data: { alias?: string; storage_quota_bytes?: number }) => request(`/clients/${clientId}`, { method: 'PATCH', body: JSON.stringify(data), }), getSettings: () => request('/settings'), updateSettings: (data: Partial) => request('/settings', { method: 'PUT', body: JSON.stringify(data), }), // Jobs getJobs: (clientId?: number) => request(clientId ? `/jobs?client_id=${clientId}` : '/jobs'), createJob: (jobData: { client_id: number; name: string; source_path: string; file_patterns: string; schedule_cron: string; keep_daily: number; keep_weekly: number; keep_monthly: number; min_stable_time_seconds: number; }) => request('/jobs', { method: 'POST', body: JSON.stringify(jobData), }), triggerJob: (jobId: number) => request<{ message: string }>(`/jobs/${jobId}/trigger`, { method: 'POST' }), deleteJob: (jobId: number) => request<{ message: string }>(`/jobs/${jobId}`, { method: 'DELETE' }), // Backups getBackups: (clientId?: number, jobId?: number) => { const params = new URLSearchParams(); if (clientId) params.append('client_id', clientId.toString()); if (jobId) params.append('job_id', jobId.toString()); return request(`/backups?${params.toString()}`); }, downloadBackup: async (backup: BackupFileItem) => { const token = getAuthToken(); const response = await fetch(`${API_BASE}/backups/${backup.id}/download`, { headers: token ? { 'Authorization': `Bearer ${token}` } : {}, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'Network error' })); throw new Error(errorData.detail || `Download failed with status ${response.status}`); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = backup.filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); }, deleteBackup: (backupId: number) => request<{ message: string }>(`/backups/${backupId}`, { method: 'DELETE' }), // Events getEvents: (limit: number = 50) => request(`/events?limit=${limit}`), };