This commit is contained in:
JMARyA 2025-01-26 10:52:02 +01:00
parent 5ae9b80302
commit 8daef6920c
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
9 changed files with 158 additions and 135 deletions

48
Cargo.lock generated
View file

@ -202,7 +202,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "based"
version = "0.1.0"
source = "git+https://git.hydrar.de/jmarya/based?branch=ui#79f08fd202abcfbc52cbab09be7dccc02f4c7c01"
source = "git+https://git.hydrar.de/jmarya/based?branch=ui#3f411e071fd4d47ff0e2d7e7eeab4f638bd35799"
dependencies = [
"bcrypt",
"chrono",
@ -403,9 +403,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
@ -1350,9 +1350,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.7.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
@ -1376,19 +1376,19 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.10.1"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "is-terminal"
version = "0.4.13"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
dependencies = [
"hermit-abi 0.4.0",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1601,9 +1601,9 @@ dependencies = [
[[package]]
name = "native-tls"
version = "0.2.12"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c"
dependencies = [
"libc",
"log",
@ -1706,9 +1706,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "openssl"
version = "0.10.68"
version = "0.10.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e"
dependencies = [
"bitflags 2.8.0",
"cfg-if",
@ -1732,9 +1732,9 @@ dependencies = [
[[package]]
name = "openssl-probe"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
@ -2313,9 +2313,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "0.38.43"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.8.0",
"errno",
@ -2459,9 +2459,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.135"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
dependencies = [
"itoa",
"memchr",
@ -3286,9 +3286,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-ident"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243"
[[package]]
name = "unicode-normalization"
@ -3354,9 +3354,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
dependencies = [
"getrandom",
"serde",

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
based = { git = "https://git.hydrar.de/jmarya/based", features = ["htmx"], branch = "ui" }
based = { git = "https://git.hydrar.de/jmarya/based", branch = "ui" }
bytesize = "1.3.0"
chrono = "0.4.39"
env_logger = "0.11.6"

View file

@ -1,5 +1,8 @@
use based::asset::AssetRoutes;
use based::auth::User;
use based::get_pg;
use based::ui::components::Shell;
use based::ui::prelude::*;
use config::Config;
use rocket::routes;
@ -19,9 +22,20 @@ async fn launch() -> _ {
let _ = User::create("admin".to_string(), "admin", based::auth::UserRole::Admin).await;
let shell = Shell::new(
Nothing(),
Nothing(),
Background(MinHeight(
ScreenValue::screen,
Padding(Text("").color(&Gray::_200)).all(ScreenValue::_10),
))
.color(Gray::_900),
)
.use_ui();
rocket::build()
.mount_assets()
.mount("/", routes![
based::htmx::htmx_script_route,
routes::index_page,
routes::pkg_route,
routes::push::upload_pkg,
@ -36,4 +50,5 @@ async fn launch() -> _ {
routes::user::change_password_post
])
.manage(config)
.manage(shell)
}

View file

@ -91,7 +91,7 @@ impl Package {
}
}
let mut ret: Vec<_> = ret.into_iter().map(|x| (x.0, x.1.join(" "))).collect();
let mut ret: Vec<_> = ret.into_iter().map(|x| (x.0, x.1.join(";"))).collect();
ret.sort_by(|a, b| a.0.cmp(&b.0));
ret
@ -218,6 +218,15 @@ impl Package {
.collect()
}
pub fn fonts(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/fonts"))
.map(|x| x.trim_start_matches("usr/share/fonts/").to_string())
.collect()
}
pub fn firmware(&self) -> Vec<String> {
let files = self.file_list();
files

View file

@ -1,8 +1,8 @@
use based::auth::MaybeUser;
use based::request::{RawResponse, RequestContext, StringResponse, respond_with};
use based::ui::components::Shell;
use based::ui::{prelude::*, render_page};
use maud::{PreEscaped, Render, html};
use based::ui::prelude::*;
use maud::{Render, html};
use pacco::pkg::mirror::MirrorRepository;
use rocket::http::{ContentType, Status};
use rocket::{State, get};
@ -21,6 +21,7 @@ pub async fn index_page(
ctx: RequestContext,
user: MaybeUser,
config: &State<Config>,
shell: &State<Shell>,
) -> StringResponse {
let repos: Vec<String> = Repository::list();
@ -40,8 +41,8 @@ pub async fn index_page(
Padding(
Shadow::medium(
Flex(Hover(
Background(Gray::_600, Nothing())).on(
Background(Gray::_700,
Background(Nothing()).color(Gray::_600)).on(
Background(
Link(
&format!("/{repo}"),
Div().vanish().push(Text(&repo).medium().color(&Gray::_100))
@ -49,8 +50,8 @@ pub async fn index_page(
if config.is_mirrored_repo(&repo) {
Some(&())
} else { None },
|_: &()| Context(Padding(Rounded(Background(Blue::_500, Text("Mirrored").white().medium().sm())).size(Size::Full)).x(ScreenValue::_3).y(ScreenValue::_1)))).use_htmx()
))).items_center().gap(ScreenValue::_4)
|_: &()| Context(Padding(Rounded(Background(Text("Mirrored").white().medium().sm()).color(Blue::_500)).size(Size::Full)).x(ScreenValue::_3).y(ScreenValue::_1)))).use_htmx()
).color(Gray::_700))).items_center().gap(ScreenValue::_4)
)).all(ScreenValue::_4))
.size(Size::Large)))
}
@ -58,7 +59,7 @@ pub async fn index_page(
)
).render();
render(content, "Repositories", ctx).await
shell.render_page(content, "Repositories", ctx).await
}
#[get("/pkg/<repo>/<arch>/<pkg_name>")]
@ -159,28 +160,6 @@ pub async fn pkg_route(
)
}
pub async fn render(
content: PreEscaped<String>,
title: &str,
ctx: RequestContext,
) -> StringResponse {
render_page(
content,
title,
ctx,
&Shell::new(
html! {
script src="https://cdn.tailwindcss.com" {};
script src="/assets/htmx.min.js" {};
meta name="viewport" content="width=device-width, initial-scale=1.0";
},
html! {},
Some("bg-gray-900 text-gray-200 min-h-screen p-10".to_string()),
),
)
.await
}
pub fn is_repo_db(pkg_name: &str) -> bool {
pkg_name.ends_with("db.tar.gz")
|| pkg_name.ends_with("db")

View file

@ -1,8 +1,6 @@
use based::ui::prelude::*;
use maud::{PreEscaped, Render};
use super::render;
// TODO : API
pub mod pkg;
@ -21,9 +19,9 @@ pub fn arch_card(a: &str, repo_name: &str, current: bool) -> PreEscaped<String>
Rounded(
Padding(if current {
Background(Blue::_500, link)
Background(link).color(Blue::_500)
} else {
Background(Gray::_700, link)
Background(link).color(Gray::_700)
})
.x(ScreenValue::_3)
.y(ScreenValue::_1),

View file

@ -1,15 +1,14 @@
use based::request::{RequestContext, StringResponse};
use based::ui::components::Shell;
use based::ui::primitives::flex::Strategy;
use based::ui::primitives::space::SpaceBetweenWidget;
use based::ui::primitives::text::{Code, TextWidget};
use based::ui::{UIWidget, prelude::*};
use maud::{PreEscaped, Render, html};
use rocket::get;
use rocket::{State, get};
use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name};
use crate::routes::render;
use super::take_out;
#[get("/<repo>/<pkg_name>?<ver>")]
@ -18,6 +17,7 @@ pub async fn pkg_ui(
pkg_name: &str,
ctx: RequestContext,
ver: Option<&str>,
shell: &State<Shell>,
) -> Option<StringResponse> {
let repo = Repository::new(repo).unwrap();
let mut pkg = repo.get_pkg_by_name(pkg_name)?;
@ -42,6 +42,7 @@ pub async fn pkg_ui(
let shared_objects = pkg.shared_objects();
let icons = pkg.icons();
let readmes = pkg.readmes();
let fonts = pkg.fonts();
let mut pkginfo = pkg.pkginfo();
let pkg_info_header = Flex(
@ -69,15 +70,15 @@ pub async fn pkg_ui(
.push_for_each(&arch, |arch: &Architecture| {
Rounded(
Padding(
Background(Gray::_700,
Span(&arch.to_string()).medium().sm())
Background(
Span(&arch.to_string()).medium().sm()).color(Gray::_700)
).x(ScreenValue::_3).y(ScreenValue::_1)
).size(Size::Full)
})
.push(Margin(
Flex(
Padding(
Hover(Background(Gray::_300, Nothing())).on(Background(Gray::_200,
Hover(Background(Nothing()).color(Gray::_300)).on(Background(
Rounded(Shadow::medium(
Link(&format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name()),
Paragraph(
@ -88,7 +89,7 @@ pub async fn pkg_ui(
p class="ml-2 text-black" { "Download" };
}
).black().xs()))).size(Size::Large)
))
).color(Gray::_200))
).x(ScreenValue::_2).y(ScreenValue::_2)
).items_center()
).left(ScreenValue::_4)
@ -101,9 +102,8 @@ pub async fn pkg_ui(
.vanish()
.push(Context(FlexGrow(
Strategy::Grow,
Animated(Background(
Gray::_800,
Shadow::large(
Animated(
Background(Shadow::large(
Rounded(
Margin(
Padding(
@ -143,14 +143,14 @@ pub async fn pkg_ui(
.all(ScreenValue::_2),
)
.size(Size::Large),
),
)),
))
.color(Gray::_800),
),
)))
.push(
SpaceBetween(
Padding(Background(
Gray::_800,
Shadow::large(
Padding(
Background(Shadow::large(
Rounded(
Margin(
Screen::medium(FlexGrow(Strategy::NoGrow, Nothing())).on(
@ -210,8 +210,9 @@ pub async fn pkg_ui(
.all(ScreenValue::_2),
)
.size(Size::Large),
),
))
))
.color(Gray::_800),
)
.all(ScreenValue::_4),
)
.y(ScreenValue::_2),
@ -257,12 +258,14 @@ pub async fn pkg_ui(
)
.push(
Padding(
Rounded(Background(
Gray::_700,
Code(&hook.1)
.sm()
.color(&Gray::_100),
))
Rounded(
Background(
Code(&hook.1)
.sm()
.color(&Gray::_100),
)
.color(Gray::_700),
)
.size(Size::Large),
)
.all(ScreenValue::_4),
@ -293,12 +296,14 @@ pub async fn pkg_ui(
)
.push(
Padding(
Rounded(Background(
Gray::_700,
Code(&readme.1)
.sm()
.color(&Gray::_100),
))
Rounded(
Background(
Code(&readme.1)
.sm()
.color(&Gray::_100),
)
.color(Gray::_700),
)
.size(Size::Large),
)
.all(ScreenValue::_4),
@ -336,6 +341,14 @@ pub async fn pkg_ui(
.push(ListElements(&shared_objects)),
)
})
.push_if(!fonts.is_empty(), || {
InfoCard(
Div()
.vanish()
.push(CardTitle("Fonts"))
.push(ListElements(&fonts)),
)
})
.push_if(!firmware.is_empty(), || {
InfoCard(
Div()
@ -412,13 +425,15 @@ pub async fn pkg_ui(
)
.push(
Rounded(
Padding(Background(
Gray::_700,
Code(install_script.trim())
.color(&Gray::_100)
.sm()
.indentation(ScreenValue::_0),
))
Padding(
Background(
Code(install_script.trim())
.color(&Gray::_100)
.sm()
.indentation(ScreenValue::_0),
)
.color(Gray::_700),
)
.all(ScreenValue::_2),
)
.size(Size::Large),
@ -430,16 +445,18 @@ pub async fn pkg_ui(
})
.render();
Some(render(content, pkg_name, ctx).await)
Some(shell.render_page(content, pkg_name, ctx).await)
}
pub fn build_info(key: String, value: String) -> PreEscaped<String> {
match key.as_str() {
"pkgname" => {}
"pkgbase" => {}
"xdata" => {}
"arch" => {}
"pkbase" => {}
"pkgver" => {}
"provides" => {}
"pkgdesc" => {
return key_value("Description".to_string(), value);
}
@ -522,13 +539,13 @@ pub fn CardTitle(title: &str) -> PreEscaped<String> {
#[allow(non_snake_case)]
pub fn InfoCard<T: UIWidget + 'static>(inner: T) -> SpaceBetweenWidget {
SpaceBetween(
Padding(Background(
Gray::_800,
Shadow::large(
Padding(
Background(Shadow::large(
Rounded(Margin(FlexGrow(Strategy::Grow, inner)).all(ScreenValue::_2))
.size(Size::Large),
),
))
))
.color(Gray::_800),
)
.all(ScreenValue::_4),
)
.y(ScreenValue::_2)

View file

@ -1,4 +1,5 @@
use based::request::{RequestContext, StringResponse};
use based::ui::components::Shell;
use based::ui::prelude::*;
use maud::{PreEscaped, Render, html};
use rocket::{State, get};
@ -8,14 +9,13 @@ use pacco::pkg::{Repository, arch::Architecture};
use crate::config::Config;
use crate::routes::ui::arch_card;
use super::render;
#[get("/<repo>?<arch>")]
pub async fn repo_ui(
repo: &str,
ctx: RequestContext,
arch: Option<&str>,
config: &State<Config>,
shell: &State<Shell>,
) -> StringResponse {
let arch = arch.map(|x| Architecture::parse(x).unwrap_or(Architecture::any));
@ -35,7 +35,6 @@ pub async fn repo_ui(
.push(Text(&repo.name)._3xl().bold().color(&Gray::_100))
.push_if(config.is_mirrored_repo(&repo.name), || {
Background(
Blue::_500,
Rounded(Margin(
Padding(Text("Mirrored").sm().medium().white())
.x(ScreenValue::_3)
@ -43,6 +42,7 @@ pub async fn repo_ui(
))
.size(Size::Full),
)
.color(Blue::_500)
})
.push(
Screen::medium(Margin(Nothing()).top(ScreenValue::_0)).on(Margin(
@ -81,8 +81,7 @@ pub async fn repo_ui(
Animated(
Flex(
Padding(
Hover(Background(Gray::_600, Nothing())).on(Background(
Gray::_700,
Hover(Background(Nothing()).color(Gray::_600)).on(Background(
Rounded(Shadow::medium(
Link(
&format!("/{}/{pkg}", repo.name),
@ -103,7 +102,8 @@ pub async fn repo_ui(
.use_htmx(),
))
.size(Size::Large),
)),
)
.color(Gray::_700)),
)
.all(ScreenValue::_4),
)
@ -115,7 +115,7 @@ pub async fn repo_ui(
let content = Div().vanish().push(repo_info).push(package_list).render();
render(content, &repo.name, ctx).await
shell.render_page(content, &repo.name, ctx).await
}
#[allow(non_snake_case)]
@ -123,23 +123,25 @@ pub fn FirstLetterCircle(name: &str) -> PreEscaped<String> {
Sized(
ScreenValue::_10,
ScreenValue::_10,
Rounded(Background(
Blue::_500,
Flex(
Text(
&name
.chars()
.next()
.unwrap_or_default()
.to_uppercase()
.to_string(),
Rounded(
Background(
Flex(
Text(
&name
.chars()
.next()
.unwrap_or_default()
.to_uppercase()
.to_string(),
)
.white()
.semibold(),
)
.white()
.semibold(),
.full_center()
.group(),
)
.full_center()
.group(),
))
.color(Blue::_500),
)
.size(Size::Full),
)
.render()

View file

@ -1,11 +1,11 @@
use based::{
auth::{Session, Sessions, User, csrf::CSRF},
request::{RequestContext, StringResponse, api::to_uuid, respond_html},
ui::AttrExtendable,
ui::{AttrExtendable, components::Shell},
};
use maud::{PreEscaped, html};
use rocket::{
FromForm,
FromForm, State,
form::Form,
get,
http::{Cookie, CookieJar},
@ -13,11 +13,10 @@ use rocket::{
response::Redirect,
};
use super::render;
use based::ui::prelude::*;
#[get("/login")]
pub async fn login(ctx: RequestContext) -> StringResponse {
pub async fn login(ctx: RequestContext, shell: &State<Shell>) -> StringResponse {
let content = html! {
div class="min-w-screen justify-center flex items-center" {
div class="bg-gray-800 shadow-lg rounded-lg p-8 max-w-sm w-full" {
@ -37,7 +36,7 @@ pub async fn login(ctx: RequestContext) -> StringResponse {
};
};
render(content, "Login", ctx).await
shell.render_page(content, "Login", ctx).await
}
#[derive(FromForm)]
@ -116,7 +115,7 @@ pub async fn new_api_key(user: User, csrf: &str, session_name: &str) -> StringRe
}
#[get("/account")]
pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse {
pub async fn account_page(user: User, ctx: RequestContext, shell: &State<Shell>) -> StringResponse {
let sessions = user.list_sessions().await;
let content = html! {
@ -128,8 +127,8 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse {
(Link("/passwd", Margin(
Rounded(Padding(
Hover(
Background(Green::_600, Nothing())).on(
Background(Green::_500, Text("Change Password").white())
Background(Nothing()).color(Green::_600)).on(
Background(Text("Change Password").white()).color(Green::_500)
)
).x(ScreenValue::_6).y(ScreenValue::_2))
).bottom(ScreenValue::_6)).use_htmx())
@ -155,7 +154,7 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse {
};
};
render(content, "Account", ctx).await
shell.render_page(content, "Account", ctx).await
}
pub async fn build_session_block(ses: &Session, user: &User) -> PreEscaped<String> {
@ -198,7 +197,11 @@ pub async fn change_password_post(form: Form<PasswordChangeForm>, user: User) ->
}
#[get("/passwd")]
pub async fn change_password(ctx: RequestContext, user: User) -> StringResponse {
pub async fn change_password(
ctx: RequestContext,
user: User,
shell: &State<Shell>,
) -> StringResponse {
let content = html! {
div class="min-w-screen justify-center flex items-center" {
div class="bg-gray-800 shadow-lg rounded-lg p-8 max-w-sm w-full" {
@ -223,5 +226,5 @@ pub async fn change_password(ctx: RequestContext, user: User) -> StringResponse
};
};
render(content, "Change Password", ctx).await
shell.render_page(content, "Change Password", ctx).await
}