12 Commits
Author SHA1 Message Date
schn33fuchs b62bba0173 Docker shit 2026-07-14 19:23:14 +02:00
schn33fuchs 5585917eb1 rename shit 2026-07-14 17:06:47 +02:00
schn33fuchs b1b29d9f39 Update backend/migrations/20260422094717_user_table.sql 2026-06-20 13:34:46 +02:00
schn33fuchs 042637d248 Add LICENSE.md 2026-06-19 22:35:11 +02:00
schn33fuchs 07003bd305 Update README.md 2026-06-19 20:26:57 +02:00
schn33fuchs 83eadc532c Video 2026-06-19 20:21:47 +02:00
schn33fuchs 2bb0f01e06 Delete frontend/frontend.tar.xz 2026-06-19 20:13:37 +02:00
TheDiluTek 9686441f71 schwarz 2026-06-18 20:16:52 +02:00
TheDiluTek a496338042 schwarze schrift sonst sieht man nix 2026-06-18 20:16:01 +02:00
TheDiluTek 3915f997da Merge branch 'main' of https://git.raffauf-clan.de/schn33fuchs/ticketsystem 2026-06-18 19:58:46 +02:00
TheDiluTek 9704d5792a njix 2026-06-18 19:58:41 +02:00
schn33fuchs c11309a3a5 Docs 2026-06-17 16:35:26 +02:00
20 changed files with 465 additions and 30 deletions
+13
View File
@@ -0,0 +1,13 @@
target/
backend/target/
frontend/target/
frontend/dist/
frontend/node_modules/
dist/
.git/
.idea/
.env
.antigravitycli/
*.pdb
**/*.rs.bk
info ding video.mp4
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Silas Raffauf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+3
View File
@@ -48,6 +48,9 @@ Alternatively download the fontend files from the 'Releases' Page and serve them
> } > }
> ``` > ```
### Example
[![Watch the video]()](https://git.raffauf-clan.de/schn33fuchs/ticketsystem/raw/branch/main/info%20ding%20video.mp4)
## Diagrams ## Diagrams
### Class Diagramm ### Class Diagramm
#### Backend #### Backend
+1 -1
View File
@@ -8,7 +8,7 @@ axum = "0.8.9"
axum-extra = { version = "0.12.6", features = ["cookie", "typed-header", "form"] } axum-extra = { version = "0.12.6", features = ["cookie", "typed-header", "form"] }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls", "chrono"] } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls", "chrono", "migrate"] }
tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros"] }
dotenv = "0.15.0" dotenv = "0.15.0"
chrono = { workspace = true } chrono = { workspace = true }
@@ -6,9 +6,3 @@ CREATE TABLE users (
pwd TEXT, pwd TEXT,
is_admin BOOLEAN NOT NULL DEFAULT false is_admin BOOLEAN NOT NULL DEFAULT false
); );
INSERT INTO users(last_name, first_name, username, pwd, is_admin) VALUES ('Raffauf', 'Silas', 'raffaufsil',
'$argon2id$v=19$m=19456,t=2,p=1$urV91vY49mehWQoShkm6pw$1AGLuMDuFnY1CKuQU9ed89s1u9kvuszQjX+eIXmCBKI', true);
INSERT INTO users(last_name, first_name, username, pwd, is_admin) VALUES ('Misun', 'Sam', 'misunsam',
'$argon2id$v=19$m=19456,t=2,p=1$AMmy1zMOQG8Wg1jE5BXhDg$xhYIZPAPPOEoKQMtzWll/uhG8YJQvcpHKQU2++L23o4', true);
+12 -2
View File
@@ -32,8 +32,10 @@ use crate::env::Env;
/// - `db`: PostgreSQL connection pool for database access (via `sqlx::PgPool`) /// - `db`: PostgreSQL connection pool for database access (via `sqlx::PgPool`)
/// - `env`: [`Env`] configuration loaded from environment variables /// - `env`: [`Env`] configuration loaded from environment variables
pub struct AppState { pub struct AppState {
db: PgPool, /// PostgreSQL connection pool for all database operations
env: Env, pub db: PgPool,
/// Environment configuration with secrets and settings
pub env: Env,
} }
/// Main application entry point. /// Main application entry point.
@@ -75,6 +77,14 @@ async fn main() {
} }
}; };
// Run database migrations on container/application startup
println!("Running database migrations...");
if let Err(err) = sqlx::migrate!("./migrations").run(&pool).await {
println!("Failed to run database migrations: {:?}", err);
std::process::exit(1);
}
println!("Database migrations completed successfully");
// Configure CORS to allow requests from frontend // Configure CORS to allow requests from frontend
let cors = CorsLayer::new() let cors = CorsLayer::new()
.allow_origin(env.origin.parse::<HeaderValue>().unwrap()) .allow_origin(env.origin.parse::<HeaderValue>().unwrap())
+39
View File
@@ -33,15 +33,25 @@ use serde::{Deserialize, Serialize};
/// ``` /// ```
#[derive(Deserialize, Serialize, Debug, PartialEq)] #[derive(Deserialize, Serialize, Debug, PartialEq)]
pub struct TicketResponse { pub struct TicketResponse {
/// Unique ticket identifier
pub id: i32, pub id: i32,
/// Ticket category/type (e.g., "maintenance", "support")
pub category: String, pub category: String,
/// Ticket subject line
pub betreff: String, pub betreff: String,
/// Detailed ticket description
pub description: String, pub description: String,
/// Room number associated with the issue
pub room: i16, pub room: i16,
/// Current ticket status (e.g., "open", "in_progress", "resolved", "archived")
pub status: String, pub status: String,
/// When the ticket was created (UTC timestamp)
pub date: chrono::DateTime<chrono::Utc>, pub date: chrono::DateTime<chrono::Utc>,
/// ID of the user who created the ticket
pub user_id: i16, pub user_id: i16,
/// First name of the ticket creator (denormalized from `User`)
pub user_first_name: String, pub user_first_name: String,
/// Last name of the ticket creator (denormalized from `User`)
pub user_last_name: String, pub user_last_name: String,
} }
@@ -62,11 +72,17 @@ pub struct TicketResponse {
/// Use [`filter_user()`](`crate::handlers::auth::filter_user`) to convert to [`FilteredUser`] for responses. /// Use [`filter_user()`](`crate::handlers::auth::filter_user`) to convert to [`FilteredUser`] for responses.
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone, sqlx::FromRow)] #[derive(Deserialize, Serialize, PartialEq, Debug, Clone, sqlx::FromRow)]
pub struct User { pub struct User {
/// Unique user identifier
pub id: i16, pub id: i16,
/// User's last name
pub last_name: String, pub last_name: String,
/// User's first name
pub first_name: String, pub first_name: String,
/// Unique login username (must be unique in the database)
pub username: String, pub username: String,
/// Whether this user has administrator privileges
pub is_admin: bool, pub is_admin: bool,
/// Argon2 password hash (NEVER expose to clients)
pub pwd: String, pub pwd: String,
} }
@@ -82,9 +98,13 @@ pub struct User {
/// - `room`: Room number where the issue is located /// - `room`: Room number where the issue is located
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub struct TicketCreateScheme { pub struct TicketCreateScheme {
/// Ticket category/type
pub category: String, pub category: String,
/// Subject line for the ticket
pub betreff: String, pub betreff: String,
/// Detailed problem description
pub description: String, pub description: String,
/// Room number where the issue is located
pub room: i16, pub room: i16,
} }
@@ -97,6 +117,7 @@ pub struct TicketCreateScheme {
/// - `status`: New ticket status (e.g., "open", "in_progress", "resolved") /// - `status`: New ticket status (e.g., "open", "in_progress", "resolved")
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub struct TicketUpdateScheme { pub struct TicketUpdateScheme {
/// New ticket status (e.g., "open", "in_progress", "resolved", "archived")
pub status: String, pub status: String,
} }
@@ -113,11 +134,17 @@ pub struct TicketUpdateScheme {
/// - `new_pwd`: New password (empty string = keep existing password) /// - `new_pwd`: New password (empty string = keep existing password)
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub struct UserUpdateScheme { pub struct UserUpdateScheme {
/// User ID to update
pub id: i16, pub id: i16,
/// Updated user first name
pub first_name: String, pub first_name: String,
/// Updated user last name
pub last_name: String, pub last_name: String,
/// Updated login username
pub username: String, pub username: String,
/// New admin privilege status
pub make_admin: bool, pub make_admin: bool,
/// New password (empty string = keep existing password)
pub new_pwd: String, pub new_pwd: String,
} }
@@ -134,10 +161,15 @@ pub struct UserUpdateScheme {
/// - `pwd`: Plain text password (hashed on server) /// - `pwd`: Plain text password (hashed on server)
#[derive(Deserialize, Serialize, Debug, sqlx::FromRow)] #[derive(Deserialize, Serialize, Debug, sqlx::FromRow)]
pub struct UserCreateScheme { pub struct UserCreateScheme {
/// User's first name
pub first_name: String, pub first_name: String,
/// User's last name
pub last_name: String, pub last_name: String,
/// Unique username for login
pub username: String, pub username: String,
/// Whether to grant admin privileges
pub is_admin: bool, pub is_admin: bool,
/// Plain text password (hashed on server before storage)
pub pwd: String, pub pwd: String,
} }
@@ -150,7 +182,9 @@ pub struct UserCreateScheme {
/// The password is never stored in plain text - only the Argon2 hash is persisted. /// The password is never stored in plain text - only the Argon2 hash is persisted.
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub struct LoginScheme { pub struct LoginScheme {
/// Username for login
pub username: String, pub username: String,
/// Plain text password (verified against stored Argon2 hash)
pub pwd: String, pub pwd: String,
} }
@@ -161,10 +195,15 @@ pub struct LoginScheme {
/// to prevent leaking sensitive data. /// to prevent leaking sensitive data.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct FilteredUser { pub struct FilteredUser {
/// Unique user identifier
pub id: i16, pub id: i16,
/// User's first name
pub first_name: String, pub first_name: String,
/// User's last name
pub last_name: String, pub last_name: String,
/// Login username
pub username: String, pub username: String,
/// Whether user has admin privileges
pub is_admin: bool, pub is_admin: bool,
} }
-14
View File
@@ -1,14 +0,0 @@
services:
postgres:
image: postgres:latest
container_name: postgres_ticket
restart: unless-stopped
ports:
- 5432:5432
environment:
- POSTGRES_PASSWORD=tickets
volumes:
- pg_data:/var/lib/postregsql/pg_data
volumes:
pg_data:
+127
View File
@@ -0,0 +1,127 @@
# ============================================================
# Dockerfile — Full application (frontend + backend)
# ============================================================
# Multi-stage build:
# 1. chef install cargo-chef for dependency caching
# 2. planner generate recipe.json from the workspace
# 3. backend build the backend binary (release)
# 4. frontend build the frontend WASM bundle with Trunk
# 5. runtime minimal image: backend binary + static frontend + nginx
# ============================================================
# --------------- Stage 1: Chef base ---------------
FROM rust:1-bookworm AS chef
RUN cargo install cargo-chef
WORKDIR /app
# --------------- Stage 2: Planner ---------------
FROM chef AS planner
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
COPY backend/ backend/
COPY frontend/ frontend/
RUN cargo chef prepare --recipe-path recipe.json
# --------------- Stage 3: Backend builder ---------------
FROM chef AS backend-builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json -p backend
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
COPY backend/ backend/
COPY frontend/ frontend/
RUN cargo build --release -p backend
# --------------- Stage 4: Frontend builder ---------------
FROM chef AS frontend-builder
# Add the WASM target
RUN rustup target add wasm32-unknown-unknown
# Install Trunk (the WASM bundler used by the frontend) and sass (for SCSS)
RUN cargo install trunk
# Cache frontend dependencies
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --target wasm32-unknown-unknown --recipe-path recipe.json -p frontend
# Copy full workspace source for the build
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
COPY backend/ backend/
COPY frontend/ frontend/
# Build the frontend WASM bundle
# Trunk outputs to frontend/dist by default
WORKDIR /app/frontend
RUN trunk build --release
# --------------- Stage 5: Runtime ---------------
FROM debian:bookworm-slim AS runtime
# Install runtime dependencies:
# - ca-certificates & libssl3: TLS for PostgreSQL connections
# - nginx: serves the frontend static files and reverse-proxies /api to the backend
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates libssl3 nginx && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy the compiled backend binary
COPY --from=backend-builder /app/target/release/backend /app/backend
# Copy migrations
COPY backend/migrations/ /app/migrations/
# Copy the frontend static build output
COPY --from=frontend-builder /app/frontend/dist /var/www/html
# Nginx configuration: serve frontend + reverse-proxy /api to the backend
RUN cat > /etc/nginx/sites-available/default <<'EOF'
server {
listen 80;
server_name _;
root /var/www/html;
index index.html;
# Serve static frontend files, fall back to index.html for client-side routing
location / {
try_files $uri $uri/ /index.html;
}
# Reverse-proxy API requests to the Axum backend
location /api/ {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
EOF
# Startup script: launch backend in the background, then nginx in the foreground
RUN cat > /app/start.sh <<'SCRIPT'
#!/bin/bash
set -e
# Start the backend API server in the background
/app/backend &
# Start nginx in the foreground (keeps the container alive)
nginx -g 'daemon off;'
SCRIPT
RUN chmod +x /app/start.sh
EXPOSE 80
# Environment variables should be provided at runtime:
# DATABASE_URL PostgreSQL connection string
# TOKEN_SECRET JWT signing key
# ORIGIN Allowed CORS origin (should be the public URL of this container)
CMD ["/app/start.sh"]
+66
View File
@@ -0,0 +1,66 @@
# ============================================================
# Dockerfile.backend — Backend only
# ============================================================
# Multi-stage build:
# 1. chef install cargo-chef for dependency caching
# 2. planner generate a recipe.json from the workspace
# 3. builder build dependencies first (cached), then the backend binary
# 4. runtime minimal image with just the compiled binary
# ============================================================
# --------------- Stage 1: Chef base ---------------
FROM rust:1-bookworm AS chef
RUN cargo install cargo-chef
WORKDIR /app
# --------------- Stage 2: Planner ---------------
FROM chef AS planner
# Copy the full workspace so cargo-chef can resolve all dependencies
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
COPY backend/ backend/
COPY frontend/ frontend/
RUN cargo chef prepare --recipe-path recipe.json
# --------------- Stage 3: Builder ---------------
FROM chef AS builder
# Cache dependency compilation in its own layer
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json -p backend
# Now copy the real source and build
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
COPY backend/ backend/
COPY frontend/ frontend/
RUN cargo build --release -p backend
# --------------- Stage 4: Runtime ---------------
FROM debian:bookworm-slim AS runtime
# Install TLS root certificates and PostgreSQL client libs (needed by sqlx
# at runtime for TLS connections via native-tls)
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates libssl3 && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy the compiled binary
COPY --from=builder /app/target/release/backend /app/backend
# Copy migrations so they can be run at startup if needed
COPY backend/migrations/ /app/migrations/
EXPOSE 8001
# Environment variables should be provided at runtime via docker run --env
# or docker compose environment/env_file.
#
# Required:
# DATABASE_URL PostgreSQL connection string
# TOKEN_SECRET JWT signing key
# ORIGIN Allowed CORS origin (frontend URL)
CMD ["/app/backend"]
+58
View File
@@ -0,0 +1,58 @@
services:
postgres:
image: postgres:latest
container_name: postgres_ticket
restart: unless-stopped
ports:
- 5432:5432
environment:
POSTGRES_PASSWORD: tickets
POSTGRES_DB: system_data
volumes:
- pg_data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
# Full application (frontend + backend + nginx)
# Usage: docker compose --profile full up --build
app:
profiles: ["full"]
build:
context: ..
dockerfile: docker/Dockerfile
container_name: ticketsystem
restart: unless-stopped
ports:
- 8080:80
environment:
DATABASE_URL: postgres://postgres:tickets@postgres:5432/system_data
TOKEN_SECRET: 160257c8c5ff2298363529e963a5901d2efa6d6ae67d634487c4d2bbc41c533f
ORIGIN: http://localhost:8080
depends_on:
postgres:
condition: service_healthy
# Backend only (API server)
# Usage: docker compose --profile backend up --build
backend:
profiles: ["backend"]
build:
context: ..
dockerfile: docker/Dockerfile.backend
container_name: ticketsystem-backend
restart: unless-stopped
ports:
- 8001:8001
environment:
DATABASE_URL: postgres://postgres:tickets@postgres:5432/system_data
TOKEN_SECRET: 160257c8c5ff2298363529e963a5901d2efa6d6ae67d634487c4d2bbc41c533f
ORIGIN: http://localhost:8000
depends_on:
postgres:
condition: service_healthy
volumes:
pg_data:
+1 -1
View File
@@ -1,6 +1,6 @@
[serve] [serve]
# The address to serve on LAN. # The address to serve on LAN.
addresses = ["127.0.0.1"] #,"10.150.9.7"] addresses = ["127.0.0.1","192.168.2.157"] #,"10.150.9.7"]
# The address to serve on WAN. # The address to serve on WAN.
# address = "0.0.0.0" # address = "0.0.0.0"
# The port to serve on. # The port to serve on.
+57 -3
View File
@@ -1,24 +1,78 @@
use yew::prelude::*; use yew::prelude::*;
/// A button component that toggles between light and dark theme modes.
///
/// Clicking this button switches the application's theme by modifying the `data-theme` attribute
/// on the document element. The theme preference is applied via CSS variables for consistent styling.
///
/// # Behavior
/// - Reads the current `data-theme` attribute value from the HTML element
/// - Sets `data-theme="dark"` if currently in light mode
/// - Clears the attribute (light mode) if currently in dark mode
/// - Re-renders to reflect the visual changes (typically toggled via CSS)
///
/// # Example Usage
/// ```ignore
/// html! {
/// <ThemeToggle />
/// }
/// ```
#[function_component] #[function_component]
pub fn ThemeToggle() -> Html { pub fn ThemeToggle() -> Html {
let is_dark = use_state(|| {
if let Some(window) = web_sys::window() {
if let Some(document) = window.document() {
if let Some(html) = document.document_element() {
return html.get_attribute("data-theme").unwrap_or_default() == "dark";
}
}
}
false
});
let onclick = { let onclick = {
Callback::from(|_| { let is_dark = is_dark.clone();
Callback::from(move |_| {
if let Some(window) = web_sys::window() { if let Some(window) = web_sys::window() {
if let Some(document) = window.document() { if let Some(document) = window.document() {
if let Some(html) = document.document_element() { if let Some(html) = document.document_element() {
let current = html.get_attribute("data-theme").unwrap_or_default(); let current = html.get_attribute("data-theme").unwrap_or_default();
let new_theme = if current == "dark" { "" } else { "dark" }; let new_theme = if current == "dark" { "" } else { "dark" };
let _ = html.set_attribute("data-theme", new_theme); let _ = html.set_attribute("data-theme", new_theme);
is_dark.set(new_theme == "dark");
} }
} }
} }
}) })
}; };
let icon_html = if *is_dark {
// Sun SVG for dark mode
html! {
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="theme-icon">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2" />
<path d="M12 20v2" />
<path d="m4.93 4.93 1.41 1.41" />
<path d="m17.66 17.66 1.41 1.41" />
<path d="M2 12h2" />
<path d="M20 12h2" />
<path d="m6.34 17.66-1.41 1.41" />
<path d="m19.07 4.93-1.41 1.41" />
</svg>
}
} else {
// Moon SVG for light mode
html! {
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="theme-icon">
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
</svg>
}
};
html! { html! {
<button {onclick} class="sidebar-button"> <button {onclick} class="sidebar-button theme-toggle-btn">
{"🌙"} {icon_html}
</button> </button>
} }
} }
+4
View File
@@ -1,5 +1,9 @@
use frontend::App; use frontend::App;
/// Main entry point for the Yew frontend application.
///
/// Initializes the Yew framework and renders the root [`App`] component into the DOM.
/// This function is typically called automatically by the WebAssembly runtime.
fn main() { fn main() {
yew::Renderer::<App>::new().render(); yew::Renderer::<App>::new().render();
} }
+2
View File
@@ -20,6 +20,7 @@ pub struct AdminSetupScheme {
pub first_name: String, pub first_name: String,
pub last_name: String, pub last_name: String,
pub username: String, pub username: String,
pub is_admin: bool,
pub pwd: String, pub pwd: String,
} }
@@ -154,6 +155,7 @@ pub fn initial_admin_setup() -> Html {
first_name: first_name_val, first_name: first_name_val,
last_name: last_name_val, last_name: last_name_val,
username: username_val, username: username_val,
is_admin: true,
pwd: pwd_val, pwd: pwd_val,
}; };
+7 -2
View File
@@ -8,9 +8,11 @@
--border-radius: 6px; --border-radius: 6px;
--rundbgbackgroundcolor: #a0a0a0; --rundbgbackgroundcolor: #a0a0a0;
--sidebar-font: #fffefe; --sidebar-font: #fffefe;
--color: #ffffff; --color: #000000;
--color-container: #f0f0f0; --color-container: #f0f0f0;
--color-text: #000000; --color-text: #000000;
--color-placeholder: #757575;
--color-input: #1a1a1a;
} }
[data-theme='dark'] { [data-theme='dark'] {
@@ -25,5 +27,8 @@
--sidebar-font: #000000; --sidebar-font: #000000;
--color: #000000; --color: #000000;
--color-container: #1a1a1a; --color-container: #1a1a1a;
--color-text: #ffffff; --color-text: #000000;
--color-placeholder: #a0a0a0;
--color-input: #f0f0f0;
} }
@@ -0,0 +1,27 @@
body {
/* Formular Styles */
input, textarea, select, button {
width: 100%;
margin-top: 1rem;
padding: 0.75rem;
font-size: 1rem;
border-radius: 0.5rem;
color: var(--color-input);
border: none;
cursor: pointer;
margin-bottom: 15px;
}
input::placeholder,
textarea::placeholder {
color: var(--color-placeholder);
opacity: 1;
}
button:hover {
cursor: pointer;
border: none;
transition: all 0.2s ease-in-out;
margin-bottom: 15px;
}
}
@@ -1,6 +1,11 @@
@use "../variables" as *; @use "../variables" as *;
@use "../mixins" as *; @use "../mixins" as *;
:root {
color-scheme: dark light;
}
.sidebar { .sidebar {
width: 260px; width: 260px;
background: var(--color-sidebar); background: var(--color-sidebar);
@@ -50,6 +55,12 @@
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
margin-top: var(--spacing-sm); margin-top: var(--spacing-sm);
&.theme-toggle-btn {
display: flex;
justify-content: center;
align-items: center;
}
} }
.sidebar-header { .sidebar-header {
@@ -178,4 +189,6 @@
pointer-events: auto; pointer-events: auto;
} }
} }
} }
+13
View File
@@ -90,10 +90,12 @@ body {
padding: 16px; padding: 16px;
font-size: 1.8rem; font-size: 1.8rem;
background-color: #f0f0f0; background-color: #f0f0f0;
color: var(--color-input);
box-shadow: #6d6d6d; box-shadow: #6d6d6d;
box-shadow: 6px 6px 8px; box-shadow: 6px 6px 8px;
} }
input[type="checkbox"] { input[type="checkbox"] {
width: auto; width: auto;
} }
@@ -123,4 +125,15 @@ body {
box-sizing: border-box; box-sizing: border-box;
} }
input, select, textarea {
color: var(--color-input);
}
input::placeholder,
textarea::placeholder {
color: var(--color-placeholder);
opacity: 1;
}
Binary file not shown.