mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-11-02 16:53:34 +00:00
Permissions polish for restricted users
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { Button } from "src/components";
|
import { Button, HasPermission } from "src/components";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
|
import { type ADMIN, MANAGE, type Permission, type Section } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tableInstance: ReactTable<any>;
|
tableInstance: ReactTable<any>;
|
||||||
@@ -12,8 +13,20 @@ interface Props {
|
|||||||
objects: string;
|
objects: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
customAddBtn?: ReactNode;
|
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 (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||||
@@ -27,16 +40,18 @@ function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color =
|
|||||||
<h2>
|
<h2>
|
||||||
<T id="object.empty" tData={{ objects }} />
|
<T id="object.empty" tData={{ objects }} />
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted">
|
<HasPermission section={permissionSection} permission={permission || MANAGE} hideError>
|
||||||
<T id="empty-subtitle" />
|
<p className="text-muted">
|
||||||
</p>
|
<T id="empty-subtitle" />
|
||||||
{customAddBtn ? (
|
</p>
|
||||||
customAddBtn
|
{customAddBtn ? (
|
||||||
) : (
|
customAddBtn
|
||||||
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
|
) : (
|
||||||
<T id="object.add" tData={{ object }} />
|
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
|
||||||
</Button>
|
<T id="object.add" tData={{ object }} />
|
||||||
)}
|
</Button>
|
||||||
|
)}
|
||||||
|
</HasPermission>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,25 +3,29 @@ import Alert from "react-bootstrap/Alert";
|
|||||||
import { Loading, LoadingPage } from "src/components";
|
import { Loading, LoadingPage } from "src/components";
|
||||||
import { useUser } from "src/hooks";
|
import { useUser } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
|
import { type ADMIN, hasPermission, type Permission, type Section } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
permission: string;
|
section?: Section | typeof ADMIN;
|
||||||
type: "manage" | "view";
|
permission: Permission;
|
||||||
hideError?: boolean;
|
hideError?: boolean;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
pageLoading?: boolean;
|
pageLoading?: boolean;
|
||||||
loadingNoLogo?: boolean;
|
loadingNoLogo?: boolean;
|
||||||
}
|
}
|
||||||
function HasPermission({
|
function HasPermission({
|
||||||
|
section,
|
||||||
permission,
|
permission,
|
||||||
type,
|
|
||||||
children,
|
children,
|
||||||
hideError = false,
|
hideError = false,
|
||||||
pageLoading = false,
|
pageLoading = false,
|
||||||
loadingNoLogo = false,
|
loadingNoLogo = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { data, isLoading } = useUser("me");
|
const { data, isLoading } = useUser("me");
|
||||||
const perms = data?.permissions;
|
|
||||||
|
if (!section) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
if (hideError) {
|
if (hideError) {
|
||||||
@@ -33,33 +37,7 @@ function HasPermission({
|
|||||||
return <Loading noLogo={loadingNoLogo} />;
|
return <Loading noLogo={loadingNoLogo} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let allowed = permission === "";
|
const allowed = hasPermission(section, permission, data?.permissions, data?.roles);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,26 @@ import cn from "classnames";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { HasPermission, NavLink } from "src/components";
|
import { HasPermission, NavLink } from "src/components";
|
||||||
import { T } from "src/locale";
|
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 {
|
interface MenuItem {
|
||||||
label: string;
|
label: string;
|
||||||
icon?: React.ElementType;
|
icon?: React.ElementType;
|
||||||
to?: string;
|
to?: string;
|
||||||
items?: MenuItem[];
|
items?: MenuItem[];
|
||||||
permission?: string;
|
permissionSection?: Section | typeof ADMIN;
|
||||||
permissionType?: "view" | "manage";
|
permission?: typeof VIEW | typeof MANAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems: MenuItem[] = [
|
const menuItems: MenuItem[] = [
|
||||||
@@ -34,26 +46,26 @@ const menuItems: MenuItem[] = [
|
|||||||
{
|
{
|
||||||
to: "/nginx/proxy",
|
to: "/nginx/proxy",
|
||||||
label: "proxy-hosts",
|
label: "proxy-hosts",
|
||||||
permission: "proxyHosts",
|
permissionSection: PROXY_HOSTS,
|
||||||
permissionType: "view",
|
permission: VIEW,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/nginx/redirection",
|
to: "/nginx/redirection",
|
||||||
label: "redirection-hosts",
|
label: "redirection-hosts",
|
||||||
permission: "redirectionHosts",
|
permissionSection: REDIRECTION_HOSTS,
|
||||||
permissionType: "view",
|
permission: VIEW,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/nginx/stream",
|
to: "/nginx/stream",
|
||||||
label: "streams",
|
label: "streams",
|
||||||
permission: "streams",
|
permissionSection: STREAMS,
|
||||||
permissionType: "view",
|
permission: VIEW,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/nginx/404",
|
to: "/nginx/404",
|
||||||
label: "dead-hosts",
|
label: "dead-hosts",
|
||||||
permission: "deadHosts",
|
permissionSection: DEAD_HOSTS,
|
||||||
permissionType: "view",
|
permission: VIEW,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -61,33 +73,33 @@ const menuItems: MenuItem[] = [
|
|||||||
to: "/access",
|
to: "/access",
|
||||||
icon: IconLock,
|
icon: IconLock,
|
||||||
label: "access-lists",
|
label: "access-lists",
|
||||||
permission: "accessLists",
|
permissionSection: ACCESS_LISTS,
|
||||||
permissionType: "view",
|
permission: VIEW,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/certificates",
|
to: "/certificates",
|
||||||
icon: IconShield,
|
icon: IconShield,
|
||||||
label: "certificates",
|
label: "certificates",
|
||||||
permission: "certificates",
|
permissionSection: CERTIFICATES,
|
||||||
permissionType: "view",
|
permission: VIEW,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/users",
|
to: "/users",
|
||||||
icon: IconUser,
|
icon: IconUser,
|
||||||
label: "users",
|
label: "users",
|
||||||
permission: "admin",
|
permissionSection: ADMIN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/audit-log",
|
to: "/audit-log",
|
||||||
icon: IconBook,
|
icon: IconBook,
|
||||||
label: "auditlogs",
|
label: "auditlogs",
|
||||||
permission: "admin",
|
permissionSection: ADMIN,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/settings",
|
to: "/settings",
|
||||||
icon: IconSettings,
|
icon: IconSettings,
|
||||||
label: "settings",
|
label: "settings",
|
||||||
permission: "admin",
|
permissionSection: ADMIN,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -99,8 +111,8 @@ const getMenuItem = (item: MenuItem, onClick?: () => void) => {
|
|||||||
return (
|
return (
|
||||||
<HasPermission
|
<HasPermission
|
||||||
key={`item-${item.label}`}
|
key={`item-${item.label}`}
|
||||||
permission={item.permission || ""}
|
section={item.permissionSection}
|
||||||
type={item.permissionType || "view"}
|
permission={item.permission || VIEW}
|
||||||
hideError
|
hideError
|
||||||
>
|
>
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
@@ -122,8 +134,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
|
|||||||
return (
|
return (
|
||||||
<HasPermission
|
<HasPermission
|
||||||
key={`item-${item.label}`}
|
key={`item-${item.label}`}
|
||||||
permission={item.permission || ""}
|
section={item.permissionSection}
|
||||||
type={item.permissionType || "view"}
|
permission={item.permission || VIEW}
|
||||||
hideError
|
hideError
|
||||||
>
|
>
|
||||||
<li className={cns}>
|
<li className={cns}>
|
||||||
@@ -147,8 +159,8 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
|
|||||||
return (
|
return (
|
||||||
<HasPermission
|
<HasPermission
|
||||||
key={`${idx}-${subitem.to}`}
|
key={`${idx}-${subitem.to}`}
|
||||||
permission={subitem.permission || ""}
|
section={subitem.permissionSection}
|
||||||
type={subitem.permissionType || "view"}
|
permission={subitem.permission || VIEW}
|
||||||
hideError
|
hideError
|
||||||
>
|
>
|
||||||
<NavLink to={subitem.to} isDropdownItem onClick={onClick}>
|
<NavLink to={subitem.to} isDropdownItem onClick={onClick}>
|
||||||
|
|||||||
@@ -47,11 +47,41 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// given the field and clicked permission, intelligently set the value, and
|
||||||
|
// other values that depends on it.
|
||||||
|
const handleChange = (form: any, field: any, perm: string) => {
|
||||||
|
if (field.name === "proxyHosts" && perm !== "hidden" && form.values.accessLists === "hidden") {
|
||||||
|
form.setFieldValue("accessLists", "view");
|
||||||
|
}
|
||||||
|
// certs are required for proxy and redirection hosts, and streams
|
||||||
|
if (
|
||||||
|
["proxyHosts", "redirectionHosts", "deadHosts", "streams"].includes(field.name) &&
|
||||||
|
perm !== "hidden" &&
|
||||||
|
form.values.certificates === "hidden"
|
||||||
|
) {
|
||||||
|
form.setFieldValue("certificates", "view");
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setFieldValue(field.name, perm);
|
||||||
|
};
|
||||||
|
|
||||||
const getPermissionButtons = (field: any, form: any) => {
|
const getPermissionButtons = (field: any, form: any) => {
|
||||||
const isManage = field.value === "manage";
|
const isManage = field.value === "manage";
|
||||||
const isView = field.value === "view";
|
const isView = field.value === "view";
|
||||||
const isHidden = field.value === "hidden";
|
const isHidden = field.value === "hidden";
|
||||||
|
|
||||||
|
let hiddenDisabled = false;
|
||||||
|
if (field.name === "accessLists") {
|
||||||
|
hiddenDisabled = form.values.proxyHosts !== "hidden";
|
||||||
|
}
|
||||||
|
if (field.name === "certificates") {
|
||||||
|
hiddenDisabled =
|
||||||
|
form.values.proxyHosts !== "hidden" ||
|
||||||
|
form.values.redirectionHosts !== "hidden" ||
|
||||||
|
form.values.deadHosts !== "hidden" ||
|
||||||
|
form.values.streams !== "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="btn-group w-100" role="group">
|
<div className="btn-group w-100" role="group">
|
||||||
@@ -63,7 +93,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value="manage"
|
value="manage"
|
||||||
checked={field.value === "manage"}
|
checked={field.value === "manage"}
|
||||||
onChange={() => form.setFieldValue(field.name, "manage")}
|
onChange={() => handleChange(form, field, "manage")}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={`${field.name}-manage`} className={getClasses(isManage)}>
|
<label htmlFor={`${field.name}-manage`} className={getClasses(isManage)}>
|
||||||
<T id="permissions.manage" />
|
<T id="permissions.manage" />
|
||||||
@@ -76,7 +106,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value="view"
|
value="view"
|
||||||
checked={field.value === "view"}
|
checked={field.value === "view"}
|
||||||
onChange={() => form.setFieldValue(field.name, "view")}
|
onChange={() => handleChange(form, field, "view")}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={`${field.name}-view`} className={getClasses(isView)}>
|
<label htmlFor={`${field.name}-view`} className={getClasses(isView)}>
|
||||||
<T id="permissions.view" />
|
<T id="permissions.view" />
|
||||||
@@ -89,7 +119,8 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value="hidden"
|
value="hidden"
|
||||||
checked={field.value === "hidden"}
|
checked={field.value === "hidden"}
|
||||||
onChange={() => form.setFieldValue(field.name, "hidden")}
|
disabled={hiddenDisabled}
|
||||||
|
onChange={() => handleChange(form, field, "hidden")}
|
||||||
/>
|
/>
|
||||||
<label htmlFor={`${field.name}-hidden`} className={getClasses(isHidden)}>
|
<label htmlFor={`${field.name}-hidden`} className={getClasses(isHidden)}>
|
||||||
<T id="permissions.hidden" />
|
<T id="permissions.hidden" />
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ import {
|
|||||||
AccessField,
|
AccessField,
|
||||||
Button,
|
Button,
|
||||||
DomainNamesField,
|
DomainNamesField,
|
||||||
|
HasPermission,
|
||||||
Loading,
|
Loading,
|
||||||
LocationsFields,
|
LocationsFields,
|
||||||
NginxConfigField,
|
NginxConfigField,
|
||||||
SSLCertificateField,
|
SSLCertificateField,
|
||||||
SSLOptionsFields,
|
SSLOptionsFields,
|
||||||
} from "src/components";
|
} from "src/components";
|
||||||
import { useProxyHost, useSetProxyHost } from "src/hooks";
|
import { useProxyHost, useSetProxyHost, useUser } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
|
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
|
||||||
import { validateNumber, validateString } from "src/modules/Validations";
|
import { validateNumber, validateString } from "src/modules/Validations";
|
||||||
import { showObjectSuccess } from "src/notifications";
|
import { showObjectSuccess } from "src/notifications";
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ interface Props extends InnerModalProps {
|
|||||||
id: number | "new";
|
id: number | "new";
|
||||||
}
|
}
|
||||||
const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||||
|
const { data: currentUser, isLoading: userIsLoading, error: userError } = useUser("me");
|
||||||
const { data, isLoading, error } = useProxyHost(id);
|
const { data, isLoading, error } = useProxyHost(id);
|
||||||
const { mutate: setProxyHost } = useSetProxyHost();
|
const { mutate: setProxyHost } = useSetProxyHost();
|
||||||
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
|
const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
|
||||||
@@ -58,13 +61,13 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal show={visible} onHide={remove}>
|
<Modal show={visible} onHide={remove}>
|
||||||
{!isLoading && error && (
|
{!isLoading && (error || userError) && (
|
||||||
<Alert variant="danger" className="m-3">
|
<Alert variant="danger" className="m-3">
|
||||||
{error?.message || "Unknown error"}
|
{error?.message || userError?.message || "Unknown error"}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{isLoading && <Loading noLogo />}
|
{isLoading || (userIsLoading && <Loading noLogo />)}
|
||||||
{!isLoading && data && (
|
{!isLoading && !userIsLoading && data && currentUser && (
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={
|
initialValues={
|
||||||
{
|
{
|
||||||
@@ -349,16 +352,18 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
|||||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||||
<T id="cancel" />
|
<T id="cancel" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<HasPermission section={PROXY_HOSTS} permission={MANAGE} hideError>
|
||||||
type="submit"
|
<Button
|
||||||
actionType="primary"
|
type="submit"
|
||||||
className="ms-auto bg-lime"
|
actionType="primary"
|
||||||
data-bs-dismiss="modal"
|
className="ms-auto bg-lime"
|
||||||
isLoading={isSubmitting}
|
data-bs-dismiss="modal"
|
||||||
disabled={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
>
|
disabled={isSubmitting}
|
||||||
<T id="save" />
|
>
|
||||||
</Button>
|
<T id="save" />
|
||||||
|
</Button>
|
||||||
|
</HasPermission>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
|||||||
49
frontend/src/modules/Permissions.ts
Normal file
49
frontend/src/modules/Permissions.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import type { UserPermissions } from "src/api/backend";
|
||||||
|
|
||||||
|
export const ADMIN = "admin";
|
||||||
|
export const VISIBILITY = "visibility";
|
||||||
|
export const PROXY_HOSTS = "proxyHosts";
|
||||||
|
export const REDIRECTION_HOSTS = "redirectionHosts";
|
||||||
|
export const DEAD_HOSTS = "deadHosts";
|
||||||
|
export const STREAMS = "streams";
|
||||||
|
export const CERTIFICATES = "certificates";
|
||||||
|
export const ACCESS_LISTS = "accessLists";
|
||||||
|
|
||||||
|
export const MANAGE = "manage";
|
||||||
|
export const VIEW = "view";
|
||||||
|
export const HIDDEN = "hidden";
|
||||||
|
|
||||||
|
export const ALL = "all";
|
||||||
|
export const USER = "user";
|
||||||
|
|
||||||
|
export type Section =
|
||||||
|
| typeof ADMIN
|
||||||
|
| typeof VISIBILITY
|
||||||
|
| typeof PROXY_HOSTS
|
||||||
|
| typeof REDIRECTION_HOSTS
|
||||||
|
| typeof DEAD_HOSTS
|
||||||
|
| typeof STREAMS
|
||||||
|
| typeof CERTIFICATES
|
||||||
|
| typeof ACCESS_LISTS;
|
||||||
|
|
||||||
|
export type Permission = typeof MANAGE | typeof VIEW;
|
||||||
|
|
||||||
|
const hasPermission = (
|
||||||
|
section: Section,
|
||||||
|
perm: Permission,
|
||||||
|
userPerms: UserPermissions | undefined,
|
||||||
|
roles: string[] | undefined,
|
||||||
|
): boolean => {
|
||||||
|
if (!userPerms) return false;
|
||||||
|
if (isAdmin(roles)) return true;
|
||||||
|
const acceptable = [MANAGE, perm];
|
||||||
|
// @ts-expect-error 7053
|
||||||
|
const v = typeof userPerms[section] !== "undefined" ? userPerms[section] : HIDDEN;
|
||||||
|
return acceptable.indexOf(v) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isAdmin = (roles: string[] | undefined): boolean => {
|
||||||
|
return roles?.includes("admin") || false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { hasPermission, isAdmin };
|
||||||
@@ -2,9 +2,10 @@ import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react";
|
|||||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import type { AccessList } from "src/api/backend";
|
import type { AccessList } from "src/api/backend";
|
||||||
import { EmptyData, GravatarFormatter, ValueWithDateFormatter } from "src/components";
|
import { EmptyData, GravatarFormatter, HasPermission, ValueWithDateFormatter } from "src/components";
|
||||||
import { TableLayout } from "src/components/Table/TableLayout";
|
import { TableLayout } from "src/components/Table/TableLayout";
|
||||||
import { intl, T } from "src/locale";
|
import { intl, T } from "src/locale";
|
||||||
|
import { ACCESS_LISTS, MANAGE } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: AccessList[];
|
data: AccessList[];
|
||||||
@@ -84,18 +85,20 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
|||||||
<IconEdit size={16} />
|
<IconEdit size={16} />
|
||||||
<T id="action.edit" />
|
<T id="action.edit" />
|
||||||
</a>
|
</a>
|
||||||
<div className="dropdown-divider" />
|
<HasPermission section={ACCESS_LISTS} permission={MANAGE} hideError>
|
||||||
<a
|
<div className="dropdown-divider" />
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDelete?.(info.row.original.id);
|
e.preventDefault();
|
||||||
}}
|
onDelete?.(info.row.original.id);
|
||||||
>
|
}}
|
||||||
<IconTrash size={16} />
|
>
|
||||||
<T id="action.delete" />
|
<IconTrash size={16} />
|
||||||
</a>
|
<T id="action.delete" />
|
||||||
|
</a>
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -130,6 +133,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
|||||||
onNew={onNew}
|
onNew={onNew}
|
||||||
isFiltered={isFiltered}
|
isFiltered={isFiltered}
|
||||||
color="cyan"
|
color="cyan"
|
||||||
|
permissionSection={ACCESS_LISTS}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { IconHelp, IconSearch } from "@tabler/icons-react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Alert from "react-bootstrap/Alert";
|
import Alert from "react-bootstrap/Alert";
|
||||||
import { deleteAccessList } from "src/api/backend";
|
import { deleteAccessList } from "src/api/backend";
|
||||||
import { Button, LoadingPage } from "src/components";
|
import { Button, HasPermission, LoadingPage } from "src/components";
|
||||||
import { useAccessLists } from "src/hooks";
|
import { useAccessLists } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
import { showAccessListModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
|
import { showAccessListModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
|
||||||
|
import { ACCESS_LISTS, MANAGE } from "src/modules/Permissions";
|
||||||
import { showObjectSuccess } from "src/notifications";
|
import { showObjectSuccess } from "src/notifications";
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
|
|
||||||
@@ -67,11 +68,17 @@ export default function TableWrapper() {
|
|||||||
<Button size="sm" onClick={() => showHelpModal("AccessLists", "cyan")}>
|
<Button size="sm" onClick={() => showHelpModal("AccessLists", "cyan")}>
|
||||||
<IconHelp size={20} />
|
<IconHelp size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
{data?.length ? (
|
<HasPermission section={ACCESS_LISTS} permission={MANAGE} hideError>
|
||||||
<Button size="sm" className="btn-cyan" onClick={() => showAccessListModal("new")}>
|
{data?.length ? (
|
||||||
<T id="object.add" tData={{ object: "access-list" }} />
|
<Button
|
||||||
</Button>
|
size="sm"
|
||||||
) : null}
|
className="btn-cyan"
|
||||||
|
onClick={() => showAccessListModal("new")}
|
||||||
|
>
|
||||||
|
<T id="object.add" tData={{ object: "access-list" }} />
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { ACCESS_LISTS, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const Access = () => {
|
const Access = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="accessLists" type="view" pageLoading loadingNoLogo>
|
<HasPermission section={ACCESS_LISTS} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { ADMIN, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const AuditLog = () => {
|
const AuditLog = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="admin" type="manage" pageLoading loadingNoLogo>
|
<HasPermission section={ADMIN} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import {
|
|||||||
DomainsFormatter,
|
DomainsFormatter,
|
||||||
EmptyData,
|
EmptyData,
|
||||||
GravatarFormatter,
|
GravatarFormatter,
|
||||||
|
HasPermission,
|
||||||
} from "src/components";
|
} from "src/components";
|
||||||
import { TableLayout } from "src/components/Table/TableLayout";
|
import { TableLayout } from "src/components/Table/TableLayout";
|
||||||
import { intl, T } from "src/locale";
|
import { intl, T } from "src/locale";
|
||||||
import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals";
|
import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals";
|
||||||
|
import { CERTIFICATES, MANAGE } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: Certificate[];
|
data: Certificate[];
|
||||||
@@ -125,29 +127,31 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
|
|||||||
<IconRefresh size={16} />
|
<IconRefresh size={16} />
|
||||||
<T id="action.renew" />
|
<T id="action.renew" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<HasPermission section={CERTIFICATES} permission={MANAGE} hideError>
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDownload?.(info.row.original.id);
|
e.preventDefault();
|
||||||
}}
|
onDownload?.(info.row.original.id);
|
||||||
>
|
}}
|
||||||
<IconDownload size={16} />
|
>
|
||||||
<T id="action.download" />
|
<IconDownload size={16} />
|
||||||
</a>
|
<T id="action.download" />
|
||||||
<div className="dropdown-divider" />
|
</a>
|
||||||
<a
|
<div className="dropdown-divider" />
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDelete?.(info.row.original.id);
|
e.preventDefault();
|
||||||
}}
|
onDelete?.(info.row.original.id);
|
||||||
>
|
}}
|
||||||
<IconTrash size={16} />
|
>
|
||||||
<T id="action.delete" />
|
<IconTrash size={16} />
|
||||||
</a>
|
<T id="action.delete" />
|
||||||
|
</a>
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -223,6 +227,7 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
|
|||||||
isFiltered={isFiltered}
|
isFiltered={isFiltered}
|
||||||
color="pink"
|
color="pink"
|
||||||
customAddBtn={customAddBtn}
|
customAddBtn={customAddBtn}
|
||||||
|
permissionSection={CERTIFICATES}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { IconHelp, IconSearch } from "@tabler/icons-react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Alert from "react-bootstrap/Alert";
|
import Alert from "react-bootstrap/Alert";
|
||||||
import { deleteCertificate, downloadCertificate } from "src/api/backend";
|
import { deleteCertificate, downloadCertificate } from "src/api/backend";
|
||||||
import { Button, LoadingPage } from "src/components";
|
import { Button, HasPermission, LoadingPage } from "src/components";
|
||||||
import { useCertificates } from "src/hooks";
|
import { useCertificates } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
showHTTPCertificateModal,
|
showHTTPCertificateModal,
|
||||||
showRenewCertificateModal,
|
showRenewCertificateModal,
|
||||||
} from "src/modals";
|
} from "src/modals";
|
||||||
|
import { CERTIFICATES, MANAGE } from "src/modules/Permissions";
|
||||||
import { showError, showObjectSuccess } from "src/notifications";
|
import { showError, showObjectSuccess } from "src/notifications";
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
|
|
||||||
@@ -70,7 +71,6 @@ export default function TableWrapper() {
|
|||||||
<T id="certificates" />
|
<T id="certificates" />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-auto col-sm-12">
|
<div className="col-md-auto col-sm-12">
|
||||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||||
{data?.length ? (
|
{data?.length ? (
|
||||||
@@ -90,50 +90,52 @@ export default function TableWrapper() {
|
|||||||
<Button size="sm" onClick={() => showHelpModal("Certificates", "pink")}>
|
<Button size="sm" onClick={() => showHelpModal("Certificates", "pink")}>
|
||||||
<IconHelp size={20} />
|
<IconHelp size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
{data?.length ? (
|
<HasPermission section={CERTIFICATES} permission={MANAGE} hideError>
|
||||||
<div className="dropdown">
|
{data?.length ? (
|
||||||
<button
|
<div className="dropdown">
|
||||||
type="button"
|
<button
|
||||||
className="btn btn-sm dropdown-toggle btn-pink mt-1"
|
type="button"
|
||||||
data-bs-toggle="dropdown"
|
className="btn btn-sm dropdown-toggle btn-pink mt-1"
|
||||||
>
|
data-bs-toggle="dropdown"
|
||||||
<T id="object.add" tData={{ object: "certificate" }} />
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-menu">
|
|
||||||
<a
|
|
||||||
className="dropdown-item"
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
showHTTPCertificateModal();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<T id="lets-encrypt-via-http" />
|
<T id="object.add" tData={{ object: "certificate" }} />
|
||||||
</a>
|
</button>
|
||||||
<a
|
<div className="dropdown-menu">
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
showDNSCertificateModal();
|
e.preventDefault();
|
||||||
}}
|
showHTTPCertificateModal();
|
||||||
>
|
}}
|
||||||
<T id="lets-encrypt-via-dns" />
|
>
|
||||||
</a>
|
<T id="lets-encrypt-via-http" />
|
||||||
<div className="dropdown-divider" />
|
</a>
|
||||||
<a
|
<a
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
href="#"
|
href="#"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showCustomCertificateModal();
|
showDNSCertificateModal();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<T id="certificates.custom" />
|
<T id="lets-encrypt-via-dns" />
|
||||||
</a>
|
</a>
|
||||||
|
<div className="dropdown-divider" />
|
||||||
|
<a
|
||||||
|
className="dropdown-item"
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
showCustomCertificateModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<T id="certificates.custom" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : null}
|
||||||
) : null}
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { CERTIFICATES, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const Certificates = () => {
|
const Certificates = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="certificates" type="view" pageLoading loadingNoLogo>
|
<HasPermission section={CERTIFICATES} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
import { useHostReport } from "src/hooks";
|
import { useHostReport } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
|
import { DEAD_HOSTS, PROXY_HOSTS, REDIRECTION_HOSTS, STREAMS, VIEW } from "src/modules/Permissions";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { data: hostReport } = useHostReport();
|
const { data: hostReport } = useHostReport();
|
||||||
@@ -16,7 +17,7 @@ const Dashboard = () => {
|
|||||||
<div className="row row-deck row-cards">
|
<div className="row row-deck row-cards">
|
||||||
<div className="col-12 my-4">
|
<div className="col-12 my-4">
|
||||||
<div className="row row-cards">
|
<div className="row row-cards">
|
||||||
<HasPermission permission="proxyHosts" type="view" hideError>
|
<HasPermission section={PROXY_HOSTS} permission={VIEW} hideError>
|
||||||
<div className="col-sm-6 col-lg-3">
|
<div className="col-sm-6 col-lg-3">
|
||||||
<a
|
<a
|
||||||
href="/nginx/proxy"
|
href="/nginx/proxy"
|
||||||
@@ -43,7 +44,7 @@ const Dashboard = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
<HasPermission permission="redirectionHosts" type="view" hideError>
|
<HasPermission section={REDIRECTION_HOSTS} permission={VIEW} hideError>
|
||||||
<div className="col-sm-6 col-lg-3">
|
<div className="col-sm-6 col-lg-3">
|
||||||
<a
|
<a
|
||||||
href="/nginx/redirection"
|
href="/nginx/redirection"
|
||||||
@@ -71,7 +72,7 @@ const Dashboard = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
<HasPermission permission="streams" type="view" hideError>
|
<HasPermission section={STREAMS} permission={VIEW} hideError>
|
||||||
<div className="col-sm-6 col-lg-3">
|
<div className="col-sm-6 col-lg-3">
|
||||||
<a
|
<a
|
||||||
href="/nginx/stream"
|
href="/nginx/stream"
|
||||||
@@ -96,7 +97,7 @@ const Dashboard = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
<HasPermission permission="deadHosts" type="view" hideError>
|
<HasPermission section={DEAD_HOSTS} permission={VIEW} hideError>
|
||||||
<div className="col-sm-6 col-lg-3">
|
<div className="col-sm-6 col-lg-3">
|
||||||
<a
|
<a
|
||||||
href="/nginx/404"
|
href="/nginx/404"
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import {
|
|||||||
DomainsFormatter,
|
DomainsFormatter,
|
||||||
EmptyData,
|
EmptyData,
|
||||||
GravatarFormatter,
|
GravatarFormatter,
|
||||||
|
HasPermission,
|
||||||
TrueFalseFormatter,
|
TrueFalseFormatter,
|
||||||
} from "src/components";
|
} from "src/components";
|
||||||
import { TableLayout } from "src/components/Table/TableLayout";
|
import { TableLayout } from "src/components/Table/TableLayout";
|
||||||
import { intl, T } from "src/locale";
|
import { intl, T } from "src/locale";
|
||||||
|
import { DEAD_HOSTS, MANAGE } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: DeadHost[];
|
data: DeadHost[];
|
||||||
@@ -89,29 +91,31 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
|||||||
<IconEdit size={16} />
|
<IconEdit size={16} />
|
||||||
<T id="action.edit" />
|
<T id="action.edit" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<HasPermission section={DEAD_HOSTS} permission={MANAGE} hideError>
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
e.preventDefault();
|
||||||
}}
|
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
||||||
>
|
}}
|
||||||
<IconPower size={16} />
|
>
|
||||||
<T id={info.row.original.enabled ? "action.disable" : "action.enable"} />
|
<IconPower size={16} />
|
||||||
</a>
|
<T id={info.row.original.enabled ? "action.disable" : "action.enable"} />
|
||||||
<div className="dropdown-divider" />
|
</a>
|
||||||
<a
|
<div className="dropdown-divider" />
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDelete?.(info.row.original.id);
|
e.preventDefault();
|
||||||
}}
|
onDelete?.(info.row.original.id);
|
||||||
>
|
}}
|
||||||
<IconTrash size={16} />
|
>
|
||||||
<T id="action.delete" />
|
<IconTrash size={16} />
|
||||||
</a>
|
<T id="action.delete" />
|
||||||
|
</a>
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -146,6 +150,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
|||||||
onNew={onNew}
|
onNew={onNew}
|
||||||
isFiltered={isFiltered}
|
isFiltered={isFiltered}
|
||||||
color="red"
|
color="red"
|
||||||
|
permissionSection={DEAD_HOSTS}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Alert from "react-bootstrap/Alert";
|
import Alert from "react-bootstrap/Alert";
|
||||||
import { deleteDeadHost, toggleDeadHost } from "src/api/backend";
|
import { deleteDeadHost, toggleDeadHost } from "src/api/backend";
|
||||||
import { Button, LoadingPage } from "src/components";
|
import { Button, HasPermission, LoadingPage } from "src/components";
|
||||||
import { useDeadHosts } from "src/hooks";
|
import { useDeadHosts } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
import { showDeadHostModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
|
import { showDeadHostModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
|
||||||
|
import { DEAD_HOSTS, MANAGE } from "src/modules/Permissions";
|
||||||
import { showObjectSuccess } from "src/notifications";
|
import { showObjectSuccess } from "src/notifications";
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
|
|
||||||
@@ -76,11 +77,13 @@ export default function TableWrapper() {
|
|||||||
<Button size="sm" onClick={() => showHelpModal("DeadHosts", "red")}>
|
<Button size="sm" onClick={() => showHelpModal("DeadHosts", "red")}>
|
||||||
<IconHelp size={20} />
|
<IconHelp size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
{data?.length ? (
|
<HasPermission section={DEAD_HOSTS} permission={MANAGE} hideError>
|
||||||
<Button size="sm" className="btn-red" onClick={() => showDeadHostModal("new")}>
|
{data?.length ? (
|
||||||
<T id="object.add" tData={{ object: "dead-host" }} />
|
<Button size="sm" className="btn-red" onClick={() => showDeadHostModal("new")}>
|
||||||
</Button>
|
<T id="object.add" tData={{ object: "dead-host" }} />
|
||||||
) : null}
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { DEAD_HOSTS, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const DeadHosts = () => {
|
const DeadHosts = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="deadHosts" type="view" pageLoading loadingNoLogo>
|
<HasPermission section={DEAD_HOSTS} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import {
|
|||||||
DomainsFormatter,
|
DomainsFormatter,
|
||||||
EmptyData,
|
EmptyData,
|
||||||
GravatarFormatter,
|
GravatarFormatter,
|
||||||
|
HasPermission,
|
||||||
TrueFalseFormatter,
|
TrueFalseFormatter,
|
||||||
} from "src/components";
|
} from "src/components";
|
||||||
import { TableLayout } from "src/components/Table/TableLayout";
|
import { TableLayout } from "src/components/Table/TableLayout";
|
||||||
import { intl, T } from "src/locale";
|
import { intl, T } from "src/locale";
|
||||||
|
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: ProxyHost[];
|
data: ProxyHost[];
|
||||||
@@ -105,29 +107,31 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
|||||||
<IconEdit size={16} />
|
<IconEdit size={16} />
|
||||||
<T id="action.edit" />
|
<T id="action.edit" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<HasPermission section={PROXY_HOSTS} permission={MANAGE} hideError>
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
e.preventDefault();
|
||||||
}}
|
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
||||||
>
|
}}
|
||||||
<IconPower size={16} />
|
>
|
||||||
<T id={info.row.original.enabled ? "action.disable" : "action.enable"} />
|
<IconPower size={16} />
|
||||||
</a>
|
<T id={info.row.original.enabled ? "action.disable" : "action.enable"} />
|
||||||
<div className="dropdown-divider" />
|
</a>
|
||||||
<a
|
<div className="dropdown-divider" />
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDelete?.(info.row.original.id);
|
e.preventDefault();
|
||||||
}}
|
onDelete?.(info.row.original.id);
|
||||||
>
|
}}
|
||||||
<IconTrash size={16} />
|
>
|
||||||
<T id="action.delete" />
|
<IconTrash size={16} />
|
||||||
</a>
|
<T id="action.delete" />
|
||||||
|
</a>
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -162,6 +166,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
|||||||
onNew={onNew}
|
onNew={onNew}
|
||||||
isFiltered={isFiltered}
|
isFiltered={isFiltered}
|
||||||
color="lime"
|
color="lime"
|
||||||
|
permissionSection={PROXY_HOSTS}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Alert from "react-bootstrap/Alert";
|
import Alert from "react-bootstrap/Alert";
|
||||||
import { deleteProxyHost, toggleProxyHost } from "src/api/backend";
|
import { deleteProxyHost, toggleProxyHost } from "src/api/backend";
|
||||||
import { Button, LoadingPage } from "src/components";
|
import { Button, HasPermission, LoadingPage } from "src/components";
|
||||||
import { useProxyHosts } from "src/hooks";
|
import { useProxyHosts } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
import { showDeleteConfirmModal, showHelpModal, showProxyHostModal } from "src/modals";
|
import { showDeleteConfirmModal, showHelpModal, showProxyHostModal } from "src/modals";
|
||||||
|
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
|
||||||
import { showObjectSuccess } from "src/notifications";
|
import { showObjectSuccess } from "src/notifications";
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
|
|
||||||
@@ -59,7 +60,6 @@ export default function TableWrapper() {
|
|||||||
<T id="proxy-hosts" />
|
<T id="proxy-hosts" />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-auto col-sm-12">
|
<div className="col-md-auto col-sm-12">
|
||||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||||
{data?.length ? (
|
{data?.length ? (
|
||||||
@@ -79,11 +79,17 @@ export default function TableWrapper() {
|
|||||||
<Button size="sm" onClick={() => showHelpModal("ProxyHosts", "lime")}>
|
<Button size="sm" onClick={() => showHelpModal("ProxyHosts", "lime")}>
|
||||||
<IconHelp size={20} />
|
<IconHelp size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
{data?.length ? (
|
<HasPermission section={PROXY_HOSTS} permission={MANAGE} hideError>
|
||||||
<Button size="sm" className="btn-lime" onClick={() => showProxyHostModal("new")}>
|
{data?.length ? (
|
||||||
<T id="object.add" tData={{ object: "proxy-host" }} />
|
<Button
|
||||||
</Button>
|
size="sm"
|
||||||
) : null}
|
className="btn-lime"
|
||||||
|
onClick={() => showProxyHostModal("new")}
|
||||||
|
>
|
||||||
|
<T id="object.add" tData={{ object: "proxy-host" }} />
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { PROXY_HOSTS, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const ProxyHosts = () => {
|
const ProxyHosts = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="proxyHosts" type="view" pageLoading loadingNoLogo>
|
<HasPermission section={PROXY_HOSTS} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import {
|
|||||||
DomainsFormatter,
|
DomainsFormatter,
|
||||||
EmptyData,
|
EmptyData,
|
||||||
GravatarFormatter,
|
GravatarFormatter,
|
||||||
|
HasPermission,
|
||||||
TrueFalseFormatter,
|
TrueFalseFormatter,
|
||||||
} from "src/components";
|
} from "src/components";
|
||||||
import { TableLayout } from "src/components/Table/TableLayout";
|
import { TableLayout } from "src/components/Table/TableLayout";
|
||||||
import { intl, T } from "src/locale";
|
import { intl, T } from "src/locale";
|
||||||
|
import { MANAGE, REDIRECTION_HOSTS } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: RedirectionHost[];
|
data: RedirectionHost[];
|
||||||
@@ -110,29 +112,31 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
|||||||
<IconEdit size={16} />
|
<IconEdit size={16} />
|
||||||
<T id="action.edit" />
|
<T id="action.edit" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<HasPermission section={REDIRECTION_HOSTS} permission={MANAGE} hideError>
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
e.preventDefault();
|
||||||
}}
|
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
||||||
>
|
}}
|
||||||
<IconPower size={16} />
|
>
|
||||||
<T id={info.row.original.enabled ? "action.disable" : "action.enable"} />
|
<IconPower size={16} />
|
||||||
</a>
|
<T id={info.row.original.enabled ? "action.disable" : "action.enable"} />
|
||||||
<div className="dropdown-divider" />
|
</a>
|
||||||
<a
|
<div className="dropdown-divider" />
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDelete?.(info.row.original.id);
|
e.preventDefault();
|
||||||
}}
|
onDelete?.(info.row.original.id);
|
||||||
>
|
}}
|
||||||
<IconTrash size={16} />
|
>
|
||||||
<T id="action.delete" />
|
<IconTrash size={16} />
|
||||||
</a>
|
<T id="action.delete" />
|
||||||
|
</a>
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -167,6 +171,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
|||||||
onNew={onNew}
|
onNew={onNew}
|
||||||
isFiltered={isFiltered}
|
isFiltered={isFiltered}
|
||||||
color="yellow"
|
color="yellow"
|
||||||
|
permissionSection={REDIRECTION_HOSTS}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Alert from "react-bootstrap/Alert";
|
import Alert from "react-bootstrap/Alert";
|
||||||
import { deleteRedirectionHost, toggleRedirectionHost } from "src/api/backend";
|
import { deleteRedirectionHost, toggleRedirectionHost } from "src/api/backend";
|
||||||
import { Button, LoadingPage } from "src/components";
|
import { Button, HasPermission, LoadingPage } from "src/components";
|
||||||
import { useRedirectionHosts } from "src/hooks";
|
import { useRedirectionHosts } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
import { showDeleteConfirmModal, showHelpModal, showRedirectionHostModal } from "src/modals";
|
import { showDeleteConfirmModal, showHelpModal, showRedirectionHostModal } from "src/modals";
|
||||||
|
import { MANAGE, REDIRECTION_HOSTS } from "src/modules/Permissions";
|
||||||
import { showObjectSuccess } from "src/notifications";
|
import { showObjectSuccess } from "src/notifications";
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
|
|
||||||
@@ -59,7 +60,6 @@ export default function TableWrapper() {
|
|||||||
<T id="redirection-hosts" />
|
<T id="redirection-hosts" />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-auto col-sm-12">
|
<div className="col-md-auto col-sm-12">
|
||||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||||
{data?.length ? (
|
{data?.length ? (
|
||||||
@@ -79,15 +79,17 @@ export default function TableWrapper() {
|
|||||||
<Button size="sm" onClick={() => showHelpModal("RedirectionHosts", "yellow")}>
|
<Button size="sm" onClick={() => showHelpModal("RedirectionHosts", "yellow")}>
|
||||||
<IconHelp size={20} />
|
<IconHelp size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
{data?.length ? (
|
<HasPermission section={REDIRECTION_HOSTS} permission={MANAGE} hideError>
|
||||||
<Button
|
{data?.length ? (
|
||||||
size="sm"
|
<Button
|
||||||
className="btn-yellow"
|
size="sm"
|
||||||
onClick={() => showRedirectionHostModal("new")}
|
className="btn-yellow"
|
||||||
>
|
onClick={() => showRedirectionHostModal("new")}
|
||||||
<T id="object.add" tData={{ object: "redirection-host" }} />
|
>
|
||||||
</Button>
|
<T id="object.add" tData={{ object: "redirection-host" }} />
|
||||||
) : null}
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { REDIRECTION_HOSTS, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const RedirectionHosts = () => {
|
const RedirectionHosts = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="redirectionHosts" type="view" pageLoading loadingNoLogo>
|
<HasPermission section={REDIRECTION_HOSTS} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import {
|
|||||||
CertificateFormatter,
|
CertificateFormatter,
|
||||||
EmptyData,
|
EmptyData,
|
||||||
GravatarFormatter,
|
GravatarFormatter,
|
||||||
|
HasPermission,
|
||||||
TrueFalseFormatter,
|
TrueFalseFormatter,
|
||||||
ValueWithDateFormatter,
|
ValueWithDateFormatter,
|
||||||
} from "src/components";
|
} from "src/components";
|
||||||
import { TableLayout } from "src/components/Table/TableLayout";
|
import { TableLayout } from "src/components/Table/TableLayout";
|
||||||
import { intl, T } from "src/locale";
|
import { intl, T } from "src/locale";
|
||||||
|
import { MANAGE, STREAMS } from "src/modules/Permissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: Stream[];
|
data: Stream[];
|
||||||
@@ -118,29 +120,31 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
|||||||
<IconEdit size={16} />
|
<IconEdit size={16} />
|
||||||
<T id="action.edit" />
|
<T id="action.edit" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<HasPermission section={STREAMS} permission={MANAGE} hideError>
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
e.preventDefault();
|
||||||
}}
|
onDisableToggle?.(info.row.original.id, !info.row.original.enabled);
|
||||||
>
|
}}
|
||||||
<IconPower size={16} />
|
>
|
||||||
<T id="action.disable" />
|
<IconPower size={16} />
|
||||||
</a>
|
<T id="action.disable" />
|
||||||
<div className="dropdown-divider" />
|
</a>
|
||||||
<a
|
<div className="dropdown-divider" />
|
||||||
className="dropdown-item"
|
<a
|
||||||
href="#"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
href="#"
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
onDelete?.(info.row.original.id);
|
e.preventDefault();
|
||||||
}}
|
onDelete?.(info.row.original.id);
|
||||||
>
|
}}
|
||||||
<IconTrash size={16} />
|
>
|
||||||
<T id="action.delete" />
|
<IconTrash size={16} />
|
||||||
</a>
|
<T id="action.delete" />
|
||||||
|
</a>
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -175,6 +179,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
|||||||
onNew={onNew}
|
onNew={onNew}
|
||||||
isFiltered={isFiltered}
|
isFiltered={isFiltered}
|
||||||
color="blue"
|
color="blue"
|
||||||
|
permissionSection={STREAMS}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Alert from "react-bootstrap/Alert";
|
import Alert from "react-bootstrap/Alert";
|
||||||
import { deleteStream, toggleStream } from "src/api/backend";
|
import { deleteStream, toggleStream } from "src/api/backend";
|
||||||
import { Button, LoadingPage } from "src/components";
|
import { Button, HasPermission, LoadingPage } from "src/components";
|
||||||
import { useStreams } from "src/hooks";
|
import { useStreams } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
import { showDeleteConfirmModal, showHelpModal, showStreamModal } from "src/modals";
|
import { showDeleteConfirmModal, showHelpModal, showStreamModal } from "src/modals";
|
||||||
|
import { MANAGE, STREAMS } from "src/modules/Permissions";
|
||||||
import { showObjectSuccess } from "src/notifications";
|
import { showObjectSuccess } from "src/notifications";
|
||||||
import Table from "./Table";
|
import Table from "./Table";
|
||||||
|
|
||||||
@@ -61,7 +62,6 @@ export default function TableWrapper() {
|
|||||||
<T id="streams" />
|
<T id="streams" />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-auto col-sm-12">
|
<div className="col-md-auto col-sm-12">
|
||||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||||
{data?.length ? (
|
{data?.length ? (
|
||||||
@@ -81,11 +81,13 @@ export default function TableWrapper() {
|
|||||||
<Button size="sm" onClick={() => showHelpModal("Streams", "blue")}>
|
<Button size="sm" onClick={() => showHelpModal("Streams", "blue")}>
|
||||||
<IconHelp size={20} />
|
<IconHelp size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
{data?.length ? (
|
<HasPermission section={STREAMS} permission={MANAGE} hideError>
|
||||||
<Button size="sm" className="btn-blue" onClick={() => showStreamModal("new")}>
|
{data?.length ? (
|
||||||
<T id="object.add" tData={{ object: "stream" }} />
|
<Button size="sm" className="btn-blue" onClick={() => showStreamModal("new")}>
|
||||||
</Button>
|
<T id="object.add" tData={{ object: "stream" }} />
|
||||||
) : null}
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</HasPermission>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { STREAMS, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const Streams = () => {
|
const Streams = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="streams" type="view" pageLoading loadingNoLogo>
|
<HasPermission section={STREAMS} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { ADMIN, VIEW } from "src/modules/Permissions";
|
||||||
import Layout from "./Layout";
|
import Layout from "./Layout";
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="admin" type="manage" pageLoading loadingNoLogo>
|
<HasPermission section={ADMIN} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<Layout />
|
<Layout />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { HasPermission } from "src/components";
|
import { HasPermission } from "src/components";
|
||||||
|
import { ADMIN, VIEW } from "src/modules/Permissions";
|
||||||
import TableWrapper from "./TableWrapper";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const Users = () => {
|
const Users = () => {
|
||||||
return (
|
return (
|
||||||
<HasPermission permission="admin" type="manage" pageLoading loadingNoLogo>
|
<HasPermission section={ADMIN} permission={VIEW} pageLoading loadingNoLogo>
|
||||||
<TableWrapper />
|
<TableWrapper />
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user