2025-01-15 22:46:25 +01:00
|
|
|
use based::request::{RequestContext, StringResponse};
|
|
|
|
use based::ui::prelude::*;
|
|
|
|
use maud::{PreEscaped, Render, html};
|
2025-01-13 11:39:00 +01:00
|
|
|
use rocket::{State, get};
|
2025-01-12 03:58:16 +01:00
|
|
|
|
2025-01-13 18:07:08 +01:00
|
|
|
use pacco::pkg::{Package, Repository, arch::Architecture, find_package_by_name};
|
2025-01-13 11:39:00 +01:00
|
|
|
|
|
|
|
use crate::config::Config;
|
2025-01-12 03:58:16 +01:00
|
|
|
|
|
|
|
use super::render;
|
|
|
|
|
|
|
|
// TODO : API
|
|
|
|
|
2025-01-13 11:39:00 +01:00
|
|
|
#[get("/<repo>/<pkg_name>?<ver>")]
|
|
|
|
pub async fn pkg_ui(
|
|
|
|
repo: &str,
|
|
|
|
pkg_name: &str,
|
|
|
|
ctx: RequestContext,
|
|
|
|
ver: Option<&str>,
|
|
|
|
) -> Option<StringResponse> {
|
2025-01-12 03:58:16 +01:00
|
|
|
let repo = Repository::new(repo).unwrap();
|
2025-01-13 11:39:00 +01:00
|
|
|
let mut pkg = repo.get_pkg_by_name(pkg_name)?;
|
|
|
|
|
|
|
|
if let Some(ver) = ver {
|
2025-01-13 18:07:08 +01:00
|
|
|
let (version, rel) = Package::version(ver);
|
|
|
|
pkg = pkg.get_version(&version);
|
|
|
|
pkg.rel = rel;
|
2025-01-13 11:39:00 +01:00
|
|
|
}
|
|
|
|
|
2025-01-12 03:58:16 +01:00
|
|
|
let versions = pkg.versions();
|
|
|
|
let arch = pkg.arch();
|
|
|
|
let install_script = pkg.install_script();
|
|
|
|
let systemd_units = pkg.systemd_units();
|
|
|
|
let pacman_hooks = pkg.pacman_hooks();
|
|
|
|
let binaries = pkg.binaries();
|
|
|
|
let mut pkginfo = pkg.pkginfo();
|
|
|
|
|
|
|
|
let content = html! {
|
|
|
|
// Package Name
|
|
|
|
div class="flex flex-wrap gap-2 justify-center items-center" {
|
2025-01-15 22:46:25 +01:00
|
|
|
(Link(&format!("/{}", repo.name),
|
|
|
|
Text(&repo.name).bold().color(&Gray::_100)._3xl()
|
|
|
|
).use_htmx())
|
|
|
|
|
2025-01-12 03:58:16 +01:00
|
|
|
p class="font-bold p-2 text-gray-400" { "/" };
|
|
|
|
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 pr-2" {
|
|
|
|
(pkg.name)
|
|
|
|
};
|
|
|
|
|
|
|
|
@if pkg.is_signed() {
|
|
|
|
div class="flex items-center gap-2 text-slate-300 pr-4" {
|
2025-01-15 22:46:25 +01:00
|
|
|
(Span("✓")._2xl().bold())
|
|
|
|
(Span("Signed").sm().medium())
|
|
|
|
// TODO : Add more info: Who signed? + Public Key recv
|
2025-01-12 03:58:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@for arch in arch {
|
2025-01-15 22:46:25 +01:00
|
|
|
(Rounded(
|
|
|
|
Padding(
|
|
|
|
Background(Gray::_700,
|
|
|
|
Span(&arch.to_string()).medium().sm())
|
|
|
|
).x(ScreenValue::_3).y(ScreenValue::_1)
|
|
|
|
).size(Size::Full)
|
|
|
|
)
|
2025-01-12 03:58:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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" {
|
|
|
|
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" {
|
|
|
|
path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m7-7l-7 7-7-7" {};
|
|
|
|
};
|
|
|
|
p class="ml-2" { "Download" };
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2025-01-12 04:58:53 +01:00
|
|
|
div class="flex flex-wrap pt-6" {
|
|
|
|
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition flex-1 m-2 grow" {
|
2025-01-12 03:58:16 +01:00
|
|
|
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 underline" { "Info" };
|
|
|
|
|
|
|
|
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "pkgdesc" }).1 {
|
|
|
|
(build_info(desc.0, desc.1))
|
|
|
|
}
|
|
|
|
|
|
|
|
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "packager" }).1 {
|
|
|
|
(build_info(desc.0, desc.1))
|
|
|
|
}
|
|
|
|
|
|
|
|
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "url" }).1 {
|
|
|
|
(build_info(desc.0, desc.1))
|
|
|
|
}
|
|
|
|
|
|
|
|
@if let Some(desc) = take_out(&mut pkginfo, |x| { x.0 == "size" }).1 {
|
|
|
|
(build_info(desc.0, desc.1))
|
|
|
|
}
|
|
|
|
|
|
|
|
@for (key, val) in pkginfo {
|
|
|
|
(build_info(key, val))
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2025-01-12 04:58:53 +01:00
|
|
|
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" {
|
2025-01-12 03:58:16 +01:00
|
|
|
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { "Versions" }
|
|
|
|
ul class="space-y-1" {
|
|
|
|
@for version in versions {
|
|
|
|
li class="text-gray-800 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400 transition" {
|
2025-01-15 22:46:25 +01:00
|
|
|
(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) } else { Text(&version) }
|
|
|
|
).use_htmx())
|
2025-01-12 03:58:16 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-01-13 11:39:00 +01:00
|
|
|
div class="flex flex-wrap pt-6" {
|
2025-01-12 03:58:16 +01:00
|
|
|
div class="space-y-2" {
|
|
|
|
h2 class="text-xl font-bold text-gray-700 dark:text-gray-300" { "Content" }
|
|
|
|
|
2025-01-12 04:58:53 +01:00
|
|
|
div class="flex flex-wrap" {
|
2025-01-12 03:58:16 +01:00
|
|
|
@if !systemd_units.is_empty() {
|
2025-01-12 04:58:53 +01:00
|
|
|
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" {
|
2025-01-12 03:58:16 +01:00
|
|
|
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Systemd Units" }
|
|
|
|
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" {
|
|
|
|
@for unit in systemd_units {
|
|
|
|
li { (unit) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@if !pacman_hooks.is_empty() {
|
2025-01-12 04:58:53 +01:00
|
|
|
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" {
|
2025-01-12 03:58:16 +01:00
|
|
|
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Pacman Hooks" }
|
|
|
|
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" {
|
|
|
|
@for hook in pacman_hooks {
|
|
|
|
h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300" { (hook.0) }
|
|
|
|
|
|
|
|
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" {
|
|
|
|
(hook.1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@if !binaries.is_empty() {
|
2025-01-12 04:58:53 +01:00
|
|
|
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" {
|
2025-01-12 03:58:16 +01:00
|
|
|
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Binaries" }
|
|
|
|
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" {
|
|
|
|
@for binary in binaries {
|
|
|
|
li { (binary) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-12 04:58:53 +01:00
|
|
|
div class="space-y-2 p-4 bg-gray-50 dark:bg-gray-800 shadow-lg rounded-lg transition m-2 grow" {
|
2025-01-12 03:58:16 +01:00
|
|
|
h3 class="text-lg font-medium text-gray-800 dark:text-gray-100 underline" { "Package Files" }
|
|
|
|
ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1" {
|
|
|
|
@for file in pkg.file_list() {
|
|
|
|
li { (file) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2025-01-12 04:27:50 +01:00
|
|
|
// Install Script
|
2025-01-12 03:58:16 +01:00
|
|
|
@if let Some(install_script) = install_script {
|
|
|
|
div class="space-y-4 pt-6" {
|
2025-01-12 04:27:50 +01:00
|
|
|
h2 class="text-3xl font-semibold text-gray-700 dark:text-gray-300" { "Install Script" }
|
2025-01-12 03:58:16 +01:00
|
|
|
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" {
|
|
|
|
(install_script)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Some(render(content, pkg_name, ctx).await)
|
|
|
|
}
|
|
|
|
|
2025-01-15 22:46:25 +01:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2025-01-12 04:27:50 +01:00
|
|
|
#[get("/<repo>?<arch>")]
|
2025-01-13 11:39:00 +01:00
|
|
|
pub async fn repo_ui(
|
|
|
|
repo: &str,
|
|
|
|
ctx: RequestContext,
|
|
|
|
arch: Option<&str>,
|
|
|
|
config: &State<Config>,
|
|
|
|
) -> StringResponse {
|
2025-01-12 04:27:50 +01:00
|
|
|
let arch = arch.map(|x| Architecture::parse(x).unwrap_or(Architecture::any));
|
2025-01-12 03:58:16 +01:00
|
|
|
|
|
|
|
let repo = Repository::new(repo).unwrap();
|
|
|
|
let architectures: Vec<_> = repo.arch().into_iter().map(|x| x.to_string()).collect();
|
2025-01-12 04:27:50 +01:00
|
|
|
let packages = if let Some(arch) = arch.clone() {
|
|
|
|
repo.list_pkg_arch(arch)
|
|
|
|
} else {
|
|
|
|
repo.list_pkg()
|
|
|
|
};
|
2025-01-12 03:58:16 +01:00
|
|
|
|
|
|
|
let content = html! {
|
|
|
|
// Repository name and architectures
|
|
|
|
div class="flex flex-wrap items-center justify-center mb-6" {
|
2025-01-13 11:39:00 +01:00
|
|
|
h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100" {
|
2025-01-12 03:58:16 +01:00
|
|
|
(repo.name)
|
|
|
|
};
|
|
|
|
|
2025-01-13 11:39:00 +01:00
|
|
|
@if config.is_mirrored_repo(&repo.name) {
|
|
|
|
div class="inline-block px-3 py-1 text-sm font-medium text-white bg-blue-500 rounded-full ml-4" { "Mirrored" };
|
|
|
|
};
|
|
|
|
|
|
|
|
div class="flex gap-2 mt-2 md:mt-0 ml-4" {
|
2025-01-12 04:27:50 +01:00
|
|
|
@for a in architectures {
|
|
|
|
@if let Some(arch) = arch.as_ref() {
|
|
|
|
@if arch.to_string() == a {
|
2025-01-15 22:46:25 +01:00
|
|
|
(arch_card(&a, &repo.name, true))
|
2025-01-12 04:27:50 +01:00
|
|
|
} @else {
|
2025-01-15 22:46:25 +01:00
|
|
|
(arch_card(&a, &repo.name, false))
|
2025-01-12 04:27:50 +01:00
|
|
|
}
|
|
|
|
} @else {
|
2025-01-15 22:46:25 +01:00
|
|
|
(arch_card(&a, &repo.name, false))
|
2025-01-12 04:27:50 +01:00
|
|
|
}
|
2025-01-12 03:58:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Package list
|
|
|
|
ul class="space-y-4" {
|
|
|
|
@for pkg in packages {
|
2025-01-15 22:46:25 +01:00
|
|
|
(
|
|
|
|
Link(
|
|
|
|
&format!("/{}/{pkg}", repo.name),
|
|
|
|
// TODO : Implement transition class
|
|
|
|
Flex(
|
|
|
|
Padding(
|
|
|
|
Hover(
|
|
|
|
Background(Gray::_600, Nothing()),
|
|
|
|
Background(Gray::_700,
|
|
|
|
Rounded(
|
|
|
|
Shadow::medium(
|
|
|
|
Div().vanish()
|
|
|
|
.push(
|
|
|
|
html! { (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(html! { (Width(ScreenValue::fit, Text(&pkg).medium().color(&Gray::_100))) })
|
|
|
|
.push(html! {
|
|
|
|
(
|
|
|
|
Flex(
|
|
|
|
Padding(
|
|
|
|
Div().vanish()
|
|
|
|
.push(Span("✓")._2xl().bold().color(&Slate::_300))
|
|
|
|
.push(Span("Signed").sm().medium().color(&Slate::_300))
|
|
|
|
).right(ScreenValue::_4)
|
|
|
|
).items_center().gap(2).group()
|
|
|
|
)
|
|
|
|
})
|
|
|
|
)
|
|
|
|
).size(Size::Large)))).all(ScreenValue::_4)).items_center().gap(4)
|
|
|
|
).use_htmx()
|
|
|
|
)
|
2025-01-12 03:58:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
render(content, &repo.name, ctx).await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn build_info(key: String, value: String) -> PreEscaped<String> {
|
|
|
|
match key.as_str() {
|
|
|
|
"pkgname" => {}
|
|
|
|
"xdata" => {}
|
|
|
|
"arch" => {}
|
|
|
|
"pkbase" => {}
|
|
|
|
"pkgver" => {}
|
|
|
|
"pkgdesc" => {
|
|
|
|
return key_value("Description".to_string(), value);
|
|
|
|
}
|
|
|
|
"packager" => {
|
|
|
|
return key_value("Packager".to_string(), value);
|
|
|
|
}
|
|
|
|
"url" => {
|
|
|
|
return html! {
|
|
|
|
div class="flex" {
|
|
|
|
span class="font-bold w-32" { (format!("Website: ")) };
|
|
|
|
a class="ml-2 text-blue-400" href=(value) { (value) };
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
"builddate" => {
|
|
|
|
let date = chrono::DateTime::from_timestamp(value.parse().unwrap(), 0).unwrap();
|
|
|
|
return key_value(
|
|
|
|
"Build at".to_string(),
|
|
|
|
date.format("%d.%m.%Y %H:%M").to_string(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
"depend" => {
|
2025-01-13 11:39:00 +01:00
|
|
|
return pkg_list_info("Depends on", &value);
|
2025-01-12 03:58:16 +01:00
|
|
|
}
|
|
|
|
"makedepend" => {
|
2025-01-13 11:39:00 +01:00
|
|
|
return pkg_list_info("Build Dependencies", &value);
|
2025-01-12 03:58:16 +01:00
|
|
|
}
|
|
|
|
"license" => {
|
|
|
|
return key_value("License".to_string(), value);
|
|
|
|
}
|
|
|
|
"size" => {
|
|
|
|
return key_value(
|
|
|
|
"Size".to_string(),
|
|
|
|
bytesize::to_string(value.parse().unwrap(), false),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
log::warn!("Unhandled PKGINFO {key} = {value}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
html! {}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn key_value(key: String, value: String) -> PreEscaped<String> {
|
2025-01-15 22:46:25 +01:00
|
|
|
Flex(
|
|
|
|
Div()
|
|
|
|
.vanish()
|
|
|
|
.push(Width(ScreenValue::_32, Span(&format!("{key}: ")).bold()))
|
|
|
|
.push(Margin(Span(&value)).left(ScreenValue::_2)),
|
|
|
|
)
|
|
|
|
.items_center()
|
|
|
|
.group()
|
|
|
|
.render()
|
2025-01-12 03:58:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn take_out<T>(v: &mut Vec<T>, f: impl Fn(&T) -> bool) -> (&mut Vec<T>, Option<T>) {
|
|
|
|
let mut index = -1;
|
|
|
|
|
|
|
|
for (i, e) in v.iter().enumerate() {
|
|
|
|
if f(e) {
|
|
|
|
index = i as i64;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if index != -1 {
|
|
|
|
let e = v.remove(index as usize);
|
|
|
|
return (v, Some(e));
|
|
|
|
}
|
|
|
|
|
|
|
|
(v, None)
|
|
|
|
}
|
2025-01-13 11:39:00 +01:00
|
|
|
|
|
|
|
pub fn find_pkg_url(pkg: &str) -> Option<String> {
|
|
|
|
if let Some(pkg) = find_package_by_name(pkg) {
|
|
|
|
return Some(format!("/{}/{}", pkg.repo, pkg.name));
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pkg_list_info(key: &str, value: &str) -> PreEscaped<String> {
|
|
|
|
let pkgs = value.split_whitespace().map(|pkg| {
|
|
|
|
if let Some(pkg_url) = find_pkg_url(pkg) {
|
|
|
|
html! {
|
|
|
|
a href=(pkg_url) class="ml-2 text-blue-400" { (pkg) };
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
html! {
|
|
|
|
span class="ml-2" { (pkg) };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
html! {
|
2025-01-15 22:46:25 +01:00
|
|
|
(Flex(html! {
|
2025-01-13 11:39:00 +01:00
|
|
|
span class="font-bold w-32" { (format!("{key}: ")) };
|
|
|
|
div class="flex flex-wrap" {
|
|
|
|
@for pkg in pkgs {
|
|
|
|
(pkg)
|
|
|
|
};
|
|
|
|
};
|
2025-01-15 22:46:25 +01:00
|
|
|
}).items_center().group())
|
2025-01-13 11:39:00 +01:00
|
|
|
}
|
|
|
|
}
|