first commit
This commit is contained in:
commit
4b98219bf7
144 changed files with 31561 additions and 0 deletions
711
frontend/lib/api.ts
Normal file
711
frontend/lib/api.ts
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
// 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 }),
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue