From 92c212c9970b5ff4e5c0b91b1a110cc0240b5a7a Mon Sep 17 00:00:00 2001 From: rioahmadfadilahxiv Date: Tue, 4 Aug 2026 16:36:14 +0700 Subject: [PATCH] Initial commit: Rancang Bangun UPS DC Berbasis IoT (Web Dashboard & Firmware ESP-12F) --- .gitignore | 13 + README.md | 30 + .../LittleFSDemo/LittleFSDemo.ino | 45 + .../LittleFSDemo/data/config.json | 5 + .../LittleFSDemo/data/info.txt | 2 + .../LittleFSDemo/data/readme.md | 2 + firmware_iot_esp12f/battLib.h | 120 + firmware_iot_esp12f/bitmap.h | 277 + firmware_iot_esp12f/data/favicon.ico | Bin 0 -> 3606 bytes firmware_iot_esp12f/data/index.html | 829 ++ firmware_iot_esp12f/dev15.ino | 98 + firmware_iot_esp12f/mqtt.h | 212 + firmware_iot_esp12f/upsCfg.h | 130 + firmware_iot_esp12f/upsIO.h | 1338 ++++ firmware_iot_esp12f/upsOled.h | 226 + firmware_iot_esp12f/upsSvr.h | 537 ++ firmware_iot_esp12f/webUI.h | 832 ++ firmware_iot_esp12f/wifi.h | 106 + web_ups/.gitignore | 41 + web_ups/README.md | 37 + web_ups/eslint.config.mjs | 18 + web_ups/next.config.ts | 7 + web_ups/package-lock.json | 6926 +++++++++++++++++ web_ups/package.json | 29 + web_ups/public/file.svg | 1 + web_ups/public/globe.svg | 1 + web_ups/public/next.svg | 1 + web_ups/public/vercel.svg | 1 + web_ups/public/window.svg | 1 + web_ups/src/app/api/auth/login/route.ts | 39 + web_ups/src/app/api/auth/logout/route.ts | 7 + web_ups/src/app/api/control/route.ts | 48 + web_ups/src/app/favicon.ico | Bin 0 -> 23620 bytes web_ups/src/app/globals.css | 53 + web_ups/src/app/layout.tsx | 25 + web_ups/src/app/login/page.tsx | 5 + web_ups/src/app/page.module.css | 78 + web_ups/src/app/page.tsx | 314 + web_ups/src/components/EventLog.module.css | 89 + web_ups/src/components/EventLog.tsx | 40 + .../src/components/EventLogsPage.module.css | 245 + web_ups/src/components/EventLogsPage.tsx | 108 + web_ups/src/components/Header.module.css | 118 + web_ups/src/components/Header.tsx | 44 + web_ups/src/components/HistoryPage.module.css | 502 ++ web_ups/src/components/HistoryPage.tsx | 375 + web_ups/src/components/Login.module.css | 431 + web_ups/src/components/Login.tsx | 244 + .../src/components/SettingsPanel.module.css | 208 + web_ups/src/components/SettingsPanel.tsx | 221 + web_ups/src/components/Sidebar.module.css | 179 + web_ups/src/components/Sidebar.tsx | 103 + web_ups/src/components/StatCards.module.css | 137 + web_ups/src/components/StatCards.tsx | 132 + .../src/components/VoltageChart.module.css | 64 + web_ups/src/components/VoltageChart.tsx | 70 + web_ups/src/lib/auth.ts | 23 + web_ups/src/lib/db.ts | 189 + web_ups/src/lib/supabase.ts | 16 + web_ups/src/proxy.ts | 52 + web_ups/tsconfig.json | 42 + 61 files changed, 16066 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 firmware_iot_esp12f/LittleFSDemo/LittleFSDemo.ino create mode 100644 firmware_iot_esp12f/LittleFSDemo/data/config.json create mode 100644 firmware_iot_esp12f/LittleFSDemo/data/info.txt create mode 100644 firmware_iot_esp12f/LittleFSDemo/data/readme.md create mode 100644 firmware_iot_esp12f/battLib.h create mode 100644 firmware_iot_esp12f/bitmap.h create mode 100644 firmware_iot_esp12f/data/favicon.ico create mode 100644 firmware_iot_esp12f/data/index.html create mode 100644 firmware_iot_esp12f/dev15.ino create mode 100644 firmware_iot_esp12f/mqtt.h create mode 100644 firmware_iot_esp12f/upsCfg.h create mode 100644 firmware_iot_esp12f/upsIO.h create mode 100644 firmware_iot_esp12f/upsOled.h create mode 100644 firmware_iot_esp12f/upsSvr.h create mode 100644 firmware_iot_esp12f/webUI.h create mode 100644 firmware_iot_esp12f/wifi.h create mode 100644 web_ups/.gitignore create mode 100644 web_ups/README.md create mode 100644 web_ups/eslint.config.mjs create mode 100644 web_ups/next.config.ts create mode 100644 web_ups/package-lock.json create mode 100644 web_ups/package.json create mode 100644 web_ups/public/file.svg create mode 100644 web_ups/public/globe.svg create mode 100644 web_ups/public/next.svg create mode 100644 web_ups/public/vercel.svg create mode 100644 web_ups/public/window.svg create mode 100644 web_ups/src/app/api/auth/login/route.ts create mode 100644 web_ups/src/app/api/auth/logout/route.ts create mode 100644 web_ups/src/app/api/control/route.ts create mode 100644 web_ups/src/app/favicon.ico create mode 100644 web_ups/src/app/globals.css create mode 100644 web_ups/src/app/layout.tsx create mode 100644 web_ups/src/app/login/page.tsx create mode 100644 web_ups/src/app/page.module.css create mode 100644 web_ups/src/app/page.tsx create mode 100644 web_ups/src/components/EventLog.module.css create mode 100644 web_ups/src/components/EventLog.tsx create mode 100644 web_ups/src/components/EventLogsPage.module.css create mode 100644 web_ups/src/components/EventLogsPage.tsx create mode 100644 web_ups/src/components/Header.module.css create mode 100644 web_ups/src/components/Header.tsx create mode 100644 web_ups/src/components/HistoryPage.module.css create mode 100644 web_ups/src/components/HistoryPage.tsx create mode 100644 web_ups/src/components/Login.module.css create mode 100644 web_ups/src/components/Login.tsx create mode 100644 web_ups/src/components/SettingsPanel.module.css create mode 100644 web_ups/src/components/SettingsPanel.tsx create mode 100644 web_ups/src/components/Sidebar.module.css create mode 100644 web_ups/src/components/Sidebar.tsx create mode 100644 web_ups/src/components/StatCards.module.css create mode 100644 web_ups/src/components/StatCards.tsx create mode 100644 web_ups/src/components/VoltageChart.module.css create mode 100644 web_ups/src/components/VoltageChart.tsx create mode 100644 web_ups/src/lib/auth.ts create mode 100644 web_ups/src/lib/db.ts create mode 100644 web_ups/src/lib/supabase.ts create mode 100644 web_ups/src/proxy.ts create mode 100644 web_ups/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1896133 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Node & Next.js +node_modules/ +.next/ +.env.local +.env.*.local +*.log +npm-debug.log* + +# OS & build +.DS_Store +Thumbs.db +*.exe +*.o diff --git a/README.md b/README.md new file mode 100644 index 0000000..74cc42f --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# Rancang Bangun Perangkat Uninterruptible Power Supply (UPS) DC Berbasis IoT dengan Menggunakan ESP-12F + +**Repositori Tugas Akhir (TKK) — Politeknik Negeri Jember (2026)** +* **Oleh**: Rio Ahmad Fadilah (NIM: E32230469) +* **Program Studi**: Teknik Komputer - Jurusan Teknologi Informasi + +--- + +## Struktur Repositori (Monorepo) + +Repositori ini memuat 2 bagian utama dalam sistem Smart DC UPS: + +### 1. `web_ups/` — Website Dashboard (Next.js & Supabase) +Aplikasi antarmuka berbasis web untuk pemantauan parameter tegangan dan kontrol daya jarak jauh (*remote control*) secara *real-time* menggunakan koneksi WebSocket MQTT dan cloud database Supabase. + +### 2. `firmware_iot_esp12f/` — Firmware Mikrokontroler IoT (ESP-12F / Arduino C++) +Kode program mikrokontroler ESP-12F yang bertugas membaca sensor tegangan, mengendalikan sistem proteksi baterai (menggunakan P-Channel MOSFET SUD45P03 dan modul XL4015), serta berkomunikasi dua arah via protokol MQTT (`broker.emqx.io`). + +--- + +## Panduan Cepat Menjalankan Website Secara Lokal + +```bash +cd web_ups +npm install +npm run dev +``` +Buka browser di `http://localhost:3000`. Gunakan kredensial login resmi: +* **Username**: `admin` +* **Password**: `atmindatang` diff --git a/firmware_iot_esp12f/LittleFSDemo/LittleFSDemo.ino b/firmware_iot_esp12f/LittleFSDemo/LittleFSDemo.ino new file mode 100644 index 0000000..b1eeecf --- /dev/null +++ b/firmware_iot_esp12f/LittleFSDemo/LittleFSDemo.ino @@ -0,0 +1,45 @@ +#include +#include +#include + +void listFiles() { + Serial.println("\nListing files in LittleFS:"); + Dir dir = LittleFS.openDir("/"); + while (dir.next()) { + Serial.printf(" %s (%d bytes)\n", dir.fileName().c_str(), dir.fileSize()); + } +} + +void readFile(const char *path) { + File file = LittleFS.open(path, "r"); + if (!file) { + Serial.printf("Failed to open file: %s\n", path); + return; + } + Serial.printf("Contents of %s:\n", path); + while (file.available()) { + Serial.write(file.read()); + } + Serial.println(); + file.close(); +} + +void setup() { + Serial.begin(115200); + delay(500); + + if (!LittleFS.begin()) { + Serial.println("LittleFS mount failed! Did you upload data folder?"); + return; + } + + Serial.println("LittleFS mounted successfully."); + listFiles(); + + // Read demo files + readFile("/info.txt"); + readFile("/config.json"); +} + +void loop() { +} diff --git a/firmware_iot_esp12f/LittleFSDemo/data/config.json b/firmware_iot_esp12f/LittleFSDemo/data/config.json new file mode 100644 index 0000000..cbfd158 --- /dev/null +++ b/firmware_iot_esp12f/LittleFSDemo/data/config.json @@ -0,0 +1,5 @@ +{ + "wifi_ssid": "YourNetwork", + "wifi_pass": "YourPassword", + "mode": "demo" +} diff --git a/firmware_iot_esp12f/LittleFSDemo/data/info.txt b/firmware_iot_esp12f/LittleFSDemo/data/info.txt new file mode 100644 index 0000000..e414132 --- /dev/null +++ b/firmware_iot_esp12f/LittleFSDemo/data/info.txt @@ -0,0 +1,2 @@ +Wemos D1 Mini LittleFS Demo +Uploaded from Arduino IDE using LittleFS Data Upload plugin. diff --git a/firmware_iot_esp12f/LittleFSDemo/data/readme.md b/firmware_iot_esp12f/LittleFSDemo/data/readme.md new file mode 100644 index 0000000..1685bbe --- /dev/null +++ b/firmware_iot_esp12f/LittleFSDemo/data/readme.md @@ -0,0 +1,2 @@ +# LittleFS Demo +This is a sample file stored in internal flash using LittleFS. diff --git a/firmware_iot_esp12f/battLib.h b/firmware_iot_esp12f/battLib.h new file mode 100644 index 0000000..1e7eaa3 --- /dev/null +++ b/firmware_iot_esp12f/battLib.h @@ -0,0 +1,120 @@ +//LiFePO4 4S battery soc and level calculation library + +//todo: + //- battery level will forced after 60-seconds if not passing battPercentageLevelThreshold (optional) + +static uint8_t battPercentageLevelThreshold = 5; //threshold for switch discrete level 0-4 +static uint8_t battLastLevel = 0; +static uint8_t battLastPercentageForLevel = 0; + +//variables +static float battVoltageFullChargeMinimal = 14.4; //used for longevity charge +static float battVoltageFullChargeMaximal = 14.6; //3.60V - 3.65V * 4S = 14.4V - 14.6V (can be used for "topped-off" charge mode, ) +static float battVoltageFullRest = 13.6; //~3.40V - 3.50V * 4S = ~13.6V - 13.7V (can be used for "optimal" charge mode, preferred for longevity) +static float battVoltageUpperZone = 13.3; //Also can be used for "Storage/Long-term Maintenance" charge mode, or after long usage without load ~13.2V - 13.3V (50-60% state of charge) +static float battVoltageLowerZone = 12.9; +//float battVoltageHalf = 13.1; //~3.25V - 3.28V * 4S = ~13.0V - 13.1V, known as nomimal +static float battVoltageEmpty = 10.0; //~2.50V - 2.80V * 4S = ~10.0V - 11.2V +static float battVoltageCritical = 12.0; //Default ~10% => 3.0V * 4S = 12V +static float battVoltageForceShutdown = 11.2; //Default ~5% => 2.8V * 4S = 11.2V +static float battVoltageResumeChargingDefault = 13.4; //Default ~90% = ~3.35V *4S = 13.4V + +//error condition +static float battOverVoltage = 15.3; //3.75V - 3.80V * 4S = 15V - 15.3V +static float battVoltageDetected = 8; +static float battVoltageNotDetected = 7; + +static void resetBatteryLevel() { + battLastLevel = 0; + battLastPercentageForLevel = 0; +} + +static void setBatteryPercentage(uint8_t soc) { + if (soc <= 0) { + battLastLevel = 0; + battLastPercentageForLevel = 0; + return; + } else if (soc >= 100) { + battLastLevel = 4; + battLastPercentageForLevel = 100; + return; + } + + if (soc > battLastPercentageForLevel) { + //charging + if (soc - battLastPercentageForLevel > battPercentageLevelThreshold) { + battLastPercentageForLevel = soc; + if (battLastPercentageForLevel > 75) { + battLastLevel = 4; + } else if (battLastPercentageForLevel > 50) { + battLastLevel = 3; + } else if (battLastPercentageForLevel > 25) { + battLastLevel = 2; + } else if (battLastPercentageForLevel > 10) { + battLastLevel = 1; + } else { + battLastLevel = 0; + } + } + } else { + //depleted or equal + if (battLastPercentageForLevel - soc > battPercentageLevelThreshold) { + battLastPercentageForLevel = soc; + if (battLastPercentageForLevel < 10) { + battLastLevel = 0; + } else if (battLastPercentageForLevel < 25) { + battLastLevel = 1; + } else if (battLastPercentageForLevel < 50) { + battLastLevel = 2; + } else if (battLastPercentageForLevel < 75) { + battLastLevel = 3; + } else { + battLastLevel = 4; + } + } + } +} + +static uint8_t getBatteryPercentage(float voltage) { + float soc = 0; + if (voltage > battVoltageUpperZone) { + soc = 90.0 + (10.0 * ((voltage - battVoltageUpperZone) / (battVoltageFullRest - battVoltageUpperZone))); + if (soc > 100) soc = 100; + } else if (voltage > battVoltageLowerZone && voltage <= battVoltageUpperZone) { + soc = 20.0 + (70.0 * ((voltage - battVoltageLowerZone) / (battVoltageUpperZone - battVoltageLowerZone))); + } else if (voltage > battVoltageEmpty) { + soc = (20.0 * ((voltage - battVoltageEmpty) / (battVoltageLowerZone - battVoltageEmpty))); + } + setBatteryPercentage(soc); + return (uint8_t) soc; +} + +static uint8_t getRawBatteryLevelFromPercentage(uint8_t soc) { + if (soc <= 0) { + return 0; + } else if (soc >= 100) { + return 4; + } + + if (soc > 75) { + return 4; + } else if (soc > 50) { + return 3; + } else if (soc > 25) { + return 2; + } else if (soc > 5) { + return 1; + } else { + return 0; + } + return 0; +} + +static uint8_t getBatteryLevel() { + return battLastLevel; +} + +static uint8_t getBatteryLevel(float voltage) { + getBatteryPercentage(voltage); + return battLastLevel; +} diff --git a/firmware_iot_esp12f/bitmap.h b/firmware_iot_esp12f/bitmap.h new file mode 100644 index 0000000..e38bc76 --- /dev/null +++ b/firmware_iot_esp12f/bitmap.h @@ -0,0 +1,277 @@ +static const unsigned char PROGMEM splash_logo_bitmap[512] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xE0,0xFF,0xE7,0x01,0x80,0xEF,0x03,0xF0,0x39,0xE0,0xFF,0x07,0x00,0x00, + 0x00,0x00,0xF8,0xFF,0xEF,0x03,0x80,0xC7,0x07,0xF8,0x78,0xF8,0xFF,0x1F,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0xCF,0x03,0xC0,0x87,0x0F,0x7C,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0xC7,0x07,0xC0,0x83,0x1F,0x3E,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0x3E,0x00,0x80,0x07,0xE0,0x03,0x1F,0x1F,0x78,0x3E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x80,0x0F,0xF0,0x01,0xBE,0x0F,0x78,0x1E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x00,0x0F,0xF0,0x00,0xFC,0x07,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x07,0x1F,0xF8,0x00,0xF8,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x07,0x1E,0x78,0x00,0xF0,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0xFE,0xFF,0x07,0x3E,0x7C,0x00,0xF8,0x03,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x00,0x3C,0x3C,0x00,0xFC,0x07,0x78,0x1E,0x00,0x7C,0x00,0x00, + 0x00,0x00,0x1E,0x00,0x00,0x78,0x3E,0x00,0xBE,0x0F,0x78,0x1E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0x3E,0x00,0x00,0xF8,0x1E,0x00,0x1F,0x1F,0x78,0x3E,0x00,0x3C,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0x07,0xF0,0x1F,0x80,0x0F,0x3E,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0xFC,0xFF,0x07,0xF0,0x0F,0xC0,0x07,0x7C,0x78,0xFC,0xFF,0x3F,0x00,0x00, + 0x00,0x00,0xF8,0xFF,0x0F,0xE0,0x07,0xE0,0x03,0xF8,0x78,0xF8,0xFF,0x1F,0x00,0x00, + 0x00,0x00,0xF0,0xFF,0x07,0xC0,0x07,0xF0,0x01,0xF0,0x79,0xF0,0xFF,0x07,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xC0,0x9F,0x83,0x83,0xC1,0x3F,0xFF,0xC1,0x61,0xFE,0xFE,0x83,0x81,0xFC,0xF1,0x07, + 0xE0,0xDF,0xC3,0xC3,0xC3,0x7F,0xFF,0xE1,0x61,0xFF,0xFF,0x83,0xC1,0xFE,0xFB,0x07, + 0x60,0xC0,0xC7,0xC3,0xC7,0x60,0x18,0xE0,0x63,0x03,0x60,0x80,0xC1,0x0E,0x1B,0x00, + 0xE0,0xCF,0xC6,0x63,0xC6,0x60,0x18,0x60,0x67,0xFF,0x60,0x80,0xC1,0x0E,0xFB,0x03, + 0xE0,0xDF,0x6E,0x63,0xCC,0x7F,0x18,0x60,0x6E,0xFF,0x60,0x80,0xC1,0xFE,0xF3,0x0F, + 0x00,0xD8,0x6C,0xF3,0xCF,0x3F,0x18,0x60,0x7C,0x03,0x60,0x80,0xC1,0xFE,0x01,0x0C, + 0xE0,0xDF,0x3C,0xFB,0xDF,0x30,0x18,0x60,0x78,0xFF,0x60,0x80,0xFF,0x0E,0xF8,0x0F, + 0xE0,0xDF,0x38,0x1B,0xD8,0x60,0x18,0x60,0x70,0xFE,0x61,0x00,0xFF,0x0E,0xF8,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +static const unsigned char PROGMEM splash_logo_bitmap_inverted[512] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0x1F,0x00,0x18,0xFE,0x7F,0x10,0xFC,0x0F,0xC6,0x1F,0x00,0xF8,0xFF,0xFF, + 0xFF,0xFF,0x07,0x00,0x10,0xFC,0x7F,0x38,0xF8,0x07,0x87,0x07,0x00,0xE0,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0x30,0xFC,0x3F,0x78,0xF0,0x83,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0x38,0xF8,0x3F,0x7C,0xE0,0xC1,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0xC1,0xFF,0x7F,0xF8,0x1F,0xFC,0xE0,0xE0,0x87,0xC1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0x7F,0xF0,0x0F,0xFE,0x41,0xF0,0x87,0xE1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0xFF,0xF0,0x0F,0xFF,0x03,0xF8,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xF8,0xE0,0x07,0xFF,0x07,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xF8,0xE1,0x87,0xFF,0x0F,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0x01,0x00,0xF8,0xC1,0x83,0xFF,0x07,0xFC,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0xFF,0xC3,0xC3,0xFF,0x03,0xF8,0x87,0xE1,0xFF,0x83,0xFF,0xFF, + 0xFF,0xFF,0xE1,0xFF,0xFF,0x87,0xC1,0xFF,0x41,0xF0,0x87,0xE1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0xC1,0xFF,0xFF,0x07,0xE1,0xFF,0xE0,0xE0,0x87,0xC1,0xFF,0xC3,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0xF8,0x0F,0xE0,0x7F,0xF0,0xC1,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0x03,0x00,0xF8,0x0F,0xF0,0x3F,0xF8,0x83,0x87,0x03,0x00,0xC0,0xFF,0xFF, + 0xFF,0xFF,0x07,0x00,0xF0,0x1F,0xF8,0x1F,0xFC,0x07,0x87,0x07,0x00,0xE0,0xFF,0xFF, + 0xFF,0xFF,0x0F,0x00,0xF8,0x3F,0xF8,0x0F,0xFE,0x0F,0x86,0x0F,0x00,0xF8,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x3F,0x60,0x7C,0x7C,0x3E,0xC0,0x00,0x3E,0x9E,0x01,0x01,0x7C,0x7E,0x03,0x0E,0xF8, + 0x1F,0x20,0x3C,0x3C,0x3C,0x80,0x00,0x1E,0x9E,0x00,0x00,0x7C,0x3E,0x01,0x04,0xF8, + 0x9F,0x3F,0x38,0x3C,0x38,0x9F,0xE7,0x1F,0x9C,0xFC,0x9F,0x7F,0x3E,0xF1,0xE4,0xFF, + 0x1F,0x30,0x39,0x9C,0x39,0x9F,0xE7,0x9F,0x98,0x00,0x9F,0x7F,0x3E,0xF1,0x04,0xFC, + 0x1F,0x20,0x91,0x9C,0x33,0x80,0xE7,0x9F,0x91,0x00,0x9F,0x7F,0x3E,0x01,0x0C,0xF0, + 0xFF,0x27,0x93,0x0C,0x30,0xC0,0xE7,0x9F,0x83,0xFC,0x9F,0x7F,0x3E,0x01,0xFE,0xF3, + 0x1F,0x20,0xC3,0x04,0x20,0xCF,0xE7,0x9F,0x87,0x00,0x9F,0x7F,0x00,0xF1,0x07,0xF0, + 0x1F,0x20,0xC7,0xE4,0x27,0x9F,0xE7,0x9F,0x8F,0x01,0x9E,0xFF,0x00,0xF1,0x07,0xF8, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF +}; + +static const unsigned char PROGMEM active_data_2x4_in_bitmap[4] = { + 0x02, + 0x02, + 0x03, + 0x02 +}; + +static const unsigned char PROGMEM active_data_2x4_out_bitmap[4] = { + 0x01, + 0x03, + 0x01, + 0x01 +}; + +static const unsigned char PROGMEM power_bitmap[32] = { + 0x80,0x01, + 0x80,0x01, + 0x90,0x09, + 0xB8,0x1D, + 0x9C,0x39, + 0x8E,0x71, + 0x86,0x61, + 0x86,0x61, + 0x86,0x61, + 0x06,0x60, + 0x06,0x60, + 0x0E,0x70, + 0x1C,0x38, + 0x78,0x1E, + 0xF0,0x0F, + 0xE0,0x07 +}; + +static const unsigned char PROGMEM wifi2_lv1_bitmap[32] = { + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x80,0x01, + 0xE0,0x07, + 0x70,0x0E, + 0x00,0x00, + 0x80,0x01, + 0x80,0x01, + 0x00,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM wifi2_lv2_bitmap[32] = { + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0x00,0x00, + 0xC0,0x03, + 0xF0,0x0F, + 0x3C,0x3C, + 0x8C,0x31, + 0xE0,0x07, + 0x70,0x0E, + 0x00,0x00, + 0x80,0x01, + 0x80,0x01, + 0x00,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM wifi2_lv3_bitmap[32] = { + 0x00,0x00, + 0x00,0x00, + 0xF0,0x0F, + 0xFC,0x3F, + 0x1E,0x78, + 0xC7,0xE3, + 0xF0,0x0F, + 0x3C,0x3C, + 0x8C,0x31, + 0xE0,0x07, + 0x70,0x0E, + 0x00,0x00, + 0x80,0x01, + 0x80,0x01, + 0x00,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM bolt_16x16_bitmap[32] = { + 0x00,0x00, + 0x00,0x01, + 0x00,0x01, + 0x80,0x01, + 0xC0,0x01, + 0xC0,0x01, + 0xE0,0x1F, + 0xE0,0x0F, + 0xF0,0x0F, + 0xF8,0x07, + 0x80,0x03, + 0x80,0x03, + 0x80,0x01, + 0x80,0x01, + 0x80,0x00, + 0x00,0x00 +}; + +static const unsigned char PROGMEM charging_7x8_bitmap[8] = { + 0x3E, + 0x41, + 0x5D, + 0x45, + 0x45, + 0x5D, + 0x41, + 0x3E +}; + + +static const unsigned char PROGMEM charge_fast_7x8_bitmap[8] = { + 0x3E, + 0x7F, + 0x63, + 0x7B, + 0x7B, + 0x63, + 0x7F, + 0x3E +}; + +static const unsigned char PROGMEM warn_16x16_bitmap[32] = { + 0x00,0x00, + 0x80,0x01, + 0xC0,0x03, + 0xC0,0x07, + 0xE0,0x07, + 0x70,0x0E, + 0x70,0x0E, + 0x78,0x1E, + 0x78,0x1E, + 0x7C,0x3E, + 0xFC,0x3F, + 0x7E,0x7E, + 0x7E,0x7E, + 0xFF,0xFF, + 0xFF,0xFF, + 0xFF,0xFF +}; + +static const unsigned char PROGMEM error_16x16_bitmap[32] = { + 0xF0,0x0F, + 0xF8,0x1F, + 0xFC,0x3F, + 0x7E,0x7E, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0x7F,0xFE, + 0xFF,0xFF, + 0x7F,0xFE, + 0x7E,0x7E, + 0xFC,0x3F, + 0xF8,0x1F, + 0xF0,0x0F +}; + +static const unsigned char PROGMEM fatal_16x16_bitmap[32] = { + 0xF0,0x0F, + 0xF8,0x1F, + 0xFC,0x3F, + 0xFE,0x7F, + 0xDF,0xFB, + 0x8F,0xF1, + 0x1F,0xF8, + 0x3F,0xFC, + 0x3F,0xFC, + 0x1F,0xF8, + 0x8F,0xF1, + 0xDF,0xFB, + 0xFE,0x7F, + 0xFC,0x3F, + 0xF8,0x1F, + 0xF0,0x0F +}; + +static const unsigned char PROGMEM timer_16x16_bitmap[32] = { + 0x80,0x01, + 0x80,0x19, + 0x00,0x38, + 0x08,0x70, + 0x10,0x60, + 0x73,0xC0, + 0xE3,0xC0, + 0xC3,0xC1, + 0x83,0xC1, + 0x03,0xC0, + 0x03,0xC0, + 0x06,0x60, + 0x06,0x60, + 0x1C,0x38, + 0xF8,0x1F, + 0xE0,0x07 +}; diff --git a/firmware_iot_esp12f/data/favicon.ico b/firmware_iot_esp12f/data/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c1443b0071a9f858bb06281d3a280e0a29bcf60b GIT binary patch literal 3606 zcmbtXc}$aM82^x?a#-*fM}b0t3Y4oz5jjd}Ia(;C1uEr~T10LI5wEGB@rn`gz;oWy z>71I(WXa}Cwq?O(TbBK`EzAB{wzxmcrppc!oyl*{`*Mm!tA5EZ-}k+Jp5ODl&-Xq@ z1;I_2B?JcxbdMDFcniWpK@b80M&DaK1mOm)#nBpx2ucEzbK&wL88t!Wf-Dn}5b_9N z1Xr$lnKTrE3+91@U#R2h>G5GgY&1k(o}aitE))Ct{w0+JBP=8cKHjtch>QrkCyNMo ziaCD1_as4YJdOwrL1=K`W8&ZZ0vqwkaWSYcXgOrz60|sK(Are%6!Ed6vJI6cbhkFK z{qB2J{%UI}5)&38Uz0Uqi}70w`AAAhMRd9z(j4oPxpLL_WTThh>BQEI{>Q}0Fz-w{ zMCt`-TDFwiP{hd*n_CIXsUMNG;}#ZHZ-(jSf5_hRDMWKaZWI6PX*Oo$w_1uJS41Hv zOU1d{Rx}_cy%3cb?{d!E{a?Z}APB|p-XDD*`U-Bd+;_Ul&%ObR(Tc_@tZsK8U#o`U z&8slI|CsOyx?|r#(AOZ|zK8R(eS~CM?h`*{%EUFJ|JvLo@HR^jo+!nJwVhBZQ?T^R zPn_R7LIJ_r4&m@e5EVC~uQC|{u6(>J`eKfNVGN8A(8A1@7wkazdm1sb!$3MQ)NMMf!0-iets-P z8qUkUtujf0lZSRgPaNfD6V(L`26t_tI#Gia_F6a^tD)CvUWvcOQHuAddk^C6q2t8_in?ngS(NJoB3M&m1YB~ z%1dx`|Mq9%f92d!s3@ObHa_8!AgGe!5k@_gBjp7d)!CkzOhF^K5yJ_a|sPX$|Nzxxx$<)43Na~O?g zd;)V}4hx(u^4dz`ib)SApWNKsR3b00Ewmz@T%CL(-2<^vk?`~J9-;Z?lxzy5VUkn+ zV!vckh$X){A1G2lcxJP&vboue$?u8IA|_081yI|{+9RM zc^M`Yp8Eww7w*E#Kd^HqecoFOW=>a4NTtfTsS$z^)H=1j1s*Lh1y(f zpuQCo6?yY1#jzKruU@P~MVTJOo}sWwlnct!#v?qU?qG%jn$H+sx={bzu9zR`jlF<@WnrTW;>T#t9o z9G>vMchyQdhtlf}0b#kNw7EoZx?l^jJ3l literal 0 HcmV?d00001 diff --git a/firmware_iot_esp12f/data/index.html b/firmware_iot_esp12f/data/index.html new file mode 100644 index 0000000..74d9288 --- /dev/null +++ b/firmware_iot_esp12f/data/index.html @@ -0,0 +1,829 @@ + + + + + + UPS Dashboard + + + + + +
+ +
+ +

