import type { PlantAsset, AssetStatus, AssetPhoto, AlertRule, PlantAlert, AppSetting, PhotoType, TelemetryField, } from './types' const BASE = '/plant/api' async function request(path: string, opts: RequestInit = {}): Promise { const res = await fetch(`${BASE}${path}`, { credentials: 'include', headers: { 'Content-Type': 'application/json', ...opts.headers }, ...opts, }) if (res.status === 401) { ;(window.top ?? window).location.href = '/login' throw new Error('Unauthenticated') } if (!res.ok) { const err = await res.json().catch(() => ({ error: res.statusText })) throw new Error(err.error || `Request failed: ${res.status}`) } return res.json() } // Assets export function fetchAssets(): Promise { return request('/assets') } export function createAsset(body: Partial): Promise { return request('/assets', { method: 'POST', body: JSON.stringify(body) }) } export function updateAsset(id: number, body: Partial): Promise { return request(`/assets/${id}`, { method: 'PATCH', body: JSON.stringify(body) }) } export function fetchAssetLatest(id: number): Promise { return request(`/assets/${id}/latest`) } // Asset photos — multipart, so no JSON content-type header export async function uploadAssetPhoto(assetId: number, file: File, photoType: PhotoType): Promise { const form = new FormData() form.append('photo_type', photoType) form.append('file', file) const res = await fetch(`${BASE}/assets/${assetId}/photos`, { method: 'POST', credentials: 'include', body: form }) if (!res.ok) { const err = await res.json().catch(() => ({ error: res.statusText })) throw new Error(err.error || `Upload failed: ${res.status}`) } return res.json() } export function fetchAssetPhotos(assetId: number): Promise { return request(`/assets/${assetId}/photos`) } export function deleteAssetPhoto(id: number): Promise<{ ok: boolean }> { return request(`/photos/${id}`, { method: 'DELETE' }) } export function photoUrl(filePath: string): string { return `${BASE}/uploads${filePath}` } // Status (dashboard) export function fetchStatus(): Promise<{ open_alerts: number; mqtt_connected: boolean; assets: AssetStatus[] }> { return request('/status') } // Alerts export function fetchAlerts(filters: { status?: string; severity?: string } = {}): Promise { const params = new URLSearchParams() if (filters.status) params.set('status', filters.status) if (filters.severity) params.set('severity', filters.severity) const qs = params.toString() return request(`/alerts${qs ? `?${qs}` : ''}`) } export function acknowledgeAlert(id: number): Promise { return request(`/alerts/${id}`, { method: 'PATCH', body: JSON.stringify({ action: 'acknowledge' }) }) } export function resolveAlert(id: number): Promise { return request(`/alerts/${id}`, { method: 'PATCH', body: JSON.stringify({ action: 'resolve' }) }) } // Alert rules — threshold/asset_id/asset_type are write-side numbers/nulls, // distinct enough from AlertRule's read-side (string threshold) shape that a // plain object type is simpler than fighting Partial here. export interface AlertRuleInput { asset_id?: number | null asset_type?: AlertRule['asset_type'] field_key?: string condition?: AlertRule['condition'] threshold?: number severity?: AlertRule['severity'] active?: boolean } export function fetchAlertRules(): Promise { return request('/alert-rules') } export function createAlertRule(body: AlertRuleInput): Promise { return request('/alert-rules', { method: 'POST', body: JSON.stringify(body) }) } export function updateAlertRule(id: number, body: AlertRuleInput): Promise { return request(`/alert-rules/${id}`, { method: 'PATCH', body: JSON.stringify(body) }) } export function deleteAlertRule(id: number): Promise<{ ok: boolean }> { return request(`/alert-rules/${id}`, { method: 'DELETE' }) } // Settings export function getSettings(): Promise<{ settings: AppSetting[] }> { return request('/settings') } export function saveSettings(settings: { key: string; value: string }[]): Promise<{ ok: boolean }> { return request('/settings', { method: 'PUT', body: JSON.stringify({ settings }) }) } export function fetchMqttStatus(): Promise<{ connected: boolean; note: string }> { return request('/settings/mqtt-status') }