Compare commits
4 Commits
439578434e
...
e39a583e95
Author | SHA1 | Date | |
---|---|---|---|
|
e39a583e95 | ||
|
12f9eebfad | ||
|
0ce3751d08 | ||
|
0eb7e9d4ca |
@@ -2,13 +2,19 @@ mod controllers;
|
|||||||
mod routes;
|
mod routes;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use utils::init_database;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::{routing::{delete, get, post, put}, Router};
|
use axum::{
|
||||||
use routes::{admin, auth as auth_routes, machines, setup};
|
routing::{delete, get, post, put},
|
||||||
use tower_http::{cors::CorsLayer, services::{ServeDir, ServeFile}};
|
Router,
|
||||||
|
};
|
||||||
|
use routes::{accounts, admin, auth as auth_routes, machines, setup};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
|
use tower_http::{
|
||||||
|
cors::CorsLayer,
|
||||||
|
services::{ServeDir, ServeFile},
|
||||||
|
};
|
||||||
|
use utils::init_database;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@@ -16,21 +22,17 @@ async fn main() -> Result<()> {
|
|||||||
let api_routes = Router::new()
|
let api_routes = Router::new()
|
||||||
.route("/setup/status", get(setup::get_setup_status))
|
.route("/setup/status", get(setup::get_setup_status))
|
||||||
.route("/setup/init", post(setup::init_setup))
|
.route("/setup/init", post(setup::init_setup))
|
||||||
|
|
||||||
.route("/auth/login", post(auth_routes::login))
|
.route("/auth/login", post(auth_routes::login))
|
||||||
.route("/auth/logout", post(auth_routes::logout))
|
.route("/auth/logout", post(auth_routes::logout))
|
||||||
|
.route("/accounts/me", get(accounts::me))
|
||||||
.route("/admin/users", get(admin::get_users))
|
.route("/admin/users", get(admin::get_users))
|
||||||
.route("/admin/users", post(admin::create_user_handler))
|
.route("/admin/users", post(admin::create_user_handler))
|
||||||
.route("/admin/users/{id}", put(admin::update_user_handler))
|
.route("/admin/users/{id}", put(admin::update_user_handler))
|
||||||
.route("/admin/users/{id}", delete(admin::delete_user_handler))
|
.route("/admin/users/{id}", delete(admin::delete_user_handler))
|
||||||
|
|
||||||
.route("/machines/register", post(machines::register_machine))
|
.route("/machines/register", post(machines::register_machine))
|
||||||
.route("/machines", get(machines::get_machines))
|
.route("/machines", get(machines::get_machines))
|
||||||
.route("/machines/{id}", delete(machines::delete_machine))
|
.route("/machines/{id}", delete(machines::delete_machine))
|
||||||
|
|
||||||
.layer(CorsLayer::permissive())
|
.layer(CorsLayer::permissive())
|
||||||
|
|
||||||
.with_state(pool);
|
.with_state(pool);
|
||||||
|
|
||||||
let dist_path = "./dist";
|
let dist_path = "./dist";
|
||||||
@@ -48,7 +50,9 @@ async fn main() -> Result<()> {
|
|||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8379").await?;
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8379").await?;
|
||||||
println!("Server running on http://0.0.0.0:8379");
|
println!("Server running on http://0.0.0.0:8379");
|
||||||
|
|
||||||
axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()).await?;
|
axum::serve(listener, app)
|
||||||
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
server/src/routes/accounts.rs
Normal file
6
server/src/routes/accounts.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use crate::utils::{auth::AuthUser, error::*, models::User};
|
||||||
|
use axum::response::Json;
|
||||||
|
|
||||||
|
pub async fn me(auth_user: AuthUser) -> Result<Json<User>, AppError> {
|
||||||
|
Ok(success_response(auth_user.user))
|
||||||
|
}
|
@@ -2,3 +2,4 @@ pub mod admin;
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod machines;
|
pub mod machines;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
pub mod accounts;
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React</title>
|
<title>Arkendro</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
8
webui/jsconfig.json
Normal file
8
webui/jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,8 +10,13 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/plus-jakarta-sans": "^5.2.6",
|
||||||
|
"@phosphor-icons/react": "^2.1.10",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1"
|
"react-dom": "^19.1.1",
|
||||||
|
"react-router-dom": "^7.8.2",
|
||||||
|
"sass-embedded": "^1.92.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
585
webui/pnpm-lock.yaml
generated
585
webui/pnpm-lock.yaml
generated
@@ -8,12 +8,27 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@fontsource/plus-jakarta-sans':
|
||||||
|
specifier: ^5.2.6
|
||||||
|
version: 5.2.6
|
||||||
|
'@phosphor-icons/react':
|
||||||
|
specifier: ^2.1.10
|
||||||
|
version: 2.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
classnames:
|
||||||
|
specifier: ^2.5.1
|
||||||
|
version: 2.5.1
|
||||||
react:
|
react:
|
||||||
specifier: ^19.1.1
|
specifier: ^19.1.1
|
||||||
version: 19.1.1
|
version: 19.1.1
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^19.1.1
|
specifier: ^19.1.1
|
||||||
version: 19.1.1(react@19.1.1)
|
version: 19.1.1(react@19.1.1)
|
||||||
|
react-router-dom:
|
||||||
|
specifier: ^7.8.2
|
||||||
|
version: 7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
sass-embedded:
|
||||||
|
specifier: ^1.92.1
|
||||||
|
version: 1.92.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.33.0
|
specifier: ^9.33.0
|
||||||
@@ -26,7 +41,7 @@ importers:
|
|||||||
version: 19.1.9(@types/react@19.1.12)
|
version: 19.1.9(@types/react@19.1.12)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.0.2(vite@7.1.5)
|
version: 5.0.2(vite@7.1.5(sass-embedded@1.92.1)(sass@1.92.1))
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.33.0
|
specifier: ^9.33.0
|
||||||
version: 9.35.0
|
version: 9.35.0
|
||||||
@@ -41,7 +56,7 @@ importers:
|
|||||||
version: 16.3.0
|
version: 16.3.0
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.1.2
|
specifier: ^7.1.2
|
||||||
version: 7.1.5
|
version: 7.1.5(sass-embedded@1.92.1)(sass@1.92.1)
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -128,6 +143,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
|
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@bufbuild/protobuf@2.7.0':
|
||||||
|
resolution: {integrity: sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.9':
|
'@esbuild/aix-ppc64@0.25.9':
|
||||||
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
|
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -322,6 +340,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
|
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@fontsource/plus-jakarta-sans@5.2.6':
|
||||||
|
resolution: {integrity: sha512-mvUiz1ta3bCVhP/DPmAOmuzhHQi6ddOo1GgaW58rpojr510Rx9BkqXqcnMhGEOMZZB3+84frvfFmw/jKCctHLw==}
|
||||||
|
|
||||||
'@humanfs/core@0.19.1':
|
'@humanfs/core@0.19.1':
|
||||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
@@ -354,6 +375,95 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.30':
|
'@jridgewell/trace-mapping@0.3.30':
|
||||||
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
|
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
|
||||||
|
|
||||||
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-arm64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-x64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@parcel/watcher-freebsd-x64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
||||||
|
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
|
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
|
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
|
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
|
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
|
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-ia32@2.5.1':
|
||||||
|
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-x64@2.5.1':
|
||||||
|
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@parcel/watcher@2.5.1':
|
||||||
|
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
||||||
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
||||||
|
'@phosphor-icons/react@2.1.10':
|
||||||
|
resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 16.8'
|
||||||
|
react-dom: '>= 16.8'
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.34':
|
'@rolldown/pluginutils@1.0.0-beta.34':
|
||||||
resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==}
|
resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==}
|
||||||
|
|
||||||
@@ -520,11 +630,18 @@ packages:
|
|||||||
brace-expansion@1.1.12:
|
brace-expansion@1.1.12:
|
||||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||||
|
|
||||||
|
braces@3.0.3:
|
||||||
|
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
browserslist@4.25.4:
|
browserslist@4.25.4:
|
||||||
resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==}
|
resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==}
|
||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
buffer-builder@0.2.0:
|
||||||
|
resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==}
|
||||||
|
|
||||||
callsites@3.1.0:
|
callsites@3.1.0:
|
||||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -536,6 +653,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
chokidar@4.0.3:
|
||||||
|
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||||
|
engines: {node: '>= 14.16.0'}
|
||||||
|
|
||||||
|
classnames@2.5.1:
|
||||||
|
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
@@ -543,12 +667,19 @@ packages:
|
|||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
colorjs.io@0.5.2:
|
||||||
|
resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==}
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
|
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
|
cookie@1.0.2:
|
||||||
|
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -568,6 +699,11 @@ packages:
|
|||||||
deep-is@0.1.4:
|
deep-is@0.1.4:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
|
|
||||||
|
detect-libc@1.0.3:
|
||||||
|
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
electron-to-chromium@1.5.215:
|
electron-to-chromium@1.5.215:
|
||||||
resolution: {integrity: sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ==}
|
resolution: {integrity: sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ==}
|
||||||
|
|
||||||
@@ -659,6 +795,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
fill-range@7.1.1:
|
||||||
|
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
find-up@5.0.0:
|
find-up@5.0.0:
|
||||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -699,6 +839,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
immutable@5.1.3:
|
||||||
|
resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==}
|
||||||
|
|
||||||
import-fresh@3.3.1:
|
import-fresh@3.3.1:
|
||||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -715,6 +858,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-number@7.0.0:
|
||||||
|
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||||
|
engines: {node: '>=0.12.0'}
|
||||||
|
|
||||||
isexe@2.0.0:
|
isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
@@ -761,6 +908,10 @@ packages:
|
|||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
|
micromatch@4.0.8:
|
||||||
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||||
|
|
||||||
@@ -775,6 +926,9 @@ packages:
|
|||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
|
node-addon-api@7.1.1:
|
||||||
|
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||||
|
|
||||||
node-releases@2.0.20:
|
node-releases@2.0.20:
|
||||||
resolution: {integrity: sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==}
|
resolution: {integrity: sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==}
|
||||||
|
|
||||||
@@ -805,6 +959,10 @@ packages:
|
|||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
picomatch@2.3.1:
|
||||||
|
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||||
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
picomatch@4.0.3:
|
picomatch@4.0.3:
|
||||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -830,10 +988,31 @@ packages:
|
|||||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
react-router-dom@7.8.2:
|
||||||
|
resolution: {integrity: sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
|
||||||
|
react-router@7.8.2:
|
||||||
|
resolution: {integrity: sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
react@19.1.1:
|
react@19.1.1:
|
||||||
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
|
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
readdirp@4.1.2:
|
||||||
|
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||||
|
engines: {node: '>= 14.18.0'}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -843,6 +1022,123 @@ packages:
|
|||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
rxjs@7.8.2:
|
||||||
|
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||||
|
|
||||||
|
sass-embedded-all-unknown@1.92.1:
|
||||||
|
resolution: {integrity: sha512-5t6/YZf+vhO3OY/49h8RCL6Cwo78luva0M+TnTM9gu9ASffRXAuOVLNKciSXa3loptyemDDS6IU5/dVH5w0KmA==}
|
||||||
|
cpu: ['!arm', '!arm64', '!riscv64', '!x64']
|
||||||
|
|
||||||
|
sass-embedded-android-arm64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-Q+UruGb7yKawHagVmVDRRKsnc4mJZvWMBnuRCu2coJo2FofyqBmXohVGXbxko97sYceA9TJTrUEx3WVKQUNCbQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
sass-embedded-android-arm@1.92.1:
|
||||||
|
resolution: {integrity: sha512-4EjpVVzuksERdgAd4BqeSXFnWtWN3DSRyEIUPJ7BhcS9sfDh2Gf6miI2kNTvIQLJ2XIJynDDcEQ8a1U9KwKUTQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
sass-embedded-android-riscv64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-nCY5btLlX7W7Jc6cCL6D2Yklpiu540EJ2G08YVGu12DrAMCBzqM347CSRf2ojp1H8jyhvmLkaFwnrJWzh+6S+w==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
sass-embedded-android-x64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-qYWR3bftJ77aLYwYDFuzDI4dcwVVixxqQxlIQWNGkHRCexj614qGSSHemr18C2eVj3mjXAQxTQxU68U7pkGPAA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
sass-embedded-darwin-arm64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-g2yQ3txjMYLKMjL2cW1xRO9nnV3ijf95NbX/QShtV6tiVUETZNWDsRMDEwBNGYY6PTE/UZerjJL1R/2xpQg6WA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
sass-embedded-darwin-x64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-eH+fgxLQhTEPjZPCgPAVuX5e514Qp/4DMAUMtlNShv4cr4TD5qOp1XlsPYR/b7uE7p2cKFkUpUn/bHNqJ2ay4A==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
sass-embedded-linux-arm64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-dNmlpGeZkry1BofhAdGFBXrpM69y9LlYuNnncf+HfsOOUtj8j0q1RwS+zb5asknhKFUOAG8GCGRY1df7Rwu35g==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-linux-arm@1.92.1:
|
||||||
|
resolution: {integrity: sha512-cT3w8yoQTqrtZvWLJeutEGmawITDTY4J6oSVQjeDcPnnoPt0gOFxem8YMznraACXvahw/2+KJDH33BTNgiPo0A==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-arm64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-TfiEBkCyNzVoOhjHXUT+vZ6+p0ueDbvRw6f4jHdkvljZzXdXMby4wh7BU1odl69rgRTkSvYKhgbErRLDR/F7pQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-arm@1.92.1:
|
||||||
|
resolution: {integrity: sha512-nPBos6lI31ef2zQhqTZhFOU7ar4impJbLIax0XsqS269YsiCwjhk11VmUloJTpFlJuKMiVXNo7dPx+katxhD/Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-riscv64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-R+RcJA4EYpJDE9JM1GgPYgZo7x94FlxZ6jPodOQkEaZ1S9kvXVCuP5X/0PXRPhu08KJOfeMsAElzfdAjUf7KJg==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-x64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-/HolYRGXJjx8nLw6oj5ZrkR7PFM7X/5kE4MYZaFMpDIPIcw3bqB2fUXLo/MYlRLsw7gBAT6hJAMBrNdKuTphfw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-linux-riscv64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-b9bxe0CMsbSsLx3nrR0cq8xpIkoAC6X36o4DGMITF3m2v3KsojC7ru9X0Gz+zUFr6rwpq/0lTNzFLNu6sPNo3w==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-linux-x64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-xuiK5Jp5NldW4bvlC7AuX1Wf7o0gLZ3md/hNg+bkTvxtCDgnUHtfdo8Q+xWP11bD9QX31xXFWpmUB8UDLi6XQQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
sass-embedded-unknown-all@1.92.1:
|
||||||
|
resolution: {integrity: sha512-AT9oXvtNY4N+Nd0wvoWqq9A5HjdH/X3aUH4boQUtXyaJ/9DUwnQmBpP5Gtn028ZS8exOGBdobmmWAuigv0k/OA==}
|
||||||
|
os: ['!android', '!darwin', '!linux', '!win32']
|
||||||
|
|
||||||
|
sass-embedded-win32-arm64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-KvmpQjY9yTBMtTYz4WBqetlv9bGaDW1aStcu7MSTbH7YiSybX/9fnxlCAEQv1WlIidQhcJAiyk0Eae+LGK7cIQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
sass-embedded-win32-x64@1.92.1:
|
||||||
|
resolution: {integrity: sha512-B6Nz/GbH7Vkpb2TkQHsGcczWM5t+70VWopWF1x5V5yxLpA8ZzVQ7NTKKi+jDoVY2Efu6ZyzgT9n5KgG2kWliXA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
sass-embedded@1.92.1:
|
||||||
|
resolution: {integrity: sha512-28YwLnF5atAhogt3E4hXzz/NB9dwKffyw08a7DEasLh94P7+aELkG3ENSHYCWB9QFN14hYNLfwr9ozUsPDhcDQ==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
sass@1.92.1:
|
||||||
|
resolution: {integrity: sha512-ffmsdbwqb3XeyR8jJR6KelIXARM9bFQe8A6Q3W4Klmwy5Ckd5gz7jgUNHo4UOqutU5Sk1DtKLbpDP0nLCg1xqQ==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
scheduler@0.26.0:
|
scheduler@0.26.0:
|
||||||
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
||||||
|
|
||||||
@@ -850,6 +1146,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
set-cookie-parser@2.7.1:
|
||||||
|
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -870,10 +1169,29 @@ packages:
|
|||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
supports-color@8.1.1:
|
||||||
|
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
sync-child-process@1.0.2:
|
||||||
|
resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
sync-message-port@1.1.3:
|
||||||
|
resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
to-regex-range@5.0.1:
|
||||||
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
|
engines: {node: '>=8.0'}
|
||||||
|
|
||||||
|
tslib@2.8.1:
|
||||||
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -887,6 +1205,9 @@ packages:
|
|||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
|
varint@6.0.0:
|
||||||
|
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
||||||
|
|
||||||
vite@7.1.5:
|
vite@7.1.5:
|
||||||
resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==}
|
resolution: {integrity: sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -1057,6 +1378,8 @@ snapshots:
|
|||||||
'@babel/helper-string-parser': 7.27.1
|
'@babel/helper-string-parser': 7.27.1
|
||||||
'@babel/helper-validator-identifier': 7.27.1
|
'@babel/helper-validator-identifier': 7.27.1
|
||||||
|
|
||||||
|
'@bufbuild/protobuf@2.7.0': {}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.9':
|
'@esbuild/aix-ppc64@0.25.9':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -1179,6 +1502,8 @@ snapshots:
|
|||||||
'@eslint/core': 0.15.2
|
'@eslint/core': 0.15.2
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
|
|
||||||
|
'@fontsource/plus-jakarta-sans@5.2.6': {}
|
||||||
|
|
||||||
'@humanfs/core@0.19.1': {}
|
'@humanfs/core@0.19.1': {}
|
||||||
|
|
||||||
'@humanfs/node@0.16.7':
|
'@humanfs/node@0.16.7':
|
||||||
@@ -1209,6 +1534,72 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-arm64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-darwin-x64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-freebsd-x64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-glibc@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-ia32@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher-win32-x64@2.5.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@parcel/watcher@2.5.1':
|
||||||
|
dependencies:
|
||||||
|
detect-libc: 1.0.3
|
||||||
|
is-glob: 4.0.3
|
||||||
|
micromatch: 4.0.8
|
||||||
|
node-addon-api: 7.1.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@parcel/watcher-android-arm64': 2.5.1
|
||||||
|
'@parcel/watcher-darwin-arm64': 2.5.1
|
||||||
|
'@parcel/watcher-darwin-x64': 2.5.1
|
||||||
|
'@parcel/watcher-freebsd-x64': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm-glibc': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm-musl': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm64-glibc': 2.5.1
|
||||||
|
'@parcel/watcher-linux-arm64-musl': 2.5.1
|
||||||
|
'@parcel/watcher-linux-x64-glibc': 2.5.1
|
||||||
|
'@parcel/watcher-linux-x64-musl': 2.5.1
|
||||||
|
'@parcel/watcher-win32-arm64': 2.5.1
|
||||||
|
'@parcel/watcher-win32-ia32': 2.5.1
|
||||||
|
'@parcel/watcher-win32-x64': 2.5.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@phosphor-icons/react@2.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
'@rolldown/pluginutils@1.0.0-beta.34': {}
|
'@rolldown/pluginutils@1.0.0-beta.34': {}
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.50.1':
|
'@rollup/rollup-android-arm-eabi@4.50.1':
|
||||||
@@ -1307,7 +1698,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
|
|
||||||
'@vitejs/plugin-react@5.0.2(vite@7.1.5)':
|
'@vitejs/plugin-react@5.0.2(vite@7.1.5(sass-embedded@1.92.1)(sass@1.92.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.28.4
|
'@babel/core': 7.28.4
|
||||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
|
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
|
||||||
@@ -1315,7 +1706,7 @@ snapshots:
|
|||||||
'@rolldown/pluginutils': 1.0.0-beta.34
|
'@rolldown/pluginutils': 1.0.0-beta.34
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
react-refresh: 0.17.0
|
react-refresh: 0.17.0
|
||||||
vite: 7.1.5
|
vite: 7.1.5(sass-embedded@1.92.1)(sass@1.92.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -1345,6 +1736,11 @@ snapshots:
|
|||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
concat-map: 0.0.1
|
concat-map: 0.0.1
|
||||||
|
|
||||||
|
braces@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
fill-range: 7.1.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
browserslist@4.25.4:
|
browserslist@4.25.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001741
|
caniuse-lite: 1.0.30001741
|
||||||
@@ -1352,6 +1748,8 @@ snapshots:
|
|||||||
node-releases: 2.0.20
|
node-releases: 2.0.20
|
||||||
update-browserslist-db: 1.1.3(browserslist@4.25.4)
|
update-browserslist-db: 1.1.3(browserslist@4.25.4)
|
||||||
|
|
||||||
|
buffer-builder@0.2.0: {}
|
||||||
|
|
||||||
callsites@3.1.0: {}
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001741: {}
|
caniuse-lite@1.0.30001741: {}
|
||||||
@@ -1361,16 +1759,27 @@ snapshots:
|
|||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
chokidar@4.0.3:
|
||||||
|
dependencies:
|
||||||
|
readdirp: 4.1.2
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
classnames@2.5.1: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
colorjs.io@0.5.2: {}
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
|
cookie@1.0.2: {}
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@@ -1385,6 +1794,9 @@ snapshots:
|
|||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
|
|
||||||
|
detect-libc@1.0.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
electron-to-chromium@1.5.215: {}
|
electron-to-chromium@1.5.215: {}
|
||||||
|
|
||||||
esbuild@0.25.9:
|
esbuild@0.25.9:
|
||||||
@@ -1509,6 +1921,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 4.0.1
|
flat-cache: 4.0.1
|
||||||
|
|
||||||
|
fill-range@7.1.1:
|
||||||
|
dependencies:
|
||||||
|
to-regex-range: 5.0.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
find-up@5.0.0:
|
find-up@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 6.0.0
|
locate-path: 6.0.0
|
||||||
@@ -1538,6 +1955,8 @@ snapshots:
|
|||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
|
immutable@5.1.3: {}
|
||||||
|
|
||||||
import-fresh@3.3.1:
|
import-fresh@3.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
parent-module: 1.0.1
|
parent-module: 1.0.1
|
||||||
@@ -1551,6 +1970,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-extglob: 2.1.1
|
is-extglob: 2.1.1
|
||||||
|
|
||||||
|
is-number@7.0.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
isexe@2.0.0: {}
|
isexe@2.0.0: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
@@ -1588,6 +2010,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
||||||
|
micromatch@4.0.8:
|
||||||
|
dependencies:
|
||||||
|
braces: 3.0.3
|
||||||
|
picomatch: 2.3.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
minimatch@3.1.2:
|
minimatch@3.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 1.1.12
|
brace-expansion: 1.1.12
|
||||||
@@ -1598,6 +2026,9 @@ snapshots:
|
|||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
|
node-addon-api@7.1.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
node-releases@2.0.20: {}
|
node-releases@2.0.20: {}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
@@ -1627,6 +2058,9 @@ snapshots:
|
|||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
picomatch@2.3.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
picomatch@4.0.3: {}
|
picomatch@4.0.3: {}
|
||||||
|
|
||||||
postcss@8.5.6:
|
postcss@8.5.6:
|
||||||
@@ -1646,8 +2080,25 @@ snapshots:
|
|||||||
|
|
||||||
react-refresh@0.17.0: {}
|
react-refresh@0.17.0: {}
|
||||||
|
|
||||||
|
react-router-dom@7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
react-router: 7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
|
||||||
|
react-router@7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
|
||||||
|
dependencies:
|
||||||
|
cookie: 1.0.2
|
||||||
|
react: 19.1.1
|
||||||
|
set-cookie-parser: 2.7.1
|
||||||
|
optionalDependencies:
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
react@19.1.1: {}
|
react@19.1.1: {}
|
||||||
|
|
||||||
|
readdirp@4.1.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
rollup@4.50.1:
|
rollup@4.50.1:
|
||||||
@@ -1677,10 +2128,113 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc': 4.50.1
|
'@rollup/rollup-win32-x64-msvc': 4.50.1
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
rxjs@7.8.2:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
sass-embedded-all-unknown@1.92.1:
|
||||||
|
dependencies:
|
||||||
|
sass: 1.92.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-android-arm64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-android-arm@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-android-riscv64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-android-x64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-darwin-arm64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-darwin-x64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-arm64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-arm@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-arm64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-arm@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-riscv64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-musl-x64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-riscv64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-linux-x64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-unknown-all@1.92.1:
|
||||||
|
dependencies:
|
||||||
|
sass: 1.92.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-win32-arm64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded-win32-x64@1.92.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sass-embedded@1.92.1:
|
||||||
|
dependencies:
|
||||||
|
'@bufbuild/protobuf': 2.7.0
|
||||||
|
buffer-builder: 0.2.0
|
||||||
|
colorjs.io: 0.5.2
|
||||||
|
immutable: 5.1.3
|
||||||
|
rxjs: 7.8.2
|
||||||
|
supports-color: 8.1.1
|
||||||
|
sync-child-process: 1.0.2
|
||||||
|
varint: 6.0.0
|
||||||
|
optionalDependencies:
|
||||||
|
sass-embedded-all-unknown: 1.92.1
|
||||||
|
sass-embedded-android-arm: 1.92.1
|
||||||
|
sass-embedded-android-arm64: 1.92.1
|
||||||
|
sass-embedded-android-riscv64: 1.92.1
|
||||||
|
sass-embedded-android-x64: 1.92.1
|
||||||
|
sass-embedded-darwin-arm64: 1.92.1
|
||||||
|
sass-embedded-darwin-x64: 1.92.1
|
||||||
|
sass-embedded-linux-arm: 1.92.1
|
||||||
|
sass-embedded-linux-arm64: 1.92.1
|
||||||
|
sass-embedded-linux-musl-arm: 1.92.1
|
||||||
|
sass-embedded-linux-musl-arm64: 1.92.1
|
||||||
|
sass-embedded-linux-musl-riscv64: 1.92.1
|
||||||
|
sass-embedded-linux-musl-x64: 1.92.1
|
||||||
|
sass-embedded-linux-riscv64: 1.92.1
|
||||||
|
sass-embedded-linux-x64: 1.92.1
|
||||||
|
sass-embedded-unknown-all: 1.92.1
|
||||||
|
sass-embedded-win32-arm64: 1.92.1
|
||||||
|
sass-embedded-win32-x64: 1.92.1
|
||||||
|
|
||||||
|
sass@1.92.1:
|
||||||
|
dependencies:
|
||||||
|
chokidar: 4.0.3
|
||||||
|
immutable: 5.1.3
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
optionalDependencies:
|
||||||
|
'@parcel/watcher': 2.5.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
scheduler@0.26.0: {}
|
scheduler@0.26.0: {}
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
|
set-cookie-parser@2.7.1: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
shebang-regex: 3.0.0
|
shebang-regex: 3.0.0
|
||||||
@@ -1695,11 +2249,28 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
|
|
||||||
|
supports-color@8.1.1:
|
||||||
|
dependencies:
|
||||||
|
has-flag: 4.0.0
|
||||||
|
|
||||||
|
sync-child-process@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
sync-message-port: 1.1.3
|
||||||
|
|
||||||
|
sync-message-port@1.1.3: {}
|
||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
|
|
||||||
|
to-regex-range@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
is-number: 7.0.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
@@ -1714,7 +2285,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
||||||
vite@7.1.5:
|
varint@6.0.0: {}
|
||||||
|
|
||||||
|
vite@7.1.5(sass-embedded@1.92.1)(sass@1.92.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.25.9
|
esbuild: 0.25.9
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -1724,6 +2297,8 @@ snapshots:
|
|||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
sass: 1.92.1
|
||||||
|
sass-embedded: 1.92.1
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@@ -1,10 +1,31 @@
|
|||||||
const App = () => {
|
import { createBrowserRouter, Navigate, RouterProvider } from "react-router-dom";
|
||||||
|
import { UserProvider } from '@/common/contexts/UserContext.jsx';
|
||||||
|
import "@/common/styles/main.sass";
|
||||||
|
import Root from "@/common/layouts/Root.jsx";
|
||||||
|
import "@fontsource/plus-jakarta-sans/300.css";
|
||||||
|
import "@fontsource/plus-jakarta-sans/400.css";
|
||||||
|
import "@fontsource/plus-jakarta-sans/600.css";
|
||||||
|
import "@fontsource/plus-jakarta-sans/700.css";
|
||||||
|
import "@fontsource/plus-jakarta-sans/800.css";
|
||||||
|
|
||||||
return (
|
const Placeholder = ({title}) => <div className="content"><h2 style={{fontSize:'1rem'}}>{title}</h2><p className="muted">Content coming soon.</p></div>;
|
||||||
<>
|
|
||||||
<h1>vite init</h1>
|
const App = () => {
|
||||||
</>
|
const router = createBrowserRouter([
|
||||||
)
|
{
|
||||||
}
|
path: "/",
|
||||||
|
element: <Root />,
|
||||||
|
children: [
|
||||||
|
{ path: "/", element: <Navigate to="/dashboard" /> },
|
||||||
|
{ path: "/dashboard", element: <Placeholder title="Dashboard" /> },
|
||||||
|
{ path: "/servers", element: <Placeholder title="Servers" /> },
|
||||||
|
{ path: "/settings", element: <Placeholder title="Settings" /> },
|
||||||
|
{ path: "/admin/users", element: <Placeholder title="User Management" /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <UserProvider><RouterProvider router={router}/></UserProvider>;
|
||||||
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
38
webui/src/common/components/Button/Button.jsx
Normal file
38
webui/src/common/components/Button/Button.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React from "react";
|
||||||
|
import cn from "classnames";
|
||||||
|
import "./styles.sass";
|
||||||
|
|
||||||
|
export const Button = ({
|
||||||
|
as: Component = "button",
|
||||||
|
variant = "primary",
|
||||||
|
size = "md",
|
||||||
|
full = false,
|
||||||
|
icon,
|
||||||
|
iconRight,
|
||||||
|
loading = false,
|
||||||
|
disabled,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const isDisabled = disabled || loading;
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
className={cn(
|
||||||
|
"btn",
|
||||||
|
`btn--${variant}`,
|
||||||
|
`btn--${size}`,
|
||||||
|
full && "btn--full",
|
||||||
|
loading && "is-loading",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{loading && <span className="btn-spinner" aria-hidden />}
|
||||||
|
{icon && <span className="btn-icon btn-icon--left">{icon}</span>}
|
||||||
|
<span className="btn-label">{children}</span>
|
||||||
|
{iconRight && <span className="btn-icon btn-icon--right">{iconRight}</span>}
|
||||||
|
</Component>
|
||||||
|
);
|
||||||
|
};
|
1
webui/src/common/components/Button/index.js
Normal file
1
webui/src/common/components/Button/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Button as default } from "./Button.jsx";
|
91
webui/src/common/components/Button/styles.sass
Normal file
91
webui/src/common/components/Button/styles.sass
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
.btn
|
||||||
|
--c-bg: #ffffff
|
||||||
|
--c-bg-hover: #f2f5f8
|
||||||
|
--c-bg-active: #e6ebf0
|
||||||
|
--c-border: #dfe3e8
|
||||||
|
--c-border-hover: #c7ced6
|
||||||
|
--c-text: #1f2429
|
||||||
|
--c-accent: #0f62fe
|
||||||
|
--c-danger: #d93025
|
||||||
|
position: relative
|
||||||
|
display: inline-flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
gap: .6rem
|
||||||
|
font-family: inherit
|
||||||
|
font-weight: 600
|
||||||
|
line-height: 1.2
|
||||||
|
cursor: pointer
|
||||||
|
border: 1px solid var(--c-border)
|
||||||
|
background: var(--c-bg)
|
||||||
|
color: var(--c-text)
|
||||||
|
border-radius: 12px
|
||||||
|
transition: all .2s ease
|
||||||
|
user-select: none
|
||||||
|
text-decoration: none
|
||||||
|
&:hover:not(:disabled)
|
||||||
|
background: var(--c-bg-hover)
|
||||||
|
border-color: var(--c-border-hover)
|
||||||
|
&:active:not(:disabled)
|
||||||
|
background: var(--c-bg-active)
|
||||||
|
transform: translateY(1px)
|
||||||
|
&:focus-visible
|
||||||
|
outline: 2px solid var(--c-accent)
|
||||||
|
outline-offset: 2px
|
||||||
|
&:disabled
|
||||||
|
opacity: .55
|
||||||
|
cursor: not-allowed
|
||||||
|
&.btn--full
|
||||||
|
width: 100%
|
||||||
|
&.btn--sm
|
||||||
|
font-size: .85rem
|
||||||
|
padding: .7rem 1rem
|
||||||
|
&.btn--md
|
||||||
|
font-size: .95rem
|
||||||
|
padding: .85rem 1.25rem
|
||||||
|
&.btn--lg
|
||||||
|
font-size: 1.05rem
|
||||||
|
padding: 1rem 1.5rem
|
||||||
|
&.btn--primary
|
||||||
|
--c-bg: #1f2429
|
||||||
|
--c-bg-hover: #374048
|
||||||
|
--c-bg-active: #2a3038
|
||||||
|
--c-border: #1f2429
|
||||||
|
--c-text: #ffffff
|
||||||
|
background: var(--c-bg)
|
||||||
|
border-color: var(--c-border)
|
||||||
|
&:hover:not(:disabled)
|
||||||
|
background: var(--c-bg-hover)
|
||||||
|
&.btn--subtle
|
||||||
|
--c-bg: #f0f3f6
|
||||||
|
--c-bg-hover: #e6ebf0
|
||||||
|
--c-bg-active: #dfe3e8
|
||||||
|
--c-border: #dfe3e8
|
||||||
|
&.btn--danger
|
||||||
|
--c-bg: #d93025
|
||||||
|
--c-bg-hover: #c22b21
|
||||||
|
--c-bg-active: #a9241b
|
||||||
|
--c-border: #d93025
|
||||||
|
--c-text: #ffffff
|
||||||
|
background: var(--c-bg)
|
||||||
|
border-color: var(--c-border)
|
||||||
|
|
||||||
|
.btn-icon
|
||||||
|
display: inline-flex
|
||||||
|
align-items: center
|
||||||
|
&--left
|
||||||
|
margin-right: .25rem
|
||||||
|
&--right
|
||||||
|
margin-left: .25rem
|
||||||
|
|
||||||
|
.btn-spinner
|
||||||
|
width: 14px
|
||||||
|
height: 14px
|
||||||
|
border: 2px solid rgba(0,0,0,.15)
|
||||||
|
border-top-color: var(--c-text)
|
||||||
|
border-radius: 50%
|
||||||
|
animation: spin .7s linear infinite
|
||||||
|
|
||||||
|
@keyframes spin
|
||||||
|
to
|
||||||
|
transform: rotate(360deg)
|
23
webui/src/common/components/Input/Input.jsx
Normal file
23
webui/src/common/components/Input/Input.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from "react";
|
||||||
|
import cn from "classnames";
|
||||||
|
import "./styles.sass";
|
||||||
|
export const Input = ({
|
||||||
|
label,
|
||||||
|
error,
|
||||||
|
icon,
|
||||||
|
className,
|
||||||
|
containerClassName,
|
||||||
|
type = "text",
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={cn("field", containerClassName)}>
|
||||||
|
{label && <label className="field-label">{label}</label>}
|
||||||
|
<div className={cn("field-control", error && "has-error", icon && "has-icon", className)}>
|
||||||
|
{icon && <span className="field-icon">{icon}</span>}
|
||||||
|
<input type={type} className="field-input" {...rest} />
|
||||||
|
</div>
|
||||||
|
{error && <div className="field-error">{error}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
1
webui/src/common/components/Input/index.js
Normal file
1
webui/src/common/components/Input/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Input as default } from "./Input.jsx";
|
63
webui/src/common/components/Input/styles.sass
Normal file
63
webui/src/common/components/Input/styles.sass
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
.field
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: .5rem
|
||||||
|
font-size: .9rem
|
||||||
|
font-weight: 600
|
||||||
|
color: #374048
|
||||||
|
|
||||||
|
.field-label
|
||||||
|
letter-spacing: .3px
|
||||||
|
margin-bottom: .2rem
|
||||||
|
|
||||||
|
.field-control
|
||||||
|
position: relative
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
background: #ffffff
|
||||||
|
border: 2px solid #e1e8f0
|
||||||
|
border-radius: 16px
|
||||||
|
padding: 1rem 1.2rem
|
||||||
|
transition: all .2s ease
|
||||||
|
&.has-icon .field-input
|
||||||
|
padding-left: 2.2rem
|
||||||
|
&.has-error
|
||||||
|
border-color: #d93025
|
||||||
|
box-shadow: 0 0 0 4px rgba(217, 48, 37, 0.1)
|
||||||
|
&:focus-within
|
||||||
|
border-color: #0f62fe
|
||||||
|
box-shadow: 0 0 0 4px rgba(15, 98, 254, 0.1)
|
||||||
|
transform: translateY(-1px)
|
||||||
|
|
||||||
|
.field-icon
|
||||||
|
position: absolute
|
||||||
|
left: 1rem
|
||||||
|
top: 50%
|
||||||
|
transform: translateY(-50%)
|
||||||
|
display: inline-flex
|
||||||
|
font-size: 1.1rem
|
||||||
|
color: #6b7781
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
|
.field-input
|
||||||
|
appearance: none
|
||||||
|
outline: none
|
||||||
|
background: transparent
|
||||||
|
border: 0
|
||||||
|
color: #1f2429
|
||||||
|
font: inherit
|
||||||
|
font-size: 1rem
|
||||||
|
font-weight: 500
|
||||||
|
width: 100%
|
||||||
|
line-height: 1.3
|
||||||
|
&::placeholder
|
||||||
|
color: #a0abb4
|
||||||
|
font-weight: 400
|
||||||
|
&:focus
|
||||||
|
outline: none
|
||||||
|
|
||||||
|
.field-error
|
||||||
|
font-size: .65rem
|
||||||
|
font-weight: 600
|
||||||
|
color: #d93025
|
||||||
|
letter-spacing: .5px
|
95
webui/src/common/components/ProfileMenu/ProfileMenu.jsx
Normal file
95
webui/src/common/components/ProfileMenu/ProfileMenu.jsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import React, {useState, useRef, useEffect, useContext} from 'react';
|
||||||
|
import {UserCircleIcon, SignOutIcon, CaretDownIcon} from '@phosphor-icons/react';
|
||||||
|
import {UserContext} from '@/common/contexts/UserContext.jsx';
|
||||||
|
import './styles.sass';
|
||||||
|
|
||||||
|
export const ProfileMenu = () => {
|
||||||
|
const {logout} = useContext(UserContext);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const menuRef = useRef(null);
|
||||||
|
|
||||||
|
// Close menu when clicking outside
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event) => {
|
||||||
|
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Close menu on escape key
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEscapeKey = (event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
document.addEventListener('keydown', handleEscapeKey);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleEscapeKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
logout();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleMenu = () => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="profile-menu" ref={menuRef}>
|
||||||
|
<button
|
||||||
|
className={`profile-menu-trigger ${isOpen ? 'active' : ''}`}
|
||||||
|
onClick={toggleMenu}
|
||||||
|
aria-label="User menu"
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
>
|
||||||
|
<div className="profile-menu-avatar">
|
||||||
|
<UserCircleIcon size={16} weight="fill"/>
|
||||||
|
</div>
|
||||||
|
<span className="profile-menu-name">Admin</span>
|
||||||
|
<CaretDownIcon
|
||||||
|
size={14}
|
||||||
|
className={`profile-menu-caret ${isOpen ? 'rotated' : ''}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div className="profile-menu-dropdown">
|
||||||
|
<div className="profile-menu-header">
|
||||||
|
<div className="profile-menu-avatar profile-menu-avatar--large">
|
||||||
|
<UserCircleIcon size={20} weight="fill"/>
|
||||||
|
</div>
|
||||||
|
<div className="profile-menu-info">
|
||||||
|
<div className="profile-menu-name-large">Admin User</div>
|
||||||
|
<div className="profile-menu-role">Administrator</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="profile-menu-divider"></div>
|
||||||
|
|
||||||
|
<div className="profile-menu-actions">
|
||||||
|
<button
|
||||||
|
className="profile-menu-item"
|
||||||
|
onClick={handleLogout}
|
||||||
|
>
|
||||||
|
<SignOutIcon size={16}/>
|
||||||
|
<span>Sign Out</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
1
webui/src/common/components/ProfileMenu/index.js
Normal file
1
webui/src/common/components/ProfileMenu/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { ProfileMenu as default } from './ProfileMenu';
|
123
webui/src/common/components/ProfileMenu/styles.sass
Normal file
123
webui/src/common/components/ProfileMenu/styles.sass
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
.profile-menu
|
||||||
|
position: relative
|
||||||
|
display: inline-block
|
||||||
|
|
||||||
|
.profile-menu-trigger
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
gap: .5rem
|
||||||
|
padding: .5rem .75rem
|
||||||
|
background: transparent
|
||||||
|
border: 1px solid var(--border)
|
||||||
|
border-radius: 10px
|
||||||
|
color: var(--text)
|
||||||
|
font-size: .85rem
|
||||||
|
font-weight: 500
|
||||||
|
cursor: pointer
|
||||||
|
transition: all .2s ease
|
||||||
|
&:hover
|
||||||
|
background: var(--bg-elev)
|
||||||
|
border-color: var(--border-strong)
|
||||||
|
&.active
|
||||||
|
background: var(--bg-elev)
|
||||||
|
border-color: var(--border-strong)
|
||||||
|
.profile-menu-caret
|
||||||
|
transform: rotate(180deg)
|
||||||
|
|
||||||
|
.profile-menu-avatar
|
||||||
|
width: 28px
|
||||||
|
height: 28px
|
||||||
|
background: rgba(15, 98, 254, 0.15)
|
||||||
|
border: 1px solid rgba(15, 98, 254, 0.25)
|
||||||
|
border-radius: 50%
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
color: var(--accent)
|
||||||
|
flex-shrink: 0
|
||||||
|
backdrop-filter: blur(8px)
|
||||||
|
&--large
|
||||||
|
width: 36px
|
||||||
|
height: 36px
|
||||||
|
background: rgba(15, 98, 254, 0.2)
|
||||||
|
border: 1px solid rgba(15, 98, 254, 0.3)
|
||||||
|
|
||||||
|
.profile-menu-name
|
||||||
|
font-weight: 600
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
.profile-menu-caret
|
||||||
|
transition: transform .2s ease
|
||||||
|
color: var(--text-dim)
|
||||||
|
&.rotated
|
||||||
|
transform: rotate(180deg)
|
||||||
|
|
||||||
|
.profile-menu-dropdown
|
||||||
|
position: absolute
|
||||||
|
top: calc(100% + .5rem)
|
||||||
|
right: 0
|
||||||
|
min-width: 200px
|
||||||
|
background: var(--bg-alt)
|
||||||
|
border: 1px solid var(--border)
|
||||||
|
border-radius: 12px
|
||||||
|
box-shadow: 0 8px 32px -8px rgba(31,36,41,.15), 0 4px 16px -4px rgba(31,36,41,.1)
|
||||||
|
z-index: 1000
|
||||||
|
animation: dropdownFadeIn .2s ease-out
|
||||||
|
|
||||||
|
.profile-menu-header
|
||||||
|
padding: 1rem
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
gap: .75rem
|
||||||
|
|
||||||
|
.profile-menu-info
|
||||||
|
flex: 1
|
||||||
|
min-width: 0
|
||||||
|
|
||||||
|
.profile-menu-name-large
|
||||||
|
font-size: .9rem
|
||||||
|
font-weight: 600
|
||||||
|
color: var(--text)
|
||||||
|
margin-bottom: .1rem
|
||||||
|
|
||||||
|
.profile-menu-role
|
||||||
|
font-size: .75rem
|
||||||
|
color: var(--text-dim)
|
||||||
|
font-weight: 500
|
||||||
|
|
||||||
|
.profile-menu-divider
|
||||||
|
height: 1px
|
||||||
|
background: var(--border)
|
||||||
|
margin: 0 .5rem
|
||||||
|
|
||||||
|
.profile-menu-actions
|
||||||
|
padding: .5rem
|
||||||
|
|
||||||
|
.profile-menu-item
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
gap: .75rem
|
||||||
|
width: 100%
|
||||||
|
padding: .75rem
|
||||||
|
background: transparent
|
||||||
|
border: none
|
||||||
|
border-radius: 8px
|
||||||
|
color: var(--text)
|
||||||
|
font-size: .85rem
|
||||||
|
font-weight: 500
|
||||||
|
cursor: pointer
|
||||||
|
transition: all .15s ease
|
||||||
|
text-align: left
|
||||||
|
&:hover
|
||||||
|
background: var(--bg-elev)
|
||||||
|
color: var(--danger)
|
||||||
|
svg
|
||||||
|
color: var(--danger)
|
||||||
|
|
||||||
|
@keyframes dropdownFadeIn
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
transform: translateY(-4px) scale(0.95)
|
||||||
|
to
|
||||||
|
opacity: 1
|
||||||
|
transform: translateY(0) scale(1)
|
60
webui/src/common/components/Sidebar/Sidebar.jsx
Normal file
60
webui/src/common/components/Sidebar/Sidebar.jsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import { HouseIcon, GearSixIcon, SquaresFourIcon, CubeIcon, UsersIcon } from '@phosphor-icons/react';
|
||||||
|
import { UserContext } from '@/common/contexts/UserContext.jsx';
|
||||||
|
import './styles.sass';
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ to: '/dashboard', label: 'Dashboard', icon: <HouseIcon weight="duotone" /> },
|
||||||
|
{ to: '/servers', label: 'Servers', icon: <SquaresFourIcon weight="duotone" /> },
|
||||||
|
{ to: '/settings', label: 'Settings', icon: <GearSixIcon weight="duotone" /> },
|
||||||
|
];
|
||||||
|
|
||||||
|
const adminNavItems = [
|
||||||
|
{ to: '/admin/users', label: 'User Management', icon: <UsersIcon weight="duotone" /> },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Sidebar = () => {
|
||||||
|
const { user } = useContext(UserContext);
|
||||||
|
const isAdmin = user?.role === 'admin' || user?.is_admin === true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className="sidebar">
|
||||||
|
<div className="sidebar-brand">
|
||||||
|
<CubeIcon size={24} weight="duotone" />
|
||||||
|
<span>Arkendro</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav className="sidebar-nav">
|
||||||
|
<div className="nav-section">
|
||||||
|
{navItems.map(item => (
|
||||||
|
<NavLink
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
className={({isActive}) => `nav-item ${isActive ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
<span className="nav-item-icon">{item.icon}</span>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<div className="nav-section">
|
||||||
|
<div className="nav-section-title">Admin</div>
|
||||||
|
{adminNavItems.map(item => (
|
||||||
|
<NavLink
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
className={({isActive}) => `nav-item ${isActive ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
<span className="nav-item-icon">{item.icon}</span>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
};
|
1
webui/src/common/components/Sidebar/index.js
Normal file
1
webui/src/common/components/Sidebar/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Sidebar as default } from './Sidebar';
|
69
webui/src/common/components/Sidebar/styles.sass
Normal file
69
webui/src/common/components/Sidebar/styles.sass
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
.sidebar
|
||||||
|
width: var(--sidebar-width)
|
||||||
|
background: var(--bg-alt)
|
||||||
|
border-right: 1px solid var(--border)
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
padding: 1.5rem 1.2rem
|
||||||
|
gap: 1.8rem
|
||||||
|
|
||||||
|
.sidebar-brand
|
||||||
|
font-size: 1.1rem
|
||||||
|
font-weight: 700
|
||||||
|
letter-spacing: .3px
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
gap: .75rem
|
||||||
|
color: var(--text)
|
||||||
|
padding-bottom: .5rem
|
||||||
|
svg
|
||||||
|
color: var(--accent)
|
||||||
|
flex-shrink: 0
|
||||||
|
|
||||||
|
.sidebar-nav
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 1.2rem
|
||||||
|
|
||||||
|
.nav-section
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: .4rem
|
||||||
|
|
||||||
|
.nav-section-title
|
||||||
|
font-size: .75rem
|
||||||
|
font-weight: 700
|
||||||
|
text-transform: uppercase
|
||||||
|
letter-spacing: .8px
|
||||||
|
color: var(--text-dim)
|
||||||
|
margin-bottom: .3rem
|
||||||
|
padding: 0 .9rem
|
||||||
|
|
||||||
|
.nav-item
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
gap: .75rem
|
||||||
|
padding: .75rem .9rem
|
||||||
|
color: var(--text-dim)
|
||||||
|
font-size: .85rem
|
||||||
|
font-weight: 600
|
||||||
|
letter-spacing: .3px
|
||||||
|
border-radius: 10px
|
||||||
|
transition: all .2s ease
|
||||||
|
cursor: pointer
|
||||||
|
text-decoration: none
|
||||||
|
&:hover
|
||||||
|
background: var(--bg-elev)
|
||||||
|
color: var(--text)
|
||||||
|
transform: translateX(2px)
|
||||||
|
&.active
|
||||||
|
background: var(--text)
|
||||||
|
color: #ffffff
|
||||||
|
font-weight: 700
|
||||||
|
box-shadow: 0 2px 8px -2px rgba(31, 36, 41, 0.2)
|
||||||
|
|
||||||
|
.nav-item-icon
|
||||||
|
font-size: 1.2rem
|
||||||
|
display: inline-flex
|
||||||
|
color: inherit
|
||||||
|
min-width: 1.2rem
|
71
webui/src/common/contexts/UserContext.jsx
Normal file
71
webui/src/common/contexts/UserContext.jsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
import { getRequest, postRequest } from "@/common/utils/RequestUtil.js";
|
||||||
|
import Login from "@/pages/Login/index.js";
|
||||||
|
|
||||||
|
export const UserContext = createContext({});
|
||||||
|
|
||||||
|
export const UserProvider = ({ children }) => {
|
||||||
|
|
||||||
|
const [sessionToken, setSessionToken] = useState(localStorage.getItem("sessionToken"));
|
||||||
|
const [isSetupCompleted, setIsSetupCompleted] = useState(false);
|
||||||
|
const [user, setUser] = useState(null);
|
||||||
|
|
||||||
|
const updateSessionToken = (sessionToken) => {
|
||||||
|
setSessionToken(sessionToken);
|
||||||
|
localStorage.setItem("sessionToken", sessionToken);
|
||||||
|
login();
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkFirstTimeSetup = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getRequest("setup/status");
|
||||||
|
setIsSetupCompleted(response?.first_user_exists);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
try {
|
||||||
|
const userObj = await getRequest("accounts/me");
|
||||||
|
setUser(userObj);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message === "Unauthorized") {
|
||||||
|
setSessionToken(null);
|
||||||
|
localStorage.removeItem("sessionToken");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
try {
|
||||||
|
await postRequest("auth/logout", { token: sessionToken });
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
const tokenFromUrl = searchParams.get('token');
|
||||||
|
const error = searchParams.get('error');
|
||||||
|
|
||||||
|
if (tokenFromUrl) {
|
||||||
|
updateSessionToken(tokenFromUrl);
|
||||||
|
} else if (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sessionToken ? login() : checkFirstTimeSetup();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ updateSessionToken, user, sessionToken, isSetupCompleted, login, logout }}>
|
||||||
|
{user == null && <Login />}
|
||||||
|
{user !== null && children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
22
webui/src/common/layouts/Root.jsx
Normal file
22
webui/src/common/layouts/Root.jsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import Sidebar from '@/common/components/Sidebar';
|
||||||
|
import ProfileMenu from '@/common/components/ProfileMenu';
|
||||||
|
|
||||||
|
const Root = () => {
|
||||||
|
return (
|
||||||
|
<div className="layout">
|
||||||
|
<Sidebar />
|
||||||
|
<div className="main">
|
||||||
|
<header className="topbar">
|
||||||
|
<h3>Dashboard</h3>
|
||||||
|
<div className="grow"></div>
|
||||||
|
<ProfileMenu />
|
||||||
|
</header>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Root;
|
69
webui/src/common/styles/main.sass
Normal file
69
webui/src/common/styles/main.sass
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
:root
|
||||||
|
--bg: #f7f9fb
|
||||||
|
--bg-alt: #ffffff
|
||||||
|
--bg-elev: #f0f3f6
|
||||||
|
--border: #dfe3e8
|
||||||
|
--border-strong: #c7ced6
|
||||||
|
--text: #1f2429
|
||||||
|
--text-dim: #5c6a78
|
||||||
|
--accent: #0f62fe
|
||||||
|
--danger: #d93025
|
||||||
|
--radius-sm: 4px
|
||||||
|
--radius: 6px
|
||||||
|
--radius-lg: 10px
|
||||||
|
--sidebar-width: 240px
|
||||||
|
|
||||||
|
*, *::before, *::after
|
||||||
|
box-sizing: border-box
|
||||||
|
|
||||||
|
html, body, #root
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
body
|
||||||
|
margin: 0
|
||||||
|
font-family: "Plus Jakarta Sans", sans-serif
|
||||||
|
background: var(--bg)
|
||||||
|
color: var(--text)
|
||||||
|
-webkit-font-smoothing: antialiased
|
||||||
|
font-size: 14px
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6, p
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
ul
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
list-style: none
|
||||||
|
|
||||||
|
a
|
||||||
|
color: var(--accent)
|
||||||
|
text-decoration: none
|
||||||
|
|
||||||
|
.layout
|
||||||
|
display: flex
|
||||||
|
min-height: 100vh
|
||||||
|
|
||||||
|
.main
|
||||||
|
flex: 1
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
min-width: 0
|
||||||
|
|
||||||
|
.topbar
|
||||||
|
height: 54px
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
padding: 0 1.1rem
|
||||||
|
border-bottom: 1px solid var(--border)
|
||||||
|
background: var(--bg-alt)
|
||||||
|
gap: 1rem
|
||||||
|
|
||||||
|
.grow
|
||||||
|
flex: 1
|
||||||
|
|
||||||
|
.content
|
||||||
|
flex: 1
|
||||||
|
padding: 1.4rem 1.3rem 2rem
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 1.2rem
|
48
webui/src/common/utils/RequestUtil.js
Normal file
48
webui/src/common/utils/RequestUtil.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
export const request = async (url, method, body, headers) => {
|
||||||
|
url = url.startsWith("/") ? url.substring(1) : url;
|
||||||
|
|
||||||
|
const response = await fetch(`/api/${url}`, {
|
||||||
|
method: method,
|
||||||
|
headers: {...headers, "Content-Type": "application/json"},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 401) throw new Error("Unauthorized");
|
||||||
|
|
||||||
|
const rawData = await response.text();
|
||||||
|
const data = rawData ? JSON.parse(rawData) : rawData.toString();
|
||||||
|
|
||||||
|
if (data.code >= 300) throw data;
|
||||||
|
|
||||||
|
if (!response.ok) throw data;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getToken = () => {
|
||||||
|
return localStorage.getItem("sessionToken");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sessionRequest = (url, method, token, body) => {
|
||||||
|
return request(url, method, body, {"Authorization": `Bearer ${token}`});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getRequest = (url) => {
|
||||||
|
return sessionRequest(url, "GET", getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
export const postRequest = (url, body) => {
|
||||||
|
return sessionRequest(url, "POST", getToken(), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const putRequest = (url, body) => {
|
||||||
|
return sessionRequest(url, "PUT", getToken(), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteRequest = (url) => {
|
||||||
|
return sessionRequest(url, "DELETE", getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
export const patchRequest = (url, body) => {
|
||||||
|
return sessionRequest(url, "PATCH", getToken(), body);
|
||||||
|
}
|
@@ -1,9 +1,12 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
import {UserProvider} from "@/common/contexts/UserContext.jsx";
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<UserProvider>
|
||||||
|
<App />
|
||||||
|
</UserProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
75
webui/src/pages/Login/Login.jsx
Normal file
75
webui/src/pages/Login/Login.jsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import './styles.sass';
|
||||||
|
import Input from '@/common/components/Input';
|
||||||
|
import Button from '@/common/components/Button';
|
||||||
|
import { UserContext } from '@/common/contexts/UserContext.jsx';
|
||||||
|
import { request } from '@/common/utils/RequestUtil.js';
|
||||||
|
import { LockIcon, UserIcon } from '@phosphor-icons/react';
|
||||||
|
|
||||||
|
export const Login = () => {
|
||||||
|
const { updateSessionToken, isSetupCompleted } = useContext(UserContext);
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [mode, setMode] = useState(isSetupCompleted ? 'login' : 'setup');
|
||||||
|
const [isExiting, setIsExiting] = useState(false);
|
||||||
|
|
||||||
|
React.useEffect(()=>{
|
||||||
|
setMode(isSetupCompleted ? 'login' : 'setup');
|
||||||
|
}, [isSetupCompleted]);
|
||||||
|
|
||||||
|
const submit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
if (mode === 'login') {
|
||||||
|
const data = await request('/auth/login','POST',{username, password});
|
||||||
|
// Trigger fade-out animation before updating token
|
||||||
|
setIsExiting(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
updateSessionToken(data.token);
|
||||||
|
}, 300); // Match the fade-out animation duration
|
||||||
|
} else { // setup
|
||||||
|
await request('/setup/init','POST',{username, password});
|
||||||
|
const data = await request('/auth/login','POST',{username, password});
|
||||||
|
setIsExiting(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
updateSessionToken(data.token);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.error || err.message || 'Error');
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`auth-wrapper ${isExiting ? 'fade-out' : ''}`}>
|
||||||
|
<div className={`auth-box ${isExiting ? 'fade-out' : ''}`}>
|
||||||
|
<div className="auth-header">
|
||||||
|
<div className="auth-title">{mode === 'login' ? 'Welcome Back' : 'Initial Setup'}</div>
|
||||||
|
<div className="auth-subtitle">
|
||||||
|
{mode === 'login'
|
||||||
|
? 'Sign in to your account to continue'
|
||||||
|
: 'Create your admin account to get started'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form className="form" onSubmit={submit}>
|
||||||
|
<div className="form-field form-field--1">
|
||||||
|
<Input label="Username" placeholder="your name" value={username} onChange={e=>setUsername(e.target.value)} icon={<UserIcon size={16} />} autoFocus required />
|
||||||
|
</div>
|
||||||
|
<div className="form-field form-field--2">
|
||||||
|
<Input type="password" label="Password" placeholder="••••••••" value={password} onChange={e=>setPassword(e.target.value)} icon={<LockIcon size={16} />} required />
|
||||||
|
</div>
|
||||||
|
{error && <div className="auth-error form-field form-field--3">{error}</div>}
|
||||||
|
<div className="form-field form-field--4">
|
||||||
|
<Button type="submit" loading={loading} variant="primary" full>{mode === 'login' ? 'Login' : 'Create Admin & Continue'}</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
1
webui/src/pages/Login/index.js
Normal file
1
webui/src/pages/Login/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {Login as default} from "./Login";
|
130
webui/src/pages/Login/styles.sass
Normal file
130
webui/src/pages/Login/styles.sass
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
.auth-wrapper
|
||||||
|
min-height: 100vh
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
padding: 2rem 1rem
|
||||||
|
background: var(--bg)
|
||||||
|
animation: fadeInWrapper .4s ease-out
|
||||||
|
|
||||||
|
.auth-box
|
||||||
|
width: 100%
|
||||||
|
max-width: 480px
|
||||||
|
background: var(--bg-alt)
|
||||||
|
border: 2px solid var(--border)
|
||||||
|
border-radius: 24px
|
||||||
|
padding: 3rem 2.5rem 2.8rem
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 1.8rem
|
||||||
|
box-shadow: 0 8px 32px -8px rgba(31,36,41,.12), 0 4px 16px -4px rgba(31,36,41,.08)
|
||||||
|
position: relative
|
||||||
|
overflow: hidden
|
||||||
|
animation: fadeInUp .6s ease-out
|
||||||
|
|
||||||
|
.auth-title
|
||||||
|
font-size: 1.75rem
|
||||||
|
font-weight: 700
|
||||||
|
letter-spacing: -0.5px
|
||||||
|
text-align: center
|
||||||
|
color: var(--text)
|
||||||
|
margin-bottom: 0.5rem
|
||||||
|
|
||||||
|
.auth-header
|
||||||
|
text-align: center
|
||||||
|
margin-bottom: 1rem
|
||||||
|
|
||||||
|
.auth-subtitle
|
||||||
|
font-size: 1rem
|
||||||
|
font-weight: 400
|
||||||
|
color: var(--text-dim)
|
||||||
|
text-align: center
|
||||||
|
line-height: 1.4
|
||||||
|
|
||||||
|
.auth-error
|
||||||
|
background: rgba(217, 48, 37, 0.1)
|
||||||
|
border: 1px solid rgba(217, 48, 37, 0.2)
|
||||||
|
border-radius: 12px
|
||||||
|
padding: 0.75rem 1rem
|
||||||
|
color: #d93025
|
||||||
|
font-size: 0.9rem
|
||||||
|
font-weight: 600
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
.form
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 1.5rem
|
||||||
|
|
||||||
|
@keyframes fadeInWrapper
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
backdrop-filter: blur(0px)
|
||||||
|
to
|
||||||
|
opacity: 1
|
||||||
|
backdrop-filter: blur(2px)
|
||||||
|
|
||||||
|
@keyframes fadeInUp
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
transform: translateY(40px) scale(0.9)
|
||||||
|
filter: blur(4px)
|
||||||
|
50%
|
||||||
|
opacity: 0.8
|
||||||
|
transform: translateY(20px) scale(0.95)
|
||||||
|
filter: blur(2px)
|
||||||
|
to
|
||||||
|
opacity: 1
|
||||||
|
transform: translateY(0) scale(1)
|
||||||
|
filter: blur(0px)
|
||||||
|
|
||||||
|
// Fade out animations for when dialog closes
|
||||||
|
.auth-wrapper.fade-out
|
||||||
|
animation: fadeOutWrapper .3s ease-in forwards
|
||||||
|
|
||||||
|
.auth-box.fade-out
|
||||||
|
animation: fadeOutDown .3s ease-in forwards
|
||||||
|
|
||||||
|
@keyframes fadeOutWrapper
|
||||||
|
from
|
||||||
|
opacity: 1
|
||||||
|
backdrop-filter: blur(2px)
|
||||||
|
to
|
||||||
|
opacity: 0
|
||||||
|
backdrop-filter: blur(0px)
|
||||||
|
|
||||||
|
@keyframes fadeOutDown
|
||||||
|
from
|
||||||
|
opacity: 1
|
||||||
|
transform: translateY(0) scale(1)
|
||||||
|
filter: blur(0px)
|
||||||
|
to
|
||||||
|
opacity: 0
|
||||||
|
transform: translateY(20px) scale(0.95)
|
||||||
|
filter: blur(2px)
|
||||||
|
|
||||||
|
// Staggered animations for form elements
|
||||||
|
.form-field
|
||||||
|
animation: slideInLeft .5s ease-out both
|
||||||
|
|
||||||
|
.form-field--1
|
||||||
|
animation-delay: .1s
|
||||||
|
|
||||||
|
.form-field--2
|
||||||
|
animation-delay: .2s
|
||||||
|
|
||||||
|
.form-field--3
|
||||||
|
animation-delay: .3s
|
||||||
|
|
||||||
|
.form-field--4
|
||||||
|
animation-delay: .4s
|
||||||
|
|
||||||
|
@keyframes slideInLeft
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
transform: translateX(-20px)
|
||||||
|
filter: blur(2px)
|
||||||
|
to
|
||||||
|
opacity: 1
|
||||||
|
transform: translateX(0)
|
||||||
|
filter: blur(0px)
|
@@ -1,6 +1,20 @@
|
|||||||
import { defineConfig } from 'vite'
|
import {defineConfig} from "vite"
|
||||||
import react from '@vitejs/plugin-react'
|
import react from "@vitejs/plugin-react"
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://localhost:8379"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
Reference in New Issue
Block a user