+ + + + +

+ +
+

Power Control:

+ +
+ + + +
+ + +
+ + +
+ +
+ + + +
+ +
+
+
+ + +
+ +
+ + + +
+ + + +
+
+ +
+

Charge Control:

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+

Monitor:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + \ No newline at end of file diff --git a/firmware_iot_esp12f/dev15.ino b/firmware_iot_esp12f/dev15.ino new file mode 100644 index 0000000..f64c650 --- /dev/null +++ b/firmware_iot_esp12f/dev15.ino @@ -0,0 +1,98 @@ +// the setup function runs once when you press reset or power the board + +#include "upsIO.h" +#include "upsSvr.h" +#include "mqtt.h" + +bool printDebug = true; + +bool inOk = false; +int16_t adc = 0; +float voltage = 0; +uint32_t infoTick = 0; + +void setup() { + // initialize digital pin LED_BUILTIN as an output. + Serial.begin(115200); + Serial.println(); + //ESP.wdtDisable(); + + //initUpsIo(true); + initUpsIo(); + //initUpsIo(false, true); + + upsServerInit(); + mqttInit(); + + //CharliplexDemoRoutine(); +} + +// the loop function runs over and over again forever +void loop() { + if (printDebug && (millis()-infoTick>=500)){ + infoTick = millis(); + adc = analogRead(A0);//ioAdc;//analogRead(A0);//ioAdc;// + //voltage = (16.0*((float)adc / 1024)) * 0.983; //use 1.5 MOhm series resistor + voltage = (16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor + Serial.println("VBatt: " + (String(voltage, 2)) + " V"); + + //adc = ioAdcFiltered; + voltage = batteryVoltageUnfiltered;//(16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor + Serial.println("VBatt (Sub-Min): " + (String(voltage, 2)) + " V"); + + //adc = adcValueMin; + voltage = batteryVoltage;//(16.0*((float)adc / 1024)) * 1.0; //use 1.5 MOhm series resistor + Serial.print("VBatt (Min): " + (String(voltage, 2)) + " V"); + Serial.print(" (" + (String(batteryPercentage)) + "%)"); + Serial.println(", LV: " + (String(batteryLevel))); + + + Serial.println("Charge Error: " + (String(chargeError))); + Serial.println("Charge Switch: " + (String(isChargeActive))); + Serial.println("Input Under Voltage: " + (String(inputUnderVoltage))); + Serial.println("Input Voltage Exists: " + (String(inputVoltageKeepup))); + + //inOk = digitalRead(pinInputSense); + //Serial.println("Vin: " + (String)(inOk?"True":"False")); + + //buttonState = !digitalRead(pinInputButton); //inverted + Serial.println("Button: " + (String)(buttonState?"True":"False")); + Serial.println("Button (short): " + (String)(buttonShortPress?"True":"False")); + Serial.println("Button (long): " + (String)(buttonLongPress?"True":"False")); + + Serial.println("Power OUT: " + (String)(powerOutputActive?"True":"False")); + //digitalWrite(pinLed, HIGH); // turn the LED on (HIGH is the voltage level) + //digitalWrite(pinPowerOut, HIGH); + + Serial.println("KEEP POWER: " + (String)(keepPowerState?"True":"False")); + + Serial.println("Display: " + (String)(displayActive?"True":"False")); + + Serial.println("RSSI: " + (String)(rssi)); + + Serial.println("Charge Allowed: " + (String)(chargeAllowed?"True":"False")); + + //for persistent shutdown demo: ============================================================== +// if (persistentShutdown){ +// Serial.println("Shutdown in: " + (String)(getPersistentShutdownTick()/1000000)); +// }else{ +// if (restartAutoPowerOn()) Serial.println("Automatic ON restarted"); +// setPersistentShutdown(10000000UL); +// } + //============================================================================================ + } + + upsServerRoutine(); + mqttRoutine(); + + upsRoutineInterrupt(); + + //delay(2000); // wait for a second + //digitalWrite(pinLed, LOW); // turn the LED off by making the voltage LOW + //digitalWrite(pinPowerOut, LOW); + + + //CharlieplexDemoRoutine(); + //UpsIoRoutine(); + +} diff --git a/firmware_iot_esp12f/mqtt.h b/firmware_iot_esp12f/mqtt.h new file mode 100644 index 0000000..7bc99e2 --- /dev/null +++ b/firmware_iot_esp12f/mqtt.h @@ -0,0 +1,212 @@ +#ifndef MQTT_H +#define MQTT_H + +#include +#include +#include + +const char* mqtt_server = "broker.emqx.io"; +const int mqtt_port = 1883; + +// Topics +const char* mqtt_topic_state = "ups/dev15/state"; +const char* mqtt_topic_cmd = "ups/dev15/cmd"; + +WiFiClient espClient; +PubSubClient mqttClient(espClient); + +unsigned long lastMqttReconnectAttempt = 0; +unsigned long lastMqttPublish = 0; +const unsigned long mqttPublishInterval = 2000; // Publish state every 2 seconds + +// Forward declaration +void mqttPublishState(); + +void mqttCallback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message arrived on topic: "); + Serial.print(topic); + Serial.print(". Message: "); + String messageTemp; + + for (int i = 0; i < length; i++) { + Serial.print((char)payload[i]); + messageTemp += (char)payload[i]; + } + Serial.println(); + + // Parse command + if (String(topic) == mqtt_topic_cmd) { + DynamicJsonDocument doc(256); + DeserializationError error = deserializeJson(doc, payload, length); + if (!error) { + String cmd = doc["cmd"].as(); + String v = doc["val"].as(); + bool updated = false; + + if (cmd == "on") { + bool tgtState = v != "0"; + if (tgtState != powerOutputActive) { + // This function is defined in upsSvr.h / main logic + // Note: mqtt.h is included after or before? Need to ensure it can call setOutputPowerState + // Actually, setOutputPowerState is declared in upsCfg.h maybe? + // If compilation fails, we can just set the flag. Wait, let's use the function since it's global. + setOutputPowerState(tgtState); + updated = true; + } + } else if (cmd == "auto") { + uint8_t tmpAutoOnMode = v.toInt(); + if ((tmpAutoOnMode != cfgAutoOn) && (tmpAutoOnMode <= 2)) { + cfgAutoOn = tmpAutoOnMode; + configChanged = true; + updated = true; + } + } else if (cmd == "shutdown") { + uint16_t tSec = v.toInt(); + if (tSec > 999) tSec = 999; + uint32_t nanoT = ((uint32_t)tSec) * 1000000UL; + if (nanoT < 999999) nanoT = 999999; + bool ok = setPersistentShutdown(nanoT); + if (cfgShutdownInSeconds != tSec && ok) { + cfgShutdownInSeconds = tSec; + configChanged = true; + updated = true; + } + } else if (cmd == "shutlv") { + int16_t lv = v.toInt(); + if (lv >= 0 && lv <= 5) { + if (lv != cfgShutdownSuggestion) { + cfgShutdownSuggestion = lv; + configChanged = true; + updated = true; + } + } + } else if (cmd == "cm") { + int16_t i = v.toInt(); + if (i >= 0 && i <= 9) { + cfgChargeMode = i; + configChanged = true; + updated = true; + } + } else if (cmd == "coil") { + int16_t i = v.toInt(); + if (i >= 0 && i <= 5) { + cfgInputLostTriggerChargeMode = i; + configChanged = true; + updated = true; + } + } else if (cmd == "covp") { + int16_t i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeOvervoltageProtection) { + cfgChargeOvervoltageProtection = i; + configChanged = true; + updated = true; + } + } else if (cmd == "rofc") { + int16_t i = v.toInt(); + if (i > 0 && needFullChargeOnce) { + resetOnGoingFullChargeAfterLineLost(); + updated = true; + } + } else if (cmd == "car") { + int16_t i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeAutomaticReducer) { + cfgChargeAutomaticReducer = i; + configChanged = true; + updated = true; + } + } + + if (updated) { + mqttPublishState(); + } + } + } +} + +boolean mqttReconnect() { + String clientId = "UPS_DEV15_" + String(random(0xffff), HEX); + Serial.print("Attempting MQTT connection as "); + Serial.println(clientId); + if (mqttClient.connect(clientId.c_str())) { + Serial.println("MQTT Connected"); + // Once connected, publish an announcement... + mqttClient.publish("ups/dev15/status", "online"); + // ... and resubscribe + mqttClient.subscribe(mqtt_topic_cmd); + } else { + Serial.print("failed, rc="); + Serial.print(mqttClient.state()); + Serial.println(" try again in 5 seconds"); + } + return mqttClient.connected(); +} + +void mqttInit() { + mqttClient.setServer(mqtt_server, mqtt_port); + mqttClient.setBufferSize(1024); // Increase buffer size for large JSON payloads + mqttClient.setCallback(mqttCallback); +} + +void mqttPublishState() { + DynamicJsonDocument jsonDoc(1024); + String jsonResponse; + + uint8_t lineActiveState = isUpsPoweredFromBattery() ? 2 : isInputLinePowered() ? 1 : isUpsOn() ? 2 : 0; + + jsonDoc["success"] = 2; + jsonDoc["on"] = powerOutputActive ? 1 : 0; + jsonDoc["state"] = isUpsOn() ? 1 : 0; + jsonDoc["v"] = String(batteryVoltage, 2); + jsonDoc["batt"] = batteryLevel; + jsonDoc["battP"] = batteryPercentage; + jsonDoc["line"] = lineActiveState; + jsonDoc["uptime"] = millis() / 1000; + + jsonDoc["auto"] = cfgAutoOn; + jsonDoc["battBlink"] = isUpsBatteryIndicatorChargingOrBlink() ? 1 : 0; + jsonDoc["battCritical"] = isUpsBatteryCritical() ? 1 : 0; + jsonDoc["chargeLv"] = getRawBatteryLevelFromPercentage(batteryPercentage); + jsonDoc["battMax"] = 4; + jsonDoc["timedShutdown"] = (persistentShutdown && powerOutputActive) ? 1 : 0; + jsonDoc["shutdownRemain"] = getPersistentShutdownTick() / 1000000; + jsonDoc["shutdownSuggestMode"] = cfgShutdownSuggestion; + jsonDoc["shutdownSuggest"] = getShutdownSuggestionFlag() ? 1 : 0; + jsonDoc["shutLastTimer"] = cfgShutdownInSeconds; + + jsonDoc["chgError"] = chargeError; + jsonDoc["charging"] = getChargingState(); + jsonDoc["chgMode"] = cfgChargeMode; + jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; + jsonDoc["chgReducer"] = cfgChargeAutomaticReducer; + jsonDoc["chgFullTrig"] = cfgInputLostTriggerChargeMode; + jsonDoc["chgOFC"] = needFullChargeOnce ? 1 : 0; + + serializeJson(jsonDoc, jsonResponse); + mqttClient.publish(mqtt_topic_state, jsonResponse.c_str()); +} + +void mqttRoutine() { + if (!mqttClient.connected()) { + long now = millis(); + if (now - lastMqttReconnectAttempt > 5000) { + lastMqttReconnectAttempt = now; + // Attempt to reconnect + if (mqttReconnect()) { + lastMqttReconnectAttempt = 0; + } + } + } else { + mqttClient.loop(); + + // Publish state periodically + long now = millis(); + if (now - lastMqttPublish > mqttPublishInterval) { + lastMqttPublish = now; + mqttPublishState(); + } + } +} + +#endif diff --git a/firmware_iot_esp12f/upsCfg.h b/firmware_iot_esp12f/upsCfg.h new file mode 100644 index 0000000..02edeb2 --- /dev/null +++ b/firmware_iot_esp12f/upsCfg.h @@ -0,0 +1,130 @@ +#include +#include + +//shutdown suggestion level +#define SHUTDOWN_SUGGESTION_DISABLED 0 +#define SHUTDOWN_SUGGESTION_ON_BATT 1 +#define SHUTDOWN_SUGGESTION_ON_BATT_75 2 +#define SHUTDOWN_SUGGESTION_ON_BATT_50 3 +#define SHUTDOWN_SUGGESTION_ON_BATT_25 4 +#define SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL 5 +static volatile uint8_t cfgShutdownSuggestion = SHUTDOWN_SUGGESTION_ON_BATT_25; + +#define AUTO_POWER_OFF 0 +#define AUTO_POWER_ON 1 +#define AUTO_POWER_PERSIST 2 +static volatile uint8_t cfgAutoOn = AUTO_POWER_ON;//AUTO_POWER_PERSIST;//AUTO_POWER_ON; + +#define SHUTDOWN_DURATION_DEFAULT 120 +static volatile uint16_t cfgShutdownInSeconds = SHUTDOWN_DURATION_DEFAULT;//default time in seconds for shutdown + +//full charge trigger: +#define INPUT_LOST_TRIGGER_CHARGE_NONE 0 +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_ALWAYS 1 //trigger full charge if below 100 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_95 2 //trigger full charge if below 95 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90 3 //trigger full charge if below 90 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_85 4 //trigger full charge if below 85 percent +#define INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80 5 //trigger full charge if below 80 percent +static volatile uint8_t cfgInputLostTriggerChargeMode = INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90; + +#define CHARGE_MODE_ALWAYS 0 +#define CHARGE_MODE_RANGE_90_100 1 +#define CHARGE_MODE_RANGE_85_100 2 +#define CHARGE_MODE_RANGE_80_100 3 +#define CHARGE_MODE_RANGE_90_95 4 +#define CHARGE_MODE_RANGE_85_95 5 +#define CHARGE_MODE_RANGE_80_95 6 +#define CHARGE_MODE_RANGE_85_90 7 +#define CHARGE_MODE_RANGE_80_90 8 +#define CHARGE_MODE_RANGE_80_85 9 +static volatile uint8_t cfgChargeMode = CHARGE_MODE_RANGE_85_90; + +static volatile uint8_t cfgChargeOvervoltageProtection = 1; +static volatile uint8_t cfgChargeAutomaticReducer = 1; + +bool spiffsOk = false; // flag tetap bernama spiffsOk agar kompatibel dengan kode lain + +bool initConfigs(){ + // Setup LittleFS + spiffsOk = LittleFS.begin(); + if (!spiffsOk){ + Serial.println("LittleFS ERROR!"); + }else{ + // DEBUG: List all files in LittleFS + Serial.println("=== LittleFS Files ==="); + Dir dir = LittleFS.openDir("/"); + int fileCount = 0; + while (dir.next()) { + fileCount++; + Serial.print(" FILE: "); + Serial.print(dir.fileName()); + Serial.print(" ["); + Serial.print(dir.fileSize()); + Serial.println(" bytes]"); + } + if (fileCount == 0) Serial.println(" (no files found)"); + Serial.println("=== End LittleFS ==="); + } + return spiffsOk; +} + +void loadCfg(){ + if (!spiffsOk) return; + // Open file for reading + File file = LittleFS.open("/cfg.json", "r"); + if (!file) { + Serial.println("Failed to open config"); + return; + } + + // Parse file contents as JSON + DynamicJsonDocument jsonDoc(255); + DeserializationError error = deserializeJson(jsonDoc, file); + file.close(); + + if (error) { + Serial.println("Failed to parse file settings"); + return; + } + + uint8_t tmpAutoOnMode = jsonDoc["auto"].as(); + cfgAutoOn = tmpAutoOnMode; + if (cfgAutoOn>AUTO_POWER_PERSIST) cfgAutoOn = AUTO_POWER_PERSIST; + + uint8_t v = jsonDoc["shut"].as(); + if (v>SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL) v = SHUTDOWN_SUGGESTION_ON_BATT_25; + cfgShutdownSuggestion = v; + + v = jsonDoc["chgMode"].as(); + if (v>CHARGE_MODE_RANGE_80_85) v = CHARGE_MODE_RANGE_90_100; + cfgChargeMode = v; + + v = jsonDoc["chgILTC"].as(); + if (v>INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80) v = INPUT_LOST_TRIGGER_CHARGE_NONE; + cfgInputLostTriggerChargeMode = v; + + v = jsonDoc["chgOVP"].as(); + if (v>1) v = 1; + cfgChargeOvervoltageProtection = v; + + v = jsonDoc["chgAR"].as(); + if (v>1) v = 1; + cfgChargeAutomaticReducer = v; +} + +void saveCfg(){ + if (!spiffsOk) return; + DynamicJsonDocument jsonDoc(255); + jsonDoc["auto"] = cfgAutoOn; + jsonDoc["shut"] = cfgShutdownSuggestion; + jsonDoc["chgMode"] = cfgChargeMode; + jsonDoc["chgILTC"] = cfgInputLostTriggerChargeMode; + jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; + jsonDoc["chgAR"] = cfgChargeAutomaticReducer; + File file = LittleFS.open("/cfg.json", "w"); + if (file) { + uint32_t sz = serializeJson(jsonDoc, file); + file.close(); + Serial.println("Configuration updated!"); + } +} diff --git a/firmware_iot_esp12f/upsIO.h b/firmware_iot_esp12f/upsIO.h new file mode 100644 index 0000000..e7615d5 --- /dev/null +++ b/firmware_iot_esp12f/upsIO.h @@ -0,0 +1,1338 @@ +//UPS CORE ========================================================================================================= +//handle: - Button, state (short hold, long hold) +// - Keep Power State +// - ADC (voltage, battery level) +// - Input OK Threshlod Sense +// - State for: power bad (blinks -- low_output <=> as_input), power ok (led on), charge disable (led off) + +//todo: + // - misc./cosmetic logic: when input line lost and charging error happens (ERROR NO BATTERY) => stop charge then reset error mode state, since it may switch to ERROR SHORTED. Can be ignored since will not happen in real condition, only happened when use voltage source from programmer. (OK) + // - charging state bugs: after power line lost, battery bar blinking and charge control pinout state is LOW, while webserver just normal. + // - charge error no longer blinking the LED in charge control pinout, since OLED are now used (OK) + // - stop charge action at least 2-3 seconds at startup to detect battery (OK) + // - Charge Error no longer blink the charigin led indicator, since now using OLED (OK) + // - Using BMS may cause capacitive reading, thus battery detection innaccurate at startup, need retweak (OK, solved by charge delay at startup) + // - directive to invert input under voltage detection should delayed 1 seconds after keep missing (since switching can cause voltage drops/spikes) - OK + // - known bugs: when no input line and power going off, charging indicator and charging control indicator may still blink or active rather than turned off (OK) + +//known schematic weakness: + // - Positive side control for output power control caused brief activation states when line powered for first time (seems caused by MOSFET effect when difference voltage spiked, since keep powering esp from programmer while trigger input line give same effect). + //Workaround: Use XL4005 EN pinout control connected to GPIO5 with diode (1N4148) and resistor if necessary + +#include "upsOled.h" +#include "battLib.h" + +#ifndef UPSIO_H + #define UPSIO_H +#endif + +#define HOLD_POWER_TICK 4000000UL ///minimum power hold since startup in nanoseconds + +//pinouts: +#define KEEP_POWER_PIN 15 //keep power pinout +//minimum voltage sense pinout +#define MINIMUM_INPUT_PIN 4 //Vin 16V sense (15.6V with 5K6 resistor) +//button pinout +#define BUTTON_INPUT_PIN 16 //button sense +//charge stop or limiter pinout +#define CHARGE_STOP_PIN 2 //same as LED_BUILTIN +//power output activation pinout +#define POWER_OUTPUT_PIN 5 //power out activation + +static uint32_t tickStep = 1; +static uint32_t tickSinceStarted = 0; +static uint32_t tickKeepPower = 0; + +static bool keepPower = false; +static bool keepPowerState = false; + +//for timer routines: +static volatile uint32_t ioAdcTick = 0; +static volatile uint8_t lowRateTick = 0; +static volatile bool lowRateRoutine = false; +#define ADC_CAP_BUFFER_NUM 32 +static volatile uint16_t adcCap[ADC_CAP_BUFFER_NUM]; +static volatile uint8_t adcCapCount = 0; +static volatile uint8_t adcIdx = 0; +static volatile uint16_t ioAdc = 0; +static volatile uint16_t ioAdcPrev = 0; +static volatile uint16_t ioAdcFiltered = 0; +static volatile uint16_t adcValueMin = 0; +//temps +static volatile uint8_t ioLoop = 0; +static volatile uint16_t adcTmp = 0; +static volatile uint8_t adcIdx2 = 0; +static volatile uint8_t adcSubTick = 0; +static volatile uint8_t adcSkipCounter = 0; + +//for battery level calculation +static volatile float adcVoltageBase = 0.0; //base voltage (for addition) +static volatile float adcMaxVoltageCapability = 16.0; //max voltage of ADC can read +static volatile float batteryVoltageUnfiltered = 0; +static volatile float batteryVoltage = 0; +static volatile float batteryLevel = 0; +static volatile uint8_t batteryPercentage = 0; +static volatile float batteryVoltageEnableOn = battVoltageCritical; + +//charge stop mode +#define CHARGE_ACTIVE_LIGHT_OFF 0 //pinout as input (may have effect charge voltage slightly below set since red led is dimmed) +#define CHARGE_ACTIVE_LIGHT_ON 1 //pinout as output, low state +#define CHARGE_STOP_LIGHT_OFF 2 //pinout as output, high state + +static volatile uint8_t chargeMode = CHARGE_ACTIVE_LIGHT_OFF; +static volatile bool isChargeActive = false; + +#define CHARGE_ERROR_NONE 0 +#define CHARGE_ERROR_NO_BATTERY 1 //also partial cell shorted, or battery not detected +#define CHARGE_ERROR_SHORTED 2 //charger output or battery shorted +#define CHARGE_ERROR_OVERVOLTAGE 4 //or also charger error / cannot be stopped +static volatile uint8_t chargeError = CHARGE_ERROR_NONE; +static volatile uint8_t chargeErrorPrev = CHARGE_ERROR_NONE; +static volatile uint8_t chargeManagementMode = 0; +static volatile bool chargeVoltageOK = false;//used for charge logic when charge voltage sense is enough + +static volatile bool powerOutputActive = false; +static volatile bool inputUnderVoltage = false; //input under voltage, or there is no input voltage at all stay for few seconds +static volatile bool inputVoltageKeepup = false;//input voltage ok, status stay for inputVoltageKeepupDuration in nanoseconds +static volatile uint32_t inputVoltageKeepupDuration = 500000UL; +static volatile uint32_t underVoltageTick = 0; +static volatile uint32_t inputVoltageKeepupTick = 0; +static volatile bool underVoltageIntterupted = false; +static volatile bool inputVoltageOK = false;//used for charge logic when input voltage sense is high +static volatile bool inputVoltageOKPrev = false; +static volatile bool startupReady = false; //delay 1 seconds from startup for +static volatile bool inputVoltageReady = false; //delay 1 seconds when input voltage resume +static volatile uint32_t inputVoltageResumeTick = 0; + +//for battery state check routine +#define SHORT_VOLTAGE 2.5 +static volatile uint32_t batteryCheckTick = 0; +static volatile uint32_t batteryCheckSkipTick = 0; +static volatile uint32_t chargeBlinkTick = 0; +static volatile uint32_t chargeStartedTick = 0; +static volatile bool blinkChargeError = false; + +//for input button pattern +static volatile bool buttonIgnore = false; //ignore button flag +static volatile bool buttonState = false; +static volatile bool buttonStatePrev = false; +static volatile bool buttonShortPress = false; //1 sec to power ON or OFF +static volatile bool buttonLongPress = false; //8 sec while powered to reset configuration (wifi setup) +static volatile bool buttonShortPressIgnore = false; +static volatile bool buttonLongPressIgnore = false; +static volatile uint32_t buttonTick = 0; +static volatile uint32_t buttonTickIgnore = 0; + +//for indicators +static volatile uint8_t flashAllBatteryLevel = 0; +static volatile bool indicatorFastBlink = false; //fast shortcut for fast blink (each 100ms) +static volatile bool indicatorMediumBlink = false; +static volatile bool indicatorSlowBlink = false; +static volatile uint32_t indicatorBlinkTick = 0; +static volatile uint8_t indicatorBlinkIndex = 0; //cycled per 10 count for 1 sec +static volatile uint32_t indicatorBlinkTickB = 0; //for fast blink +//wifi indicator +#define NET_STATE_OFF 0 +#define NET_STATE_CONNECTING 1 +#define NET_STATE_CONNECTED 2 +static volatile uint8_t indicatorNetworkState = 0; +static volatile uint8_t indicatorNetworkSignalLevel = 0; +static volatile uint8_t indicatorNetworkBlinkerTX = 0; +static volatile uint8_t indicatorNetworkBlinkerRX = 0; +static volatile bool displayNetworkFlipFlag = false; + +//battery indicator +static volatile uint8_t battIdx = 0; + +//charging mode: +//#define CHARGE_MODE_DEFAULT 0 //charges to full then stop, resumed when capacity lower than 85% +//#define CHARGE_MODE_ALWAYS 1 //keep battery charging all the time +//#define CHARGE_MODE_TOPPED 2 //charges to full then stop, resumed when voltage lower than full rest +//#define CHARGE_MODE_TOPPED_CUSTOM 3 //charges to full then stop, resumed when capacity lower than n-percentage +//#define CHARGE_MODE_RANGED 4 //charges to a-percentage, resumed when capacity lower than b-percentage +static volatile bool chargeAfterInputLost = false;//force charge resume activation when input is resumed +static volatile bool fullChargeAfterInputLost = true; //force full charge after input lost (chargeAfterInputLost should be set to true) +static volatile uint8_t triggerFullChargeAfterInputLostAtPercentage = 90;//capacity left before full charge triggered (fullChargeAfterInputLost should be true) +static volatile bool chargeToFull = true; //will charge to full before stop, if false => wull use chargePercentageForStop +static volatile uint8_t chargePercentageForStop = 100; +static volatile uint8_t chargePercentageForResume = 85; +static volatile bool chargeAlwaysActive = false; //use this to charge continuosly +static volatile bool chargeOvervoltageProtection = true; //activate over voltage protection +static volatile bool chargeReduceAtBatteryFull = true; //try to reduce charge voltage by hardware when reach minimum fullest +static volatile bool chargeInputLost = true; //charge lost flags +static volatile uint32_t chargeWaitBeforeStop = 120000000UL; //in nanoseconds (120000000 = 2 minutes) +static volatile uint32_t chargeWaitStopTick = 0; +static volatile bool needFullChargeOnce = false; + +//critical shutdown +static volatile uint32_t criticalDelayShutdown = 120000000UL; //in nanoseconds (120000000 = 2 minutes) +static volatile uint32_t persistentDelayShutdown = 120000000UL; //in nanoseconds (120000000 = 2 minutes) +static volatile uint32_t criticalShutdownTick = 0; +static volatile uint32_t persistentShutdownTick = 0; +static volatile bool persistentShutdown = false; +static volatile bool criticalShutdown = false; + +//automatic power resume +static volatile bool autoPowerOn = false; //power on when input resumed and in stable powered? +static volatile bool forceAutoPowerAtBatteryError = true; //force power on, even no battery exist or shorted (charge system error) +static volatile uint32_t autoPowerOnDelay = 4000000UL; //default is 4 seconds +static volatile uint32_t autoPowerOnTick = 0; +static volatile bool ongoingToPowerOn = false; //var flag +static volatile bool powerEverLoss = true; + +//static volatile bool interruptEnable = false; + +//when not using interrupt (that caused conflict with asyncwerbserver) +static volatile uint32_t currentNs = 0; +static volatile uint32_t lastNs = 0; + + +//static volatile bool batterySimpleIndicator = false; +static volatile bool batteryBlinkingIndicatorState = false; //blinking shortcut (either on charge active or critical) +static volatile bool batteryLastStateCritical = false; + +static volatile uint32_t displayRefreshDelay = 100000; //10 fps +static volatile uint32_t displayTick = 0; + +static volatile bool onGoingDisplayOff = false; +static volatile uint32_t turnOffDisplayAfter = 0; +static volatile uint32_t turnOffDisplayTick = 0; +static volatile bool displayActive = false; + +static volatile bool displayFlipFlag = false; + +static volatile bool splashEverDisplayed = false; +static volatile bool displaySplashNow = false; +static volatile uint32_t splashDisplayTick = 0; + +static volatile bool chargeAllowed = false; +static volatile uint32_t chargeAllowedTick = 0; +static volatile uint32_t chargeAllowedDelay = 3000000; + +bool isUpsBatteryCritical(){ + //staged + if (batteryLastStateCritical){ + if ((batteryVoltage>battVoltageCritical) && (batteryLevel>0) && (batteryPercentage>12)){ + batteryLastStateCritical = false; + } + }else{ + if ((batteryVoltage 60) { //1:60 sps filter + adcSubTick = 0; + ioAdcFiltered = ioAdc; + } else { + if (adcSkipCounter < 60) { + //feed first 60 cycles + ioAdcFiltered = ioAdc; + adcSkipCounter++; + } else { + return; //skips + } + } + //===================================================== + + //main minimum sub samplings (long filter, 1:32 sps): + + //if have 1:8 changes jumps + if (ioAdc > ioAdcPrev) { + if ((ioAdc - ioAdcPrev) > 128) { + adcCapCount = 0; + adcIdx = 0; + adcValueMin = ioAdc; + } + } else { + if ((ioAdcPrev - ioAdc) > 128) { + adcCapCount = 0; + adcIdx = 0; + adcValueMin = ioAdc; + } + } + ioAdcPrev = ioAdc; + + batteryVoltageUnfiltered = adcVoltageBase + (adcMaxVoltageCapability * ((float)ioAdc / 1024)); + + adcCap[adcIdx] = ioAdc; + + adcCapCount++; + if (adcCapCount > ADC_CAP_BUFFER_NUM) adcCapCount = ADC_CAP_BUFFER_NUM; + if (adcCapCount == 1) { + adcValueMin = ioAdc; + } else if (adcCapCount < ADC_CAP_BUFFER_NUM) { + if (ioAdc < adcValueMin) adcValueMin = ioAdc; + } else { + adcTmp = ioAdc; + if (adcIdx == 0) { + adcIdx2 = ADC_CAP_BUFFER_NUM - 1; + } else { + adcIdx2 = adcIdx - 1; + } + for (ioLoop = 1; ioLoop < ADC_CAP_BUFFER_NUM; ioLoop++) { + if (adcCap[adcIdx2] < adcTmp) adcTmp = adcCap[adcIdx2]; + if (adcIdx2 == 0) { + adcIdx2 = ADC_CAP_BUFFER_NUM - 1; + } else { + adcIdx2--; + } + } + adcValueMin = adcTmp; + + //batteryLevel = getBatteryLevel(); + } + + adcIdx++; + if (adcIdx >= ADC_CAP_BUFFER_NUM) adcIdx = 0; + + batteryVoltage = adcVoltageBase + (adcMaxVoltageCapability * ((float)adcValueMin / 1024)); + batteryPercentage = getBatteryPercentage(batteryVoltage); + batteryLevel = getBatteryLevel(); + +} + +void setDisplayForTurnOff(uint32_t tDelay){ + onGoingDisplayOff = true; + turnOffDisplayTick = tickSinceStarted; + turnOffDisplayAfter = tDelay; +} + +void turnOnDisplay(){ + onGoingDisplayOff = false; + displayActive = true; +} + +void turnOffDisplay(){ + onGoingDisplayOff = false; + displayActive = false; +} + +static void setChargeMode(uint8_t chgMode) { + + if (chgMode == CHARGE_STOP_LIGHT_OFF) { + pinMode(CHARGE_STOP_PIN, OUTPUT); + digitalWrite(CHARGE_STOP_PIN, HIGH); + chargeMode = chgMode; + isChargeActive = false; + needFullChargeOnce = false; + } else if (chgMode == CHARGE_ACTIVE_LIGHT_ON) { + if (chargeAllowed){ + pinMode(CHARGE_STOP_PIN, OUTPUT); + digitalWrite(CHARGE_STOP_PIN, LOW); + chargeMode = chgMode; + isChargeActive = true; + } + } else if (chgMode == CHARGE_ACTIVE_LIGHT_OFF) { + if (chargeAllowed){ + pinMode(CHARGE_STOP_PIN, INPUT); + //digitalWrite(CHARGE_STOP_PIN, LOW); + chargeMode = chgMode; + if (!isChargeActive){ + //reset waiting tick each time charge activated + if (chargeWaitStopTick>0) chargeWaitStopTick = 0; + } + isChargeActive = true; + } + } +} + +uint8_t getChargingState(){ + //0 = false, 1 = reduced, 2 = normal charge + if (inputVoltageKeepup){ + if (chargeMode==CHARGE_ACTIVE_LIGHT_OFF){ + return 1; + }else if (chargeMode==CHARGE_ACTIVE_LIGHT_ON){ + return 2; + } + } + return 0; +} + +static void setKeepPower(bool state) { + keepPower = state; + if (keepPowerState != keepPower) { + keepPowerState = keepPower; + if (keepPowerState) { + pinMode(KEEP_POWER_PIN, OUTPUT); + digitalWrite(KEEP_POWER_PIN, HIGH); + } else { + //pinMode(KEEP_POWER_PIN, OUTPUT); + //digitalWrite(KEEP_POWER_PIN, LOW); + pinMode(KEEP_POWER_PIN, INPUT); + } + } +} + +static bool getKeepPower() { + return keepPower; +} + +static void resetCycle(){ + tickKeepPower = tickSinceStarted; + indicatorBlinkTick = 0; + indicatorFastBlink = false; + indicatorBlinkIndex = 0; +} + +static void keepPowerRecycle(){ + //delay keep power befor turn it off + setKeepPower(true); + keepPower = false; + resetCycle(); +} + +static void setOutputPowerState(bool state){ + if (powerOutputActive != state) { + powerOutputActive = state; + //reset shutdown + criticalShutdown = false; + persistentShutdown = false; + ongoingToPowerOn = false; + //automatically keep power to mcu + + if (powerOutputActive){ + turnOnDisplay(); //automatically turn on the display + setKeepPower(true); //keep power to mcu + }else{ + keepPowerRecycle(); //prepare to turn off mcu power if no input power + } + digitalWrite(POWER_OUTPUT_PIN, powerOutputActive?HIGH:LOW); + } +} + +static void batteryChargeRoutine() { + + //charge management: + if (isChargeActive){ + //check for short + //if (chargeError == CHARGE_ERROR_NONE){ + if (inputVoltageOK && inputVoltageReady && (batteryVoltageUnfilteredbattOverVoltage) ){ + //overvoltage detection + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge + batteryCheckSkipTick = 0;//reset skip check for faster act + chargeManagementMode = 0; + chargeError = CHARGE_ERROR_OVERVOLTAGE; + //Serial.println("CHARGE_ERROR_OVERVOLTAGE"); + } + //} + + if (chargeError==CHARGE_ERROR_NONE){ + + //stop the charges by mode *********************************************************************************************************** + if (!chargeAlwaysActive){ + if (chargeToFull || needFullChargeOnce){ + if (batteryVoltage>battVoltageFullChargeMinimal){ + //wait n-seconds before stop the charges + if ((chargeWaitStopTick==0) && (chargeWaitBeforeStop>0)) { + chargeWaitStopTick = tickSinceStarted; + }else{ + if (chargeWaitBeforeStop==0){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//instant stop + needFullChargeOnce = false; + }else{ + if ((tickSinceStarted-chargeWaitStopTick)>=chargeWaitBeforeStop){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop charge + needFullChargeOnce = false; + } + } + } + }else if (batteryVoltage>battVoltageFullChargeMaximal){ + setChargeMode(CHARGE_STOP_LIGHT_OFF); //instant stop + needFullChargeOnce = false; + } + }else{ + //wait n-seconds before stop the charges + if (batteryPercentage>=chargePercentageForStop){ + if ((chargeWaitStopTick==0) && (chargeWaitBeforeStop>0)) { + chargeWaitStopTick = tickSinceStarted; + }else{ + if (chargeWaitBeforeStop==0){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//instant stop + }else{ + if ((tickSinceStarted-chargeWaitStopTick)>=chargeWaitBeforeStop){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop charge + } + } + } + } + //instant stop if overshoot by +1 percent + if ( (batteryPercentage >= (chargePercentageForStop+1)) || (batteryVoltage>=battVoltageFullChargeMaximal) ){ + setChargeMode(CHARGE_STOP_LIGHT_OFF); + } + } + } + //************************************************************************************************************************************ + + //slowing the charge when reach full using charge with light off (will slightly reduce top voltage) to prolong battery life? ------- + if (isChargeActive){ + //(optional by variable) + if (chargeReduceAtBatteryFull){ + if ((batteryVoltage>battVoltageFullChargeMinimal) || ((!chargeAlwaysActive) && (batteryVoltage>=(chargePercentageForStop-0)))){ + //reduced charge (charge indicator off) + if (chargeMode != CHARGE_ACTIVE_LIGHT_OFF) setChargeMode(CHARGE_ACTIVE_LIGHT_OFF); + }else if ((chargeAlwaysActive && (batteryVoltage<=battVoltageFullRest)) || ( (!chargeAlwaysActive) && (batteryVoltage<(chargePercentageForStop-2)) ) ){ + //normal charge (charge with indicator on) + if (chargeMode != CHARGE_ACTIVE_LIGHT_ON) setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + } + }else{ + //normal charge indicator (on) + if (chargeMode != CHARGE_ACTIVE_LIGHT_ON) setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + } + } + //---------------------------------------------------------------------------------------------------------------------------------- + + } + + }else{ + if (chargeError==CHARGE_ERROR_NONE){ + //resume charge by mode: + //force charge after input lost >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + if (!chargeInputLost && chargeAfterInputLost){ + //if (inputUnderVoltage) { + if (!inputVoltageKeepup) { + chargeInputLost = true; + } + } + + //delayed charge logic again + if (chargeInputLost && inputVoltageKeepup){ + chargeAllowed = false; + chargeAllowedTick = tickSinceStarted; + } + + if (chargeAllowed){ + if (chargeInputLost && chargeAfterInputLost && inputVoltageKeepup){ + if (fullChargeAfterInputLost && (batteryPercentage<=triggerFullChargeAfterInputLostAtPercentage)) needFullChargeOnce = true; + //only resume charge if condition meet below upper voltage criteria + if (chargeToFull || chargeAlwaysActive || needFullChargeOnce){ + //charge with light indicator on + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + }else{ + if (batteryPercentage>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + //normal charge logic #################################################################### + if (chargeAllowed){ + if (inputVoltageKeepup){ + if (chargeAlwaysActive || needFullChargeOnce){ + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + }else{ + if (batteryPercentage<=chargePercentageForResume){ + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + } + } + } + } + //######################################################################################## + } + } +} + +static bool setPersistentShutdown(uint32_t delayNs){ + if (!persistentShutdown && powerOutputActive){ + persistentDelayShutdown = delayNs; + persistentShutdown = true; + persistentShutdownTick = tickSinceStarted; + return true; + } + return false; +} + +static uint32_t getPersistentShutdownTick(){ + if (!persistentShutdown || !powerOutputActive) return 0; + if ((tickSinceStarted-persistentShutdownTick) > persistentDelayShutdown) return 0; + return (persistentDelayShutdown - (tickSinceStarted-persistentShutdownTick)); +} + +static bool autoPowerOnEligible(){ + return (autoPowerOn && !powerOutputActive && (forceAutoPowerAtBatteryError || ( ((batteryVoltage>battVoltageForceShutdown) && (chargeError==CHARGE_ERROR_NONE)) || ((batteryVoltage>battVoltageCritical) && (chargeError==CHARGE_ERROR_OVERVOLTAGE))) ) ); +} + +static bool restartAutoPowerOn(){ + //restart automatic power on when voltage input exists + if (ongoingToPowerOn) return false; + if (startupReady && inputVoltageKeepup && inputVoltageOK && autoPowerOnEligible()){ + ongoingToPowerOn = true; + autoPowerOnTick = tickSinceStarted; + powerEverLoss = false; + return true; + } + return false; +} + +static void powerManagementRoutine(){ + //manage shutdown when battery running very low capacity (below critical) while input lost (1-2 minutes), while instant shutdown (less than 5 sec) when below force shutdown voltage + //if (inputVoltageOK){ + if (inputVoltageKeepup){ + //reset critical shutdown if exists ??????????????????????????????????????????????????????????????????? + if (powerOutputActive && (!persistentShutdown) && criticalShutdown){ + criticalShutdown = false; + criticalShutdownTick = 0; + //stop delayed mcu power detach + if ((!keepPower && keepPowerState)){ + setKeepPower(true); + turnOnDisplay(); + } + } + //????????????????????????????????????????????????????????????????????????????????????????????????????? + //automatic power on (only if battery exists and input power resume) ********************************* + if ((powerEverLoss||ongoingToPowerOn) && powerOutputActive){ + ongoingToPowerOn = false; + powerEverLoss = false; //reset + } + if (autoPowerOnEligible()){ + if (ongoingToPowerOn){ + //wait timer reaching delay + if ((tickSinceStarted-autoPowerOnTick) > autoPowerOnDelay){ + turnOnDisplay(); + ongoingToPowerOn = false; + setOutputPowerState(true); + } + }else{ + if (startupReady && powerEverLoss){ + ongoingToPowerOn = true; + autoPowerOnTick = tickSinceStarted; + powerEverLoss = false; + } + } + } + //**************************************************************************************************** + }else{ + //reset automatic power on timing if exist ****************************************** + if (ongoingToPowerOn){ + ongoingToPowerOn = false; + autoPowerOnTick = 0; + } + powerEverLoss = true; + //*********************************************************************************** + + //start power shutdown countdown when critical, or instant shutdown when voltage <= battVoltageForceShutdown + if (powerOutputActive || keepPowerState){ + if (!(criticalShutdown || persistentShutdown)){ + if (batteryVoltage>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + if (criticalShutdown || persistentShutdown){ + if (persistentShutdown){ + if (criticalShutdown) criticalShutdown = false; //clear critical shutdown flag + if ((tickSinceStarted-persistentShutdownTick) >= persistentDelayShutdown){ + persistentShutdown = false; + persistentShutdownTick = 0; + setOutputPowerState(false); + keepPowerRecycle(); + } + }else if (criticalShutdown){ + if ((tickSinceStarted-criticalShutdownTick) >= criticalDelayShutdown){ + criticalShutdown = false; + criticalShutdownTick = 0; + setOutputPowerState(false); + keepPowerRecycle(); + } + } + } + //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +} + +static void batteryManagementRoutine() { + //only charge when input voltage threshold detected ------ + if (!inputVoltageKeepup){ + if (isChargeActive){ + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge again + chargeError==CHARGE_ERROR_NONE; + chargeManagementMode = 0; + } + } + //-------------------------------------------------------- + + //skip check + if (batteryCheckSkipTick > 0) { + if (tickSinceStarted > batteryCheckSkipTick) { + batteryCheckSkipTick = 0; + //Serial.print("VBatt check: "); + //Serial.println(batteryVoltageUnfiltered); + } else { + //if (batteryCheckSkipTick); //reset when overflow 32-bit + return; + } + } + + //used for demo: + //chargeError = CHARGE_ERROR_NONE; + //isChargeActive = false; + + switch (chargeError) { + case CHARGE_ERROR_NONE: { + //if (chargeMode == CHARGE_STOP_LIGHT_OFF) { + if (!isChargeActive) { + //check battery if exists + if (chargeManagementMode == 0) { + chargeManagementMode = 1; + batteryCheckSkipTick = tickSinceStarted + 500000; //add another 0.5s + } else { + if (batteryVoltageUnfiltered <= battVoltageNotDetected) { + chargeManagementMode = 0; + chargeError = CHARGE_ERROR_NO_BATTERY; + } else { + batteryChargeRoutine(); + batteryCheckSkipTick = tickSinceStarted + 500000; //add another 0.5s + } + } + } else { + batteryChargeRoutine(); + batteryCheckSkipTick = tickSinceStarted + 200000; //add another 0.2s + } + } + break; + case CHARGE_ERROR_NO_BATTERY: { + //wait until battery detected to continue charge + //blinkChargeError = false; + blinkChargeError = true; + if (chargeManagementMode == 0) { + //try to charge in case battery dropped too much, then wait 1 sec + if (inputVoltageOK){//input voltage attached / powered + chargeManagementMode = 1; + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); //enable charge + //blinkChargeError = true; + chargeBlinkTick = tickSinceStarted; + chargeStartedTick = tickSinceStarted; + }else{ + //wait for battery voltage raised again (user attach a normal battery) + if (!isChargeActive) setChargeMode(CHARGE_ACTIVE_LIGHT_ON); //enable charge + if (batteryVoltageUnfiltered>battVoltageNotDetected){ + setChargeMode(CHARGE_STOP_LIGHT_OFF); + chargeError = CHARGE_ERROR_NONE; + } + } + //batteryCheckSkipTick = tickSinceStarted + 500000; //skip 0.5 sec + } else if (chargeManagementMode == 1) { + //check if voltage raised > battVoltageNotDetected. If yes, keep for 60 sec + blinkChargeError = true; + if (tickSinceStarted - chargeStartedTick > 60000000UL) { + chargeManagementMode = 2; + chargeStartedTick = tickSinceStarted; + setChargeMode(CHARGE_STOP_LIGHT_OFF); //disable charge + } else { + if (tickSinceStarted-chargeStartedTick>2000000){ //activate after 2 sec of charging. minimal waiting time till charging module voltage raised > 3.0V + if (isChargeActive && (batteryVoltageUnfiltered2000000){ + chargeManagementMode = 3; + chargeStartedTick = tickSinceStarted; + } + } else { + //check the voltage again + chargeManagementMode = 0; + //if (batteryVoltageUnfiltered > battVoltageNotDetected) { + if (batteryVoltageUnfiltered >= battVoltageDetected) { + setChargeMode(CHARGE_STOP_LIGHT_OFF); + chargeError = CHARGE_ERROR_NONE; + chargeStartedTick = tickSinceStarted; + } + } + + //blinking on charge indicator? + if (blinkChargeError) { + //NO LONGER USED +// if (tickSinceStarted - chargeBlinkTick > 100000) { +// chargeBlinkTick = tickSinceStarted; +// if (chargeMode == CHARGE_STOP_LIGHT_OFF) { +// //do nothing +// } else if (chargeMode == CHARGE_ACTIVE_LIGHT_ON) { +// setChargeMode(CHARGE_ACTIVE_LIGHT_OFF); +// } else if (chargeMode == CHARGE_ACTIVE_LIGHT_OFF) { +// setChargeMode(CHARGE_ACTIVE_LIGHT_ON); +// } +// } + //if (chargeMode == CHARGE_STOP_LIGHT_OFF) { + // //do nothing + //} else{ + // setChargeMode(indicatorFastBlink?CHARGE_ACTIVE_LIGHT_ON:CHARGE_ACTIVE_LIGHT_OFF); + //} + } + } + break; + + case CHARGE_ERROR_SHORTED: { + bool blinkChargeError = false; + if (chargeManagementMode == 0) { + //wait 10 seconds before retry or resume charge + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge + chargeManagementMode = 1; + batteryCheckSkipTick = tickSinceStarted + 10000000UL; //add another 10s + } else if (chargeManagementMode == 1) { + //retry charge again + chargeManagementMode = 2; + setChargeMode(CHARGE_ACTIVE_LIGHT_ON); + batteryCheckSkipTick = tickSinceStarted + 500000UL; //add another 0.5s + } else if (chargeManagementMode == 2) { + //check voltage again (larger than 3 Volt) and act againts error + chargeVoltageOK = batteryVoltageUnfiltered>battVoltageNotDetected; + if (batteryVoltageUnfiltered > SHORT_VOLTAGE) { + chargeManagementMode = 3; + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge for a while + batteryCheckSkipTick = tickSinceStarted + 2000000UL; //add another 2s + }else{ + chargeManagementMode = 0; + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge again + } + }else{ + chargeManagementMode = 0; + if (batteryVoltageUnfiltered>battVoltageNotDetected){ + chargeError = CHARGE_ERROR_NONE; + }else{ + chargeError = CHARGE_ERROR_NO_BATTERY; + } + } + } + break; + case CHARGE_ERROR_OVERVOLTAGE: { + //only happened when charging active and over-voltage protection active + //sense also when adc capped at max (>=adcMaxVoltageCapability) or ioAdc >= 1023 + //stop the charging with charge limiter indicator is on + if (chargeManagementMode == 0) { + chargeManagementMode = 1; + setChargeMode(CHARGE_STOP_LIGHT_OFF);//stop the charge + //batteryChargeRoutine(); + batteryCheckSkipTick = tickSinceStarted + 15000000UL; //add another 15s skips + }else if (chargeManagementMode == 1){ + //stop the error after battery level reached below 100% + if (batteryVoltage inputVoltageKeepupDuration){ + inputVoltageKeepup = false; + } + } + + if (inputVoltageOKPrev!=inputVoltageOK){ + inputVoltageOKPrev = inputVoltageOK; + inputVoltageReady = false; + inputVoltageResumeTick = tickSinceStarted; + } + + if (!inputVoltageOK){ + underVoltageTick = tickSinceStarted; + if (startupReady) inputUnderVoltage = true; + } + + if (inputVoltageOK){ + //2 sec for stabilize + if (!inputVoltageReady && ((tickSinceStarted-inputVoltageResumeTick) > 2000000)){ + inputVoltageReady = true; + } + //recheck again + if (inputUnderVoltage && ((tickSinceStarted-underVoltageTick)>2000000)){ + inputUnderVoltage = false; + underVoltageTick = tickSinceStarted;//reset timer + } + } + + //when inputVoltageKeepup but display not ON after system ready, it will be turned on + if (startupReady && inputVoltageKeepup && !displayActive){ + if (!onGoingDisplayOff){ + if (!splashEverDisplayed){ + displaySplashNow = true; + splashDisplayTick = tickSinceStarted; + } + displayActive = true; + } + } + + //button input >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + buttonState = !digitalRead(BUTTON_INPUT_PIN); + if (!buttonState && buttonTickIgnore>0 && tickSinceStarted>buttonTickIgnore) buttonTickIgnore = 0; //ignore button pressed, should be realeased to retrigger action + + if (buttonState && buttonTickIgnore==0){ + if (buttonTick==0) buttonTick = tickSinceStarted; + if (!buttonShortPressIgnore && (tickSinceStarted-buttonTick>1000000UL)) buttonShortPress = true; + if (!buttonLongPressIgnore && (tickSinceStarted-buttonTick>8000000UL)) buttonLongPress = true; + //manage + if (powerOutputActive){ + if (!buttonShortPressIgnore && buttonShortPress){ + buttonShortPressIgnore = true; + setOutputPowerState(false); + //keep power for 4 seconds before total power off (in case saving options and send off signal) + keepPowerRecycle(); //going complete off automatically after keep power cycle countdown 0 + if (inputVoltageKeepup){ + //keep display + //setDisplayForTurnOff(2000000); + //setDisplayForTurnOff(100000); + }else{ + setDisplayForTurnOff(100000); //turn off display after 0.1s + } + Serial.println("POWER OUT: OFF"); + } + }else{ + //power output currently off + //check battery state or power input + if (!buttonShortPressIgnore && buttonShortPress){ + buttonShortPressIgnore = true; + if (!inputUnderVoltage || (batteryVoltage>batteryVoltageEnableOn)){ + turnOnDisplay(); + if (!splashEverDisplayed){ + displaySplashNow = true; + splashDisplayTick = tickSinceStarted; + } + setKeepPower(true); + setOutputPowerState(true); + Serial.println("POWER OUT: ON"); + }else{ + //set battery level to blink, ignore button for 5 seconds + keepPowerRecycle(); + displayFlipFlag = false; + flashAllBatteryLevel = 4; //flash all battery level to give low battery power rejection + buttonTickIgnore = 3000000; //ignore input for 3 seconds + //turn off display after 1 seconds + turnOnDisplay(); + setDisplayForTurnOff(2000000); + + Serial.println("INSUFFICIENT POWER!"); + } + } + } + } + //switching + if (buttonState!=buttonStatePrev){ + buttonStatePrev = buttonState; + buttonTick = tickSinceStarted; + buttonShortPress = false; + buttonShortPressIgnore = false; + buttonLongPressIgnore = false; + buttonLongPress = false; + buttonTick = 0; + } + //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>s +} + +static void setNetworkIndicatorInTransmission(bool rx, bool tx){ + if (tx) indicatorNetworkBlinkerTX = 4; + if (rx) indicatorNetworkBlinkerRX = 4; +} + +static void setNetworkState(uint8_t state){ + if (state!=indicatorNetworkState){ + indicatorNetworkBlinkerTX = 0;//reset blink counter + indicatorNetworkBlinkerRX = 0;//reset blink counter + indicatorNetworkState = state; + } +} + +static void indicatorRoutine(){ + //manage leds indicators timing + //cycle syncer for blink flag and timing + if (tickSinceStarted-indicatorBlinkTick>=100000){ + indicatorBlinkTick = tickSinceStarted; + indicatorFastBlink = !indicatorFastBlink; + //unique blink: 2X fast blink, 1 long off + //indicatorMediumBlink = (indicatorBlinkIndex<=1) || (indicatorBlinkIndex>=3 && indicatorBlinkIndex<=4) || (indicatorBlinkIndex>=6 && indicatorBlinkIndex<=7); + //evenly blink (halved) + if (indicatorFastBlink){ + indicatorMediumBlink = !indicatorMediumBlink; + if (indicatorMediumBlink){ + indicatorSlowBlink = !indicatorSlowBlink; + } + } + indicatorBlinkIndex++; + if (indicatorBlinkIndex>=10) indicatorBlinkIndex = 0; + } +} + +static void upsRoutine(){ + //manage keep power ================================================================================================== + if ((!keepPower && keepPowerState) && ((tickSinceStarted-tickKeepPower)>HOLD_POWER_TICK)){ + setKeepPower(false); + Serial.println("POWER KEEP: OFF"); + } + //==================================================================================================================== + + //turn off display? + if (onGoingDisplayOff){ + if ((tickSinceStarted-turnOffDisplayTick)>=turnOffDisplayAfter){ + displayActive = false; + } + } + + //charge delay start + if (!chargeAllowed){ + if (tickSinceStarted-chargeAllowedTick > chargeAllowedDelay){ + chargeAllowedTick = tickSinceStarted; + chargeAllowed = true; + } + } + + //turn on display when powered by input line (even when programmer attached) + if (!onGoingDisplayOff && !displayActive){ + if (inputVoltageKeepup && startupReady){ + if (!splashEverDisplayed){ + displaySplashNow = true; + splashDisplayTick = tickSinceStarted; + } + displayActive = true; + } + } + +} + +void displayRefresh(){ + + //display program: + if (!displayActive) { + clearDisplay(); + sendDisplay(); + return; + } + + //splash: + if (displaySplashNow){ + splashEverDisplayed = true; + clearDisplay(); + displaySplash(); + sendDisplay(); + if ((tickSinceStarted-splashDisplayTick)>2000000){ + displaySplashNow = false; + } + return; + } + + //blinking battery (weak) + if (flashAllBatteryLevel>0){ + clearDisplay(); + //tick flagging----------------------------------------- + if (displayFlipFlag!=indicatorMediumBlink){ + displayFlipFlag = indicatorMediumBlink; + if (!indicatorMediumBlink) flashAllBatteryLevel--; + } + //------------------------------------------------------ + if (flashAllBatteryLevel>0){ + if (indicatorMediumBlink){ + drawBatteryBlocks(55, 4, 0); + drawBatteryCase(55, 4, 2); + } + String s = "BATTERY LOW"; + //drawString(stringCenterX(s),32, s); + drawString(stringRightSideX(s),32, s); + //drawString(stringCenterX("Please Charge!"),32, "Please Charge!"); + //drawString(10,32, "Please Charge!"); + }else{ + //turn off display + turnOffDisplay(); + } + sendDisplay(); + return; + } + + clearDisplay(); + + //draw power + if (powerOutputActive){ + if (persistentShutdown){ + if (indicatorMediumBlink) drawPower(0,0, true); + }else{ + drawPower(0,0, false); + } + } + + //power in state: + if (inputVoltageKeepup){ + drawBolt(20,0); + } + + //draw battery + if (isChargeActive) { + if (batteryVoltage>=battVoltageFullChargeMinimal){ + drawBatteryBlocks(55, 4, 4); + }else{ + if (batteryLevel>0){ + battIdx = batteryLevel-1; + }else{ + battIdx = 0; + } + if (indicatorSlowBlink){ + drawBatteryBlocks(55, 4, battIdx+1); + }else{ + drawBatteryBlocks(55, 4, battIdx); + } + } + }else{ + if (batteryVoltage1){ + //fast charging + drawCharging(88, 24, false); + } + + //battery errror state: + if (chargeError==CHARGE_ERROR_NO_BATTERY){ + if (indicatorMediumBlink) drawWarning(20,16, 1); //error + if (indicatorMediumBlink) drawString(39, 32, "NO BAT."); + }else if (chargeError==CHARGE_ERROR_SHORTED){ + if (indicatorMediumBlink) drawWarning(20,16, 2); //fatal error + if (indicatorMediumBlink) drawString(39, 32, "SHORT"); + }else if (chargeError==CHARGE_ERROR_OVERVOLTAGE){ + if (indicatorMediumBlink) drawWarning(20,16, 0); //warn + if (indicatorMediumBlink) drawString(39, 32, "OVER."); + } + + //network and tx rx indicator --------------------------- + if (indicatorNetworkState==NET_STATE_CONNECTING){ + if (indicatorMediumBlink) drawWifiAndTransmission(0, 16, indicatorNetworkSignalLevel, false, false); + }else if (indicatorNetworkState==NET_STATE_CONNECTED){ + //indicatorNetworkBlinker + drawWifiAndTransmission(0, 16, indicatorNetworkSignalLevel, indicatorNetworkBlinkerRX>0, indicatorNetworkBlinkerTX>0); + } + //tick flagging----------------------------------------- + if (displayNetworkFlipFlag!=indicatorFastBlink){ + displayNetworkFlipFlag = indicatorFastBlink; + if (indicatorNetworkBlinkerTX>0) indicatorNetworkBlinkerTX--; + if (indicatorNetworkBlinkerRX>0) indicatorNetworkBlinkerRX--; + } + //------------------------------------------------------ + + sendDisplay(); + + //displayDemoRoutine1(); //for screen demo +} + +static void upsRoutineInterrupt(){ + //if (!interruptEnable){ + //currentNs = ESP.getCycleCount(); + //tickSinceStarted += (currentNs-lastNs) * 6;//cycles * 6.25; + //lastNs = currentNs; + currentNs = millis(); + if (currentNs==lastNs) return; //skip this time + tickSinceStarted += (currentNs-lastNs) * 1000; + lastNs = currentNs; + //} + + //startup init + if (!startupReady && (tickSinceStarted>=1000000)){ + startupReady = true; //delay signal from startup + inputVoltageOK = digitalRead(MINIMUM_INPUT_PIN); + if (inputVoltageOK) inputVoltageKeepup = true; + inputUnderVoltage = !inputVoltageOK; + //attachVoltageFallIntterupt(); + } + + //adc filter handler, 16666ns = ~60 samples perseconds + if ((tickSinceStarted - ioAdcTick) > 16666) { + ioAdcTick = tickSinceStarted; + adcReadFiltered(); + inputManagementRoutine(); + upsRoutine(); + lowRateTick++; + if (lowRateTick>=6){ + lowRateTick = 0; + lowRateRoutine = true; + } + } + + //333333 ns = ~4 samples perseconds + //100000 ns = ~10 samples perseconds + //if ((tickSinceStarted - batteryCheckTick) > 100000) { + if (lowRateRoutine) { + lowRateRoutine=false; + //inputVoltageOK = digitalRead(MINIMUM_INPUT_PIN); + //batteryCheckTick = tickSinceStarted; + bool b = isUpsBatteryCritical(); //refresh critical state + indicatorRoutine(); + batteryManagementRoutine(); + powerManagementRoutine(); + + //displayRefresh + displayRefresh(); + } + + +} + +static void initUpsIo() { + //interruptEnable = enableInterrupt; + digitalWrite(POWER_OUTPUT_PIN, LOW);//disable output activation + pinMode(POWER_OUTPUT_PIN, OUTPUT); + digitalWrite(POWER_OUTPUT_PIN, LOW);//disable output activation + powerOutputActive = false; + + //voltage selection + batteryVoltageEnableOn = battVoltageCritical; + + tickSinceStarted = 0; + + chargeAllowed = false; + chargeAllowedTick = 0; + + inputUnderVoltage = false; + inputVoltageKeepup = false; + startupReady = false; + + adcSkipCounter = 0; + resetBatteryLevel();//reset battery soc + + adcReadFiltered();//read once + + initDisplay(false); //init display lib + + //keep power for defined millis + keepPower = false; + if (HOLD_POWER_TICK > 0) { + keepPowerState = true; + pinMode(KEEP_POWER_PIN, OUTPUT); + digitalWrite(KEEP_POWER_PIN, HIGH); + } else { + keepPowerState = false; + pinMode(KEEP_POWER_PIN, INPUT); + } + + pinMode(MINIMUM_INPUT_PIN, INPUT); + inputVoltageKeepup = digitalRead(MINIMUM_INPUT_PIN); + inputUnderVoltage = !inputVoltageKeepup; + + //init charge pins + pinMode(CHARGE_STOP_PIN, OUTPUT); + setChargeMode(CHARGE_STOP_LIGHT_OFF); //disable charge first + chargeError = CHARGE_ERROR_NONE;//clear error + chargeManagementMode = 0; //reset mode + if (inputVoltageKeepup){ + batteryCheckSkipTick = 2000000; //skip 200000ns (2s) + }else{ + batteryCheckSkipTick = 500000; //skip 500000ns (0.5s) + } + + pinMode(BUTTON_INPUT_PIN, INPUT); + + +} + +bool isInputLinePowered(){ + return inputVoltageKeepup; +} + +bool isUpsPoweredFromBattery(){ + return powerOutputActive && !inputVoltageKeepup; + //return (powerOutputActive || keepPowerState) && !inputVoltageKeepup; + //return !inputVoltageKeepup; +} + +bool isUpsOn(){ + return powerOutputActive; +} + +bool isUpsBatteryIndicatorChargingOrBlink(){ + if (!inputVoltageKeepup && isUpsBatteryCritical()) return true; + return batteryBlinkingIndicatorState; + //return false; +} diff --git a/firmware_iot_esp12f/upsOled.h b/firmware_iot_esp12f/upsOled.h new file mode 100644 index 0000000..f44005c --- /dev/null +++ b/firmware_iot_esp12f/upsOled.h @@ -0,0 +1,226 @@ +#include +#include +#include + +#include "bitmap.h" + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 32 // 0.91 inch OLED is usually 128x32 + +// Custom Pins +#define OLED_SCL 12 // MISO = D6 = OLED_SCL +#define OLED_SDA 13 // MOSI = D7 = OLED_SDA +#define UNUSED_PIN 14 // SCK = D5 (reserved when need reset pin) +#define OLED_RESET -1 // Or 0 if display has a reset pin +U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ OLED_SCL, /* data=*/ OLED_SDA); // pin remapping with ESP8266 HW I2C + +uint8_t displayContrast = 96; +uint8_t battLevelNum = 4; +uint8_t battBlockWidth = 15; uint8_t battBlockHeight = 14; +uint8_t battBlockGap = 3; + +//for demo +uint8_t dispDemoIdx = 0; + +void displaySplash(){ + u8g2.setContrast(255); + //u8g2.drawXBMP(0, 0, 128, 32, splash_logo_bitmap); + u8g2.drawXBMP(0, 0, 128, 32, splash_logo_bitmap_inverted); + u8g2.sendBuffer(); +} + +void drawBatteryBlocks(int8_t x, int8_t y, int8_t nFilled){ + int8_t xp = x+((battBlockWidth+battBlockGap)*battLevelNum); + xp-=battBlockGap; + int8_t yp = y; + u8g2.setDrawColor(1); + for (int8_t i=0; i2) u8g2.drawXBMP(x, y, 16, 16, wifi2_lv3_bitmap); +} + +void drawWifiAndTransmission(int8_t x, int8_t y, uint8_t level, bool inArrow, bool outArrow){ + //power 0 = not drawn + drawWifi(x, y+1, level); + drawActiveTransmission(x+11, y+12, inArrow, outArrow); +} + +void drawPower(int8_t x, int8_t y, bool timed){ + u8g2.setDrawColor(1); + if (timed){ + u8g2.drawXBMP(x, y, 16, 16, timer_16x16_bitmap); + }else{ + u8g2.drawXBMP(x, y, 16, 16, power_bitmap); + } +} + +void drawBolt(int8_t x, int8_t y){ + u8g2.setDrawColor(1); + u8g2.drawXBMP(x, y, 16, 16, bolt_16x16_bitmap); +} + +void drawCharging(int8_t x, int8_t y, bool reduced){ + u8g2.setDrawColor(1); + if (reduced){ + u8g2.drawXBMP(x, y, 7, 8, charging_7x8_bitmap); + }else{ + u8g2.drawXBMP(x, y, 7, 8, charge_fast_7x8_bitmap); + } +} + +void drawWarning(int8_t x, int8_t y, uint8_t mode){ + u8g2.setDrawColor(1); + if (mode==0){ + u8g2.drawXBMP(x, y, 16, 16, warn_16x16_bitmap); + }else if (mode==1){ + u8g2.drawXBMP(x, y, 16, 16, error_16x16_bitmap); + }else if (mode==2){ + u8g2.drawXBMP(x, y, 16, 16, fatal_16x16_bitmap); + } +} + +void initDisplay(bool showSplash){ + pinMode(UNUSED_PIN, INPUT); + u8g2.begin(); + u8g2.setContrast(displayContrast); + if (showSplash){ + displaySplash(); + }else{ + u8g2.setContrast(displayContrast); + u8g2.clearBuffer(); + u8g2.sendBuffer(); + } +} + +void clearDisplay(){ + u8g2.clearBuffer(); +} + +void sendDisplay(){ + u8g2.sendBuffer(); +} + +void drawString(int8_t x, int8_t y, String s){ + u8g2.setFont(u8g2_font_ncenB08_tr); + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + char_array[s.length()] = 0; + u8g2.setDrawColor(1); + u8g2.drawStr(x, y, char_array); +} + +uint8_t stringWidth(String s){ + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + char_array[s.length()] = 0; + u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + return u8g2.getStrWidth(char_array); +} + +uint8_t stringCenterX(String s){ + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + uint8_t w = u8g2.getStrWidth(char_array); + return (SCREEN_WIDTH-w)/2; +} + +uint8_t stringRightSideX(String s){ + char char_array[32]; + s.toCharArray(char_array, s.length()+1); + u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + uint8_t w = u8g2.getStrWidth(char_array); + return (SCREEN_WIDTH-w); +} + +void displayDemoRoutine1(){ + u8g2.setContrast(displayContrast); + u8g2.clearBuffer(); + //drawBatteryBlock(5,5, true); + drawBatteryBlocks(55, 4, dispDemoIdx); + drawBatteryCase(55, 4, 2); + + //u8g2.setDrawColor(1); + //u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font + //u8g2.drawStr(100,32,"100%"); + drawBatteryPercentage(100,32,28, 25*dispDemoIdx); + + if (dispDemoIdx<=3) drawPower(0,0, dispDemoIdx>=2); + if (dispDemoIdx==1 || dispDemoIdx==3) drawBolt(20,0); + + //drawWifi(0, 16, dispDemoIdx); + //drawActiveTransmission(11,28, (dispDemoIdx==1 || dispDemoIdx==3)?true:false, (dispDemoIdx==2 || dispDemoIdx==3)?true:false); + + drawWifiAndTransmission(0, 16, dispDemoIdx, (dispDemoIdx==1 || dispDemoIdx==3)?true:false, (dispDemoIdx==2 || dispDemoIdx==3)?true:false); + + drawCharging(88, 24, dispDemoIdx==4); + + drawWarning(20,16, dispDemoIdx); + u8g2.setFont(u8g2_font_ncenB08_tr); + if (dispDemoIdx==0) u8g2.drawStr(39, 32, "OVER."); + if (dispDemoIdx==1) u8g2.drawStr(39, 32, "N0 BAT."); + if (dispDemoIdx==2) u8g2.drawStr(39, 32, "SHORT"); + + dispDemoIdx++; + if (dispDemoIdx > 4) dispDemoIdx = 0; + u8g2.sendBuffer(); +} diff --git a/firmware_iot_esp12f/upsSvr.h b/firmware_iot_esp12f/upsSvr.h new file mode 100644 index 0000000..bfbc5c5 --- /dev/null +++ b/firmware_iot_esp12f/upsSvr.h @@ -0,0 +1,537 @@ +//todo: +// - more accurate TX/RX indication (for security concern), e.g. favicon is served static, thus not display TX/RX arrow. +// - Login and apikey access for next version + +#include "wifi.h" +#include "upsCfg.h" + +#ifndef UPSIO_H +#include "upsIo.h" +#endif + +//#include //for esp32 +#include //for esp8266 +#include + +const char* http_username = "admin"; +const char* http_password = "admin123"; + +String base64Decode(const String& input) { + const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + String out; + int buf = 0, bits = 0; + for (size_t i = 0; i < input.length(); i++) { + char c = input[i]; + if (c == '=') break; + const char* p = strchr(b64, c); + if (!p) continue; + buf = (buf << 6) | (p - b64); + bits += 6; + if (bits >= 8) { + bits -= 8; + out += (char)((buf >> bits) & 0xFF); + buf &= (1 << bits) - 1; + } + } + return out; +} + +bool checkAuth(AsyncWebServerRequest *request) { + if (!request->hasHeader("Authorization")) return false; + String auth = request->header("Authorization"); + if (!auth.startsWith("Basic ")) return false; + String b64 = auth.substring(6); + b64.trim(); + String decoded = base64Decode(b64); + int sep = decoded.indexOf(':'); + if (sep < 0) return false; + String user = decoded.substring(0, sep); + String pass = decoded.substring(sep + 1); + return user.equals(http_username) && pass.equals(http_password); +} + +int8_t wifiStatePrev = 99; + +bool configChanged = false; + +AsyncWebServer server(80); + +void applyConfigs() { + autoPowerOn = (cfgAutoOn >= AUTO_POWER_ON); //auto power on + + //cfgChargeMode: charge mode: + switch (cfgChargeMode) { + case CHARGE_MODE_ALWAYS: + { + chargeAlwaysActive = true; + } + break; + case CHARGE_MODE_RANGE_90_100: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 100; + chargePercentageForResume = 90; + } + break; + case CHARGE_MODE_RANGE_85_100: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 100; + chargePercentageForResume = 85; + } + break; + case CHARGE_MODE_RANGE_80_100: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 100; + chargePercentageForResume = 80; + } + break; + case CHARGE_MODE_RANGE_90_95: + { + chargeAlwaysActive = false; + chargeToFull = false; + chargePercentageForStop = 95; + chargePercentageForResume = 90; + } + break; + case CHARGE_MODE_RANGE_85_95: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 95; + chargePercentageForResume = 85; + } + break; + case CHARGE_MODE_RANGE_80_95: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 95; + chargePercentageForResume = 80; + } + break; + case CHARGE_MODE_RANGE_85_90: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 90; + chargePercentageForResume = 85; + } + break; + case CHARGE_MODE_RANGE_80_90: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 90; + chargePercentageForResume = 80; + } + break; + case CHARGE_MODE_RANGE_80_85: + { + chargeAlwaysActive = false; + chargeToFull = true; + chargePercentageForStop = 85; + chargePercentageForResume = 80; + } + break; + } + + //cfgInputLostTriggerChargeMode: + switch (cfgInputLostTriggerChargeMode) { + case INPUT_LOST_TRIGGER_CHARGE_NONE: + { + chargeAfterInputLost = false; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_ALWAYS: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = true; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_95: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 95; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 90; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_85: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 85; + } + break; + case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80: + { + chargeAfterInputLost = true; + fullChargeAfterInputLost = false; + triggerFullChargeAfterInputLostAtPercentage = 80; + } + break; + } + + //cfgChargeOvervoltageProtection: + chargeOvervoltageProtection = (cfgChargeOvervoltageProtection > 0); + //cfgChargeAutomaticReducer: + chargeReduceAtBatteryFull = (cfgChargeAutomaticReducer > 0); +} + +bool getShutdownSuggestionFlag() { + if (cfgShutdownSuggestion == SHUTDOWN_SUGGESTION_DISABLED) return false; + if (!startupReady) return false; + switch (cfgShutdownSuggestion) { + case SHUTDOWN_SUGGESTION_ON_BATT: + { + return powerOutputActive && !inputVoltageKeepup; + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_75: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 3); + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_50: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 2); + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_25: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 1); + } + break; + case SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL: + { + return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 1) && isUpsBatteryCritical(); + } + break; + default: + { + return false; + } + } + + return false; +} + +void handleGetState(AsyncWebServerRequest *request) { + if (!checkAuth(request)) { + setNetworkIndicatorInTransmission(true, false); + AsyncWebServerResponse *response = request->beginResponse(401, "application/json", "{\"success\":0}"); + // response->addHeader("WWW-Authenticate", "Basic realm=\"UPS Login\""); // Removed to prevent native browser prompt + request->send(response); + setNetworkIndicatorInTransmission(true, true); + return; + } + setNetworkIndicatorInTransmission(true, false); + bool isRefresh = true; + String v = ""; + uint8_t i = 0; + + if (request->hasParam("on")) { + isRefresh = false; + v = request->getParam("on")->value(); + bool tgtState = v != "0"; + Serial.print("Switch request: "); + Serial.println(tgtState); + if (tgtState != powerOutputActive) { + setOutputPowerState(tgtState); + Serial.println("Switching..."); + } + } + + if (request->hasParam("auto")) { + isRefresh = false; + v = request->getParam("auto")->value(); + uint8_t tmpAutoOnMode = v.toInt(); + if ((tmpAutoOnMode != cfgAutoOn) && (tmpAutoOnMode <= AUTO_POWER_PERSIST)) { + cfgAutoOn = tmpAutoOnMode; + configChanged = true; + } + } + + if (request->hasParam("shutdown")) { + isRefresh = false; + v = request->getParam("shutdown")->value(); + uint16_t tSec = v.toInt(); + if (tSec > 999) tSec = 999; + uint32_t nanoT = ((uint32_t)tSec) * 1000000UL; + if (nanoT < 999999) nanoT = 999999; + bool ok = setPersistentShutdown(nanoT); + if ((cfgShutdownInSeconds != tSec && ok)) { + cfgShutdownInSeconds = tSec; + configChanged = true; + } + Serial.print("Timed Shutdown request: "); + Serial.println(tSec); + //Serial.println(ok); + } + + if (request->hasParam("shutlv")) { + isRefresh = false; + v = request->getParam("shutlv")->value(); + int16_t lv = v.toInt(); + if (lv >= SHUTDOWN_SUGGESTION_DISABLED && lv <= SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL) { + if (lv != cfgShutdownSuggestion) { + cfgShutdownSuggestion = lv; + configChanged = true; + Serial.print("Shutdown level changed: "); + Serial.println(lv); + } + } else { + Serial.print("Invalid shutdown level: "); + Serial.println(v); + } + } + + //battery management params: + //cfgChargeMode (charge mode): + if (request->hasParam("cm")) { + isRefresh = false; + v = request->getParam("cm")->value(); + i = v.toInt(); + if (i <= CHARGE_MODE_RANGE_80_85) { + cfgChargeMode = i; + configChanged = true; + Serial.print("CM: "); + Serial.println(i); + } + } + + //cfgInputLostTriggerChargeMode (charge on input lost): + if (request->hasParam("coil")) { + isRefresh = false; + v = request->getParam("coil")->value(); + i = v.toInt(); + if (i <= INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80) { + cfgInputLostTriggerChargeMode = i; + configChanged = true; + Serial.print("COIL: "); + Serial.println(i); + } + } + + //cfgChargeOvervoltageProtection (charge overvoltage protection): + if (request->hasParam("covp")) { + isRefresh = false; + v = request->getParam("covp")->value(); + i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeOvervoltageProtection) { + cfgChargeOvervoltageProtection = i; + configChanged = true; + Serial.print("COVP: "); + Serial.println(i); + } + } + //reset "On-Going Full Charge" triggered after input lost (reset ongoing full charge): + if (request->hasParam("rofc")) { + isRefresh = false; + v = request->getParam("rofc")->value(); + i = v.toInt(); + if (i > 0 && needFullChargeOnce) { + resetOnGoingFullChargeAfterLineLost(); + Serial.print("ROFC: "); + Serial.println(i); + } + } + + //cfgChargeAutomaticReducer (charge automatic reducer) + if (request->hasParam("car")) { + isRefresh = false; + v = request->getParam("car")->value(); + i = v.toInt(); + if (i > 1) i = 1; + if (i != cfgChargeAutomaticReducer) { + cfgChargeAutomaticReducer = i; + configChanged = true; + Serial.print("CAR: "); + Serial.println(i); + } + } + + DynamicJsonDocument jsonDoc(1536); + String jsonResponse; + //String stateOn = isUpsOn()?"1":"0"; + //String autoOn = String(cfgAutoOn); + //String voltageValue = String(batteryVoltage, 2); + //2 battery, 1 Input line or AC, 0 OFF + //String lineActive = isUpsPoweredFromBattery()?"2":isInputLinePowered()?"1":isUpsOn()?"2":"0"; + uint8_t lineActiveState = isUpsPoweredFromBattery() ? 2 : isInputLinePowered() ? 1 + : isUpsOn() ? 2 + : 0; + //String batt = String(batteryLevel); + //String battMax = "4"; + // String battBlink = IsUpsChargingOrBlink()?"1":"0"; + // String chargeLv = IsUpsChargingOrBlink()?(String(UpsBattMinimalLevelUnderChargingOrBlink())):(String(UpsBatteryLevel())); + // String battCritical = IsUpsBatteryCritical()?"1":"0"; + // String isTimedShut = timedShutdownFlag?"1":"0"; + // String onShutRemaining = String(GetShutdownCountdown()); + // String shutdownSuggest = GetShutdownSuggestionFlag()?"1":"0"; + // String shutdownSuggestMode = String(cfgShutdownSuggestion); + // String shutdownLastPeriod = String(timeShutdownLastPeriod); + + //json vars: + jsonDoc["success"] = isRefresh ? 2 : 1; + jsonDoc["on"] = powerOutputActive ? 1 : 0; + ; + jsonDoc["state"] = isUpsOn() ? 1 : 0; + jsonDoc["v"] = String(batteryVoltage, 2); + jsonDoc["batt"] = batteryLevel; + jsonDoc["battP"] = batteryPercentage; + jsonDoc["line"] = lineActiveState; + + jsonDoc["auto"] = cfgAutoOn; + jsonDoc["battBlink"] = isUpsBatteryIndicatorChargingOrBlink() ? 1 : 0; + jsonDoc["battCritical"] = isUpsBatteryCritical() ? 1 : 0; + jsonDoc["chargeLv"] = getRawBatteryLevelFromPercentage(batteryPercentage); //real level + jsonDoc["batt"] = batteryLevel; //staged level + jsonDoc["battMax"] = 4; + jsonDoc["timedShutdown"] = (persistentShutdown && powerOutputActive) ? 1 : 0; + jsonDoc["shutdownRemain"] = getPersistentShutdownTick() / 1000000; + jsonDoc["shutdownSuggestMode"] = cfgShutdownSuggestion; + jsonDoc["shutdownSuggest"] = getShutdownSuggestionFlag() ? 1 : 0; //internal flag raised if cfgShutdownSuggestion fullfill the condition + jsonDoc["shutLastTimer"] = cfgShutdownInSeconds; + //charge configuration: + jsonDoc["chgError"] = chargeError; //error state + jsonDoc["charging"] = getChargingState(); //on charging state + jsonDoc["chgMode"] = cfgChargeMode; + jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; //over voltage protection + jsonDoc["chgReducer"] = cfgChargeAutomaticReducer; + jsonDoc["chgFullTrig"] = cfgInputLostTriggerChargeMode; + jsonDoc["chgOFC"] = needFullChargeOnce ? 1 : 0; //when input lost and "Ongoing Full Charge" flag triggered + + jsonDoc["vSub"] = String(batteryVoltageUnfiltered, 2); + jsonDoc["chgSwitch"] = isChargeActive ? 1 : 0; + jsonDoc["inputUV"] = inputUnderVoltage ? 1 : 0; + jsonDoc["inputVExist"] = inputVoltageKeepup ? 1 : 0; + jsonDoc["btn"] = buttonState ? 1 : 0; + jsonDoc["btnShort"] = buttonShortPress ? 1 : 0; + jsonDoc["btnLong"] = buttonLongPress ? 1 : 0; + jsonDoc["keepPower"] = keepPowerState ? 1 : 0; + jsonDoc["display"] = displayActive ? 1 : 0; + jsonDoc["rssi"] = rssi; + jsonDoc["chargeAllowed"] = chargeAllowed ? 1 : 0; + + serializeJson(jsonDoc, jsonResponse); + request->send(200, "application/json", jsonResponse); + + setNetworkIndicatorInTransmission(true, true); +} + +void onRootRequest(AsyncWebServerRequest *request) { + setNetworkIndicatorInTransmission(true, false); + Serial.println("onRootRequest"); + bool isOK = false; + if (spiffsOk) { + if (LittleFS.exists("/index.html")) { + isOK = true; + request->send(LittleFS, "/index.html", "text/html"); + setNetworkIndicatorInTransmission(true, true); + } + } + if (!isOK) { + request->send(404, "text/text", "404: Not Found"); + setNetworkIndicatorInTransmission(true, true); + } +} + +void upsServerInit() { + + bool b = initConfigs(); + if (b) { + loadCfg(); + applyConfigs(); + } + + //transfer configuration to ups library: + if (cfgAutoOn >= AUTO_POWER_ON) { + autoPowerOn = true; + restartAutoPowerOn(); + } else { + autoPowerOn = false; + } + + //for favicon + server.serveStatic("/favicon.ico", LittleFS, "/favicon.ico"); + server.serveStatic("*/favicon.ico", LittleFS, "/favicon.ico"); + + //login endpoint: validate credentials + server.on("/login", HTTP_GET, [](AsyncWebServerRequest *request) { + setNetworkIndicatorInTransmission(true, false); + if (!checkAuth(request)) { + request->send(401, "application/json", "{\"success\":0}"); + } else { + request->send(200, "application/json", "{\"success\":1}"); + } + setNetworkIndicatorInTransmission(true, true); + }); + + //logout endpoint: clear basic auth by returning 401 + server.on("/logout", HTTP_GET, [](AsyncWebServerRequest *request) { + setNetworkIndicatorInTransmission(true, false); + AsyncWebServerResponse *response = request->beginResponse(401, "application/json", "{\"success\":0,\"message\":\"Logged out\"}"); + request->send(response); + setNetworkIndicatorInTransmission(true, true); + }); + + //for RESTapi + server.on("/state", HTTP_GET, handleGetState); + + //root + server.on("/", onRootRequest); + server.on("/*", onRootRequest); + //Redirect all not-found requests to the root + server.onNotFound(onRootRequest); + + server.begin(); + Serial.println("Server is running!"); +} + +void upsServerRoutine() { + WiFiManagerRoutine(); + + //updating wifi state: + if (wifiCurrentState != wifiStatePrev) { + wifiStatePrev = wifiCurrentState; + setNetworkState(wifiStatePrev); + } + + //update signal: + indicatorNetworkSignalLevel = wifiSignalLevel; + + //manage auto on mode: + if (cfgAutoOn == AUTO_POWER_PERSIST) { + if (inputVoltageKeepup && inputVoltageOK) { + if (restartAutoPowerOn()) Serial.println("Automatic ON restarted"); + } + } + + //if button long pressed ------------------------------------------- + if (buttonLongPressIgnore && buttonLongPress) { + buttonLongPressIgnore = true; + Serial.println("Long Pressed Button Action is Reserved!"); + //(not used) + } + //------------------------------------------------------------------ + + //saving configuration: + if (configChanged) { + configChanged = false; + saveCfg(); + applyConfigs(); + } +} diff --git a/firmware_iot_esp12f/webUI.h b/firmware_iot_esp12f/webUI.h new file mode 100644 index 0000000..94d74f0 --- /dev/null +++ b/firmware_iot_esp12f/webUI.h @@ -0,0 +1,832 @@ +#pragma once +#include + +const char index_html[] PROGMEM = R"rawliteral( + + + + + + UPS Dashboard + + + + + +
+ +
+ +

