Skip to content

Commit 973ccef

Browse files
authored
Add setting to change dashboard view for regular users (#362)
1 parent f4a2d6f commit 973ccef

File tree

11 files changed

+159
-11
lines changed

11 files changed

+159
-11
lines changed

src/app/(dashboard)/peers/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ import { SetupModalContent } from "@/modules/setup-netbird-modal/SetupModal";
1717
const PeersTable = lazy(() => import("@/modules/peers/PeersTable"));
1818

1919
export default function Peers() {
20-
const { isUser } = useLoggedInUser();
20+
const { permission } = useLoggedInUser();
2121

2222
return (
2323
<PageContainer>
24-
<PeersView />
24+
{permission?.dashboard_view === "blocked" ? (
25+
<PeersDefaultView />
26+
) : (
27+
<PeersView />
28+
)}
2529
</PageContainer>
2630
);
2731
}

src/app/(dashboard)/settings/page.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22

33
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
44
import { VerticalTabs } from "@components/VerticalTabs";
5-
import { AlertOctagonIcon, FolderGit2Icon, ShieldIcon } from "lucide-react";
5+
import {
6+
AlertOctagonIcon,
7+
FolderGit2Icon,
8+
LockIcon,
9+
ShieldIcon,
10+
} from "lucide-react";
611
import React, { useState } from "react";
712
import { useLoggedInUser } from "@/contexts/UsersProvider";
813
import PageContainer from "@/layouts/PageContainer";
914
import { useAccount } from "@/modules/account/useAccount";
1015
import AuthenticationTab from "@/modules/settings/AuthenticationTab";
1116
import DangerZoneTab from "@/modules/settings/DangerZoneTab";
1217
import GroupsTab from "@/modules/settings/GroupsTab";
18+
import PermissionsTab from "@/modules/settings/PermissionsTab";
1319

1420
export default function NetBirdSettings() {
1521
const [tab, setTab] = useState("authentication");
@@ -28,6 +34,10 @@ export default function NetBirdSettings() {
2834
<FolderGit2Icon size={14} />
2935
Groups
3036
</VerticalTabs.Trigger>
37+
<VerticalTabs.Trigger value="permissions">
38+
<LockIcon size={14} />
39+
Permissions
40+
</VerticalTabs.Trigger>
3141
<VerticalTabs.Trigger value="danger-zone" disabled={!isOwner}>
3242
<AlertOctagonIcon size={14} />
3343
Danger zone
@@ -36,6 +46,7 @@ export default function NetBirdSettings() {
3646
<RestrictedAccess page={"Settings"}>
3747
<div className={"border-l border-nb-gray-930 w-full"}>
3848
{account && <AuthenticationTab account={account} />}
49+
{account && <PermissionsTab account={account} />}
3950
{account && <GroupsTab account={account} />}
4051
{account && <DangerZoneTab account={account} />}
4152
</div>

src/contexts/GroupsProvider.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ const GroupContext = React.createContext(
2020

2121
export default function GroupsProvider({ children }: Props) {
2222
const path = usePathname();
23-
const { isUser } = useLoggedInUser();
23+
const { permission } = useLoggedInUser();
2424

25-
return <GroupsProviderContent>{children}</GroupsProviderContent>;
25+
return path === "/peers" && permission.dashboard_view == "blocked" ? (
26+
<>{children}</>
27+
) : (
28+
<GroupsProviderContent>{children}</GroupsProviderContent>
29+
);
2630
}
2731

2832
export function GroupsProviderContent({ children }: Props) {

src/contexts/UsersProvider.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import FullScreenLoading from "@components/ui/FullScreenLoading";
22
import useFetchApi from "@utils/api";
33
import React, { useMemo } from "react";
4+
import { Permission } from "@/interfaces/Permission";
45
import { User } from "@/interfaces/User";
56

67
type Props = {
@@ -43,5 +44,19 @@ export const useLoggedInUser = () => {
4344
const isAdmin = loggedInUser ? loggedInUser?.role === "admin" : false;
4445
const isUser = !isOwner && !isAdmin;
4546
const isOwnerOrAdmin = isOwner || isAdmin;
46-
return { loggedInUser, isOwner, isAdmin, isUser, isOwnerOrAdmin } as const;
47+
48+
const permission = useMemo(() => {
49+
return {
50+
dashboard_view: loggedInUser?.permissions.dashboard_view || "blocked",
51+
} as Permission;
52+
}, [loggedInUser]);
53+
54+
return {
55+
loggedInUser,
56+
isOwner,
57+
isAdmin,
58+
isUser,
59+
isOwnerOrAdmin,
60+
permission,
61+
} as const;
4762
};

src/interfaces/Account.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export interface Account {
1010
jwt_groups_enabled: boolean;
1111
jwt_groups_claim_name: string;
1212
jwt_allow_groups: string[];
13+
regular_users_view_blocked: boolean;
1314
};
1415
}

src/interfaces/Permission.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface Permission {
2+
dashboard_view: "limited" | "full" | "blocked";
3+
}

src/interfaces/User.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Permission } from "@/interfaces/Permission";
2+
13
export interface User {
24
id: string;
35
email?: string;
@@ -9,6 +11,7 @@ export interface User {
911
is_service_user?: boolean;
1012
is_blocked?: boolean;
1113
last_login?: Date;
14+
permissions: Permission;
1215
}
1316

1417
export enum Role {

src/layouts/DashboardLayout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function DashboardPageContent({ children }: { children: React.ReactNode }) {
4242
const { mobileNavOpen, toggleMobileNav } = useApplicationContext();
4343
const isSm = useIsSm();
4444
const isXs = useIsXs();
45-
const { isUser } = useLoggedInUser();
45+
const { permission } = useLoggedInUser();
4646

4747
const navOpenPageWidth = isSm ? "50%" : isXs ? "65%" : "80%";
4848
const { bannerHeight } = useAnnouncement();
@@ -154,7 +154,9 @@ function DashboardPageContent({ children }: { children: React.ReactNode }) {
154154
height: `calc(100vh - ${headerHeight + bannerHeight}px)`,
155155
}}
156156
>
157-
<Navigation hideOnMobile />
157+
{permission.dashboard_view !== "blocked" && (
158+
<Navigation hideOnMobile />
159+
)}
158160
{children}
159161
</div>
160162
</motion.div>

src/layouts/Header.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default function NavbarWithDropdown() {
4040

4141
const { toggleMobileNav } = useApplicationContext();
4242
const { bannerHeight } = useAnnouncement();
43-
const { isUser } = useLoggedInUser();
43+
const { permission } = useLoggedInUser();
4444

4545
return (
4646
<>
@@ -62,7 +62,8 @@ export default function NavbarWithDropdown() {
6262
<Button
6363
className={cn(
6464
"!px-3 md:hidden",
65-
isUser && "opacity-0 pointer-events-none",
65+
permission.dashboard_view == "blocked" &&
66+
"opacity-0 pointer-events-none",
6667
)}
6768
variant={"default-outline"}
6869
onClick={toggleMobileNav}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import Breadcrumbs from "@components/Breadcrumbs";
2+
import Button from "@components/Button";
3+
import FancyToggleSwitch from "@components/FancyToggleSwitch";
4+
import { notify } from "@components/Notification";
5+
import * as Tabs from "@radix-ui/react-tabs";
6+
import { useApiCall } from "@utils/api";
7+
import { GaugeIcon, LockIcon } from "lucide-react";
8+
import React, { useState } from "react";
9+
import { useSWRConfig } from "swr";
10+
import SettingsIcon from "@/assets/icons/SettingsIcon";
11+
import { useHasChanges } from "@/hooks/useHasChanges";
12+
import { Account } from "@/interfaces/Account";
13+
14+
type Props = {
15+
account: Account;
16+
};
17+
18+
export default function PermissionsTab({ account }: Props) {
19+
const { mutate } = useSWRConfig();
20+
const saveRequest = useApiCall<Account>("/accounts/" + account.id);
21+
22+
const [userViewBlocked, setUserViewBlocked] = useState<boolean>(
23+
account?.settings.regular_users_view_blocked ?? false,
24+
);
25+
26+
const { hasChanges, updateRef } = useHasChanges([userViewBlocked]);
27+
28+
const saveChanges = async () => {
29+
notify({
30+
title: "Permission Settings",
31+
description: "Permissions were updated successfully.",
32+
promise: saveRequest
33+
.put({
34+
id: account.id,
35+
settings: {
36+
regular_users_view_blocked: userViewBlocked,
37+
groups_propagation_enabled:
38+
account.settings?.groups_propagation_enabled,
39+
peer_login_expiration_enabled:
40+
account.settings?.peer_login_expiration_enabled,
41+
peer_login_expiration: account.settings?.peer_login_expiration,
42+
jwt_groups_enabled: account.settings?.jwt_groups_enabled,
43+
jwt_groups_claim_name: account.settings?.jwt_groups_claim_name,
44+
jwt_allow_groups: account.settings?.jwt_allow_groups,
45+
},
46+
})
47+
.then(() => {
48+
mutate("/accounts");
49+
updateRef([userViewBlocked]);
50+
}),
51+
loadingMessage: "Updating permissions...",
52+
});
53+
};
54+
55+
return (
56+
<Tabs.Content value={"permissions"} className={"w-full"}>
57+
<div className={"p-default py-6 max-w-xl"}>
58+
<Breadcrumbs>
59+
<Breadcrumbs.Item
60+
href={"/settings"}
61+
label={"Settings"}
62+
icon={<SettingsIcon size={13} />}
63+
/>
64+
<Breadcrumbs.Item
65+
href={"/settings?tab=permissions"}
66+
label={"Permissions"}
67+
icon={<LockIcon size={14} />}
68+
active
69+
/>
70+
</Breadcrumbs>
71+
<div className={"flex items-start justify-between"}>
72+
<h1>Permissions</h1>
73+
<Button
74+
variant={"primary"}
75+
disabled={!hasChanges}
76+
onClick={saveChanges}
77+
>
78+
Save Changes
79+
</Button>
80+
</div>
81+
82+
<div className={"flex flex-col gap-6 mt-8 mb-3"}>
83+
<FancyToggleSwitch
84+
value={userViewBlocked}
85+
onChange={setUserViewBlocked}
86+
label={
87+
<>
88+
<GaugeIcon size={15} />
89+
Restrict dashboard for regular users
90+
</>
91+
}
92+
helpText={
93+
"Access to the dashboard will be limited and regular users will not be able to view any peers."
94+
}
95+
/>
96+
</div>
97+
</div>
98+
</Tabs.Content>
99+
);
100+
}

0 commit comments

Comments
 (0)