Skip to content

Commit 4709f98

Browse files
committed
Permissions polish for restricted users
1 parent 74a8c5d commit 4709f98

File tree

28 files changed

+457
-306
lines changed

28 files changed

+457
-306
lines changed

frontend/src/components/EmptyData.tsx

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { Table as ReactTable } from "@tanstack/react-table";
22
import cn from "classnames";
33
import type { ReactNode } from "react";
4-
import { Button } from "src/components";
4+
import { Button, HasPermission } from "src/components";
55
import { T } from "src/locale";
6+
import { type ADMIN, MANAGE, type Permission, type Section } from "src/modules/Permissions";
67

78
interface Props {
89
tableInstance: ReactTable<any>;
@@ -12,8 +13,20 @@ interface Props {
1213
objects: string;
1314
color?: string;
1415
customAddBtn?: ReactNode;
16+
permissionSection?: Section | typeof ADMIN;
17+
permission?: Permission;
1518
}
16-
function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color = "primary", customAddBtn }: Props) {
19+
function EmptyData({
20+
tableInstance,
21+
onNew,
22+
isFiltered,
23+
object,
24+
objects,
25+
color = "primary",
26+
customAddBtn,
27+
permissionSection,
28+
permission,
29+
}: Props) {
1730
return (
1831
<tr>
1932
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
@@ -27,16 +40,18 @@ function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color =
2740
<h2>
2841
<T id="object.empty" tData={{ objects }} />
2942
</h2>
30-
<p className="text-muted">
31-
<T id="empty-subtitle" />
32-
</p>
33-
{customAddBtn ? (
34-
customAddBtn
35-
) : (
36-
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
37-
<T id="object.add" tData={{ object }} />
38-
</Button>
39-
)}
43+
<HasPermission section={permissionSection} permission={permission || MANAGE} hideError>
44+
<p className="text-muted">
45+
<T id="empty-subtitle" />
46+
</p>
47+
{customAddBtn ? (
48+
customAddBtn
49+
) : (
50+
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
51+
<T id="object.add" tData={{ object }} />
52+
</Button>
53+
)}
54+
</HasPermission>
4055
</>
4156
)}
4257
</div>

frontend/src/components/HasPermission.tsx

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@ import Alert from "react-bootstrap/Alert";
33
import { Loading, LoadingPage } from "src/components";
44
import { useUser } from "src/hooks";
55
import { T } from "src/locale";
6+
import { type ADMIN, hasPermission, type Permission, type Section } from "src/modules/Permissions";
67

78
interface Props {
8-
permission: string;
9-
type: "manage" | "view";
9+
section?: Section | typeof ADMIN;
10+
permission: Permission;
1011
hideError?: boolean;
1112
children?: ReactNode;
1213
pageLoading?: boolean;
1314
loadingNoLogo?: boolean;
1415
}
1516
function HasPermission({
17+
section,
1618
permission,
17-
type,
1819
children,
1920
hideError = false,
2021
pageLoading = false,
2122
loadingNoLogo = false,
2223
}: Props) {
2324
const { data, isLoading } = useUser("me");
24-
const perms = data?.permissions;
25+
26+
if (!section) {
27+
return <>{children}</>;
28+
}
2529

2630
if (isLoading) {
2731
if (hideError) {
@@ -33,33 +37,7 @@ function HasPermission({
3337
return <Loading noLogo={loadingNoLogo} />;
3438
}
3539

36-
let allowed = permission === "";
37-
const acceptable = ["manage", type];
38-
39-
switch (permission) {
40-
case "admin":
41-
allowed = data?.roles?.includes("admin") || false;
42-
break;
43-
case "proxyHosts":
44-
allowed = acceptable.indexOf(perms?.proxyHosts || "") !== -1;
45-
break;
46-
case "redirectionHosts":
47-
allowed = acceptable.indexOf(perms?.redirectionHosts || "") !== -1;
48-
break;
49-
case "deadHosts":
50-
allowed = acceptable.indexOf(perms?.deadHosts || "") !== -1;
51-
break;
52-
case "streams":
53-
allowed = acceptable.indexOf(perms?.streams || "") !== -1;
54-
break;
55-
case "accessLists":
56-
allowed = acceptable.indexOf(perms?.accessLists || "") !== -1;
57-
break;
58-
case "certificates":
59-
allowed = acceptable.indexOf(perms?.certificates || "") !== -1;
60-
break;
61-
}
62-
40+
const allowed = hasPermission(section, permission, data?.permissions, data?.roles);
6341
if (allowed) {
6442
return <>{children}</>;
6543
}

frontend/src/components/SiteMenu.tsx

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,26 @@ import cn from "classnames";
1111
import React from "react";
1212
import { HasPermission, NavLink } from "src/components";
1313
import { T } from "src/locale";
14+
import {
15+
ACCESS_LISTS,
16+
ADMIN,
17+
CERTIFICATES,
18+
DEAD_HOSTS,
19+
type MANAGE,
20+
PROXY_HOSTS,
21+
REDIRECTION_HOSTS,
22+
type Section,
23+
STREAMS,
24+
VIEW,
25+
} from "src/modules/Permissions";
1426

1527
interface MenuItem {
1628
label: string;
1729
icon?: React.ElementType;
1830
to?: string;
1931
items?: MenuItem[];
20-
permission?: string;
21-
permissionType?: "view" | "manage";
32+
permissionSection?: Section | typeof ADMIN;
33+
permission?: typeof VIEW | typeof MANAGE;
2234
}
2335

2436
const menuItems: MenuItem[] = [
@@ -34,60 +46,60 @@ const menuItems: MenuItem[] = [
3446
{
3547
to: "/nginx/proxy",
3648
label: "proxy-hosts",
37-
permission: "proxyHosts",
38-
permissionType: "view",
49+
permissionSection: PROXY_HOSTS,
50+
permission: VIEW,
3951
},
4052
{
4153
to: "/nginx/redirection",
4254
label: "redirection-hosts",
43-
permission: "redirectionHosts",
44-
permissionType: "view",
55+
permissionSection: REDIRECTION_HOSTS,
56+
permission: VIEW,
4557
},
4658
{
4759
to: "/nginx/stream",
4860
label: "streams",
49-
permission: "streams",
50-
permissionType: "view",
61+
permissionSection: STREAMS,
62+
permission: VIEW,
5163
},
5264
{
5365
to: "/nginx/404",
5466
label: "dead-hosts",
55-
permission: "deadHosts",
56-
permissionType: "view",
67+
permissionSection: DEAD_HOSTS,
68+
permission: VIEW,
5769
},
5870
],
5971
},
6072
{
6173
to: "/access",
6274
icon: IconLock,
6375
label: "access-lists",
64-
permission: "accessLists",
65-
permissionType: "view",
76+
permissionSection: ACCESS_LISTS,
77+
permission: VIEW,
6678
},
6779
{
6880
to: "/certificates",
6981
icon: IconShield,
7082
label: "certificates",
71-
permission: "certificates",
72-
permissionType: "view",
83+
permissionSection: CERTIFICATES,
84+
permission: VIEW,
7385
},
7486
{
7587
to: "/users",
7688
icon: IconUser,
7789
label: "users",
78-
permission: "admin",
90+
permissionSection: ADMIN,
7991
},
8092
{
8193
to: "/audit-log",
8294
icon: IconBook,
8395
label: "auditlogs",
84-
permission: "admin",
96+
permissionSection: ADMIN,
8597
},
8698
{
8799
to: "/settings",
88100
icon: IconSettings,
89101
label: "settings",
90-
permission: "admin",
102+
permissionSection: ADMIN,
91103
},
92104
];
93105

@@ -99,8 +111,8 @@ const getMenuItem = (item: MenuItem, onClick?: () => void) => {
99111
return (
100112
<HasPermission
101113
key={`item-${item.label}`}
102-
permission={item.permission || ""}
103-
type={item.permissionType || "view"}
114+
section={item.permissionSection}
115+
permission={item.permission || VIEW}
104116
hideError
105117
>
106118
<li className="nav-item">
@@ -122,8 +134,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
122134
return (
123135
<HasPermission
124136
key={`item-${item.label}`}
125-
permission={item.permission || ""}
126-
type={item.permissionType || "view"}
137+
section={item.permissionSection}
138+
permission={item.permission || VIEW}
127139
hideError
128140
>
129141
<li className={cns}>
@@ -147,8 +159,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
147159
return (
148160
<HasPermission
149161
key={`${idx}-${subitem.to}`}
150-
permission={subitem.permission || ""}
151-
type={subitem.permissionType || "view"}
162+
section={subitem.permissionSection}
163+
permission={subitem.permission || VIEW}
152164
hideError
153165
>
154166
<NavLink to={subitem.to} isDropdownItem onClick={onClick}>

frontend/src/modals/PermissionsModal.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,41 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
4747
});
4848
};
4949

50+
// given the field and clicked permission, intelligently set the value, and
51+
// other values that depends on it.
52+
const handleChange = (form: any, field: any, perm: string) => {
53+
if (field.name === "proxyHosts" && perm !== "hidden" && form.values.accessLists === "hidden") {
54+
form.setFieldValue("accessLists", "view");
55+
}
56+
// certs are required for proxy and redirection hosts, and streams
57+
if (
58+
["proxyHosts", "redirectionHosts", "deadHosts", "streams"].includes(field.name) &&
59+
perm !== "hidden" &&
60+
form.values.certificates === "hidden"
61+
) {
62+
form.setFieldValue("certificates", "view");
63+
}
64+
65+
form.setFieldValue(field.name, perm);
66+
};
67+
5068
const getPermissionButtons = (field: any, form: any) => {
5169
const isManage = field.value === "manage";
5270
const isView = field.value === "view";
5371
const isHidden = field.value === "hidden";
5472

73+
let hiddenDisabled = false;
74+
if (field.name === "accessLists") {
75+
hiddenDisabled = form.values.proxyHosts !== "hidden";
76+
}
77+
if (field.name === "certificates") {
78+
hiddenDisabled =
79+
form.values.proxyHosts !== "hidden" ||
80+
form.values.redirectionHosts !== "hidden" ||
81+
form.values.deadHosts !== "hidden" ||
82+
form.values.streams !== "hidden";
83+
}
84+
5585
return (
5686
<div>
5787
<div className="btn-group w-100" role="group">
@@ -63,7 +93,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
6393
autoComplete="off"
6494
value="manage"
6595
checked={field.value === "manage"}
66-
onChange={() => form.setFieldValue(field.name, "manage")}
96+
onChange={() => handleChange(form, field, "manage")}
6797
/>
6898
<label htmlFor={`${field.name}-manage`} className={getClasses(isManage)}>
6999
<T id="permissions.manage" />
@@ -76,7 +106,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
76106
autoComplete="off"
77107
value="view"
78108
checked={field.value === "view"}
79-
onChange={() => form.setFieldValue(field.name, "view")}
109+
onChange={() => handleChange(form, field, "view")}
80110
/>
81111
<label htmlFor={`${field.name}-view`} className={getClasses(isView)}>
82112
<T id="permissions.view" />
@@ -89,7 +119,8 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
89119
autoComplete="off"
90120
value="hidden"
91121
checked={field.value === "hidden"}
92-
onChange={() => form.setFieldValue(field.name, "hidden")}
122+
disabled={hiddenDisabled}
123+
onChange={() => handleChange(form, field, "hidden")}
93124
/>
94125
<label htmlFor={`${field.name}-hidden`} className={getClasses(isHidden)}>
95126
<T id="permissions.hidden" />

0 commit comments

Comments
 (0)