Compare commits
4 Commits
7fc5a6335d
...
main
Author | SHA1 | Date | |
---|---|---|---|
2b2ecd8abb | |||
3ffac72220 | |||
![]() |
6b66d10855 | ||
![]() |
195e0f4f8f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,6 +22,9 @@ target/
|
||||
|
||||
.idea
|
||||
custom-bullshit
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
|
3514
Cargo.lock
generated
3514
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@@ -4,3 +4,22 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.5.1", features = ["ssr"] }
|
||||
axum = "0.7.5"
|
||||
tokio = { version = "1.37.0", features = ["full"] }
|
||||
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio"] }
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
log = "0.4.21"
|
||||
wasm-bindgen = "0.2.100"
|
||||
|
||||
[profile]
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
|
||||
[profile.server-dev]
|
||||
inherits = "dev"
|
||||
|
||||
[profile.android-dev]
|
||||
inherits = "dev"
|
||||
|
288
ROADMAP.md
Normal file
288
ROADMAP.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# Owlibou Tavern — Detailed Roadmap (M0 → M10)
|
||||
|
||||
A focused plan for Dioxus Fullstack (Workspace Edition 2024), admin/editor CRUD, private deployment, legal sensitivity, and SQLx ergonomics.
|
||||
|
||||
---
|
||||
|
||||
## M0 — Workspace Bootstrap (Dioxus Fullstack WSE 2024)
|
||||
**Goal:** Reproducible workspace skeleton with SSR + client, ready for DB + auth.
|
||||
|
||||
**Deliverables**
|
||||
- `rust-toolchain.toml` pinned to `RUST_STABLE`
|
||||
- Cargo **workspace** with crates:
|
||||
- `app-web` (Dioxus Web)
|
||||
- `app-server` (Axum/Dioxus server functions + SSR)
|
||||
- `domain` (entities, DTOs, validators, error types)
|
||||
- `data` (DB pool, queries, migrations wrapper)
|
||||
- `shared` (isomorphic types/formatters for web+server)
|
||||
- `cli` (admin ops: create admin user, run migrations, seed)
|
||||
- Dioxus config for Workspace Edition 2024 (server functions enabled)
|
||||
- Tracing/logging, error boundary scaffold, `.env` loading
|
||||
|
||||
**Key Steps**
|
||||
1. **Workspace**
|
||||
- Root `Cargo.toml` with members.
|
||||
- `rust-toolchain.toml` with `channel = "RUST_STABLE"`.
|
||||
2. **Server**
|
||||
- Axum + Dioxus Fullstack integration (SSR, server functions).
|
||||
- `tower-http` for compression, security headers, request logging.
|
||||
3. **Web**
|
||||
- Dioxus Router; app shell (sidebar/topbar); error boundary + 404 route.
|
||||
4. **Common**
|
||||
- `thiserror` + `anyhow` error model; `serde` for JSON.
|
||||
- `tracing` + `tracing-subscriber` with `RUST_LOG`.
|
||||
5. **DX**
|
||||
- `dx` tasks or `cargo-watch` for hot reload.
|
||||
- Pre-commit hooks: `cargo fmt --check`, `clippy -D warnings`.
|
||||
|
||||
**Acceptance**
|
||||
- `cargo run -p app-server` serves SSR page.
|
||||
- Hot reload works; logs render; basic routes respond.
|
||||
|
||||
**Assumptions challenged**
|
||||
- Server functions vs. pure REST: if non-Dioxus clients likely, prefer REST now and use server functions selectively.
|
||||
|
||||
---
|
||||
|
||||
## M1 — Data Layer & Migrations
|
||||
**Goal:** Safe DB access layer; deterministic migrations; WAL & pragmas if SQLite.
|
||||
|
||||
**Deliverables**
|
||||
- `data` crate: pool factory, migrations runner, query helpers.
|
||||
- `sqlx` wired; `.env` (`DATABASE_URL`), connection healthcheck.
|
||||
- Migration files mirroring `/database/main`.
|
||||
|
||||
**Key Steps**
|
||||
1. Add `sqlx` + `sqlx-cli`; enable `sqlite` (or your DB) features.
|
||||
2. `sqlx::migrate!()` (or CLI) to run migrations at startup/CLI.
|
||||
3. Pool settings (max cons, timeouts); WAL mode if SQLite.
|
||||
4. `domain` structs mirroring tables; `FromRow` derives; validators.
|
||||
|
||||
**Acceptance**
|
||||
- `cargo run -p cli migrate` applies migrations.
|
||||
- `/healthz/db` returns OK.
|
||||
|
||||
**Assumptions challenged**
|
||||
- If schema evolves fast, add **idempotent** migrations and a **baseline**.
|
||||
|
||||
---
|
||||
|
||||
## M2 — Read-Only DB Viewer (Browser)
|
||||
**Goal:** Admin table browser with pagination, sort, filters, detail view.
|
||||
|
||||
**Deliverables**
|
||||
- Table list page (all tables)
|
||||
- Grid view per table with server-side pagination
|
||||
- Type-aware rendering (dates, bools, FKs as labels)
|
||||
- Record detail drawer/view
|
||||
|
||||
**Key Steps**
|
||||
1. API/server functions: `GET /api/v1/{table}` w/ `page`, `q`, `order`.
|
||||
2. UI: reusable `<DataGrid>`; column config via schema reflection or static map.
|
||||
3. Empty state, loading, error toasts.
|
||||
|
||||
**Acceptance**
|
||||
- Browse all tables; sort; filter; open details.
|
||||
|
||||
**Counterpoint**
|
||||
- Don’t overbuild a “universal admin” first—target priority tables.
|
||||
|
||||
---
|
||||
|
||||
## M3 — CRUD for One Core Entity
|
||||
**Goal:** End-to-end create/edit/delete for the flagship table.
|
||||
|
||||
**Deliverables**
|
||||
- Create/Edit forms with client + server validation
|
||||
- Delete with confirm modal; optimistic UI update
|
||||
- Consistent error model → field vs. form errors
|
||||
|
||||
**Key Steps**
|
||||
1. REST/server functions: `POST/PUT/DELETE /api/v1/entity`.
|
||||
2. `validator` crate or bespoke validators in `domain`.
|
||||
3. UI: form components (text, select, date, FK pickers); dirty-state guard.
|
||||
|
||||
**Acceptance**
|
||||
- Create/edit/delete works; bad input → friendly field errors.
|
||||
|
||||
**Assumption challenged**
|
||||
- Complex relations? Use a **form stepper** or “+ create related” in-place.
|
||||
|
||||
---
|
||||
|
||||
## M4 — Authentication (Private Setup)
|
||||
**Goal:** Session-based login, secure cookies, password hashing.
|
||||
|
||||
**Deliverables**
|
||||
- `/login`, `/logout`, `/me`
|
||||
- Session middleware (cookies: HttpOnly, Secure, SameSite=Strict)
|
||||
- `argon2` (or `bcrypt`) hashing; password policy checks
|
||||
- Login page; lockout/backoff after N failures
|
||||
|
||||
**Key Steps**
|
||||
1. `axum-login` or `tower-sessions` + user store.
|
||||
2. CSRF mitigation for cookie sessions (token or double-submit).
|
||||
3. CLI to create first admin: `cli user create --admin`.
|
||||
|
||||
**Acceptance**
|
||||
- Only authenticated users can access viewer/CRUD.
|
||||
- Brute-force throttling observable.
|
||||
|
||||
**Skeptic note**
|
||||
- Single-origin private admin → cookies > JWT for simplicity/safety.
|
||||
|
||||
---
|
||||
|
||||
## M5 — RBAC & Policy Enforcement
|
||||
**Goal:** Least-privilege access; backend **and** UI gating.
|
||||
|
||||
**Deliverables**
|
||||
- Roles: `Admin`, `Editor`, (optional `Viewer`)
|
||||
- `AuthContext { user_id, roles }` extractor
|
||||
- Route guards (e.g., only Admin can delete; Editor can create/update)
|
||||
- UI hides forbidden actions; backend still enforces
|
||||
|
||||
**Key Steps**
|
||||
1. Permission matrix (routes × roles).
|
||||
2. Axum layer/guards per route group.
|
||||
3. Audit log table (`who`, `when`, `what`, `before→after`).
|
||||
|
||||
**Acceptance**
|
||||
- Forbidden ops → 403; writes are audited.
|
||||
|
||||
**Bias flag**
|
||||
- Hiding buttons ≠ authorization. Enforce on server.
|
||||
|
||||
---
|
||||
|
||||
## M6 — Full Schema Coverage & Relations UX
|
||||
**Goal:** CRUD across schema; ergonomic FK/M2M editing.
|
||||
|
||||
**Deliverables**
|
||||
- Per-table pages with consistent forms
|
||||
- FK pickers with search (typeahead) and clear labels
|
||||
- M2M editors (dual list, multi-select, or chips)
|
||||
- Bulk actions (delete, export CSV) with permission checks
|
||||
|
||||
**Key Steps**
|
||||
1. Label resolution helpers (JOINs or cached lookups).
|
||||
2. Server-side pagination for big tables; index review.
|
||||
3. Import/export (CSV/JSON) with validation + dry-run.
|
||||
|
||||
**Acceptance**
|
||||
- Each table has list/detail/edit; relations feel smooth; import/export works.
|
||||
|
||||
**Counterpoint**
|
||||
- Extract a form generator only after repeating patterns emerge.
|
||||
|
||||
---
|
||||
|
||||
## M7 — Security Hardening (Legal Sensitivity)
|
||||
**Goal:** Tighten security; document threat model; minimize blast radius.
|
||||
|
||||
**Deliverables**
|
||||
- TLS via `rustls` **or** reverse proxy (Caddy/nginx)
|
||||
- Strict CORS (or disabled), `Content-Security-Policy`, `Referrer-Policy`
|
||||
- Input validation everywhere; no string-built SQL
|
||||
- Secrets in env/secret store; none in git
|
||||
- Data retention policy + purges
|
||||
- Optional: field-level encryption at rest for sensitive columns
|
||||
|
||||
**Key Steps**
|
||||
1. Security headers middleware.
|
||||
2. Rate limiting per session/IP for writes.
|
||||
3. Structured logging; redact PII; access + write audit logs.
|
||||
4. Backups & key rotation plan; restore rehearsal.
|
||||
|
||||
**Acceptance**
|
||||
- SSL Labs A (if exposed); scanners clean; restore drill passes.
|
||||
|
||||
**Frame shift**
|
||||
- Treat it as **regulated** even if it isn’t—cheaper than breach postmortems.
|
||||
|
||||
---
|
||||
|
||||
## M8 — Ops: Build, Deploy, Backup
|
||||
**Goal:** One-command build/deploy; predictable backups; health & metrics.
|
||||
|
||||
**Deliverables**
|
||||
- Multi-stage Dockerfile; `docker-compose.yml` for private stack
|
||||
- Systemd unit (if bare-metal) with `Restart=always`
|
||||
- Health endpoints `/healthz`, `/readyz`; DB readiness check
|
||||
- Backups: nightly DB snapshot + off-host copy; retention window
|
||||
|
||||
**Key Steps**
|
||||
1. CI: build, test, produce image/artifacts.
|
||||
2. Observability: basic metrics (req/s, p95), log shipper.
|
||||
3. Runbook: deploy, rollback, rotate secrets, restore backup.
|
||||
|
||||
**Acceptance**
|
||||
- Fresh machine → deploy in minutes with docs; restore tested.
|
||||
|
||||
**Counterpoint**
|
||||
- LAN-only? A simple systemd service may beat Docker complexity.
|
||||
|
||||
---
|
||||
|
||||
## M9 — Testing: Unit, Integration, E2E
|
||||
**Goal:** Confidence without ceremony.
|
||||
|
||||
**Deliverables**
|
||||
- Unit tests: `domain` validators, `data` queries (test DB)
|
||||
- Integration tests: APIs (ephemeral DB; hit endpoints)
|
||||
- E2E smoke: Playwright (or similar) against SSR app
|
||||
|
||||
**Key Steps**
|
||||
1. Test fixtures: migrate temp DB, seed minimal data.
|
||||
2. Golden tests for JSON; snapshot UI for critical pages.
|
||||
3. Security tests: auth bypass, CSRF, rate limits.
|
||||
|
||||
**Acceptance**
|
||||
- CI runs and fails on regressions.
|
||||
|
||||
---
|
||||
|
||||
## M10 — Docs & Admin UX Polish
|
||||
**Goal:** Maintainable, pleasant, self-serve.
|
||||
|
||||
**Deliverables**
|
||||
- `README.md` (setup/run/deploy) + `SECURITY.md` + `RUNBOOK.md`
|
||||
- Admin guide (user mgmt, imports, backups)
|
||||
- QoL: keyboard shortcuts, column presets, saved filters
|
||||
|
||||
**Acceptance**
|
||||
- New teammate stands it up and performs CRUD in ≤30 minutes using docs.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklists
|
||||
|
||||
**Pin toolchain & linting**
|
||||
- `rust-toolchain.toml` → `channel="RUST_STABLE"`
|
||||
- `.cargo/config.toml` tuned for dev
|
||||
- `cargo fmt`, `clippy -D warnings` in CI
|
||||
|
||||
**Security defaults**
|
||||
- Cookies: `HttpOnly`, `Secure`, `SameSite=Strict`
|
||||
- CSP: `default-src 'self'`; `frame-ancestors 'none'`; block mixed content
|
||||
- No dev endpoints in prod; no directory listings
|
||||
- DB: prepared statements only; least privilege
|
||||
|
||||
**DB pragmas (SQLite)**
|
||||
- WAL mode; `synchronous=NORMAL` (or `FULL` for extra safety)
|
||||
- `PRAGMA foreign_keys = ON;`
|
||||
- Indices for filters/sorts used in viewer
|
||||
|
||||
---
|
||||
|
||||
## Risks & Mitigations
|
||||
- **Generic admin scope creep.** Focus on 1–2 core entities (M3); templatize later.
|
||||
- **Auth choice confusion.** Private, single-origin → cookies; avoid JWT complexity.
|
||||
- **Log-based legal exposure.** Redact PII; isolate/audit logs; retention limits.
|
||||
- **Backups untested.** Run restore drills. Untested backups are fiction.
|
||||
|
||||
---
|
||||
|
||||
## Assumptions (call out for correction)
|
||||
- Using **SQLx + SQLite** initially (swap easily for Postgres/MySQL).
|
||||
- Private LAN/VPN deployment; not publicly exposed unless decided later.
|
0
dist/.keep
vendored
Normal file
0
dist/.keep
vendored
Normal file
BIN
rules.db.empty
BIN
rules.db.empty
Binary file not shown.
11
server.log
Normal file
11
server.log
Normal file
@@ -0,0 +1,11 @@
|
||||
Compiling unicode-ident v1.0.18
|
||||
Compiling cfg-if v1.0.3
|
||||
Compiling futures-core v0.3.31
|
||||
Compiling pin-project-lite v0.2.16
|
||||
Compiling once_cell v1.21.3
|
||||
Compiling scopeguard v1.2.0
|
||||
Compiling proc-macro2 v1.0.101
|
||||
Compiling lock_api v0.4.13
|
||||
Compiling memchr v2.7.5
|
||||
Compiling log v0.4.27
|
||||
Compiling libc v0.2.175
|
1
src/components/mod.rs
Normal file
1
src/components/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod nav;
|
15
src/components/nav.rs
Normal file
15
src/components/nav.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn NavBar() -> Element {
|
||||
rsx! {
|
||||
nav {
|
||||
ul {
|
||||
li { a { href: "/", "Home" } }
|
||||
li { a { href: "/creatures", "Creatures" } }
|
||||
li { a { href: "/import", "Import" } }
|
||||
li { a { href: "/changelog", "Changelog" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
src/db.rs
Normal file
53
src/db.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
|
||||
|
||||
const DATABASE_URL: &str = "sqlite:rules.db";
|
||||
|
||||
// A function to establish a connection pool.
|
||||
// On the server, this will be called once and the pool will be reused.
|
||||
pub async fn get_pool() -> Result<SqlitePool, sqlx::Error> {
|
||||
SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(DATABASE_URL)
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, sqlx::FromRow)]
|
||||
pub struct Creature {
|
||||
pub id: i64,
|
||||
pub optolith_key: Option<String>,
|
||||
pub name: String,
|
||||
pub typ: Option<String>,
|
||||
pub groessenkategorie: Option<String>,
|
||||
pub attr_mu: Option<i64>,
|
||||
pub attr_kl: Option<i64>,
|
||||
pub attr_in: Option<i64>,
|
||||
pub attr_ch: Option<i64>,
|
||||
pub attr_ff: Option<i64>,
|
||||
pub attr_ge: Option<i64>,
|
||||
pub attr_ko: Option<i64>,
|
||||
pub attr_kk: Option<i64>,
|
||||
pub le_formel: Option<String>,
|
||||
pub sk_wert: Option<i64>,
|
||||
pub zk_wert: Option<i64>,
|
||||
pub gs_wert: Option<i64>,
|
||||
pub ini_formel: Option<String>,
|
||||
pub rs_wert: Option<i64>,
|
||||
pub beschreibung: Option<String>,
|
||||
pub fluchtverhalten: Option<String>,
|
||||
}
|
||||
|
||||
// A function to fetch all creatures from the database.
|
||||
// This will be called from a server function.
|
||||
pub async fn get_all_creatures(pool: &SqlitePool) -> Result<Vec<Creature>, sqlx::Error> {
|
||||
sqlx::query_as::<_, Creature>("SELECT * FROM creature ORDER BY name ASC")
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
// A function to fetch a single creature by its ID.
|
||||
pub async fn get_creature(pool: &SqlitePool, id: i64) -> Result<Creature, sqlx::Error> {
|
||||
sqlx::query_as::<_, Creature>("SELECT * FROM creature WHERE id = ?")
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
87
src/main.rs
87
src/main.rs
@@ -1,3 +1,86 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
#![allow(non_snake_case)]
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::Html,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
mod db;
|
||||
mod pages;
|
||||
mod components;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub pool: SqlitePool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let pool = db::get_pool().await.expect("Failed to create database pool");
|
||||
let app_state = AppState { pool };
|
||||
|
||||
let router = Router::new()
|
||||
.route("/", get(home_page))
|
||||
.route("/creatures", get(creatures_page))
|
||||
.route("/creatures/:id/edit", get(creature_edit_page))
|
||||
.route("/import", get(import_page))
|
||||
.route("/changelog", get(changelog_page))
|
||||
.with_state(app_state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
println!("listening on http://{}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, router).await.unwrap();
|
||||
}
|
||||
|
||||
// The main layout component
|
||||
#[component]
|
||||
fn Layout(children: Element) -> Element {
|
||||
rsx! {
|
||||
// The NavBar component
|
||||
components::nav::NavBar {}
|
||||
// The actual page content
|
||||
div {
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper to render a Dioxus component to a full HTML page.
|
||||
fn render_page(root: Element) -> String {
|
||||
// Create a new VirtualDom with the Layout component as the root
|
||||
// and the page content as its children
|
||||
let mut dom = VirtualDom::new_with_props(Layout, LayoutProps { children: root });
|
||||
|
||||
// Rebuild the DOM once to process all components
|
||||
dom.rebuild_in_place();
|
||||
|
||||
// Render the DOM to a string
|
||||
format!("<!DOCTYPE html><html><head><title>Owlibou Tavern</title></head><body>{}</body></html>", dioxus::ssr::render(&dom))
|
||||
}
|
||||
|
||||
async fn home_page() -> Html<String> {
|
||||
Html(render_page(rsx! { pages::home::HomePage {} }))
|
||||
}
|
||||
|
||||
async fn creatures_page(State(state): State<AppState>) -> Html<String> {
|
||||
let creatures = db::get_all_creatures(&state.pool).await.unwrap_or_default();
|
||||
Html(render_page(rsx! { pages::creatures::CreaturesPage { creatures: creatures } }))
|
||||
}
|
||||
|
||||
async fn creature_edit_page(Path(id): Path<i64>, State(state): State<AppState>) -> Html<String> {
|
||||
match db::get_creature(&state.pool, id).await {
|
||||
Ok(creature) => Html(render_page(rsx! { pages::creature_edit::CreatureEditPage { creature: creature } })),
|
||||
Err(_) => Html("Creature not found".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn import_page() -> Html<String> {
|
||||
Html(render_page(rsx! { pages::import::ImportPage {} }))
|
||||
}
|
||||
|
||||
async fn changelog_page() -> Html<String> {
|
||||
Html(render_page(rsx! { pages::changelog::ChangelogPage {} }))
|
||||
}
|
||||
|
24
src/pages/changelog.rs
Normal file
24
src/pages/changelog.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn ChangelogPage() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h2 { "Changelog" }
|
||||
|
||||
h3 { "Version 0.1.0 (In Progress)" }
|
||||
ul {
|
||||
li { "Implemented basic frontend structure with routing." }
|
||||
li { "Added database integration and view for creatures." }
|
||||
li { "Scaffolded edit and import functionalities." }
|
||||
li { "Created this changelog page." }
|
||||
}
|
||||
|
||||
h3 { "Version 0.0.1 (Initial Setup)" }
|
||||
ul {
|
||||
li { "Initialized the project with Dioxus." }
|
||||
li { "Set up database schema." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
src/pages/creature_edit.rs
Normal file
19
src/pages/creature_edit.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::db::Creature;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn CreatureEditPage(creature: Creature) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h2 { "Edit Creature (ID: {creature.id})" }
|
||||
div {
|
||||
p { "Name: {creature.name}" }
|
||||
p { "Type: {creature.typ.as_deref().unwrap_or(\"-\")}" }
|
||||
button {
|
||||
onclick: move |_| {},
|
||||
"Save Changes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
42
src/pages/creatures.rs
Normal file
42
src/pages/creatures.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::db::Creature;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn CreaturesPage(creatures: Vec<Creature>) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h2 { "Creatures" }
|
||||
if creatures.is_empty() {
|
||||
p { "No creatures found in the database." }
|
||||
} else {
|
||||
table {
|
||||
thead {
|
||||
tr {
|
||||
th { "ID" }
|
||||
th { "Name" }
|
||||
th { "Type" }
|
||||
th { "Description" }
|
||||
th { "Actions" }
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
for creature in creatures {
|
||||
tr {
|
||||
td { "{creature.id}" }
|
||||
td { "{creature.name}" }
|
||||
td { "{creature.typ.as_deref().unwrap_or(\"-\")}" }
|
||||
td { "{creature.beschreibung.as_deref().unwrap_or(\"-\")}" }
|
||||
td {
|
||||
a {
|
||||
href: "/creatures/{creature.id}/edit",
|
||||
"Edit"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/pages/home.rs
Normal file
11
src/pages/home.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn HomePage() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h2 { "Home" }
|
||||
p { "Welcome to Owlibou Tavern!" }
|
||||
}
|
||||
}
|
||||
}
|
26
src/pages/import.rs
Normal file
26
src/pages/import.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn ImportPage() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
h2 { "Import Data" }
|
||||
p { "This page will be used to import data from files." }
|
||||
form {
|
||||
prevent_default: "onsubmit",
|
||||
onsubmit: move |_| {
|
||||
// Placeholder for form submission logic
|
||||
log::info!("Form submitted for file import.");
|
||||
},
|
||||
input {
|
||||
r#type: "file",
|
||||
accept: ".json,.csv", // Example file types
|
||||
}
|
||||
button {
|
||||
r#type: "submit",
|
||||
"Import"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
src/pages/mod.rs
Normal file
7
src/pages/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// This module contains the page components.
|
||||
|
||||
pub mod home;
|
||||
pub mod creatures;
|
||||
pub mod changelog;
|
||||
pub mod creature_edit;
|
||||
pub mod import;
|
Reference in New Issue
Block a user