mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-11-01 00:03:33 +00:00
More react
- consolidated lang items - proxy host paths work
This commit is contained in:
@@ -97,6 +97,14 @@ export interface Certificate {
|
||||
redirectionHosts?: RedirectionHost[];
|
||||
}
|
||||
|
||||
export interface ProxyLocation {
|
||||
path: string;
|
||||
advancedConfig: string;
|
||||
forwardScheme: string;
|
||||
forwardHost: string;
|
||||
forwardPort: number;
|
||||
}
|
||||
|
||||
export interface ProxyHost {
|
||||
id: number;
|
||||
createdOn: string;
|
||||
@@ -116,7 +124,7 @@ export interface ProxyHost {
|
||||
allowWebsocketUpgrade: boolean;
|
||||
http2Support: boolean;
|
||||
enabled: boolean;
|
||||
locations?: string[]; // todo: string or object?
|
||||
locations?: ProxyLocation[];
|
||||
hstsEnabled: boolean;
|
||||
hstsSubdomains: boolean;
|
||||
// Expansions:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import cn from "classnames";
|
||||
import type { ReactNode } from "react";
|
||||
import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
|
||||
@@ -6,8 +8,12 @@ interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNew?: () => void;
|
||||
isFiltered?: boolean;
|
||||
object: string;
|
||||
objects: string;
|
||||
color?: string;
|
||||
customAddBtn?: ReactNode;
|
||||
}
|
||||
export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
|
||||
function EmptyData({ tableInstance, onNew, isFiltered, object, objects, color = "primary", customAddBtn }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
@@ -19,14 +25,18 @@ export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
|
||||
) : (
|
||||
<>
|
||||
<h2>
|
||||
<T id="access.empty" />
|
||||
<T id="object.empty" tData={{ objects }} />
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
<T id="empty-subtitle" />
|
||||
</p>
|
||||
<Button className="btn-cyan my-3" onClick={onNew}>
|
||||
<T id="access.add" />
|
||||
</Button>
|
||||
{customAddBtn ? (
|
||||
customAddBtn
|
||||
) : (
|
||||
<Button className={cn("my-3", `btn-${color}`)} onClick={onNew}>
|
||||
<T id="object.add" tData={{ object }} />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -34,3 +44,5 @@ export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export { EmptyData };
|
||||
@@ -12,7 +12,7 @@ export function ErrorNotFound() {
|
||||
<T id="notfound.title" />
|
||||
</p>
|
||||
<p className="empty-subtitle text-secondary">
|
||||
<T id="notfound.text" />
|
||||
<T id="notfound.content" />
|
||||
</p>
|
||||
<div className="empty-action">
|
||||
<Button type="button" size="md" onClick={() => navigate("/")}>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
|
||||
return (
|
||||
<>
|
||||
<p className="text-muted">
|
||||
<T id="access.help.rules-order" />
|
||||
<T id="access-list.help.rules-order" />
|
||||
</p>
|
||||
{values.map((client: AccessListClient, idx: number) => (
|
||||
<div className="row mb-1" key={idx}>
|
||||
@@ -101,7 +101,7 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
|
||||
</div>
|
||||
<div className="row mb-3">
|
||||
<p className="text-muted">
|
||||
<T id="access.help-rules-last" />
|
||||
<T id="access-list.help-rules-last" />
|
||||
</p>
|
||||
<div className="col-11">
|
||||
<div className="input-group mb-2">
|
||||
|
||||
@@ -31,7 +31,7 @@ interface Props {
|
||||
name?: string;
|
||||
label?: string;
|
||||
}
|
||||
export function AccessField({ name = "accessListId", label = "access.title", id = "accessListId" }: Props) {
|
||||
export function AccessField({ name = "accessListId", label = "access-list", id = "accessListId" }: Props) {
|
||||
const { isLoading, isError, error, data } = useAccessLists(["owner", "items", "clients"]);
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
@@ -44,7 +44,7 @@ export function AccessField({ name = "accessListId", label = "access.title", id
|
||||
value: item.id || 0,
|
||||
label: item.name,
|
||||
subLabel: intl.formatMessage(
|
||||
{ id: "access.subtitle" },
|
||||
{ id: "access-list.subtitle" },
|
||||
{
|
||||
users: item?.items?.length,
|
||||
rules: item?.clients?.length,
|
||||
@@ -57,7 +57,7 @@ export function AccessField({ name = "accessListId", label = "access.title", id
|
||||
// Public option
|
||||
options?.unshift({
|
||||
value: 0,
|
||||
label: intl.formatMessage({ id: "access.public" }),
|
||||
label: intl.formatMessage({ id: "access-list.public" }),
|
||||
subLabel: "No basic auth required",
|
||||
icon: <IconLockOpen2 size={14} className="text-red" />,
|
||||
});
|
||||
|
||||
3
frontend/src/components/Form/LocationsFields.module.css
Normal file
3
frontend/src/components/Form/LocationsFields.module.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.locationCard {
|
||||
border-color: light-dark(var(--tblr-gray-200), var(--tblr-gray-700)) !important;
|
||||
}
|
||||
185
frontend/src/components/Form/LocationsFields.tsx
Normal file
185
frontend/src/components/Form/LocationsFields.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import { IconSettings } from "@tabler/icons-react";
|
||||
import CodeEditor from "@uiw/react-textarea-code-editor";
|
||||
import cn from "classnames";
|
||||
import { useFormikContext } from "formik";
|
||||
import { useState } from "react";
|
||||
import type { ProxyLocation } from "src/api/backend";
|
||||
import { intl, T } from "src/locale";
|
||||
import styles from "./LocationsFields.module.css";
|
||||
|
||||
interface Props {
|
||||
initialValues: ProxyLocation[];
|
||||
name?: string;
|
||||
}
|
||||
export function LocationsFields({ initialValues, name = "items" }: Props) {
|
||||
const [values, setValues] = useState<ProxyLocation[]>(initialValues || []);
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [advVisible, setAdvVisible] = useState<number[]>([]);
|
||||
|
||||
const blankItem: ProxyLocation = {
|
||||
path: "",
|
||||
advancedConfig: "",
|
||||
forwardScheme: "http",
|
||||
forwardHost: "",
|
||||
forwardPort: 80,
|
||||
};
|
||||
|
||||
const toggleAdvVisible = (idx: number) => {
|
||||
setAdvVisible(advVisible.includes(idx) ? advVisible.filter((i) => i !== idx) : [...advVisible, idx]);
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
setValues([...values, blankItem]);
|
||||
};
|
||||
|
||||
const handleRemove = (idx: number) => {
|
||||
const newValues = values.filter((_: ProxyLocation, i: number) => i !== idx);
|
||||
setValues(newValues);
|
||||
setFormField(newValues);
|
||||
};
|
||||
|
||||
const handleChange = (idx: number, field: string, fieldValue: string) => {
|
||||
const newValues = values.map((v: ProxyLocation, i: number) => (i === idx ? { ...v, [field]: fieldValue } : v));
|
||||
setValues(newValues);
|
||||
setFormField(newValues);
|
||||
};
|
||||
|
||||
const setFormField = (newValues: ProxyLocation[]) => {
|
||||
const filtered = newValues.filter((v: ProxyLocation) => v?.path?.trim() !== "");
|
||||
setFieldValue(name, filtered);
|
||||
};
|
||||
|
||||
if (values.length === 0) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<button type="button" className="btn my-3" onClick={handleAdd}>
|
||||
<T id="action.add-location" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{values.map((item: ProxyLocation, idx: number) => (
|
||||
<div key={idx} className={cn("card", "card-active", "mb-3", styles.locationCard)}>
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col-md-10">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Location</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="/path"
|
||||
autoComplete="off"
|
||||
value={item.path}
|
||||
onChange={(e) => handleChange(idx, "path", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 text-end">
|
||||
<button
|
||||
type="button"
|
||||
className="btn p-0"
|
||||
title="Advanced"
|
||||
onClick={() => toggleAdvVisible(idx)}
|
||||
>
|
||||
<IconSettings size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-3">
|
||||
<div className="mb-3">
|
||||
<label className="form-label" htmlFor="forwardScheme">
|
||||
<T id="host.forward-scheme" />
|
||||
</label>
|
||||
<select
|
||||
id="forwardScheme"
|
||||
className="form-control"
|
||||
value={item.forwardScheme}
|
||||
onChange={(e) => handleChange(idx, "forwardScheme", e.target.value)}
|
||||
>
|
||||
<option value="http">http</option>
|
||||
<option value="https">https</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="mb-3">
|
||||
<label className="form-label" htmlFor="forwardHost">
|
||||
<T id="proxy-host.forward-host" />
|
||||
</label>
|
||||
<input
|
||||
id="forwardHost"
|
||||
type="text"
|
||||
className="form-control"
|
||||
required
|
||||
placeholder="eg: 10.0.0.1/path/"
|
||||
value={item.forwardHost}
|
||||
onChange={(e) => handleChange(idx, "forwardHost", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<div className="mb-3">
|
||||
<label className="form-label" htmlFor="forwardPort">
|
||||
<T id="host.forward-port" />
|
||||
</label>
|
||||
<input
|
||||
id="forwardPort"
|
||||
type="number"
|
||||
min={1}
|
||||
max={65535}
|
||||
className="form-control"
|
||||
required
|
||||
placeholder="eg: 8081"
|
||||
value={item.forwardPort}
|
||||
onChange={(e) => handleChange(idx, "forwardPort", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{advVisible.includes(idx) && (
|
||||
<div className="">
|
||||
<CodeEditor
|
||||
language="nginx"
|
||||
placeholder={intl.formatMessage({ id: "nginx-config.placeholder" })}
|
||||
padding={15}
|
||||
data-color-mode="dark"
|
||||
minHeight={170}
|
||||
indentWidth={2}
|
||||
value={item.advancedConfig}
|
||||
onChange={(e) => handleChange(idx, "advancedConfig", e.target.value)}
|
||||
style={{
|
||||
fontFamily:
|
||||
"ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace",
|
||||
borderRadius: "0.3rem",
|
||||
minHeight: "170px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-1">
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleRemove(idx);
|
||||
}}
|
||||
>
|
||||
<T id="action.delete" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<button type="button" className="btn btn-sm" onClick={handleAdd}>
|
||||
<T id="action.add-location" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export * from "./AccessField";
|
||||
export * from "./BasicAuthFields";
|
||||
export * from "./DNSProviderFields";
|
||||
export * from "./DomainNamesField";
|
||||
export * from "./LocationsFields";
|
||||
export * from "./NginxConfigField";
|
||||
export * from "./SSLCertificateField";
|
||||
export * from "./SSLOptionsFields";
|
||||
|
||||
@@ -25,33 +25,33 @@ const menuItems: MenuItem[] = [
|
||||
{
|
||||
to: "/",
|
||||
icon: IconHome,
|
||||
label: "dashboard.title",
|
||||
label: "dashboard",
|
||||
},
|
||||
{
|
||||
icon: IconDeviceDesktop,
|
||||
label: "hosts.title",
|
||||
label: "hosts",
|
||||
items: [
|
||||
{
|
||||
to: "/nginx/proxy",
|
||||
label: "proxy-hosts.title",
|
||||
label: "proxy-hosts",
|
||||
permission: "proxyHosts",
|
||||
permissionType: "view",
|
||||
},
|
||||
{
|
||||
to: "/nginx/redirection",
|
||||
label: "redirection-hosts.title",
|
||||
label: "redirection-hosts",
|
||||
permission: "redirectionHosts",
|
||||
permissionType: "view",
|
||||
},
|
||||
{
|
||||
to: "/nginx/stream",
|
||||
label: "streams.title",
|
||||
label: "streams",
|
||||
permission: "streams",
|
||||
permissionType: "view",
|
||||
},
|
||||
{
|
||||
to: "/nginx/404",
|
||||
label: "dead-hosts.title",
|
||||
label: "dead-hosts",
|
||||
permission: "deadHosts",
|
||||
permissionType: "view",
|
||||
},
|
||||
@@ -60,33 +60,33 @@ const menuItems: MenuItem[] = [
|
||||
{
|
||||
to: "/access",
|
||||
icon: IconLock,
|
||||
label: "access.title",
|
||||
label: "access-lists",
|
||||
permission: "accessLists",
|
||||
permissionType: "view",
|
||||
},
|
||||
{
|
||||
to: "/certificates",
|
||||
icon: IconShield,
|
||||
label: "certificates.title",
|
||||
label: "certificates",
|
||||
permission: "certificates",
|
||||
permissionType: "view",
|
||||
},
|
||||
{
|
||||
to: "/users",
|
||||
icon: IconUser,
|
||||
label: "users.title",
|
||||
label: "users",
|
||||
permission: "admin",
|
||||
},
|
||||
{
|
||||
to: "/audit-log",
|
||||
icon: IconBook,
|
||||
label: "auditlog.title",
|
||||
label: "auditlogs",
|
||||
permission: "admin",
|
||||
},
|
||||
{
|
||||
to: "/settings",
|
||||
icon: IconSettings,
|
||||
label: "settings.title",
|
||||
label: "settings",
|
||||
permission: "admin",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { AccessList } from "src/api/backend";
|
||||
import { T } from "src/locale";
|
||||
import { showAccessListModal } from "src/modals";
|
||||
|
||||
interface Props {
|
||||
access?: AccessList;
|
||||
}
|
||||
export function AccessListFormatter({ access }: Props) {
|
||||
if (!access) {
|
||||
return <T id="public" />;
|
||||
}
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-action btn-sm px-1"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
showAccessListModal(access?.id || 0);
|
||||
}}
|
||||
>
|
||||
{access.name}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./AccessListformatter";
|
||||
export * from "./CertificateFormatter";
|
||||
export * from "./DomainsFormatter";
|
||||
export * from "./EmailFormatter";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./Button";
|
||||
export * from "./EmptyData";
|
||||
export * from "./ErrorNotFound";
|
||||
export * from "./Flag";
|
||||
export * from "./Form";
|
||||
|
||||
@@ -52,6 +52,7 @@ const useSetAccessList = () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["access-list", id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["access-lists"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["audit-logs"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["proxy-hosts"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -63,8 +63,33 @@ const changeLocale = (locale: string): void => {
|
||||
|
||||
// This is a translation component that wraps the translation in a span with a data
|
||||
// attribute so devs can inspect the element to see the translation ID
|
||||
const T = ({ id, data }: { id: string; data?: any }) => {
|
||||
return <span data-translation-id={id}>{intl.formatMessage({ id }, data)}</span>;
|
||||
const T = ({
|
||||
id,
|
||||
data,
|
||||
tData,
|
||||
}: {
|
||||
id: string;
|
||||
data?: Record<string, string | number | undefined>;
|
||||
tData?: Record<string, string>;
|
||||
}) => {
|
||||
const translatedData: Record<string, string> = {};
|
||||
if (tData) {
|
||||
// iterate over tData and translate each value
|
||||
Object.entries(tData).forEach(([key, value]) => {
|
||||
translatedData[key] = intl.formatMessage({ id: value });
|
||||
});
|
||||
}
|
||||
return (
|
||||
<span data-translation-id={id}>
|
||||
{intl.formatMessage(
|
||||
{ id },
|
||||
{
|
||||
...data,
|
||||
...translatedData,
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl, T };
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"dashboard.title": "Armaturenbrett"
|
||||
"dashboard": "Armaturenbrett"
|
||||
}
|
||||
@@ -1,33 +1,28 @@
|
||||
{
|
||||
"access.access-count": "{count} {count, plural, one {Rule} other {Rules}}",
|
||||
"access.actions-title": "Access List #{id}",
|
||||
"access.add": "Add Access List",
|
||||
"access.auth-count": "{count} {count, plural, one {User} other {Users}}",
|
||||
"access.edit": "Edit Access",
|
||||
"access.empty": "There are no Access Lists",
|
||||
"access.help-rules-last": "When at least 1 rule exists, this deny all rule will be added last",
|
||||
"access.help.rules-order": "Note that the allow and deny directives will be applied in the order they are defined.",
|
||||
"access.new": "New Access",
|
||||
"access.pass-auth": "Pass Auth to Upstream",
|
||||
"access.public": "Publicly Accessible",
|
||||
"access.satisfy-any": "Satisfy Any",
|
||||
"access.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}",
|
||||
"access.title": "Access",
|
||||
"access-list": "Access List",
|
||||
"access-list.access-count": "{count} {count, plural, one {Rule} other {Rules}}",
|
||||
"access-list.auth-count": "{count} {count, plural, one {User} other {Users}}",
|
||||
"access-list.help-rules-last": "When at least 1 rule exists, this deny all rule will be added last",
|
||||
"access-list.help.rules-order": "Note that the allow and deny directives will be applied in the order they are defined.",
|
||||
"access-list.pass-auth": "Pass Auth to Upstream",
|
||||
"access-list.public": "Publicly Accessible",
|
||||
"access-list.satisfy-any": "Satisfy Any",
|
||||
"access-list.subtitle": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}",
|
||||
"access-lists": "Access Lists",
|
||||
"action.add": "Add",
|
||||
"action.add-location": "Add Location",
|
||||
"action.close": "Close",
|
||||
"action.delete": "Delete",
|
||||
"action.disable": "Disable",
|
||||
"action.edit": "Edit",
|
||||
"action.enable": "Enable",
|
||||
"action.permissions": "Permissions",
|
||||
"action.view-details": "View Details",
|
||||
"auditlog.title": "Audit Log",
|
||||
"auditlogs": "Audit Logs",
|
||||
"cancel": "Cancel",
|
||||
"certificates.actions-title": "Certificate #{id}",
|
||||
"certificates.add": "Add Certificate",
|
||||
"certificate": "Certificate",
|
||||
"certificates": "Certificates",
|
||||
"certificates.custom": "Custom Certificate",
|
||||
"certificates.empty": "There are no Certificates",
|
||||
"certificates.title": "SSL Certificates",
|
||||
"close": "Close",
|
||||
"column.access": "Access",
|
||||
"column.authorization": "Authorization",
|
||||
"column.authorizations": "Authorizations",
|
||||
@@ -52,16 +47,10 @@
|
||||
"column.ssl": "SSL",
|
||||
"column.status": "Status",
|
||||
"created-on": "Created: {date}",
|
||||
"dashboard.title": "Dashboard",
|
||||
"dead-host.delete.content": "Are you sure you want to delete this 404 host?",
|
||||
"dead-host.delete.title": "Delete 404 Host",
|
||||
"dead-host.edit": "Edit 404 Host",
|
||||
"dead-host.new": "New 404 Host",
|
||||
"dead-hosts.actions-title": "404 Host #{id}",
|
||||
"dead-hosts.add": "Add 404 Host",
|
||||
"dashboard": "Dashboard",
|
||||
"dead-host": "404 Host",
|
||||
"dead-hosts": "404 Hosts",
|
||||
"dead-hosts.count": "{count} {count, plural, one {404 Host} other {404 Hosts}}",
|
||||
"dead-hosts.empty": "There are no 404 Hosts",
|
||||
"dead-hosts.title": "404 Hosts",
|
||||
"disabled": "Disabled",
|
||||
"domain-names": "Domain Names",
|
||||
"domain-names.max": "{count} domain names maximum",
|
||||
@@ -102,7 +91,6 @@
|
||||
"event.updated-redirection-host": "Updated Redirection Host",
|
||||
"event.updated-user": "Updated User",
|
||||
"footer.github-fork": "Fork me on Github",
|
||||
"generic.flags.title": "Options",
|
||||
"host.flags.block-exploits": "Block Common Exploits",
|
||||
"host.flags.cache-assets": "Cache Assets",
|
||||
"host.flags.preserve-path": "Preserve Path",
|
||||
@@ -110,7 +98,7 @@
|
||||
"host.flags.websockets-upgrade": "Websockets Support",
|
||||
"host.forward-port": "Forward Port",
|
||||
"host.forward-scheme": "Scheme",
|
||||
"hosts.title": "Hosts",
|
||||
"hosts": "Hosts",
|
||||
"http-only": "HTTP Only",
|
||||
"lets-encrypt": "Let's Encrypt",
|
||||
"loading": "Loading…",
|
||||
@@ -119,27 +107,23 @@
|
||||
"nginx-config.placeholder": "# Enter your custom Nginx configuration here at your own risk!",
|
||||
"no-permission-error": "You do not have access to view this.",
|
||||
"notfound.action": "Take me home",
|
||||
"notfound.text": "We are sorry but the page you are looking for was not found",
|
||||
"notfound.content": "We are sorry but the page you are looking for was not found",
|
||||
"notfound.title": "Oops… You just found an error page",
|
||||
"notification.access-deleted": "Access has been deleted",
|
||||
"notification.access-saved": "Access has been saved",
|
||||
"notification.dead-host-saved": "404 Host has been saved",
|
||||
"notification.error": "Error",
|
||||
"notification.host-deleted": "Host has been deleted",
|
||||
"notification.host-disabled": "Host has been disabled",
|
||||
"notification.host-enabled": "Host has been enabled",
|
||||
"notification.proxy-host-saved": "Proxy Host has been saved",
|
||||
"notification.redirection-host-saved": "Redirection Host has been saved",
|
||||
"notification.stream-deleted": "Stream has been deleted",
|
||||
"notification.stream-disabled": "Stream has been disabled",
|
||||
"notification.stream-enabled": "Stream has been enabled",
|
||||
"notification.object-deleted": "{object} has been deleted",
|
||||
"notification.object-disabled": "{object} has been disabled",
|
||||
"notification.object-enabled": "{object} has been enabled",
|
||||
"notification.object-saved": "{object} has been saved",
|
||||
"notification.success": "Success",
|
||||
"notification.user-deleted": "User has been deleted",
|
||||
"notification.user-disabled": "User has been disabled",
|
||||
"notification.user-enabled": "User has been enabled",
|
||||
"notification.user-saved": "User has been saved",
|
||||
"object.actions-title": "{object} #{id}",
|
||||
"object.add": "Add {object}",
|
||||
"object.delete": "Delete {object}",
|
||||
"object.delete.content": "Are you sure you want to delete this {object}?",
|
||||
"object.edit": "Edit {object}",
|
||||
"object.empty": "There are no {objects}",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"options": "Options",
|
||||
"password": "Password",
|
||||
"password.generate": "Generate random password",
|
||||
"password.hide": "Hide Password",
|
||||
@@ -150,55 +134,37 @@
|
||||
"permissions.visibility.all": "All Items",
|
||||
"permissions.visibility.title": "Item Visibility",
|
||||
"permissions.visibility.user": "Created Items Only",
|
||||
"proxy-host.edit": "Edit Proxy Host",
|
||||
"proxy-host": "Proxy Host",
|
||||
"proxy-host.forward-host": "Forward Hostname / IP",
|
||||
"proxy-host.new": "New Proxy Host",
|
||||
"proxy-hosts.actions-title": "Proxy Host #{id}",
|
||||
"proxy-hosts.add": "Add Proxy Host",
|
||||
"proxy-hosts": "Proxy Hosts",
|
||||
"proxy-hosts.count": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}",
|
||||
"proxy-hosts.empty": "There are no Proxy Hosts",
|
||||
"proxy-hosts.title": "Proxy Hosts",
|
||||
"redirection-host.delete.content": "Are you sure you want to delete this Redirection host?",
|
||||
"redirection-host.delete.title": "Delete Redirection Host",
|
||||
"public": "Public",
|
||||
"redirection-host": "Redirection Host",
|
||||
"redirection-host.forward-domain": "Forward Domain",
|
||||
"redirection-host.new": "New Redirection Host",
|
||||
"redirection-hosts.actions-title": "Redirection Host #{id}",
|
||||
"redirection-hosts.add": "Add Redirection Host",
|
||||
"redirection-hosts": "Redirection Hosts",
|
||||
"redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}",
|
||||
"redirection-hosts.empty": "There are no Redirection Hosts",
|
||||
"redirection-hosts.title": "Redirection Hosts",
|
||||
"role.admin": "Administrator",
|
||||
"role.standard-user": "Standard User",
|
||||
"save": "Save",
|
||||
"settings.title": "Settings",
|
||||
"settings": "Settings",
|
||||
"setup.preamble": "Get started by creating your admin account.",
|
||||
"setup.title": "Welcome!",
|
||||
"sign-in": "Sign in",
|
||||
"ssl-certificate": "SSL Certificate",
|
||||
"stream.delete.content": "Are you sure you want to delete this Stream?",
|
||||
"stream.delete.title": "Delete Stream",
|
||||
"stream.edit": "Edit Stream",
|
||||
"stream": "Stream",
|
||||
"stream.forward-host": "Forward Host",
|
||||
"stream.incoming-port": "Incoming Port",
|
||||
"stream.new": "New Stream",
|
||||
"streams.actions-title": "Stream #{id}",
|
||||
"streams.add": "Add Stream",
|
||||
"streams": "Streams",
|
||||
"streams.count": "{count} {count, plural, one {Stream} other {Streams}}",
|
||||
"streams.empty": "There are no Streams",
|
||||
"streams.tcp": "TCP",
|
||||
"streams.title": "Streams",
|
||||
"streams.udp": "UDP",
|
||||
"user": "User",
|
||||
"user.change-password": "Change Password",
|
||||
"user.confirm-password": "Confirm Password",
|
||||
"user.current-password": "Current Password",
|
||||
"user.delete.content": "Are you sure you want to delete this user?",
|
||||
"user.delete.title": "Delete User",
|
||||
"user.edit": "Edit User",
|
||||
"user.edit-profile": "Edit Profile",
|
||||
"user.flags.title": "Properties",
|
||||
"user.full-name": "Full Name",
|
||||
"user.logout": "Logout",
|
||||
"user.new": "New User",
|
||||
"user.new-password": "New Password",
|
||||
"user.nickname": "Nickname",
|
||||
"user.set-password": "Set Password",
|
||||
@@ -206,8 +172,5 @@
|
||||
"user.switch-dark": "Switch to Dark mode",
|
||||
"user.switch-light": "Switch to Light mode",
|
||||
"username": "Username",
|
||||
"users.actions-title": "User #{id}",
|
||||
"users.add": "Add User",
|
||||
"users.empty": "There are no Users",
|
||||
"users.title": "Users"
|
||||
"users": "Users"
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"dashboard.title": "داشبورد"
|
||||
"dashboard": "داشبورد"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dashboard.title": {
|
||||
"dashboard": {
|
||||
"defaultMessage": "Armaturenbrett"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,43 @@
|
||||
{
|
||||
"access.access-count": {
|
||||
"access-list": {
|
||||
"defaultMessage": "Access List"
|
||||
},
|
||||
"access-list.access-count": {
|
||||
"defaultMessage": "{count} {count, plural, one {Rule} other {Rules}}"
|
||||
},
|
||||
"access.actions-title": {
|
||||
"defaultMessage": "Access List #{id}"
|
||||
},
|
||||
"access.add": {
|
||||
"defaultMessage": "Add Access List"
|
||||
},
|
||||
"access.auth-count": {
|
||||
"access-list.auth-count": {
|
||||
"defaultMessage": "{count} {count, plural, one {User} other {Users}}"
|
||||
},
|
||||
"access.edit": {
|
||||
"defaultMessage": "Edit Access"
|
||||
},
|
||||
"access.empty": {
|
||||
"defaultMessage": "There are no Access Lists"
|
||||
},
|
||||
"access.help-rules-last": {
|
||||
"access-list.help-rules-last": {
|
||||
"defaultMessage": "When at least 1 rule exists, this deny all rule will be added last"
|
||||
},
|
||||
"access.help.rules-order": {
|
||||
"access-list.help.rules-order": {
|
||||
"defaultMessage": "Note that the allow and deny directives will be applied in the order they are defined."
|
||||
},
|
||||
"access.new": {
|
||||
"defaultMessage": "New Access"
|
||||
},
|
||||
"access.pass-auth": {
|
||||
"access-list.pass-auth": {
|
||||
"defaultMessage": "Pass Auth to Upstream"
|
||||
},
|
||||
"access.public": {
|
||||
"access-list.public": {
|
||||
"defaultMessage": "Publicly Accessible"
|
||||
},
|
||||
"access.satisfy-any": {
|
||||
"access-list.satisfy-any": {
|
||||
"defaultMessage": "Satisfy Any"
|
||||
},
|
||||
"access.subtitle": {
|
||||
"access-list.subtitle": {
|
||||
"defaultMessage": "{users} {users, plural, one {User} other {Users}}, {rules} {rules, plural, one {Rule} other {Rules}} - Created: {date}"
|
||||
},
|
||||
"access.title": {
|
||||
"defaultMessage": "Access"
|
||||
"access-lists": {
|
||||
"defaultMessage": "Access Lists"
|
||||
},
|
||||
"action.add": {
|
||||
"defaultMessage": "Add"
|
||||
},
|
||||
"action.add-location": {
|
||||
"defaultMessage": "Add Location"
|
||||
},
|
||||
"action.close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
"action.delete": {
|
||||
"defaultMessage": "Delete"
|
||||
},
|
||||
@@ -62,30 +56,21 @@
|
||||
"action.view-details": {
|
||||
"defaultMessage": "View Details"
|
||||
},
|
||||
"auditlog.title": {
|
||||
"defaultMessage": "Audit Log"
|
||||
"auditlogs": {
|
||||
"defaultMessage": "Audit Logs"
|
||||
},
|
||||
"cancel": {
|
||||
"defaultMessage": "Cancel"
|
||||
},
|
||||
"certificates.actions-title": {
|
||||
"defaultMessage": "Certificate #{id}"
|
||||
"certificate": {
|
||||
"defaultMessage": "Certificate"
|
||||
},
|
||||
"certificates.add": {
|
||||
"defaultMessage": "Add Certificate"
|
||||
"certificates": {
|
||||
"defaultMessage": "Certificates"
|
||||
},
|
||||
"certificates.custom": {
|
||||
"defaultMessage": "Custom Certificate"
|
||||
},
|
||||
"certificates.empty": {
|
||||
"defaultMessage": "There are no Certificates"
|
||||
},
|
||||
"certificates.title": {
|
||||
"defaultMessage": "SSL Certificates"
|
||||
},
|
||||
"close": {
|
||||
"defaultMessage": "Close"
|
||||
},
|
||||
"column.access": {
|
||||
"defaultMessage": "Access"
|
||||
},
|
||||
@@ -158,36 +143,18 @@
|
||||
"created-on": {
|
||||
"defaultMessage": "Created: {date}"
|
||||
},
|
||||
"dashboard.title": {
|
||||
"dashboard": {
|
||||
"defaultMessage": "Dashboard"
|
||||
},
|
||||
"dead-host.delete.content": {
|
||||
"defaultMessage": "Are you sure you want to delete this 404 host?"
|
||||
"dead-host": {
|
||||
"defaultMessage": "404 Host"
|
||||
},
|
||||
"dead-host.delete.title": {
|
||||
"defaultMessage": "Delete 404 Host"
|
||||
},
|
||||
"dead-host.edit": {
|
||||
"defaultMessage": "Edit 404 Host"
|
||||
},
|
||||
"dead-host.new": {
|
||||
"defaultMessage": "New 404 Host"
|
||||
},
|
||||
"dead-hosts.actions-title": {
|
||||
"defaultMessage": "404 Host #{id}"
|
||||
},
|
||||
"dead-hosts.add": {
|
||||
"defaultMessage": "Add 404 Host"
|
||||
"dead-hosts": {
|
||||
"defaultMessage": "404 Hosts"
|
||||
},
|
||||
"dead-hosts.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {404 Host} other {404 Hosts}}"
|
||||
},
|
||||
"dead-hosts.empty": {
|
||||
"defaultMessage": "There are no 404 Hosts"
|
||||
},
|
||||
"dead-hosts.title": {
|
||||
"defaultMessage": "404 Hosts"
|
||||
},
|
||||
"disabled": {
|
||||
"defaultMessage": "Disabled"
|
||||
},
|
||||
@@ -308,9 +275,6 @@
|
||||
"footer.github-fork": {
|
||||
"defaultMessage": "Fork me on Github"
|
||||
},
|
||||
"generic.flags.title": {
|
||||
"defaultMessage": "Options"
|
||||
},
|
||||
"host.flags.block-exploits": {
|
||||
"defaultMessage": "Block Common Exploits"
|
||||
},
|
||||
@@ -332,7 +296,7 @@
|
||||
"host.forward-scheme": {
|
||||
"defaultMessage": "Scheme"
|
||||
},
|
||||
"hosts.title": {
|
||||
"hosts": {
|
||||
"defaultMessage": "Hosts"
|
||||
},
|
||||
"http-only": {
|
||||
@@ -359,62 +323,47 @@
|
||||
"notfound.action": {
|
||||
"defaultMessage": "Take me home"
|
||||
},
|
||||
"notfound.text": {
|
||||
"notfound.content": {
|
||||
"defaultMessage": "We are sorry but the page you are looking for was not found"
|
||||
},
|
||||
"notfound.title": {
|
||||
"defaultMessage": "Oops… You just found an error page"
|
||||
},
|
||||
"notification.access-deleted": {
|
||||
"defaultMessage": "Access has been deleted"
|
||||
},
|
||||
"notification.access-saved": {
|
||||
"defaultMessage": "Access has been saved"
|
||||
},
|
||||
"notification.dead-host-saved": {
|
||||
"defaultMessage": "404 Host has been saved"
|
||||
},
|
||||
"notification.error": {
|
||||
"defaultMessage": "Error"
|
||||
},
|
||||
"notification.host-deleted": {
|
||||
"defaultMessage": "Host has been deleted"
|
||||
"notification.object-deleted": {
|
||||
"defaultMessage": "{object} has been deleted"
|
||||
},
|
||||
"notification.host-disabled": {
|
||||
"defaultMessage": "Host has been disabled"
|
||||
"notification.object-disabled": {
|
||||
"defaultMessage": "{object} has been disabled"
|
||||
},
|
||||
"notification.host-enabled": {
|
||||
"defaultMessage": "Host has been enabled"
|
||||
"notification.object-enabled": {
|
||||
"defaultMessage": "{object} has been enabled"
|
||||
},
|
||||
"notification.proxy-host-saved": {
|
||||
"defaultMessage": "Proxy Host has been saved"
|
||||
},
|
||||
"notification.redirection-host-saved": {
|
||||
"defaultMessage": "Redirection Host has been saved"
|
||||
},
|
||||
"notification.stream-deleted": {
|
||||
"defaultMessage": "Stream has been deleted"
|
||||
},
|
||||
"notification.stream-disabled": {
|
||||
"defaultMessage": "Stream has been disabled"
|
||||
},
|
||||
"notification.stream-enabled": {
|
||||
"defaultMessage": "Stream has been enabled"
|
||||
"notification.object-saved": {
|
||||
"defaultMessage": "{object} has been saved"
|
||||
},
|
||||
"notification.success": {
|
||||
"defaultMessage": "Success"
|
||||
},
|
||||
"notification.user-deleted": {
|
||||
"defaultMessage": "User has been deleted"
|
||||
"object.actions-title": {
|
||||
"defaultMessage": "{object} #{id}"
|
||||
},
|
||||
"notification.user-disabled": {
|
||||
"defaultMessage": "User has been disabled"
|
||||
"object.add": {
|
||||
"defaultMessage": "Add {object}"
|
||||
},
|
||||
"notification.user-enabled": {
|
||||
"defaultMessage": "User has been enabled"
|
||||
"object.delete": {
|
||||
"defaultMessage": "Delete {object}"
|
||||
},
|
||||
"notification.user-saved": {
|
||||
"defaultMessage": "User has been saved"
|
||||
"object.delete.content": {
|
||||
"defaultMessage": "Are you sure you want to delete this {object}?"
|
||||
},
|
||||
"object.edit": {
|
||||
"defaultMessage": "Edit {object}"
|
||||
},
|
||||
"object.empty": {
|
||||
"defaultMessage": "There are no {objects}"
|
||||
},
|
||||
"offline": {
|
||||
"defaultMessage": "Offline"
|
||||
@@ -422,6 +371,9 @@
|
||||
"online": {
|
||||
"defaultMessage": "Online"
|
||||
},
|
||||
"options": {
|
||||
"defaultMessage": "Options"
|
||||
},
|
||||
"password": {
|
||||
"defaultMessage": "Password"
|
||||
},
|
||||
@@ -452,57 +404,33 @@
|
||||
"permissions.visibility.user": {
|
||||
"defaultMessage": "Created Items Only"
|
||||
},
|
||||
"proxy-host.edit": {
|
||||
"defaultMessage": "Edit Proxy Host"
|
||||
"proxy-host": {
|
||||
"defaultMessage": "Proxy Host"
|
||||
},
|
||||
"proxy-host.forward-host": {
|
||||
"defaultMessage": "Forward Hostname / IP"
|
||||
},
|
||||
"proxy-host.new": {
|
||||
"defaultMessage": "New Proxy Host"
|
||||
},
|
||||
"proxy-hosts.actions-title": {
|
||||
"defaultMessage": "Proxy Host #{id}"
|
||||
},
|
||||
"proxy-hosts.add": {
|
||||
"defaultMessage": "Add Proxy Host"
|
||||
"proxy-hosts": {
|
||||
"defaultMessage": "Proxy Hosts"
|
||||
},
|
||||
"proxy-hosts.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {Proxy Host} other {Proxy Hosts}}"
|
||||
},
|
||||
"proxy-hosts.empty": {
|
||||
"defaultMessage": "There are no Proxy Hosts"
|
||||
"public": {
|
||||
"defaultMessage": "Public"
|
||||
},
|
||||
"proxy-hosts.title": {
|
||||
"defaultMessage": "Proxy Hosts"
|
||||
},
|
||||
"redirection-host.delete.content": {
|
||||
"defaultMessage": "Are you sure you want to delete this Redirection host?"
|
||||
},
|
||||
"redirection-host.delete.title": {
|
||||
"defaultMessage": "Delete Redirection Host"
|
||||
"redirection-host": {
|
||||
"defaultMessage": "Redirection Host"
|
||||
},
|
||||
"redirection-host.forward-domain": {
|
||||
"defaultMessage": "Forward Domain"
|
||||
},
|
||||
"redirection-host.new": {
|
||||
"defaultMessage": "New Redirection Host"
|
||||
},
|
||||
"redirection-hosts.actions-title": {
|
||||
"defaultMessage": "Redirection Host #{id}"
|
||||
},
|
||||
"redirection-hosts.add": {
|
||||
"defaultMessage": "Add Redirection Host"
|
||||
"redirection-hosts": {
|
||||
"defaultMessage": "Redirection Hosts"
|
||||
},
|
||||
"redirection-hosts.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}"
|
||||
},
|
||||
"redirection-hosts.empty": {
|
||||
"defaultMessage": "There are no Redirection Hosts"
|
||||
},
|
||||
"redirection-hosts.title": {
|
||||
"defaultMessage": "Redirection Hosts"
|
||||
},
|
||||
"role.admin": {
|
||||
"defaultMessage": "Administrator"
|
||||
},
|
||||
@@ -512,7 +440,7 @@
|
||||
"save": {
|
||||
"defaultMessage": "Save"
|
||||
},
|
||||
"settings.title": {
|
||||
"settings": {
|
||||
"defaultMessage": "Settings"
|
||||
},
|
||||
"setup.preamble": {
|
||||
@@ -527,14 +455,8 @@
|
||||
"ssl-certificate": {
|
||||
"defaultMessage": "SSL Certificate"
|
||||
},
|
||||
"stream.delete.content": {
|
||||
"defaultMessage": "Are you sure you want to delete this Stream?"
|
||||
},
|
||||
"stream.delete.title": {
|
||||
"defaultMessage": "Delete Stream"
|
||||
},
|
||||
"stream.edit": {
|
||||
"defaultMessage": "Edit Stream"
|
||||
"stream": {
|
||||
"defaultMessage": "Stream"
|
||||
},
|
||||
"stream.forward-host": {
|
||||
"defaultMessage": "Forward Host"
|
||||
@@ -542,30 +464,21 @@
|
||||
"stream.incoming-port": {
|
||||
"defaultMessage": "Incoming Port"
|
||||
},
|
||||
"stream.new": {
|
||||
"defaultMessage": "New Stream"
|
||||
},
|
||||
"streams.actions-title": {
|
||||
"defaultMessage": "Stream #{id}"
|
||||
},
|
||||
"streams.add": {
|
||||
"defaultMessage": "Add Stream"
|
||||
"streams": {
|
||||
"defaultMessage": "Streams"
|
||||
},
|
||||
"streams.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {Stream} other {Streams}}"
|
||||
},
|
||||
"streams.empty": {
|
||||
"defaultMessage": "There are no Streams"
|
||||
},
|
||||
"streams.tcp": {
|
||||
"defaultMessage": "TCP"
|
||||
},
|
||||
"streams.title": {
|
||||
"defaultMessage": "Streams"
|
||||
},
|
||||
"streams.udp": {
|
||||
"defaultMessage": "UDP"
|
||||
},
|
||||
"user": {
|
||||
"defaultMessage": "User"
|
||||
},
|
||||
"user.change-password": {
|
||||
"defaultMessage": "Change Password"
|
||||
},
|
||||
@@ -575,30 +488,15 @@
|
||||
"user.current-password": {
|
||||
"defaultMessage": "Current Password"
|
||||
},
|
||||
"user.delete.content": {
|
||||
"defaultMessage": "Are you sure you want to delete this user?"
|
||||
},
|
||||
"user.delete.title": {
|
||||
"defaultMessage": "Delete User"
|
||||
},
|
||||
"user.edit": {
|
||||
"defaultMessage": "Edit User"
|
||||
},
|
||||
"user.edit-profile": {
|
||||
"defaultMessage": "Edit Profile"
|
||||
},
|
||||
"user.flags.title": {
|
||||
"defaultMessage": "Properties"
|
||||
},
|
||||
"user.full-name": {
|
||||
"defaultMessage": "Full Name"
|
||||
},
|
||||
"user.logout": {
|
||||
"defaultMessage": "Logout"
|
||||
},
|
||||
"user.new": {
|
||||
"defaultMessage": "New User"
|
||||
},
|
||||
"user.new-password": {
|
||||
"defaultMessage": "New Password"
|
||||
},
|
||||
@@ -620,16 +518,7 @@
|
||||
"username": {
|
||||
"defaultMessage": "Username"
|
||||
},
|
||||
"users.actions-title": {
|
||||
"defaultMessage": "User #{id}"
|
||||
},
|
||||
"users.add": {
|
||||
"defaultMessage": "Add User"
|
||||
},
|
||||
"users.empty": {
|
||||
"defaultMessage": "There are no Users"
|
||||
},
|
||||
"users.title": {
|
||||
"users": {
|
||||
"defaultMessage": "Users"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dashboard.title": {
|
||||
"dashboard": {
|
||||
"defaultMessage": "داشبورد"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AccessClientFields, BasicAuthFields, Button, Loading } from "src/compon
|
||||
import { useAccessList, useSetAccessList } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
const showAccessListModal = (id: number | "new") => {
|
||||
EasyModal.show(AccessListModal, { id });
|
||||
@@ -72,7 +72,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
setAccessList(payload, {
|
||||
onError: (err: any) => setErrorMsg(<T id={err.message} />),
|
||||
onSuccess: () => {
|
||||
showSuccess(intl.formatMessage({ id: "notification.access-saved" }));
|
||||
showObjectSuccess("access-list", "saved");
|
||||
remove();
|
||||
},
|
||||
onSettled: () => {
|
||||
@@ -110,7 +110,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id={data?.id ? "access.edit" : "access.new"} />
|
||||
<T id={data?.id ? "object.edit" : "object.add"} tData={{ object: "access-list" }} />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="p-0">
|
||||
@@ -186,13 +186,13 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Field>
|
||||
<div className="my-3">
|
||||
<h3 className="py-2">
|
||||
<T id="generic.flags.title" />
|
||||
<T id="options" />
|
||||
</h3>
|
||||
<div className="divide-y">
|
||||
<div>
|
||||
<label className="row" htmlFor="satisfyAny">
|
||||
<span className="col">
|
||||
<T id="access.satisfy-any" />
|
||||
<T id="access-list.satisfy-any" />
|
||||
</span>
|
||||
<span className="col-auto">
|
||||
<Field name="satisfyAny" type="checkbox">
|
||||
@@ -224,7 +224,7 @@ const AccessListModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<div>
|
||||
<label className="row" htmlFor="passAuth">
|
||||
<span className="col">
|
||||
<T id="access.pass-auth" />
|
||||
<T id="access-list.pass-auth" />
|
||||
</span>
|
||||
<span className="col-auto">
|
||||
<Field name="passAuth" type="checkbox">
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
SSLOptionsFields,
|
||||
} from "src/components";
|
||||
import { useDeadHost, useSetDeadHost } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { T } from "src/locale";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
const showDeadHostModal = (id: number | "new") => {
|
||||
EasyModal.show(DeadHostModal, { id });
|
||||
@@ -42,7 +42,7 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
setDeadHost(payload, {
|
||||
onError: (err: any) => setErrorMsg(<T id={err.message} />),
|
||||
onSuccess: () => {
|
||||
showSuccess(intl.formatMessage({ id: "notification.dead-host-saved" }));
|
||||
showObjectSuccess("dead-host", "saved");
|
||||
remove();
|
||||
},
|
||||
onSettled: () => {
|
||||
@@ -80,7 +80,7 @@ const DeadHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id={data?.id ? "dead-host.edit" : "dead-host.new"} />
|
||||
<T id={data?.id ? "object.edit" : "object.add"} tData={{ object: "dead-host" }} />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="p-0">
|
||||
|
||||
@@ -7,7 +7,8 @@ import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
|
||||
interface ShowProps {
|
||||
title: string;
|
||||
title?: ReactNode;
|
||||
tTitle?: string;
|
||||
children: ReactNode;
|
||||
onConfirm: () => Promise<void> | void;
|
||||
invalidations?: any[];
|
||||
@@ -19,59 +20,59 @@ const showDeleteConfirmModal = (props: ShowProps) => {
|
||||
EasyModal.show(DeleteConfirmModal, props);
|
||||
};
|
||||
|
||||
const DeleteConfirmModal = EasyModal.create(({ title, children, onConfirm, invalidations, visible, remove }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [error, setError] = useState<ReactNode | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const DeleteConfirmModal = EasyModal.create(
|
||||
({ title, tTitle, children, onConfirm, invalidations, visible, remove }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [error, setError] = useState<ReactNode | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (isSubmitting) return;
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
try {
|
||||
await onConfirm();
|
||||
remove();
|
||||
// invalidate caches as requested
|
||||
invalidations?.forEach((inv) => {
|
||||
queryClient.invalidateQueries({ queryKey: inv });
|
||||
});
|
||||
} catch (err: any) {
|
||||
setError(<T id={err.message} />);
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
const onSubmit = async () => {
|
||||
if (isSubmitting) return;
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
try {
|
||||
await onConfirm();
|
||||
remove();
|
||||
// invalidate caches as requested
|
||||
invalidations?.forEach((inv) => {
|
||||
queryClient.invalidateQueries({ queryKey: inv });
|
||||
});
|
||||
} catch (err: any) {
|
||||
setError(<T id={err.message} />);
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id={title} />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||
{error}
|
||||
</Alert>
|
||||
{children}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
actionType="primary"
|
||||
className="ms-auto btn-red"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
<T id="action.delete" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{tTitle ? <T id={tTitle} /> : title ? title : null}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||
{error}
|
||||
</Alert>
|
||||
{children}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
<T id="cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
actionType="primary"
|
||||
className="ms-auto btn-red"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
<T id="action.delete" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export { showDeleteConfirmModal };
|
||||
|
||||
@@ -46,7 +46,7 @@ const EventDetailsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove}>
|
||||
<T id="close" />
|
||||
<T id="action.close" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</>
|
||||
|
||||
4
frontend/src/modals/PermissionsModal.module.css
Normal file
4
frontend/src/modals/PermissionsModal.module.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.active {
|
||||
border-color: var(--tblr-orange) !important;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { setPermissions } from "src/api/backend";
|
||||
import { Button, Loading } from "src/components";
|
||||
import { useUser } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import styles from "./PermissionsModal.module.css";
|
||||
|
||||
const showPermissionsModal = (id: number) => {
|
||||
EasyModal.show(PermissionsModal, { id });
|
||||
@@ -39,7 +40,18 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
const getClasses = (active: boolean) => {
|
||||
return cn("btn", active ? styles.active : null, {
|
||||
active,
|
||||
"bg-orange-lt": active,
|
||||
});
|
||||
};
|
||||
|
||||
const getPermissionButtons = (field: any, form: any) => {
|
||||
const isManage = field.value === "manage";
|
||||
const isView = field.value === "view";
|
||||
const isHidden = field.value === "hidden";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="btn-group w-100" role="group">
|
||||
@@ -53,7 +65,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
checked={field.value === "manage"}
|
||||
onChange={() => form.setFieldValue(field.name, "manage")}
|
||||
/>
|
||||
<label htmlFor={`${field.name}-manage`} className={cn("btn", { active: field.value === "manage" })}>
|
||||
<label htmlFor={`${field.name}-manage`} className={getClasses(isManage)}>
|
||||
<T id="permissions.manage" />
|
||||
</label>
|
||||
<input
|
||||
@@ -66,7 +78,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
checked={field.value === "view"}
|
||||
onChange={() => form.setFieldValue(field.name, "view")}
|
||||
/>
|
||||
<label htmlFor={`${field.name}-view`} className={cn("btn", { active: field.value === "view" })}>
|
||||
<label htmlFor={`${field.name}-view`} className={getClasses(isView)}>
|
||||
<T id="permissions.view" />
|
||||
</label>
|
||||
<input
|
||||
@@ -79,7 +91,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
checked={field.value === "hidden"}
|
||||
onChange={() => form.setFieldValue(field.name, "hidden")}
|
||||
/>
|
||||
<label htmlFor={`${field.name}-hidden`} className={cn("btn", { active: field.value === "hidden" })}>
|
||||
<label htmlFor={`${field.name}-hidden`} className={getClasses(isHidden)}>
|
||||
<T id="permissions.hidden" />
|
||||
</label>
|
||||
</div>
|
||||
@@ -142,7 +154,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${field.name}-user`}
|
||||
className={cn("btn", { active: field.value === "user" })}
|
||||
className={getClasses(field.value === "user")}
|
||||
>
|
||||
<T id="permissions.visibility.user" />
|
||||
</label>
|
||||
@@ -158,7 +170,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${field.name}-all`}
|
||||
className={cn("btn", { active: field.value === "all" })}
|
||||
className={getClasses(field.value === "all")}
|
||||
>
|
||||
<T id="permissions.visibility.all" />
|
||||
</label>
|
||||
@@ -170,7 +182,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="ignored" className="form-label">
|
||||
<T id="proxy-hosts.title" />
|
||||
<T id="proxy-hosts" />
|
||||
</label>
|
||||
<Field name="proxyHosts">
|
||||
{({ field, form }: any) => getPermissionButtons(field, form)}
|
||||
@@ -178,7 +190,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="ignored" className="form-label">
|
||||
<T id="redirection-hosts.title" />
|
||||
<T id="redirection-hosts" />
|
||||
</label>
|
||||
<Field name="redirectionHosts">
|
||||
{({ field, form }: any) => getPermissionButtons(field, form)}
|
||||
@@ -186,7 +198,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="ignored" className="form-label">
|
||||
<T id="dead-hosts.title" />
|
||||
<T id="dead-hosts" />
|
||||
</label>
|
||||
<Field name="deadHosts">
|
||||
{({ field, form }: any) => getPermissionButtons(field, form)}
|
||||
@@ -194,7 +206,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="ignored" className="form-label">
|
||||
<T id="streams.title" />
|
||||
<T id="streams" />
|
||||
</label>
|
||||
<Field name="streams">
|
||||
{({ field, form }: any) => getPermissionButtons(field, form)}
|
||||
@@ -202,7 +214,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="ignored" className="form-label">
|
||||
<T id="access.title" />
|
||||
<T id="access-lists" />
|
||||
</label>
|
||||
<Field name="accessLists">
|
||||
{({ field, form }: any) => getPermissionButtons(field, form)}
|
||||
@@ -210,7 +222,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="ignored" className="form-label">
|
||||
<T id="certificates.title" />
|
||||
<T id="certificates" />
|
||||
</label>
|
||||
<Field name="certificates">
|
||||
{({ field, form }: any) => getPermissionButtons(field, form)}
|
||||
@@ -225,8 +237,7 @@ const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
actionType="primary"
|
||||
className="ms-auto"
|
||||
className="ms-auto btn-orange"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
|
||||
@@ -10,14 +10,15 @@ import {
|
||||
Button,
|
||||
DomainNamesField,
|
||||
Loading,
|
||||
LocationsFields,
|
||||
NginxConfigField,
|
||||
SSLCertificateField,
|
||||
SSLOptionsFields,
|
||||
} from "src/components";
|
||||
import { useProxyHost, useSetProxyHost } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { validateNumber, validateString } from "src/modules/Validations";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
const showProxyHostModal = (id: number | "new") => {
|
||||
EasyModal.show(ProxyHostModal, { id });
|
||||
@@ -45,7 +46,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
setProxyHost(payload, {
|
||||
onError: (err: any) => setErrorMsg(<T id={err.message} />),
|
||||
onSuccess: () => {
|
||||
showSuccess(intl.formatMessage({ id: "notification.proxy-host-saved" }));
|
||||
showObjectSuccess("proxy-host", "saved");
|
||||
remove();
|
||||
},
|
||||
onSettled: () => {
|
||||
@@ -95,7 +96,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id={data?.id ? "proxy-host.edit" : "proxy-host.new"} />
|
||||
<T id={data?.id ? "object.edit" : "object.add"} tData={{ object: "proxy-host" }} />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="p-0">
|
||||
@@ -251,7 +252,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<AccessField />
|
||||
<div className="my-3">
|
||||
<h4 className="py-2">
|
||||
<T id="generic.flags.title" />
|
||||
<T id="options" />
|
||||
</h4>
|
||||
<div className="divide-y">
|
||||
<div>
|
||||
@@ -327,7 +328,7 @@ const ProxyHostModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="tab-pane" id="tab-locations" role="tabpanel">
|
||||
locations TODO
|
||||
<LocationsFields initialValues={data?.locations || []} />
|
||||
</div>
|
||||
<div className="tab-pane" id="tab-ssl" role="tabpanel">
|
||||
<SSLCertificateField
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
SSLOptionsFields,
|
||||
} from "src/components";
|
||||
import { useRedirectionHost, useSetRedirectionHost } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
const showRedirectionHostModal = (id: number | "new") => {
|
||||
EasyModal.show(RedirectionHostModal, { id });
|
||||
@@ -44,7 +44,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
setRedirectionHost(payload, {
|
||||
onError: (err: any) => setErrorMsg(<T id={err.message} />),
|
||||
onSuccess: () => {
|
||||
showSuccess(intl.formatMessage({ id: "notification.redirection-host-saved" }));
|
||||
showObjectSuccess("redirection-host", "saved");
|
||||
remove();
|
||||
},
|
||||
onSettled: () => {
|
||||
@@ -90,7 +90,10 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id={data?.id ? "redirection-host.edit" : "redirection-host.new"} />
|
||||
<T
|
||||
id={data?.id ? "object.edit" : "object.add"}
|
||||
tData={{ object: "redirection-host" }}
|
||||
/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="p-0">
|
||||
@@ -211,7 +214,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
</div>
|
||||
<div className="my-3">
|
||||
<h4 className="py-2">
|
||||
<T id="generic.flags.title" />
|
||||
<T id="options" />
|
||||
</h4>
|
||||
<div className="divide-y">
|
||||
<div>
|
||||
|
||||
@@ -5,9 +5,9 @@ import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import { Button, Loading, SSLCertificateField, SSLOptionsFields } from "src/components";
|
||||
import { useSetStream, useStream } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { validateNumber, validateString } from "src/modules/Validations";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
const showStreamModal = (id: number | "new") => {
|
||||
EasyModal.show(StreamModal, { id });
|
||||
@@ -35,7 +35,7 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
setStream(payload, {
|
||||
onError: (err: any) => setErrorMsg(<T id={err.message} />),
|
||||
onSuccess: () => {
|
||||
showSuccess(intl.formatMessage({ id: "notification.stream-saved" }));
|
||||
showObjectSuccess("stream", "saved");
|
||||
remove();
|
||||
},
|
||||
onSettled: () => {
|
||||
@@ -72,7 +72,7 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id={data?.id ? "stream.edit" : "stream.new"} />
|
||||
<T id={data?.id ? "object.edit" : "object.add"} tData={{ object: "stream" }} />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="p-0">
|
||||
@@ -180,7 +180,7 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
className="form-label"
|
||||
htmlFor="forwardingPort"
|
||||
>
|
||||
<T id="stream.forward-port" />
|
||||
<T id="host.forward-port" />
|
||||
</label>
|
||||
<input
|
||||
id="forwardingPort"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Button, Loading } from "src/components";
|
||||
import { useSetUser, useUser } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { validateEmail, validateString } from "src/modules/Validations";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
|
||||
const showUserModal = (id: number | "me" | "new") => {
|
||||
EasyModal.show(UserModal, { id });
|
||||
@@ -48,7 +48,7 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
setUser(payload, {
|
||||
onError: (err: any) => setErrorMsg(err.message),
|
||||
onSuccess: () => {
|
||||
showSuccess(intl.formatMessage({ id: "notification.user-saved" }));
|
||||
showObjectSuccess("user", "saved");
|
||||
remove();
|
||||
},
|
||||
onSettled: () => {
|
||||
@@ -83,7 +83,7 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<Form>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<T id={data?.id ? "user.edit" : "user.new"} />
|
||||
<T id={data?.id ? "object.edit" : "object.add"} tData={{ object: "user" }} />
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
@@ -172,7 +172,7 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
{currentUser && data && currentUser?.id !== data?.id ? (
|
||||
<div className="my-3">
|
||||
<h4 className="py-2">
|
||||
<T id="user.flags.title" />
|
||||
<T id="options" />
|
||||
</h4>
|
||||
<div className="divide-y">
|
||||
<div>
|
||||
@@ -227,8 +227,7 @@ const UserModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
actionType="primary"
|
||||
className="ms-auto"
|
||||
className="ms-auto btn-orange"
|
||||
data-bs-dismiss="modal"
|
||||
isLoading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
|
||||
@@ -24,4 +24,15 @@ const showError = (message: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export { showSuccess, showError };
|
||||
const showObjectSuccess = (obj: string, action: string) => {
|
||||
showSuccess(
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: `notification.object-${action}`,
|
||||
},
|
||||
{ object: intl.formatMessage({ id: obj }) },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export { showSuccess, showError, showObjectSuccess };
|
||||
|
||||
@@ -2,10 +2,9 @@ import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { AccessList } from "src/api/backend";
|
||||
import { GravatarFormatter, ValueWithDateFormatter } from "src/components";
|
||||
import { EmptyData, GravatarFormatter, ValueWithDateFormatter } from "src/components";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import Empty from "./Empty";
|
||||
|
||||
interface Props {
|
||||
data: AccessList[];
|
||||
@@ -36,12 +35,12 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
columnHelper.accessor((row: any) => row.items, {
|
||||
id: "items",
|
||||
header: intl.formatMessage({ id: "column.authorization" }),
|
||||
cell: (info: any) => <T id="access.auth-count" data={{ count: info.getValue().length }} />,
|
||||
cell: (info: any) => <T id="access-list.auth-count" data={{ count: info.getValue().length }} />,
|
||||
}),
|
||||
columnHelper.accessor((row: any) => row.clients, {
|
||||
id: "clients",
|
||||
header: intl.formatMessage({ id: "column.access" }),
|
||||
cell: (info: any) => <T id="access.access-count" data={{ count: info.getValue().length }} />,
|
||||
cell: (info: any) => <T id="access-list.access-count" data={{ count: info.getValue().length }} />,
|
||||
}),
|
||||
columnHelper.accessor((row: any) => row.satisfyAny, {
|
||||
id: "satisfyAny",
|
||||
@@ -50,7 +49,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
}),
|
||||
columnHelper.accessor((row: any) => row.proxyHostCount, {
|
||||
id: "proxyHostCount",
|
||||
header: intl.formatMessage({ id: "proxy-hosts.title" }),
|
||||
header: intl.formatMessage({ id: "proxy-hosts" }),
|
||||
cell: (info: any) => <T id="proxy-hosts.count" data={{ count: info.getValue() }} />,
|
||||
}),
|
||||
columnHelper.display({
|
||||
@@ -68,7 +67,11 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">
|
||||
<T id="access.actions-title" data={{ id: info.row.original.id }} />
|
||||
<T
|
||||
id="object.actions-title"
|
||||
tData={{ object: "access-list" }}
|
||||
data={{ id: info.row.original.id }}
|
||||
/>
|
||||
</span>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
@@ -119,7 +122,16 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
return (
|
||||
<TableLayout
|
||||
tableInstance={tableInstance}
|
||||
emptyState={<Empty tableInstance={tableInstance} onNew={onNew} isFiltered={isFiltered} />}
|
||||
emptyState={
|
||||
<EmptyData
|
||||
object="access-list"
|
||||
objects="access-lists"
|
||||
tableInstance={tableInstance}
|
||||
onNew={onNew}
|
||||
isFiltered={isFiltered}
|
||||
color="cyan"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import Alert from "react-bootstrap/Alert";
|
||||
import { deleteAccessList } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useAccessLists } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { showAccessListModal, showDeleteConfirmModal } from "src/modals";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
export default function TableWrapper() {
|
||||
@@ -23,7 +23,7 @@ export default function TableWrapper() {
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteAccessList(id);
|
||||
showSuccess(intl.formatMessage({ id: "notification.access-deleted" }));
|
||||
showObjectSuccess("access-list", "deleted");
|
||||
};
|
||||
|
||||
let filtered = null;
|
||||
@@ -44,7 +44,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="access.title" />
|
||||
<T id="access-lists" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
@@ -63,7 +63,7 @@ export default function TableWrapper() {
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" className="btn-cyan" onClick={() => showAccessListModal("new")}>
|
||||
<T id="access.add" />
|
||||
<T id="object.add" tData={{ object: "access-list" }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@ export default function TableWrapper() {
|
||||
title: "access.delete.title",
|
||||
onConfirm: () => handleDelete(id),
|
||||
invalidations: [["access-lists"], ["access-list", id]],
|
||||
children: <T id="access.delete.content" />,
|
||||
children: <T id="object.delete.content" tData={{ object: "access-list" }} />,
|
||||
})
|
||||
}
|
||||
onNew={() => showAccessListModal("new")}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="auditlog.title" />
|
||||
<T id="auditlogs" />
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import { T } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNew?: () => void;
|
||||
onNewCustom?: () => void;
|
||||
isFiltered?: boolean;
|
||||
}
|
||||
export default function Empty({ tableInstance, onNew, onNewCustom, isFiltered }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
<div className="text-center my-4">
|
||||
{isFiltered ? (
|
||||
<h2>
|
||||
<T id="empty-search" />
|
||||
</h2>
|
||||
) : (
|
||||
<>
|
||||
<h2>
|
||||
<T id="certificates.empty" />
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
<T id="empty-subtitle" />
|
||||
</p>
|
||||
<div className="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
className="btn dropdown-toggle btn-pink my-3"
|
||||
data-bs-toggle="dropdown"
|
||||
>
|
||||
<T id="certificates.add" />
|
||||
</button>
|
||||
<div className="dropdown-menu">
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onNew?.();
|
||||
}}
|
||||
>
|
||||
<T id="lets-encrypt" />
|
||||
</a>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onNewCustom?.();
|
||||
}}
|
||||
>
|
||||
<T id="certificates.custom" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,9 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { Certificate } from "src/api/backend";
|
||||
import { DomainsFormatter, GravatarFormatter } from "src/components";
|
||||
import { DomainsFormatter, EmptyData, GravatarFormatter } from "src/components";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import Empty from "./Empty";
|
||||
|
||||
interface Props {
|
||||
data: Certificate[];
|
||||
@@ -69,7 +68,11 @@ export default function Table({ data, isFetching }: Props) {
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">
|
||||
<T id="certificates.actions-title" data={{ id: info.row.original.id }} />
|
||||
<T
|
||||
id="object.actions-title"
|
||||
tData={{ object: "certificate" }}
|
||||
data={{ id: info.row.original.id }}
|
||||
/>
|
||||
</span>
|
||||
<a className="dropdown-item" href="#">
|
||||
<IconEdit size={16} />
|
||||
@@ -107,5 +110,50 @@ export default function Table({ data, isFetching }: Props) {
|
||||
enableSortingRemoval: false,
|
||||
});
|
||||
|
||||
return <TableLayout tableInstance={tableInstance} emptyState={<Empty tableInstance={tableInstance} />} />;
|
||||
const customAddBtn = (
|
||||
<div className="dropdown">
|
||||
<button type="button" className="btn dropdown-toggle btn-pink my-3" 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();
|
||||
// onNew();
|
||||
}}
|
||||
>
|
||||
<T id="lets-encrypt" />
|
||||
</a>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
// onNewCustom();
|
||||
}}
|
||||
>
|
||||
<T id="certificates.custom" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<TableLayout
|
||||
tableInstance={tableInstance}
|
||||
emptyState={
|
||||
<EmptyData
|
||||
object="certificate"
|
||||
objects="certificates"
|
||||
tableInstance={tableInstance}
|
||||
// onNew={onNew}
|
||||
// isFiltered={isFiltered}
|
||||
color="pink"
|
||||
customAddBtn={customAddBtn}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="certificates.title" />
|
||||
<T id="certificates" />
|
||||
</h2>
|
||||
</div>
|
||||
<div className="col-md-auto col-sm-12">
|
||||
@@ -51,7 +51,7 @@ export default function TableWrapper() {
|
||||
className="btn btn-sm dropdown-toggle btn-pink mt-1"
|
||||
data-bs-toggle="dropdown"
|
||||
>
|
||||
<T id="certificates.add" />
|
||||
<T id="object.add" tData={{ object: "certificate" }} />
|
||||
</button>
|
||||
<div className="dropdown-menu">
|
||||
<a className="dropdown-item" href="#">
|
||||
|
||||
@@ -10,7 +10,7 @@ const Dashboard = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>
|
||||
<T id="dashboard.title" />
|
||||
<T id="dashboard" />
|
||||
</h2>
|
||||
<div className="row row-deck row-cards">
|
||||
<div className="col-12 my-4">
|
||||
@@ -127,6 +127,7 @@ const Dashboard = () => {
|
||||
- use syntax highligting for audit logs json output
|
||||
- double check output of access field selection on proxy host dialog, after access lists are completed
|
||||
- proxy host custom locations dialog
|
||||
- check permissions in all places
|
||||
|
||||
More for api, then implement here:
|
||||
- Properly implement refresh tokens
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNew?: () => void;
|
||||
isFiltered?: boolean;
|
||||
}
|
||||
export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
<div className="text-center my-4">
|
||||
{isFiltered ? (
|
||||
<h2>
|
||||
<T id="empty-search" />
|
||||
</h2>
|
||||
) : (
|
||||
<>
|
||||
<h2>
|
||||
<T id="dead-hosts.empty" />
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
<T id="empty-subtitle" />
|
||||
</p>
|
||||
<Button className="btn-red my-3" onClick={onNew}>
|
||||
<T id="dead-hosts.add" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,9 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { DeadHost } from "src/api/backend";
|
||||
import { CertificateFormatter, DomainsFormatter, GravatarFormatter, StatusFormatter } from "src/components";
|
||||
import { CertificateFormatter, DomainsFormatter, EmptyData, GravatarFormatter, StatusFormatter } from "src/components";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import Empty from "./Empty";
|
||||
|
||||
interface Props {
|
||||
data: DeadHost[];
|
||||
@@ -67,7 +66,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">
|
||||
<T id="dead-hosts.actions-title" data={{ id: info.row.original.id }} />
|
||||
<T
|
||||
id="object.actions-title"
|
||||
tData={{ object: "dead-host" }}
|
||||
data={{ id: info.row.original.id }}
|
||||
/>
|
||||
</span>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
@@ -129,7 +132,16 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
return (
|
||||
<TableLayout
|
||||
tableInstance={tableInstance}
|
||||
emptyState={<Empty tableInstance={tableInstance} onNew={onNew} isFiltered={isFiltered} />}
|
||||
emptyState={
|
||||
<EmptyData
|
||||
object="dead-host"
|
||||
objects="dead-hosts"
|
||||
tableInstance={tableInstance}
|
||||
onNew={onNew}
|
||||
isFiltered={isFiltered}
|
||||
color="red"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import Alert from "react-bootstrap/Alert";
|
||||
import { deleteDeadHost, toggleDeadHost } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useDeadHosts } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { showDeadHostModal, showDeleteConfirmModal } from "src/modals";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
export default function TableWrapper() {
|
||||
@@ -25,14 +25,14 @@ export default function TableWrapper() {
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteDeadHost(id);
|
||||
showSuccess(intl.formatMessage({ id: "notification.host-deleted" }));
|
||||
showObjectSuccess("dead-host", "deleted");
|
||||
};
|
||||
|
||||
const handleDisableToggle = async (id: number, enabled: boolean) => {
|
||||
await toggleDeadHost(id, enabled);
|
||||
queryClient.invalidateQueries({ queryKey: ["dead-hosts"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["dead-host", id] });
|
||||
showSuccess(intl.formatMessage({ id: enabled ? "notification.host-enabled" : "notification.host-disabled" }));
|
||||
showObjectSuccess("dead-host", enabled ? "enabled" : "disabled");
|
||||
};
|
||||
|
||||
let filtered = null;
|
||||
@@ -53,7 +53,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="dead-hosts.title" />
|
||||
<T id="dead-hosts" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
@@ -72,7 +72,7 @@ export default function TableWrapper() {
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" className="btn-red" onClick={() => showDeadHostModal("new")}>
|
||||
<T id="dead-hosts.add" />
|
||||
<T id="object.add" tData={{ object: "dead-host" }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -86,10 +86,10 @@ export default function TableWrapper() {
|
||||
onEdit={(id: number) => showDeadHostModal(id)}
|
||||
onDelete={(id: number) =>
|
||||
showDeleteConfirmModal({
|
||||
title: "dead-host.delete.title",
|
||||
title: <T id="object.delete" tData={{ object: "dead-host" }} />,
|
||||
onConfirm: () => handleDelete(id),
|
||||
invalidations: [["dead-hosts"], ["dead-host", id]],
|
||||
children: <T id="dead-host.delete.content" />,
|
||||
children: <T id="object.delete.content" tData={{ object: "dead-host" }} />,
|
||||
})
|
||||
}
|
||||
onDisableToggle={handleDisableToggle}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNew?: () => void;
|
||||
isFiltered?: boolean;
|
||||
}
|
||||
export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
<div className="text-center my-4">
|
||||
{isFiltered ? (
|
||||
<h2>
|
||||
<T id="empty-search" />
|
||||
</h2>
|
||||
) : (
|
||||
<>
|
||||
<h2>
|
||||
<T id="proxy-hosts.empty" />
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
<T id="empty-subtitle" />
|
||||
</p>
|
||||
<Button className="btn-lime my-3" onClick={onNew}>
|
||||
<T id="proxy-hosts.add" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,16 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { ProxyHost } from "src/api/backend";
|
||||
import { CertificateFormatter, DomainsFormatter, GravatarFormatter, StatusFormatter } from "src/components";
|
||||
import {
|
||||
AccessListFormatter,
|
||||
CertificateFormatter,
|
||||
DomainsFormatter,
|
||||
EmptyData,
|
||||
GravatarFormatter,
|
||||
StatusFormatter,
|
||||
} from "src/components";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import Empty from "./Empty";
|
||||
|
||||
interface Props {
|
||||
data: ProxyHost[];
|
||||
@@ -53,12 +59,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
return <CertificateFormatter certificate={info.getValue()} />;
|
||||
},
|
||||
}),
|
||||
// TODO: formatter for access list
|
||||
columnHelper.accessor((row: any) => row.access, {
|
||||
columnHelper.accessor((row: any) => row.accessList, {
|
||||
id: "accessList",
|
||||
header: intl.formatMessage({ id: "column.access" }),
|
||||
cell: (info: any) => {
|
||||
return info.getValue();
|
||||
return <AccessListFormatter access={info.getValue()} />;
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor((row: any) => row.enabled, {
|
||||
@@ -83,7 +88,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">
|
||||
<T id="proxy-hosts.actions-title" data={{ id: info.row.original.id }} />
|
||||
<T
|
||||
id="object.actions-title"
|
||||
tData={{ object: "proxy-host" }}
|
||||
data={{ id: info.row.original.id }}
|
||||
/>
|
||||
</span>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
@@ -145,7 +154,16 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
return (
|
||||
<TableLayout
|
||||
tableInstance={tableInstance}
|
||||
emptyState={<Empty tableInstance={tableInstance} onNew={onNew} isFiltered={isFiltered} />}
|
||||
emptyState={
|
||||
<EmptyData
|
||||
object="proxy-host"
|
||||
objects="proxy-hosts"
|
||||
tableInstance={tableInstance}
|
||||
onNew={onNew}
|
||||
isFiltered={isFiltered}
|
||||
color="lime"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import Alert from "react-bootstrap/Alert";
|
||||
import { deleteProxyHost, toggleProxyHost } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useProxyHosts } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { showDeleteConfirmModal, showProxyHostModal } from "src/modals";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
export default function TableWrapper() {
|
||||
@@ -25,14 +25,14 @@ export default function TableWrapper() {
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteProxyHost(id);
|
||||
showSuccess(intl.formatMessage({ id: "notification.host-deleted" }));
|
||||
showObjectSuccess("proxy-host", "deleted");
|
||||
};
|
||||
|
||||
const handleDisableToggle = async (id: number, enabled: boolean) => {
|
||||
await toggleProxyHost(id, enabled);
|
||||
queryClient.invalidateQueries({ queryKey: ["proxy-hosts"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["proxy-host", id] });
|
||||
showSuccess(intl.formatMessage({ id: enabled ? "notification.host-enabled" : "notification.host-disabled" }));
|
||||
showObjectSuccess("proxy-host", enabled ? "enabled" : "disabled");
|
||||
};
|
||||
|
||||
let filtered = null;
|
||||
@@ -57,7 +57,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="proxy-hosts.title" />
|
||||
<T id="proxy-hosts" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
@@ -76,7 +76,7 @@ export default function TableWrapper() {
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" className="btn-lime" onClick={() => showProxyHostModal("new")}>
|
||||
<T id="proxy-hosts.add" />
|
||||
<T id="object.add" tData={{ object: "proxy-host" }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNew?: () => void;
|
||||
isFiltered?: boolean;
|
||||
}
|
||||
export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
<div className="text-center my-4">
|
||||
{isFiltered ? (
|
||||
<h2>
|
||||
<T id="empty-search" />
|
||||
</h2>
|
||||
) : (
|
||||
<>
|
||||
<h2>
|
||||
<T id="redirection-hosts.empty" />
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
<T id="empty-subtitle" />
|
||||
</p>
|
||||
<Button className="btn-yellow my-3" onClick={onNew}>
|
||||
<T id="redirection-hosts.add" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,9 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { RedirectionHost } from "src/api/backend";
|
||||
import { CertificateFormatter, DomainsFormatter, GravatarFormatter, StatusFormatter } from "src/components";
|
||||
import { CertificateFormatter, DomainsFormatter, EmptyData, GravatarFormatter, StatusFormatter } from "src/components";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import Empty from "./Empty";
|
||||
|
||||
interface Props {
|
||||
data: RedirectionHost[];
|
||||
@@ -88,7 +87,11 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">
|
||||
<T id="redirection-hosts.actions-title" data={{ id: info.row.original.id }} />
|
||||
<T
|
||||
id="object.actions-title"
|
||||
tData={{ object: "redirection-host" }}
|
||||
data={{ id: info.row.original.id }}
|
||||
/>
|
||||
</span>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
@@ -150,7 +153,16 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
return (
|
||||
<TableLayout
|
||||
tableInstance={tableInstance}
|
||||
emptyState={<Empty tableInstance={tableInstance} onNew={onNew} isFiltered={isFiltered} />}
|
||||
emptyState={
|
||||
<EmptyData
|
||||
object="redirection-host"
|
||||
objects="redirection-hosts"
|
||||
tableInstance={tableInstance}
|
||||
onNew={onNew}
|
||||
isFiltered={isFiltered}
|
||||
color="yellow"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import Alert from "react-bootstrap/Alert";
|
||||
import { deleteRedirectionHost, toggleRedirectionHost } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useRedirectionHosts } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { showDeleteConfirmModal, showRedirectionHostModal } from "src/modals";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
export default function TableWrapper() {
|
||||
@@ -25,14 +25,14 @@ export default function TableWrapper() {
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteRedirectionHost(id);
|
||||
showSuccess(intl.formatMessage({ id: "notification.host-deleted" }));
|
||||
showObjectSuccess("redirection-host", "deleted");
|
||||
};
|
||||
|
||||
const handleDisableToggle = async (id: number, enabled: boolean) => {
|
||||
await toggleRedirectionHost(id, enabled);
|
||||
queryClient.invalidateQueries({ queryKey: ["redirection-hosts"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["redirection-host", id] });
|
||||
showSuccess(intl.formatMessage({ id: enabled ? "notification.host-enabled" : "notification.host-disabled" }));
|
||||
showObjectSuccess("redirection-host", enabled ? "enabled" : "disabled");
|
||||
};
|
||||
|
||||
let filtered = null;
|
||||
@@ -56,7 +56,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="redirection-hosts.title" />
|
||||
<T id="redirection-hosts" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
@@ -79,7 +79,7 @@ export default function TableWrapper() {
|
||||
className="btn-yellow"
|
||||
onClick={() => showRedirectionHostModal("new")}
|
||||
>
|
||||
<T id="redirection-hosts.add" />
|
||||
<T id="object.add" tData={{ object: "redirection-host" }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,10 +93,10 @@ export default function TableWrapper() {
|
||||
onEdit={(id: number) => showRedirectionHostModal(id)}
|
||||
onDelete={(id: number) =>
|
||||
showDeleteConfirmModal({
|
||||
title: "redirection-host.delete.title",
|
||||
title: <T id="object.delete" tData={{ object: "redirection-host" }} />,
|
||||
onConfirm: () => handleDelete(id),
|
||||
invalidations: [["redirection-hosts"], ["redirection-host", id]],
|
||||
children: <T id="redirection-host.delete.content" />,
|
||||
children: <T id="object.delete.content" tData={{ object: "redirection-host" }} />,
|
||||
})
|
||||
}
|
||||
onDisableToggle={handleDisableToggle}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNew?: () => void;
|
||||
isFiltered?: boolean;
|
||||
}
|
||||
export default function Empty({ tableInstance, onNew, isFiltered }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
<div className="text-center my-4">
|
||||
{isFiltered ? (
|
||||
<h2>
|
||||
<T id="empty-search" />
|
||||
</h2>
|
||||
) : (
|
||||
<>
|
||||
<h2>
|
||||
<T id="streams.empty" />
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
<T id="empty-subtitle" />
|
||||
</p>
|
||||
<Button className="btn-blue my-3" onClick={onNew}>
|
||||
<T id="streams.add" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -2,10 +2,15 @@ import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { Stream } from "src/api/backend";
|
||||
import { CertificateFormatter, GravatarFormatter, StatusFormatter, ValueWithDateFormatter } from "src/components";
|
||||
import {
|
||||
CertificateFormatter,
|
||||
EmptyData,
|
||||
GravatarFormatter,
|
||||
StatusFormatter,
|
||||
ValueWithDateFormatter,
|
||||
} from "src/components";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import Empty from "./Empty";
|
||||
|
||||
interface Props {
|
||||
data: Stream[];
|
||||
@@ -96,7 +101,11 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">
|
||||
<T id="streams.actions-title" data={{ id: info.row.original.id }} />
|
||||
<T
|
||||
id="object.actions-title"
|
||||
tData={{ object: "stream" }}
|
||||
data={{ id: info.row.original.id }}
|
||||
/>
|
||||
</span>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
@@ -158,7 +167,16 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
return (
|
||||
<TableLayout
|
||||
tableInstance={tableInstance}
|
||||
emptyState={<Empty tableInstance={tableInstance} onNew={onNew} isFiltered={isFiltered} />}
|
||||
emptyState={
|
||||
<EmptyData
|
||||
object="stream"
|
||||
objects="streams"
|
||||
tableInstance={tableInstance}
|
||||
onNew={onNew}
|
||||
isFiltered={isFiltered}
|
||||
color="blue"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import Alert from "react-bootstrap/Alert";
|
||||
import { deleteStream, toggleStream } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useStreams } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { showDeleteConfirmModal, showStreamModal } from "src/modals";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
export default function TableWrapper() {
|
||||
@@ -26,16 +26,14 @@ export default function TableWrapper() {
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteStream(id);
|
||||
showSuccess(intl.formatMessage({ id: "notification.stream-deleted" }));
|
||||
showObjectSuccess("stream", "deleted");
|
||||
};
|
||||
|
||||
const handleDisableToggle = async (id: number, enabled: boolean) => {
|
||||
await toggleStream(id, enabled);
|
||||
queryClient.invalidateQueries({ queryKey: ["streams"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["stream", id] });
|
||||
showSuccess(
|
||||
intl.formatMessage({ id: enabled ? "notification.stream-enabled" : "notification.stream-disabled" }),
|
||||
);
|
||||
showObjectSuccess("stream", enabled ? "enabled" : "disabled");
|
||||
};
|
||||
|
||||
let filtered = null;
|
||||
@@ -60,7 +58,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="streams.title" />
|
||||
<T id="streams" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
@@ -79,7 +77,7 @@ export default function TableWrapper() {
|
||||
/>
|
||||
</div>
|
||||
<Button size="sm" className="btn-blue" onClick={() => showStreamModal("new")}>
|
||||
<T id="streams.add" />
|
||||
<T id="object.add" tData={{ object: "stream" }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,10 +91,10 @@ export default function TableWrapper() {
|
||||
onEdit={(id: number) => showStreamModal(id)}
|
||||
onDelete={(id: number) =>
|
||||
showDeleteConfirmModal({
|
||||
title: "stream.delete.title",
|
||||
title: <T id="object.delete" tData={{ object: "stream" }} />,
|
||||
onConfirm: () => handleDelete(id),
|
||||
invalidations: [["streams"], ["stream", id]],
|
||||
children: <T id="stream.delete.content" />,
|
||||
children: <T id="object.delete.content" tData={{ object: "stream" }} />,
|
||||
})
|
||||
}
|
||||
onDisableToggle={handleDisableToggle}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function SettingTable() {
|
||||
<div className="card-header">
|
||||
<div className="row w-full">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="settings.title" />
|
||||
<T id="settings" />
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
onNewUser?: () => void;
|
||||
isFiltered?: boolean;
|
||||
}
|
||||
export default function Empty({ tableInstance, onNewUser, isFiltered }: Props) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
<div className="text-center my-4">
|
||||
{isFiltered ? (
|
||||
<h2>
|
||||
<T id="empty-search" />
|
||||
</h2>
|
||||
) : (
|
||||
<>
|
||||
<h2>
|
||||
<T id="users.empty" />
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
<T id="empty-subtitle" />
|
||||
</p>
|
||||
<Button className="btn-orange my-3" onClick={onNewUser}>
|
||||
<T id="users.add" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useMemo } from "react";
|
||||
import type { User } from "src/api/backend";
|
||||
import {
|
||||
EmailFormatter,
|
||||
EmptyData,
|
||||
EnabledFormatter,
|
||||
GravatarFormatter,
|
||||
RolesFormatter,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
} from "src/components";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import Empty from "./Empty";
|
||||
|
||||
interface Props {
|
||||
data: User[];
|
||||
@@ -101,7 +101,11 @@ export default function Table({
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<span className="dropdown-header">
|
||||
<T id="users.actions-title" data={{ id: info.row.original.id }} />
|
||||
<T
|
||||
id="object.actions-title"
|
||||
tData={{ object: "user" }}
|
||||
data={{ id: info.row.original.id }}
|
||||
/>
|
||||
</span>
|
||||
<a
|
||||
className="dropdown-item"
|
||||
@@ -112,7 +116,7 @@ export default function Table({
|
||||
}}
|
||||
>
|
||||
<IconEdit size={16} />
|
||||
<T id="user.edit" />
|
||||
<T id="action.edit" />
|
||||
</a>
|
||||
{currentUserId !== info.row.original.id ? (
|
||||
<>
|
||||
@@ -189,7 +193,16 @@ export default function Table({
|
||||
return (
|
||||
<TableLayout
|
||||
tableInstance={tableInstance}
|
||||
emptyState={<Empty tableInstance={tableInstance} onNewUser={onNewUser} isFiltered={isFiltered} />}
|
||||
emptyState={
|
||||
<EmptyData
|
||||
object="user"
|
||||
objects="users"
|
||||
tableInstance={tableInstance}
|
||||
onNew={onNewUser}
|
||||
isFiltered={isFiltered}
|
||||
color="orange"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import Alert from "react-bootstrap/Alert";
|
||||
import { deleteUser, toggleUser } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useUser, useUsers } from "src/hooks";
|
||||
import { intl, T } from "src/locale";
|
||||
import { T } from "src/locale";
|
||||
import { showDeleteConfirmModal, showPermissionsModal, showSetPasswordModal, showUserModal } from "src/modals";
|
||||
import { showSuccess } from "src/notifications";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
export default function TableWrapper() {
|
||||
@@ -26,14 +26,14 @@ export default function TableWrapper() {
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
await deleteUser(id);
|
||||
showSuccess(intl.formatMessage({ id: "notification.user-deleted" }));
|
||||
showObjectSuccess("user", "deleted");
|
||||
};
|
||||
|
||||
const handleDisableToggle = async (id: number, enabled: boolean) => {
|
||||
await toggleUser(id, enabled);
|
||||
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["user", id] });
|
||||
showSuccess(intl.formatMessage({ id: enabled ? "notification.user-enabled" : "notification.user-disabled" }));
|
||||
showObjectSuccess("user", enabled ? "enabled" : "disabled");
|
||||
};
|
||||
|
||||
let filtered = null;
|
||||
@@ -58,7 +58,7 @@ export default function TableWrapper() {
|
||||
<div className="row w-full">
|
||||
<div className="col">
|
||||
<h2 className="mt-1 mb-0">
|
||||
<T id="users.title" />
|
||||
<T id="users" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
@@ -78,7 +78,7 @@ export default function TableWrapper() {
|
||||
</div>
|
||||
|
||||
<Button size="sm" className="btn-orange" onClick={() => showUserModal("new")}>
|
||||
<T id="users.add" />
|
||||
<T id="object.add" tData={{ object: "user" }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,10 +95,10 @@ export default function TableWrapper() {
|
||||
onSetPassword={(id: number) => showSetPasswordModal(id)}
|
||||
onDeleteUser={(id: number) =>
|
||||
showDeleteConfirmModal({
|
||||
title: "user.delete.title",
|
||||
title: <T id="object.delete" tData={{ object: "user" }} />,
|
||||
onConfirm: () => handleDelete(id),
|
||||
invalidations: [["users"], ["user", id]],
|
||||
children: <T id="user.delete.content" />,
|
||||
children: <T id="object.delete.content" tData={{ object: "user" }} />,
|
||||
})
|
||||
}
|
||||
onDisableToggle={handleDisableToggle}
|
||||
|
||||
Reference in New Issue
Block a user