Add setting to change dashboard view for regular users (#362)

This commit is contained in:
Eduard Gert
2024-03-27 16:09:58 +01:00
committed by GitHub
parent f4a2d6fae8
commit 973cceff79
11 changed files with 159 additions and 11 deletions

View File

@@ -17,11 +17,15 @@ import { SetupModalContent } from "@/modules/setup-netbird-modal/SetupModal";
const PeersTable = lazy(() => import("@/modules/peers/PeersTable"));
export default function Peers() {
const { isUser } = useLoggedInUser();
const { permission } = useLoggedInUser();
return (
<PageContainer>
<PeersView />
{permission?.dashboard_view === "blocked" ? (
<PeersDefaultView />
) : (
<PeersView />
)}
</PageContainer>
);
}

View File

@@ -2,7 +2,12 @@
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { VerticalTabs } from "@components/VerticalTabs";
import { AlertOctagonIcon, FolderGit2Icon, ShieldIcon } from "lucide-react";
import {
AlertOctagonIcon,
FolderGit2Icon,
LockIcon,
ShieldIcon,
} from "lucide-react";
import React, { useState } from "react";
import { useLoggedInUser } from "@/contexts/UsersProvider";
import PageContainer from "@/layouts/PageContainer";
@@ -10,6 +15,7 @@ import { useAccount } from "@/modules/account/useAccount";
import AuthenticationTab from "@/modules/settings/AuthenticationTab";
import DangerZoneTab from "@/modules/settings/DangerZoneTab";
import GroupsTab from "@/modules/settings/GroupsTab";
import PermissionsTab from "@/modules/settings/PermissionsTab";
export default function NetBirdSettings() {
const [tab, setTab] = useState("authentication");
@@ -28,6 +34,10 @@ export default function NetBirdSettings() {
<FolderGit2Icon size={14} />
Groups
</VerticalTabs.Trigger>
<VerticalTabs.Trigger value="permissions">
<LockIcon size={14} />
Permissions
</VerticalTabs.Trigger>
<VerticalTabs.Trigger value="danger-zone" disabled={!isOwner}>
<AlertOctagonIcon size={14} />
Danger zone
@@ -36,6 +46,7 @@ export default function NetBirdSettings() {
<RestrictedAccess page={"Settings"}>
<div className={"border-l border-nb-gray-930 w-full"}>
{account && <AuthenticationTab account={account} />}
{account && <PermissionsTab account={account} />}
{account && <GroupsTab account={account} />}
{account && <DangerZoneTab account={account} />}
</div>

View File

@@ -20,9 +20,13 @@ const GroupContext = React.createContext(
export default function GroupsProvider({ children }: Props) {
const path = usePathname();
const { isUser } = useLoggedInUser();
const { permission } = useLoggedInUser();
return <GroupsProviderContent>{children}</GroupsProviderContent>;
return path === "/peers" && permission.dashboard_view == "blocked" ? (
<>{children}</>
) : (
<GroupsProviderContent>{children}</GroupsProviderContent>
);
}
export function GroupsProviderContent({ children }: Props) {

View File

@@ -1,6 +1,7 @@
import FullScreenLoading from "@components/ui/FullScreenLoading";
import useFetchApi from "@utils/api";
import React, { useMemo } from "react";
import { Permission } from "@/interfaces/Permission";
import { User } from "@/interfaces/User";
type Props = {
@@ -43,5 +44,19 @@ export const useLoggedInUser = () => {
const isAdmin = loggedInUser ? loggedInUser?.role === "admin" : false;
const isUser = !isOwner && !isAdmin;
const isOwnerOrAdmin = isOwner || isAdmin;
return { loggedInUser, isOwner, isAdmin, isUser, isOwnerOrAdmin } as const;
const permission = useMemo(() => {
return {
dashboard_view: loggedInUser?.permissions.dashboard_view || "blocked",
} as Permission;
}, [loggedInUser]);
return {
loggedInUser,
isOwner,
isAdmin,
isUser,
isOwnerOrAdmin,
permission,
} as const;
};

View File

@@ -10,5 +10,6 @@ export interface Account {
jwt_groups_enabled: boolean;
jwt_groups_claim_name: string;
jwt_allow_groups: string[];
regular_users_view_blocked: boolean;
};
}

View File

@@ -0,0 +1,3 @@
export interface Permission {
dashboard_view: "limited" | "full" | "blocked";
}

View File

@@ -1,3 +1,5 @@
import { Permission } from "@/interfaces/Permission";
export interface User {
id: string;
email?: string;
@@ -9,6 +11,7 @@ export interface User {
is_service_user?: boolean;
is_blocked?: boolean;
last_login?: Date;
permissions: Permission;
}
export enum Role {

View File

@@ -42,7 +42,7 @@ function DashboardPageContent({ children }: { children: React.ReactNode }) {
const { mobileNavOpen, toggleMobileNav } = useApplicationContext();
const isSm = useIsSm();
const isXs = useIsXs();
const { isUser } = useLoggedInUser();
const { permission } = useLoggedInUser();
const navOpenPageWidth = isSm ? "50%" : isXs ? "65%" : "80%";
const { bannerHeight } = useAnnouncement();
@@ -154,7 +154,9 @@ function DashboardPageContent({ children }: { children: React.ReactNode }) {
height: `calc(100vh - ${headerHeight + bannerHeight}px)`,
}}
>
<Navigation hideOnMobile />
{permission.dashboard_view !== "blocked" && (
<Navigation hideOnMobile />
)}
{children}
</div>
</motion.div>

View File

@@ -40,7 +40,7 @@ export default function NavbarWithDropdown() {
const { toggleMobileNav } = useApplicationContext();
const { bannerHeight } = useAnnouncement();
const { isUser } = useLoggedInUser();
const { permission } = useLoggedInUser();
return (
<>
@@ -62,7 +62,8 @@ export default function NavbarWithDropdown() {
<Button
className={cn(
"!px-3 md:hidden",
isUser && "opacity-0 pointer-events-none",
permission.dashboard_view == "blocked" &&
"opacity-0 pointer-events-none",
)}
variant={"default-outline"}
onClick={toggleMobileNav}

View File

@@ -0,0 +1,100 @@
import Breadcrumbs from "@components/Breadcrumbs";
import Button from "@components/Button";
import FancyToggleSwitch from "@components/FancyToggleSwitch";
import { notify } from "@components/Notification";
import * as Tabs from "@radix-ui/react-tabs";
import { useApiCall } from "@utils/api";
import { GaugeIcon, LockIcon } from "lucide-react";
import React, { useState } from "react";
import { useSWRConfig } from "swr";
import SettingsIcon from "@/assets/icons/SettingsIcon";
import { useHasChanges } from "@/hooks/useHasChanges";
import { Account } from "@/interfaces/Account";
type Props = {
account: Account;
};
export default function PermissionsTab({ account }: Props) {
const { mutate } = useSWRConfig();
const saveRequest = useApiCall<Account>("/accounts/" + account.id);
const [userViewBlocked, setUserViewBlocked] = useState<boolean>(
account?.settings.regular_users_view_blocked ?? false,
);
const { hasChanges, updateRef } = useHasChanges([userViewBlocked]);
const saveChanges = async () => {
notify({
title: "Permission Settings",
description: "Permissions were updated successfully.",
promise: saveRequest
.put({
id: account.id,
settings: {
regular_users_view_blocked: userViewBlocked,
groups_propagation_enabled:
account.settings?.groups_propagation_enabled,
peer_login_expiration_enabled:
account.settings?.peer_login_expiration_enabled,
peer_login_expiration: account.settings?.peer_login_expiration,
jwt_groups_enabled: account.settings?.jwt_groups_enabled,
jwt_groups_claim_name: account.settings?.jwt_groups_claim_name,
jwt_allow_groups: account.settings?.jwt_allow_groups,
},
})
.then(() => {
mutate("/accounts");
updateRef([userViewBlocked]);
}),
loadingMessage: "Updating permissions...",
});
};
return (
<Tabs.Content value={"permissions"} className={"w-full"}>
<div className={"p-default py-6 max-w-xl"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/settings"}
label={"Settings"}
icon={<SettingsIcon size={13} />}
/>
<Breadcrumbs.Item
href={"/settings?tab=permissions"}
label={"Permissions"}
icon={<LockIcon size={14} />}
active
/>
</Breadcrumbs>
<div className={"flex items-start justify-between"}>
<h1>Permissions</h1>
<Button
variant={"primary"}
disabled={!hasChanges}
onClick={saveChanges}
>
Save Changes
</Button>
</div>
<div className={"flex flex-col gap-6 mt-8 mb-3"}>
<FancyToggleSwitch
value={userViewBlocked}
onChange={setUserViewBlocked}
label={
<>
<GaugeIcon size={15} />
Restrict dashboard for regular users
</>
}
helpText={
"Access to the dashboard will be limited and regular users will not be able to view any peers."
}
/>
</div>
</div>
</Tabs.Content>
);
}

View File

@@ -100,7 +100,11 @@ export function ServiceUserModalContent({ onSuccess }: ModalProps) {
/>
</div>
<UserRoleSelector value={role as Role} onChange={setRole} />
<UserRoleSelector
value={role as Role}
onChange={setRole}
hideOwner={true}
/>
</div>
</div>