feat: add configurable headers and instance branding

This commit is contained in:
ajnart committed 2026-08-23 22:28:13 +02:00
1 parent ec295fe26e
commit 86550aadc9
93 files changed
+28223 -567

No files matched your search

+47 -2
View File
@@ -10,7 +10,14 @@ import {
} from "@homarr/db/queries";
import { boards, serverSettings } from "@homarr/db/schema";
import type { ServerSettings } from "@homarr/server-settings";
import { defaultServerSettingsKeys } from "@homarr/server-settings";
import {
authBrandingSchema,
brandingServerSettingsSchema,
defaultServerSettingsKeys,
featureControlsServerSettingsSchema,
parseBrandingSettings,
parseFeatureControls,
} from "@homarr/server-settings";
import { createTRPCRouter, permissionRequiredProcedure, publicProcedure } from "../trpc";
@@ -22,6 +29,15 @@ const boardServerSettingsSchema = z.object({
}) satisfies z.ZodType<ServerSettings["board"]>;
const boardServerSettingsUpdateSchema = boardServerSettingsSchema.partial();
const brandingServerSettingsUpdateSchema = brandingServerSettingsSchema.partial().extend({
authBranding: authBrandingSchema.partial().optional(),
});
const legacyAuthBrandingUpdateSchema = z.object({
showCustomAppNameOnLogin: z.boolean().optional(),
showCustomLogoOnLogin: z.boolean().optional(),
showCustomGreetingOnLogin: z.boolean().optional(),
});
const featureControlsServerSettingsUpdateSchema = featureControlsServerSettingsSchema.partial();
export const serverSettingsRouter = createTRPCRouter({
getCulture: publicProcedure.query(async ({ ctx }) => {
@@ -30,6 +46,10 @@ export const serverSettingsRouter = createTRPCRouter({
getAll: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => {
return await getServerSettingsAsync(ctx.db);
}),
getBranding: publicProcedure.query(async ({ ctx }) => {
const branding = await getServerSettingByKeyAsync(ctx.db, "branding");
return parseBrandingSettings(branding);
}),
getBoardSettings: permissionRequiredProcedure
.requiresPermission("admin")
.meta({
@@ -100,10 +120,35 @@ export const serverSettingsRouter = createTRPCRouter({
}),
)
.mutation(async ({ ctx, input }) => {
if (input.settingsKey === "branding") {
const current = await getServerSettingByKeyAsync(ctx.db, "branding");
const parsedInput = brandingServerSettingsUpdateSchema.parse(input.value);
const legacyInput = legacyAuthBrandingUpdateSchema.parse(input.value);
const authBranding = { ...current.authBranding };
authBranding.showAppName = legacyInput.showCustomAppNameOnLogin ?? authBranding.showAppName;
authBranding.showLogo = legacyInput.showCustomLogoOnLogin ?? authBranding.showLogo;
authBranding.showGreeting = legacyInput.showCustomGreetingOnLogin ?? authBranding.showGreeting;
Object.assign(authBranding, parsedInput.authBranding);
const value = brandingServerSettingsSchema.parse({
...parseBrandingSettings(current),
...parsedInput,
authBranding,
});
await updateServerSettingByKeyAsync(ctx.db, "branding", value);
return;
}
if (input.settingsKey === "featureControls") {
const current = await getServerSettingByKeyAsync(ctx.db, "featureControls");
const parsedInput = featureControlsServerSettingsUpdateSchema.parse(input.value);
const value = parseFeatureControls({ ...current, ...parsedInput });
await updateServerSettingByKeyAsync(ctx.db, "featureControls", value);
return;
}
const current = await getServerSettingByKeyAsync(ctx.db, input.settingsKey);
await updateServerSettingByKeyAsync(ctx.db, input.settingsKey, {
...current,
...input.value,
} as ServerSettings[keyof ServerSettings]);
} as ServerSettings[typeof input.settingsKey]);
}),
});