diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json index 37566117a..8becd0c7f 100644 --- a/web/public/locales/en/common.json +++ b/web/public/locales/en/common.json @@ -168,6 +168,7 @@ "systemMetrics": "System metrics", "configuration": "Configuration", "systemLogs": "System logs", + "profiles": "Profiles", "settings": "Settings", "configurationEditor": "Configuration Editor", "languages": "Languages", diff --git a/web/src/components/menu/GeneralSettings.tsx b/web/src/components/menu/GeneralSettings.tsx index 245ee8a72..7353a3035 100644 --- a/web/src/components/menu/GeneralSettings.tsx +++ b/web/src/components/menu/GeneralSettings.tsx @@ -2,6 +2,7 @@ import { LuActivity, LuGithub, LuLanguages, + LuLayers, LuLifeBuoy, LuList, LuLogOut, @@ -69,6 +70,9 @@ import SetPasswordDialog from "../overlay/SetPasswordDialog"; import { toast } from "sonner"; import axios from "axios"; import { FrigateConfig } from "@/types/frigateConfig"; +import type { ProfilesApiResponse } from "@/types/profile"; +import { getProfileColor } from "@/utils/profileColors"; +import { Badge } from "@/components/ui/badge"; import { useTranslation } from "react-i18next"; import { supportedLanguageKeys } from "@/lib/const"; @@ -84,6 +88,8 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { const { getLocaleDocUrl } = useDocDomain(); const { data: profile } = useSWR("profile"); const { data: config } = useSWR("config"); + const { data: profilesData, mutate: updateProfiles } = + useSWR("profiles"); const logoutUrl = config?.proxy?.logout_url || "/api/logout"; // languages @@ -105,6 +111,41 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { }); }, [t]); + // profiles + + const allProfileNames = useMemo( + () => profilesData?.profiles?.map((p) => p.name) ?? [], + [profilesData], + ); + + const profileFriendlyNames = useMemo(() => { + const map = new Map(); + profilesData?.profiles?.forEach((p) => map.set(p.name, p.friendly_name)); + return map; + }, [profilesData]); + + const hasProfiles = allProfileNames.length > 0; + + const handleActivateProfile = async (profileName: string | null) => { + try { + await axios.put("profile/set", { profile: profileName || null }); + await updateProfiles(); + toast.success( + profileName + ? t("profiles.activated", { + ns: "views/settings", + profile: profileFriendlyNames.get(profileName) ?? profileName, + }) + : t("profiles.deactivated", { ns: "views/settings" }), + { position: "top-center" }, + ); + } catch { + toast.error(t("profiles.activateFailed", { ns: "views/settings" }), { + position: "top-center", + }); + } + }; + // settings const { language, setLanguage } = useLanguage(); @@ -285,6 +326,118 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { {t("menu.systemLogs")} + {hasProfiles && ( + + + + {t("menu.profiles")} + + + + {!isDesktop && ( + <> + + {t("menu.profiles")} + + + {t("menu.profiles")} + + + )} + + handleActivateProfile(null)} + > +
+ + {t("profiles.baseConfig", { + ns: "views/settings", + })} + + {!profilesData?.active_profile && ( + + {t("profiles.active", { + ns: "views/settings", + })} + + )} +
+
+ {allProfileNames.map((profileName) => { + const color = getProfileColor( + profileName, + allProfileNames, + ); + const isActive = + profilesData?.active_profile === profileName; + return ( + + handleActivateProfile(profileName) + } + > +
+
+ + + {profileFriendlyNames.get(profileName) ?? + profileName} + +
+ {isActive && ( + + {t("profiles.active", { + ns: "views/settings", + })} + + )} +
+
+ ); + })} +
+
+
+ )} )} diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index e20037388..789a5ce0c 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -99,7 +99,6 @@ import { import type { ProfileState, ProfilesApiResponse } from "@/types/profile"; import { getProfileColor } from "@/utils/profileColors"; import { ProfileSectionDropdown } from "@/components/settings/ProfileSectionDropdown"; -import { Badge } from "@/components/ui/badge"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import RestartDialog from "@/components/overlay/dialog/RestartDialog"; import SaveAllPreviewPopover, { @@ -1524,24 +1523,6 @@ export default function Settings() {

{t("menu.settings", { ns: "common" })}

- {profilesData?.active_profile && ( - { - setPage("profiles"); - setContentMobileOpen(true); - }} - > - {profileFriendlyNames.get(profilesData.active_profile) ?? - profilesData.active_profile} - - )} @@ -1750,18 +1731,6 @@ export default function Settings() { {t("menu.settings", { ns: "common" })} - {profilesData?.active_profile && ( - setPage("profiles")} - > - {profilesData.active_profile} - - )}
{hasPendingChanges && (