Merge branch 'dev' of https://github.com/bulwarkmail/webmail into dev
This commit is contained in:
+30
-2
@@ -67,6 +67,17 @@ JMAP_SERVER_URL=https://your-jmap-server.com
|
|||||||
# For Docker, mount a persistent volume at this path.
|
# For Docker, mount a persistent volume at this path.
|
||||||
# SETTINGS_DATA_DIR=./data/settings
|
# SETTINGS_DATA_DIR=./data/settings
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Server Listen Address
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Hostname the server binds to (default: 0.0.0.0)
|
||||||
|
# Set to "::" for IPv6 or "[::]" for dual-stack support.
|
||||||
|
# HOSTNAME=0.0.0.0
|
||||||
|
|
||||||
|
# Port the server listens on (default: 3000)
|
||||||
|
# PORT=3000
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Logging
|
# Logging
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -78,10 +89,27 @@ JMAP_SERVER_URL=https://your-jmap-server.com
|
|||||||
# LOG_LEVEL=info
|
# LOG_LEVEL=info
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Login Page Customization (all optional)
|
# Branding (all optional)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Custom logo images for the login page (PNG, SVG, etc.)
|
# Custom favicon for the browser tab.
|
||||||
|
# Supported formats: SVG (recommended), PNG, ICO.
|
||||||
|
# Recommended size: 32×32px minimum, 512×512px maximum (or SVG for best scaling).
|
||||||
|
# Can be an absolute URL or a path relative to the public/ directory.
|
||||||
|
# Defaults to the Bulwark favicon if not set.
|
||||||
|
# FAVICON_URL=/branding/my-favicon.svg
|
||||||
|
|
||||||
|
# Custom logos for the sidebar (shown in the main app after login).
|
||||||
|
# Supported formats: SVG (recommended), PNG, WebP.
|
||||||
|
# Recommended size: min 24×24px, max 128×128px
|
||||||
|
# Can be absolute URLs or paths relative to the public/ directory.
|
||||||
|
# If not set, no logo is shown in the sidebar.
|
||||||
|
# APP_LOGO_LIGHT_URL=/branding/my-logo-color.svg
|
||||||
|
# APP_LOGO_DARK_URL=/branding/my-logo-white.svg
|
||||||
|
|
||||||
|
# Custom logo images for the login page.
|
||||||
|
# Supported formats: SVG (recommended), PNG, WebP.
|
||||||
|
# Recommended size: min 32×32px, max 512×512px
|
||||||
# Can be absolute URLs or paths relative to the public/ directory.
|
# Can be absolute URLs or paths relative to the public/ directory.
|
||||||
# Light mode logo (shown on light backgrounds), defaults to Bulwark logo.
|
# Light mode logo (shown on light backgrounds), defaults to Bulwark logo.
|
||||||
LOGIN_LOGO_LIGHT_URL=/branding/Bulwark_Logo_Color.svg
|
LOGIN_LOGO_LIGHT_URL=/branding/Bulwark_Logo_Color.svg
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ LABEL org.opencontainers.image.description="Modern webmail client built with Nex
|
|||||||
LABEL org.opencontainers.image.source="https://github.com/bulwarkmail/webmail"
|
LABEL org.opencontainers.image.source="https://github.com/bulwarkmail/webmail"
|
||||||
LABEL org.opencontainers.image.url="https://github.com/bulwarkmail/webmail"
|
LABEL org.opencontainers.image.url="https://github.com/bulwarkmail/webmail"
|
||||||
LABEL org.opencontainers.image.licenses="AGPL-3.0-only"
|
LABEL org.opencontainers.image.licenses="AGPL-3.0-only"
|
||||||
LABEL org.opencontainers.image.vendor="root.cloud"
|
LABEL org.opencontainers.image.vendor="rbm.systems"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|||||||
@@ -243,6 +243,16 @@ APP_NAME=My Webmail
|
|||||||
|
|
||||||
All variables are **runtime** — Docker deployments can be configured without rebuilding.
|
All variables are **runtime** — Docker deployments can be configured without rebuilding.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Server Listen Address</summary>
|
||||||
|
|
||||||
|
```env
|
||||||
|
HOSTNAME=0.0.0.0 # Default; use "::" for IPv6
|
||||||
|
PORT=3000 # Default listen port
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>OAuth2/OIDC (SSO)</summary>
|
<summary>OAuth2/OIDC (SSO)</summary>
|
||||||
|
|
||||||
|
|||||||
@@ -41,11 +41,13 @@ import { isFilePreviewable } from "@/lib/file-preview";
|
|||||||
import { Search, Filter, ChevronDown, X, Paperclip, Star, Mail, MailOpen, RotateCcw, PenSquare, PenLine, CheckSquare, Square } from "lucide-react";
|
import { Search, Filter, ChevronDown, X, Paperclip, Star, Mail, MailOpen, RotateCcw, PenSquare, PenLine, CheckSquare, Square } from "lucide-react";
|
||||||
import { ResizeHandle } from "@/components/layout/resize-handle";
|
import { ResizeHandle } from "@/components/layout/resize-handle";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const tCommon = useTranslations('common');
|
const tCommon = useTranslations('common');
|
||||||
|
const { appName } = useConfig();
|
||||||
const [showComposer, setShowComposer] = useState(false);
|
const [showComposer, setShowComposer] = useState(false);
|
||||||
const [composerMode, setComposerMode] = useState<'compose' | 'reply' | 'replyAll' | 'forward'>('compose');
|
const [composerMode, setComposerMode] = useState<'compose' | 'reply' | 'replyAll' | 'forward'>('compose');
|
||||||
const [composerDraftText, setComposerDraftText] = useState("");
|
const [composerDraftText, setComposerDraftText] = useState("");
|
||||||
@@ -224,7 +226,7 @@ export default function Home() {
|
|||||||
|
|
||||||
// Update page title based on context
|
// Update page title based on context
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let title = tCommon('app_title');
|
let title = appName;
|
||||||
|
|
||||||
if (showComposer) {
|
if (showComposer) {
|
||||||
// Composing email
|
// Composing email
|
||||||
@@ -234,11 +236,11 @@ export default function Home() {
|
|||||||
replyAll: t('email_composer.reply_all'),
|
replyAll: t('email_composer.reply_all'),
|
||||||
forward: t('email_composer.forward'),
|
forward: t('email_composer.forward'),
|
||||||
}[composerMode] || t('email_composer.new_message');
|
}[composerMode] || t('email_composer.new_message');
|
||||||
title = `${modeText} - ${tCommon('app_title')}`;
|
title = `${modeText} - ${appName}`;
|
||||||
} else if (selectedEmail) {
|
} else if (selectedEmail) {
|
||||||
// Reading email
|
// Reading email
|
||||||
const subject = selectedEmail.subject || t('email_viewer.no_subject');
|
const subject = selectedEmail.subject || t('email_viewer.no_subject');
|
||||||
title = `${subject} - ${tCommon('app_title')}`;
|
title = `${subject} - ${appName}`;
|
||||||
} else if (selectedMailbox && mailboxes.length > 0) {
|
} else if (selectedMailbox && mailboxes.length > 0) {
|
||||||
// Mailbox view
|
// Mailbox view
|
||||||
const mailbox = mailboxes.find(mb => mb.id === selectedMailbox);
|
const mailbox = mailboxes.find(mb => mb.id === selectedMailbox);
|
||||||
@@ -246,13 +248,13 @@ export default function Home() {
|
|||||||
const mailboxName = mailbox.name;
|
const mailboxName = mailbox.name;
|
||||||
const unreadCount = mailbox.unreadEmails || 0;
|
const unreadCount = mailbox.unreadEmails || 0;
|
||||||
title = unreadCount > 0
|
title = unreadCount > 0
|
||||||
? `${mailboxName} (${unreadCount}) - ${tCommon('app_title')}`
|
? `${mailboxName} (${unreadCount}) - ${appName}`
|
||||||
: `${mailboxName} - ${tCommon('app_title')}`;
|
: `${mailboxName} - ${appName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = title;
|
document.title = title;
|
||||||
}, [showComposer, composerMode, selectedEmail, selectedMailbox, mailboxes, t, tCommon]);
|
}, [showComposer, composerMode, selectedEmail, selectedMailbox, mailboxes, t, appName]);
|
||||||
|
|
||||||
// Check auth on mount
|
// Check auth on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export async function GET() {
|
|||||||
settingsSyncEnabled: process.env.SETTINGS_SYNC_ENABLED === 'true' && !!process.env.SESSION_SECRET,
|
settingsSyncEnabled: process.env.SETTINGS_SYNC_ENABLED === 'true' && !!process.env.SESSION_SECRET,
|
||||||
stalwartFeaturesEnabled: process.env.STALWART_FEATURES !== 'false',
|
stalwartFeaturesEnabled: process.env.STALWART_FEATURES !== 'false',
|
||||||
devMode: process.env.DEV_MOCK_JMAP === 'true',
|
devMode: process.env.DEV_MOCK_JMAP === 'true',
|
||||||
|
faviconUrl: process.env.FAVICON_URL || '/branding/Bulwark_Favicon.svg',
|
||||||
|
appLogoLightUrl: process.env.APP_LOGO_LIGHT_URL || '',
|
||||||
|
appLogoDarkUrl: process.env.APP_LOGO_DARK_URL || '',
|
||||||
loginLogoLightUrl: process.env.LOGIN_LOGO_LIGHT_URL || '/branding/Bulwark_Logo_Color.svg',
|
loginLogoLightUrl: process.env.LOGIN_LOGO_LIGHT_URL || '/branding/Bulwark_Logo_Color.svg',
|
||||||
loginLogoDarkUrl: process.env.LOGIN_LOGO_DARK_URL || '/branding/Bulwark_Logo_White.svg',
|
loginLogoDarkUrl: process.env.LOGIN_LOGO_DARK_URL || '/branding/Bulwark_Logo_White.svg',
|
||||||
loginCompanyName: process.env.LOGIN_COMPANY_NAME || '',
|
loginCompanyName: process.env.LOGIN_COMPANY_NAME || '',
|
||||||
|
|||||||
+9
-4
@@ -14,10 +14,15 @@ const geistMono = Geist_Mono({
|
|||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
title: "Bulwark Webmail",
|
const faviconUrl = process.env.FAVICON_URL;
|
||||||
description: "Minimalist webmail client using JMAP protocol",
|
|
||||||
};
|
return {
|
||||||
|
title: process.env.APP_NAME || process.env.NEXT_PUBLIC_APP_NAME || "Webmail",
|
||||||
|
description: "Minimalist webmail client using JMAP protocol",
|
||||||
|
...(faviconUrl ? { icons: { icon: faviconUrl } } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import { useSettingsStore, KEYWORD_PALETTE, KeywordDefinition } from "@/stores/s
|
|||||||
import { useEmailStore } from "@/stores/email-store";
|
import { useEmailStore } from "@/stores/email-store";
|
||||||
import { toast } from "@/stores/toast-store";
|
import { toast } from "@/stores/toast-store";
|
||||||
import { debug } from "@/lib/debug";
|
import { debug } from "@/lib/debug";
|
||||||
|
import { useConfig } from "@/hooks/use-config";
|
||||||
|
import { useThemeStore } from "@/stores/theme-store";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
mailboxes: Mailbox[];
|
mailboxes: Mailbox[];
|
||||||
@@ -361,6 +363,8 @@ export function Sidebar({
|
|||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
const { sidebarCollapsed: isCollapsed, toggleSidebarCollapsed } = useUIStore();
|
const { sidebarCollapsed: isCollapsed, toggleSidebarCollapsed } = useUIStore();
|
||||||
const { primaryIdentity } = useAuthStore();
|
const { primaryIdentity } = useAuthStore();
|
||||||
|
const { appLogoLightUrl, appLogoDarkUrl } = useConfig();
|
||||||
|
const resolvedTheme = useThemeStore((s) => s.resolvedTheme);
|
||||||
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set());
|
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set());
|
||||||
const [tagsExpanded, setTagsExpanded] = useState(() => {
|
const [tagsExpanded, setTagsExpanded] = useState(() => {
|
||||||
try {
|
try {
|
||||||
@@ -460,6 +464,17 @@ export function Sidebar({
|
|||||||
<X className="w-5 h-5" />
|
<X className="w-5 h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
const logoUrl = resolvedTheme === 'dark' ? (appLogoDarkUrl || appLogoLightUrl) : (appLogoLightUrl || appLogoDarkUrl);
|
||||||
|
return logoUrl ? (
|
||||||
|
<img
|
||||||
|
src={logoUrl}
|
||||||
|
alt=""
|
||||||
|
className={cn("object-contain flex-shrink-0", isCollapsed ? "w-6 h-6" : "w-6 h-6")}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
})()}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ services:
|
|||||||
image: ghcr.io/bulwarkmail/webmail:latest
|
image: ghcr.io/bulwarkmail/webmail:latest
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- HOSTNAME=0.0.0.0 # Use "::" for IPv6
|
||||||
|
- PORT=3000
|
||||||
env_file:
|
env_file:
|
||||||
- .env.local
|
- .env.local
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ interface ConfigData {
|
|||||||
settingsSyncEnabled: boolean;
|
settingsSyncEnabled: boolean;
|
||||||
stalwartFeaturesEnabled: boolean;
|
stalwartFeaturesEnabled: boolean;
|
||||||
devMode: boolean;
|
devMode: boolean;
|
||||||
|
faviconUrl: string;
|
||||||
|
appLogoLightUrl: string;
|
||||||
|
appLogoDarkUrl: string;
|
||||||
loginLogoLightUrl: string;
|
loginLogoLightUrl: string;
|
||||||
loginLogoDarkUrl: string;
|
loginLogoDarkUrl: string;
|
||||||
loginCompanyName: string;
|
loginCompanyName: string;
|
||||||
@@ -79,6 +82,9 @@ export function useConfig(): AppConfig {
|
|||||||
settingsSyncEnabled: configCache?.settingsSyncEnabled || false,
|
settingsSyncEnabled: configCache?.settingsSyncEnabled || false,
|
||||||
stalwartFeaturesEnabled: configCache?.stalwartFeaturesEnabled ?? true,
|
stalwartFeaturesEnabled: configCache?.stalwartFeaturesEnabled ?? true,
|
||||||
devMode: configCache?.devMode || false,
|
devMode: configCache?.devMode || false,
|
||||||
|
faviconUrl: configCache?.faviconUrl || '/branding/Bulwark_Favicon.svg',
|
||||||
|
appLogoLightUrl: configCache?.appLogoLightUrl || '',
|
||||||
|
appLogoDarkUrl: configCache?.appLogoDarkUrl || '',
|
||||||
loginLogoLightUrl: configCache?.loginLogoLightUrl || '/branding/Bulwark_Logo_Color.svg',
|
loginLogoLightUrl: configCache?.loginLogoLightUrl || '/branding/Bulwark_Logo_Color.svg',
|
||||||
loginLogoDarkUrl: configCache?.loginLogoDarkUrl || '/branding/Bulwark_Logo_White.svg',
|
loginLogoDarkUrl: configCache?.loginLogoDarkUrl || '/branding/Bulwark_Logo_White.svg',
|
||||||
loginCompanyName: configCache?.loginCompanyName || '',
|
loginCompanyName: configCache?.loginCompanyName || '',
|
||||||
@@ -103,6 +109,9 @@ export function useConfig(): AppConfig {
|
|||||||
settingsSyncEnabled: configCache.settingsSyncEnabled,
|
settingsSyncEnabled: configCache.settingsSyncEnabled,
|
||||||
stalwartFeaturesEnabled: configCache.stalwartFeaturesEnabled,
|
stalwartFeaturesEnabled: configCache.stalwartFeaturesEnabled,
|
||||||
devMode: configCache.devMode,
|
devMode: configCache.devMode,
|
||||||
|
faviconUrl: configCache.faviconUrl,
|
||||||
|
appLogoLightUrl: configCache.appLogoLightUrl,
|
||||||
|
appLogoDarkUrl: configCache.appLogoDarkUrl,
|
||||||
loginLogoLightUrl: configCache.loginLogoLightUrl,
|
loginLogoLightUrl: configCache.loginLogoLightUrl,
|
||||||
loginLogoDarkUrl: configCache.loginLogoDarkUrl,
|
loginLogoDarkUrl: configCache.loginLogoDarkUrl,
|
||||||
loginCompanyName: configCache.loginCompanyName,
|
loginCompanyName: configCache.loginCompanyName,
|
||||||
@@ -128,6 +137,9 @@ export function useConfig(): AppConfig {
|
|||||||
settingsSyncEnabled: data.settingsSyncEnabled,
|
settingsSyncEnabled: data.settingsSyncEnabled,
|
||||||
stalwartFeaturesEnabled: data.stalwartFeaturesEnabled,
|
stalwartFeaturesEnabled: data.stalwartFeaturesEnabled,
|
||||||
devMode: data.devMode,
|
devMode: data.devMode,
|
||||||
|
faviconUrl: data.faviconUrl,
|
||||||
|
appLogoLightUrl: data.appLogoLightUrl,
|
||||||
|
appLogoDarkUrl: data.appLogoDarkUrl,
|
||||||
loginLogoLightUrl: data.loginLogoLightUrl,
|
loginLogoLightUrl: data.loginLogoLightUrl,
|
||||||
loginLogoDarkUrl: data.loginLogoDarkUrl,
|
loginLogoDarkUrl: data.loginLogoDarkUrl,
|
||||||
loginCompanyName: data.loginCompanyName,
|
loginCompanyName: data.loginCompanyName,
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ describe('config API route', () => {
|
|||||||
delete process.env.SETTINGS_SYNC_ENABLED;
|
delete process.env.SETTINGS_SYNC_ENABLED;
|
||||||
delete process.env.STALWART_FEATURES;
|
delete process.env.STALWART_FEATURES;
|
||||||
delete process.env.DEV_MOCK_JMAP;
|
delete process.env.DEV_MOCK_JMAP;
|
||||||
|
delete process.env.FAVICON_URL;
|
||||||
|
delete process.env.APP_LOGO_LIGHT_URL;
|
||||||
|
delete process.env.APP_LOGO_DARK_URL;
|
||||||
delete process.env.LOGIN_COMPANY_NAME;
|
delete process.env.LOGIN_COMPANY_NAME;
|
||||||
delete process.env.LOGIN_IMPRINT_URL;
|
delete process.env.LOGIN_IMPRINT_URL;
|
||||||
delete process.env.LOGIN_PRIVACY_POLICY_URL;
|
delete process.env.LOGIN_PRIVACY_POLICY_URL;
|
||||||
@@ -60,6 +63,9 @@ describe('config API route', () => {
|
|||||||
expect(config.loginImprintUrl).toBe('');
|
expect(config.loginImprintUrl).toBe('');
|
||||||
expect(config.loginPrivacyPolicyUrl).toBe('');
|
expect(config.loginPrivacyPolicyUrl).toBe('');
|
||||||
expect(config.loginWebsiteUrl).toBe('');
|
expect(config.loginWebsiteUrl).toBe('');
|
||||||
|
expect(config.faviconUrl).toBe('/branding/Bulwark_Favicon.svg');
|
||||||
|
expect(config.appLogoLightUrl).toBe('');
|
||||||
|
expect(config.appLogoDarkUrl).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use runtime env vars over defaults', async () => {
|
it('should use runtime env vars over defaults', async () => {
|
||||||
@@ -142,4 +148,16 @@ describe('config API route', () => {
|
|||||||
|
|
||||||
expect(config.stalwartFeaturesEnabled).toBe(false);
|
expect(config.stalwartFeaturesEnabled).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return custom favicon and app logo URLs', async () => {
|
||||||
|
process.env.FAVICON_URL = '/branding/custom-favicon.svg';
|
||||||
|
process.env.APP_LOGO_LIGHT_URL = '/branding/my-logo.svg';
|
||||||
|
process.env.APP_LOGO_DARK_URL = '/branding/my-logo-white.svg';
|
||||||
|
|
||||||
|
const config = await getConfig();
|
||||||
|
|
||||||
|
expect(config.faviconUrl).toBe('/branding/custom-favicon.svg');
|
||||||
|
expect(config.appLogoLightUrl).toBe('/branding/my-logo.svg');
|
||||||
|
expect(config.appLogoDarkUrl).toBe('/branding/my-logo-white.svg');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "bulwark-webmail",
|
"name": "bulwark-webmail",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"description": "Bulwark Webmail — a modern webmail client built for Stalwart Mail Server",
|
"description": "Bulwark Webmail — a modern webmail client built for Stalwart Mail Server",
|
||||||
"author": "Matthieu MALVACHE <matthieu@root.cloud>",
|
"author": "Bulwark Webmail <bulwark@rbm.systems>",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ CFG_LOG_LEVEL="info"
|
|||||||
CFG_LOGIN_COMPANY_NAME=""
|
CFG_LOGIN_COMPANY_NAME=""
|
||||||
CFG_LOGIN_LOGO_LIGHT_URL=""
|
CFG_LOGIN_LOGO_LIGHT_URL=""
|
||||||
CFG_LOGIN_LOGO_DARK_URL=""
|
CFG_LOGIN_LOGO_DARK_URL=""
|
||||||
|
CFG_FAVICON_URL=""
|
||||||
|
CFG_APP_LOGO_LIGHT_URL=""
|
||||||
|
CFG_APP_LOGO_DARK_URL=""
|
||||||
CFG_LOGIN_IMPRINT_URL=""
|
CFG_LOGIN_IMPRINT_URL=""
|
||||||
CFG_LOGIN_PRIVACY_POLICY_URL=""
|
CFG_LOGIN_PRIVACY_POLICY_URL=""
|
||||||
CFG_LOGIN_WEBSITE_URL=""
|
CFG_LOGIN_WEBSITE_URL=""
|
||||||
@@ -761,20 +764,30 @@ screen_login_customization() {
|
|||||||
CURRENT_STEP=5
|
CURRENT_STEP=5
|
||||||
draw_header
|
draw_header
|
||||||
|
|
||||||
section_header 5 "Login Page Branding" \
|
section_header 5 "Branding" \
|
||||||
"Add your company name and legal links to the login page."
|
"Customize logos, favicon, and login page for your domain identity."
|
||||||
|
|
||||||
echo -e " All fields are ${BOLD}optional${RESET}. Press Enter to skip any field."
|
echo -e " All fields are ${BOLD}optional${RESET}. Press Enter to skip any field."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
echo -e " ${DIM}Custom favicon for the browser tab (SVG recommended, 32×32 to 512×512px)${RESET}"
|
||||||
|
echo -e " ${DIM}Leave blank to use the default Bulwark favicon.${RESET}"
|
||||||
|
prompt_value "Favicon URL" "$CFG_FAVICON_URL" "CFG_FAVICON_URL"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${DIM}App logo shown in the sidebar (SVG recommended, 24×24 to 128×128px)${RESET}"
|
||||||
|
echo -e " ${DIM}Leave blank for no sidebar logo.${RESET}"
|
||||||
|
prompt_value "App logo URL (light mode)" "$CFG_APP_LOGO_LIGHT_URL" "CFG_APP_LOGO_LIGHT_URL"
|
||||||
|
echo ""
|
||||||
|
prompt_value "App logo URL (dark mode)" "$CFG_APP_LOGO_DARK_URL" "CFG_APP_LOGO_DARK_URL"
|
||||||
|
echo ""
|
||||||
echo -e " ${DIM}Shown on the login page footer. Example: Acme Corp${RESET}"
|
echo -e " ${DIM}Shown on the login page footer. Example: Acme Corp${RESET}"
|
||||||
prompt_value "Company / organization name" "$CFG_LOGIN_COMPANY_NAME" "CFG_LOGIN_COMPANY_NAME"
|
prompt_value "Company / organization name" "$CFG_LOGIN_COMPANY_NAME" "CFG_LOGIN_COMPANY_NAME"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}Custom logo URLs for the login page (PNG, SVG, etc.)${RESET}"
|
echo -e " ${DIM}Custom logo URLs for the login page (SVG recommended, 32×32 to 512×512px)${RESET}"
|
||||||
echo -e " ${DIM}Leave blank to use the default Bulwark logo.${RESET}"
|
echo -e " ${DIM}Leave blank to use the default Bulwark logo.${RESET}"
|
||||||
prompt_value "Logo URL (light mode)" "$CFG_LOGIN_LOGO_LIGHT_URL" "CFG_LOGIN_LOGO_LIGHT_URL"
|
prompt_value "Login logo URL (light mode)" "$CFG_LOGIN_LOGO_LIGHT_URL" "CFG_LOGIN_LOGO_LIGHT_URL"
|
||||||
echo ""
|
echo ""
|
||||||
prompt_value "Logo URL (dark mode)" "$CFG_LOGIN_LOGO_DARK_URL" "CFG_LOGIN_LOGO_DARK_URL"
|
prompt_value "Login logo URL (dark mode)" "$CFG_LOGIN_LOGO_DARK_URL" "CFG_LOGIN_LOGO_DARK_URL"
|
||||||
echo ""
|
echo ""
|
||||||
prompt_value "Website URL" "$CFG_LOGIN_WEBSITE_URL" "CFG_LOGIN_WEBSITE_URL"
|
prompt_value "Website URL" "$CFG_LOGIN_WEBSITE_URL" "CFG_LOGIN_WEBSITE_URL"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -782,9 +795,9 @@ screen_login_customization() {
|
|||||||
echo ""
|
echo ""
|
||||||
prompt_value "Privacy policy URL" "$CFG_LOGIN_PRIVACY_POLICY_URL" "CFG_LOGIN_PRIVACY_POLICY_URL"
|
prompt_value "Privacy policy URL" "$CFG_LOGIN_PRIVACY_POLICY_URL" "CFG_LOGIN_PRIVACY_POLICY_URL"
|
||||||
|
|
||||||
if [[ -z "$CFG_LOGIN_COMPANY_NAME" && -z "$CFG_LOGIN_LOGO_LIGHT_URL" && -z "$CFG_LOGIN_LOGO_DARK_URL" && -z "$CFG_LOGIN_WEBSITE_URL" && -z "$CFG_LOGIN_IMPRINT_URL" && -z "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
|
if [[ -z "$CFG_LOGIN_COMPANY_NAME" && -z "$CFG_LOGIN_LOGO_LIGHT_URL" && -z "$CFG_LOGIN_LOGO_DARK_URL" && -z "$CFG_FAVICON_URL" && -z "$CFG_APP_LOGO_LIGHT_URL" && -z "$CFG_APP_LOGO_DARK_URL" && -z "$CFG_LOGIN_WEBSITE_URL" && -z "$CFG_LOGIN_IMPRINT_URL" && -z "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
note "No branding configured. The login page will show defaults."
|
note "No branding configured. The app will use defaults."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
draw_footer
|
draw_footer
|
||||||
@@ -892,13 +905,19 @@ screen_summary() {
|
|||||||
|
|
||||||
# Login page
|
# Login page
|
||||||
echo -e " ${CYAN}${BOLD}BRANDING${RESET}"
|
echo -e " ${CYAN}${BOLD}BRANDING${RESET}"
|
||||||
if [[ -n "$CFG_LOGIN_COMPANY_NAME" || -n "$CFG_LOGIN_LOGO_LIGHT_URL" || -n "$CFG_LOGIN_LOGO_DARK_URL" || -n "$CFG_LOGIN_WEBSITE_URL" || -n "$CFG_LOGIN_IMPRINT_URL" || -n "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
|
if [[ -n "$CFG_LOGIN_COMPANY_NAME" || -n "$CFG_LOGIN_LOGO_LIGHT_URL" || -n "$CFG_LOGIN_LOGO_DARK_URL" || -n "$CFG_FAVICON_URL" || -n "$CFG_APP_LOGO_LIGHT_URL" || -n "$CFG_APP_LOGO_DARK_URL" || -n "$CFG_LOGIN_WEBSITE_URL" || -n "$CFG_LOGIN_IMPRINT_URL" || -n "$CFG_LOGIN_PRIVACY_POLICY_URL" ]]; then
|
||||||
|
[[ -n "$CFG_FAVICON_URL" ]] && \
|
||||||
|
echo -e " Favicon URL ........... ${BOLD}${CFG_FAVICON_URL}${RESET}"
|
||||||
|
[[ -n "$CFG_APP_LOGO_LIGHT_URL" ]] && \
|
||||||
|
echo -e " App Logo (light) ...... ${BOLD}${CFG_APP_LOGO_LIGHT_URL}${RESET}"
|
||||||
|
[[ -n "$CFG_APP_LOGO_DARK_URL" ]] && \
|
||||||
|
echo -e " App Logo (dark) ....... ${BOLD}${CFG_APP_LOGO_DARK_URL}${RESET}"
|
||||||
[[ -n "$CFG_LOGIN_COMPANY_NAME" ]] && \
|
[[ -n "$CFG_LOGIN_COMPANY_NAME" ]] && \
|
||||||
echo -e " Company Name .......... ${BOLD}${CFG_LOGIN_COMPANY_NAME}${RESET}"
|
echo -e " Company Name .......... ${BOLD}${CFG_LOGIN_COMPANY_NAME}${RESET}"
|
||||||
[[ -n "$CFG_LOGIN_LOGO_LIGHT_URL" ]] && \
|
[[ -n "$CFG_LOGIN_LOGO_LIGHT_URL" ]] && \
|
||||||
echo -e " Logo (light mode) ..... ${BOLD}${CFG_LOGIN_LOGO_LIGHT_URL}${RESET}"
|
echo -e " Login Logo (light) .... ${BOLD}${CFG_LOGIN_LOGO_LIGHT_URL}${RESET}"
|
||||||
[[ -n "$CFG_LOGIN_LOGO_DARK_URL" ]] && \
|
[[ -n "$CFG_LOGIN_LOGO_DARK_URL" ]] && \
|
||||||
echo -e " Logo (dark mode) ...... ${BOLD}${CFG_LOGIN_LOGO_DARK_URL}${RESET}"
|
echo -e " Login Logo (dark) ..... ${BOLD}${CFG_LOGIN_LOGO_DARK_URL}${RESET}"
|
||||||
[[ -n "$CFG_LOGIN_WEBSITE_URL" ]] && \
|
[[ -n "$CFG_LOGIN_WEBSITE_URL" ]] && \
|
||||||
echo -e " Website URL ........... ${BOLD}${CFG_LOGIN_WEBSITE_URL}${RESET}"
|
echo -e " Website URL ........... ${BOLD}${CFG_LOGIN_WEBSITE_URL}${RESET}"
|
||||||
[[ -n "$CFG_LOGIN_IMPRINT_URL" ]] && \
|
[[ -n "$CFG_LOGIN_IMPRINT_URL" ]] && \
|
||||||
@@ -1003,9 +1022,12 @@ ENVEOF
|
|||||||
LOG_FORMAT=${CFG_LOG_FORMAT}
|
LOG_FORMAT=${CFG_LOG_FORMAT}
|
||||||
LOG_LEVEL=${CFG_LOG_LEVEL}
|
LOG_LEVEL=${CFG_LOG_LEVEL}
|
||||||
|
|
||||||
# -- Login Page Customization --------------------------------------------------
|
# -- Branding ------------------------------------------------------------------
|
||||||
ENVEOF
|
ENVEOF
|
||||||
|
|
||||||
|
[[ -n "$CFG_FAVICON_URL" ]] && echo "FAVICON_URL=${CFG_FAVICON_URL}" >> "$ENV_FILE"
|
||||||
|
[[ -n "$CFG_APP_LOGO_LIGHT_URL" ]] && echo "APP_LOGO_LIGHT_URL=${CFG_APP_LOGO_LIGHT_URL}" >> "$ENV_FILE"
|
||||||
|
[[ -n "$CFG_APP_LOGO_DARK_URL" ]] && echo "APP_LOGO_DARK_URL=${CFG_APP_LOGO_DARK_URL}" >> "$ENV_FILE"
|
||||||
[[ -n "$CFG_LOGIN_COMPANY_NAME" ]] && echo "LOGIN_COMPANY_NAME=${CFG_LOGIN_COMPANY_NAME}" >> "$ENV_FILE"
|
[[ -n "$CFG_LOGIN_COMPANY_NAME" ]] && echo "LOGIN_COMPANY_NAME=${CFG_LOGIN_COMPANY_NAME}" >> "$ENV_FILE"
|
||||||
[[ -n "$CFG_LOGIN_LOGO_LIGHT_URL" ]] && echo "LOGIN_LOGO_LIGHT_URL=${CFG_LOGIN_LOGO_LIGHT_URL}" >> "$ENV_FILE"
|
[[ -n "$CFG_LOGIN_LOGO_LIGHT_URL" ]] && echo "LOGIN_LOGO_LIGHT_URL=${CFG_LOGIN_LOGO_LIGHT_URL}" >> "$ENV_FILE"
|
||||||
[[ -n "$CFG_LOGIN_LOGO_DARK_URL" ]] && echo "LOGIN_LOGO_DARK_URL=${CFG_LOGIN_LOGO_DARK_URL}" >> "$ENV_FILE"
|
[[ -n "$CFG_LOGIN_LOGO_DARK_URL" ]] && echo "LOGIN_LOGO_DARK_URL=${CFG_LOGIN_LOGO_DARK_URL}" >> "$ENV_FILE"
|
||||||
|
|||||||
Reference in New Issue
Block a user