+ + + + +

+ +
+

Power Control:

+ +
+ + + +
+ + +
+ + +
+ +
+ + + +
+ +
+
+
+ + +
+ +
+ + + +
+ + + +
+
+ +
+

Charge Control:

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ + +
+

Monitor:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + +)rawliteral"; diff --git a/firmware_iot_esp12f/wifi.h b/firmware_iot_esp12f/wifi.h new file mode 100644 index 0000000..a5080e2 --- /dev/null +++ b/firmware_iot_esp12f/wifi.h @@ -0,0 +1,106 @@ +#include +#include + +bool useStaticIP = false; +#ifndef STASSID + #define STASSID "FADI" + #define STAPSK "12341234" +#endif + +const char* ssid = STASSID; +const char* password = STAPSK; +String staticIP = "192.168.0.100"; +String staticGateway = "192.168.0.1"; +String staticSubnet = "255.255.255.0"; +String staticDNS = "192.168.0.1"; +String mdnsPrefix = "SMART-UPS"; //for fast access like http://smart-ups.local + +static volatile bool wifiStarted = false; +static volatile uint32_t wifiNextCheckTick = 0; +static volatile bool wifiConnected = false; +static volatile bool wifiConnecting = false; +static volatile uint8_t wifiCurrentState = 0; +static volatile bool setWifiConfig = false; + +static volatile uint8_t wifiSignalLevel = 0; //0=none, 1=low, 2=medium, 2=high + +long rssi = -999; + +void WiFiManagerRoutine() { + int8_t wifiState = WiFi.status(); + bool isWifiConnected = wifiState == WL_CONNECTED; + if (isWifiConnected) wifiConnecting = false; + if (wifiConnected != isWifiConnected){ + setWifiConfig = true; //reset config on reconnect + wifiConnected = isWifiConnected; + if (wifiConnected) wifiCurrentState = 2; //connected + } + + //wifi signal update + rssi = WiFi.RSSI(); + if (rssi <= -80){ + wifiSignalLevel = 1; + }else if (rssi > -80 && rssi <= -70){ + wifiSignalLevel = 2; + }else if (rssi > -70 && rssi <= -60){ + wifiSignalLevel = 3; + }else{ + wifiSignalLevel = 3; + } + + if (!isWifiConnected){ + if (wifiState==WL_NO_SSID_AVAIL || wifiState==WL_CONNECT_FAILED || wifiState==WL_CONNECTION_LOST) { + wifiConnecting = false; + wifiCurrentState=0; + wifiSignalLevel = 0; + } + + if (!wifiStarted || (millis() - wifiNextCheckTick) >= 30000UL){ //check each 30seconds + wifiStarted = true; + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.setOutputPower(20.5);//set to max dbm for reliability (for highest RF power output, supply current ~ 80mA) + //WiFi.setOutputPower(0);//(for lowest RF power output, supply current ~ 70mA) + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + wifiNextCheckTick = millis(); //next 30 seconds check again + wifiConnecting = true; + wifiCurrentState = 1; //connecting + } + }else{ + if (!wifiStarted){ + wifiStarted = true; + setWifiConfig = true; + } + if (setWifiConfig){ + //set connection on first time connected + setWifiConfig = false; + + WiFi.setAutoReconnect(true); + WiFi.persistent(true); + + if (useStaticIP){ + IPAddress ip, gateway, subnet, dns; + bool b = ip.fromString(staticIP); + b = gateway.fromString(staticGateway); + b = subnet.fromString(staticSubnet); + b = dns.fromString(staticDNS); + WiFi.config(ip, gateway, subnet, dns); + } + + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + if (MDNS.begin(mdnsPrefix)) { + Serial.print("MDNS responder started as "); + Serial.print(mdnsPrefix); + Serial.println(".local"); + } + } + + MDNS.update(); + + } + +} diff --git a/web_ups/.gitignore b/web_ups/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/web_ups/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/web_ups/README.md b/web_ups/README.md new file mode 100644 index 0000000..9d112ca --- /dev/null +++ b/web_ups/README.md @@ -0,0 +1,37 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +# ups_dashboard diff --git a/web_ups/eslint.config.mjs b/web_ups/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/web_ups/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/web_ups/next.config.ts b/web_ups/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/web_ups/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/web_ups/package-lock.json b/web_ups/package-lock.json new file mode 100644 index 0000000..ab764e6 --- /dev/null +++ b/web_ups/package-lock.json @@ -0,0 +1,6926 @@ +{ + "name": "ups-dashboard", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ups-dashboard", + "version": "0.1.0", + "dependencies": { + "@supabase/supabase-js": "^2.105.3", + "jose": "^6.2.3", + "lucide-react": "^1.14.0", + "mqtt": "^5.15.1", + "next": "16.2.4", + "react": "19.2.4", + "react-dom": "19.2.4", + "recharts": "^3.8.1" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.4", + "typescript": "^5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", + "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.4.tgz", + "integrity": "sha512-tOX826JJ96gYK/go18sPUgMq9FK1tqxBFfUCEufJb5XIkWFFmpgU7mahJANKGkHs7F41ir3tReJ3Lv5La0RvhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", + "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", + "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", + "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", + "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", + "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", + "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", + "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", + "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@supabase/auth-js": { + "version": "2.105.3", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.105.3.tgz", + "integrity": "sha512-hMFuzP++mjRfe0/BUq4/e82CXIDgyjUgg0khLN8waol/gzoM1t2iGmhfJSGvQHQ1dr3XqWpP6ThAw4bLHMot5Q==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.105.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.105.3.tgz", + "integrity": "sha512-KyutUwLLUZ9fRXsiFACL6lq7akBVHFl0fnqQnrxjbsPco8jeb4EyirQuvr52QCLnikzjMRC0uxAHOSM54aDrZA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.1.tgz", + "integrity": "sha512-hWGJkDAfWUNY8k0C080u3sGNFd2ncl9erhKgP7hnGkgJWEfT5Pd/SXal4QmWXBECVlZrannMAc9sBaaRyWpiUA==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.105.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.105.3.tgz", + "integrity": "sha512-jFVYRHcri0ZMcTzKpQ2r2wWOB8/rPsbj92kxmCmVJUiRrdgiMtuYlkS06Fhs8UJZhEOL0UpGhh06XDwh8JwtBQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.105.3", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.105.3.tgz", + "integrity": "sha512-L+qPiJlq1RKh3QD2fORGCFo2RKDKlvG9mjvPtUEQJ2tMixrx70VIV6j8BdWzQkbc1Nao6mvTWajyDhX3TFgljw==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.1", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.105.3", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.105.3.tgz", + "integrity": "sha512-M7oPCCcHim/FsR6rKIs10Nd9mW051N2SQvA27jiVLa7oQMFFb7faX5dCQRV4GS5QeFsBcV5J/fWl4Ppoaw8cBQ==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.105.3", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.105.3.tgz", + "integrity": "sha512-5Dm9+I61LAWwjw+0zcqXhSmTxUJaYHBPyHwMCIBH4TBUNwDn2pYUIsi6oUu0I5r9HtLtaFl7w4wa+DV9gRsbDg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.105.3", + "@supabase/functions-js": "2.105.3", + "@supabase/postgrest-js": "2.105.3", + "@supabase/realtime-js": "2.105.3", + "@supabase/storage-js": "2.105.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/readable-stream": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", + "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.4.tgz", + "integrity": "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bl": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/broker-factory": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.14.tgz", + "integrity": "sha512-L45k5HMbPIrMid0nTOZ/UPXG/c0aRuQKVrSDFIb1zOkvfiyHgYmIjc3cSiN1KwQIvRDOtKE0tfb3I9EZ3CmpQQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "fast-unique-numbers": "^9.0.27", + "tslib": "^2.8.1", + "worker-factory": "^7.0.49" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commist": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", + "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.345.tgz", + "integrity": "sha512-F9JXQGiMrz6yVNPI2qOVPvB9HzjH5cGzhs8oJ6A28V5L/YnzN/0KsuiibqF+F1Fd9qxFzD1BUnYSd8JfULxTwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.4.tgz", + "integrity": "sha512-A6ekXYFj/YQxBPMl45g3e+U8zJo+X2+ZQwcz34pPKjpc/3S4roBA2Rd9xWB4FKuSxhofo1/95WjzmUY+wHrOhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.2.4", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-unique-numbers": { + "version": "9.0.27", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.27.tgz", + "integrity": "sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip-address": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.1.tgz", + "integrity": "sha512-1FMu8/N15Ck1BL551Jf42NYIoin2unWjLQ2Fze/DXryJRl5twqtwNHlO39qERGbIOcKYWHdgRryhOC+NG4eaLw==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz", + "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mqtt": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.15.1.tgz", + "integrity": "sha512-V1WnkGuJh3ec9QXzy5Iylw8OOBK+Xu1WhxcQ9mMpLThG+/JZIMV1PgLNRgIiqXhZnvnVLsuyxHl5A/3bHHbcAA==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.21", + "@types/ws": "^8.18.1", + "commist": "^3.2.0", + "concat-stream": "^2.0.0", + "debug": "^4.4.1", + "help-me": "^5.0.0", + "lru-cache": "^10.4.3", + "minimist": "^1.2.8", + "mqtt-packet": "^9.0.2", + "number-allocator": "^1.0.14", + "readable-stream": "^4.7.0", + "rfdc": "^1.4.1", + "socks": "^2.8.6", + "split2": "^4.2.0", + "worker-timers": "^8.0.23", + "ws": "^8.18.3" + }, + "bin": { + "mqtt": "build/bin/mqtt.js", + "mqtt_pub": "build/bin/pub.js", + "mqtt_sub": "build/bin/sub.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/mqtt-packet": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz", + "integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==", + "license": "MIT", + "dependencies": { + "bl": "^6.0.8", + "debug": "^4.3.4", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/mqtt/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", + "integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.4", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.4", + "@next/swc-darwin-x64": "16.2.4", + "@next/swc-linux-arm64-gnu": "16.2.4", + "@next/swc-linux-arm64-musl": "16.2.4", + "@next/swc-linux-x64-gnu": "16.2.4", + "@next/swc-linux-x64-musl": "16.2.4", + "@next/swc-win32-arm64-msvc": "16.2.4", + "@next/swc-win32-x64-msvc": "16.2.4", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/recharts": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", + "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/worker-factory": { + "version": "7.0.49", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.49.tgz", + "integrity": "sha512-lW7tpgy6aUv2dFsQhv1yv+XFzdkCf/leoKRTGMPVK5/die6RrUjqgJHJf556qO+ZfytNG6wPXc17E8zzsOLUDw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "fast-unique-numbers": "^9.0.27", + "tslib": "^2.8.1" + } + }, + "node_modules/worker-timers": { + "version": "8.0.31", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.31.tgz", + "integrity": "sha512-ngkq5S6JuZyztom8tDgBzorLo9byhBMko/sXfgiUD945AuzKGg1GCgDMCC3NaYkicLpGKXutONM36wEX8UbBCA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "tslib": "^2.8.1", + "worker-timers-broker": "^8.0.16", + "worker-timers-worker": "^9.0.14" + } + }, + "node_modules/worker-timers-broker": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.16.tgz", + "integrity": "sha512-JyP3AvUGyPGbBGW7XiUewm2+0pN/aYo1QpVf5kdXAfkDZcN3p7NbWrG6XnyDEpDIvfHk/+LCnOW/NsuiU9riYA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "broker-factory": "^3.1.14", + "fast-unique-numbers": "^9.0.27", + "tslib": "^2.8.1", + "worker-timers-worker": "^9.0.14" + } + }, + "node_modules/worker-timers-worker": { + "version": "9.0.14", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.14.tgz", + "integrity": "sha512-/qF06C60sXmSLfUl7WglvrDIbspmPOM8UrG63Dnn4bi2x4/DfqHS/+dxF5B+MdHnYO5tVuZYLHdAodrKdabTIg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "tslib": "^2.8.1", + "worker-factory": "^7.0.49" + } + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", + "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/web_ups/package.json b/web_ups/package.json new file mode 100644 index 0000000..585ad7b --- /dev/null +++ b/web_ups/package.json @@ -0,0 +1,29 @@ +{ + "name": "ups-dashboard", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "@supabase/supabase-js": "^2.105.3", + "jose": "^6.2.3", + "lucide-react": "^1.14.0", + "mqtt": "^5.15.1", + "next": "16.2.4", + "react": "19.2.4", + "react-dom": "19.2.4", + "recharts": "^3.8.1" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.4", + "typescript": "^5" + } +} diff --git a/web_ups/public/file.svg b/web_ups/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/web_ups/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ups/public/globe.svg b/web_ups/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/web_ups/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ups/public/next.svg b/web_ups/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/web_ups/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ups/public/vercel.svg b/web_ups/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/web_ups/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ups/public/window.svg b/web_ups/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/web_ups/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ups/src/app/api/auth/login/route.ts b/web_ups/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..bae120b --- /dev/null +++ b/web_ups/src/app/api/auth/login/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from 'next/server'; +import { encrypt } from '@/lib/auth'; +import { cookies } from 'next/headers'; + +export async function POST(request: Request) { + try { + const { username, password } = await request.json(); + + const validUsername = process.env.DASHBOARD_USERNAME || 'admin'; + const validPassword = process.env.DASHBOARD_PASSWORD || 'admin'; + + if (username === validUsername && password === validPassword) { + // Create session + const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days + const session = await encrypt({ username, expires }); + + // Save the session in a cookie + (await cookies()).set('auth_token', session, { + expires, + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + path: '/', + }); + + return NextResponse.json({ success: true }); + } + + return NextResponse.json( + { error: 'Invalid username or password' }, + { status: 401 } + ); + } catch (error) { + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ); + } +} diff --git a/web_ups/src/app/api/auth/logout/route.ts b/web_ups/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..fa76637 --- /dev/null +++ b/web_ups/src/app/api/auth/logout/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; + +export async function POST() { + (await cookies()).delete('auth_token'); + return NextResponse.json({ success: true }); +} diff --git a/web_ups/src/app/api/control/route.ts b/web_ups/src/app/api/control/route.ts new file mode 100644 index 0000000..ae85695 --- /dev/null +++ b/web_ups/src/app/api/control/route.ts @@ -0,0 +1,48 @@ +import { NextResponse } from 'next/server'; + +const ESP_IP = process.env.ESP_DEVICE_IP || 'http://192.168.0.100'; + +export async function POST(request: Request) { + // Bug #5: Authentication check + const secret = process.env.CONTROL_API_SECRET; + if (secret) { + const authHeader = request.headers.get('Authorization'); + if (authHeader !== `Bearer ${secret}`) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + } + + const { searchParams } = new URL(request.url); + const cmd = searchParams.get('cmd'); + const value = searchParams.get('value'); + + if (!cmd || value === null) { + return NextResponse.json({ error: 'Missing parameters' }, { status: 400 }); + } + + try { + // Construct the URL exactly as expected by the ESP: http://192.168.0.100/state?cmd=value + const targetUrl = `${ESP_IP}/state?${cmd}=${value}`; + + // We add a timeout to fetch in case the ESP is unreachable + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 5000); + + const res = await fetch(targetUrl, { + method: 'GET', + signal: controller.signal + }); + + clearTimeout(timeoutId); + + if (!res.ok) { + return NextResponse.json({ error: 'ESP returned an error' }, { status: res.status }); + } + + const data = await res.json(); + return NextResponse.json({ success: true, data }); + } catch (error: unknown) { + console.error('Proxy Error:', error); + return NextResponse.json({ error: 'Failed to reach ESP', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 }); + } +} diff --git a/web_ups/src/app/favicon.ico b/web_ups/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0c58cefe1bf8f312085793c0e778bc37649245a9 GIT binary patch literal 23620 zcmce;by$>N+b(?5U6RraAW9=D(lrQ(lp-lDDBT@{(k-BZBCT{I-Cfe%sdP#Y1K;BJ zJn!D`xA(jE{`Nng2Oh)B-1oZIx@w)*d0mT8H5GXxd|G@6f`}9q;Ljll9sG(8;bMcA zBlpn@@PcQrpyLEV#H}~~(K_w2UxN>CJIiT1zpyiRb~AP~gWTNQcr9$LoJ@`F&3Nq` z-z4se(LxY2qzISRaR0VF=jN{Q@(O$3&HVm`_`4720hqX?=xO)?yzsD)kkq?jl%wVG z`fhVBE-p5CR#WOS`Rcgpy)_T_ zqamk$lt}+CpS5|X0kn{R{p&j380GaV?4oNTh3cU;)X{_j)BwGQ&LDfO0uk774Ha|@ zaiWc)b>Z0KH*KU`dE!cCSdI-*3P6uj(x5vSG&n{y5Q$4K3oJJOc9R4o2~FZI7T5e9 zTzr`;l`_qzhk;ZD>nC2I%{E(|3*zR#oA;nX)sO zYe=*J^vUoF4~$hv3{AM-8DfHPwQ5OWFpf_oi)cUfDVYNg@cj;Fp*r+ML+DbYFfgR+ z%Q;2@1~W|)F=X!2>tIudo|?}KX=d!`_`+bNb7+tdM%Rm4Qmu9<^_v9r2xC3pl?4vp z(LkFXms7m?;=5KYME#JWDSELxC8Udy401(RcwS8$-^K+U(xDmAO@BUMhry6z7zvP3 z<8)xvEo{i69b+=E0*|{CZ-eIDda}<@j+0P;bA*(U0*?FnS@YZ+Iku3HFo_)ewV;y~ zm#*&DL2bkE+{5P|<0(?tmpxX$NxVCZm8mAtA~vF!KA~a0%@1{D5lkl=M}9OdF7A5E zMobqh;s1o0`L0G3OK1pvW=%T9ZTaUPJdHTQ{yP1pJ%LRZmO7fG64F4cOTeeja~TSQpI%I;Ztm4-Wu;>kFTxQv7!~B6sOQf60 z`sBUAt#dVOvg$|S?e8%p^E*#ml!m4`&*keVn;t-Pt9j47XrS$t=!zX4cAh=DVF7xf zh0_NuqMcoOlw>h+TvhhOdDM-eVZ*Q-*;TZ4qEwZJ+@FC;XftarKgGYoxbN5C{1Lwr z!gO0_b5exE`v>@#X;6i?!wKG~re&5s>mj{*n}{e^p6~hTuLXTq!nDV=u|g4q?j(ii zF}YJcaU(g;gTXB2b-yO=j|CU8hzh=Z>PGJO4jTSl@RZISORA>*?aZ*2f<|R>70L|} zR2!Uy4whfe2XuCH`!T(6*2s@nc>Jk0I*!kkmo`dvjSB3LmQ3loxp*3(Yi}c+SM?>e~)`f<-KK;ZDkDf{8qDZEa=p zx-sLr1IEWSwYjNoehR>krIrG`|7jM6vxxZ7MTMlTtt~%oq`e^R($W&1zx1|{x%93M zx5r=A=gGv{#EXxHRCGHb=0Yw%Yg_~IFAug12}cdk)GoES;>jOokQDyR+C6) zz2LLc$)_3}A5AY!r!Oxre`{g!uH8hvfDz{CLlut2!^4wZlqW0eBc4yi6~WlSk$HlaWw(+~6Ife@*Otd7G;3zVEd+P<)Az3)&ZWyIo{yq+BC&VJ<7``#= zvCpRkMMbg>(rA`emPRFqZL09iGHsshoI+`JU+|d~pYTAo(&>E2PSRZwXQrRxw-g?k zK%|0-KU=dEWt_0gjPHL@d!-Q&c8H&F9-b2N?L)hz^h-1Lr(xl|!qgT=uN4=P;&ynS z$1Na^@BV;bUa!4aNCM&sium#;OjpMp%Pk5sGaKbfhsMow5g#?jr8SdUjH zBO{5j^B*XD!R>;KSm)7;^*<`C3>GW=*n|cj&0NQyUiSUbOuh_N5cu7GK3QYQ^6QUg zFhf~k1y#d4mh?FhF7TMPa4sL)6o;cbYG_ayb$8&&)sDj+{g@@<)-RA*g_#flrQH}DbelvRYRd?Xm&b%wdve9ei6vU8> z4v%7JkIL}5z7ld>d=i?v7wo%ium8E zgtmtRMGy42vkU8vh;=eQ|53Vfg29A^FKVB(#No8W6aEZCOJ&Ge$&`S@*~qc|Tw6>^ z(hCbUckYLTbjk5UKgnIn%Zs&nOYH8Dk;PyVLOXC-pZaNbQnDBVW9Tgo*iDQrQP)T_ zaxyZ-_8)#T?}9wfg)Tf`k(J~j*?lu8XUljg&P7T;*VK|6^_^Q_)OLR8v2fW`#JFZ> zLA^2@KHUT@u6|8SL^CMhfWf|i?Oix-phQRbLt8S&ny%2?t@LwM9!{Oga4bZ?9 z!_3$a3^Q7|J9o&Ihr}~QQmhU z51Q7Oa$NC%WFcxYnrTHK00oTqXZkDmX`ol|)3atE9XKs%?;dS@Gqea>#*UCu(EdJc zMjAsvfENxgY9q6Zm%eWd*^^=W)5X5wF~Vhr$pr++!GP7S6JeJtv3#+=XGFccL+wZR zM?CP?ikAcL3O}b3fw859W-T|&GAm-25qFa`YVxdk{nO+390=-GXwl>d8{>kIHV|y; zIr{iuuN9a_|GyE(|5}8*Sc@jWFJ3b$HCIQlW^?nv^K#^NVnec%9RCL=cJHxgziix) zq5W^pnFpp)?Wg&I;3P2ehTtEZt`}h!WGi(81z+@Jr82>kK9N48IJ!?}`ZhTBZt`1!a z*gWtUI2=yf2K`rWcw5$Y)Bcgp6B1RhnlJoNRY=pI%D+sq?cXK|qVA3N4f|>PPE--z zn6Z!%rc4WLjMqQpm9~O5nQJX%hg#FdV2XR)~%z{N%na467pzx20Lk!Xk zEkokI{J<0anwwq(aV=ddQxB`@Me=Y_r>C0Fr%AI9&F&)-NYDcgmao@sE|k|KIz7>dt+IJA@GL@~aNA8(SiJ z9PgmLbL*ea(sT0_{xL}4$z#0lUbleQ@QkPLAI&uF(M~k~mkRw)s(yv9osq0imJ1AB zIJ6Q8aBO|-_?*APB{%0%yw^wnYS&sf|Pk_TY zmnr{`q1c`b0O)_D!|&-1R`ZVsV+OfC|HmYm|80^$VZ~reiotF>tnDznG2<#UR?=Hk zIj*&=TnKw~uMAqr$H#xV-X<&dYp^v-jIJFoC30|63P4DwKA z`FmynVn+nPAPKr($9Tg-rtf9c;=C6LV14Av4=jGMdjV`?RW$X@ejtdZ4Wz1D#>>Fr zDiM{?|7madU&`kHTh!=M-a!wU_OIE*r&#^=#KGWEPx#-VQMKUA4o__!DjIRIr7fm_ zra}GyxprpGOrcA3*-_qeEp16M)Mu3)^l7p@g%xTEZLVHvK7%Ue-p6eHmgS3=IV%8| zkYH86F+%D|1s7-At_!}Us5w>%P|4~Kd^BEUi(*TOHuQP+lVHH+(PH>(QEZ6l-Y;=~ z)IyP*oMMCi3T9A%vHRgOIJ~o4(3XvLjyf%GI?ZreQLNeIwW2i zHDe`pD@Zv3%|B6n+yN1OZ1^=}lLKh&uD=r$;yVm-)4d7qCyKsN+oo?Am83l!7-Qe? z`DMjNZ?9nFUq7W+msIy^O*Dl=U+L(IEzXf)-@JV_fjbfz)`bHpOm&*0w6b2K;5%d( zkVSI!SO5=~1PJ!;mOFDf`8viw5g6M(?0$4-t1{`U5F z`IqAJH-$i*{aDALcI(lXT1`UC+Sw_q7jw>4cCKuHo)O4;lQ7X7iO~0Dw}%n6YuDvM z=4DP3cKn+TeKsh8#SRJF@GYp`Ah?cPP62g7LCmcb*e-ocUX2bW{C#%BqU99P>f=*l zGEGPr9&$M*$uv{V)#17uhP+^3zIe!`K0V3IpG)bnCV1mmYCO+3qu1Bpja{oW?W3VJ zUaYMIVHwXBGyiIQcq?vKIBGbI>YX}+RwE*T?+;J%yF!0o)P{qOE_9s)vUWg{3gSWw5C0m?1%~xoSUG=K* z0slM+9o2Dgt@^fAGubg+#D!AwCF0)wH1b`P2^Ha!--Za7Rud%kRYOOUZS*QR;7SXO zXlKu2L-A*m%v$OK~MCx7D1ytv@2x7PBbl14#0Mb{tnJ|R${ zuh4*f#7@m}jy7o7hrn#mnnq{YMj1(q8r|Gxgqp}K2p(r%6*E>eW`gf5{;dGE@G0)m z`M1TW4A?LgO1*gFuCse*ABfT6bEoQh75y>pIo#oK{j_ zhYFa+SUxVA*06S(`LPh9Jm>^Rr$mx=VxC7iGM?YIl-|-?Aq>X5ZKc)vx&$|JPT;=oKguqnjN22BlC^jkZ{x&P zPc`xuZrsbJ?At`6Aq?Ox$p-KLjb43_1vX4~#2upL^JS5uG3OkSzJ~xP(tI@CB6| zoRvj@BT-76D;1=4QSt*fK0m`sf3c=bfmet=Ve)8Y;1}&dxH)jLOd27PX&SFUl9TbFHb4j z9WyA5$BQfLgNk}c)D~a0E z5IZ}8Pf$@&`Ws--BW_m>EDn`ElQ2aMcfADPb%5`L#60f6YD9Kl4+`9;orq{uuQj16#gE0Vss2t(Y<4-*h}De2>{@od2h)h>=N;u--50wcxQdC?@#H|a2M>?1B(^7^afbw(vw4jg8S^vMFhAs(!O*eD7)U@>9P3Jm3 zcr=<;oyMpfdHnWjL-mEJIF$M$=b+IC%pHP+GDHc3DvHqERQVvV_mYVDUaV})ooMkf zNuFV0ww`85N}jT$pM<6urMsqA=b(LJ$I8CT1D8{j$n6PJ*LxWiR6`4CcCS=?)xDy_ z{+bBt5alm_Yi<1SuPl-TpoEl$HfS*_26vYVlh$~~ z7vK{0&cIHs`Y8<10?!roU9b*IRay9u6!?xRt)s*Fu8Lt`2=Ls<8>`y6W(1%V4nKu! zSTn)fkAjL2AEw!FS=<7X#~7Q?VBz4RToa7Wc)TaJUG27D@J07)jMoLGqiUr-3ohYK zfNlM)q53`2Gn~NOR}3+({1EBA{E{@PsoU}&d^TyG9y+D9$6b8_Y3kBsa^K^MHv}S2 zT;-nap@oDpVe;8YmMgJ3S^$iWU#wej_m_C&FH^4=uRoaF=4I3Dk24bE)i5yVGL_BOKo3KEJUZFXU68PKQS*QjlsriQn~!;kOoRNjLfDav<#3 zo4Lxn+Q6k%K2QI68wCi*-7n-_;!fzs>H;(m%sE?XowaJY(_Mqn{1l`tmaypO%E>&Y zvPOA-Yh7ZX4?Yy39U7Xuc_E95!6n9%R#3ow?=MSAe+!+DkM9hL#DHeS#TIwwDqh>k z%3|n#of&7&&dHHBHD%@O=~Y)(k2*Yb{22c!jaibZT3EQNuTR=mg&6R|FOhbrA3)pI zmxqQSVLSfP8FyG8)P+y8la^s)KEbmtvQ1W3tvn=Q+iou_^2&mAymx2zakXFm+8JxfT4SnZAzFgCd_;=H|HY}%Wq z_l&Tny>0PCbQcr)NI|`Vw9zYZ8M))#Xv4+X^9k`_*hGMJWk0%0!iCVk^O;<2Svl$8%FE_*{5f^YgdMlFPl z78jdU4hRhK%*$@elQ4Az?KQN2eaYppyR8#UFT|Zo_e7=PQ#5gf>HiS9dHcccdNyC6 zFjuL3@Clh->(cs5r%b?U+?tJ;K6}07+gJ&!e6iC(*Zidxs@UbPFcW zTZz4E+yxr+E2;YWdUnESD5mjPvwlf;cD-$nl0F1QNbEJ}L#7Tl75zfl?aotM|1nGT zRT^>6lQtg$#-<&&+0exgq;gS1$s5ih5p)Qij>tx;-)3go6-NZz24^h&-z0dDC7ius z#l{=8>yohBp^6jL+dEV+z5O}WuP;ITjx@X?=zLyr*YS`k`D4HjF3sh)7m8wsXXIk% zGt6ucD5Ax#Fr&=AwQSlow4YgVbomm3>X@n5BU2RhUMeVCog9Qt6WpNv5euCCXLUUpSwWmyNC;DFYBYkB?i{fvL!N)HOQ-Y55Dvc)^FscqRzmzWAzsJ+n zUc+v;_$$=EG)igI005rMgua5wvIDN|`C`IMDv!N~(T%R?C-jeR=4Ueafu%I6z|23G zxV`jcp2Oo~Oxqfd&-v0#qr(yUTa9he3p_8)l5k)T1)vY+AgM7zt?e;$xuoQ;+)@1+ z@zFoH#CPjaU;nyW!2H31c8v+U>yD^j=!9~t%XZmDFc6eic3#d$mshYi9g2S6@vww- z&Wz{YytT}axA_I`a9>HWTbafqH=`S2Y?qU3)Gd;f{Y;gt&f-v^8#_2(|%9oo?T)H0reDV3fS%hJ?Zp*grutSoRf}XoKp(4AesB@Re&15fGe6msq z3;5v^Q`3yrC_4fWQ3+}9W46k(8Mk%C&^$EXMhMY32K0pCiy@C*XkVPaDcuntbX$K+ z$RL@t_3$woHe|LhSIU4PB|2)%IDL z?k1po-<~R1Uw-^_|L6!l+@-FNEOvWBIr)9Eg9EYfZq&}gN(>IwqIZRusvFqUIw$FN z{hIp@w1n0{Ec@8}4~W}=zmR39D7?V^fmQ7qZN^Wt-?gH;IKWqzea1IEqNAf-X30LD zuNxY-u@T1DJG4xG>#Fw1Av(Ed-q7yt+a@K15977xnvFL1$->D7=@4ai8FkZ0uYAy^ z0{(wr&7hi=zjE(2Yj12rQmu1yXNu3em<;(|wW%L3wO$&Z$15F7aVUN8+)gm*bhs0W zfnzd1ce*$}V=FIZlxosBg<6TOa+_!9s&yaQEqN~^YCQaHp?PM)FsMg?0>khA84YuF z1mJh9wJ!>}Ij(goXT$tEpHqsSGfh^K{M~CH5Kt#`KOB;O;B<)N2P|bdRG#sP^_s%3 zMkFu&B*&iV7fHH@=7hwIO6?#D?l5kxu?KAB{{He-VHH&9yVyrHKAN-+pKcpWkIl@` zg-f6aQ+V`vkwq^+O{Pezs3n1{mEU$1mm75{n3eczO@WAr=tj3RLXMancZfQtALbc< z_Mq-)N@eyw7{lj&b%Bd)2=`N9RhX#%CX7GP{qyI~#SD^pd6`!vzv|5%5i>ky-a0E>T)aY6`J8YcZHRgy#EV}K$-lL+`DC;08yY(4wn~$! z+&EO|K)Y#K>@d~nA?i0R4jxenz-9?}zkLPsJ*k#7?Wjl zz{c-92XOllA7Y`<>k3ra&n9q~md_jO*=8E{b2_6~JZ5QT9kzJH4tf`7r^;@m5^-DL zS%n@42*i9>C`usnQ2jjWagEQ$E2W6Ek_OGsH6iqoUTA8yBf!xB#ief1X^pt{gSZXr zu~#|_xs;SYQBu0^PDw8QRsGy`xLvK$V62Y2Jk&*^|po!ES zD**BoCw$^YgYYbz8`UN$%fTwv9@N&4$^==1=BJFD8v2Ll!rA?j(O=)wYv_vw*DQ;@ zys8D7^mAa9Uo7<6Yway+xYIlKRrJps=!sF7mDn;L9aTWG8|oXpx5=Y6?y(_h?OV5X zQ-(*iyts2Q(2ywgqDeVNXlsA)5~*%rzEFdqMLx}nCSH^nIq5J$Hq9zM zWZL~>+nKf>wNF%9kqEkUkPjlmA+ZamXq=n1&MRF-C?W+n|aIAZp6yP9=~}T z|CH+06FWn;Suv)+<7SINZWC!U>p8q`hd5W%_O|YObhGtK+}N{M>xdMY54|}Ngc0;2 zle;b$Eg~*09X2y$a|Matuat>g&U}wy0I$1_Nq>KU4LMqRRpvj@N&A_{4APUyIuwrc z#`Md>10E`Z?4Vl6$r@aoE69Rvv9{=!ZH){OLG-`+<7@^FPsLr@m=(fcu=#o~ww z9fY#o8}1jQ0YUY19sT9oB@^r#e%GxY`ApbqAYVRZjf{p!FhFOy!w1cf(xO?W?O{ z^rxDe65qrf6b=tUKY|QXC8r{n9@%iM``j>ND1=X|uyjlKI_%K#S>neC7sK8oE|o0V zfmNM^%3ZctMwBRc*6-UBSnk+CMysYp%e%*KKG^^DUVp9o>0oOt&wlQ#7_leb zGU%FxDD2(cVXE_~S{F(oVpvvtC_qzeQaB~r+>^ye`XKdH*le`#=BP5A*N=@xNQlx+ z7z&ZCmHpOPyi<2=AK`T(9?(wm*-Y%my0c#`8viEZ!W5c4@{`6{KfXpHQ#M@u#->l2 zi?wI2<_RIH)Uk^ZCc!}y3uIpR@X4BG%J=jm|Ffkrd39%*K@Xh z@Sf9<%pp>~H!!ef2)OK;mhuP{$bG}iCgjC>rm}XH+!w2QH6U^NrdH)j=gXIyIDmD2 z!e_p+yAu3e-cb5lk1=*ftKijRc2jG?&Ue0=4@oK$C(3!(IVogNA%F0Y zC6?|lsTW*>ud!+M-kFaS2})-*$U1CMdpUTYH^v+mtr=NnlXGnf^(Tpk%Of6<)hrO)We%<-Rhe6@dJ_y0+-vLq$mcP-nO#^yBiYg{R*=K;rYb!7cH7LtN&w z_7p>R^}~{pk>L+wwh;h^fPh!^cgvZr-(&Kg$`v-l;vSmwA(qK|KSma*kCLRDL}wm9 zQHO4AoE0MadXu>Om@VFZ>rm<&Tv7&H=OZ-l2%C2@SrevBJJHMiMI%El@Y1=?zJXp@ z;I9g*N^xQxe-U1I-ErTO+FqR6exkPw{g{3wL_VYnx(T-Dt6mM8ND7pcXdj+a$MThB zX8l;IvFe<1X{3Q>#p?KXr%w4-P?7rtdnOawCys7bqRnnZPz??SnXgppnkt9aOD^QY zq{5x2@M7vg;YGv}+aNh^=w?`=#AHMX!X3w!G1-+N>w6VV+?@#d4m=B_!uqkNcg>NK zBDoUq)f4Mi#DJ6En1nwCgh*CQjT>Dq+xGt2&Q4^JCW6N!xGMkGvuE|TFwj-Sc6_b# zHX3vGWQ>Jv{v^cISHWnp>7bBGojqJk^(;7?%{!VTENXEvngSrRT=UT6g5_>f?{U{! zqqAa6vEv=IHr49(!UNE7wljT}WoQDMzdA**9e<*A4E;D5HLF823mVw(3OzmHVMT%G#Z>AIL zi>FnFuQezD;+0b?J`+5SjEqdUf1pc0S*cpJHvl4B!bLRY`XkG*Re|qk=f2gAqJ?L8 z6}knF8!UJ#_Lu7q5F+gNc`}YX2zeK6=c|PpTwgi0?0b z>D#-Mv^6w5dv`WkK?PI+a!v2)fmUDE*}Wl-X=%t)d4rkG z+E`S0iB-#;s5I?0DobnUAJtQg7wSaDUtQFFZ&1v5vg8&Wws2)(oh`RHzsJrHr@#v) z^Yv@b{cmDN8TO5ZLqqPnMSKb(e=h7AoAvDKcysumfVPg6KXNlmGXl>KX)1=drjsM1 ziP0}-i+q*K8gkL?`e`B=m>M}|=CPz^3Ig;~5Fu{1%1?Yg`KUg<%W_u?e;|J@u>T-5 zw(};wvGE}}MHHy+7K+J7;64hp$Be-@)$_z6^&hc}$hj1D^Y56BiC-{BG@ zKb`joUX3#afm8rlHn+b)%A3;|B2`o&zxcl1y7r6C3*CrH^C-D~hu@DrRUDxr=4gg+iuzxQAN!A^R=BJ^q z>y8G)8yjU@7MDUSQ; zLCb{rNlG^nZupf1kEq>g;QD3NLc0@3wJMbJ9?g$lrNPLdjoM{d@_mv)R@8W`y5x%`oTdJa!$>kM2(^TjuS!OvrhF`xDHBPs0 zyvaUW-4^gqG5m&9QL>+H-tx68FI31H`uQZz7!Sk`+gf#PAm#S5^;;<0qZ0$Z3@CbS z|DAEMVi%`P5VN>{=NScJ@7pBb)f96n4hAIr%ta0l(DkIdr*H5EE+;g!R!&u))`~F- zc|@65A-(8k9&I0dwC}!-@8SEy%=H^*$(NCl(S3hOet%^ib+;f#)T8<64 zrtq_i=A@Q^Mb$)KR%{XmPVf9TDIT@2?mTh)qh>3iXAnvjQgfDOA`ok?4rO{9HKB*;ASVx?{S;YZtpv#sJ zevu8|@23G79%pCES0q|Z>l^H|Zr$n8_S;=#OTK&!mu~A=LeE8A8va+D=IV{pR2CbsOxPCXorSKhdG zb^fUS^UYH9PvMvciS4=?M* zDFIXX4XO3~8z{PH=AQL?sfwW@>{9>*B-67y*MdrqyTjtp-wG@kj+2_fN6L1U@i%v~ zr+xfGiWBlx;!OWHalN7ZKepwrooKuMl_KXc4aL6pLW&3Rdw5JYG(a{);GC}JC{^BH8q z!YA!M^R$!9&s2#s6}UgKK=Jcz1j7;^AVj+4^F;f`vN!!kfYl2`DthoKo&O>Y1(hqg zhmS^KrxBBP9^SC}-|xK$ym%lav~sL<#XE`vwIE64ul3rfy{lu@QYz*yKd!P=wIp>( z;AWMT26VXo-6yr&BkWi}W`9q@F!b55I4rrwmju?dP# zNe-A)&4ziWJnmBNCr59TBsv0Lm3R$_)HALm`;dqhPygI#0xHbXN&v+(TUaBa)L=Gr$&z9U_9P(JDewFuHp&_uTPn)FJKUpT`Kej8Wd@ zsbU^o>qs=m=X>(EZ@MVPXZM>LQ${6UEF)Q?nZ5A*6Xq7fd3jXfcjRD<%#NO}F2Cut z2EDlVx#1_|^XL4h<5MlEiL19Pna`@I)EQ)lg!3_CJ0qKKcG2rY zoasXNnHw(o?%K0Lul3WG8EhsAceOJgA=g=V=Uvz%H&!GC26XSn_0&PZXt*UD1eN}; zJePSVE74T=k%|cN1Fd8>h=4c+ zOfX>>*qY)s0lxqW9n7O|riS?j2M3u)v-XrvTLk&x*#kdlh@=wEOdLyx=?JTS|31|a zu23lgxn$!#@AeLzb4+a0vuzIk%uVJuI;aI zZcLx6;uhhtTrgrzKdQbJlM;s&F1KAG3)2JTukEnI{^rqP?$I|r$0GsLDg8Y%n*t?x z&jm~i<)t(t)KZl2j?wEhYW5KK%C1j{({Tu0CJ#)oiCQBEdQNeFl&#auG2l(8kZn4x z4zeuCtH~{FW&XMIaWx49g4du}7pw1EG5NfdB`vLm?mAzm{CT!G#P*8<(Y9YXOJtLW z5+=_G=3qQG@t6IB;T)yf#;PRL(pQh-xwZ*I6@jP&kRm4@2)%Rc;N^|-+CiJehhYq?uKT!R zCu5rKIGFcb3Cr0dQr~mOW#p9&myCo@ zI8-F>(>kuuFG-~DDGr_1RgbaJ=Z6*oLx0WgnNuo1ki0%vlA)GTkoX9%oMdN;q*>r( zj%jZZgJyati=Eek{;FmLbNAXF%k!1s({%^&B!lSr3=Wg4mQQCRnX>D`-VyIViKKMR zmhS93R0sQP{Yh>l#>^IX~oHdNz{$LW1LZr4@hAVc0iEJfWP+9y+#sU!~jTN-2E=bku2 zu1^GXmYc6_eZF934){|8!t4x%A4jh1yspmg;R!G}>uX z$kZw=6wQwi?YqR!CQavLpTU>1&`$6hgn}bU2^w7Qt z%)tzB?otAIx7G*7NZNpQNvJ2-8v>?JH|OZCuCCC<^Rr1W zBgpjez_K#af2w8sECgVtbz6Y)(qqrF;z5D*xI%KvYo*KMA{(EMuzELQCK24Y?$=IU zpu6h~TxzO;@7wV&3wxm+%QxBpnmf`?04Pi=QQyAxwhb!LBWzr?=DLb8J+3nUsD`|_ z@cgErOu0$Epv4Vod;}*tx!G&Ma0+ z+-f44RAO*9=y^stj;kiGD15MrAkYMaSI$ub&iKYE+q|q+Lk9Sw0;jX^u4=**C=y>E zD>WM|_90|WSxolQKs!mmbhrUpb^#Ug-wSS;wUjaSiBwAg1yq?G!Qu9XSFKxXxzbihK?PEk;5~=C?XTb3Kh>jtneO@VjZ>;dW76f0L1~WWGXV!Tj z^a!_nkD_q9DA!M+#P2{*3lB1n!wY<)vTSnUK#|l3H#27BJakmoe;&~Bl`}z4Z%F$E zmVYAI?!3f4G2#2*N)oX;pP@7>LJ^m}z~afr0qrO7tlj#>Kf5r#2uzlw42M5{JH3}- z4O*?NZJ+6FZ(Zh-{YyBY{E_WruMnb!eD7T%7j)U$?pF5K-l`UBkg@!$?gyW@d@4Iv zj9?(u{7EMdZZR6|_lX_k zH8H-wmp&P!otBm+vy+G=Y4tDJkfNUBlMzBmZzYBxh_3qP?*f3X6PXK|PyWR4~ zs(eD_Qxq0tJXEagEDXFg95k7wous9KZsBm>98a9kND@e5XI0(?h|>4+$Y)!pf~NfU z575^1!iMOi@QlJs%R6kbi?_=B&#Q*kKCMZ3=irzTT=tj1+;7P`(tN{O$%wQRB zN-fk}%z0rziQ=~BgJKDQgUURBXs{ZOp7CfQzVE~nKAGv9*n&tnJT3?RO#xol_}uSt zEF0tPw3T}!c+k&7UcAf~J$0m=E_h2HBTeoB_#+jB;Amy_joZQzpF*xSE!4p=%--ME zEh8KUJ*zB}2M1mxsAT$6DFBs8JAKZgp@@6&Z-CKyX9-Y}!^qF%5(a`Uk5%M)l zkR#asld865MoohoHcSh*&9AfeCIcoiiR7itYo>qP{WH5%?{2XSeVazrPyfIj;<~(9 zP>ApNKY8W@)t%mJQ3+p?>n}n?%H@TP9bP_W!dK#MoWuosg|7GDU)L}jeL_Zmr!8kK z&LG61iR>D`pE)F7pqK_(d5Iky>IVh538kf__fHPcpwWr3wA@^L&@j~1)6+IJl65dn zSHz{M1iJH>B*mzK4bvd@GQt>wLkc56Tefl*Mg*rm4)Wr+q){)@?(wQNU;V|j*>#ea zJ^%an{zE)>uv?!*K#&{;1Ob)jpY@t%7v(nn%ra0r(uO-*)L}_|HGhy@1`XCsBs)!Z zFMKILY{_k(or#zCG7qzXeys;?loDvQ@xB4dd-cm_7@|3!1|*|=xFlcH62kh7A!cQ# zqu5rISE%-#+~2&kAsAa6;+(J;20TJi(DbQ**o`l<;*goa3sVOByuH z3|lp|Qw9g4xjpSwYF}~qW!HloSJ}yFkR$a+2^zs(?Np&Ws>R3qpijB1nBSu`;#0S& z@!{+IL;->%U(Zshu!|l?aBtKLTUYpM7%^(_Y8JC9>E~z%OEL8C;h|yd?X_1Tuw}@tba__cv1}-Kf0?)u!>ON|sr9 zYXY-}0mzGGq@Hc(tb`AHbjdMF$lh~LWy%tkt^^oIWx>1NSgD|+S?PM;Z?zo7^o%}4 zE-tf0*1lT%p$84&nfu|fyY(QcBC0@>>+_YbbnPrwOt$!q&|Y{F`5j+-s~;Bns*>Ce zaw}2}!tbS+RK{6kGA(M4^Dl}SZG4P;P{HRZKc615u647%kC)I52#abTrmFrjBFczh z64#u6ud%+a1P{atQIuha44@BUsAL~8`&P0S0TS9U7!FTRExyznoNEZe*1T2O`0+2{Q>Nq?1KzM_;UVuSe}iXZ7w z^YhvwE+T~Q`KqQ~w|u_7TPr{oqX|w)cITJ!lLR-CT_0a}HL)m!MLbW1t?_f_3Nq63 zD^xAaQ>LWn-Zgv|7Vuxrxz2u9di9pM1hasflwEr|F zMR~bDLuhBc`!Ox3lcI}PS0*73fHNxG0lWBy=p+2&4MvL9QM${!W|r1&ZJ5~j z$HFxZL~i(OvEYDM%(K$yZCO+0aZ1&FGnb~JIbF(v#y_{(64aNF;M)0Cn{(h9g^r`Q5!W93|+0lXnUhPIP z2<32ITk*(_c>d2kcIh|m3);^VUrXiDNtb>ItJkp;k+@5_e_hxzjLkr-QL_T`Qb&hN zqpXil=PIEc%ntsE*tE&VoGxMIJ-2Bwc^?kAP$C%LXU$pV@2T{ZKHUbPbBC|1huL0E zT*f3*V=O)MBQkvKT6))$JUG$`1pFI?FBSlfc0B{bv#ymMkI6g$c?i&D|4a8iZez%4X2Z_W02zd4TrWBuK%sbHwrp|MVM2Vx9r8{;szM>?h@6N$Dr7zx=76F6xHrFz5SFf zSdPL?4i=1D7uPocpR20G08i%n(2_IFztw#I>_?NVDk*wrFN!W4HXpZS=M6rFI$)S4wF zDII)(t}6bDn$uAFyiAjc<6KGI9rV^~FkKvaiH^QmDS_uP zMtgcc1aRC-YbDg|E_d?o@4v~;3Xe4}J91jlFI`{K41RJG>$Tq8EiGjXQN`Sz2 zDIs$5K0Nl2p)lmaA$ykLRjiAPOUK@7ZrWYS|I(8pmq)Z-^`P|aTs*CWGv|VP5H;7I z7$hnE@kJ9`NwI2BxwnQ;i_JA%iAf3db3|>V#putsYIQqQ-6{9U>pa~1hlav)a!_ky z-xzimdKJdr8Dy*x)iY&jfMWF(L6)DwqwH4cuXy_N?PZYe@H%_?fv4}!NctxS*LJom zor+36^h9;6UgL-roMnjDL3y`SjXL^z4BWVUM|J%BrcH@o$}Y>YG)*z2e1-zh?pvT$ zq&Ky#UIpeG8|%d#o42UuZ76wN{jy@6M}Kzftmu1w2R9PD@=5p}j$XQPPd98u^aU!{ z0%P-OB`84yzY?+>_D1iJ>*Q&82TvZyTU;_gr%um0p|f-pR#}#5FWo(mGB{8xop86#Ed3$?`Un-^H}`0wjKjj!hEeun{UIqu;1;x$%R1#54}Xfa0?-8bF1Y+v<48uCrm6c2NKH!e}8ATH)&16>+p`7tKvv*!H+Wl}g_GXJXzN62gx##D#nFV4rW*if@&(ro_(Cj_f z@NmYSE(xoa%L#*hX@-w23z@2lwU(g4DBs% z>68XT>CfR{Z54N|=gQR?$-cG!iWRsw)|VZ(gLj|ZwXvn6By3ti<+%q+wsq(x#mw@0 zat5s&Z}AVK5O*u8j##SF_?YY&SQ(;;q9%YEHv@agi~L?J|Q-`!gH59 z#_`^#fvn1CV%;SYnEwb;COu+E{~9FAd*jL*NlJIs<3D=7mLsqkZ?57dO3Me81+Hqu z0`OWpMBoOO2us`Jui(sepY452-`mpXjUUouVLD_idvbU>rGOj8f71db{LS;~X zyqPg3Sz4!NVrI;!d$fevl(qF!k`QoMe5x|=`!zvqV|pVP&1wmi|3p}(Hlv$FPyn(5 zogKeN|BB<(IOANVLND$cBF1}dwmXK%)B8c53h1RCsC)xBYT_^>tEz9_X0B+{gGY7f zoOynuExOKI!2Z5pZwpRiDCepu{iPB^M5$e-sn!K&!|1yQfxJ>#8@nr$6Uo(f4Ovy9 z>Len52Q6>7kXh;0pcXfS9q2X;Ye>n$I=y8cn*s|?lh_wN&tDFGzzP9c&s?_tNb_4LV7n(BakDbOi+ZM*EEY?7wa1womlw?#E^ zl!r3dgbH%5l`` zl7u)@6L_NEnul950d4mp(ac~vY^tLgE}>`(JTo+XrQJ2vp;_lKp6^&PG1Clsz-nmc zkYH~5xrWohkaWXul_B}oKX^7*mdE9wkFwsL%%xS!YU}-OY$#`-u6ky9H0V>3lZUbL z{N*TcyM3OsKC+|AC8Vfa$4w5z{)TBi9NYmlVq$T<;Cj%$tD&9}9`nN7V4{yuYxMBc zk!Mg$PGxu-PD#Lz>Kes37!)@X-*Fb4{0UZRFaPRMEtlnts@i6;aBeQw^ zQQk_UWa7PQc;hWen2)Ipn;2OxqScB(H-9l=TW&d@39T=eqn*-A&|E!?IPT+GKY00h zUlm<^36wDYU?Xf*yN=;$T$9GKiGPdG3jabY8?q<0`}siq|J~~Ca$F-08;{}Fg7(^0{U!J zoLUU-b=y{Q+R`=V*vU*w91WFX#?Kf`@S;_n&icCTdP#QHx9dE)te7E8T>Y=~EmLJF zs?`-T!(0u_W zuJZO%_AHN3;|C+vBGPk*{*j8r-!&BRYA@`3#v@0&^b7oz`*jRkkXs#}&36{Gh-Aq? z&12i0avj&HT@URiiit9P@osOoJ3l$`h8eK)-HkGPZ> z&yua6)8KD%u>+k{P4NjYe^~w(R@F2?ziwX{!n-*B;2_2Ytb9WY{c{)v`Xu$(*KzKU z#dH3Tf20yeVQHgLg?af})1gw>8aiFufFvK$P0Y@$qOv3mtK6{*q^0!FIj_uiBp~q9M{WDunB|h-ZNGiJA19yMZgr9aIN+NjtLmTm?Qe zJcxrez?+0+)jq8LV~QRx08DdE_mN_IyOtIszh`$?ZY{Y1SXdp+eB_R{8<>~^UY-~)jtnX2hQICWNRYqXHUct*5u z3$H2T0`*059|Vz0l|pL)MLZ`Vg=2q#ypbHYfwYRM;uQNf-}kkb=*^X{49%4U_adJahFoG-&B+^Jf{_YuL# z(3TmDge}IEB8KeWCK9>1;Cipbx+se`_s7U{^N#ybH9i0ZQ?8I)(A7H0ulf>s1Ge%Q zwUT@=8-3jL-$qYf0lfIr7}a9sdr}byoxzo9>j9M;au;;ZKCe*ez6y`{u>_g7z~d6~ z1ccjsjGOca_mqGM9g3FdE$6ImK_EPB*~jxg!f|hxt(|_O_Pg`}TuF&Y+d3XMu4k;t zAX(&38{7MMAm<}s_6NcJwS!dt{W+kIlguy5Z{8V6*Dq_BZz7EhZ?-DsbJju=ou$_# z3;RrBaw3d)dgK6Cd3QD05K63j9Haym!v#$?A5_5E-$Rtx9;&3s#Ly5Rf(~3%Q%N|vi zj6%chfBiQuN7`DRLO?}YZ+ZG`phx@74kKK zHoCN=q@;Z8X}G+kCZ?ZmC}S{(Xz; z%9i1=(Dn#c@ge=poLSOensFJWX8x9qZ(Yr-nS|Lg%_JMce1 zF&TJ9*rtr#nQu8(eo*X#K%>nv*qYH>3&QN_fGsaab2SjL%uYd1h!g-su48AdAfCFzj<68%W-#iNtj}RRF$>&9_`3Ue%V+ z{suPJ#IGg+CrzqYJV1XebOY-H8K+pxvSU74R!N2S8z8ZMzy@jac_6;iUyW1mr({HaMcVyXtDu^#_vxY0L+yvMwM*s#PCgh7oA+jeCO6X69 zqF8pIF=CfquIs3l9tCU&vo^al`$N%0sT|-4CjV|Sr2m^JWQ$F)?$ktLj-zOnuFRz+ z*OvJ&qs>h$xdc_Dnie%gW_snj!i;u}=j?0Nh`Cy)_b$RVNKaV0Fq3d&_FK52+O`q*(Fa7*iO|NplVBL7*et9fM~4p3knUbZ@1-!+N%XI?SBO5$HgLOhHfBfo5@o65TZuLE=>k0J3c(WICwo2R1fFQdt~s)X77V7(U%{D$Q6 z+|&%fAT?(oTx#0{VG3H-u$84sZ1o87{O-rvH$Z_f+(dALIt(=V{guY*06+F%p+eop zjjoN;*hz?50`=q>zBljHZt52Eq-W8N{X;;eBhsH7G4KRbz+H?JTzu%ReHga$?@`OK zpu#4U)BeI;>%thfGzFaw5L4OQ)oDL=BAkuV(PK${z2WiX2(y!1MIt+VW>j5XD0PGn zIf%Q`>4Hn)Tp|BuN9pO%!0=J6) dy_^@*OTF8EiY6^@f(+n*y7FUmv7$xj{{hE;gn9q~ literal 0 HcmV?d00001 diff --git a/web_ups/src/app/globals.css b/web_ups/src/app/globals.css new file mode 100644 index 0000000..9431c36 --- /dev/null +++ b/web_ups/src/app/globals.css @@ -0,0 +1,53 @@ +:root { + --font-family: var(--font-inter), system-ui, sans-serif; + --font-geist-mono: 'Geist Mono', 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace; + + --bg-main: #f9fafb; + --bg-panel: #ffffff; + + --text-primary: #111827; + --text-secondary: #6b7280; + --text-muted: #9ca3af; + + --border-color: #f3f4f6; + + --color-primary: #3b82f6; + --color-primary-light: #eff6ff; + --color-success: #10b981; + --color-success-light: #ecfdf5; + --color-warning: #f59e0b; + --color-warning-light: #fffbeb; + --color-purple: #8b5cf6; + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-card: 0 10px 15px -3px rgba(0, 0, 0, 0.02), 0 4px 6px -2px rgba(0, 0, 0, 0.01); + + --border-radius-sm: 0.375rem; + --border-radius-md: 0.5rem; + --border-radius-lg: 1rem; +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; + font-family: var(--font-family); + background-color: var(--bg-main); + color: var(--text-primary); +} + +a { + color: inherit; + text-decoration: none; +} + +ul { + list-style: none; +} diff --git a/web_ups/src/app/layout.tsx b/web_ups/src/app/layout.tsx new file mode 100644 index 0000000..825a6d3 --- /dev/null +++ b/web_ups/src/app/layout.tsx @@ -0,0 +1,25 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ + variable: "--font-inter", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "IoT DC UPS Dashboard", + description: "Monitor your UPS status real-time", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/web_ups/src/app/login/page.tsx b/web_ups/src/app/login/page.tsx new file mode 100644 index 0000000..23fc270 --- /dev/null +++ b/web_ups/src/app/login/page.tsx @@ -0,0 +1,5 @@ +import Login from '@/components/Login'; + +export default function LoginPage() { + return ; +} diff --git a/web_ups/src/app/page.module.css b/web_ups/src/app/page.module.css new file mode 100644 index 0000000..43f4b90 --- /dev/null +++ b/web_ups/src/app/page.module.css @@ -0,0 +1,78 @@ +.layout { + display: flex; + min-height: 100vh; +} + +.mainContent { + flex: 1; + margin-left: 260px; /* Sidebar width */ + padding: 2rem 2.5rem; + background-color: var(--bg-main); + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.gridContent { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 1.5rem; + flex: 1; + align-items: stretch; +} + +/* Responsive adjustments */ +@media (max-width: 1200px) { + .gridContent { + grid-template-columns: 1fr; + } +} + +.mobileHeader { + display: none; +} + +@media (max-width: 900px) { + .mainContent { + margin-left: 0; + padding: 1.25rem 1rem; + } + + .mobileHeader { + display: flex; + align-items: center; + justify-content: space-between; + background-color: var(--bg-panel); + padding: 0.75rem 1.25rem; + border-radius: var(--border-radius-lg); + border: 1px solid var(--border-color); + margin-bottom: 1.5rem; + box-shadow: var(--shadow-sm); + } + + .mobileTitle { + font-weight: 700; + font-size: 1.15rem; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 0.5rem; + } + + .menuBtn { + display: flex; + align-items: center; + justify-content: center; + padding: 0.5rem; + background: var(--bg-main); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + color: var(--text-primary); + cursor: pointer; + transition: all 0.2s ease; + } + + .menuBtn:active { + background-color: var(--border-color); + } +} diff --git a/web_ups/src/app/page.tsx b/web_ups/src/app/page.tsx new file mode 100644 index 0000000..6224479 --- /dev/null +++ b/web_ups/src/app/page.tsx @@ -0,0 +1,314 @@ +"use client"; + +import React, { useEffect, useState, useRef, useCallback } from 'react'; +import mqtt from 'mqtt'; +import { Menu, Zap } from 'lucide-react'; +import Sidebar from '@/components/Sidebar'; +import Header from '@/components/Header'; +import StatCards from '@/components/StatCards'; +import dynamic from 'next/dynamic'; +const VoltageChart = dynamic(() => import('@/components/VoltageChart'), { ssr: false }); +const HistoryPage = dynamic(() => import('@/components/HistoryPage'), { ssr: false }); +import EventLog from '@/components/EventLog'; +import EventLogsPage from '@/components/EventLogsPage'; +import SettingsPanel from '@/components/SettingsPanel'; +import Login from '@/components/Login'; +import styles from './page.module.css'; +import { insertHistoryPoint, insertEvent, fetchHistory, fetchEvents } from '@/lib/db'; +import type { HistoryRow, EventRow } from '@/lib/db'; + +export interface UpsData { + success: number; + on: number; + state: number; + v: string; + batt: number; + battP: number; + line: number; + charging: number; + chgError: number; + uptime?: number; + auto?: number; + battBlink?: number; + battCritical?: number; + chargeLv?: number; + battMax?: number; + timedShutdown?: number; + shutdownRemain?: number; + shutdownSuggestMode?: number; + shutdownSuggest?: number; + shutLastTimer?: number; + chgMode?: number; + chgOVP?: number; + chgReducer?: number; + chgFullTrig?: number; + chgOFC?: number; +} + +export interface UpsEvent { + id: string; + message: string; + time: string; + severity?: 'info' | 'success' | 'warning' | 'error'; +} + +// Classify event severity based on message content +function classifySeverity(msg: string): 'info' | 'success' | 'warning' | 'error' { + const lower = msg.toLowerCase(); + if (lower.includes('error') || lower.includes('fail') || lower.includes('disconnect') || lower.includes('offline')) return 'error'; + if (lower.includes('warn') || lower.includes('critical') || lower.includes('low')) return 'warning'; + if (lower.includes('connect') || lower.includes('online') || lower.includes('subscrib')) return 'success'; + return 'info'; +} + +// Convert DB history row to chart point +function rowToChartPoint(row: HistoryRow) { + return { + time: row.created_at + ? new Date(row.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) + : '--:--:--', + batteryPct: row.battery_pct, + vBatt: row.v_batt, + }; +} + +// Convert DB event row to UpsEvent +function rowToEvent(row: EventRow): UpsEvent { + return { + id: String(row.id ?? crypto.randomUUID()), + message: row.message, + time: row.created_at + ? new Date(row.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) + : '--:--:--', + severity: row.severity, + }; +} + +export default function Dashboard() { + const [activeTab, setActiveTab] = useState<'dashboard' | 'history' | 'logs' | 'settings'>('dashboard'); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const [upsData, setUpsData] = useState(null); + const [mqttStatus, setMqttStatus] = useState<'Connecting...' | 'Online' | 'Offline'>('Connecting...'); + const [events, setEvents] = useState([]); + const [chartData, setChartData] = useState<{ time: string; batteryPct: number; vBatt: number }[]>([]); + const [liveChartData, setLiveChartData] = useState<{ time: string; batteryPct: number; vBatt: number }[]>([]); + const [dbLoaded, setDbLoaded] = useState(false); + const mqttClientRef = useRef(null); + const prevUpsDataRef = useRef(null); + const lastMqttMessageRef = useRef(0); + + // Throttle: only save to Supabase every 30 seconds + const lastSaveRef = useRef(0); + + const addEvent = useCallback((msg: string, persist = true) => { + const severity = classifySeverity(msg); + const ev: UpsEvent = { + id: crypto.randomUUID(), + message: msg, + time: new Date().toLocaleTimeString(), + severity, + }; + setEvents(prev => [ev, ...prev].slice(0, 200)); + + // Save to Supabase (for non-trivial messages) + if (persist) { + insertEvent({ message: msg, severity } as Partial & Pick); + } + }, []); + + const clearEvents = async () => { + const { clearAllEvents } = await import('@/lib/db'); + await clearAllEvents(); + setEvents([]); + }; + + // Auto-clear data if no MQTT message received for 10 seconds (device offline) + useEffect(() => { + const interval = setInterval(() => { + if (lastMqttMessageRef.current > 0) { + const elapsed = (Date.now() - lastMqttMessageRef.current) / 1000; + if (elapsed >= 10 && prevUpsDataRef.current !== null) { + setUpsData(null); + setLiveChartData([]); + prevUpsDataRef.current = null; + addEvent('IoT Device went Offline (No telemetry signal for >10s)', true); + } + } + }, 1000); + return () => clearInterval(interval); + }, [addEvent]); + + // Initial data load on mount + useEffect(() => { + const loadInitialData = async () => { + try { + const [histRows, evRows] = await Promise.all([ + fetchHistory(100), + fetchEvents(200) + ]); + + if (histRows.length > 0) { + const points = histRows.map(rowToChartPoint); + setChartData(points); + } + if (evRows.length > 0) { + const evs = evRows + .map(rowToEvent) + .filter(ev => + !ev.message.startsWith('Connected to') && + !ev.message.startsWith('Subscribed to') && + !ev.message.startsWith('Connecting to') && + !ev.message.startsWith('Subscription error') && + !ev.message.startsWith('MQTT Connection Offline') + ); + setEvents(evs); + } + setDbLoaded(true); + } catch (err) { + console.warn('[DB] initial load warning:', err); + } + }; + + loadInitialData(); + }, []); + + // MQTT Connection setup + useEffect(() => { + setMqttStatus('Connecting...'); + console.log('Connecting to MQTT Broker (wss://broker.emqx.io:8084/mqtt)...'); + + const client = mqtt.connect('wss://broker.emqx.io:8084/mqtt'); + mqttClientRef.current = client; + + client.on('connect', () => { + setMqttStatus('Online'); + console.log('Connected to MQTT Broker'); + client.subscribe('ups/dev15/state', (err) => { + if (err && err.message !== 'client disconnecting') { + console.warn(`Subscription warning: ${err.message}`); + } + }); + }); + + client.on('message', (topic, message) => { + if (topic === 'ups/dev15/state') { + try { + // Sanitize the string: remove control characters like \n, \t, etc. that might break JSON parse + const rawString = message.toString().replace(/[\x00-\x1F\x7F-\x9F]/g, ""); + const data: UpsData = JSON.parse(rawString); + lastMqttMessageRef.current = Date.now(); + setUpsData(data); + + // Track state changes to generate event logs + const prevData = prevUpsDataRef.current; + if (prevData) { + // Power Source change + if (prevData.line !== data.line) { + if (data.line > 0) addEvent('AC Power Restored (Line Active)', true); + else addEvent('AC Power Lost (Running on Battery)', true); + } + // Output State change + if (prevData.on !== data.on) { + if (data.on === 1) addEvent('UPS Output turned ON', true); + else addEvent('UPS Output turned OFF', true); + } + // Battery Critical + if (prevData.battCritical !== data.battCritical && data.battCritical === 1) { + addEvent('WARNING: Battery level is CRITICAL', true); + } + // Charging state + if (prevData.charging !== data.charging) { + if (data.charging === 0 && data.battP >= 95) addEvent('Battery Fully Charged', true); + else if (data.charging === 1) addEvent('Battery is now charging', false); + } + // Shutdown Timer + if ((prevData.shutdownRemain ?? 0) === 0 && (data.shutdownRemain ?? 0) > 0) { + addEvent(`Shutdown timer started (${data.shutdownRemain}s)`, true); + } else if ((prevData.shutdownRemain ?? 0) > 0 && (data.shutdownRemain ?? 0) === 0 && data.on === 1) { + addEvent('Shutdown timer cancelled', true); + } + } + prevUpsDataRef.current = data; + + const newPoint = { + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }), + batteryPct: data.battP, + vBatt: parseFloat(data.v), + }; + + setChartData(prev => [...prev, newPoint].slice(-100)); + setLiveChartData(prev => [...prev, newPoint].slice(-15)); + + // Throttled save to Supabase (every 30 seconds) + const now = Date.now(); + if (now - lastSaveRef.current >= 30000) { + lastSaveRef.current = now; + insertHistoryPoint({ + v_batt: parseFloat(data.v), + battery_pct: data.battP, + line_state: data.line, + power_on: data.on === 1, + }); + } + + } catch (e) { + // Changed to console.warn to prevent Next.js from throwing a red error overlay in dev mode + console.warn('JSON parse error (likely malformed MQTT data):', e); + } + } + }); + + client.on('offline', () => { + setMqttStatus('Offline'); + console.log('MQTT Connection Offline'); + }); + + return () => { + client.end(); + }; + }, [dbLoaded, addEvent]); + + return ( +
+ setIsSidebarOpen(false)} /> +
+
+
+ + UPS Monitor +
+ +
+ {activeTab === 'dashboard' &&
} + + {activeTab === 'dashboard' && ( + <> + +
+ + !ev.message.startsWith('Connected to') && !ev.message.startsWith('Subscribed to') && !ev.message.startsWith('Connecting to')) : []} /> +
+ + )} + + {activeTab === 'history' && ( + + )} + + {activeTab === 'logs' && } + + {activeTab === 'settings' && { + if (mqttClientRef.current && mqttClientRef.current.connected) { + const payload = JSON.stringify({ cmd, val: String(val) }); + mqttClientRef.current.publish('ups/dev15/cmd', payload); + addEvent(`Sent command: ${cmd}=${val}`, false); + } else { + alert("MQTT is not connected. Cannot send command."); + } + }} />} +
+
+ ); +} diff --git a/web_ups/src/components/EventLog.module.css b/web_ups/src/components/EventLog.module.css new file mode 100644 index 0000000..eb4393a --- /dev/null +++ b/web_ups/src/components/EventLog.module.css @@ -0,0 +1,89 @@ +.logContainer { + background-color: var(--bg-panel); + border-radius: var(--border-radius-lg); + padding: 1.5rem; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); + display: flex; + flex-direction: column; + height: 460px; + max-height: 460px; + overflow: hidden; +} + +.logHeader { + margin-bottom: 1.5rem; +} + +.title { + font-size: 1.125rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 0.25rem; +} + +.subtitle { + font-size: 0.85rem; + color: var(--text-muted); +} + +.logList { + display: flex; + flex: 1; + min-height: 0; + flex-direction: column; + gap: 1.25rem; + overflow-y: auto; + padding-right: 0.5rem; +} + +.logList::-webkit-scrollbar { + width: 4px; +} + +.logList::-webkit-scrollbar-track { + background: var(--bg-main); + border-radius: 2px; +} + +.logList::-webkit-scrollbar-thumb { + background: var(--text-muted); + border-radius: 2px; +} + +.logItem { + display: flex; + gap: 0.75rem; +} + +.bullet { + width: 8px; + height: 8px; + border-radius: 50%; + margin-top: 0.35rem; + flex-shrink: 0; +} + +.bullet.error { background-color: #ef4444; } +.bullet.warning { background-color: var(--color-warning); } +.bullet.success { background-color: var(--color-success); } +.bullet.info { background-color: var(--color-primary); } + +.logContent { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.logMessage { + font-size: 0.9rem; + color: var(--text-primary); + font-weight: 500; + line-height: 1.4; +} + +.logTime { + font-size: 0.75rem; + color: var(--text-muted); + font-family: var(--font-geist-mono), monospace; +} diff --git a/web_ups/src/components/EventLog.tsx b/web_ups/src/components/EventLog.tsx new file mode 100644 index 0000000..d7847ec --- /dev/null +++ b/web_ups/src/components/EventLog.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { UpsEvent } from '../app/page'; +import styles from './EventLog.module.css'; + +interface EventLogProps { + events: UpsEvent[]; +} + +export default function EventLog({ events }: EventLogProps) { + return ( +
+
+

Event Log

+ System events and alerts +
+ +
+ {events.length === 0 ? ( +
+ Belum Ada Aktivitas Real-time + Catatan kejadian akan terbit seketika saat perangkat IoT terhubung dan mendeteksi perubahan daya. +
+ ) : ( + events.map((event) => { + const sev = event.severity || 'info'; + return ( +
+
+
+ {event.message} + {event.time} +
+
+ ); + }) + )} +
+
+ ); +} diff --git a/web_ups/src/components/EventLogsPage.module.css b/web_ups/src/components/EventLogsPage.module.css new file mode 100644 index 0000000..7ff0d0c --- /dev/null +++ b/web_ups/src/components/EventLogsPage.module.css @@ -0,0 +1,245 @@ +.container { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.pageHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.pageTitle { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.02em; + margin-bottom: 0.25rem; +} + +.pageSubtitle { + font-size: 0.85rem; + color: var(--text-muted); +} + +.headerActions { + display: flex; + gap: 0.5rem; +} + +.actionBtn { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.5rem 1rem; + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + font-size: 0.82rem; + font-weight: 500; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.15s ease; + box-shadow: var(--shadow-sm); +} + +.actionBtn:hover:not(:disabled) { + background: var(--color-primary-light); + color: var(--color-primary); + border-color: var(--color-primary); +} + +.actionBtn.danger:hover:not(:disabled) { + background: #fef2f2; + color: #ef4444; + border-color: #ef4444; +} + +.actionBtn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +/* Header Meta (Subtitle & Mini Badges) */ +.headerMeta { + display: flex; + align-items: center; + gap: 0.75rem; + margin-top: 0.25rem; + flex-wrap: wrap; +} + +.metaDivider { + color: var(--border-color); + font-size: 0.8rem; +} + +.severityMini { + display: flex; + align-items: center; + gap: 0.85rem; +} + +.sevItem { + display: flex; + align-items: center; + gap: 0.35rem; + font-size: 0.82rem; + font-weight: 500; + color: var(--text-secondary); +} + +.miniDot { + width: 6px; + height: 6px; + border-radius: 50%; +} + +.miniDot.error { background: #ef4444; } +.miniDot.warning { background: var(--color-warning); } +.miniDot.success { background: var(--color-success); } +.miniDot.info { background: var(--color-primary); } + +/* Log Card */ +.logCard { + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-lg); + padding: 1.25rem; + box-shadow: var(--shadow-sm); + display: flex; + flex-direction: column; + gap: 1rem; +} + +.searchBar { + display: flex; + align-items: center; + gap: 0.6rem; + background: var(--bg-main); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + padding: 0.5rem 0.9rem; +} + +.searchIcon { + color: var(--text-muted); + flex-shrink: 0; +} + +.searchInput { + flex: 1; + border: none; + background: transparent; + font-size: 0.875rem; + color: var(--text-primary); + outline: none; +} + +.resultCount { + font-size: 0.78rem; + color: var(--text-muted); + background: var(--border-color); + padding: 0.15rem 0.5rem; + border-radius: 999px; + white-space: nowrap; +} + +.logList { + display: flex; + flex-direction: column; + gap: 0.5rem; + max-height: 520px; + overflow-y: auto; + padding-right: 0.25rem; +} + +.logList::-webkit-scrollbar { width: 4px; } +.logList::-webkit-scrollbar-track { background: var(--bg-main); border-radius: 2px; } +.logList::-webkit-scrollbar-thumb { background: var(--text-muted); border-radius: 2px; } + +.logItem { + display: flex; + gap: 0.75rem; + padding: 0.5rem 0; + border-bottom: 1px solid var(--border-color); +} + +.logItem:last-child { + border-bottom: none; +} + +.logContent { + display: flex; + flex-direction: column; + gap: 0.25rem; + min-width: 0; +} + +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + margin-top: 0.35rem; +} + +.dot.error { background: #ef4444; } +.dot.warning { background: var(--color-warning); } +.dot.success { background: var(--color-success); } +.dot.info { background: var(--color-primary); } + +.logMessage { + font-size: 0.875rem; + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.logTime { + font-size: 0.75rem; + font-family: var(--font-geist-mono), monospace; + color: var(--text-muted); + white-space: nowrap; + flex-shrink: 0; +} + +/* Empty state */ +.emptyState { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem 1rem; + color: var(--text-muted); + gap: 0.75rem; + font-size: 0.9rem; +} + +.emptyIcon { + color: var(--border-color); +} + +@media (max-width: 768px) { + .pageHeader { + flex-direction: column; + gap: 1rem; + } + + .headerActions { + width: 100%; + justify-content: flex-start; + flex-wrap: wrap; + } + + .logCard { + padding: 1rem; + } + + .searchBar { + padding: 0.4rem 0.7rem; + } +} diff --git a/web_ups/src/components/EventLogsPage.tsx b/web_ups/src/components/EventLogsPage.tsx new file mode 100644 index 0000000..dd5f5cd --- /dev/null +++ b/web_ups/src/components/EventLogsPage.tsx @@ -0,0 +1,108 @@ +import React, { useState, useMemo } from 'react'; +import { Search, Trash2, Download, Info } from 'lucide-react'; +import { UpsEvent } from '../app/page'; +import styles from './EventLogsPage.module.css'; + +interface EventLogsPageProps { + events: UpsEvent[]; + onClear: () => void; +} + +export default function EventLogsPage({ events, onClear }: EventLogsPageProps) { + const [search, setSearch] = useState(''); + + const filtered = useMemo(() => { + if (!search.trim()) return events; + return events.filter(e => + e.message.toLowerCase().includes(search.toLowerCase()) + ); + }, [events, search]); + + const handleExport = () => { + const rows = events.map(e => `"${e.time}","${e.message.replace(/"/g, '""')}"`).join('\n'); + const blob = new Blob([`Time,Message\n${rows}`], { type: 'text/csv' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `ups_events_${new Date().toISOString().slice(0, 10)}.csv`; + a.click(); + URL.revokeObjectURL(url); + }; + + const severityCount = useMemo(() => ({ + error: events.filter(e => (e.severity || 'info') === 'error').length, + warning: events.filter(e => (e.severity || 'info') === 'warning').length, + success: events.filter(e => (e.severity || 'info') === 'success').length, + info: events.filter(e => (e.severity || 'info') === 'info').length, + }), [events]); + + return ( +
+ {/* Page Header */} +
+
+

Event Logs

+
+ {events.length} total events recorded + +
+ {severityCount.error} Errors + {severityCount.warning} Warnings + {severityCount.success} Connections + {severityCount.info} Info +
+
+
+
+ + +
+
+ + {/* Search + Log List */} +
+
+ + setSearch(e.target.value)} + /> + {search && ( + {filtered.length} results + )} +
+ +
+ {filtered.length === 0 ? ( +
+ + {events.length === 0 ? 'No events recorded yet.' : 'No events match your search.'} +
+ ) : ( + filtered.map(event => { + const sev = event.severity || 'info'; + return ( +
+
+
+ {event.message} + {event.time} +
+
+ ); + }) + )} +
+
+
+ ); +} diff --git a/web_ups/src/components/Header.module.css b/web_ups/src/components/Header.module.css new file mode 100644 index 0000000..30bf906 --- /dev/null +++ b/web_ups/src/components/Header.module.css @@ -0,0 +1,118 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.titleArea { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.title { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.02em; +} + +.badge { + display: inline-flex; + align-items: center; + background-color: var(--border-color); + color: var(--text-secondary); + font-size: 0.75rem; + font-weight: 600; + padding: 0.2rem 0.5rem; + border-radius: var(--border-radius-sm); + width: fit-content; + font-family: var(--font-geist-mono), monospace; +} + +.statusArea { + display: flex; + align-items: center; + gap: 1.5rem; + background-color: var(--bg-panel); + padding: 0.5rem 1rem; + border-radius: 2rem; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); +} + +.statusItem { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.85rem; + font-weight: 500; + color: var(--text-secondary); +} + +.statusItem.online { + color: var(--color-success); +} + +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--color-success); + box-shadow: 0 0 0 2px var(--color-success-light); +} + +.uptime { + font-family: var(--font-geist-mono), monospace; + color: var(--text-primary); + font-weight: 600; +} + +.divider { + width: 1px; + height: 16px; + background-color: var(--border-color); +} + +.themeToggle { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-muted); +} + +.toggleSwitch { + width: 36px; + height: 20px; + background-color: var(--border-color); + border-radius: 10px; + position: relative; + cursor: pointer; +} + +.toggleKnob { + width: 16px; + height: 16px; + background-color: white; + border-radius: 50%; + position: absolute; + top: 2px; + left: 2px; + box-shadow: var(--shadow-sm); +} + +@media (max-width: 768px) { + .header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + margin-bottom: 1.5rem; + } + + .statusArea { + width: 100%; + justify-content: space-around; + padding: 0.65rem 1rem; + } +} diff --git a/web_ups/src/components/Header.tsx b/web_ups/src/components/Header.tsx new file mode 100644 index 0000000..d72f771 --- /dev/null +++ b/web_ups/src/components/Header.tsx @@ -0,0 +1,44 @@ +"use client"; + +import React, { useEffect, useState } from 'react'; +// Icons removed: theme toggle not yet implemented +import styles from './Header.module.css'; + +interface HeaderProps { + mqttStatus: string; + uptimeSeconds?: number; +} + +function formatUptime(seconds: number): string { + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = seconds % 60; + return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; +} + +export default function Header({ mqttStatus, uptimeSeconds = 0 }: HeaderProps) { + return ( +
+
+

