mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-04-23 13:08:11 +00:00
[FEAT] Custom login screen icon + custom app name (#1500)
* implement custom icon on login screen for single & multi user + custom app name feature * hide field when not relevant * set customApp name * show original anythingllm login logo unless custom logo is set * nit-picks * remove console log --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
parent
a89812703b
commit
6a2d7aca28
12 changed files with 225 additions and 25 deletions
frontend/src
server
|
@ -1,27 +1,41 @@
|
||||||
import { createContext, useEffect, useState } from "react";
|
import { createContext, useEffect, useState } from "react";
|
||||||
import AnythingLLM from "./media/logo/anything-llm.png";
|
import AnythingLLM from "./media/logo/anything-llm.png";
|
||||||
|
import DefaultLoginLogo from "./media/illustrations/login-logo.svg";
|
||||||
import System from "./models/system";
|
import System from "./models/system";
|
||||||
|
|
||||||
export const LogoContext = createContext();
|
export const LogoContext = createContext();
|
||||||
|
|
||||||
export function LogoProvider({ children }) {
|
export function LogoProvider({ children }) {
|
||||||
const [logo, setLogo] = useState("");
|
const [logo, setLogo] = useState("");
|
||||||
|
const [loginLogo, setLoginLogo] = useState("");
|
||||||
|
const [isCustomLogo, setIsCustomLogo] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchInstanceLogo() {
|
async function fetchInstanceLogo() {
|
||||||
try {
|
try {
|
||||||
const logoURL = await System.fetchLogo();
|
const { isCustomLogo, logoURL } = await System.fetchLogo();
|
||||||
logoURL ? setLogo(logoURL) : setLogo(AnythingLLM);
|
if (logoURL) {
|
||||||
|
setLogo(logoURL);
|
||||||
|
setLoginLogo(isCustomLogo ? logoURL : DefaultLoginLogo);
|
||||||
|
setIsCustomLogo(isCustomLogo);
|
||||||
|
} else {
|
||||||
|
setLogo(AnythingLLM);
|
||||||
|
setLoginLogo(DefaultLoginLogo);
|
||||||
|
setIsCustomLogo(false);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLogo(AnythingLLM);
|
setLogo(AnythingLLM);
|
||||||
|
setLoginLogo(DefaultLoginLogo);
|
||||||
|
setIsCustomLogo(false);
|
||||||
console.error("Failed to fetch logo:", err);
|
console.error("Failed to fetch logo:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchInstanceLogo();
|
fetchInstanceLogo();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogoContext.Provider value={{ logo, setLogo }}>
|
<LogoContext.Provider value={{ logo, setLogo, loginLogo, isCustomLogo }}>
|
||||||
{children}
|
{children}
|
||||||
</LogoContext.Provider>
|
</LogoContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -168,6 +168,7 @@ export default function MultiUserAuth() {
|
||||||
const [token, setToken] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
const [showRecoveryForm, setShowRecoveryForm] = useState(false);
|
const [showRecoveryForm, setShowRecoveryForm] = useState(false);
|
||||||
const [showResetPasswordForm, setShowResetPasswordForm] = useState(false);
|
const [showResetPasswordForm, setShowResetPasswordForm] = useState(false);
|
||||||
|
const [customAppName, setCustomAppName] = useState(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isRecoveryCodeModalOpen,
|
isOpen: isRecoveryCodeModalOpen,
|
||||||
|
@ -250,6 +251,15 @@ export default function MultiUserAuth() {
|
||||||
}
|
}
|
||||||
}, [downloadComplete, user, token]);
|
}, [downloadComplete, user, token]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCustomAppName = async () => {
|
||||||
|
const { appName } = await System.fetchCustomAppName();
|
||||||
|
setCustomAppName(appName || "");
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
fetchCustomAppName();
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (showRecoveryForm) {
|
if (showRecoveryForm) {
|
||||||
return (
|
return (
|
||||||
<RecoveryForm
|
<RecoveryForm
|
||||||
|
@ -272,11 +282,11 @@ export default function MultiUserAuth() {
|
||||||
Welcome to
|
Welcome to
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
|
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
|
||||||
AnythingLLM
|
{customAppName || "AnythingLLM"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-white/90 text-center">
|
<p className="text-sm text-white/90 text-center">
|
||||||
Sign in to your AnythingLLM account.
|
Sign in to your {customAppName || "AnythingLLM"} account.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import System from "../../../models/system";
|
import System from "../../../models/system";
|
||||||
import { AUTH_TOKEN } from "../../../utils/constants";
|
import { AUTH_TOKEN } from "../../../utils/constants";
|
||||||
import useLogo from "../../../hooks/useLogo";
|
|
||||||
import paths from "../../../utils/paths";
|
import paths from "../../../utils/paths";
|
||||||
import ModalWrapper from "@/components/ModalWrapper";
|
import ModalWrapper from "@/components/ModalWrapper";
|
||||||
import { useModal } from "@/hooks/useModal";
|
import { useModal } from "@/hooks/useModal";
|
||||||
|
@ -10,10 +9,10 @@ import RecoveryCodeModal from "@/components/Modals/DisplayRecoveryCodeModal";
|
||||||
export default function SingleUserAuth() {
|
export default function SingleUserAuth() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const { logo: _initLogo } = useLogo();
|
|
||||||
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
const [recoveryCodes, setRecoveryCodes] = useState([]);
|
||||||
const [downloadComplete, setDownloadComplete] = useState(false);
|
const [downloadComplete, setDownloadComplete] = useState(false);
|
||||||
const [token, setToken] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
|
const [customAppName, setCustomAppName] = useState(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isRecoveryCodeModalOpen,
|
isOpen: isRecoveryCodeModalOpen,
|
||||||
|
@ -57,6 +56,15 @@ export default function SingleUserAuth() {
|
||||||
}
|
}
|
||||||
}, [downloadComplete, token]);
|
}, [downloadComplete, token]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCustomAppName = async () => {
|
||||||
|
const { appName } = await System.fetchCustomAppName();
|
||||||
|
setCustomAppName(appName || "");
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
fetchCustomAppName();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={handleLogin}>
|
<form onSubmit={handleLogin}>
|
||||||
|
@ -68,11 +76,11 @@ export default function SingleUserAuth() {
|
||||||
Welcome to
|
Welcome to
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
|
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] to-[#FFFFFF] bg-clip-text text-transparent">
|
||||||
AnythingLLM
|
{customAppName || "AnythingLLM"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-white/90 text-center">
|
<p className="text-sm text-white/90 text-center">
|
||||||
Sign in to your AnythingLLM instance.
|
Sign in to your {customAppName || "AnythingLLM"} instance.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,10 +9,9 @@ import {
|
||||||
} from "../../../utils/constants";
|
} from "../../../utils/constants";
|
||||||
import useLogo from "../../../hooks/useLogo";
|
import useLogo from "../../../hooks/useLogo";
|
||||||
import illustration from "@/media/illustrations/login-illustration.svg";
|
import illustration from "@/media/illustrations/login-illustration.svg";
|
||||||
import loginLogo from "@/media/illustrations/login-logo.svg";
|
|
||||||
|
|
||||||
export default function PasswordModal({ mode = "single" }) {
|
export default function PasswordModal({ mode = "single" }) {
|
||||||
const { logo: _initLogo } = useLogo();
|
const { loginLogo } = useLogo();
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 right-0 z-50 w-full overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] h-full bg-[#25272C] flex flex-col md:flex-row items-center justify-center">
|
<div className="fixed top-0 left-0 right-0 z-50 w-full overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] h-full bg-[#25272C] flex flex-col md:flex-row items-center justify-center">
|
||||||
<div
|
<div
|
||||||
|
@ -37,10 +36,11 @@ export default function PasswordModal({ mode = "single" }) {
|
||||||
<div className="flex flex-col items-center justify-center h-full w-full md:w-1/2 z-50 relative">
|
<div className="flex flex-col items-center justify-center h-full w-full md:w-1/2 z-50 relative">
|
||||||
<img
|
<img
|
||||||
src={loginLogo}
|
src={loginLogo}
|
||||||
className={`mb-8 w-[84px] h-[84px] absolute ${
|
alt="Logo"
|
||||||
mode === "single" ? "md:top-50" : "md:top-36"
|
className={`hidden md:flex rounded-2xl w-fit m-4 z-30 ${
|
||||||
} top-44 z-30`}
|
mode === "single" ? "md:top-[170px]" : "md:top-36"
|
||||||
alt="logo"
|
} absolute max-h-[65px] md:bg-login-gradient md:shadow-[0_4px_14px_rgba(0,0,0,0.25)]`}
|
||||||
|
style={{ objectFit: "contain" }}
|
||||||
/>
|
/>
|
||||||
{mode === "single" ? <SingleUserAuth /> : <MultiUserAuth />}
|
{mode === "single" ? <SingleUserAuth /> : <MultiUserAuth />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,6 +2,6 @@ import { useContext } from "react";
|
||||||
import { LogoContext } from "../LogoContext";
|
import { LogoContext } from "../LogoContext";
|
||||||
|
|
||||||
export default function useLogo() {
|
export default function useLogo() {
|
||||||
const { logo, setLogo } = useContext(LogoContext);
|
const { logo, setLogo, loginLogo, isCustomLogo } = useContext(LogoContext);
|
||||||
return { logo, setLogo };
|
return { logo, setLogo, loginLogo, isCustomLogo };
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ const System = {
|
||||||
cacheKeys: {
|
cacheKeys: {
|
||||||
footerIcons: "anythingllm_footer_links",
|
footerIcons: "anythingllm_footer_links",
|
||||||
supportEmail: "anythingllm_support_email",
|
supportEmail: "anythingllm_support_email",
|
||||||
|
customAppName: "anythingllm_custom_app_name",
|
||||||
},
|
},
|
||||||
ping: async function () {
|
ping: async function () {
|
||||||
return await fetch(`${API_BASE}/ping`)
|
return await fetch(`${API_BASE}/ping`)
|
||||||
|
@ -305,19 +306,58 @@ const System = {
|
||||||
);
|
);
|
||||||
return { email: supportEmail, error: null };
|
return { email: supportEmail, error: null };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchCustomAppName: async function () {
|
||||||
|
const cache = window.localStorage.getItem(this.cacheKeys.customAppName);
|
||||||
|
const { appName, lastFetched } = cache
|
||||||
|
? safeJsonParse(cache, { appName: "", lastFetched: 0 })
|
||||||
|
: { appName: "", lastFetched: 0 };
|
||||||
|
|
||||||
|
if (!!appName && Date.now() - lastFetched < 3_600_000)
|
||||||
|
return { appName: appName, error: null };
|
||||||
|
|
||||||
|
const { customAppName, error } = await fetch(
|
||||||
|
`${API_BASE}/system/custom-app-name`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
cache: "no-cache",
|
||||||
|
headers: baseHeaders(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
return { customAppName: "", error: e.message };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!customAppName || !!error) {
|
||||||
|
window.localStorage.removeItem(this.cacheKeys.customAppName);
|
||||||
|
return { appName: "", error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
window.localStorage.setItem(
|
||||||
|
this.cacheKeys.customAppName,
|
||||||
|
JSON.stringify({ appName: customAppName, lastFetched: Date.now() })
|
||||||
|
);
|
||||||
|
return { appName: customAppName, error: null };
|
||||||
|
},
|
||||||
fetchLogo: async function () {
|
fetchLogo: async function () {
|
||||||
return await fetch(`${API_BASE}/system/logo`, {
|
return await fetch(`${API_BASE}/system/logo`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
cache: "no-cache",
|
cache: "no-cache",
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
if (res.ok && res.status !== 204) return res.blob();
|
if (res.ok && res.status !== 204) {
|
||||||
|
const isCustomLogo = res.headers.get("X-Is-Custom-Logo") === "true";
|
||||||
|
const blob = await res.blob();
|
||||||
|
const logoURL = URL.createObjectURL(blob);
|
||||||
|
return { isCustomLogo, logoURL };
|
||||||
|
}
|
||||||
throw new Error("Failed to fetch logo!");
|
throw new Error("Failed to fetch logo!");
|
||||||
})
|
})
|
||||||
.then((blob) => URL.createObjectURL(blob))
|
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
return null;
|
return { isCustomLogo: false, logoURL: null };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchPfp: async function (id) {
|
fetchPfp: async function (id) {
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import Admin from "@/models/admin";
|
||||||
|
import System from "@/models/system";
|
||||||
|
import showToast from "@/utils/toast";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export default function CustomAppName() {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
|
const [customAppName, setCustomAppName] = useState("");
|
||||||
|
const [originalAppName, setOriginalAppName] = useState("");
|
||||||
|
const [canCustomize, setCanCustomize] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchInitialParams = async () => {
|
||||||
|
const settings = await System.keys();
|
||||||
|
if (!settings?.MultiUserMode && !settings?.RequiresAuth) {
|
||||||
|
setCanCustomize(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { appName } = await System.fetchCustomAppName();
|
||||||
|
setCustomAppName(appName || "");
|
||||||
|
setOriginalAppName(appName || "");
|
||||||
|
setCanCustomize(true);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
fetchInitialParams();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateCustomAppName = async (e, newValue = null) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let custom_app_name = newValue;
|
||||||
|
if (newValue === null) {
|
||||||
|
const form = new FormData(e.target);
|
||||||
|
custom_app_name = form.get("customAppName");
|
||||||
|
}
|
||||||
|
const { success, error } = await Admin.updateSystemPreferences({
|
||||||
|
custom_app_name,
|
||||||
|
});
|
||||||
|
if (!success) {
|
||||||
|
showToast(`Failed to update custom app name: ${error}`, "error");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
showToast("Successfully updated custom app name.", "success");
|
||||||
|
window.localStorage.removeItem(System.cacheKeys.customAppName);
|
||||||
|
setCustomAppName(custom_app_name);
|
||||||
|
setOriginalAppName(custom_app_name);
|
||||||
|
setHasChanges(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setCustomAppName(e.target.value);
|
||||||
|
setHasChanges(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!canCustomize || loading) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="mb-6" onSubmit={updateCustomAppName}>
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<h2 className="text-base leading-6 font-bold text-white">
|
||||||
|
Custom App Name
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs leading-[18px] font-base text-white/60">
|
||||||
|
Set a custom app name that is displayed on the login page.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
<input
|
||||||
|
name="customAppName"
|
||||||
|
type="text"
|
||||||
|
className="bg-zinc-900 mt-3 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 max-w-[275px] placeholder:text-white/20"
|
||||||
|
placeholder="AnythingLLM"
|
||||||
|
required={true}
|
||||||
|
autoComplete="off"
|
||||||
|
onChange={handleChange}
|
||||||
|
value={customAppName}
|
||||||
|
/>
|
||||||
|
{originalAppName !== "" && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => updateCustomAppName(e, "")}
|
||||||
|
className="mt-4 text-white text-base font-medium hover:text-opacity-60"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{hasChanges && (
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="transition-all mt-6 w-fit duration-300 border border-slate-200 px-5 py-2.5 rounded-lg text-white text-sm items-center flex gap-x-2 hover:bg-slate-200 hover:text-slate-800 focus:ring-gray-800"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ import useLogo from "@/hooks/useLogo";
|
||||||
import System from "@/models/system";
|
import System from "@/models/system";
|
||||||
import showToast from "@/utils/toast";
|
import showToast from "@/utils/toast";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import AnythingLLM from "@/media/logo/anything-llm.png";
|
|
||||||
import { Plus } from "@phosphor-icons/react";
|
import { Plus } from "@phosphor-icons/react";
|
||||||
|
|
||||||
export default function CustomLogo() {
|
export default function CustomLogo() {
|
||||||
|
@ -36,7 +35,7 @@ export default function CustomLogo() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoURL = await System.fetchLogo();
|
const { logoURL } = await System.fetchLogo();
|
||||||
_setLogo(logoURL);
|
_setLogo(logoURL);
|
||||||
|
|
||||||
showToast("Image uploaded successfully.", "success");
|
showToast("Image uploaded successfully.", "success");
|
||||||
|
@ -51,13 +50,13 @@ export default function CustomLogo() {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
console.error("Failed to remove logo:", error);
|
console.error("Failed to remove logo:", error);
|
||||||
showToast(`Failed to remove logo: ${error}`, "error");
|
showToast(`Failed to remove logo: ${error}`, "error");
|
||||||
const logoURL = await System.fetchLogo();
|
const { logoURL } = await System.fetchLogo();
|
||||||
setLogo(logoURL);
|
setLogo(logoURL);
|
||||||
setIsDefaultLogo(false);
|
setIsDefaultLogo(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoURL = await System.fetchLogo();
|
const { logoURL } = await System.fetchLogo();
|
||||||
_setLogo(logoURL);
|
_setLogo(logoURL);
|
||||||
|
|
||||||
showToast("Image successfully removed.", "success");
|
showToast("Image successfully removed.", "success");
|
||||||
|
|
|
@ -4,6 +4,7 @@ import FooterCustomization from "./FooterCustomization";
|
||||||
import SupportEmail from "./SupportEmail";
|
import SupportEmail from "./SupportEmail";
|
||||||
import CustomLogo from "./CustomLogo";
|
import CustomLogo from "./CustomLogo";
|
||||||
import CustomMessages from "./CustomMessages";
|
import CustomMessages from "./CustomMessages";
|
||||||
|
import CustomAppName from "./CustomAppName";
|
||||||
|
|
||||||
export default function Appearance() {
|
export default function Appearance() {
|
||||||
return (
|
return (
|
||||||
|
@ -25,6 +26,7 @@ export default function Appearance() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<CustomLogo />
|
<CustomLogo />
|
||||||
|
<CustomAppName />
|
||||||
<CustomMessages />
|
<CustomMessages />
|
||||||
<FooterCustomization />
|
<FooterCustomization />
|
||||||
<SupportEmail />
|
<SupportEmail />
|
||||||
|
|
|
@ -355,6 +355,9 @@ function adminEndpoints(app) {
|
||||||
?.value,
|
?.value,
|
||||||
[]
|
[]
|
||||||
) || [],
|
) || [],
|
||||||
|
custom_app_name:
|
||||||
|
(await SystemSettings.get({ label: "custom_app_name" }))?.value ||
|
||||||
|
null,
|
||||||
};
|
};
|
||||||
response.status(200).json({ settings });
|
response.status(200).json({ settings });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -526,17 +526,24 @@ function systemEndpoints(app) {
|
||||||
const defaultFilename = getDefaultFilename();
|
const defaultFilename = getDefaultFilename();
|
||||||
const logoPath = await determineLogoFilepath(defaultFilename);
|
const logoPath = await determineLogoFilepath(defaultFilename);
|
||||||
const { found, buffer, size, mime } = fetchLogo(logoPath);
|
const { found, buffer, size, mime } = fetchLogo(logoPath);
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
response.sendStatus(204).end();
|
response.sendStatus(204).end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
|
"Access-Control-Expose-Headers":
|
||||||
|
"Content-Disposition,X-Is-Custom-Logo,Content-Type,Content-Length",
|
||||||
"Content-Type": mime || "image/png",
|
"Content-Type": mime || "image/png",
|
||||||
"Content-Disposition": `attachment; filename=${path.basename(
|
"Content-Disposition": `attachment; filename=${path.basename(
|
||||||
logoPath
|
logoPath
|
||||||
)}`,
|
)}`,
|
||||||
"Content-Length": size,
|
"Content-Length": size,
|
||||||
|
"X-Is-Custom-Logo":
|
||||||
|
currentLogoFilename !== null &&
|
||||||
|
currentLogoFilename !== defaultFilename,
|
||||||
});
|
});
|
||||||
response.end(Buffer.from(buffer, "base64"));
|
response.end(Buffer.from(buffer, "base64"));
|
||||||
return;
|
return;
|
||||||
|
@ -573,6 +580,22 @@ function systemEndpoints(app) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// No middleware protection in order to get this on the login page
|
||||||
|
app.get("/system/custom-app-name", async (_, response) => {
|
||||||
|
try {
|
||||||
|
const customAppName =
|
||||||
|
(
|
||||||
|
await SystemSettings.get({
|
||||||
|
label: "custom_app_name",
|
||||||
|
})
|
||||||
|
)?.value ?? null;
|
||||||
|
response.status(200).json({ customAppName: customAppName });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching custom app name:", error);
|
||||||
|
response.status(500).json({ message: "Internal server error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
"/system/pfp/:id",
|
"/system/pfp/:id",
|
||||||
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
||||||
|
|
|
@ -27,6 +27,7 @@ const SystemSettings = {
|
||||||
"agent_search_provider",
|
"agent_search_provider",
|
||||||
"default_agent_skills",
|
"default_agent_skills",
|
||||||
"agent_sql_connections",
|
"agent_sql_connections",
|
||||||
|
"custom_app_name",
|
||||||
],
|
],
|
||||||
validations: {
|
validations: {
|
||||||
footer_data: (updates) => {
|
footer_data: (updates) => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue