refactor: remove dead code (#3337)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-09 01:41:26 +02:00
1 parent a19002652b
commit 93d1f8af46
1527 files changed
+21170 -73846

No files matched your search

-1
View File
@@ -11,7 +11,6 @@ anyhow = "1.0.104"
axum = { version = "0.8.9", features = ["macros"] }
base64 = "0.23.1"
chrono = { version = "0.4", default-features = false, features = ["serde"] }
cookie = "0.18.2"
fluxer_common = { path = "../fluxer_common" }
hmac = "0.13.0"
maud = { version = "0.27.0", features = ["axum"] }
+1 -10
View File
@@ -3,7 +3,7 @@
use crate::api::generated::snowflake;
use super::client::{AdminApiClient, ApiResult};
use super::types::{ActiveJobsResponse, CancelJobResponse, GetJobResponse, ListJobsResponse};
use super::types::{CancelJobResponse, GetJobResponse, ListJobsResponse};
pub struct ListJobsParams {
pub limit: u32,
@@ -59,15 +59,6 @@ impl AdminApiClient {
)
.await
}
pub async fn list_active_jobs(&self) -> ApiResult<ActiveJobsResponse> {
let response = self
.generated()
.list_admin_active_jobs()
.await
.map_err(|e| self.generated_error(e))?;
self.generated_value(response.into_inner())
}
}
fn cursor_field<'a>(cursor: Option<&'a serde_json::Value>, field: &str) -> &'a str {
-5
View File
@@ -21,8 +21,3 @@ pub struct GetJobResponse {
pub struct CancelJobResponse {
pub cancelled: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ActiveJobsResponse {
pub jobs: Vec<serde_json::Value>,
}
+1 -14
View File
@@ -2,13 +2,12 @@
use crate::{
api::types::AdminUser,
middleware::flash::{self, FlashData},
middleware::flash,
session::{self, Session},
state::AppState,
};
use axum::{
extract::{Request, State},
http::StatusCode,
middleware::Next,
response::{IntoResponse, Redirect, Response},
};
@@ -65,10 +64,6 @@ pub async fn require_auth(
response
}
pub fn get_flash(request: &Request) -> Option<FlashData> {
request.extensions().get::<FlashData>().cloned()
}
fn admin_cookie_path(config: &crate::config::AdminConfig) -> &str {
if config.base_path.is_empty() {
"/"
@@ -159,11 +154,3 @@ async fn fetch_admin_user(
Err(_) => AdminFetchResult::None,
}
}
pub fn get_auth_context(request: &Request) -> Option<&AuthContext> {
request.extensions().get::<AuthContext>()
}
pub fn require_auth_context(request: &Request) -> Result<&AuthContext, Box<Response>> {
get_auth_context(request).ok_or_else(|| Box::new(StatusCode::UNAUTHORIZED.into_response()))
}
@@ -1,122 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use axum::{
http::StatusCode,
response::{Html, IntoResponse, Response},
};
use maud::{DOCTYPE, html};
pub struct AppError {
pub status: StatusCode,
pub message: String,
pub detail: Option<String>,
pub home_url: String,
}
impl AppError {
pub fn not_found(message: &str, base_path: &str) -> Self {
Self {
status: StatusCode::NOT_FOUND,
message: message.to_owned(),
detail: None,
home_url: if base_path.is_empty() {
"/".to_owned()
} else {
base_path.to_owned()
},
}
}
pub fn internal(message: &str) -> Self {
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
message: message.to_owned(),
detail: None,
home_url: "/login".to_owned(),
}
}
pub fn forbidden(message: &str) -> Self {
Self {
status: StatusCode::FORBIDDEN,
message: message.to_owned(),
detail: None,
home_url: "/login".to_owned(),
}
}
pub fn with_detail(mut self, detail: String) -> Self {
self.detail = Some(detail);
self
}
pub fn with_base_path(mut self, base_path: &str) -> Self {
self.home_url = if base_path.is_empty() {
"/".to_owned()
} else {
base_path.to_owned()
};
self
}
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let status_code = self.status.as_u16();
let status_text = self.status.canonical_reason().unwrap_or("Error");
let home_url = &self.home_url;
let (border, bg, text_color) = if status_code >= 500 {
("border-red-300", "bg-red-50", "text-red-800")
} else if status_code == 403 {
("border-yellow-300", "bg-yellow-50", "text-yellow-800")
} else {
("border-neutral-300", "bg-neutral-50", "text-neutral-800")
};
let markup = html! {
(DOCTYPE)
html lang="en" {
head {
meta charset="UTF-8";
meta name="viewport" content="width=device-width, initial-scale=1.0";
title { (status_code) " " (status_text) " ~ Fluxer Admin" }
}
body class="min-h-screen bg-neutral-50 flex items-center justify-center" {
div class="mx-auto max-w-lg px-4 py-16 text-center" {
h1 class="text-6xl font-bold text-neutral-300 mb-4" {
(status_code)
}
h2 class="text-xl font-semibold text-neutral-700 mb-2" {
(status_text)
}
div class={"rounded-lg border px-4 py-3 text-sm mb-6 " (border) " " (bg) " " (text_color)} {
div { (self.message) }
@if let Some(ref detail) = self.detail {
div class="mt-2 break-all rounded border border-current/20 \
bg-white/60 px-3 py-2 text-xs" {
(detail)
}
}
}
a href=(home_url)
class="text-blue-600 hover:text-blue-800 hover:underline text-sm" {
"Go to admin"
}
}
}
}
};
(self.status, Html(markup.into_string())).into_response()
}
}
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
tracing::error!(?err, "internal server error");
Self {
status: StatusCode::INTERNAL_SERVER_ERROR,
message: "An internal error occurred.".to_owned(),
detail: None,
home_url: "/login".to_owned(),
}
}
}
-7
View File
@@ -18,13 +18,6 @@ pub fn is_htmx_request(headers: &HeaderMap) -> bool {
.is_some_and(|value| value == "true")
}
pub fn htmx_current_url(headers: &HeaderMap) -> Option<String> {
headers
.get("HX-Current-URL")
.and_then(|value| value.to_str().ok())
.map(|s| s.to_owned())
}
pub fn htmx_target(headers: &HeaderMap) -> Option<String> {
headers
.get("HX-Target")
-1
View File
@@ -3,7 +3,6 @@
pub mod account_identity;
pub mod auth;
pub mod csrf;
pub mod error_handler;
pub mod flash;
pub mod htmx;
pub mod self_hosted;
-6
View File
@@ -9,17 +9,11 @@ pub const ADMIN_OAUTH_SCOPE: &str = "identify email";
#[derive(Debug, Deserialize)]
pub struct TokenResponse {
pub access_token: String,
pub token_type: String,
}
#[derive(Debug, Deserialize)]
pub struct UserInfo {
pub id: String,
pub username: String,
pub discriminator: String,
pub avatar: Option<String>,
pub email: Option<String>,
pub global_name: Option<String>,
}
pub fn authorize_url(config: &AdminConfig, state: &str) -> String {
+1 -17
View File
@@ -12,7 +12,7 @@ use crate::{
templates,
};
use axum::{
Form, Json, Router,
Form, Router,
extract::{FromRequest, Path, Query, Request, State},
http::HeaderMap,
response::{Html, IntoResponse, Response},
@@ -46,7 +46,6 @@ struct JobActionForm {
pub fn router() -> Router<AppState> {
Router::new()
.route("/jobs", get(jobs_list))
.route("/jobs/active.json", get(jobs_active_json))
.route("/jobs/{job_id}", get(job_detail).post(job_detail_post))
}
@@ -128,21 +127,6 @@ async fn jobs_list(
Html(markup.into_string()).into_response()
}
async fn jobs_active_json(
State(state): State<AppState>,
auth: axum::Extension<AuthContext>,
) -> Response {
let config = state.config();
let client = AdminApiClient::new(state.http_client(), config, &auth.0.session);
match client.list_active_jobs().await {
Ok(data) => Json(serde_json::json!(data)).into_response(),
Err(error) => {
tracing::warn!(%error, "admin API request failed: list active jobs");
Json(serde_json::json!({ "jobs": [] })).into_response()
}
}
}
async fn job_detail(
State(state): State<AppState>,
headers: HeaderMap,
-10
View File
@@ -64,7 +64,6 @@ pub fn router() -> Router<AppState> {
"/limit-config",
get(limit_config_page).post(system_actions::limit_config_post),
)
.route("/strange-place", get(strange_place_page))
}
async fn gateway_page(
@@ -263,15 +262,6 @@ async fn limit_config_page(
Html(markup.into_string()).into_response()
}
async fn strange_place_page(
State(state): State<AppState>,
auth: axum::Extension<AuthContext>,
) -> Response {
let config = state.config();
let markup = templates::pages::strange_place::strange_place_page(config, &auth.0);
Html(markup.into_string()).into_response()
}
fn nonempty(s: &str) -> Option<String> {
if s.is_empty() {
None
-29
View File
@@ -77,33 +77,6 @@ main:focus {
outline-offset: 2px;
}
.flash-success {
border: 1px solid #86efac;
background-color: #f0fdf4;
color: #166534;
border-radius: 0.5rem;
padding: 0.75rem 1rem;
font-size: 0.875rem;
}
.flash-error {
border: 1px solid #fca5a5;
background-color: #fef2f2;
color: #991b1b;
border-radius: 0.5rem;
padding: 0.75rem 1rem;
font-size: 0.875rem;
}
.flash-info {
border: 1px solid #93c5fd;
background-color: #eff6ff;
color: #1e40af;
border-radius: 0.5rem;
padding: 0.75rem 1rem;
font-size: 0.875rem;
}
.drawer-panel {
inset: unset;
margin: 0;
@@ -115,8 +88,6 @@ main:focus {
}
.drawer-panel[data-drawer-side="right"] { right: 0; top: 0; }
.drawer-panel[data-drawer-side="left"] { left: 0; top: 0; }
.drawer-panel[data-drawer-side="bottom"] { bottom: 0; left: 0; right: 0; }
.drawer-panel[data-open],
.drawer-panel:popover-open {
@@ -5,16 +5,12 @@ use maud::{Markup, html};
#[derive(Clone, Copy)]
pub enum AlertVariant {
Info,
Warning,
Error,
Success,
}
pub fn alert(variant: AlertVariant, title: Option<&str>, content: Markup) -> Markup {
let variant_classes = match variant {
AlertVariant::Info => "bg-blue-50 border-blue-200 text-blue-700",
AlertVariant::Warning => "bg-neutral-50 border-neutral-200 text-neutral-700",
AlertVariant::Error => "bg-red-50 border-red-200 text-red-700",
AlertVariant::Success => "bg-green-50 border-green-200 text-green-700",
};
html! {
@@ -30,15 +26,3 @@ pub fn alert(variant: AlertVariant, title: Option<&str>, content: Markup) -> Mar
pub fn alert_info(content: Markup) -> Markup {
alert(AlertVariant::Info, None, content)
}
pub fn alert_error(title: &str, content: Markup) -> Markup {
alert(AlertVariant::Error, Some(title), content)
}
pub fn alert_success(title: &str, content: Markup) -> Markup {
alert(AlertVariant::Success, Some(title), content)
}
pub fn alert_warning(title: &str, content: Markup) -> Markup {
alert(AlertVariant::Warning, Some(title), content)
}
@@ -12,7 +12,3 @@ pub fn auto_refresh(enabled: bool, interval_ms: u32) -> Markup {
script defer { (PreEscaped(script)) }
}
}
pub fn auto_refresh_default(enabled: bool) -> Markup {
auto_refresh(enabled, 3000)
}
@@ -29,11 +29,3 @@ pub fn badge(label: &str, variant: BadgeVariant) -> Markup {
}
}
}
pub fn flag_badge(label: &str, active: bool) -> Markup {
if active {
badge(label, BadgeVariant::Success)
} else {
badge(label, BadgeVariant::Default)
}
}
@@ -44,10 +44,6 @@ pub fn data_field_mono(label: &str, value: &str) -> Markup {
data_field(label, data_field_value(value, DATA_FIELD_MONO_CLASS))
}
pub fn data_field_link(label: &str, href: &str, display: &str) -> Markup {
data_field(label, data_field_link_value(href, display, "break-words"))
}
pub fn data_field_link_mono(label: &str, href: &str, display: &str) -> Markup {
data_field(label, data_field_link_value(href, display, "break-all"))
}
@@ -66,20 +62,3 @@ pub fn data_grid(cols: u8, content: Markup) -> Markup {
div class=(col_class) { (content) }
}
}
pub fn metadata_row(label: &str, value: Markup) -> Markup {
html! {
div class="flex flex-wrap gap-x-2 gap-y-0.5" {
span class="flex-shrink-0 text-neutral-500 text-sm" {
(label) ":"
}
span class="min-w-0 break-words text-neutral-900 text-sm" {
(value)
}
}
}
}
pub fn metadata_row_text(label: &str, value: &str) -> Markup {
metadata_row(label, html! { (value) })
}
@@ -7,22 +7,16 @@ use super::icons::{close_icon, spinner_icon};
#[derive(Clone, Copy)]
pub enum DrawerSide {
Right,
Left,
Bottom,
}
#[derive(Clone, Copy)]
pub enum DrawerWidth {
Sm,
Md,
Lg,
Xl,
}
fn width_class(width: DrawerWidth) -> &'static str {
match width {
DrawerWidth::Sm => "sm:max-w-sm",
DrawerWidth::Md => "sm:max-w-md",
DrawerWidth::Lg => "sm:max-w-lg",
DrawerWidth::Xl => "sm:max-w-xl",
}
@@ -31,24 +25,18 @@ fn width_class(width: DrawerWidth) -> &'static str {
fn side_class(side: DrawerSide) -> &'static str {
match side {
DrawerSide::Right => "right-0 top-0 h-[100dvh] w-full sm:w-[92vw]",
DrawerSide::Left => "left-0 top-0 h-[100dvh] w-full sm:w-[92vw]",
DrawerSide::Bottom => "bottom-0 left-0 right-0 max-h-[92dvh] w-full",
}
}
fn side_off_class(side: DrawerSide) -> &'static str {
match side {
DrawerSide::Right => "translate-x-full",
DrawerSide::Left => "-translate-x-full",
DrawerSide::Bottom => "translate-y-full",
}
}
fn side_name(side: DrawerSide) -> &'static str {
match side {
DrawerSide::Right => "right",
DrawerSide::Left => "left",
DrawerSide::Bottom => "bottom",
}
}
@@ -68,10 +56,7 @@ pub fn drawer(
"drawer-panel pointer-events-auto fixed z-50 flex flex-col \
bg-white shadow-2xl {} {} {}",
side_class(side),
match side {
DrawerSide::Bottom => "",
_ => width_class(width),
},
width_class(width),
side_off_class(side),
);
html! {
@@ -150,16 +135,6 @@ pub fn drawer_loading_state() -> Markup {
}
}
pub fn drawer_error_state(message: &str) -> Markup {
html! {
div role="alert"
class="rounded-lg border border-red-200 bg-red-50 p-4 \
text-red-800 text-sm" {
(message)
}
}
}
pub fn drawer_controller_script() -> Markup {
html! {
script defer { (PreEscaped(DRAWER_CONTROLLER_SCRIPT)) }
@@ -2,25 +2,6 @@
use maud::{Markup, html};
#[derive(Clone, Copy)]
pub enum EmptyStateVariant {
Empty,
Loading,
Error,
}
pub fn empty_state(variant: EmptyStateVariant, content: Markup) -> Markup {
let classes = match variant {
EmptyStateVariant::Empty | EmptyStateVariant::Loading => {
"text-neutral-500 text-center py-8"
}
EmptyStateVariant::Error => "text-red-600 text-center py-8",
};
html! {
div class=(classes) { (content) }
}
}
pub fn empty_state_full(icon: Option<Markup>, title: &str, description: Option<&str>) -> Markup {
html! {
div class="flex flex-col items-center justify-center py-12 text-center" {
@@ -62,11 +43,3 @@ pub fn not_found_state(
}
}
}
pub fn empty_state_text(message: &str) -> Markup {
html! {
div class="text-neutral-500 text-center py-8" {
(message)
}
}
}
@@ -9,14 +9,3 @@ pub fn error_alert(error: &str) -> Markup {
}
}
}
pub fn error_card(title: &str, message: &str) -> Markup {
html! {
div class="rounded-lg border border-neutral-200 bg-white p-4 shadow-sm sm:p-6" {
div class="flex flex-col gap-4" {
h3 class="text-base font-semibold text-gray-900" { (title) }
p class="text-sm text-neutral-500" { (message) }
}
}
}
}
@@ -151,10 +151,6 @@ pub fn danger_button(label: &str) -> Markup {
button_markup(label, "submit", DANGER_BUTTON_CLASS)
}
pub fn secondary_button(label: &str) -> Markup {
button_markup(label, "button", SECONDARY_BUTTON_CLASS)
}
pub fn form_field_group(
label: &str,
name: &str,
@@ -23,39 +23,6 @@ pub fn paperclip_icon(color: &str) -> Markup {
}
}
pub fn checkmark_icon(color: &str) -> Markup {
html! {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"
class={"inline-block h-4 w-4 " (color)}
{
(PreEscaped(concat!(
r#"<rect width="256" height="256" fill="none"/>"#,
r#"<polyline points="40 144 96 200 224 72" fill="none""#,
r#" stroke="currentColor" stroke-linecap="round""#,
r#" stroke-linejoin="round" stroke-width="24"/>"#,
)))
}
}
}
pub fn x_icon(color: &str) -> Markup {
html! {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"
class={"inline-block h-4 w-4 " (color)}
{
(PreEscaped(concat!(
r#"<rect width="256" height="256" fill="none"/>"#,
r#"<line x1="200" y1="56" x2="56" y2="200" fill="none""#,
r#" stroke="currentColor" stroke-linecap="round""#,
r#" stroke-linejoin="round" stroke-width="24"/>"#,
r#"<line x1="200" y1="200" x2="56" y2="56" fill="none""#,
r#" stroke="currentColor" stroke-linecap="round""#,
r#" stroke-linejoin="round" stroke-width="24"/>"#,
)))
}
}
}
pub fn close_icon() -> Markup {
html! {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
@@ -82,63 +49,3 @@ pub fn spinner_icon() -> Markup {
}
}
}
pub fn search_icon() -> Markup {
html! {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="h-4 w-4" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true"
{
(PreEscaped(concat!(
r#"<circle cx="11" cy="11" r="8"/>"#,
r#"<line x1="21" y1="21" x2="16.65" y2="16.65"/>"#,
)))
}
}
}
pub fn chevron_right_icon() -> Markup {
html! {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="h-4 w-4" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true"
{
(PreEscaped(r#"<polyline points="9 18 15 12 9 6"/>"#))
}
}
}
pub fn external_link_icon() -> Markup {
html! {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="h-3.5 w-3.5" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true"
{
(PreEscaped(concat!(
r#"<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8"#,
r#"a2 2 0 0 1 2-2h6"/>"#,
r#"<polyline points="15 3 21 3 21 9"/>"#,
r#"<line x1="10" y1="14" x2="21" y2="3"/>"#,
)))
}
}
}
pub fn copy_icon() -> Markup {
html! {
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
class="h-3.5 w-3.5" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true"
{
(PreEscaped(concat!(
r#"<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>"#,
r#"<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9"#,
r#"a2 2 0 0 1 2 2v1"/>"#,
)))
}
}
}
@@ -21,10 +21,8 @@ pub mod report_category;
pub mod report_webhook;
pub mod resource_link;
pub mod section_card;
pub mod stack;
pub mod table;
pub mod tooltip;
pub mod typography;
pub mod user_display;
pub mod user_profile_badges;
pub mod voice;
@@ -2,21 +2,6 @@
use maud::{Markup, PreEscaped, html};
pub fn page_container(spacing: &str, content: Markup) -> Markup {
let class = match spacing {
"sm" => "space-y-4",
"lg" => "space-y-8",
_ => "space-y-6",
};
html! {
div class=(class) { (content) }
}
}
pub fn page_container_md(content: Markup) -> Markup {
page_container("md", content)
}
pub fn page_header(title: &str, description: Option<&str>) -> Markup {
page_header_full(title, description, None, None, None, maud::html! {})
}
@@ -122,25 +107,3 @@ pub fn detail_row(label: &str, value: Markup) -> Markup {
}
}
}
pub fn tabs(tabs: &[(&str, &str, bool)], base_url: &str) -> Markup {
html! {
div class="border-b border-neutral-200 mb-6" {
nav class="-mb-px flex gap-6" aria-label="Tabs" {
@for (id, label, active) in tabs {
@let classes = if *active {
"border-brand-primary text-brand-primary whitespace-nowrap border-b-2 \
pb-3 pt-1 text-sm font-medium"
} else {
"border-transparent text-neutral-500 hover:border-neutral-300 \
hover:text-neutral-700 whitespace-nowrap border-b-2 pb-3 pt-1 \
text-sm font-medium"
};
a href={(base_url) "?tab=" (id)} class=(classes) {
(label)
}
}
}
}
}
}
@@ -10,8 +10,6 @@ pub enum ResourceType {
const RESOURCE_LINK_CLASS: &str = "text-neutral-900 underline decoration-neutral-300 \
hover:text-neutral-600 hover:decoration-neutral-500 text-sm";
const NAV_LINK_CLASS: &str = "label rounded-lg border border-neutral-300 bg-white \
px-3 py-2 text-neutral-700 transition-colors hover:bg-neutral-50";
impl ResourceType {
fn path_segment(self) -> &'static str {
@@ -20,20 +18,6 @@ impl ResourceType {
ResourceType::Guild => "guilds",
}
}
fn peek_drawer_id(self) -> &'static str {
match self {
ResourceType::User => "user-peek",
ResourceType::Guild => "guild-peek",
}
}
fn label(self) -> &'static str {
match self {
ResourceType::User => "User",
ResourceType::Guild => "Guild",
}
}
}
pub fn resource_link(
@@ -52,40 +36,3 @@ pub fn resource_link(
a href=(href) class=(RESOURCE_LINK_CLASS) { (display) }
}
}
pub fn resource_link_peek(
base_path: &str,
resource_type: ResourceType,
resource_id: &str,
display: Markup,
peek_title: Option<&str>,
) -> Markup {
let href = format!(
"{}/{}/{}",
base_path,
resource_type.path_segment(),
resource_id,
);
let fragment_href = format!("{}/fragment", href);
let title = peek_title
.map(|t| t.to_owned())
.unwrap_or_else(|| format!("{} {}", resource_type.label(), resource_id));
html! {
a href=(href)
class=(RESOURCE_LINK_CLASS)
data-drawer-open=(resource_type.peek_drawer_id())
data-drawer-href=(fragment_href)
hx-get=(fragment_href)
hx-target={"#" (resource_type.peek_drawer_id()) "-body"}
hx-swap="innerHTML"
data-drawer-title=(title) {
(display)
}
}
}
pub fn nav_link(href: &str, content: Markup) -> Markup {
html! {
a href=(href) class=(NAV_LINK_CLASS) { (content) }
}
}
@@ -1,53 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use maud::{Markup, html};
#[derive(Clone, Copy)]
pub enum StackGap {
Sm,
Md,
Lg,
}
#[derive(Clone, Copy)]
pub enum StackAlign {
Start,
Center,
End,
Stretch,
}
fn gap_class(gap: StackGap) -> &'static str {
match gap {
StackGap::Sm => "gap-2",
StackGap::Md => "gap-4",
StackGap::Lg => "gap-6",
}
}
fn align_class(align: StackAlign) -> &'static str {
match align {
StackAlign::Start => "items-start",
StackAlign::Center => "items-center",
StackAlign::End => "items-end",
StackAlign::Stretch => "items-stretch",
}
}
pub fn vstack(gap: StackGap, align: StackAlign, content: Markup) -> Markup {
let classes = format!("flex flex-col {} {}", gap_class(gap), align_class(align),);
html! {
div class=(classes) { (content) }
}
}
pub fn hstack(gap: StackGap, align: StackAlign, content: Markup) -> Markup {
let classes = format!("flex flex-row {} {}", gap_class(gap), align_class(align),);
html! {
div class=(classes) { (content) }
}
}
pub fn stack(content: Markup) -> Markup {
vstack(StackGap::Md, StackAlign::Stretch, content)
}
@@ -45,10 +45,6 @@ pub fn table_cell(muted: bool, content: Markup) -> Markup {
html! { td class=(table_cell_class(muted)) { (content) } }
}
pub fn table_cell_span(muted: bool, col_span: u32, content: Markup) -> Markup {
html! { td class=(table_cell_class(muted)) colspan=(col_span) { (content) } }
}
fn table_cell_class(muted: bool) -> &'static str {
if muted {
"px-3 py-3 text-sm sm:px-6 sm:py-4 text-neutral-600"
@@ -1,155 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use maud::{Markup, html};
#[derive(Clone, Copy)]
pub enum HeadingLevel {
H1,
H2,
H3,
H4,
H5,
H6,
}
#[derive(Clone, Copy)]
pub enum HeadingSize {
Xs,
Sm,
Base,
Lg,
Xl,
Xl2,
Xl3,
Xl4,
}
fn default_heading_classes(level: HeadingLevel) -> &'static str {
match level {
HeadingLevel::H1 => "text-3xl font-bold",
HeadingLevel::H2 => "text-2xl font-semibold",
HeadingLevel::H3 => "text-xl font-semibold",
HeadingLevel::H4 => "text-lg font-semibold",
HeadingLevel::H5 => "text-base font-semibold",
HeadingLevel::H6 => "text-sm font-semibold",
}
}
fn custom_size_class(size: HeadingSize) -> &'static str {
match size {
HeadingSize::Xs => "text-xs",
HeadingSize::Sm => "text-sm",
HeadingSize::Base => "text-base",
HeadingSize::Lg => "text-lg",
HeadingSize::Xl => "text-xl",
HeadingSize::Xl2 => "text-2xl",
HeadingSize::Xl3 => "text-3xl",
HeadingSize::Xl4 => "text-4xl",
}
}
pub fn heading(level: HeadingLevel, size: Option<HeadingSize>, content: Markup) -> Markup {
let size_classes = match size {
Some(s) => custom_size_class(s),
None => default_heading_classes(level),
};
let classes = format!("text-gray-900 tracking-tight {size_classes}");
match level {
HeadingLevel::H1 => html! { h1 class=(classes) { (content) } },
HeadingLevel::H2 => html! { h2 class=(classes) { (content) } },
HeadingLevel::H3 => html! { h3 class=(classes) { (content) } },
HeadingLevel::H4 => html! { h4 class=(classes) { (content) } },
HeadingLevel::H5 => html! { h5 class=(classes) { (content) } },
HeadingLevel::H6 => html! { h6 class=(classes) { (content) } },
}
}
#[derive(Clone, Copy)]
pub enum TextSize {
Xs,
Sm,
Base,
Lg,
}
#[derive(Clone, Copy)]
pub enum TextColor {
Default,
Muted,
Primary,
Danger,
Success,
}
pub fn text(size: TextSize, color: TextColor, content: Markup) -> Markup {
let size_class = match size {
TextSize::Xs => "text-xs",
TextSize::Sm => "text-sm",
TextSize::Base => "text-base",
TextSize::Lg => "text-lg",
};
let color_class = match color {
TextColor::Default => "text-gray-900",
TextColor::Muted => "text-neutral-500",
TextColor::Primary => "text-brand-primary",
TextColor::Danger => "text-red-600",
TextColor::Success => "text-green-600",
};
html! {
p class={(size_class) " font-normal " (color_class)} { (content) }
}
}
pub fn caption(content: Markup) -> Markup {
html! {
p class="text-xs text-gray-500" { (content) }
}
}
#[derive(Clone, Copy)]
pub enum CaptionVariant {
Default,
Error,
Success,
}
pub fn caption_variant(variant: CaptionVariant, content: Markup) -> Markup {
let color = match variant {
CaptionVariant::Default => "text-gray-500",
CaptionVariant::Error => "text-red-600",
CaptionVariant::Success => "text-green-600",
};
html! {
p class={"text-xs " (color)} { (content) }
}
}
pub fn label_text(for_id: Option<&str>, required: bool, content: Markup) -> Markup {
let classes = "block text-xs font-semibold uppercase tracking-wide text-neutral-500";
html! {
label for=[for_id] class=(classes) {
(content)
@if required {
span class="ml-1 text-red-600" { "*" }
}
}
}
}
pub fn section_heading(title: &str, actions: Option<Markup>) -> Markup {
match actions {
Some(action_content) => html! {
div class="mb-4 flex items-center justify-between" {
h2 class="font-semibold text-gray-900 text-xl" { (title) }
div class="flex items-center gap-2" { (action_content) }
}
},
None => html! {
h2 class="mb-4 font-semibold text-gray-900 text-xl" { (title) }
},
}
}
pub fn inline_text(content: &str) -> Markup {
html! { span { (content) } }
}
-6
View File
@@ -126,9 +126,3 @@ fn render_flash(flash: &crate::api::types::FlashMessage) -> Markup {
}
}
}
pub fn flash_container() -> Markup {
html! {
div id="flash-container" class="mb-4 sm:mb-6 empty:hidden" {}
}
}
@@ -585,25 +585,6 @@ window.__adminCopyToClipboard = function (text, btn, successLabel) {
};
"#;
pub const ARCHIVE_POLL_SCRIPT: &str = r#"
(function () {
var rows = document.querySelectorAll('tr[data-archive-id]');
var hasPending = false;
for (var i = 0; i < rows.length; i++) {
var status = rows[i].querySelector('.archive-status');
if (status && status.textContent.indexOf('Completed') === -1 && status.textContent.indexOf('Failed') === -1) {
hasPending = true;
break;
}
}
if (hasPending) {
setTimeout(function () {
window.location.reload();
}, 5000);
}
})();
"#;
pub const SH_LINK_REWRITE_SCRIPT: &str = r#"
(function () {
if (window.location.search.indexOf('sh=1') === -1) return;
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::{acl, config::AdminConfig};
use maud::{Markup, PreEscaped, html};
use super::layout_scripts::SIDEBAR_ACTIVE_SCROLL_SCRIPT;
pub use super::layout_sidebar_nav::{NAV_SECTIONS, NavItem, NavSection};
use super::layout_sidebar_nav::NAV_SECTIONS;
pub fn render_sidebar(
config: &AdminConfig,
@@ -21,7 +21,6 @@ use maud::{Markup, html};
pub struct GuildsListParams {
pub q: String,
pub ids: String,
pub requested_ids: Vec<String>,
pub limit: u32,
pub page: u32,
@@ -38,7 +37,6 @@ impl GuildsListParams {
Self {
q: q.unwrap_or_default().trim().to_owned(),
requested_ids: parse_comma_separated(&ids),
ids: ids.trim().to_owned(),
limit: match limit.unwrap_or(50) {
25 => 25,
100 => 100,
-1
View File
@@ -29,7 +29,6 @@ pub mod profile_substring_bans;
pub mod report_detail;
pub mod reports_list;
pub mod search_index;
pub mod strange_place;
pub mod system_dm;
pub mod url_domain_bans;
pub mod user_detail;
@@ -1,27 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::{config::AdminConfig, middleware::auth::AuthContext, templates::layout::admin_layout};
use maud::{Markup, html};
pub fn strange_place_page(config: &AdminConfig, auth: &AuthContext) -> Markup {
let content = html! {
div class="mx-auto max-w-2xl" {
div class="rounded-lg border border-neutral-200 bg-white p-8 shadow-sm" {
div class="space-y-4 text-center" {
div class="mx-auto flex h-16 w-16 items-center justify-center \
rounded-full bg-neutral-100" {
span class="text-lg text-neutral-400" { "?" }
}
h2 class="text-lg font-semibold text-neutral-900" {
"You've reached a strange place"
}
p class="text-sm text-neutral-600" {
"You don't have access to any admin features. "
"Contact an administrator to request access."
}
}
}
}
};
admin_layout(config, auth, "Strange Place", "", None, content)
}
@@ -33,26 +33,6 @@ pub const USER_TABS: &[(&str, &str)] = &[
("audit_logs", "Audit Logs"),
];
pub fn user_detail_page(
config: &AdminConfig,
auth: &AuthContext,
user: Option<&AdminUser>,
user_id: &str,
premium_badge_name: Option<&str>,
is_htmx: bool,
) -> Markup {
user_detail_with_tab(
config,
auth,
user,
user_id,
"overview",
None,
premium_badge_name,
is_htmx,
)
}
#[allow(clippy::too_many_arguments)]
pub fn user_detail_with_tab(
config: &AdminConfig,
@@ -15,7 +15,6 @@ pub mod moderation;
pub mod overview;
pub mod relationships;
pub mod reports;
pub mod settings;
pub(super) fn resolved_user_display(user: &AdminResolvedUser) -> String {
let tag = user_tag(
@@ -1,110 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::{
api::types::AdminUser,
templates::components::page_container::{card_with_header, detail_row},
utils::{
bigint::{format_discriminator, has_flag, list_flags},
user_tag::user_tag,
},
};
use maud::{Markup, html};
pub fn settings_tab(user: &AdminUser) -> Markup {
html! {
div class="space-y-6" {
(card_with_header("Profile Settings", html! {
dl class="divide-y divide-neutral-100" {
(detail_row("Username", html! {
(user_tag(&user.username, &format_discriminator(&user.discriminator), user.bot))
}))
(detail_row("Display Name", html! {
@if let Some(ref name) = user.global_name {
(name)
} @else {
span class="text-neutral-400" { "Not set" }
}
}))
(detail_row("Email", html! {
@if let Some(ref email) = user.email {
(email)
} @else {
span class="text-neutral-400" { "Hidden" }
}
}))
(detail_row("Avatar", html! {
@if let Some(ref hash) = user.avatar {
span class="text-xs" { (hash) }
} @else {
span class="text-neutral-400" { "Default" }
}
}))
(detail_row("Bot", html! {
@if user.bot { "Yes" } @else { "No" }
}))
(detail_row("System", html! {
@if user.system { "Yes" } @else { "No" }
}))
}
}))
(card_with_header("Flags", html! {
dl class="divide-y divide-neutral-100" {
(detail_row("User Flags (raw)", html! {
span class="text-xs" { (user.flags) }
}))
(detail_row("Premium Flags (raw)", html! {
span class="text-xs" { (user.premium_flags) }
}))
(detail_row("Active flag bits", html! {
@let bits = list_flags(user.flags);
@if bits.is_empty() {
span class="text-neutral-400" { "None" }
} @else {
div class="flex flex-wrap gap-2" {
@for bit in &bits {
span class="inline-flex items-center rounded-full px-2 \
py-0.5 text-xs font-medium bg-blue-100 \
text-blue-700" {
"bit " (bit)
}
}
}
}
}))
}
}))
(card_with_header("Known Flags", html! {
div class="grid grid-cols-2 gap-2 md:grid-cols-3" {
(flag_row("Staff", user.flags, 0))
(flag_row("Partner", user.flags, 1))
(flag_row("HypeSquad Events", user.flags, 2))
(flag_row("Bug Hunter Lvl 1", user.flags, 3))
(flag_row("HypeSquad Bravery", user.flags, 6))
(flag_row("HypeSquad Brilliance", user.flags, 7))
(flag_row("HypeSquad Balance", user.flags, 8))
(flag_row("Early Supporter", user.flags, 9))
(flag_row("Bug Hunter Lvl 2", user.flags, 14))
(flag_row("Verified Bot", user.flags, 16))
(flag_row("Verified Developer", user.flags, 17))
(flag_row("Certified Moderator", user.flags, 18))
}
}))
}
}
}
fn flag_row(label: &str, flags: u64, bit: u32) -> Markup {
let active = has_flag(flags, 1u64 << bit);
let classes = if active {
"rounded-lg border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
} else {
"rounded-lg border border-neutral-200 bg-neutral-50 px-3 py-2 text-sm text-neutral-500"
};
html! {
div class=(classes) {
(label)
}
}
}
@@ -33,7 +33,6 @@ pub struct UserListParams {
pub q: String,
pub email: String,
pub ip: String,
pub ids: String,
pub requested_ids: Vec<String>,
pub limit: u32,
pub page: u32,
@@ -54,7 +53,6 @@ impl UserListParams {
email: email.unwrap_or_default().trim().to_owned(),
ip: ip.unwrap_or_default().trim().to_owned(),
requested_ids: parse_ids_query(&ids),
ids: ids.trim().to_owned(),
limit: match limit.unwrap_or(25) {
50 => 50,
100 => 100,
-58
View File
@@ -1,31 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pub fn has_flag(flags: u64, flag: u64) -> bool {
flags & flag != 0
}
pub fn set_flag(flags: u64, flag: u64) -> u64 {
flags | flag
}
pub fn clear_flag(flags: u64, flag: u64) -> u64 {
flags & !flag
}
pub fn toggle_flag(flags: u64, flag: u64) -> u64 {
flags ^ flag
}
pub fn list_flags(flags: u64) -> Vec<u32> {
let mut result = Vec::new();
for bit in 0..64 {
if flags & (1u64 << bit) != 0 {
result.push(bit);
}
}
result
}
pub fn format_discriminator(discriminator: &str) -> String {
let num: u16 = discriminator.parse().unwrap_or(0);
format!("{num:04}")
@@ -53,36 +27,4 @@ mod tests {
assert_eq!(format_discriminator(""), "0000");
assert_eq!(format_discriminator("abc"), "0000");
}
#[test]
fn has_flag_checks_bit() {
assert!(has_flag(0b1010, 0b0010));
assert!(!has_flag(0b1010, 0b0001));
assert!(has_flag(0b1111, 0b0100));
}
#[test]
fn set_flag_sets_bit() {
assert_eq!(set_flag(0b1000, 0b0010), 0b1010);
assert_eq!(set_flag(0b1010, 0b0010), 0b1010);
}
#[test]
fn clear_flag_clears_bit() {
assert_eq!(clear_flag(0b1010, 0b0010), 0b1000);
assert_eq!(clear_flag(0b1000, 0b0010), 0b1000);
}
#[test]
fn toggle_flag_toggles_bit() {
assert_eq!(toggle_flag(0b1000, 0b0010), 0b1010);
assert_eq!(toggle_flag(0b1010, 0b0010), 0b1000);
}
#[test]
fn list_flags_returns_set_positions() {
assert_eq!(list_flags(0b1010), vec![1, 3]);
assert_eq!(list_flags(0b0001), vec![0]);
assert!(list_flags(0).is_empty());
}
}
-72
View File
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use axum::{body, extract::Request};
use serde::Deserialize;
use std::collections::BTreeMap;
use url::form_urlencoded;
@@ -130,36 +129,6 @@ impl MultiValueForm {
}
}
pub fn parse_page(value: Option<&str>) -> u32 {
value
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(1)
.max(1)
}
pub fn parse_per_page(value: Option<&str>, default: u32) -> u32 {
value
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(default)
.clamp(1, 100)
}
pub fn sanitize_redirect(value: &str) -> String {
let trimmed = value.trim();
if trimmed.is_empty() || !trimmed.starts_with('/') || trimmed.starts_with("//") {
return "/".to_owned();
}
trimmed.to_owned()
}
#[derive(Deserialize)]
pub struct HtmxFormData {
#[serde(default)]
pub _csrf: String,
#[serde(flatten)]
pub fields: std::collections::HashMap<String, String>,
}
#[cfg(test)]
mod tests {
use super::*;
@@ -228,45 +197,4 @@ mod tests {
assert!(MultiValueForm::parse(b"enabled=true").bool_value("enabled"));
assert!(!MultiValueForm::parse(b"enabled=false").bool_value("enabled"));
}
#[test]
fn parse_page_defaults_to_one() {
assert_eq!(parse_page(None), 1);
assert_eq!(parse_page(Some("")), 1);
assert_eq!(parse_page(Some("abc")), 1);
}
#[test]
fn parse_page_clamps_to_minimum_one() {
assert_eq!(parse_page(Some("0")), 1);
}
#[test]
fn parse_page_valid_input() {
assert_eq!(parse_page(Some("5")), 5);
assert_eq!(parse_page(Some("100")), 100);
}
#[test]
fn sanitize_redirect_prevents_open_redirect() {
assert_eq!(sanitize_redirect(""), "/");
assert_eq!(sanitize_redirect("https://evil.com"), "/");
assert_eq!(sanitize_redirect("//evil.com"), "/");
assert_eq!(sanitize_redirect("javascript:alert(1)"), "/");
}
#[test]
fn sanitize_redirect_allows_valid_paths() {
assert_eq!(sanitize_redirect("/users"), "/users");
assert_eq!(sanitize_redirect("/guilds/123"), "/guilds/123");
assert_eq!(sanitize_redirect(" /trimmed "), "/trimmed");
}
#[test]
fn parse_per_page_clamps_range() {
assert_eq!(parse_per_page(None, 25), 25);
assert_eq!(parse_per_page(Some("0"), 25), 1);
assert_eq!(parse_per_page(Some("200"), 25), 100);
assert_eq!(parse_per_page(Some("50"), 25), 50);
}
}
-74
View File
@@ -1,7 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use std::time::{SystemTime, UNIX_EPOCH};
const FLUXER_EPOCH: u64 = 1_420_070_400_000;
pub fn snowflake_to_timestamp_ms(snowflake: &str) -> Option<u64> {
@@ -10,39 +8,6 @@ pub fn snowflake_to_timestamp_ms(snowflake: &str) -> Option<u64> {
Some(timestamp_ms)
}
pub fn relative_time(unix_seconds: u64) -> String {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before Unix epoch")
.as_secs();
let diff = now.saturating_sub(unix_seconds);
if diff < 60 {
return "just now".to_owned();
}
if diff < 3600 {
let minutes = diff / 60;
return format!(
"{} minute{} ago",
minutes,
if minutes == 1 { "" } else { "s" }
);
}
if diff < 86400 {
let hours = diff / 3600;
return format!("{} hour{} ago", hours, if hours == 1 { "" } else { "s" });
}
let days = diff / 86400;
if days < 30 {
return format!("{} day{} ago", days, if days == 1 { "" } else { "s" });
}
let months = days / 30;
if months < 12 {
return format!("{} month{} ago", months, if months == 1 { "" } else { "s" });
}
let years = months / 12;
format!("{} year{} ago", years, if years == 1 { "" } else { "s" })
}
pub fn format_admin_timestamp(iso: &str) -> String {
time::OffsetDateTime::parse(iso, &time::format_description::well_known::Rfc3339)
.or_else(|_| {
@@ -115,45 +80,6 @@ mod tests {
assert_eq!(snowflake_to_timestamp_ms("-1"), None);
}
#[test]
fn relative_time_just_now() {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
assert_eq!(relative_time(now), "just now");
}
#[test]
fn relative_time_minutes() {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
assert_eq!(relative_time(now - 120), "2 minutes ago");
assert_eq!(relative_time(now - 60), "1 minute ago");
}
#[test]
fn relative_time_hours() {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
assert_eq!(relative_time(now - 3600), "1 hour ago");
assert_eq!(relative_time(now - 7200), "2 hours ago");
}
#[test]
fn relative_time_days() {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
assert_eq!(relative_time(now - 86400), "1 day ago");
assert_eq!(relative_time(now - 86400 * 5), "5 days ago");
}
#[test]
fn snowflake_creation_date_invalid() {
assert_eq!(snowflake_creation_date("abc"), "Unknown");