Compare commits
10 Commits
release-20
...
release-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47dc4da6d1 | ||
|
|
c501ed9207 | ||
|
|
4861faee18 | ||
|
|
6a4943d692 | ||
|
|
b27caa77f2 | ||
|
|
cba8c20d9b | ||
|
|
0cdd92dc95 | ||
|
|
b9798ce0e2 | ||
|
|
6a882b75b6 | ||
|
|
40c9f66e5c |
@@ -7,7 +7,7 @@ edition = "2021"
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
leptos = { version = "0.8.15", features = ["csr"] }
|
leptos = { version = "0.8.15", features = ["csr", "msgpack"] }
|
||||||
leptos_router = { version = "0.8.11" }
|
leptos_router = { version = "0.8.11" }
|
||||||
|
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
|
|||||||
1159
frontend/package-lock.json
generated
1159
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
|||||||
"autoprefixer": "^10.4.23",
|
"autoprefixer": "^10.4.23",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"postcss-cli": "^11.0.1",
|
"postcss-cli": "^11.0.1",
|
||||||
"postcss-preset-env": "^11.1.3",
|
"postcss-preset-env": "^10.1.3",
|
||||||
"tailwindcss": "^4.1.18"
|
"tailwindcss": "^4.1.18"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ use leptos_shadcn_toast::SonnerProvider;
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn App() -> impl IntoView {
|
pub fn App() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<SonnerProvider>
|
||||||
|
<InnerApp />
|
||||||
|
</SonnerProvider>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn InnerApp() -> impl IntoView {
|
||||||
crate::store::provide_torrent_store();
|
crate::store::provide_torrent_store();
|
||||||
let store = use_context::<crate::store::TorrentStore>();
|
let store = use_context::<crate::store::TorrentStore>();
|
||||||
|
|
||||||
@@ -70,126 +79,124 @@ pub fn App() -> impl IntoView {
|
|||||||
});
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<SonnerProvider>
|
<div class="relative w-full h-screen" style="height: 100dvh;">
|
||||||
<div class="relative w-full h-screen" style="height: 100dvh;">
|
<Router>
|
||||||
<Router>
|
<Routes fallback=|| view! { <div class="p-4">"404 Not Found"</div> }>
|
||||||
<Routes fallback=|| view! { <div class="p-4">"404 Not Found"</div> }>
|
<Route path=leptos_router::path!("/login") view=move || {
|
||||||
<Route path=leptos_router::path!("/login") view=move || {
|
let authenticated = is_authenticated.0.get();
|
||||||
let authenticated = is_authenticated.0.get();
|
let setup_needed = needs_setup.0.get();
|
||||||
let setup_needed = needs_setup.0.get();
|
|
||||||
|
Effect::new(move |_| {
|
||||||
Effect::new(move |_| {
|
if setup_needed {
|
||||||
if setup_needed {
|
let navigate = use_navigate();
|
||||||
let navigate = use_navigate();
|
navigate("/setup", Default::default());
|
||||||
|
} else if authenticated {
|
||||||
|
log::info!("Already authenticated, redirecting to home");
|
||||||
|
let navigate = use_navigate();
|
||||||
|
navigate("/", Default::default());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view! { <Login /> }
|
||||||
|
} />
|
||||||
|
<Route path=leptos_router::path!("/setup") view=move || {
|
||||||
|
Effect::new(move |_| {
|
||||||
|
if is_authenticated.0.get() {
|
||||||
|
let navigate = use_navigate();
|
||||||
|
navigate("/", Default::default());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view! { <Setup /> }
|
||||||
|
} />
|
||||||
|
|
||||||
|
<Route path=leptos_router::path!("/") view=move || {
|
||||||
|
let navigate = use_navigate();
|
||||||
|
Effect::new(move |_| {
|
||||||
|
if !is_loading.0.get() {
|
||||||
|
if needs_setup.0.get() {
|
||||||
|
log::info!("Setup not completed, redirecting to setup");
|
||||||
navigate("/setup", Default::default());
|
navigate("/setup", Default::default());
|
||||||
} else if authenticated {
|
} else if !is_authenticated.0.get() {
|
||||||
log::info!("Already authenticated, redirecting to home");
|
log::info!("Not authenticated, redirecting to login");
|
||||||
let navigate = use_navigate();
|
|
||||||
navigate("/", Default::default());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
view! { <Login /> }
|
|
||||||
} />
|
|
||||||
<Route path=leptos_router::path!("/setup") view=move || {
|
|
||||||
Effect::new(move |_| {
|
|
||||||
if is_authenticated.0.get() {
|
|
||||||
let navigate = use_navigate();
|
|
||||||
navigate("/", Default::default());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
view! { <Setup /> }
|
|
||||||
} />
|
|
||||||
|
|
||||||
<Route path=leptos_router::path!("/") view=move || {
|
|
||||||
let navigate = use_navigate();
|
|
||||||
Effect::new(move |_| {
|
|
||||||
if !is_loading.0.get() {
|
|
||||||
if needs_setup.0.get() {
|
|
||||||
log::info!("Setup not completed, redirecting to setup");
|
|
||||||
navigate("/setup", Default::default());
|
|
||||||
} else if !is_authenticated.0.get() {
|
|
||||||
log::info!("Not authenticated, redirecting to login");
|
|
||||||
navigate("/login", Default::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<Show when=move || !is_loading.0.get() fallback=|| view! {
|
|
||||||
<div class="flex h-screen bg-background">
|
|
||||||
// Sidebar skeleton
|
|
||||||
<div class="w-56 border-r border-border p-4 space-y-4">
|
|
||||||
<Skeleton class="h-8 w-3/4" />
|
|
||||||
<div class="space-y-2">
|
|
||||||
<Skeleton class="h-6 w-full" />
|
|
||||||
<Skeleton class="h-6 w-full" />
|
|
||||||
<Skeleton class="h-6 w-4/5" />
|
|
||||||
<Skeleton class="h-6 w-full" />
|
|
||||||
<Skeleton class="h-6 w-3/5" />
|
|
||||||
<Skeleton class="h-6 w-full" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
// Main content skeleton
|
|
||||||
<div class="flex-1 flex flex-col">
|
|
||||||
// Header skeleton
|
|
||||||
<div class="border-b border-border p-4 flex items-center gap-4">
|
|
||||||
<Skeleton class="h-8 w-48" />
|
|
||||||
<Skeleton class="h-8 w-64" />
|
|
||||||
<div class="ml-auto"><Skeleton class="h-8 w-24" /></div>
|
|
||||||
</div>
|
|
||||||
// Table skeleton rows
|
|
||||||
<div class="flex-1 p-4 space-y-3">
|
|
||||||
<Skeleton class="h-10 w-full" />
|
|
||||||
<Skeleton class="h-10 w-full" />
|
|
||||||
<Skeleton class="h-10 w-full" />
|
|
||||||
<Skeleton class="h-10 w-full" />
|
|
||||||
<Skeleton class="h-10 w-full" />
|
|
||||||
<Skeleton class="h-10 w-3/4" />
|
|
||||||
</div>
|
|
||||||
// Status bar skeleton
|
|
||||||
<div class="border-t border-border p-3">
|
|
||||||
<Skeleton class="h-5 w-96" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}.into_any()>
|
|
||||||
<Show when=move || is_authenticated.0.get() fallback=|| ()>
|
|
||||||
<Protected>
|
|
||||||
<div class="flex flex-col h-full overflow-hidden">
|
|
||||||
<div class="flex-1 overflow-hidden">
|
|
||||||
<TorrentTable />
|
|
||||||
</div>
|
|
||||||
<TorrentDetail />
|
|
||||||
</div>
|
|
||||||
</Protected>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
}.into_any()
|
|
||||||
}/>
|
|
||||||
|
|
||||||
<Route path=leptos_router::path!("/settings") view=move || {
|
|
||||||
Effect::new(move |_| {
|
|
||||||
if !is_authenticated.0.get() {
|
|
||||||
let navigate = use_navigate();
|
|
||||||
navigate("/login", Default::default());
|
navigate("/login", Default::default());
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<Show when=move || !is_loading.0.get() fallback=|| ()>
|
|
||||||
<Show when=move || is_authenticated.0.get() fallback=|| ()>
|
|
||||||
<Protected>
|
|
||||||
<div class="p-4">"Settings Page (Coming Soon)"</div>
|
|
||||||
</Protected>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
}
|
}
|
||||||
}/>
|
});
|
||||||
</Routes>
|
|
||||||
</Router>
|
view! {
|
||||||
</div>
|
<Show when=move || !is_loading.0.get() fallback=|| view! {
|
||||||
</SonnerProvider>
|
<div class="flex h-screen bg-background">
|
||||||
|
// Sidebar skeleton
|
||||||
|
<div class="w-56 border-r border-border p-4 space-y-4">
|
||||||
|
<Skeleton class="h-8 w-3/4" />
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Skeleton class="h-6 w-full" />
|
||||||
|
<Skeleton class="h-6 w-full" />
|
||||||
|
<Skeleton class="h-6 w-4/5" />
|
||||||
|
<Skeleton class="h-6 w-full" />
|
||||||
|
<Skeleton class="h-6 w-3/5" />
|
||||||
|
<Skeleton class="h-6 w-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
// Main content skeleton
|
||||||
|
<div class="flex-1 flex flex-col">
|
||||||
|
// Header skeleton
|
||||||
|
<div class="border-b border-border p-4 flex items-center gap-4">
|
||||||
|
<Skeleton class="h-8 w-48" />
|
||||||
|
<Skeleton class="h-8 w-64" />
|
||||||
|
<div class="ml-auto"><Skeleton class="h-8 w-24" /></div>
|
||||||
|
</div>
|
||||||
|
// Table skeleton rows
|
||||||
|
<div class="flex-1 p-4 space-y-3">
|
||||||
|
<Skeleton class="h-10 w-full" />
|
||||||
|
<Skeleton class="h-10 w-full" />
|
||||||
|
<Skeleton class="h-10 w-full" />
|
||||||
|
<Skeleton class="h-10 w-full" />
|
||||||
|
<Skeleton class="h-10 w-full" />
|
||||||
|
<Skeleton class="h-10 w-3/4" />
|
||||||
|
</div>
|
||||||
|
// Status bar skeleton
|
||||||
|
<div class="border-t border-border p-3">
|
||||||
|
<Skeleton class="h-5 w-96" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}.into_any()>
|
||||||
|
<Show when=move || is_authenticated.0.get() fallback=|| ()>
|
||||||
|
<Protected>
|
||||||
|
<div class="flex flex-col h-full overflow-hidden">
|
||||||
|
<div class="flex-1 overflow-hidden">
|
||||||
|
<TorrentTable />
|
||||||
|
</div>
|
||||||
|
<TorrentDetail />
|
||||||
|
</div>
|
||||||
|
</Protected>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
}.into_any()
|
||||||
|
}/>
|
||||||
|
|
||||||
|
<Route path=leptos_router::path!("/settings") view=move || {
|
||||||
|
Effect::new(move |_| {
|
||||||
|
if !is_authenticated.0.get() {
|
||||||
|
let navigate = use_navigate();
|
||||||
|
navigate("/login", Default::default());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Show when=move || !is_loading.0.get() fallback=|| ()>
|
||||||
|
<Show when=move || is_authenticated.0.get() fallback=|| ()>
|
||||||
|
<Protected>
|
||||||
|
<div class="p-4">"Settings Page (Coming Soon)"</div>
|
||||||
|
</Protected>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
}
|
||||||
|
}/>
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,17 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
||||||
struct-patch = "0.5"
|
struct-patch = "0.5"
|
||||||
rmp-serde = "1.3"
|
rmp-serde = "1.3"
|
||||||
|
bytes = "1"
|
||||||
|
http = "1"
|
||||||
|
|
||||||
# Leptos 0.8.7
|
# Leptos 0.8.7
|
||||||
leptos = { version = "0.8.7", features = ["nightly"] }
|
leptos = { version = "0.8.15", features = ["nightly", "msgpack"] }
|
||||||
leptos_router = { version = "0.8.7", features = ["nightly"] }
|
leptos_router = { version = "0.8.7", features = ["nightly"] }
|
||||||
leptos_axum = { version = "0.8.7", optional = true }
|
leptos_axum = { version = "0.8.7", optional = true }
|
||||||
axum = { version = "0.8", features = ["macros"], optional = true }
|
axum = { version = "0.8", features = ["macros"], optional = true }
|
||||||
|
|
||||||
# SSR Dependencies (XML-RPC & SCGI)
|
# SSR Dependencies (XML-RPC & SCGI)
|
||||||
tokio = { version = "1", features = ["full"], optional = true }
|
tokio = { version = "1", features = ["full"], optional = true }
|
||||||
bytes = { version = "1", optional = true }
|
|
||||||
thiserror = { version = "2", optional = true }
|
thiserror = { version = "2", optional = true }
|
||||||
quick-xml = { version = "0.31", features = ["serde", "serialize"], optional = true }
|
quick-xml = { version = "0.31", features = ["serde", "serialize"], optional = true }
|
||||||
|
|
||||||
@@ -34,7 +35,6 @@ bcrypt = { version = "0.17", optional = true }
|
|||||||
default = []
|
default = []
|
||||||
ssr = [
|
ssr = [
|
||||||
"dep:tokio",
|
"dep:tokio",
|
||||||
"dep:bytes",
|
|
||||||
"dep:thiserror",
|
"dep:thiserror",
|
||||||
"dep:quick-xml",
|
"dep:quick-xml",
|
||||||
"dep:leptos_axum",
|
"dep:leptos_axum",
|
||||||
|
|||||||
1
shared/src/codec.rs
Normal file
1
shared/src/codec.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub use leptos::server_fn::codec::MsgPack;
|
||||||
@@ -11,6 +11,8 @@ pub mod xmlrpc;
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
|
||||||
|
pub mod codec;
|
||||||
|
|
||||||
pub mod server_fns;
|
pub mod server_fns;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::codec::MsgPack;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct UserResponse {
|
pub struct UserResponse {
|
||||||
@@ -19,7 +20,7 @@ pub struct SetupStatus {
|
|||||||
pub completed: bool,
|
pub completed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(GetSetupStatus, "/api/server_fns/GetSetupStatus")]
|
#[server(GetSetupStatus, "/api/server_fns/GetSetupStatus", input = MsgPack, output = MsgPack)]
|
||||||
pub async fn get_setup_status() -> Result<SetupStatus, ServerFnError> {
|
pub async fn get_setup_status() -> Result<SetupStatus, ServerFnError> {
|
||||||
use crate::DbContext;
|
use crate::DbContext;
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ pub async fn get_setup_status() -> Result<SetupStatus, ServerFnError> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(Setup, "/api/server_fns/Setup")]
|
#[server(Setup, "/api/server_fns/Setup", input = MsgPack, output = MsgPack)]
|
||||||
pub async fn setup(username: String, password: String) -> Result<(), ServerFnError> {
|
pub async fn setup(username: String, password: String) -> Result<(), ServerFnError> {
|
||||||
use crate::DbContext;
|
use crate::DbContext;
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ pub async fn setup(username: String, password: String) -> Result<(), ServerFnErr
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(Login, "/api/server_fns/Login")]
|
#[server(Login, "/api/server_fns/Login", input = MsgPack, output = MsgPack)]
|
||||||
pub async fn login(username: String, password: String) -> Result<UserResponse, ServerFnError> {
|
pub async fn login(username: String, password: String) -> Result<UserResponse, ServerFnError> {
|
||||||
use crate::DbContext;
|
use crate::DbContext;
|
||||||
use leptos_axum::ResponseOptions;
|
use leptos_axum::ResponseOptions;
|
||||||
@@ -110,7 +111,7 @@ pub async fn login(username: String, password: String) -> Result<UserResponse, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(Logout, "/api/server_fns/Logout")]
|
#[server(Logout, "/api/server_fns/Logout", input = MsgPack, output = MsgPack)]
|
||||||
pub async fn logout() -> Result<(), ServerFnError> {
|
pub async fn logout() -> Result<(), ServerFnError> {
|
||||||
use leptos_axum::ResponseOptions;
|
use leptos_axum::ResponseOptions;
|
||||||
use cookie::{Cookie, SameSite};
|
use cookie::{Cookie, SameSite};
|
||||||
@@ -131,7 +132,7 @@ pub async fn logout() -> Result<(), ServerFnError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(GetUser, "/api/server_fns/GetUser")]
|
#[server(GetUser, "/api/server_fns/GetUser", input = MsgPack, output = MsgPack)]
|
||||||
pub async fn get_user() -> Result<Option<UserResponse>, ServerFnError> {
|
pub async fn get_user() -> Result<Option<UserResponse>, ServerFnError> {
|
||||||
use axum::http::HeaderMap;
|
use axum::http::HeaderMap;
|
||||||
use leptos_axum::extract;
|
use leptos_axum::extract;
|
||||||
|
|||||||
Reference in New Issue
Block a user