fix(admin): use instance branding on self-hosted (#3401)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-11 15:57:12 +02:00
1 parent af7724dbf6
commit 50c28aa8fa
9 files changed
+206 -49

No files matched your search

+78 -5
View File
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::{api::types::FlashLevel, config::AdminConfig, middleware::auth::AuthContext};
use crate::{
api::types::{FlashLevel, PremiumBranding},
config::AdminConfig,
middleware::auth::AuthContext,
};
use maud::{DOCTYPE, Markup, PreEscaped, html};
use super::components::drawer::drawer_controller_script;
@@ -11,6 +15,28 @@ use super::layout_scripts::{
};
use super::layout_sidebar::render_sidebar;
const DEFAULT_PRODUCT_NAME: &str = "Fluxer";
pub fn product_name(branding: Option<&PremiumBranding>) -> &str {
branding
.and_then(|branding| branding.product_name.as_deref())
.unwrap_or(DEFAULT_PRODUCT_NAME)
}
pub fn favicon_links(config: &AdminConfig, branding: Option<&PremiumBranding>) -> Markup {
let cdn = &config.static_cdn_endpoint;
html! {
@if let Some(favicon_url) = branding.and_then(|branding| branding.favicon_url.as_deref()) {
link rel="icon" href=(favicon_url);
} @else {
link rel="icon" type="image/x-icon" href={(cdn) "/web/favicon.ico"};
link rel="apple-touch-icon" href={(cdn) "/web/apple-touch-icon.png"};
link rel="icon" type="image/png" sizes="32x32" href={(cdn) "/web/favicon-32x32.png"};
link rel="icon" type="image/png" sizes="16x16" href={(cdn) "/web/favicon-16x16.png"};
}
}
}
fn cache_busted_asset(base_path: &str, asset_version: &str, path: &str) -> String {
format!("{base_path}{path}?t={asset_version}")
}
@@ -72,10 +98,7 @@ pub fn admin_layout_ext(
title { (title) " ~ Fluxer Admin" }
link rel="stylesheet" href={(base) "/static/fonts/" (crate::fonts::STYLESHEET_FILE_NAME)};
link rel="stylesheet" href=(cache_busted_asset(base, asset_version, "/static/app.css"));
link rel="icon" type="image/x-icon" href={(config.static_cdn_endpoint) "/web/favicon.ico"};
link rel="apple-touch-icon" href={(config.static_cdn_endpoint) "/web/apple-touch-icon.png"};
link rel="icon" type="image/png" sizes="32x32" href={(config.static_cdn_endpoint) "/web/favicon-32x32.png"};
link rel="icon" type="image/png" sizes="16x16" href={(config.static_cdn_endpoint) "/web/favicon-16x16.png"};
(favicon_links(config, auth.branding.as_ref()))
script src=(cache_busted_asset(base, asset_version, "/static/htmx.min.js")) defer {}
}
body class="min-h-screen overflow-hidden bg-neutral-50" hx-boost="true" {
@@ -126,3 +149,53 @@ fn render_flash(flash: &crate::api::types::FlashMessage) -> Markup {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::ProxyConfig;
fn config() -> AdminConfig {
AdminConfig {
host: String::new(),
port: 3020,
secret_key_base: "test-secret".to_owned(),
base_path: String::new(),
api_endpoint: String::new(),
media_endpoint: String::new(),
static_cdn_endpoint: "https://static.example".to_owned(),
reports_bucket_origin: String::new(),
admin_endpoint: String::new(),
web_app_endpoint: String::new(),
oauth_client_id: String::new(),
oauth_client_secret: String::new(),
oauth_redirect_uri: String::new(),
build_version: "test".to_owned(),
self_hosted: true,
proxy: ProxyConfig {
trust_client_ip_header: false,
client_ip_header_name: String::new(),
},
}
}
#[test]
fn instance_branding_replaces_the_fluxer_name_and_favicons() {
let branding = PremiumBranding {
product_name: Some("Example".to_owned()),
favicon_url: Some("https://media.example/favicon.png".to_owned()),
..PremiumBranding::default()
};
let links = favicon_links(&config(), Some(&branding)).into_string();
assert!(links.contains("https://media.example/favicon.png"));
assert!(!links.contains("static.example"));
assert_eq!(product_name(Some(&branding)), "Example");
}
#[test]
fn missing_branding_keeps_the_fluxer_name_and_favicons() {
let links = favicon_links(&config(), Some(&PremiumBranding::default())).into_string();
assert!(links.contains("https://static.example/web/favicon.ico"));
assert_eq!(product_name(None), "Fluxer");
}
}
+12 -4
View File
@@ -1,9 +1,17 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::config::AdminConfig;
use crate::{
api::types::PremiumBranding,
config::AdminConfig,
templates::layout::{favicon_links, product_name},
};
use maud::{DOCTYPE, Markup, html};
pub fn login_page(config: &AdminConfig, error_message: Option<&str>) -> Markup {
pub fn login_page(
config: &AdminConfig,
branding: Option<&PremiumBranding>,
error_message: Option<&str>,
) -> Markup {
let base = &config.base_path;
html! {
(DOCTYPE)
@@ -14,7 +22,7 @@ pub fn login_page(config: &AdminConfig, error_message: Option<&str>) -> Markup {
title { "Login ~ Fluxer Admin" }
link rel="stylesheet" href={(base) "/static/fonts/" (crate::fonts::STYLESHEET_FILE_NAME)};
link rel="stylesheet" href={(base) "/static/app.css"};
link rel="icon" type="image/x-icon" href={(config.static_cdn_endpoint) "/web/favicon.ico"};
(favicon_links(config, branding))
}
body class="flex min-h-[100dvh] items-center justify-center bg-neutral-50 p-4" {
main class="w-full max-w-sm" {
@@ -38,7 +46,7 @@ pub fn login_page(config: &AdminConfig, error_message: Option<&str>) -> Markup {
bg-neutral-900 text-white hover:bg-neutral-800 \
px-4 py-2 text-base w-full sm:w-fit \
focus:ring-offset-white" {
span { "Sign in with Fluxer" }
span { "Sign in with " (product_name(branding)) }
}
}
}
@@ -11,7 +11,7 @@ use crate::{
},
page_container::{card, page_header},
},
layout::admin_layout,
layout::{admin_layout, product_name},
},
};
use maud::{Markup, html};
@@ -37,9 +37,9 @@ pub fn system_dm_page(
"Send a system DM"
}
p class="text-sm text-neutral-500" {
"Sent from the official Fluxer system account. Each recipient \
will receive the same content as a DM. Progress is observable \
on the "
"Sent from the official " (product_name(auth.branding.as_ref()))
" system account. Each recipient will receive the same content \
as a DM. Progress is observable on the "
a href=(jobs_url) class="font-medium text-neutral-900 hover:underline" {
"Jobs page"
}