Squashed commit of the following:

commit 8b7e357c0e
Author: JMARyA <jmarya@hydrar.de>
Date:   Sat Jan 18 19:12:11 2025 +0100

    add more package info

commit bf9813b7a4
Author: JMARyA <jmarya@hydrar.de>
Date:   Fri Jan 17 22:48:19 2025 +0100

    add display for man entries + config files

commit 6c1ca1e8c0
Author: JMARyA <jmarya@hydrar.de>
Date:   Fri Jan 17 17:52:28 2025 +0100

    finish ui

commit 36128864aa
Author: JMARyA <jmarya@hydrar.de>
Date:   Wed Jan 15 22:46:25 2025 +0100

    refactor
This commit is contained in:
JMARyA 2025-01-18 19:13:26 +01:00
parent 92cefff212
commit 380352dd07
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
6 changed files with 635 additions and 272 deletions

48
Cargo.lock generated
View file

@ -202,7 +202,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]] [[package]]
name = "based" name = "based"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.hydrar.de/jmarya/based#a5ecf145a93b96b2545a68ce8e98b44ab50379a2" source = "git+https://git.hydrar.de/jmarya/based?branch=ui#79f08fd202abcfbc52cbab09be7dccc02f4c7c01"
dependencies = [ dependencies = [
"bcrypt", "bcrypt",
"chrono", "chrono",
@ -255,9 +255,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.7.0" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -313,9 +313,9 @@ checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.9" version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -485,9 +485,9 @@ dependencies = [
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f"
[[package]] [[package]]
name = "der" name = "der"
@ -535,7 +535,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7"
dependencies = [ dependencies = [
"bitflags 2.7.0", "bitflags 2.8.0",
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote", "quote",
@ -1449,7 +1449,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.7.0", "bitflags 2.8.0",
"libc", "libc",
"redox_syscall", "redox_syscall",
] ]
@ -1488,9 +1488,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.22" version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]] [[package]]
name = "loom" name = "loom"
@ -1562,9 +1562,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
@ -1710,7 +1710,7 @@ version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [ dependencies = [
"bitflags 2.7.0", "bitflags 2.8.0",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -2005,7 +2005,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [ dependencies = [
"bitflags 2.7.0", "bitflags 2.8.0",
] ]
[[package]] [[package]]
@ -2317,7 +2317,7 @@ version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [ dependencies = [
"bitflags 2.7.0", "bitflags 2.8.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -2420,7 +2420,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [ dependencies = [
"bitflags 2.7.0", "bitflags 2.8.0",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -2695,7 +2695,7 @@ checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64 0.22.1", "base64 0.22.1",
"bitflags 2.7.0", "bitflags 2.8.0",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono", "chrono",
@ -2739,7 +2739,7 @@ checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64 0.22.1", "base64 0.22.1",
"bitflags 2.7.0", "bitflags 2.8.0",
"byteorder", "byteorder",
"chrono", "chrono",
"crc", "crc",
@ -2890,7 +2890,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [ dependencies = [
"bitflags 2.7.0", "bitflags 2.8.0",
"core-foundation", "core-foundation",
"system-configuration-sys 0.6.0", "system-configuration-sys 0.6.0",
] ]
@ -3354,9 +3354,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.11.1" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"serde", "serde",
@ -3364,9 +3364,9 @@ dependencies = [
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"

View file

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

View file

@ -192,6 +192,82 @@ impl Package {
Some(base) Some(base)
} }
pub fn man_entries(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/man"))
.map(|x| {
x.trim_start_matches("usr/share/man/")
.trim_end_matches(".gz")
.to_string()
})
.collect()
}
pub fn kernel_modules(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/lib/modules"))
.map(|x| {
x.trim_start_matches("usr/lib/modules/")
.trim_end_matches(".zst")
.to_string()
})
.collect()
}
pub fn firmware(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/lib/firmware"))
.map(|x| {
x.trim_start_matches("usr/lib/firmware/")
.trim_end_matches(".zst")
.to_string()
})
.collect()
}
pub fn keyrings(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/pacman/keyrings"))
.map(|x| {
x.trim_start_matches("usr/share/pacman/keyrings/")
.to_string()
})
.collect()
}
pub fn shared_objects(&self) -> Vec<String> {
list_tar_file(&self.base_path().join(self.file_name()))
.unwrap_or_default()
.into_iter()
.filter(|x| {
let file_name = x.split("/").last().unwrap();
file_name.contains(".so.") || file_name.ends_with(".so")
})
.collect()
}
pub fn icons(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/icons"))
.map(|x| x.trim_start_matches("usr/share/icons/").to_string())
.collect()
}
pub fn etc_entries(&self) -> Vec<String> {
let files = self.file_list();
files.into_iter().filter(|x| x.starts_with("etc")).collect()
}
pub fn systemd_units(&self) -> Vec<String> { pub fn systemd_units(&self) -> Vec<String> {
// TODO : Extract unit infos // TODO : Extract unit infos
list_tar_file(&self.base_path().join(self.file_name())) list_tar_file(&self.base_path().join(self.file_name()))
@ -199,7 +275,7 @@ impl Package {
.into_iter() .into_iter()
.filter(|x| { .filter(|x| {
let ext = x.split(".").last().unwrap(); let ext = x.split(".").last().unwrap();
ext == "service" || ext == "timer" || ext == "mount" ext == "service" || ext == "timer" || ext == "mount" || ext == "socket"
}) })
.collect() .collect()
} }

View file

@ -1,7 +1,8 @@
use based::auth::MaybeUser; use based::auth::MaybeUser;
use based::page::{Shell, htmx_link, render_page};
use based::request::{RawResponse, RequestContext, StringResponse, respond_with}; use based::request::{RawResponse, RequestContext, StringResponse, respond_with};
use maud::{PreEscaped, html}; use based::ui::components::Shell;
use based::ui::{prelude::*, render_page};
use maud::{PreEscaped, Render, html};
use pacco::pkg::mirror::MirrorRepository; use pacco::pkg::mirror::MirrorRepository;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
use rocket::{State, get}; use rocket::{State, get};
@ -23,27 +24,39 @@ pub async fn index_page(
) -> StringResponse { ) -> StringResponse {
let repos: Vec<String> = Repository::list(); let repos: Vec<String> = Repository::list();
let content = html!( let content = Div().vanish()
div class="flex justify-between" { .push(
h1 class="text-4xl font-bold pb-6" { "Repositories" }; Flex(
@if let Some(user) = user.take_user() { Div().vanish()
a class="text-lg" href="/account" { (user.username) }; .push(Padding(Text("Repositories")._4xl().bold()).bottom(ScreenValue::_6))
}; .push_some(user.take_user().as_ref(), |user: &based::auth::User| Link("/account", Text(&user.username).large()))
}; ).justify(Justify::Between)
div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" { ).push(
@for repo in repos { html!(
(htmx_link(&format!("/{repo}"), "flex items-center gap-4 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg shadow-md transition hover:bg-gray-200 dark:hover:bg-gray-600", "", html! { div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" {
p class="font-medium text-gray-800 dark:text-gray-100" { @for repo in repos {
(repo) (Animated(
Rounded(
@if config.is_mirrored_repo(&repo) { Padding(
div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full" { "Mirrored" }; Shadow::medium(
}; Flex(Hover(
}; Background(Gray::_600, Nothing())).on(
})) Background(Gray::_700,
} Link(
}; &format!("/{repo}"),
); Div().vanish().push(Text(&repo).medium().color(&Gray::_100))
.push_some(
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)
)).all(ScreenValue::_4))
.size(Size::Large)))
}
}
)
).render();
render(content, "Repositories", ctx).await render(content, "Repositories", ctx).await
} }

View file

@ -1,8 +1,9 @@
use based::{ use based::request::{RequestContext, StringResponse};
page::htmx_link, use based::ui::primitives::flex::Strategy;
request::{RequestContext, StringResponse}, use based::ui::primitives::space::SpaceBetweenWidget;
}; use based::ui::primitives::text::{Code, TextWidget};
use maud::{PreEscaped, html}; use based::ui::{UIWidget, prelude::*};
use maud::{PreEscaped, Render, html};
use rocket::{State, get}; use rocket::{State, get};
use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name}; use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name};
@ -35,151 +36,305 @@ pub async fn pkg_ui(
let systemd_units = pkg.systemd_units(); let systemd_units = pkg.systemd_units();
let pacman_hooks = pkg.pacman_hooks(); let pacman_hooks = pkg.pacman_hooks();
let binaries = pkg.binaries(); let binaries = pkg.binaries();
let man_entries = pkg.man_entries();
let etc_entries = pkg.etc_entries();
let kernel_modules = pkg.kernel_modules();
let firmware = pkg.firmware();
let keyrings = pkg.keyrings();
let shared_objects = pkg.shared_objects();
let icons = pkg.icons();
let mut pkginfo = pkg.pkginfo(); let mut pkginfo = pkg.pkginfo();
let content = html! { let content = Div().vanish()
// Package Name .push(
div class="flex flex-wrap gap-2 justify-center items-center" { Flex(
(htmx_link(&format!("/{}", repo.name), "text-3xl font-bold text-gray-800 dark:text-gray-100", "", html! { // Package Name
(repo.name) Div().vanish()
})) .push(
p class="font-bold p-2 text-gray-400" { "/" }; Link(&format!("/{}", repo.name),
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 pr-2" { Text(&repo.name).bold().color(&Gray::_100)._3xl()
(pkg.name) ).use_htmx()
}; )
.push(Padding(Text("/").bold().color(&Gray::_400)).all(ScreenValue::_2))
@if pkg.is_signed() { .push(
div class="flex items-center gap-2 text-slate-300 pr-4" { Padding(Text(&pkg.name)._3xl().bold().color(&Gray::_100)).right(ScreenValue::_2)
span class="text-2xl font-bold" { )
"" .push_if(pkg.is_signed(), || {
} Flex(
span class="text-sm font-medium" { "Signed" } Padding(
// TODO : Add more info: Who signed? + Public Key recv Div().vanish()
} .push(Span("")._2xl().bold().color(&Slate::_300))
} .push(Span("Signed").sm().medium().color(&Slate::_300))
// TODO : Add more info: Who signed? + Public Key recv
@for arch in arch { ).right(ScreenValue::_4)
span class="px-3 py-1 text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full" { ).items_center().gap(ScreenValue::_2)
(arch.to_string()) })
}; .push_for_each(&arch, |arch: &Architecture| {
} Rounded(
Padding(
a href=(format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name())) class="ml-4 inline-flex items-center px-2 py-2 bg-gray-200 text-black font-xs rounded-lg shadow-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" { Background(Gray::_700,
svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" { Span(&arch.to_string()).medium().sm())
path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {}; ).x(ScreenValue::_3).y(ScreenValue::_1)
}; ).size(Size::Full)
p class="ml-2" { "Download" }; })
}; .push(Margin(
Flex(
}; Padding(
Hover(Background(Gray::_300, Nothing())).on(Background(Gray::_200,
div class="flex flex-wrap pt-6" { Rounded(Shadow::medium(
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition flex-1 m-2 grow" { Link(&format!("/pkg/{}/{}/{}", pkg.repo, pkg.arch.to_string(), pkg.file_name()),
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 underline" { "Info" }; Paragraph(
html! {
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1 { svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" {
(build_info(desc.0, desc.1)) path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {};
} };
p class="ml-2 text-black" { "Download" };
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "packager" }).1 { }
(build_info(desc.0, desc.1)) ).black().xs()))).size(Size::Large)
} ))
).x(ScreenValue::_2).y(ScreenValue::_2)
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "url" }).1 { ).items_center()
(build_info(desc.0, desc.1)) ).left(ScreenValue::_4)
} )
).wrap(Wrap::Wrap).gap(ScreenValue::_2).full_center().group()
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "size" }).1 { )
(build_info(desc.0, desc.1)) .push(
} Padding(
Flex(
@for (key, val) in pkginfo { Div().vanish().push(
(build_info(key, val)) Context(
}; FlexGrow(Strategy::Grow,
}; Animated(Background(Gray::_800, Shadow::large(Rounded(Margin(Padding(SpaceBetween(
Div()
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow md:grow-0 md:shrink" { .push(
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { "Versions" } Context(Text("Info").xl().semibold().color(&Gray::_300).underlined())
ul class="space-y-1" { )
@for version in versions { .push_some(
li class="text-gray-800 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400 transition" { take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1,
(htmx_link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name), if pkg.version.as_ref().map(|x| *x == Package::version(&version).0).unwrap_or_default() { "text-blue-500" } else { "" }, "", html! { |x: (String, String)| build_info(x.0, x.1)
(version) )
})) .push_some(
}; take_out(&mut pkginfo, |x| { x.0 == "packager" }).1,
} |x: (String, String)| build_info(x.0, x.1)
} )
} .push_some(
}; take_out(&mut pkginfo, |x| { x.0 == "url" }).1,
|x: (String, String)| build_info(x.0, x.1)
div class="flex flex-wrap pt-6" { )
div class="space-y-2" { .push_some(
h2 class="text-xl font-bold text-gray-700 dark:text-gray-300" { "Content" } take_out(&mut pkginfo, |x| { x.0 == "size" }).1,
|x: (String, String)| build_info(x.0, x.1)
div class="flex flex-wrap" { )
@if !systemd_units.is_empty() { .push_for_each(&pkginfo, |(key, val)| build_info(key.clone(), val.clone()))
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { ).y(ScreenValue::_2)).all(ScreenValue::_4)).all(ScreenValue::_2)).size(Size::Large)))))
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Systemd Units" } )).push(
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { SpaceBetween(
@for unit in systemd_units { Padding(
li { (unit) } Background(Gray::_800,
} Shadow::large(
} Rounded(
} Margin(
} Screen::medium(FlexGrow(Strategy::NoGrow, Nothing())).on(
FlexGrow(Strategy::Grow,
@if !pacman_hooks.is_empty() { Div().vanish()
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { .push(
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Pacman Hooks" } Text("Versions").xl().semibold().color(&Gray::_300)
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { )
@for hook in pacman_hooks { .push(
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { (hook.0) } SpaceBetween(
Div().vanish()
pre class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg text-gray-800 dark:text-gray-100 overflow-x-auto text-sm" { .push_for_each(&versions, |version: &String| {
(hook.1) Animated(Link(&format!("/{}/{}?ver={version}", repo.name, &pkg.name),
} if pkg.version.as_ref()
} .map(|x| *x == Package::version(&version).0).unwrap_or_default()
} { Text(&version).color(&Blue::_500).render() } else {
} Hover(Text("").color(&Blue::_400)).on(
} Text(&version).color(&Gray::_100)).render()
}
@if !binaries.is_empty() { ).use_htmx())
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { })
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Binaries" } ).y(ScreenValue::_1)
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { )
@for binary in binaries { ))
li { (binary) } ).all(ScreenValue::_2)
} ).size(Size::Large)
} )
} )
} ).all(ScreenValue::_4)
).y(ScreenValue::_2)
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" { )
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Package Files" } ).wrap(Wrap::Wrap).group()
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" { ).top(ScreenValue::_6)
@for file in pkg.file_list() { ).push(
li { (file) } Padding(Flex(
} SpaceBetween(
} Div().vanish()
} .push(
} Text("Content").xl().bold().color(&Gray::_300)
}; )
}; .push(
Flex(
// Install Script Div().vanish()
@if let Some(install_script) = install_script { .push_if(!systemd_units.is_empty(), || {
div class="space-y-4 pt-6" { InfoCard(
h2 class="text-3xl font-semibold text-gray-700 dark:text-gray-300" { "Install Script" } Div().vanish()
pre class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg text-gray-800 dark:text-gray-100 overflow-x-auto text-sm" { .push(
(install_script) CardTitle("Systemd Units")
} )
}; .push(
} ListElements(&systemd_units)
}; )
)
})
.push_if(!pacman_hooks.is_empty(), || {
InfoCard(
Div().vanish()
.push(CardTitle("Pacman Hooks"))
.push(
Paragraph(
SpaceBetween(
Div().vanish()
.push_for_each(&pacman_hooks, |hook: &(String, String)| {
Div().vanish()
.push(
Text(&hook.0).xl().semibold().color(&Gray::_300)
)
.push(
Padding(Rounded(Background(Gray::_700, Code(&hook.1).sm().color(&Gray::_100))).size(Size::Large)).all(ScreenValue::_4)
)
})
.push(
html! {
@for unit in &systemd_units {
li { (unit) }
}
})
).y(ScreenValue::_1)
).list_style(ListStyle::Disc).color(&Gray::_300)
)
)
})
.push_if(!binaries.is_empty(),
|| InfoCard(
Div().vanish()
.push(
CardTitle("Binaries")
).push(
ListElements(&binaries)
)
)
) .push_if(!kernel_modules.is_empty(),
|| InfoCard(
Div().vanish()
.push(CardTitle("Kernel Modules"))
.push(
ListElements(&kernel_modules)
)
)
).push_if(!shared_objects.is_empty(),
|| InfoCard(
Div().vanish()
.push(CardTitle("Shared Objects (Libraries)"))
.push(
ListElements(&shared_objects)
)
)
).push_if(!firmware.is_empty(),
|| InfoCard(
Div().vanish()
.push(CardTitle("Firmware"))
.push(
ListElements(&firmware)
)
)
).push_if(!keyrings.is_empty(),
|| InfoCard(
Div().vanish()
.push(CardTitle("Pacman Keyrings"))
.push(
ListElements(&keyrings)
)
)
).push_if(!icons.is_empty(),
|| InfoCard(
Div().vanish()
.push(CardTitle("Icons"))
.push(
ListElements(&icons)
)
)
)
.push_if(!man_entries.is_empty(),
|| InfoCard(
Div().vanish()
.push(CardTitle("Man Entries"))
.push(
ListElements(&man_entries)
)
)
)
.push_if(!etc_entries.is_empty(),
|| InfoCard(
Div().vanish()
.push(CardTitle("Config Files"))
.push(
ListElements(&etc_entries)
)
)
)
.push(
InfoCard(
Div().vanish().push(
CardTitle("Package Files")
)
.push(
ListElements(&pkg.file_list())
)
)
)
).group().wrap(Wrap::Wrap)
)
).y(ScreenValue::_2)
).wrap(Wrap::Wrap).group()).top(ScreenValue::_6)
).push_some(install_script.as_ref(), |install_script: &String| {
SpaceBetween(
Margin(
Div()
.push(Text("Install Script")._3xl().semibold().color(&Gray::_300).indentation(ScreenValue::_2))
.push(Rounded(Padding(Background(Gray::_700,
Code(install_script.trim())
.color(&Gray::_100).sm()
.indentation(ScreenValue::_0)
)).all(ScreenValue::_2)).size(Size::Large))
).top(ScreenValue::_6)
).y(ScreenValue::_4)
}).render();
Some(render(content, pkg_name, ctx).await) Some(render(content, pkg_name, ctx).await)
} }
pub fn arch_card(a: &str, repo_name: &str, current: bool) -> PreEscaped<String> {
let url = if current {
&format!("/{repo_name}")
} else {
&format!("/{repo_name}?arch={a}")
};
let link = Link(&url, Text(&a).sm().medium().color(&Gray::_300)).use_htmx();
Rounded(
Padding(if current {
Background(Blue::_500, link)
} else {
Background(Gray::_700, link)
})
.x(ScreenValue::_3)
.y(ScreenValue::_1),
)
.size(Size::Full)
.render()
}
#[get("/<repo>?<arch>")] #[get("/<repo>?<arch>")]
pub async fn repo_ui( pub async fn repo_ui(
repo: &str, repo: &str,
@ -197,56 +352,128 @@ pub async fn repo_ui(
repo.list_pkg() repo.list_pkg()
}; };
let content = html! { let repo_info = Margin(
// Repository name and architectures // Repository name and architectures
div class="flex flex-wrap items-center justify-center mb-6" { Flex(
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100" { Div()
(repo.name) .vanish()
}; .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)
.y(ScreenValue::_1),
))
.size(Size::Full),
)
})
.push(
Screen::medium(Margin(Nothing()).top(ScreenValue::_0)).on(Margin(
Flex(Div().vanish().push_for_each(&architectures, |a: &String| {
html! {
@if let Some(arch) = arch.as_ref() {
@if arch.to_string() == *a {
(arch_card(&a, &repo.name, true))
} @else {
(arch_card(&a, &repo.name, false))
}
} @else {
(arch_card(&a, &repo.name, false))
}
}
}))
.group()
.gap(ScreenValue::_2),
)
.top(ScreenValue::_2)),
)
.push(
Text(&format!("{} packages", packages.len()))
.sm()
.color(&Gray::_100),
),
)
.gap(ScreenValue::_3)
.wrap(Wrap::Wrap)
.full_center()
.group(),
)
.bottom(ScreenValue::_6);
@if config.is_mirrored_repo(&repo.name) { let package_list = SpaceBetween(Div().vanish().push_for_each(&packages, |pkg| {
div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full ml-4" { "Mirrored" }; Animated(
}; Flex(
Padding(
Hover(Background(Gray::_600, Nothing())).on(Background(
Gray::_700,
Rounded(Shadow::medium(
Link(
&format!("/{}/{pkg}", repo.name),
Div()
.vanish()
.push(Context(Sized(
ScreenValue::_10,
ScreenValue::_10,
Rounded(Background(
Blue::_500,
Flex(
Text(
&pkg.chars()
.next()
.unwrap_or_default()
.to_uppercase()
.to_string(),
)
.white()
.semibold(),
)
.full_center()
.group(),
))
.size(Size::Full),
)))
.push(Context(Width(
ScreenValue::fit,
Text(&pkg).medium().color(&Gray::_100),
)))
.push(Context(
Padding(
Flex(
Div()
.vanish()
.push(
Span("")._2xl().bold().color(&Slate::_300),
)
.push(
Span("Signed")
.sm()
.medium()
.color(&Slate::_300),
),
)
.items_center()
.gap(ScreenValue::_2)
.group(),
)
.right(ScreenValue::_4),
)),
)
.use_htmx(),
))
.size(Size::Large),
)),
)
.all(ScreenValue::_4),
)
.items_center()
.gap(ScreenValue::_4),
)
}))
.y(ScreenValue::_4);
div class="flex gap-2 mt-2 md:mt-0 ml-4" { let content = Div().vanish().push(repo_info).push(package_list).render();
@for a in architectures {
@if let Some(arch) = arch.as_ref() {
@if arch.to_string() == a {
(htmx_link(&format!("/{}", repo.name), "px-3 py-1 text-sm font-medium bg-blue-400 dark:bg-blue-500 text-gray-600 dark:text-gray-300 rounded-full", "", html! {
(a)
}));
} @else {
(htmx_link(&format!("/{}?arch={}", repo.name, a), "px-3 py-1 text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full", "", html! {
(a)
}));
}
} @else {
(htmx_link(&format!("/{}?arch={}", repo.name, a), "px-3 py-1 text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full", "", html! {
(a)
}));
}
}
}
};
// Package list
ul class="space-y-4" {
@for pkg in packages {
(htmx_link(&format!("/{}/{pkg}", repo.name), "flex items-center gap-4 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg shadow-md transition hover:bg-gray-200 dark:hover:bg-gray-600", "", html! {
div class="w-10 h-10 flex items-center justify-center rounded-full bg-blue-500 text-white font-semibold" {
{(pkg.chars().next().unwrap_or_default().to_uppercase())}
};
p class="font-medium flex-1 text-gray-800 dark:text-gray-100 max-w-fit" {
(pkg)
};
div class="flex items-center gap-2 text-slate-300 pr-4" {
span class="text-2xl font-bold" { "" };
span class="text-sm font-medium" { "Signed" };
};
}))
}
}
};
render(content, &repo.name, ctx).await render(content, &repo.name, ctx).await
} }
@ -265,12 +492,16 @@ pub fn build_info(key: String, value: String) -> PreEscaped<String> {
return key_value("Packager".to_string(), value); return key_value("Packager".to_string(), value);
} }
"url" => { "url" => {
return html! { return Flex(
div class="flex" { Div()
span class="font-bold w-32" { (format!("Website: ")) }; .vanish()
a class="ml-2 text-blue-400" href=(value) { (value) }; .push(Width(ScreenValue::_32, Span("Website: ").bold()))
}; .push(
}; Margin(Link(&value, Span(&value).color(&Blue::_400))).left(ScreenValue::_6),
),
)
.group()
.render();
} }
"builddate" => { "builddate" => {
let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap(); let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap();
@ -303,12 +534,15 @@ pub fn build_info(key: String, value: String) -> PreEscaped<String> {
} }
pub fn key_value(key: String, value: String) -> PreEscaped<String> { pub fn key_value(key: String, value: String) -> PreEscaped<String> {
html! { Flex(
div class="flex items-center" { Div()
span class="font-bold w-32" { (format!("{key}: ")) }; .vanish()
span class="ml-2" { (value) }; .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold()))
}; .push(Margin(Span(&value)).left(ScreenValue::_6)),
} )
.items_center()
.group()
.render()
} }
pub fn take_out<T>(v: &mut Vec<T>, f: impl Fn(&T) -> bool) -> (&mut Vec<T>, Option<T>) { pub fn take_out<T>(v: &mut Vec<T>, f: impl Fn(&T) -> bool) -> (&mut Vec<T>, Option<T>) {
@ -339,24 +573,57 @@ pub fn find_pkg_url(pkg: &str) -> Option<String> {
pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped<String> { pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped<String> {
let pkgs = value.split_whitespace().map(|pkg| { let pkgs = value.split_whitespace().map(|pkg| {
if let Some(pkg_url) = find_pkg_url(pkg) { if let Some(pkg_url) = find_pkg_url(pkg) {
html! { Margin(Link(&pkg_url, Text(&pkg).color(&Blue::_400)).use_htmx()).left(ScreenValue::_6)
a href=(pkg_url) class="ml-2 text-blue-400" { (pkg) };
}
} else { } else {
html! { Margin(Span(&pkg)).left(ScreenValue::_6)
span class="ml-2" { (pkg) };
}
} }
}); });
html! { html! {
div class="flex items-center" { (Flex(
span class="font-bold w-32" { (format!("{key}: ")) }; Div().vanish()
div class="flex flex-wrap" { .push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold()))
@for pkg in pkgs { .push(Flex(
(pkg) html! {
}; @for pkg in pkgs {
}; (pkg)
}; }
}
).group().wrap(Wrap::Wrap))
).items_center().group())
} }
} }
#[allow(non_snake_case)]
pub fn ListElements(el: &[String]) -> TextWidget {
Paragraph(
SpaceBetween(html! {
@for unit in el {
li { (unit) }
}
})
.y(ScreenValue::_1),
)
.list_style(ListStyle::Disc)
.color(&Gray::_300)
}
#[allow(non_snake_case)]
pub fn CardTitle(title: &str) -> PreEscaped<String> {
Context(Text(title).large().medium().color(&Gray::_100).underlined())
}
#[allow(non_snake_case)]
pub fn InfoCard<T: UIWidget + 'static>(inner: T) -> SpaceBetweenWidget {
SpaceBetween(
Padding(Background(
Gray::_800,
Shadow::large(
Rounded(Margin(FlexGrow(Strategy::Grow, inner)).all(ScreenValue::_2))
.size(Size::Large),
),
))
.all(ScreenValue::_4),
)
.y(ScreenValue::_2)
}

