refactor: update whitelabeling hooks and API usage for improved access control and consistency

This commit is contained in:
Mauricio Siu
2026-03-10 00:47:30 -06:00
parent a6999b1cf2
commit 33532d3cf7
13 changed files with 108 additions and 32 deletions

View File

@@ -45,12 +45,12 @@ import {
import { authClient } from "@/lib/auth-client";
import { cn } from "@/lib/utils";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type User = typeof authClient.$Infer.Session.user;
export const ImpersonationBar = () => {
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const [users, setUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [isImpersonating, setIsImpersonating] = useState(false);

View File

@@ -23,8 +23,8 @@ import {
Loader2,
LogIn,
type LucideIcon,
Palette,
Package,
Palette,
PieChart,
Rocket,
Server,
@@ -895,10 +895,10 @@ export default function Page({ children }: Props) {
const pathname = usePathname();
const { data: auth } = api.user.get.useQuery();
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
const { data: whitelabeling } = api.whitelabeling.getPublic.useQuery(
undefined,
{ staleTime: 5 * 60 * 1000, refetchOnWindowFocus: false },
);
const { data: whitelabeling } = api.whitelabeling.get.useQuery(undefined, {
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
});
const includesProjects = pathname?.includes("/dashboard/project");
const { data: isCloud } = api.settings.isCloud.useQuery();

View File

@@ -37,6 +37,8 @@ const formSchema = z.object({
faviconUrl: z.string(),
customCss: z.string(),
loginLogoUrl: z.string(),
supportUrl: z.string(),
docsUrl: z.string(),
errorPageTitle: z.string(),
errorPageDescription: z.string(),
metaTitle: z.string(),
@@ -173,6 +175,8 @@ export function WhitelabelingSettings() {
faviconUrl: "",
customCss: "",
loginLogoUrl: "",
supportUrl: "",
docsUrl: "",
errorPageTitle: "",
errorPageDescription: "",
metaTitle: "",
@@ -190,6 +194,8 @@ export function WhitelabelingSettings() {
faviconUrl: data.faviconUrl ?? "",
customCss: data.customCss ?? "",
loginLogoUrl: data.loginLogoUrl ?? "",
supportUrl: data.supportUrl ?? "",
docsUrl: data.docsUrl ?? "",
errorPageTitle: data.errorPageTitle ?? "",
errorPageDescription: data.errorPageDescription ?? "",
metaTitle: data.metaTitle ?? "",
@@ -219,8 +225,8 @@ export function WhitelabelingSettings() {
primaryColor: null,
customCss: values.customCss || null,
loginLogoUrl: values.loginLogoUrl || null,
supportUrl: null,
docsUrl: null,
supportUrl: values.supportUrl || null,
docsUrl: values.docsUrl || null,
errorPageTitle: values.errorPageTitle || null,
errorPageDescription: values.errorPageDescription || null,
metaTitle: values.metaTitle || null,
@@ -231,6 +237,7 @@ export function WhitelabelingSettings() {
toast.success("Whitelabeling settings updated");
await refetch();
await utils.whitelabeling.getPublic.invalidate();
await utils.whitelabeling.get.invalidate();
})
.catch((error) => {
toast.error(
@@ -245,6 +252,7 @@ export function WhitelabelingSettings() {
toast.success("Whitelabeling settings reset to defaults");
await refetch();
await utils.whitelabeling.getPublic.invalidate();
await utils.whitelabeling.get.invalidate();
})
.catch((error) => {
toast.error(error?.message || "Failed to reset whitelabeling settings");
@@ -423,9 +431,9 @@ export function WhitelabelingSettings() {
{/* Metadata & Links Section */}
<Card className="bg-transparent">
<CardHeader>
<CardTitle>Metadata</CardTitle>
<CardTitle>Metadata & Links</CardTitle>
<CardDescription>
Customize the page title and footer text.
Customize the page title, footer text, and sidebar links.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
@@ -462,6 +470,46 @@ export function WhitelabelingSettings() {
</FormItem>
)}
/>
<FormField
control={form.control}
name="supportUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Support URL</FormLabel>
<FormControl>
<Input
placeholder="https://support.example.com"
{...field}
/>
</FormControl>
<FormDescription>
Custom URL for the "Support" link in the sidebar.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="docsUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Documentation URL</FormLabel>
<FormControl>
<Input
placeholder="https://docs.example.com"
{...field}
/>
</FormControl>
<FormDescription>
Custom URL for the "Documentation" link in the sidebar.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>

View File

@@ -98,7 +98,7 @@ import {
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
export type Services = {
serverId?: string | null;
@@ -371,7 +371,7 @@ const EnvironmentPage = (
{ projectId: selectedTargetProject },
{ enabled: !!selectedTargetProject },
);
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const emptyServices =

View File

@@ -56,7 +56,7 @@ import {
import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type TabState =
| "projects"
@@ -96,7 +96,7 @@ const Service = (
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.project?.projectId || "",
});
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const environmentDropdownItems =
environments?.map((env) => ({

View File

@@ -52,7 +52,7 @@ import {
import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type TabState =
| "projects"
@@ -85,7 +85,7 @@ const Service = (
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const environmentDropdownItems =
environments?.map((env) => ({

View File

@@ -45,7 +45,7 @@ import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
@@ -66,7 +66,7 @@ const Mariadb = (
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const environmentDropdownItems =
environments?.map((env) => ({

View File

@@ -45,7 +45,7 @@ import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
@@ -65,7 +65,7 @@ const Mongo = (
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const environmentDropdownItems =
environments?.map((env) => ({

View File

@@ -45,7 +45,7 @@ import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
@@ -64,7 +64,7 @@ const MySql = (
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const environmentDropdownItems =
environments?.map((env) => ({

View File

@@ -45,7 +45,7 @@ import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type TabState = "projects" | "monitoring" | "settings" | "backups" | "advanced";
@@ -64,7 +64,7 @@ const Postgresql = (
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const environmentDropdownItems =
environments?.map((env) => ({

View File

@@ -44,7 +44,7 @@ import { UseKeyboardNav } from "@/hooks/use-keyboard-nav";
import { cn } from "@/lib/utils";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { useWhitelabelingPublic } from "@/utils/hooks/use-whitelabeling";
import { useWhitelabeling } from "@/utils/hooks/use-whitelabeling";
type TabState = "projects" | "monitoring" | "settings" | "advanced";
@@ -64,7 +64,7 @@ const Redis = (
const { data: environments } = api.environment.byProjectId.useQuery({
projectId: data?.environment?.projectId || "",
});
const { config: whitelabeling } = useWhitelabelingPublic();
const { config: whitelabeling } = useWhitelabeling();
const appName = whitelabeling?.appName || "Dokploy";
const environmentDropdownItems =
environments?.map((env) => ({

View File

@@ -8,11 +8,12 @@ import { apiUpdateWhitelabeling } from "@/server/db/schema";
import {
createTRPCRouter,
enterpriseProcedure,
protectedProcedure,
publicProcedure,
} from "../../trpc";
export const whitelabelingRouter = createTRPCRouter({
get: enterpriseProcedure.query(async () => {
get: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return null;
}
@@ -80,13 +81,28 @@ export const whitelabelingRouter = createTRPCRouter({
return { success: true };
}),
// Public endpoint so the whitelabeling config can be applied globally
// (including on the login page before auth)
// Public endpoint only for unauthenticated pages (login, register, error)
// Returns only the fields needed for public pages
getPublic: publicProcedure.query(async () => {
if (IS_CLOUD) {
return null;
}
const settings = await getWebServerSettings();
return settings?.whitelabelingConfig ?? null;
const config = settings?.whitelabelingConfig;
if (!config) return null;
return {
appName: config.appName,
appDescription: config.appDescription,
logoUrl: config.logoUrl,
loginLogoUrl: config.loginLogoUrl,
faviconUrl: config.faviconUrl,
primaryColor: config.primaryColor,
customCss: config.customCss,
metaTitle: config.metaTitle,
errorPageTitle: config.errorPageTitle,
errorPageDescription: config.errorPageDescription,
footerText: config.footerText,
};
}),
});

View File

@@ -1,8 +1,20 @@
import { api } from "@/utils/api";
/**
* Hook to access whitelabeling config for authenticated pages (dashboard, services, etc.).
* Requires the user to be logged in.
*/
export function useWhitelabeling() {
const { data, ...rest } = api.whitelabeling.get.useQuery(undefined, {
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
});
return { config: data ?? null, ...rest };
}
/**
* Hook to access the public whitelabeling config.
* Can be used on any page (including login) since it uses the public endpoint.
* Only for unauthenticated pages (login, register, error, invitation, password reset).
*/
export function useWhitelabelingPublic() {
const { data, ...rest } = api.whitelabeling.getPublic.useQuery(undefined, {