711 lines
26 KiB
TypeScript
711 lines
26 KiB
TypeScript
// BASE is the Next.js rewrite prefix defined in next.config.ts:
|
|
// /api/backend/:path* → http://backend:8000/:path*
|
|
// All fetch paths below start with /api/... giving the full path /api/backend/api/...
|
|
// This double "api" is intentional: /api/backend is the proxy mount point, /api/... is the FastAPI route.
|
|
const BASE = process.env.NEXT_PUBLIC_API_URL ?? "/api/backend"
|
|
|
|
async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`, { cache: "no-store", ...init })
|
|
if (!res.ok) throw new Error(`API ${path} → ${res.status}`)
|
|
if (res.status === 204) return undefined as T
|
|
return res.json()
|
|
}
|
|
|
|
// ── Types ────────────────────────────────────────────────────────
|
|
|
|
export type KpiData = {
|
|
total_power_kw: number
|
|
pue: number
|
|
avg_temperature: number
|
|
active_alarms: number
|
|
}
|
|
|
|
export type PowerBucket = {
|
|
bucket: string
|
|
total_kw: number
|
|
}
|
|
|
|
export type TempBucket = {
|
|
bucket: string
|
|
room_id: string
|
|
avg_temp: number
|
|
}
|
|
|
|
export type Alarm = {
|
|
id: number
|
|
sensor_id: string | null
|
|
site_id: string
|
|
room_id: string | null
|
|
rack_id: string | null
|
|
severity: "critical" | "warning" | "info"
|
|
message: string
|
|
state: string
|
|
triggered_at: string
|
|
}
|
|
|
|
export type RoomStatus = {
|
|
room_id: string
|
|
avg_temp: number
|
|
total_kw: number
|
|
alarm_count: number
|
|
status: "ok" | "warning" | "critical"
|
|
}
|
|
|
|
// ── Fetchers ─────────────────────────────────────────────────────
|
|
|
|
export const fetchKpis = (siteId: string) =>
|
|
apiFetch<KpiData>(`/api/readings/kpis?site_id=${siteId}`)
|
|
|
|
export const fetchPowerHistory = (siteId: string, hours = 1) =>
|
|
apiFetch<PowerBucket[]>(`/api/readings/site-power-history?site_id=${siteId}&hours=${hours}`)
|
|
|
|
export const fetchTempHistory = (siteId: string, hours = 1) =>
|
|
apiFetch<TempBucket[]>(`/api/readings/room-temp-history?site_id=${siteId}&hours=${hours}`)
|
|
|
|
export const fetchAlarms = (siteId: string, state = "active", limit = 100) =>
|
|
apiFetch<Alarm[]>(`/api/alarms?site_id=${siteId}&state=${state}&limit=${limit}`)
|
|
|
|
export const fetchRoomStatus = (siteId: string) =>
|
|
apiFetch<RoomStatus[]>(`/api/readings/room-status?site_id=${siteId}`)
|
|
|
|
export type AlarmStats = {
|
|
active: number
|
|
acknowledged: number
|
|
resolved: number
|
|
critical: number
|
|
warning: number
|
|
}
|
|
|
|
export const fetchAlarmStats = (siteId: string) =>
|
|
apiFetch<AlarmStats>(`/api/alarms/stats?site_id=${siteId}`)
|
|
|
|
export const acknowledgeAlarm = async (alarmId: number): Promise<void> => {
|
|
const res = await fetch(`${BASE}/api/alarms/${alarmId}/acknowledge`, { method: "POST" })
|
|
if (!res.ok) throw new Error(`Acknowledge failed: ${res.status}`)
|
|
}
|
|
|
|
export const resolveAlarm = async (alarmId: number): Promise<void> => {
|
|
const res = await fetch(`${BASE}/api/alarms/${alarmId}/resolve`, { method: "POST" })
|
|
if (!res.ok) throw new Error(`Resolve failed: ${res.status}`)
|
|
}
|
|
|
|
export type RackAsset = {
|
|
rack_id: string
|
|
temp: number | null
|
|
power_kw: number | null
|
|
status: "ok" | "warning" | "critical" | "unknown"
|
|
alarm_count: number
|
|
}
|
|
|
|
export type CracAsset = {
|
|
crac_id: string
|
|
state: "online" | "fault" | "unknown"
|
|
supply_temp: number | null
|
|
return_temp: number | null
|
|
fan_pct: number | null
|
|
}
|
|
|
|
export type UpsAsset = {
|
|
ups_id: string
|
|
state: "online" | "battery" | "overload" | "unknown"
|
|
charge_pct: number | null
|
|
load_pct: number | null
|
|
runtime_min: number | null
|
|
voltage_v: number | null
|
|
}
|
|
|
|
export type UpsHistoryPoint = {
|
|
bucket: string
|
|
charge_pct: number | null
|
|
load_pct: number | null
|
|
runtime_min: number | null
|
|
voltage_v: number | null
|
|
}
|
|
|
|
export type AssetsData = {
|
|
site_id: string
|
|
rooms: { room_id: string; crac: CracAsset; racks: RackAsset[] }[]
|
|
ups_units: UpsAsset[]
|
|
}
|
|
|
|
export const fetchAssets = (siteId: string) =>
|
|
apiFetch<AssetsData>(`/api/assets?site_id=${siteId}`)
|
|
|
|
export type RackPower = { rack_id: string; power_kw: number | null }
|
|
export type RoomPowerBreakdown = { room_id: string; racks: RackPower[] }
|
|
export type PowerHistoryBucket = { bucket: string; room_id: string; total_kw: number }
|
|
|
|
export const fetchRackBreakdown = (siteId: string) =>
|
|
apiFetch<RoomPowerBreakdown[]>(`/api/power/rack-breakdown?site_id=${siteId}`)
|
|
|
|
export const fetchRoomPowerHistory = (siteId: string, hours = 6) =>
|
|
apiFetch<PowerHistoryBucket[]>(`/api/power/room-history?site_id=${siteId}&hours=${hours}`)
|
|
|
|
export const fetchUpsStatus = (siteId: string) =>
|
|
apiFetch<UpsAsset[]>(`/api/power/ups?site_id=${siteId}`)
|
|
|
|
export const fetchUpsHistory = (siteId: string, upsId: string, hours = 6) =>
|
|
apiFetch<UpsHistoryPoint[]>(`/api/power/ups/history?site_id=${siteId}&ups_id=${upsId}&hours=${hours}`)
|
|
|
|
export type RackEnvReading = { rack_id: string; temperature: number | null; humidity: number | null }
|
|
export type RoomEnvReadings = { room_id: string; racks: RackEnvReading[] }
|
|
export type HumidityBucket = { bucket: string; room_id: string; avg_humidity: number }
|
|
export type CracStatus = {
|
|
crac_id: string
|
|
room_id: string | null
|
|
state: "online" | "fault"
|
|
delta: number | null
|
|
rated_capacity_kw: number
|
|
// Thermal
|
|
supply_temp: number | null
|
|
return_temp: number | null
|
|
supply_humidity: number | null
|
|
return_humidity: number | null
|
|
airflow_cfm: number | null
|
|
filter_dp_pa: number | null
|
|
// Capacity
|
|
cooling_capacity_kw: number | null
|
|
cooling_capacity_pct: number | null
|
|
cop: number | null
|
|
sensible_heat_ratio: number | null
|
|
// Compressor
|
|
compressor_state: number | null
|
|
compressor_load_pct: number | null
|
|
compressor_power_kw: number | null
|
|
compressor_run_hours: number | null
|
|
high_pressure_bar: number | null
|
|
low_pressure_bar: number | null
|
|
discharge_superheat_c: number | null
|
|
liquid_subcooling_c: number | null
|
|
// Fan
|
|
fan_pct: number | null
|
|
fan_rpm: number | null
|
|
fan_power_kw: number | null
|
|
fan_run_hours: number | null
|
|
// Electrical
|
|
total_unit_power_kw: number | null
|
|
input_voltage_v: number | null
|
|
input_current_a: number | null
|
|
power_factor: number | null
|
|
}
|
|
|
|
export type CracHistoryPoint = {
|
|
bucket: string
|
|
supply_temp: number | null
|
|
return_temp: number | null
|
|
delta_t: number | null
|
|
capacity_kw: number | null
|
|
capacity_pct: number | null
|
|
cop: number | null
|
|
comp_load: number | null
|
|
filter_dp: number | null
|
|
fan_pct: number | null
|
|
}
|
|
|
|
export const fetchRackEnvReadings = (siteId: string) =>
|
|
apiFetch<RoomEnvReadings[]>(`/api/env/rack-readings?site_id=${siteId}`)
|
|
|
|
export const fetchHumidityHistory = (siteId: string, hours = 6) =>
|
|
apiFetch<HumidityBucket[]>(`/api/env/humidity-history?site_id=${siteId}&hours=${hours}`)
|
|
|
|
export const fetchCracStatus = (siteId: string) =>
|
|
apiFetch<CracStatus[]>(`/api/env/crac-status?site_id=${siteId}`)
|
|
|
|
export type RackHistoryPoint = { bucket: string; temperature?: number; humidity?: number; power_kw?: number }
|
|
export type RackHistory = { rack_id: string; site_id: string; history: RackHistoryPoint[]; alarms: Alarm[] }
|
|
|
|
export const fetchRackHistory = (siteId: string, rackId: string, hours = 6) =>
|
|
apiFetch<RackHistory>(`/api/env/rack-history?site_id=${siteId}&rack_id=${rackId}&hours=${hours}`)
|
|
|
|
export type CracDeltaPoint = { bucket: string; delta: number }
|
|
|
|
export const fetchCracDeltaHistory = (siteId: string, cracId: string, hours = 1) =>
|
|
apiFetch<CracDeltaPoint[]>(`/api/env/crac-delta-history?site_id=${siteId}&crac_id=${cracId}&hours=${hours}`)
|
|
|
|
export const fetchCracHistory = (siteId: string, cracId: string, hours = 6) =>
|
|
apiFetch<CracHistoryPoint[]>(`/api/env/crac-history?site_id=${siteId}&crac_id=${cracId}&hours=${hours}`)
|
|
|
|
export type ReportSummary = {
|
|
site_id: string
|
|
generated_at: string
|
|
kpis: { total_power_kw: number; avg_temperature: number }
|
|
alarm_stats: { active: number; acknowledged: number; resolved: number; critical: number; warning: number }
|
|
crac_uptime: { crac_id: string; room_id: string; uptime_pct: number }[]
|
|
ups_uptime: { ups_id: string; uptime_pct: number }[]
|
|
}
|
|
|
|
export const fetchReportSummary = (siteId: string) =>
|
|
apiFetch<ReportSummary>(`/api/reports/summary?site_id=${siteId}`)
|
|
|
|
export const reportExportUrl = (type: "power" | "temperature" | "alarms", siteId: string, hours = 24) =>
|
|
type === "alarms"
|
|
? `${BASE}/api/reports/export/alarms?site_id=${siteId}`
|
|
: `${BASE}/api/reports/export/${type}?site_id=${siteId}&hours=${hours}`
|
|
|
|
export type RackCapacity = {
|
|
rack_id: string
|
|
room_id: string
|
|
power_kw: number | null
|
|
power_capacity_kw: number
|
|
power_pct: number | null
|
|
temp: number | null
|
|
}
|
|
|
|
export type RoomCapacity = {
|
|
room_id: string
|
|
power: { used_kw: number; capacity_kw: number; pct: number; headroom_kw: number }
|
|
cooling: { load_kw: number; capacity_kw: number; pct: number; headroom_kw: number }
|
|
space: { racks_total: number; racks_populated: number; pct: number }
|
|
}
|
|
|
|
export type CapacitySummary = {
|
|
site_id: string
|
|
config: { rack_power_kw: number; room_power_kw: number; crac_cooling_kw: number; rack_u_total: number }
|
|
rooms: RoomCapacity[]
|
|
racks: RackCapacity[]
|
|
}
|
|
|
|
export const fetchCapacitySummary = (siteId: string) =>
|
|
apiFetch<CapacitySummary>(`/api/capacity/summary?site_id=${siteId}`)
|
|
|
|
export type Device = {
|
|
device_id: string
|
|
name: string
|
|
type: "server" | "switch" | "patch_panel" | "pdu" | "storage" | "firewall" | "kvm"
|
|
rack_id: string
|
|
room_id: string
|
|
site_id: string
|
|
u_start: number
|
|
u_height: number
|
|
ip: string
|
|
serial: string
|
|
model: string
|
|
status: "online" | "offline" | "unknown"
|
|
power_draw_w: number
|
|
}
|
|
|
|
export const fetchAllDevices = (siteId: string) =>
|
|
apiFetch<Device[]>(`/api/assets/devices?site_id=${siteId}`)
|
|
|
|
export const fetchRackDevices = (siteId: string, rackId: string) =>
|
|
apiFetch<Device[]>(`/api/assets/rack-devices?site_id=${siteId}&rack_id=${rackId}`)
|
|
|
|
// ── Generator ─────────────────────────────────────────────────────
|
|
|
|
export type GeneratorStatus = {
|
|
gen_id: string
|
|
state: "standby" | "running" | "test" | "fault" | "unknown"
|
|
fuel_pct: number | null
|
|
fuel_litres: number | null
|
|
fuel_rate_lph: number | null
|
|
load_kw: number | null
|
|
load_pct: number | null
|
|
run_hours: number | null
|
|
voltage_v: number | null
|
|
frequency_hz: number | null
|
|
engine_rpm: number | null
|
|
oil_pressure_bar: number | null
|
|
coolant_temp_c: number | null
|
|
exhaust_temp_c: number | null
|
|
alternator_temp_c: number | null
|
|
power_factor: number | null
|
|
battery_v: number | null
|
|
}
|
|
|
|
export type GeneratorHistoryPoint = {
|
|
bucket: string
|
|
load_pct: number | null
|
|
fuel_pct: number | null
|
|
coolant_temp_c: number | null
|
|
exhaust_temp_c: number | null
|
|
frequency_hz: number | null
|
|
alternator_temp_c: number | null
|
|
}
|
|
|
|
export const fetchGeneratorStatus = (siteId: string) =>
|
|
apiFetch<GeneratorStatus[]>(`/api/generator/status?site_id=${siteId}`)
|
|
|
|
export const fetchGeneratorHistory = (siteId: string, genId: string, hours = 6) =>
|
|
apiFetch<GeneratorHistoryPoint[]>(`/api/generator/history?site_id=${siteId}&gen_id=${genId}&hours=${hours}`)
|
|
|
|
// ── ATS ───────────────────────────────────────────────────────────
|
|
|
|
export type AtsStatus = {
|
|
ats_id: string
|
|
state: "stable" | "transferring"
|
|
active_feed: "utility-a" | "utility-b" | "generator"
|
|
transfer_count: number
|
|
last_transfer_ms: number | null
|
|
utility_a_v: number | null
|
|
utility_b_v: number | null
|
|
generator_v: number | null
|
|
}
|
|
|
|
export const fetchAtsStatus = (siteId: string) =>
|
|
apiFetch<AtsStatus[]>(`/api/power/ats?site_id=${siteId}`)
|
|
|
|
// ── Phase breakdown ───────────────────────────────────────────────
|
|
|
|
export type RackPhase = {
|
|
rack_id: string
|
|
room_id: string
|
|
phase_a_kw: number | null
|
|
phase_b_kw: number | null
|
|
phase_c_kw: number | null
|
|
phase_a_a: number | null
|
|
phase_b_a: number | null
|
|
phase_c_a: number | null
|
|
imbalance_pct: number | null
|
|
}
|
|
export type RoomPhase = { room_id: string; racks: RackPhase[] }
|
|
|
|
export const fetchPhaseBreakdown = (siteId: string) =>
|
|
apiFetch<RoomPhase[]>(`/api/power/phase?site_id=${siteId}`)
|
|
|
|
// ── Power redundancy ──────────────────────────────────────────────
|
|
|
|
export type PowerRedundancy = {
|
|
site_id: string
|
|
level: "2N" | "N+1" | "N"
|
|
ups_total: number
|
|
ups_online: number
|
|
generator_ok: boolean
|
|
ats_active_feed: string | null
|
|
notes: string
|
|
}
|
|
|
|
export const fetchPowerRedundancy = (siteId: string) =>
|
|
apiFetch<PowerRedundancy>(`/api/power/redundancy?site_id=${siteId}`)
|
|
|
|
// ── Utility power ─────────────────────────────────────────────────
|
|
|
|
export type UtilityPower = {
|
|
site_id: string
|
|
total_kw: number
|
|
tariff_sgd_kwh: number
|
|
kwh_month_to_date: number
|
|
cost_sgd_mtd: number
|
|
kwh_annual_est: number
|
|
cost_sgd_annual_est: number
|
|
currency: string
|
|
}
|
|
|
|
export const fetchUtilityPower = (siteId: string) =>
|
|
apiFetch<UtilityPower>(`/api/power/utility?site_id=${siteId}`)
|
|
|
|
// ── Chiller ───────────────────────────────────────────────────────
|
|
|
|
export type ChillerStatus = {
|
|
chiller_id: string
|
|
state: "online" | "fault" | "unknown"
|
|
chw_supply_c: number | null
|
|
chw_return_c: number | null
|
|
chw_delta_c: number | null
|
|
flow_gpm: number | null
|
|
cooling_load_kw: number | null
|
|
cooling_load_pct: number | null
|
|
cop: number | null
|
|
compressor_load_pct: number | null
|
|
condenser_pressure_bar: number | null
|
|
evaporator_pressure_bar: number | null
|
|
cw_supply_c: number | null
|
|
cw_return_c: number | null
|
|
run_hours: number | null
|
|
}
|
|
|
|
export type ChillerHistoryPoint = {
|
|
bucket: string
|
|
cop: number | null
|
|
load_kw: number | null
|
|
load_pct: number | null
|
|
chw_supply_c: number | null
|
|
chw_return_c: number | null
|
|
comp_load: number | null
|
|
}
|
|
|
|
export const fetchChillerStatus = (siteId: string) =>
|
|
apiFetch<ChillerStatus[]>(`/api/cooling/status?site_id=${siteId}`)
|
|
|
|
export const fetchChillerHistory = (siteId: string, chillerId: string, hours = 6) =>
|
|
apiFetch<ChillerHistoryPoint[]>(`/api/cooling/history?site_id=${siteId}&chiller_id=${chillerId}&hours=${hours}`)
|
|
|
|
// ── Fire / VESDA ──────────────────────────────────────────────────
|
|
|
|
export type FireZoneStatus = {
|
|
zone_id: string
|
|
room_id: string | null
|
|
level: "normal" | "alert" | "action" | "fire"
|
|
obscuration_pct_m: number | null
|
|
detector_1_ok: boolean
|
|
detector_2_ok: boolean
|
|
power_ok: boolean
|
|
flow_ok: boolean
|
|
}
|
|
|
|
export const fetchFireStatus = (siteId: string) =>
|
|
apiFetch<FireZoneStatus[]>(`/api/fire/status?site_id=${siteId}`)
|
|
|
|
// ── Leak sensors ──────────────────────────────────────────────────
|
|
|
|
export type LeakSensorStatus = {
|
|
sensor_id: string
|
|
floor_zone: string
|
|
under_floor: boolean
|
|
near_crac: boolean
|
|
room_id: string | null
|
|
state: "clear" | "detected" | "unknown"
|
|
recorded_at: string | null
|
|
}
|
|
|
|
export const fetchLeakStatus = (siteId: string) =>
|
|
apiFetch<LeakSensorStatus[]>(`/api/leak/status?site_id=${siteId}`)
|
|
|
|
// ── Energy report ─────────────────────────────────────────────────
|
|
|
|
export type EnergyReport = {
|
|
site_id: string
|
|
period_days: number
|
|
from_date: string
|
|
to_date: string
|
|
kwh_total: number
|
|
cost_sgd: number
|
|
tariff_sgd_kwh: number
|
|
currency: string
|
|
pue_estimated: number
|
|
pue_trend: { day: string; avg_it_kw: number; pue_est: number }[]
|
|
}
|
|
|
|
export const fetchEnergyReport = (siteId: string, days = 30) =>
|
|
apiFetch<EnergyReport>(`/api/reports/energy?site_id=${siteId}&days=${days}`)
|
|
|
|
// ── Network ───────────────────────────────────────────────────────
|
|
|
|
export type NetworkSwitchStatus = {
|
|
switch_id: string
|
|
name: string
|
|
model: string
|
|
room_id: string
|
|
rack_id: string
|
|
role: "core" | "edge" | "access"
|
|
port_count: number
|
|
state: "up" | "degraded" | "down" | "unknown"
|
|
uptime_s: number | null
|
|
active_ports: number | null
|
|
bandwidth_in_mbps: number | null
|
|
bandwidth_out_mbps: number | null
|
|
cpu_pct: number | null
|
|
mem_pct: number | null
|
|
temperature_c: number | null
|
|
packet_loss_pct: number | null
|
|
}
|
|
|
|
export const fetchNetworkStatus = (siteId: string) =>
|
|
apiFetch<NetworkSwitchStatus[]>(`/api/network/status?site_id=${siteId}`)
|
|
|
|
export type PduReading = {
|
|
rack_id: string
|
|
room_id: string
|
|
total_kw: number | null
|
|
phase_a_kw: number | null
|
|
phase_b_kw: number | null
|
|
phase_c_kw: number | null
|
|
phase_a_a: number | null
|
|
phase_b_a: number | null
|
|
phase_c_a: number | null
|
|
imbalance_pct: number | null
|
|
status: "ok" | "warning" | "critical"
|
|
}
|
|
|
|
export const fetchPduReadings = (siteId: string) =>
|
|
apiFetch<PduReading[]>(`/api/assets/pdus?site_id=${siteId}`)
|
|
|
|
// ── Maintenance Windows ───────────────────────────────────────────
|
|
|
|
export type MaintenanceWindow = {
|
|
id: string
|
|
site_id: string
|
|
title: string
|
|
target: string
|
|
target_label: string
|
|
start_dt: string
|
|
end_dt: string
|
|
suppress_alarms: boolean
|
|
notes: string
|
|
created_at: string
|
|
status: "active" | "scheduled" | "expired"
|
|
}
|
|
|
|
export const fetchMaintenanceWindows = (siteId: string) =>
|
|
apiFetch<MaintenanceWindow[]>(`/api/maintenance?site_id=${siteId}`)
|
|
|
|
export const fetchActiveMaintenanceWindows = (siteId: string) =>
|
|
apiFetch<MaintenanceWindow[]>(`/api/maintenance/active?site_id=${siteId}`)
|
|
|
|
export const createMaintenanceWindow = (body: {
|
|
site_id: string; title: string; target: string; target_label: string;
|
|
start_dt: string; end_dt: string; suppress_alarms: boolean; notes: string;
|
|
}) => apiFetch<MaintenanceWindow>("/api/maintenance", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })
|
|
|
|
export const deleteMaintenanceWindow = (id: string) =>
|
|
apiFetch<void>(`/api/maintenance/${id}`, { method: "DELETE" })
|
|
|
|
// ── Floor layout ──────────────────────────────────────────────────
|
|
|
|
export const fetchFloorLayout = (siteId: string) =>
|
|
apiFetch<Record<string, unknown>>(`/api/floor-layout?site_id=${siteId}`)
|
|
|
|
export const saveFloorLayout = (siteId: string, layout: Record<string, unknown>) =>
|
|
apiFetch<{ ok: boolean }>(`/api/floor-layout?site_id=${siteId}`, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(layout),
|
|
})
|
|
|
|
export type ParticleStatus = {
|
|
room_id: string
|
|
particles_0_5um: number | null
|
|
particles_5um: number | null
|
|
iso_class: number | null
|
|
}
|
|
|
|
export const fetchParticleStatus = (siteId: string) =>
|
|
apiFetch<ParticleStatus[]>(`/api/env/particles?site_id=${siteId}`)
|
|
|
|
// ── Settings: Sensors ─────────────────────────────────────────────────────────
|
|
|
|
export type DeviceType = 'ups' | 'generator' | 'crac' | 'chiller' | 'ats' | 'rack' | 'network_switch' | 'leak' | 'fire_zone' | 'custom'
|
|
export type Protocol = 'mqtt' | 'modbus_tcp' | 'modbus_rtu' | 'snmp' | 'bacnet' | 'http'
|
|
|
|
export type SensorDevice = {
|
|
id: number
|
|
site_id: string
|
|
device_id: string
|
|
name: string
|
|
device_type: DeviceType
|
|
room_id: string | null
|
|
rack_id: string | null
|
|
protocol: Protocol
|
|
protocol_config: Record<string, unknown>
|
|
enabled: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
recent_readings?: { sensor_type: string; value: number; unit: string; recorded_at: string }[]
|
|
}
|
|
|
|
export type SensorCreate = {
|
|
device_id: string
|
|
name: string
|
|
device_type: DeviceType
|
|
room_id?: string | null
|
|
rack_id?: string | null
|
|
protocol: Protocol
|
|
protocol_config: Record<string, unknown>
|
|
enabled: boolean
|
|
}
|
|
|
|
export type SensorUpdate = Partial<Omit<SensorCreate, 'device_id'>>
|
|
|
|
export const fetchSensors = (siteId: string, params?: { device_type?: string; room_id?: string; protocol?: string }) => {
|
|
const p: Record<string, string> = { site_id: siteId }
|
|
if (params?.device_type) p.device_type = params.device_type
|
|
if (params?.room_id) p.room_id = params.room_id
|
|
if (params?.protocol) p.protocol = params.protocol
|
|
return apiFetch<SensorDevice[]>(`/api/settings/sensors?${new URLSearchParams(p)}`)
|
|
}
|
|
|
|
export const fetchSensor = (id: number) =>
|
|
apiFetch<SensorDevice>(`/api/settings/sensors/${id}`)
|
|
|
|
export const createSensor = (siteId: string, body: SensorCreate) =>
|
|
apiFetch<SensorDevice>(`/api/settings/sensors?site_id=${siteId}`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
})
|
|
|
|
export const updateSensor = (id: number, body: SensorUpdate) =>
|
|
apiFetch<SensorDevice>(`/api/settings/sensors/${id}`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
})
|
|
|
|
export const deleteSensor = (id: number) =>
|
|
apiFetch<void>(`/api/settings/sensors/${id}`, { method: 'DELETE' })
|
|
|
|
// ── Settings: Thresholds ───────────────────────────────────────────────────────
|
|
|
|
export type AlarmThreshold = {
|
|
id: number
|
|
site_id: string
|
|
sensor_type: string
|
|
threshold_value: number
|
|
direction: 'above' | 'below'
|
|
severity: 'warning' | 'critical'
|
|
message_template: string
|
|
enabled: boolean
|
|
locked: boolean
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export type ThresholdUpdate = { threshold_value?: number; severity?: string; enabled?: boolean }
|
|
export type ThresholdCreate = { sensor_type: string; threshold_value: number; direction: string; severity: string; message_template: string }
|
|
|
|
export const fetchThresholds = (siteId: string) =>
|
|
apiFetch<AlarmThreshold[]>(`/api/settings/thresholds?site_id=${siteId}`)
|
|
|
|
export const updateThreshold = (id: number, body: ThresholdUpdate) =>
|
|
apiFetch<AlarmThreshold>(`/api/settings/thresholds/${id}`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
})
|
|
|
|
export const createThreshold = (siteId: string, body: ThresholdCreate) =>
|
|
apiFetch<AlarmThreshold>(`/api/settings/thresholds?site_id=${siteId}`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
})
|
|
|
|
export const deleteThreshold = (id: number) =>
|
|
apiFetch<void>(`/api/settings/thresholds/${id}`, { method: 'DELETE' })
|
|
|
|
export const resetThresholds = (siteId: string) =>
|
|
apiFetch<{ ok: boolean; count: number }>(`/api/settings/thresholds/reset?site_id=${siteId}`, { method: 'POST' })
|
|
|
|
// ── Settings: Site / Notifications / Integrations ─────────────────────────────
|
|
|
|
export type SiteSettings = { name: string; timezone: string; description: string }
|
|
export type NotificationSettings = { critical_alarms: boolean; warning_alarms: boolean; generator_events: boolean; maintenance_reminders: boolean; webhook_url: string; email_recipients: string }
|
|
export type IntegrationSettings = { mqtt_host: string; mqtt_port: number }
|
|
export type PagePrefs = { default_time_range_hours: number; refresh_interval_seconds: number }
|
|
|
|
const _settingsPut = <T>(path: string, value: Partial<T>) =>
|
|
apiFetch<T>(`/api/settings/${path}`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value }),
|
|
})
|
|
|
|
export const fetchSiteSettings = (siteId: string) => apiFetch<SiteSettings>(`/api/settings/site?site_id=${siteId}`)
|
|
export const updateSiteSettings = (siteId: string, v: Partial<SiteSettings>) => _settingsPut<SiteSettings>(`site?site_id=${siteId}`, v)
|
|
export const fetchNotifications = (siteId: string) => apiFetch<NotificationSettings>(`/api/settings/notifications?site_id=${siteId}`)
|
|
export const updateNotifications = (siteId: string, v: Partial<NotificationSettings>) => _settingsPut<NotificationSettings>(`notifications?site_id=${siteId}`, v)
|
|
export const fetchIntegrations = (siteId: string) => apiFetch<IntegrationSettings>(`/api/settings/integrations?site_id=${siteId}`)
|
|
export const updateIntegrations = (siteId: string, v: Partial<IntegrationSettings>) => _settingsPut<IntegrationSettings>(`integrations?site_id=${siteId}`, v)
|
|
export const fetchPagePrefs = (siteId: string) => apiFetch<PagePrefs>(`/api/settings/page-prefs?site_id=${siteId}`)
|
|
export const updatePagePrefs = (siteId: string, v: Partial<PagePrefs>) => _settingsPut<PagePrefs>(`page-prefs?site_id=${siteId}`, v)
|
|
|
|
// ── Scenarios (simulator control) ─────────────────────────────────
|
|
|
|
export type ScenarioInfo = {
|
|
name: string
|
|
label: string
|
|
description: string
|
|
duration: string
|
|
compound: boolean
|
|
default_target: string | null
|
|
targets: string[]
|
|
}
|
|
|
|
export const fetchScenarios = () =>
|
|
apiFetch<ScenarioInfo[]>("/api/scenarios")
|
|
|
|
export const triggerScenario = (scenario: string, target?: string) =>
|
|
apiFetch<{ ok: boolean }>("/api/scenarios/trigger", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ scenario, target: target ?? null }),
|
|
})
|