View file

@ -1,7 +1,7 @@
use based::{ use based::{
auth::{Session, Sessions, User, csrf::CSRF}, auth::{Session, Sessions, User, csrf::CSRF},
page::htmx_link,
request::{RequestContext, StringResponse, api::to_uuid, respond_html}, request::{RequestContext, StringResponse, api::to_uuid, respond_html},
ui::AttrExtendable,
}; };
use maud::{PreEscaped, html}; use maud::{PreEscaped, html};
use rocket::{ use rocket::{
@ -14,6 +14,7 @@ use rocket::{
}; };
use super::render; use super::render;
use based::ui::prelude::*;
#[get("/login")] #[get("/login")]
pub async fn login(ctx: RequestContext) -> StringResponse { pub async fn login(ctx: RequestContext) -> StringResponse {
@ -83,7 +84,7 @@ pub async fn new_api_key(user: User, csrf: &str, session_name: &str) -> StringRe
if session_name.is_empty() { if session_name.is_empty() {
return respond_html( return respond_html(
html! { html! {
div id="next_session" {}; (Div().id("next_session"))
(user.update_csrf().await) (user.update_csrf().await)
} }
.into_string(), .into_string(),
@ -99,7 +100,7 @@ pub async fn new_api_key(user: User, csrf: &str, session_name: &str) -> StringRe
span class="text-red-500" { (api.token) }; span class="text-red-500" { (api.token) };
}; };
div id="next_session" {}; (Div().id("next_session"))
(user.update_csrf().await) (user.update_csrf().await)
} }
.into_string(), .into_string(),
@ -124,7 +125,14 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse {
h2 class="text-xl font-semibold mb-2" { (user.username) }; h2 class="text-xl font-semibold mb-2" { (user.username) };
}; };
(htmx_link("/passwd", "mb-6 bg-green-500 text-white py-2 px-6 rounded hover:bg-green-600", "", html! { "Change Password" })) (Link("/passwd", Margin(
Rounded(Padding(
Hover(
Background(Green::_600, Nothing())).on(
Background(Green::_500, Text("Change Password").white())
)
).x(ScreenValue::_6).y(ScreenValue::_2))
).bottom(ScreenValue::_6)).use_htmx())
section class="mb-6 mt-6" { section class="mb-6 mt-6" {
h3 class="text-lg font-semibold mb-4" { "Active Sessions" }; h3 class="text-lg font-semibold mb-4" { "Active Sessions" };
@ -134,8 +142,7 @@ pub async fn account_page(user: User, ctx: RequestContext) -> StringResponse {
(build_session_block(&ses, &user).await) (build_session_block(&ses, &user).await)
}; };
div id="next_session" {}; (Div().id("next_session"))
} }
} }