diff --git a/.gitea/workflows/backend.yaml b/.gitea/workflows/backend.yaml index dff7288..87d6402 100644 --- a/.gitea/workflows/backend.yaml +++ b/.gitea/workflows/backend.yaml @@ -77,6 +77,7 @@ jobs: env: POSTGRES_URL: "postgresql://postgres:postgres@postgres:5432" DATABASE_URL: "postgresql://postgres:postgres@postgres:5432" + SKIP_DOCKER: "1" - name: Upload Test Logs if: ${{ failure() }} uses: actions/upload-artifact@v3 diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 8cab33c..041d5bb 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -32,6 +32,21 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.19" @@ -258,6 +273,21 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clap" version = "4.5.41" @@ -575,6 +605,21 @@ version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -619,6 +664,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -637,8 +693,10 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -809,7 +867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" dependencies = [ "bytes 0.4.12", - "futures", + "futures 0.1.31", "http 0.1.21", "tokio-buf", ] @@ -938,6 +996,30 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -1334,12 +1416,15 @@ name = "nuchat" version = "0.1.0" dependencies = [ "axum", + "chrono", "clap", + "futures 0.3.31", "http 1.3.1", "reqwest", "serde", "serde_json", "sqlx", + "tap", "tokio", "tower", "tower-http", @@ -2111,6 +2196,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes 1.10.1", + "chrono", "crc", "crossbeam-queue", "either", @@ -2135,6 +2221,7 @@ dependencies = [ "tokio-stream", "tracing", "url", + "uuid", ] [[package]] @@ -2186,6 +2273,7 @@ dependencies = [ "bitflags 2.9.1", "byteorder", "bytes 1.10.1", + "chrono", "crc", "digest", "dotenvy", @@ -2214,6 +2302,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -2227,6 +2316,7 @@ dependencies = [ "base64", "bitflags 2.9.1", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -2251,6 +2341,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -2261,6 +2352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", @@ -2276,6 +2368,7 @@ dependencies = [ "thiserror", "tracing", "url", + "uuid", ] [[package]] @@ -2359,6 +2452,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.20.0" @@ -2454,7 +2553,7 @@ checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" dependencies = [ "bytes 0.4.12", "either", - "futures", + "futures 0.1.31", ] [[package]] @@ -2464,7 +2563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" dependencies = [ "crossbeam-utils 0.7.2", - "futures", + "futures 0.1.31", ] [[package]] @@ -2474,7 +2573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", - "futures", + "futures 0.1.31", "log", ] @@ -2506,7 +2605,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" dependencies = [ "crossbeam-utils 0.7.2", - "futures", + "futures 0.1.31", "lazy_static", "log", "mio 0.6.23", @@ -2546,7 +2645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" dependencies = [ "fnv", - "futures", + "futures 0.1.31", ] [[package]] @@ -2556,7 +2655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" dependencies = [ "bytes 0.4.12", - "futures", + "futures 0.1.31", "iovec", "mio 0.6.23", "tokio-io", @@ -2625,7 +2724,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442ba79e23bda499cdaa5ee52b3776bf08cb84a1d5ca6d3d82bfd4f12c1eefc3" dependencies = [ - "futures", + "futures 0.1.31", "http 0.1.21", "http-body 0.1.0", "http-connection", @@ -2646,7 +2745,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc0c98637d23732f8de6dfd16494c9f1559c3b9e20b4a46462c8f9b9e827bfa" dependencies = [ - "futures", + "futures 0.1.31", ] [[package]] @@ -2970,6 +3069,41 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.1.3" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 50a57a3..c68853c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -4,12 +4,15 @@ version = "0.1.0" edition = "2024" [dependencies] -axum = "0.8.4" +axum = { version = "0.8.4", features = [] } +chrono = { version = "0.4.41", features = ["serde"] } clap = { version = "4.5.41", features = ["derive"] } +futures = "0.3.31" http = "1.3.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.141" -sqlx = { version = "0.8.6", features = ["postgres", "macros", "runtime-tokio"] } +sqlx = { version = "0.8.6", features = ["postgres", "macros", "runtime-tokio", "uuid", "chrono"] } +tap = "1.0.1" tokio = { version = "1.0", features = ["full"] } tower = { version = "0.5.2", features = ["full"] } tower-http = { version = "0.6.6", features = ["timeout", "trace", "auth", "request-id"] } diff --git a/backend/migrations/20250728102640_create_servers_table.sql b/backend/migrations/20250728102640_create_servers_table.sql new file mode 100644 index 0000000..a4afe12 --- /dev/null +++ b/backend/migrations/20250728102640_create_servers_table.sql @@ -0,0 +1,7 @@ +-- Add migration script here +CREATE TABLE servers ( + id uuid NOT NULL, + PRIMARY KEY(id), + name TEXT NOT NULL, + created_at timestamptz NOT NULL +) \ No newline at end of file diff --git a/backend/migrations/20250728150137_create_messages_table.sql b/backend/migrations/20250728150137_create_messages_table.sql new file mode 100644 index 0000000..ed48aa0 --- /dev/null +++ b/backend/migrations/20250728150137_create_messages_table.sql @@ -0,0 +1,7 @@ +-- Add migration script here +CREATE TABLE messages ( + id uuid NOT NULL, + PRIMARY KEY(id), + contents TEXT NOT NULL, + created_at timestamptz NOT NULL +) diff --git a/backend/scripts/create_test_db.sql b/backend/scripts/create_test_db.sql deleted file mode 100644 index 2c89ee4..0000000 --- a/backend/scripts/create_test_db.sql +++ /dev/null @@ -1,2 +0,0 @@ -DROP DATABASE IF EXISTS nuchat_test; -CREATE DATABASE nuchat_test; diff --git a/backend/scripts/test.sh b/backend/scripts/test.sh index 057d4a8..3a5104d 100755 --- a/backend/scripts/test.sh +++ b/backend/scripts/test.sh @@ -8,13 +8,26 @@ if ! command -v cargo-nextest > /dev/null 2>&1; then exit 1 fi -psql "$POSTGRES_URL" -f ./scripts/create_test_db.sql - -if [ "$?" -ne "0" ]; then - echo "Unable to connect to database, make sure it is started" +if ! command -v sqlx > /dev/null 2>&1; then + echo "Command not found sqlx" + echo "Try installing with cargo install sqlx-cli" exit 1 fi +export DATABASE_URL="$POSTGRES_URL/nuchat_dev" + +if [ -z "$SKIP_DOCKER" ]; then + # force restart database so no connections + # prevent database from being dropped + docker compose -f ../docker-compose.yml down + docker compose -f ../docker-compose.yml up -d db + sleep 1 +fi + +# recreate database and tables +sqlx database drop -y +sqlx database create +sqlx migrate run if [ ! -d logs ]; then mkdir logs @@ -24,7 +37,8 @@ fi curl -s -X POST localhost:7001/admin/shutdown 2>&1 > /dev/null # start server -cargo run -- --port 7001 --postgres-url "$POSTGRES_URL" 2>&1 > logs/nuchat.log & +cargo run -- --port 7001 --postgres-url "$POSTGRES_URL" --database "nuchat_dev" 2>&1 > logs/nuchat.log & +sleep 1 # run tests -cargo nextest run --color=always 2>&1 | tee logs/test-output.log +cargo nextest run --color=always --no-fail-fast 2>&1 | tee logs/test-output.log diff --git a/backend/src/main.rs b/backend/src/main.rs index dbdf488..832d5ba 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -28,7 +28,7 @@ struct Args { admin_secret: Option, /// postgres base url, should container users and host info - #[arg(long, default_value = "postgres://postgres:postgres@localhost:5432/")] + #[arg(long, default_value = "postgres://postgres:postgres@localhost:5432")] postgres_url: String, /// name of database to use @@ -49,7 +49,9 @@ async fn main() { .with(tracing_subscriber::fmt::layer().with_target(false)) .init(); - let pool = Pool::::connect(&config.0.postgres_url) + let database_url = format!("{}/{}", config.0.postgres_url, config.0.database_name); + info!("Connecting to database: {database_url}"); + let pool = Pool::::connect(&database_url) .await .expect("Could not connect to database"); diff --git a/backend/src/router.rs b/backend/src/router.rs index ecbebe7..6da5ae9 100644 --- a/backend/src/router.rs +++ b/backend/src/router.rs @@ -1,5 +1,7 @@ mod admin; mod healthcheck; +mod messages; +mod servers; use std::sync::mpsc; use std::time::Duration; @@ -32,7 +34,16 @@ pub fn app(state: &AppState) -> (Router, mpsc::Receiver) { Router::new() .with_state(state.clone()) .route("/healthcheck", get(healthcheck::healthcheck)) - .route("/forever", get(std::future::pending::<()>)) + .route( + "/servers", + get(servers::get_servers).post(servers::create_server), + ) + .route("/servers/{id}", get(servers::get_server_by_id)) + .route( + "/messages", + get(messages::get_messages).post(messages::create_message), + ) + .route("/messages/{id}", get(messages::get_message_by_id)) .nest("/admin", admin::router(tx, state)) .layer( ServiceBuilder::new() diff --git a/backend/src/router/healthcheck.rs b/backend/src/router/healthcheck.rs index ebe916c..f1996f7 100644 --- a/backend/src/router/healthcheck.rs +++ b/backend/src/router/healthcheck.rs @@ -41,7 +41,7 @@ mod test { let state = AppState::new(NuState::new( pool, Config { - database_name: String::from("nuchat_test"), + database_name: String::from("nuchat_dev"), ..Config::default() }, )); diff --git a/backend/src/router/messages.rs b/backend/src/router/messages.rs new file mode 100644 index 0000000..098938b --- /dev/null +++ b/backend/src/router/messages.rs @@ -0,0 +1,68 @@ +use axum::{ + Form, Json, + body::Body, + extract::{Path, State}, + response::{IntoResponse, Response}, +}; +use http::StatusCode; +use sqlx::types::chrono; +use tracing::info; +use uuid::Uuid; + +use crate::AppState; + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct Message { + pub contents: String, + pub id: Uuid, + pub created_at: chrono::DateTime, +} + +#[derive(serde::Deserialize)] +pub struct CreateMessage { + pub contents: String, +} + +pub async fn get_messages(State(s): State) -> Result>, StatusCode> { + sqlx::query_as!(Message, r#"SELECT id, contents, created_at FROM messages"#) + .fetch_all(&s.db) + .await + .map(Json) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) +} + +pub async fn create_message( + State(s): State, + Form(message): Form, +) -> Result, StatusCode> { + info!("Creating new message with name: {}", message.contents); + let id = Uuid::now_v7(); + + sqlx::query!( + r"INSERT INTO messages (id, contents, created_at) VALUES($1, $2, current_timestamp) RETURNING id", + id, + message.contents + ).fetch_one(&s.db).await + .map(|row| { + let mut resp = Json(row.id).into_response(); + let status = resp.status_mut(); + *status = StatusCode::CREATED; + info!("Successfully created message Message[{}, {}]", row.id, message.contents); + resp + }).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) +} + +pub async fn get_message_by_id( + Path(id): Path, + State(s): State, +) -> Result, StatusCode> { + sqlx::query_as!( + Message, + r#"SELECT id, contents, created_at FROM messages WHERE id = $1"#, + id + ) + .fetch_optional(&s.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) + .and_then(|mayber_message| mayber_message.map(Json).ok_or(StatusCode::NOT_FOUND)) +} diff --git a/backend/src/router/servers.rs b/backend/src/router/servers.rs new file mode 100644 index 0000000..d24ec3a --- /dev/null +++ b/backend/src/router/servers.rs @@ -0,0 +1,68 @@ +use axum::{ + Form, Json, + body::Body, + extract::{Path, State}, + response::{IntoResponse, Response}, +}; +use http::StatusCode; +use sqlx::types::chrono; +use tracing::info; +use uuid::Uuid; + +use crate::AppState; + +#[derive(serde::Deserialize, serde::Serialize, Debug)] +pub struct Server { + pub name: String, + pub id: Uuid, + pub created_at: chrono::DateTime, +} + +#[derive(serde::Deserialize)] +pub struct CreateServer { + pub name: String, +} + +pub async fn get_servers(State(s): State) -> Result>, StatusCode> { + sqlx::query_as!(Server, r#"SELECT id, name, created_at FROM servers"#) + .fetch_all(&s.db) + .await + .map(Json) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) +} + +pub async fn create_server( + State(s): State, + Form(server): Form, +) -> Result, StatusCode> { + info!("Creating new server with name: {}", server.name); + let id = Uuid::now_v7(); + + sqlx::query!( + r"INSERT INTO servers (id, name, created_at) VALUES($1, $2, current_timestamp) RETURNING id", + id, + server.name + ).fetch_one(&s.db).await + .map(|row| { + let mut resp = Json(row.id).into_response(); + let status = resp.status_mut(); + *status = StatusCode::CREATED; + info!("Successfully created server Server[{}, {}]", row.id, server.name); + resp + }).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) +} + +pub async fn get_server_by_id( + Path(id): Path, + State(s): State, +) -> Result, StatusCode> { + sqlx::query_as!( + Server, + r#"SELECT id, name, created_at FROM servers WHERE id = $1"#, + id + ) + .fetch_optional(&s.db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) + .and_then(|mayber_server| mayber_server.map(Json).ok_or(StatusCode::NOT_FOUND)) +} diff --git a/docker-compose.yml b/docker-compose.yml index 3bce027..25a1a34 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,6 @@ services: environment: POSTGRES_USER: $POSTGRES_USER POSTGRES_PASSWORD: $POSTGRES_PASSWORD - POSTGRES_DB: nuchat + POSTGRES_DB: nuchat_dev ports: - "5432:5432" diff --git a/ui/app/app.vue b/ui/app/app.vue index fa4ee3e..ed9e94c 100644 --- a/ui/app/app.vue +++ b/ui/app/app.vue @@ -1,6 +1,8 @@ diff --git a/ui/app/assets/css/main.css b/ui/app/assets/css/main.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/ui/app/assets/css/main.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file diff --git a/ui/app/components/AppFooter.vue b/ui/app/components/AppFooter.vue new file mode 100644 index 0000000..982cbcb --- /dev/null +++ b/ui/app/components/AppFooter.vue @@ -0,0 +1,7 @@ + diff --git a/ui/app/components/AppHeader.vue b/ui/app/components/AppHeader.vue new file mode 100644 index 0000000..2b2c2bf --- /dev/null +++ b/ui/app/components/AppHeader.vue @@ -0,0 +1,5 @@ + diff --git a/ui/app/components/ServerLink.vue b/ui/app/components/ServerLink.vue new file mode 100644 index 0000000..eff11ce --- /dev/null +++ b/ui/app/components/ServerLink.vue @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/ui/app/components/ServerSidebar.vue b/ui/app/components/ServerSidebar.vue new file mode 100644 index 0000000..721e320 --- /dev/null +++ b/ui/app/components/ServerSidebar.vue @@ -0,0 +1,36 @@ + + + diff --git a/ui/app/layouts/default.vue b/ui/app/layouts/default.vue new file mode 100644 index 0000000..f367174 --- /dev/null +++ b/ui/app/layouts/default.vue @@ -0,0 +1,12 @@ + diff --git a/ui/nuxt.config.ts b/ui/nuxt.config.ts index 73a35c1..a134f76 100644 --- a/ui/nuxt.config.ts +++ b/ui/nuxt.config.ts @@ -1,3 +1,5 @@ +import tailwindcss from "@tailwindcss/vite"; + // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ compatibilityDate: "2025-07-15", @@ -8,6 +10,12 @@ export default defineNuxtConfig({ "@nuxt/test-utils", "@nuxt/test-utils/module", ], + css: ["~/assets/css/main.css"], + vite: { + plugins: [ + tailwindcss(), + ] + }, nitro: { preset: "node-server", }, diff --git a/ui/package-lock.json b/ui/package-lock.json index ceffdbc..0ba03af 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -9,7 +9,9 @@ "dependencies": { "@nuxt/eslint": "1.6.0", "@nuxt/icon": "^1.15.0", + "@tailwindcss/vite": "^4.1.11", "nuxt": "^4.0.1", + "tailwindcss": "^4.1.11", "vue": "^3.5.17", "vue-router": "^4.5.1" }, @@ -4518,6 +4520,277 @@ "eslint": ">=9.0.0" } }, + "node_modules/@tailwindcss/node": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.11", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", + "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "tailwindcss": "4.1.11" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", @@ -7498,6 +7771,19 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -9893,6 +10179,243 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -13216,6 +13739,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tar": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", diff --git a/ui/package.json b/ui/package.json index a9ff433..82ed053 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,7 +16,9 @@ "dependencies": { "@nuxt/eslint": "1.6.0", "@nuxt/icon": "^1.15.0", + "@tailwindcss/vite": "^4.1.11", "nuxt": "^4.0.1", + "tailwindcss": "^4.1.11", "vue": "^3.5.17", "vue-router": "^4.5.1" },