Files
onever_drive/frontend/src/services/api.ts
T

240 lines
6.7 KiB
TypeScript

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 <T>(endpoint: string, options: RequestInit = {}): Promise<T> => {
const token = getAuthToken();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
};
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<DashboardStats>('/stats'),
// Clients
getClients: () => request<ClientItem[]>('/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<ClientItem>(`/clients/${clientId}`, {
method: 'PATCH',
body: JSON.stringify(data),
}),
getSettings: () => request<SystemSettingsResponse>('/settings'),
updateSettings: (data: Partial<SystemSettingsResponse>) =>
request<SystemSettingsResponse>('/settings', {
method: 'PUT',
body: JSON.stringify(data),
}),
// Jobs
getJobs: (clientId?: number) =>
request<BackupJobItem[]>(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<BackupJobItem>('/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<BackupFileItem[]>(`/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<EventLogItem[]>(`/events?limit=${limit}`),
};