Permissions polish for restricted users

This commit is contained in:
Jamie Curnow
2025-10-30 19:01:00 +10:00
parent 74a8c5d806
commit 4709f9826c
28 changed files with 457 additions and 306 deletions

View File

@@ -1,8 +1,9 @@
import type { Table as ReactTable } from "@tanstack/react-table";
import cn from "classnames";
import type { ReactNode } from "react";
import { Button } from "src/components";
import { Button, HasPermission } from "src/components";
import { T } from "src/locale";
import { type ADMIN, MANAGE, type Permission, type Section } from "src/modules/Permissions";
interface Props {
tableInstance: ReactTable<any>;
@@ -12,8 +13,20 @@ interface Props {
objects: string;
color?: string;
customAddBtn?: ReactNode;
permissionSection?: Section | typeof ADMIN;
permission?: Permission;
}
function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color = "primary", customAddBtn }: Props) {
function EmptyData({
tableInstance,
onNew,
isFiltered,
object,
objects,
color = "primary",
customAddBtn,
permissionSection,
permission,
}: Props) {
return (
<tr>
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
@@ -27,16 +40,18 @@ function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color =
<h2>
<T id="object.empty" tData={{ objects }} />
</h2>
<p className="text-muted">
<T id="empty-subtitle" />
</p>
{customAddBtn ? (
customAddBtn
) : (
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
<T id="object.add" tData={{ object }} />
</Button>
)}
<HasPermission section={permissionSection} permission={permission || MANAGE} hideError>
<p className="text-muted">
<T id="empty-subtitle" />
</p>
{customAddBtn ? (
customAddBtn
) : (
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
<T id="object.add" tData={{ object }} />
</Button>
)}
</HasPermission>
</>
)}
</div>

View File

@@ -3,25 +3,29 @@ import Alert from "react-bootstrap/Alert";
import { Loading, LoadingPage } from "src/components";
import { useUser } from "src/hooks";
import { T } from "src/locale";
import { type ADMIN, hasPermission, type Permission, type Section } from "src/modules/Permissions";
interface Props {
permission: string;
type: "manage" | "view";
section?: Section | typeof ADMIN;
permission: Permission;
hideError?: boolean;
children?: ReactNode;
pageLoading?: boolean;
loadingNoLogo?: boolean;
}
function HasPermission({
section,
permission,
type,
children,
hideError = false,
pageLoading = false,
loadingNoLogo = false,
}: Props) {
const { data, isLoading } = useUser("me");
const perms = data?.permissions;
if (!section) {
return <>{children}</>;
}
if (isLoading) {
if (hideError) {
@@ -33,33 +37,7 @@ function HasPermission({
return <Loading noLogo={loadingNoLogo} />;
}
let allowed = permission === "";
const acceptable = ["manage", type];
switch (permission) {
case "admin":
allowed = data?.roles?.includes("admin") || false;
break;
case "proxyHosts":
allowed = acceptable.indexOf(perms?.proxyHosts || "") !== -1;
break;
case "redirectionHosts":
allowed = acceptable.indexOf(perms?.redirectionHosts || "") !== -1;
break;
case "deadHosts":
allowed = acceptable.indexOf(perms?.deadHosts || "") !== -1;
break;
case "streams":
allowed = acceptable.indexOf(perms?.streams || "") !== -1;
break;
case "accessLists":
allowed = acceptable.indexOf(perms?.accessLists || "") !== -1;
break;
case "certificates":
allowed = acceptable.indexOf(perms?.certificates || "") !== -1;
break;
}
const allowed = hasPermission(section, permission, data?.permissions, data?.roles);
if (allowed) {
return <>{children}</>;
}

View File

@@ -11,14 +11,26 @@ import cn from "classnames";
import React from "react";
import { HasPermission, NavLink } from "src/components";
import { T } from "src/locale";
import {
ACCESS_LISTS,
ADMIN,
CERTIFICATES,
DEAD_HOSTS,
type MANAGE,
PROXY_HOSTS,
REDIRECTION_HOSTS,
type Section,
STREAMS,
VIEW,
} from "src/modules/Permissions";
interface MenuItem {
label: string;
icon?: React.ElementType;
to?: string;
items?: MenuItem[];
permission?: string;
permissionType?: "view" | "manage";
permissionSection?: Section | typeof ADMIN;
permission?: typeof VIEW | typeof MANAGE;
}
const menuItems: MenuItem[] = [
@@ -34,26 +46,26 @@ const menuItems: MenuItem[] = [
{
to: "/nginx/proxy",
label: "proxy-hosts",
permission: "proxyHosts",
permissionType: "view",
permissionSection: PROXY_HOSTS,
permission: VIEW,
},
{
to: "/nginx/redirection",
label: "redirection-hosts",
permission: "redirectionHosts",
permissionType: "view",
permissionSection: REDIRECTION_HOSTS,
permission: VIEW,
},
{
to: "/nginx/stream",
label: "streams",
permission: "streams",
permissionType: "view",
permissionSection: STREAMS,
permission: VIEW,
},
{
to: "/nginx/404",
label: "dead-hosts",
permission: "deadHosts",
permissionType: "view",
permissionSection: DEAD_HOSTS,
permission: VIEW,
},
],
},
@@ -61,33 +73,33 @@ const menuItems: MenuItem[] = [
to: "/access",
icon: IconLock,
label: "access-lists",
permission: "accessLists",
permissionType: "view",
permissionSection: ACCESS_LISTS,
permission: VIEW,
},
{
to: "/certificates",
icon: IconShield,
label: "certificates",
permission: "certificates",
permissionType: "view",
permissionSection: CERTIFICATES,
permission: VIEW,
},
{
to: "/users",
icon: IconUser,
label: "users",
permission: "admin",
permissionSection: ADMIN,
},
{
to: "/audit-log",
icon: IconBook,
label: "auditlogs",
permission: "admin",
permissionSection: ADMIN,
},
{
to: "/settings",
icon: IconSettings,
label: "settings",
permission: "admin",
permissionSection: ADMIN,
},
];
@@ -99,8 +111,8 @@ const getMenuItem = (item: MenuItem, onClick?: () => void) => {
return (
<HasPermission
key={`item-${item.label}`}
permission={item.permission || ""}
type={item.permissionType || "view"}
section={item.permissionSection}
permission={item.permission || VIEW}
hideError
>
<li className="nav-item">
@@ -122,8 +134,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
return (
<HasPermission
key={`item-${item.label}`}
permission={item.permission || ""}
type={item.permissionType || "view"}
section={item.permissionSection}
permission={item.permission || VIEW}
hideError
>
<li className={cns}>
@@ -147,8 +159,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
return (
<HasPermission
key={`${idx}-${subitem.to}`}
permission={subitem.permission || ""}
type={subitem.permissionType || "view"}
section={subitem.permissionSection}
permission={subitem.permission || VIEW}
hideError
>
<NavLink to={subitem.to} isDropdownItem onClick={onClick}>