IoT DC UPS Dashboard

+
UPS_DC_01
+
+ +
+
+
+ {mqttStatus} +
+ +
+ +
+ {formatUptime(uptimeSeconds)} +
+ +
+ +
+
+ ); +} diff --git a/web_ups/src/components/HistoryPage.module.css b/web_ups/src/components/HistoryPage.module.css new file mode 100644 index 0000000..f6f35b9 --- /dev/null +++ b/web_ups/src/components/HistoryPage.module.css @@ -0,0 +1,502 @@ +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.container { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +/* ── Page Header ──────────────────────────────────────────────────────────── */ +.pageHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.pageTitle { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.02em; + margin-bottom: 0.25rem; +} + +.pageSubtitle { + font-size: 0.85rem; + color: var(--text-muted); +} + +.headerActions { + display: flex; + gap: 0.5rem; +} + +/* ── Filter Bar ───────────────────────────────────────────────────────────── */ +.filterBar { + display: flex; + align-items: center; + justify-content: space-between; + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-lg); + padding: 0.4rem 0.75rem; + box-shadow: var(--shadow-sm); + gap: 0.5rem; + flex-wrap: wrap; + width: fit-content; +} + +.filterGroup { + display: flex; + align-items: center; + gap: 0.35rem; + flex-wrap: wrap; +} + +.filterIcon { + color: var(--text-muted); + margin-right: 0.25rem; + flex-shrink: 0; +} + +.filterBtn { + padding: 0.35rem 0.85rem; + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + font-size: 0.82rem; + font-weight: 500; + cursor: pointer; + background: transparent; + color: var(--text-secondary); + transition: all 0.15s ease; + white-space: nowrap; +} + +.filterBtn:hover:not(:disabled) { + background: var(--color-primary-light); + color: var(--color-primary); + border-color: var(--color-primary); +} + +.filterBtn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.filterActive { + background: var(--color-primary) !important; + color: white !important; + border-color: var(--color-primary) !important; +} + +.filterDivider { + width: 1px; + height: 20px; + background: var(--border-color); + margin: 0 0.25rem; +} + +.loadingBadge { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.78rem; + font-weight: 500; + color: var(--color-primary); + background: var(--color-primary-light); + padding: 0.3rem 0.75rem; + border-radius: 999px; +} + +.dbBadge { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.78rem; + font-weight: 500; + color: var(--color-success); + background: var(--color-success-light); + padding: 0.3rem 0.75rem; + border-radius: 999px; + border: 1px solid #a7f3d0; + white-space: nowrap; +} + +/* ── Custom Date Picker ───────────────────────────────────────────────────── */ +.customDateBar { + display: flex; + align-items: center; + gap: 0.75rem; + background: var(--bg-panel); + border: 1px solid var(--color-primary); + border-radius: var(--border-radius-lg); + padding: 0.75rem 1.25rem; + box-shadow: 0 0 0 3px var(--color-primary-light); + flex-wrap: wrap; +} + +.customLabel { + font-size: 0.82rem; + font-weight: 600; + color: var(--text-secondary); +} + +.dateInput { + padding: 0.4rem 0.75rem; + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + font-size: 0.85rem; + color: var(--text-primary); + background: var(--bg-main); + outline: none; + cursor: pointer; + transition: border-color 0.15s ease; +} + +.dateInput:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px var(--color-primary-light); +} + +.applyBtn { + padding: 0.4rem 1.1rem; + background: var(--color-primary); + color: white; + border: none; + border-radius: var(--border-radius-md); + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + transition: opacity 0.15s ease; +} + +.applyBtn:hover:not(:disabled) { + opacity: 0.85; +} + +.applyBtn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.exportBtn { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.5rem 1rem; + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + font-size: 0.85rem; + font-weight: 500; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.15s ease; + box-shadow: var(--shadow-sm); +} + +.exportBtn:hover:not(:disabled) { + background: var(--color-primary-light); + color: var(--color-primary); + border-color: var(--color-primary); +} + +.exportBtn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +/* Summary Grid */ +.summaryGrid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 1rem; +} + +@media (max-width: 1100px) { + .summaryGrid { + grid-template-columns: repeat(3, 1fr); + } +} + +.summaryCard { + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-lg); + padding: 1.25rem 1.5rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + box-shadow: var(--shadow-sm); +} + +.summaryLabel { + font-size: 0.78rem; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.summaryValue { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 0.25rem; +} + +.summaryValue.trendUp { color: var(--color-success); } +.summaryValue.trendDown { color: #ef4444; } + +.unit { + font-size: 0.9rem; + font-weight: 500; + color: var(--text-muted); + margin-left: 2px; +} + +/* Chart Card */ +.chartCard { + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-lg); + padding: 1.5rem; + box-shadow: var(--shadow-sm); +} + +.chartControls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + flex-wrap: wrap; + gap: 0.75rem; +} + +.modeToggle { + display: flex; + background: var(--bg-main); + border-radius: var(--border-radius-md); + padding: 3px; + gap: 2px; + border: 1px solid var(--border-color); +} + +.modeBtn { + padding: 0.35rem 0.9rem; + border: none; + border-radius: calc(var(--border-radius-md) - 2px); + font-size: 0.82rem; + font-weight: 500; + cursor: pointer; + background: transparent; + color: var(--text-muted); + transition: all 0.15s ease; +} + +.modeBtn.active { + background: var(--bg-panel); + color: var(--text-primary); + box-shadow: var(--shadow-sm); +} + +.rangeFilter { + display: flex; + align-items: center; + gap: 0.35rem; + color: var(--text-muted); + font-size: 0.82rem; +} + +.rangeBtn { + padding: 0.3rem 0.7rem; + border: 1px solid var(--border-color); + border-radius: var(--border-radius-sm); + font-size: 0.8rem; + font-weight: 500; + cursor: pointer; + background: transparent; + color: var(--text-secondary); + transition: all 0.15s ease; +} + +.rangeBtn.active { + background: var(--color-primary); + color: white; + border-color: var(--color-primary); +} + +.rangeBtn:not(.active):hover { + background: var(--color-primary-light); + color: var(--color-primary); + border-color: var(--color-primary); +} + +.chartDataCount { + font-size: 0.78rem; + color: var(--text-muted); + background: var(--bg-main); + padding: 0.2rem 0.6rem; + border-radius: 999px; + border: 1px solid var(--border-color); +} + +.emptyChart { + height: 280px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: var(--text-muted); + font-size: 0.9rem; +} + +/* Table Card */ +.tableCard { + background: var(--bg-panel); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-lg); + padding: 1.5rem; + box-shadow: var(--shadow-sm); +} + +.tableTitle { + font-size: 1rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 1rem; +} + +.tableCount { + font-size: 0.8rem; + font-weight: 400; + color: var(--text-muted); +} + +.tableWrapper { + overflow-x: auto; + max-height: 320px; + overflow-y: auto; +} + +.tableWrapper::-webkit-scrollbar { width: 4px; height: 4px; } +.tableWrapper::-webkit-scrollbar-track { background: var(--bg-main); border-radius: 2px; } +.tableWrapper::-webkit-scrollbar-thumb { background: var(--text-muted); border-radius: 2px; } + +.table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} + +.table th { + text-align: left; + padding: 0.6rem 1rem; + font-size: 0.75rem; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.04em; + border-bottom: 1px solid var(--border-color); + position: sticky; + top: 0; + background: var(--bg-panel); + z-index: 1; +} + +.table td { + padding: 0.75rem 1rem; + color: var(--text-primary); + border-bottom: 1px solid var(--border-color); + vertical-align: middle; +} + +.table tr:last-child td { + border-bottom: none; +} + +.table tr:hover td { + background-color: var(--bg-main); +} + +.timeCell { + font-family: var(--font-geist-mono), monospace; + font-size: 0.8rem; + color: var(--text-secondary) !important; +} + +.voltCell { + font-family: var(--font-geist-mono), monospace; + font-weight: 600; +} + +.battCell { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.miniBar { + width: 60px; + height: 6px; + border-radius: 3px; + background: var(--border-color); + overflow: hidden; +} + +.miniFill { + height: 100%; + border-radius: 3px; + transition: width 0.3s ease; +} + +.emptyRow { + text-align: center; + color: var(--text-muted); + padding: 2rem !important; +} + +@media (max-width: 768px) { + .pageHeader { + flex-direction: column; + gap: 1rem; + } + + .summaryGrid { + grid-template-columns: repeat(2, 1fr); + } + + .chartControls { + flex-direction: column; + align-items: stretch; + } + + .modeToggle { + justify-content: center; + } + + .rangeFilter { + justify-content: center; + flex-wrap: wrap; + } + + .customDateBar { + flex-direction: column; + align-items: stretch; + } +} + +@media (max-width: 500px) { + .summaryGrid { + grid-template-columns: 1fr; + } + + .tableCard { + padding: 1rem; + } +} diff --git a/web_ups/src/components/HistoryPage.tsx b/web_ups/src/components/HistoryPage.tsx new file mode 100644 index 0000000..f4af943 --- /dev/null +++ b/web_ups/src/components/HistoryPage.tsx @@ -0,0 +1,375 @@ +"use client"; + +import React, { useState, useMemo, useCallback } from 'react'; +import { + AreaChart, Area, XAxis, YAxis, + CartesianGrid, Tooltip, ResponsiveContainer, Legend +} from 'recharts'; +import { Download, TrendingUp, TrendingDown, Minus, Database, RefreshCw, Calendar } from 'lucide-react'; +import styles from './HistoryPage.module.css'; +import type { HistoryRow } from '@/lib/db'; + +interface ChartPoint { + time: string; + batteryPct: number; + vBatt: number; +} + +interface HistoryPageProps { + data: ChartPoint[]; +} + +type DateFilter = 'today' | 'week' | 'month' | 'custom'; +type ChartMode = 'voltage' | 'battery'; + +// ── Date helpers ────────────────────────────────────────────────────────────── +function startOfDay(d: Date) { + const r = new Date(d); r.setHours(0, 0, 0, 0); return r; +} +function startOfWeek(d: Date) { + const r = new Date(d); + const day = r.getDay(); // 0=Sun + r.setDate(r.getDate() - (day === 0 ? 6 : day - 1)); + r.setHours(0, 0, 0, 0); + return r; +} +function startOfMonth(d: Date) { + return new Date(d.getFullYear(), d.getMonth(), 1, 0, 0, 0, 0); +} +function toLocalDateString(d: Date) { + // returns YYYY-MM-DD in local timezone (for ) + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, '0'); + const dd = String(d.getDate()).padStart(2, '0'); + return `${y}-${m}-${dd}`; +} +function rowToPoint(row: HistoryRow): ChartPoint { + return { + time: row.created_at + ? new Date(row.created_at).toLocaleString([], { + month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', hour12: false + }) + : '--:--', + batteryPct: row.battery_pct ?? 0, + vBatt: row.v_batt ?? 0, + }; +} + +export default function HistoryPage({ data }: HistoryPageProps) { + const today = useMemo(() => new Date(), []); + + const [chartMode, setChartMode] = useState('voltage'); + const [dateFilter, setDateFilter] = useState('today'); + const [customFrom, setCustomFrom] = useState(toLocalDateString(startOfDay(today))); + const [customTo, setCustomTo] = useState(toLocalDateString(today)); + + const [dbData, setDbData] = useState([]); + const [loadingDb, setLoadingDb] = useState(false); + + // ── DB fetch ──────────────────────────────────────────────────────────────── + const loadFromDatabase = useCallback(async (filter: DateFilter, fromStr?: string, toStr?: string) => { + setLoadingDb(true); + try { + const now = new Date(); + let from: Date; + let to: Date = new Date(now.getTime() + 86400000); // tomorrow to be inclusive + + switch (filter) { + case 'today': from = startOfDay(now); break; + case 'week': from = startOfWeek(now); break; + case 'month': from = startOfMonth(now); break; + case 'custom': + from = new Date(fromStr + 'T00:00:00'); + to = new Date(toStr + 'T23:59:59'); + break; + default: from = startOfDay(now); + } + + const { fetchHistoryByRange } = await import('@/lib/db'); + const rows = await fetchHistoryByRange(from.toISOString(), to.toISOString(), 2000); + setDbData(rows.map(rowToPoint)); + } catch (err) { + console.error('[HistoryPage] load from DB error:', err); + } finally { + setLoadingDb(false); + } + }, []); + + // Auto-load Today on mount + React.useEffect(() => { + loadFromDatabase('today'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // ── Active data selection ─────────────────────────────────────────────────── + const activeData: ChartPoint[] = useMemo(() => dbData, [dbData]); + + // ── Summary stats ─────────────────────────────────────────────────────────── + const avgVBatt = activeData.length + ? (activeData.reduce((s, d) => s + d.vBatt, 0) / activeData.length).toFixed(2) : '—'; + const minVBatt = activeData.length + ? Math.min(...activeData.map(d => d.vBatt)).toFixed(2) : '—'; + const maxVBatt = activeData.length + ? Math.max(...activeData.map(d => d.vBatt)).toFixed(2) : '—'; + const avgBattPct = activeData.length + ? Math.round(activeData.reduce((s, d) => s + d.batteryPct, 0) / activeData.length) : 0; + const trend = activeData.length >= 2 + ? activeData[activeData.length - 1].vBatt - activeData[0].vBatt : 0; + + // ── Export ────────────────────────────────────────────────────────────────── + const handleExport = () => { + if (!activeData.length) return; + const headers = 'Time,Battery %,V-Batt\n'; + const rows = activeData.map(d => + `${d.time},${d.batteryPct},${d.vBatt}` + ).join('\n'); + const blob = new Blob([headers + rows], { type: 'text/csv' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `ups_history_${new Date().toISOString().slice(0, 10)}.csv`; + a.click(); + URL.revokeObjectURL(url); + }; + + // ── Filter label for subtitle ─────────────────────────────────────────────── + const filterLabel: Record = { + today: 'Today', + week: 'This Week', + month: 'This Month', + custom: `${customFrom} – ${customTo}`, + }; + + const handleFilterClick = (f: DateFilter) => { + setDateFilter(f); + if (f !== 'custom') { + loadFromDatabase(f); + } + // custom: wait for user to press Apply + }; + + return ( +
+ + {/* ── Page Header ───────────────────────────────────────────────────── */} +
+
+

Voltage History

+ + {activeData.length} data points · {filterLabel[dateFilter]} + +
+
+ +
+
+ + {/* ── Date Filter Bar ───────────────────────────────────────────────── */} +
+
+ + {(['today', 'week', 'month'] as DateFilter[]).map(f => ( + + ))} + +
+ + {/* Loading indicator */} + {loadingDb && ( +
+ + Loading... +
+ )} +
+ + {/* ── Custom Date Picker ────────────────────────────────────────────── */} + {dateFilter === 'custom' && ( +
+ From + setCustomFrom(e.target.value)} + /> + To + setCustomTo(e.target.value)} + /> + +
+ )} + + {/* ── Summary Cards ─────────────────────────────────────────────────── */} +
+
+ Avg V-Batt + {avgVBatt}V +
+
+ Min V-Batt + {minVBatt}V +
+
+ Max V-Batt + {maxVBatt}V +
+
+ Avg Battery + {avgBattPct}% +
+
+ Voltage Trend + 0 ? styles.trendUp : trend < 0 ? styles.trendDown : ''}`}> + {trend > 0 ? : trend < 0 ? : } + {Math.abs(trend).toFixed(2)}V + +
+
+ + {/* ── Chart ─────────────────────────────────────────────────────────── */} +
+
+
+ + +
+ {activeData.length} pts +
+ + {activeData.length === 0 ? ( +
+ + No data found for this period. + +
+ ) : chartMode === 'voltage' ? ( + + + + + + + + + + + + + + + `${v}V`} domain={[0, 16]} dx={-8} /> + [`${(val as number).toFixed(2)} V`, name as string]} + /> + + + + + ) : ( + + + + + + + + + + + `${v}%`} domain={[0, 100]} dx={-8} /> + [`${val as number}%`, 'Battery']} + /> + + + + )} +
+ + {/* ── Data Table ────────────────────────────────────────────────────── */} +
+

Data Log ({activeData.length} rows)

+
+ + + + + + + + + + {[...activeData].reverse().map((row, idx) => ( + + + + + + ))} + {activeData.length === 0 && ( + + )} + +
TimeBattery %V-Batt
{row.time} +
+
+
+
+ {row.batteryPct}% +
+
{row.vBatt.toFixed(2)}V
No data for this period.
+
+
+ +
+ ); +} diff --git a/web_ups/src/components/Login.module.css b/web_ups/src/components/Login.module.css new file mode 100644 index 0000000..33e6a36 --- /dev/null +++ b/web_ups/src/components/Login.module.css @@ -0,0 +1,431 @@ +/* Split-Screen Indonesian SaaS Design (Exact Match) */ +.container { + min-height: 100vh; + display: flex; + background-color: #ffffff; + font-family: var(--font-family), sans-serif; + color: #0f172a; + overflow-x: hidden; +} + +/* Left Column: Clean & Minimalist Login Form */ +.leftColumn { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 3.5rem 4rem; + max-width: 550px; + background-color: #ffffff; + z-index: 10; + border-right: 1px solid #f1f5f9; +} + +.formContainer { + margin: auto 0; + width: 100%; + max-width: 400px; + align-self: center; +} + +.header { + margin-bottom: 2.25rem; +} + +.title { + font-size: 2.1rem; + font-weight: 800; + color: #0f172a; + margin-bottom: 0.6rem; + letter-spacing: -0.025em; + line-height: 1.2; +} + +.subtitle { + font-size: 0.95rem; + color: #64748b; + line-height: 1.5; +} + +.form { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.fieldGroup { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.label { + font-size: 0.875rem; + font-weight: 700; + color: #1e293b; +} + +.inputWrapper { + position: relative; + display: flex; + align-items: center; + width: 100%; +} + +.inputPrefix { + position: absolute; + left: 1rem; + color: #94a3b8; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + transition: color 0.2s ease; +} + +.inputSuffix { + position: absolute; + right: 1rem; + color: #94a3b8; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + background: none; + border: none; + padding: 0; + transition: color 0.2s ease; +} + +.inputSuffix:hover { + color: #475569; +} + +.input { + width: 100%; + padding: 0.85rem 2.85rem 0.85rem 2.75rem; + border: 1.5px solid #e2e8f0; + border-radius: 0.65rem; + font-size: 0.95rem; + color: #0f172a; + background: #ffffff; + transition: border-color 0.2s ease, background-color 0.2s ease; + outline: none; +} + +.input::placeholder { + color: #94a3b8; + opacity: 0.8; +} + +.input:focus { + border-color: #3b82f6; + background: #f8fafc; +} + +.input:focus~.inputPrefix { + color: #3b82f6; +} + +.inputError { + border-color: #ef4444 !important; + animation: shake 0.4s ease-in-out; +} + +.errorMessage { + font-size: 0.825rem; + color: #ef4444; + font-weight: 600; + margin-top: -0.25rem; +} + +/* Remember Me & Forgot Password Row */ +.optionsRow { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.875rem; + margin-top: -0.25rem; +} + +.rememberMe { + display: flex; + align-items: center; + gap: 0.5rem; + color: #64748b; + cursor: pointer; + user-select: none; +} + +.checkbox { + width: 1rem; + height: 1rem; + border-radius: 0.25rem; + border: 1.5px solid #cbd5e1; + cursor: pointer; + accent-color: #2563eb; +} + +.forgotLink { + color: #2563eb; + font-weight: 600; + text-decoration: none; + transition: color 0.2s ease; +} + +.forgotLink:hover { + color: #1d4ed8; + text-decoration: underline; +} + +/* Solid Button without Glow */ +.submitBtn { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; + padding: 0.9rem; + background-color: #2563eb; + color: #ffffff; + border: none; + border-radius: 0.65rem; + font-size: 0.98rem; + font-weight: 700; + cursor: pointer; + transition: background-color 0.2s ease; + margin-top: 0.5rem; + box-shadow: none; + /* Tanpa efek glow / bayangan */ +} + +.submitBtn:hover { + background-color: #1d4ed8; +} + +.submitBtn:active { + background-color: #1e40af; +} + +.submitBtn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + + +/* Right Column: Soft Pastel Blue with Exact Reference Graphics */ +.rightColumn { + flex: 1.4; + display: flex; + background: linear-gradient(135deg, #f0f6ff 0%, #e8f2ff 60%, #e2efff 100%); + position: relative; + overflow: hidden; + color: #0f172a; + padding: 4rem 5rem; + align-items: center; +} + +.showcaseContainer { + width: 100%; + max-width: 780px; + position: relative; + z-index: 5; +} + +.heroRow { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2.5rem; + position: relative; +} + +.heroText { + max-width: 460px; +} + +.subTag { + font-size: 0.85rem; + font-weight: 700; + color: #2563eb; + margin-bottom: 0.75rem; + display: block; +} + +.showcaseHeading { + font-size: 2.75rem; + font-weight: 800; + line-height: 1.2; + letter-spacing: -0.03em; + color: #0f172a; + margin-bottom: 1.25rem; +} + +.showcaseHeading span { + color: #2563eb; + /* Kata dengan aksen biru terang */ +} + +.titleDivider { + width: 60px; + height: 4px; + background-color: #2563eb; + border-radius: 2px; + margin-bottom: 1.5rem; +} + +.showcaseDesc { + font-size: 1.025rem; + line-height: 1.65; + color: #475569; +} + +/* Top Right Concentric Shield Illustration */ +.shieldIllustration { + position: relative; + flex-shrink: 0; + margin-right: -2rem; +} + +.orbitRing { + transform-origin: 140px 140px; + animation: rotateOrbit 45s linear infinite; +} + +.pulseGlow { + transform-origin: 140px 140px; + animation: pulseConcentric 3.5s ease-in-out infinite; +} + +.floatingShield { + animation: floatShield 4.5s ease-in-out infinite; +} + +@keyframes rotateOrbit { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes pulseConcentric { + + 0%, + 100% { + opacity: 0.35; + transform: scale(1); + } + + 50% { + opacity: 0.85; + transform: scale(1.06); + } +} + +@keyframes floatShield { + + 0%, + 100% { + transform: translateY(0px); + } + + 50% { + transform: translateY(-7px); + } +} + +/* Feature Highlights with Soft Light Blue Icon Boxes */ +.featureList { + display: flex; + flex-direction: column; + gap: 1.85rem; +} + +.featureItem { + display: flex; + align-items: flex-start; + gap: 1.25rem; + max-width: 620px; +} + +.featureIconBox { + color: #2563eb; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding-top: 0.15rem; +} + +.featureText { + display: flex; + flex-direction: column; + gap: 0.35rem; + padding-top: 0.15rem; +} + +.featureTitle { + font-size: 1.1rem; + font-weight: 700; + color: #0f172a; +} + +.featureDesc { + font-size: 0.925rem; + color: #64748b; + line-height: 1.55; +} + +/* Background Bottom Right Grid Dots Decor */ +.bgDots { + position: absolute; + bottom: 2rem; + right: 2.5rem; + pointer-events: none; + opacity: 0.7; +} + +@keyframes shake { + + 0%, + 100% { + transform: translateX(0); + } + + 20%, + 60% { + transform: translateX(-4px); + } + + 40%, + 80% { + transform: translateX(4px); + } +} + +/* Responsive Design */ +@media (max-width: 1180px) { + .shieldIllustration { + display: none; + } +} + +@media (max-width: 1024px) { + .rightColumn { + display: none; + } + + .leftColumn { + max-width: 100%; + padding: 2.5rem 1.5rem; + align-items: center; + border-right: none; + } + + .formContainer { + max-width: 440px; + } +} \ No newline at end of file diff --git a/web_ups/src/components/Login.tsx b/web_ups/src/components/Login.tsx new file mode 100644 index 0000000..29960d4 --- /dev/null +++ b/web_ups/src/components/Login.tsx @@ -0,0 +1,244 @@ +"use client"; + +import React, { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { User, Lock, ArrowRight, Eye, EyeOff, Activity, ShieldCheck, Cloud } from 'lucide-react'; +import styles from './Login.module.css'; + +export default function Login() { + const router = useRouter(); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [rememberMe, setRememberMe] = useState(false); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (username.trim() === '' || password.trim() === '') return; + + setLoading(true); + try { + const res = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }), + }); + + if (res.ok) { + router.push('/'); + router.refresh(); + } else { + setError(true); + setTimeout(() => setError(false), 2000); + setPassword(''); + } + } catch (err) { + setError(true); + setTimeout(() => setError(false), 2000); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* Kolom Kiri: Form Login Bahasa Indonesia Rapi */} +
+
{/* Spacer vertical alignments */} + +
+
+

Selamat Datang

+

+ Masuk untuk memantau dan mengelola sistem UPS DC berbasis IoT Anda. +

+
+ +
+
+ +
+
+ +
+ { + setUsername(e.target.value); + setError(false); + }} + autoFocus + /> +
+
+ +
+ +
+
+ +
+ { + setPassword(e.target.value); + setError(false); + }} + /> + +
+
+ + {error && Username atau password salah} + + {/* Opsi Ingat Saya */} +
+ +
+ + {/* Tombol Masuk Solid (Tanpa Glow) */} + +
+
+ +
{/* Bottom spacer for vertical alignment */} +
+ + {/* Kolom Kanan: Nuansa Biru Muda Lembut dengan Fitur Bahasa Indonesia */} +
+
+ {/* Bagian Hero + Ilustrasi Tameng Konsentris di Kanan */} +
+
+ Sistem UPS DC +

+ Monitoring Cerdas.
+ Perlindungan Optimal. +

+
+

+ Solusi pemantauan dan perlindungan daya DC secara real-time untuk memastikan sistem Anda selalu andal. +

+
+ + {/* Ilustrasi Lingkaran Konsentris Tameng Petir (Sesuai Referensi) */} +
+ + {/* Lingkaran Luar Putus-Putus & Titik Orbit (Posisi Segitiga Emas: 11:00, 04:00, 07:00) */} + + + {/* Pukul 11:00 */} + {/* Pukul 04:00 */} + {/* Pukul 07:00 */} + + + {/* Lingkaran Menengah & Dalam (Berdenyut Aktif/Pulse) */} + + + + + + + + {/* Ikon Shield & Petir di Tengah (Melayang Elegan) */} + + + + + +
+
+ + {/* Daftar 3 Poin Keunggulan (Boks Ikon Biru Muda Lembut #dbeafe) */} +
+
+
+ +
+
+ Pemantauan Real-time + + Pantau tegangan, arus, dan status daya secara real-time dari mana saja. + +
+
+ +
+
+ +
+
+ Perlindungan Cerdas + + Sistem proteksi otomatis terhadap over-voltage, over-discharge, dan kondisi abnormal lainnya. + +
+
+ +
+
+ +
+
+ Akses Cloud IoT + + Data tersimpan aman di cloud dan dapat diakses kapan saja untuk analisis dan histori. + +
+
+
+
+ + {/* Dekorasi Dot Grid di Pojok Kiri/Kanan Bawah */} +
+ + {Array.from({ length: 5 }).map((_, r) => + Array.from({ length: 7 }).map((_, c) => ( + + )) + )} + +
+
+
+ ); +} diff --git a/web_ups/src/components/SettingsPanel.module.css b/web_ups/src/components/SettingsPanel.module.css new file mode 100644 index 0000000..62ef7b1 --- /dev/null +++ b/web_ups/src/components/SettingsPanel.module.css @@ -0,0 +1,208 @@ +.panelContainer { + background-color: var(--bg-panel); + border-radius: var(--border-radius-lg); + padding: 2rem; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); +} + +.panelHeader { + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--border-color); +} + +.title { + font-size: 1.25rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 0.25rem; +} + +.subtitle { + font-size: 0.9rem; + color: var(--text-muted); +} + +.grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 2rem; +} + +.section { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.sectionTitle { + font-size: 1.05rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 0.5rem; +} + +.formGroup { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1rem; + background-color: var(--bg-main); + border-radius: var(--border-radius-md); + border: 1px solid var(--border-color); +} + +.labelInfo { + display: flex; + flex-direction: column; +} + +.label { + font-size: 0.9rem; + font-weight: 600; + color: var(--text-primary); +} + +.description { + font-size: 0.75rem; + color: var(--text-muted); +} + +.controlArea { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.select { + padding: 0.5rem; + border-radius: var(--border-radius-sm); + border: 1px solid var(--border-color); + background-color: white; + color: var(--text-primary); + font-size: 0.85rem; + font-family: inherit; + cursor: pointer; + outline: none; +} + +.select:focus { + border-color: var(--color-primary); +} + +.input { + width: 70px; + padding: 0.5rem; + border-radius: var(--border-radius-sm); + border: 1px solid var(--border-color); + background-color: white; + color: var(--text-primary); + font-size: 0.85rem; + text-align: center; + outline: none; +} + +.input:focus { + border-color: var(--color-primary); +} + +.button { + padding: 0.5rem 1rem; + background-color: var(--color-primary); + color: white; + border: none; + border-radius: var(--border-radius-sm); + font-size: 0.85rem; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; +} + +.button:hover { + background-color: #2563eb; +} + +.button.secondary { + background-color: var(--bg-main); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.button.secondary:hover { + background-color: #e5e7eb; +} + +/* Toggle Switch CSS */ +.switch { + position: relative; + display: inline-block; + width: 44px; + height: 24px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .2s; + border-radius: 24px; +} + +.slider:before { + position: absolute; + content: ""; + height: 18px; + width: 18px; + left: 3px; + bottom: 3px; + background-color: white; + transition: .2s; + border-radius: 50%; + box-shadow: var(--shadow-sm); +} + +input:checked + .slider { + background-color: var(--color-success); +} + +input:checked + .slider:before { + transform: translateX(20px); +} + +input:disabled + .slider { + background-color: #e5e7eb; + cursor: not-allowed; +} + +@media (max-width: 768px) { + .grid { + grid-template-columns: 1fr; + gap: 1.5rem; + } + + .panelContainer { + padding: 1.25rem; + } + + .formGroup { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .controlArea { + width: 100%; + justify-content: flex-end; + } +} diff --git a/web_ups/src/components/SettingsPanel.tsx b/web_ups/src/components/SettingsPanel.tsx new file mode 100644 index 0000000..2021a35 --- /dev/null +++ b/web_ups/src/components/SettingsPanel.tsx @@ -0,0 +1,221 @@ +import React, { useState } from 'react'; +import { UpsData } from '../app/page'; +import styles from './SettingsPanel.module.css'; + +interface SettingsPanelProps { + data: UpsData | null; + onSendCommand: (cmd: string, value: string | number) => void; +} + +export default function SettingsPanel({ data, onSendCommand }: SettingsPanelProps) { + const [shutTimer, setShutTimer] = useState("120"); + + const handleToggle = (cmd: string, checked: boolean) => { + onSendCommand(cmd, checked ? 1 : 0); + }; + + const handleSelect = (cmd: string, e: React.ChangeEvent) => { + onSendCommand(cmd, e.target.value); + }; + + const isPowerOn = data?.on === 1; + + return ( +
+
+

Device Configuration

+ Manage power states, charging logic, and automation +
+ +
+ {/* Left Column: Power Control */} +
+

Power Control

+ +
+
+ Power Output + Main power switch +
+
+ +
+
+ +
+
+ Auto ON Mode + Recovery behavior on AC restore +
+
+ +
+
+ +
+
+ Shutdown Cast + Auto shutdown condition +
+
+ +
+
+ +
+
+ Timed Shutdown + Delay before power off +
+
+ setShutTimer(e.target.value)} + min="10" max="999" + disabled={!isPowerOn} + /> + sec + +
+
+
+ + {/* Right Column: Charge Control */} +
+

Charge Control

+ +
+
+ Charge Mode + Battery target limits +
+
+ +
+
+ +
+
+ AVR + Automatic Voltage Reducer +
+
+ +
+
+ +
+
+ OVP + Overvoltage Protection +
+
+ +
+
+ +
+
+ FCoPL + Full Charge on Power Lost +
+
+ + {data?.chgOFC === 1 && ( + + )} +
+
+ +
+
+
+ ); +} diff --git a/web_ups/src/components/Sidebar.module.css b/web_ups/src/components/Sidebar.module.css new file mode 100644 index 0000000..0d94881 --- /dev/null +++ b/web_ups/src/components/Sidebar.module.css @@ -0,0 +1,179 @@ +.sidebar { + width: 260px; + height: 100vh; + background-color: var(--bg-panel); + border-right: 1px solid var(--border-color); + display: flex; + flex-direction: column; + position: fixed; + left: 0; + top: 0; + z-index: 10; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 1.5rem; + font-weight: 600; + font-size: 1.125rem; + color: var(--text-primary); +} + +.freeLogoIcon { + color: #2563eb; + flex-shrink: 0; +} + +.logoText { + display: flex; + flex-direction: column; +} + +.logoSubtitle { + font-size: 0.65rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 500; +} + +.menuSection { + flex: 1; + padding: 1rem 1rem; +} + +.menuTitle { + font-size: 0.75rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; + margin-bottom: 1rem; + padding-left: 0.5rem; +} + +.menuItem { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + border-radius: var(--border-radius-lg); + color: var(--text-secondary); + font-size: 0.95rem; + font-weight: 500; + transition: all 0.2s ease; + margin-bottom: 0.25rem; + cursor: pointer; + background: none; + border: none; + width: 100%; + text-align: left; + font-family: inherit; +} + +.menuItem:hover { + background-color: var(--bg-main); + color: var(--text-primary); +} + +.menuItem.active { + background-color: var(--color-primary-light); + color: var(--color-primary); +} + +.menuIcon { + width: 18px; + height: 18px; +} + +.profile { + padding: 1.5rem; + border-top: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: space-between; +} + +.profileInfo { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.avatar { + width: 36px; + height: 36px; + background-color: #1e1b4b; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 1rem; +} + +.profileDetails { + display: flex; + flex-direction: column; +} + +.profileName { + font-size: 0.9rem; + font-weight: 600; + color: var(--text-primary); +} + +.profileRole { + font-size: 0.75rem; + color: var(--text-muted); +} + +.logoutBtn { + color: var(--text-muted); + cursor: pointer; + transition: color 0.2s; + background: none; + border: none; +} + +.logoutBtn:hover { + color: var(--text-primary); +} + +.overlay { + display: none; +} + +@media (max-width: 900px) { + .sidebar { + transform: translateX(-100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: none; + } + + .sidebar.open { + transform: translateX(0); + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2); + } + + .overlay { + display: block; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(2px); + z-index: 9; + animation: fadeIn 0.2s ease; + } +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} diff --git a/web_ups/src/components/Sidebar.tsx b/web_ups/src/components/Sidebar.tsx new file mode 100644 index 0000000..4292f6e --- /dev/null +++ b/web_ups/src/components/Sidebar.tsx @@ -0,0 +1,103 @@ +"use client"; + +import React from 'react'; +import { useRouter } from 'next/navigation'; +import { LayoutDashboard, Activity, Bell, Settings, LogOut, Zap } from 'lucide-react'; +import styles from './Sidebar.module.css'; + +interface SidebarProps { + activeTab: 'dashboard' | 'history' | 'logs' | 'settings'; + onTabChange: (tab: 'dashboard' | 'history' | 'logs' | 'settings') => void; + isOpen?: boolean; + onClose?: () => void; +} + +export default function Sidebar({ activeTab, onTabChange, isOpen = false, onClose }: SidebarProps) { + const router = useRouter(); + + const handleLogout = async () => { + try { + await fetch('/api/auth/logout', { method: 'POST' }); + router.push('/login'); + router.refresh(); + } catch (err) { + console.error('Failed to logout', err); + } + }; + + return ( + <> + {isOpen &&
} + + + ); +} diff --git a/web_ups/src/components/StatCards.module.css b/web_ups/src/components/StatCards.module.css new file mode 100644 index 0000000..92fbf02 --- /dev/null +++ b/web_ups/src/components/StatCards.module.css @@ -0,0 +1,137 @@ +.cardsGrid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1.5rem; + margin-bottom: 1.5rem; +} + +.card { + background-color: var(--bg-panel); + border-radius: var(--border-radius-lg); + padding: 1.5rem; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 140px; +} + +.cardHeader { + display: flex; + justify-content: space-between; + align-items: center; + color: var(--text-secondary); + font-size: 0.85rem; + font-weight: 500; + margin-bottom: 1rem; +} + +.iconWrapper { + color: var(--color-primary); + display: flex; + align-items: center; + justify-content: center; +} + +.cardContent { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.valueArea { + display: flex; + align-items: baseline; + gap: 0.25rem; +} + +.value { + font-size: 1.875rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.02em; +} + +.unit { + font-size: 1rem; + font-weight: 600; + color: var(--text-secondary); +} + +.subtext { + font-size: 0.85rem; + color: var(--text-muted); + font-weight: 500; +} + +.status { + display: flex; + align-items: center; + gap: 0.35rem; + font-size: 0.75rem; + color: var(--text-secondary); + font-weight: 500; +} + +.dot { + width: 6px; + height: 6px; + border-radius: 50%; + background-color: var(--text-muted); +} + +.dot.active { + background-color: var(--color-success); +} + +.dot.error { + background-color: var(--color-danger, #ef4444); +} + +.progressBarContainer { + width: 100%; + height: 4px; + background-color: var(--border-color); + border-radius: 2px; + margin-top: 0.5rem; + overflow: hidden; +} + +.progressBar { + height: 100%; + background-color: var(--color-success); + border-radius: 2px; +} + +.powerSourceValue { + font-size: 1.5rem; + font-weight: 700; + color: var(--text-primary); +} + +.powerSourceIcon { + color: #a855f7; /* Purple for power */ +} + +.batteryIcon { + color: var(--color-success); +} + +@media (max-width: 1100px) { + .cardsGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 600px) { + .cardsGrid { + grid-template-columns: 1fr; + gap: 1rem; + } + + .card { + padding: 1.25rem; + min-height: 120px; + } +} diff --git a/web_ups/src/components/StatCards.tsx b/web_ups/src/components/StatCards.tsx new file mode 100644 index 0000000..83dc7a1 --- /dev/null +++ b/web_ups/src/components/StatCards.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import { Zap, Activity, BatteryCharging, Power } from 'lucide-react'; +import { UpsData } from '../app/page'; +import styles from './StatCards.module.css'; + +interface StatCardsProps { + data: UpsData | null; +} + +export default function StatCards({ data }: StatCardsProps) { + // If no data received yet (device offline), show dashes + if (!data) { + return ( +
+ {['Output Status', 'Charging Status', 'Battery Level', 'Power Source'].map(title => ( +
+
+ {title} +
+
+
+ +
+
+
+ No signal +
+
+
+ ))} +
+ ); + } + + const outputOn = data.on === 1; + const isCharging = data.charging === 1; + const chgError = data.chgError === 1; + const isFull = data.charging === 0 && (data.battP ?? 0) >= 95 && (data.line ?? 0) > 0; + + let chargingText = "Discharging"; + if (chgError) chargingText = "Error"; + else if (isFull) chargingText = "Fully Charged"; + else if (isCharging) chargingText = "Charging"; + else if ((data.line ?? 0) > 0) chargingText = "Standby"; + + const battPercent = data.battP ?? 0; + const battVoltage = data.v ?? "0.00"; + + const isBypass = (data.line ?? 0) > 0; + const powerSource = isBypass ? "Adaptor" : "Battery"; + + return ( +
+ {/* Card 1: Output Status */} +
+
+ Output Status +
+ +
+
+
+
+ {outputOn ? 'ON' : 'OFF'} +
+
+
+ {outputOn ? 'Powering Load' : 'Disabled'} +
+
+
+ + {/* Card 2: Charging Status */} +
+
+ Charging Status +
+ +
+
+
+
+ {chargingText} +
+
+
+ {chgError ? 'Check Battery' : isCharging ? 'Active' : 'Idle'} +
+
+
+ + {/* Card 3: Battery Level */} +
+
+ Battery Level +
+ +
+
+
+
+ {battPercent} + % + ({battVoltage}V) +
+
+
+
+
+
+ + {/* Card 4: Power Source */} +
+
+ Power Source +
+ +
+
+
+
+ {powerSource} +
+
+ Relay: {isBypass ? 'Bypass Mode' : 'Inverter Mode'} +
+
+
+
+ ); +} + diff --git a/web_ups/src/components/VoltageChart.module.css b/web_ups/src/components/VoltageChart.module.css new file mode 100644 index 0000000..aaaf081 --- /dev/null +++ b/web_ups/src/components/VoltageChart.module.css @@ -0,0 +1,64 @@ +.chartContainer { + background-color: var(--bg-panel); + border-radius: var(--border-radius-lg); + padding: 1.5rem; + border: 1px solid var(--border-color); + box-shadow: var(--shadow-sm); + display: flex; + flex-direction: column; + height: 460px; + max-height: 460px; + overflow: hidden; +} + +.chartHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1.5rem; +} + +.titleArea { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.title { + font-size: 1.125rem; + font-weight: 600; + color: var(--text-primary); +} + +.subtitle { + font-size: 0.85rem; + color: var(--text-muted); +} + +.exportBtn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background-color: white; + border: 1px solid var(--border-color); + border-radius: var(--border-radius-md); + color: var(--text-secondary); + font-size: 0.85rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: var(--shadow-sm); +} + +.exportBtn:hover { + background-color: var(--bg-main); + color: var(--text-primary); +} + +.chartContent { + flex: 1; + width: 100%; + min-height: 300px; + position: relative; +} diff --git a/web_ups/src/components/VoltageChart.tsx b/web_ups/src/components/VoltageChart.tsx new file mode 100644 index 0000000..e18a011 --- /dev/null +++ b/web_ups/src/components/VoltageChart.tsx @@ -0,0 +1,70 @@ +"use client"; + +import React from 'react'; +import { Download } from 'lucide-react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; +import styles from './VoltageChart.module.css'; + +interface ChartDataPoint { + time: string; + batteryPct: number; + vBatt: number; +} + +interface VoltageChartProps { + data: ChartDataPoint[]; +} + +export default function VoltageChart({ data }: VoltageChartProps) { + const handleExport = () => { + if (!data.length) return; + const headers = 'Time,Battery %,V-Batt\n'; + const rows = data.map(d => + `"${d.time}",${d.batteryPct},${d.vBatt}` + ).join('\n'); + const blob = new Blob([headers + rows], { type: 'text/csv' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `ups_voltage_${new Date().toISOString().slice(0, 10)}.csv`; + a.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+
+
+

Voltage History

+ Real-time discharge/charge monitoring +
+ +
+
+ {data.length === 0 ? ( +
+ Perangkat IoT Belum Terhubung + Grafik akan muncul secara real-time tatkala perangkat UPS fisik Anda aktif terhubung dan mulai mengirimkan data telemetri. +
+ ) : ( + + + + + `${val}V`} domain={[0, 16]} /> + `${val}%`} domain={[0, 100]} /> + + + + + + + + )} +
+
+ ); +} diff --git a/web_ups/src/lib/auth.ts b/web_ups/src/lib/auth.ts new file mode 100644 index 0000000..f7f7c2f --- /dev/null +++ b/web_ups/src/lib/auth.ts @@ -0,0 +1,23 @@ +import { SignJWT, jwtVerify } from 'jose'; + +const secretKey = process.env.AUTH_SECRET || 'fallback-secret-key-for-development-only'; +const encodedKey = new TextEncoder().encode(secretKey); + +export async function encrypt(payload: any) { + return new SignJWT(payload) + .setProtectedHeader({ alg: 'HS256' }) + .setIssuedAt() + .setExpirationTime('7d') + .sign(encodedKey); +} + +export async function decrypt(session: string | undefined = '') { + try { + const { payload } = await jwtVerify(session, encodedKey, { + algorithms: ['HS256'], + }); + return payload; + } catch (error) { + return null; + } +} diff --git a/web_ups/src/lib/db.ts b/web_ups/src/lib/db.ts new file mode 100644 index 0000000..32702ce --- /dev/null +++ b/web_ups/src/lib/db.ts @@ -0,0 +1,189 @@ +import { supabase } from './supabase'; + +// Types + +export interface HistoryRow { + id?: number; + created_at?: string; + v_batt: number; + battery_pct: number; + v_in?: number; + v_out?: number; + line_state: number; + power_on: boolean; +} + +export interface EventRow { + id?: number; + created_at?: string; + message: string; + severity: 'info' | 'success' | 'warning' | 'error'; +} + +let dbUnavailable = false; +let warnedUnavailable = false; +let retryTimeoutId: ReturnType | null = null; + +function isNetworkError(error: unknown) { + const message = error instanceof Error ? error.message : String(error ?? ''); + return /failed to fetch|fetch failed|networkerror|load failed/i.test(message); +} + +function logDbIssue(operation: string, error: unknown) { + const message = error instanceof Error ? error.message : String(error ?? 'Unknown error'); + + if (isNetworkError(error)) { + dbUnavailable = true; + + if (!warnedUnavailable) { + warnedUnavailable = true; + console.warn( + `[DB] Supabase tidak bisa dijangkau (${message}). ` + + 'Dashboard tetap berjalan tanpa sinkronisasi database. ' + + 'Periksa NEXT_PUBLIC_SUPABASE_URL dan koneksi jaringan.' + ); + } + // Auto-retry after 60 seconds + if (!retryTimeoutId) { + retryTimeoutId = setTimeout(() => { + dbUnavailable = false; + warnedUnavailable = false; + retryTimeoutId = null; + console.info('[DB] Retrying Supabase connection...'); + }, 60000); + } + return; + } + + console.warn(`[DB] ${operation} error:`, message); +} + +function shouldSkipDb() { + return dbUnavailable; +} + +// History + +/** + * Simpan satu titik data history ke Supabase. + */ +export async function insertHistoryPoint(row: HistoryRow) { + if (shouldSkipDb()) return; + + try { + const { error } = await supabase.from('ups_history').insert([row]); + if (error) logDbIssue('insertHistoryPoint', error); + } catch (error) { + logDbIssue('insertHistoryPoint', error); + } +} + +/** + * Ambil N data history terakhir dari Supabase, diurutkan dari terlama ke terbaru. + */ +export async function fetchHistory(limit = 100): Promise { + if (shouldSkipDb()) return []; + + try { + const { data, error } = await supabase + .from('ups_history') + .select('*') + .order('created_at', { ascending: false }) + .limit(limit); + + if (error) { + logDbIssue('fetchHistory', error); + return []; + } + return (data as HistoryRow[]).reverse(); + } catch (error) { + logDbIssue('fetchHistory', error); + return []; + } +} + +/** + * Ambil data history dari Supabase dalam rentang tanggal tertentu. + * @param from ISO string (misal "2024-05-01T00:00:00.000Z") + * @param to ISO string (misal "2024-05-07T23:59:59.999Z") + * @param limit maksimum jumlah baris yang diambil + */ +export async function fetchHistoryByRange(from: string, to: string, limit = 2000): Promise { + if (shouldSkipDb()) return []; + + try { + const { data, error } = await supabase + .from('ups_history') + .select('*') + .gte('created_at', from) + .lte('created_at', to) + .order('created_at', { ascending: true }) + .limit(limit); + + if (error) { + logDbIssue('fetchHistoryByRange', error); + return []; + } + return data as HistoryRow[]; + } catch (error) { + logDbIssue('fetchHistoryByRange', error); + return []; + } +} + +// Events + +/** + * Simpan satu event log ke Supabase. + */ +export async function insertEvent(row: EventRow) { + if (shouldSkipDb()) return; + + try { + const { error } = await supabase.from('ups_events').insert([row]); + if (error) logDbIssue('insertEvent', error); + } catch (error) { + logDbIssue('insertEvent', error); + } +} + +/** + * Ambil N event log terakhir dari Supabase, diurutkan dari terbaru ke terlama. + */ +export async function fetchEvents(limit = 200): Promise { + if (shouldSkipDb()) return []; + + try { + const { data, error } = await supabase + .from('ups_events') + .select('*') + .order('created_at', { ascending: false }) + .limit(limit); + + if (error) { + logDbIssue('fetchEvents', error); + return []; + } + return data as EventRow[]; + } catch (error) { + logDbIssue('fetchEvents', error); + return []; + } +} + +/** + * Hapus semua event log dari Supabase. + */ +export async function clearAllEvents() { + if (shouldSkipDb()) return; + + try { + const { error } = await supabase + .from('ups_events') + .delete() + .gte('id', 0); // delete all rows + if (error) logDbIssue('clearAllEvents', error); + } catch (error) { + logDbIssue('clearAllEvents', error); + } +} diff --git a/web_ups/src/lib/supabase.ts b/web_ups/src/lib/supabase.ts new file mode 100644 index 0000000..5a1282c --- /dev/null +++ b/web_ups/src/lib/supabase.ts @@ -0,0 +1,16 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + +if (!supabaseUrl || !supabaseAnonKey) { + console.warn( + '[Supabase] Missing environment variables: NEXT_PUBLIC_SUPABASE_URL and/or NEXT_PUBLIC_SUPABASE_ANON_KEY. ' + + 'Database features will be unavailable.' + ); +} + +export const supabase = createClient( + supabaseUrl || 'https://placeholder.supabase.co', + supabaseAnonKey || 'placeholder-key' +); diff --git a/web_ups/src/proxy.ts b/web_ups/src/proxy.ts new file mode 100644 index 0000000..0f6ca81 --- /dev/null +++ b/web_ups/src/proxy.ts @@ -0,0 +1,52 @@ +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; +import { decrypt } from '@/lib/auth'; + +export default async function proxy(request: NextRequest) { + const path = request.nextUrl.pathname; + + // Protect root path and api control + const isProtectedRoute = path === '/' || path.startsWith('/api/control'); + + if (isProtectedRoute) { + const cookie = request.cookies.get('auth_token')?.value; + const session = await decrypt(cookie); + + // If accessing /api/control without cookie, check if they have the bearer token instead + if (path.startsWith('/api/control')) { + const authHeader = request.headers.get('Authorization'); + const apiSecret = process.env.CONTROL_API_SECRET; + + // If there's an API secret configured, and they provided the right bearer token, let them through + if (apiSecret && authHeader === `Bearer ${apiSecret}`) { + return NextResponse.next(); + } + + // Otherwise, if they don't have a valid web session cookie, block them + if (!session) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + return NextResponse.next(); + } + + // For the web dashboard (root), redirect to login if not authenticated + if (!session) { + return NextResponse.redirect(new URL('/login', request.url)); + } + } + + // Redirect authenticated users away from the login page + if (path === '/login') { + const cookie = request.cookies.get('auth_token')?.value; + const session = await decrypt(cookie); + if (session) { + return NextResponse.redirect(new URL('/', request.url)); + } + } + + return NextResponse.next(); +} + +export const config = { + matcher: ['/', '/login', '/api/control/:path*'], +}; diff --git a/web_ups/tsconfig.json b/web_ups/tsconfig.json new file mode 100644 index 0000000..759e6d7 --- /dev/null +++ b/web_ups/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": [ + "node_modules" + ] +}