diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index d0ae5700d..7d31180a9 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -564,15 +564,15 @@ }, "roles": { "management": { - "title": "Role Management", - "desc": "Manage roles and their camera access permissions for this Frigate instance." + "title": "Viewer Role Management", + "desc": "Manage custom viewer roles and their camera access permissions for this Frigate instance." }, "addRole": "Add Role", "table": { "role": "Role", "cameras": "Cameras", "actions": "Actions", - "noRoles": "No roles found.", + "noRoles": "No custom roles found.", "editCameras": "Edit Cameras", "deleteRole": "Delete Role" }, @@ -581,7 +581,7 @@ "createRole": "Role {{role}} created successfully", "updateCameras": "Cameras updated for role {{role}}", "deleteRole": "Role {{role}} deleted successfully", - "userRolesUpdated": "{{count}} user(s) assigned to this role have been updated to 'viewer'." + "userRolesUpdated": "{{count}} user(s) assigned to this role have been updated to 'viewer', which has access to all cameras." }, "error": { "createRoleFailed": "Failed to create role: {{errorMessage}}", @@ -601,7 +601,7 @@ }, "deleteRole": { "title": "Delete Role", - "desc": "This action cannot be undone. This will permanently delete the role and assign any users with this role to the 'viewer' role.", + "desc": "This action cannot be undone. This will permanently delete the role and assign any users with this role to the 'viewer' role, which will give viewer access to all cameras.", "warn": "Are you sure you want to delete {{role}}?", "deleting": "Deleting..." }, diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index edbd304d8..53b20eab8 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -33,7 +33,8 @@ import CameraSettingsView from "@/views/settings/CameraSettingsView"; import ObjectSettingsView from "@/views/settings/ObjectSettingsView"; import MotionTunerView from "@/views/settings/MotionTunerView"; import MasksAndZonesView from "@/views/settings/MasksAndZonesView"; -import AuthenticationView from "@/views/settings/AuthenticationView"; +import UsersView from "@/views/settings/UsersView"; +import RolesView from "@/views/settings/RolesView"; import NotificationView from "@/views/settings/NotificationsSettingsView"; import EnrichmentsSettingsView from "@/views/settings/EnrichmentsSettingsView"; import UiSettingsView from "@/views/settings/UiSettingsView"; @@ -57,6 +58,7 @@ const allSettingsViews = [ "triggers", "debug", "users", + "roles", "notifications", "frigateplus", ] as const; @@ -288,7 +290,8 @@ export default function Settings() { setUnsavedChanges={setUnsavedChanges} /> )} - {page == "users" && } + {page == "users" && } + {page == "roles" && } {page == "notifications" && ( )} diff --git a/web/src/views/settings/AuthenticationView.tsx b/web/src/views/settings/AuthenticationView.tsx index 9c9920b69..6b60f1256 100644 --- a/web/src/views/settings/AuthenticationView.tsx +++ b/web/src/views/settings/AuthenticationView.tsx @@ -38,7 +38,13 @@ import DeleteRoleDialog from "@/components/overlay/DeleteRoleDialog"; import { Separator } from "@/components/ui/separator"; import { CameraNameLabel } from "@/components/camera/CameraNameLabel"; -export default function AuthenticationView() { +type AuthenticationViewProps = { + section?: "users" | "roles"; +}; + +export default function AuthenticationView({ + section, +}: AuthenticationViewProps) { const { t } = useTranslation("views/settings"); const { data: config, mutate: updateConfig } = useSWR("config"); @@ -377,10 +383,12 @@ export default function AuthenticationView() { const roles = useMemo(() => { return config?.auth?.roles - ? Object.entries(config.auth.roles).map(([name, data]) => ({ - name, - cameras: Array.isArray(data) ? data : [], - })) + ? Object.entries(config.auth.roles) + .filter(([name]) => name !== "admin") + .map(([name, data]) => ({ + name, + cameras: Array.isArray(data) ? data : [], + })) : []; }, [config]); @@ -396,319 +404,166 @@ export default function AuthenticationView() { ); } - return ( -
- -
-
-
- - {t("users.management.title")} - -

- {t("users.management.desc")} -

-
- + // Users section + const UsersSection = ( + <> +
+
+ + {t("users.management.title")} + +

+ {t("users.management.desc")} +

-
-
-
- - + + +
+
+
+
+ + + + {t("users.table.username")} + + {t("users.table.role")} + + {t("users.table.actions")} + + + + + {users.length === 0 ? ( - - {t("users.table.username")} - - {t("users.table.role")} - - {t("users.table.actions")} - + + {t("users.table.noUsers")} + - - - {users.length === 0 ? ( - - - {t("users.table.noUsers")} + ) : ( + users.map((user) => ( + + +
+ {user.username === "admin" ? ( + + ) : ( + + )} + {user.username} +
-
- ) : ( - users.map((user) => ( - - -
- {user.username === "admin" ? ( - - ) : ( - - )} - {user.username} -
-
- - - {t("role." + (user.role || "viewer"), { - ns: "common", - })} - - - - -
- {user.username !== "admin" && - user.username !== "viewer" && ( - - - - - -

{t("users.table.changeRole")}

-
-
- )} - - - - - - -

{t("users.updatePassword")}

-
-
- - {user.username !== "admin" && ( + + + {t("role." + (user.role || "viewer"), { + ns: "common", + })} + + + + +
+ {user.username !== "admin" && + user.username !== "viewer" && ( -

{t("users.table.deleteUser")}

+

{t("users.table.changeRole")}

)} -
-
-
- - )) - )} - -
-
-
-
- + + + + + +

{t("users.updatePassword")}

+
+
-
-
- - {t("roles.management.title")} - -

- {t("roles.management.desc")} -

-
- -
-
-
-
- - - - - {t("roles.table.role")} - - {t("roles.table.cameras")} - - {t("roles.table.actions")} - - - - - {roles.length === 0 ? ( - - - {t("roles.table.noRoles")} + {user.username !== "admin" && ( + + + + + +

{t("users.table.deleteUser")}

+
+
+ )} + +
- ) : ( - roles.map((roleData) => ( - - - {roleData.name} - - - {roleData.cameras.length === 0 ? ( - - {t("menu.live.allCameras", { ns: "common" })} - - ) : roleData.cameras.length > 5 ? ( - - {roleData.cameras.length} cameras - - ) : ( -
- {roleData.cameras.map((camera) => ( - - - - ))} -
- )} -
- - -
- {roleData.name !== "admin" && - roleData.name !== "viewer" && ( - <> - - - - - -

{t("roles.table.editCameras")}

-
-
- - - - - - -

{t("roles.table.deleteRole")}

-
-
- - )} -
-
-
-
- )) - )} -
-
-
+ )) + )} + +
- setShowSetPassword(false)} @@ -735,6 +590,159 @@ export default function AuthenticationView() { onCancel={() => setShowRoleChange(false)} /> )} + + ); + + // Roles section + const RolesSection = ( + <> +
+
+ + {t("roles.management.title")} + +

+ {t("roles.management.desc")} +

+
+ +
+
+
+
+ + + + + {t("roles.table.role")} + + {t("roles.table.cameras")} + + {t("roles.table.actions")} + + + + + {roles.length === 0 ? ( + + + {t("roles.table.noRoles")} + + + ) : ( + roles.map((roleData) => ( + + + {roleData.name} + + + {roleData.cameras.length === 0 ? ( + + {t("menu.live.allCameras", { ns: "common" })} + + ) : roleData.cameras.length > 5 ? ( + + {roleData.cameras.length} cameras + + ) : ( +
+ {roleData.cameras.map((camera) => ( + + + + ))} +
+ )} +
+ + +
+ {roleData.name !== "admin" && + roleData.name !== "viewer" && ( + <> + + + + + +

{t("roles.table.editCameras")}

+
+
+ + + + + + +

{t("roles.table.deleteRole")}

+
+
+ + )} +
+
+
+
+ )) + )} +
+
+
+
+
+ + ); + + return ( +
+ +
+ {section === "users" && UsersSection} + {section === "roles" && RolesSection} + {!section && ( + <> + {UsersSection} + + {RolesSection} + + )} +
); } diff --git a/web/src/views/settings/RolesView.tsx b/web/src/views/settings/RolesView.tsx new file mode 100644 index 000000000..5afbf2eb0 --- /dev/null +++ b/web/src/views/settings/RolesView.tsx @@ -0,0 +1,5 @@ +import AuthenticationView from "./AuthenticationView"; + +export default function RolesView() { + return ; +} diff --git a/web/src/views/settings/UsersView.tsx b/web/src/views/settings/UsersView.tsx new file mode 100644 index 000000000..a30d7356f --- /dev/null +++ b/web/src/views/settings/UsersView.tsx @@ -0,0 +1,5 @@ +import AuthenticationView from "./AuthenticationView"; + +export default function UsersView() { + return ; +}