Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting to change dashboard view for regular users #362

Merged
merged 3 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/app/(dashboard)/peers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
}
Expand Down
13 changes: 12 additions & 1 deletion src/app/(dashboard)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

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";
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");
Expand All @@ -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
Expand All @@ -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>
Expand Down
8 changes: 6 additions & 2 deletions src/contexts/GroupsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
17 changes: 16 additions & 1 deletion src/contexts/UsersProvider.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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;
};
1 change: 1 addition & 0 deletions src/interfaces/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}
3 changes: 3 additions & 0 deletions src/interfaces/Permission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Permission {
dashboard_view: "limited" | "full" | "blocked";
}
3 changes: 3 additions & 0 deletions src/interfaces/User.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Permission } from "@/interfaces/Permission";

export interface User {
id: string;
email?: string;
Expand All @@ -9,6 +11,7 @@ export interface User {
is_service_user?: boolean;
is_blocked?: boolean;
last_login?: Date;
permissions: Permission;
}

export enum Role {
Expand Down
6 changes: 4 additions & 2 deletions src/layouts/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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>
Expand Down
5 changes: 3 additions & 2 deletions src/layouts/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function NavbarWithDropdown() {

const { toggleMobileNav } = useApplicationContext();
const { bannerHeight } = useAnnouncement();
const { isUser } = useLoggedInUser();
const { permission } = useLoggedInUser();

return (
<>
Expand All @@ -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}
Expand Down
100 changes: 100 additions & 0 deletions src/modules/settings/PermissionsTab.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
6 changes: 5 additions & 1 deletion src/modules/users/ServiceUserModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
Loading