mirror of
https://github.com/jc21/nginx-proxy-manager.git
synced 2024-08-30 18:22:48 +00:00
Conform hosts table
This commit is contained in:
parent
4ff911def0
commit
af9349d4a7
@ -119,9 +119,6 @@
|
|||||||
"create-hint": {
|
"create-hint": {
|
||||||
"defaultMessage": "Warum erstellen Sie nicht eine?"
|
"defaultMessage": "Warum erstellen Sie nicht eine?"
|
||||||
},
|
},
|
||||||
"create-host": {
|
|
||||||
"defaultMessage": "Host erstellen"
|
|
||||||
},
|
|
||||||
"create-host-title": {
|
"create-host-title": {
|
||||||
"defaultMessage": "Es gibt keine Proxy-Hosts"
|
"defaultMessage": "Es gibt keine Proxy-Hosts"
|
||||||
},
|
},
|
||||||
@ -242,6 +239,9 @@
|
|||||||
"general-settings.title": {
|
"general-settings.title": {
|
||||||
"defaultMessage": "Allgemeine Einstellungen"
|
"defaultMessage": "Allgemeine Einstellungen"
|
||||||
},
|
},
|
||||||
|
"host.create": {
|
||||||
|
"defaultMessage": "Host erstellen"
|
||||||
|
},
|
||||||
"hosts.title": {
|
"hosts.title": {
|
||||||
"defaultMessage": "Gastgeber"
|
"defaultMessage": "Gastgeber"
|
||||||
},
|
},
|
||||||
|
@ -395,9 +395,6 @@
|
|||||||
"create-hint": {
|
"create-hint": {
|
||||||
"defaultMessage": "Why don't you create one?"
|
"defaultMessage": "Why don't you create one?"
|
||||||
},
|
},
|
||||||
"create-host": {
|
|
||||||
"defaultMessage": "Create Host"
|
|
||||||
},
|
|
||||||
"create-host-title": {
|
"create-host-title": {
|
||||||
"defaultMessage": "There are no Hosts"
|
"defaultMessage": "There are no Hosts"
|
||||||
},
|
},
|
||||||
@ -518,6 +515,9 @@
|
|||||||
"general-settings.title": {
|
"general-settings.title": {
|
||||||
"defaultMessage": "General Settings"
|
"defaultMessage": "General Settings"
|
||||||
},
|
},
|
||||||
|
"host.create": {
|
||||||
|
"defaultMessage": "Create Host"
|
||||||
|
},
|
||||||
"hosts.title": {
|
"hosts.title": {
|
||||||
"defaultMessage": "Hosts"
|
"defaultMessage": "Hosts"
|
||||||
},
|
},
|
||||||
|
@ -119,9 +119,6 @@
|
|||||||
"create-hint": {
|
"create-hint": {
|
||||||
"defaultMessage": "چرا یکی را ایجاد نمی کنید؟"
|
"defaultMessage": "چرا یکی را ایجاد نمی کنید؟"
|
||||||
},
|
},
|
||||||
"create-host": {
|
|
||||||
"defaultMessage": "هاست ایجاد کنید"
|
|
||||||
},
|
|
||||||
"create-nginx-template": {
|
"create-nginx-template": {
|
||||||
"defaultMessage": "قالب هاست ایجاد کنید"
|
"defaultMessage": "قالب هاست ایجاد کنید"
|
||||||
},
|
},
|
||||||
@ -245,6 +242,9 @@
|
|||||||
"nginx-templates.title": {
|
"nginx-templates.title": {
|
||||||
"defaultMessage": "قالب های میزبان"
|
"defaultMessage": "قالب های میزبان"
|
||||||
},
|
},
|
||||||
|
"host.create": {
|
||||||
|
"defaultMessage": "هاست ایجاد کنید"
|
||||||
|
},
|
||||||
"hosts.title": {
|
"hosts.title": {
|
||||||
"defaultMessage": "میزبان"
|
"defaultMessage": "میزبان"
|
||||||
},
|
},
|
||||||
|
218
frontend/src/modals/HostCreateModal.tsx
Normal file
218
frontend/src/modals/HostCreateModal.tsx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
FormControl,
|
||||||
|
FormErrorMessage,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Stack,
|
||||||
|
useToast,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { CertificateAuthority } from "api/npm";
|
||||||
|
import { PrettyButton } from "components";
|
||||||
|
import { Formik, Form, Field } from "formik";
|
||||||
|
import { useSetCertificateAuthority } from "hooks";
|
||||||
|
import { intl } from "locale";
|
||||||
|
import { validateNumber, validateString } from "modules/Validations";
|
||||||
|
|
||||||
|
interface HostCreateModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
function HostCreateModal({ isOpen, onClose }: HostCreateModalProps) {
|
||||||
|
const toast = useToast();
|
||||||
|
const { mutate: setCertificateAuthority } = useSetCertificateAuthority();
|
||||||
|
|
||||||
|
const onSubmit = async (
|
||||||
|
payload: CertificateAuthority,
|
||||||
|
{ setErrors, setSubmitting }: any,
|
||||||
|
) => {
|
||||||
|
const showErr = (msg: string) => {
|
||||||
|
toast({
|
||||||
|
description: intl.formatMessage({
|
||||||
|
id: `error.${msg}`,
|
||||||
|
}),
|
||||||
|
status: "error",
|
||||||
|
position: "top",
|
||||||
|
duration: 3000,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setCertificateAuthority(payload, {
|
||||||
|
onError: (err: any) => {
|
||||||
|
if (err.message === "ca-bundle-does-not-exist") {
|
||||||
|
setErrors({
|
||||||
|
caBundle: intl.formatMessage({
|
||||||
|
id: `error.${err.message}`,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showErr(err.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess: () => onClose(),
|
||||||
|
onSettled: () => setSubmitting(false),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} closeOnOverlayClick={false}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<Formik
|
||||||
|
initialValues={
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
acmeshServer: "",
|
||||||
|
caBundle: "",
|
||||||
|
maxDomains: 5,
|
||||||
|
isWildcardSupported: false,
|
||||||
|
} as CertificateAuthority
|
||||||
|
}
|
||||||
|
onSubmit={onSubmit}>
|
||||||
|
{({ isSubmitting }) => (
|
||||||
|
<Form>
|
||||||
|
<ModalHeader>
|
||||||
|
{intl.formatMessage({ id: "certificate-authority.create" })}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Field name="name" validate={validateString(1, 100)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={form.errors.name && form.touched.name}>
|
||||||
|
<FormLabel htmlFor="name">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.name",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="name"
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: "certificate-authority.name",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="acmeshServer" validate={validateString(2, 255)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.acmeshServer && form.touched.acmeshServer
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="acmeshServer">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.acmesh-server",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="acmeshServer"
|
||||||
|
placeholder="https://example.com/acme/directory"
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.acmeshServer}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="caBundle" validate={validateString(2, 255)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.caBundle && form.touched.caBundle
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="caBundle">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.ca-bundle",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="caBundle"
|
||||||
|
placeholder="/path/to/certs/custom-ca-bundle.crt"
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.caBundle}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
name="maxDomains"
|
||||||
|
validate={validateNumber(1)}
|
||||||
|
type="number">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.maxDomains && form.touched.maxDomains
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="maxDomains">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.max-domains",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input {...field} id="maxDomains" type="number" />
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.maxDomains}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="isWildcardSupported" type="checkbox">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isInvalid={
|
||||||
|
form.errors.isWildcardSupported &&
|
||||||
|
form.touched.isWildcardSupported
|
||||||
|
}>
|
||||||
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
isChecked={field.checked}
|
||||||
|
size="md"
|
||||||
|
colorScheme="green">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.has-wildcard-support",
|
||||||
|
})}
|
||||||
|
</Checkbox>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.isWildcardSupported}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Stack>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<PrettyButton mr={3} isLoading={isSubmitting}>
|
||||||
|
{intl.formatMessage({ id: "form.save" })}
|
||||||
|
</PrettyButton>
|
||||||
|
<Button onClick={onClose} isLoading={isSubmitting}>
|
||||||
|
{intl.formatMessage({ id: "form.cancel" })}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HostCreateModal };
|
@ -6,6 +6,7 @@ export * from "./CertificateEditModal";
|
|||||||
export * from "./ChangePasswordModal";
|
export * from "./ChangePasswordModal";
|
||||||
export * from "./DNSProviderCreateModal";
|
export * from "./DNSProviderCreateModal";
|
||||||
// export * from "./DNSProviderEditModal.tsx.disabled";
|
// export * from "./DNSProviderEditModal.tsx.disabled";
|
||||||
|
export * from "./HostCreateModal";
|
||||||
export * from "./ProfileModal";
|
export * from "./ProfileModal";
|
||||||
export * from "./SetPasswordModal";
|
export * from "./SetPasswordModal";
|
||||||
export * from "./UpstreamCreateModal";
|
export * from "./UpstreamCreateModal";
|
||||||
|
@ -18,33 +18,22 @@ import { intl } from "locale";
|
|||||||
import { FiEdit } from "react-icons/fi";
|
import { FiEdit } from "react-icons/fi";
|
||||||
import { useSortBy, useFilters, useTable, usePagination } from "react-table";
|
import { useSortBy, useFilters, useTable, usePagination } from "react-table";
|
||||||
|
|
||||||
const rowActions = [
|
export interface TableProps {
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: "action.edit" }),
|
|
||||||
onClick: (e: any, data: any) => {
|
|
||||||
alert(JSON.stringify(data, null, 2));
|
|
||||||
},
|
|
||||||
icon: <FiEdit />,
|
|
||||||
show: (data: any) => !data.isSystem,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export interface HostsTableProps {
|
|
||||||
data: any;
|
data: any;
|
||||||
pagination: TablePagination;
|
pagination: TablePagination;
|
||||||
sortBy: TableSortBy[];
|
sortBy: TableSortBy[];
|
||||||
filters: TableFilter[];
|
filters: TableFilter[];
|
||||||
onTableEvent: any;
|
onTableEvent: any;
|
||||||
}
|
}
|
||||||
function HostsTable({
|
function Table({
|
||||||
data,
|
data,
|
||||||
pagination,
|
pagination,
|
||||||
onTableEvent,
|
onTableEvent,
|
||||||
sortBy,
|
sortBy,
|
||||||
filters,
|
filters,
|
||||||
}: HostsTableProps) {
|
}: TableProps) {
|
||||||
const [columns, tableData] = useMemo(() => {
|
const [columns, tableData] = useMemo(() => {
|
||||||
const columns: any[] = [
|
const columns: any = [
|
||||||
{
|
{
|
||||||
accessor: "user.gravatarUrl",
|
accessor: "user.gravatarUrl",
|
||||||
Cell: GravatarFormatter(),
|
Cell: GravatarFormatter(),
|
||||||
@ -82,7 +71,15 @@ function HostsTable({
|
|||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
accessor: "id",
|
accessor: "id",
|
||||||
Cell: ActionsFormatter(rowActions),
|
Cell: ActionsFormatter([
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: "action.edit" }),
|
||||||
|
onClick: (e: any, data: any) => {
|
||||||
|
alert(JSON.stringify(data, null, 2));
|
||||||
|
},
|
||||||
|
icon: <FiEdit />,
|
||||||
|
},
|
||||||
|
]),
|
||||||
className: "w-80",
|
className: "w-80",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -162,4 +159,4 @@ function HostsTable({
|
|||||||
return <TableLayout pagination={pagination} {...tableInstance} />;
|
return <TableLayout pagination={pagination} {...tableInstance} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { HostsTable };
|
export default Table;
|
97
frontend/src/pages/Hosts/TableWrapper.tsx
Normal file
97
frontend/src/pages/Hosts/TableWrapper.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { useEffect, useReducer, useState } from "react";
|
||||||
|
|
||||||
|
import { Alert, AlertIcon } from "@chakra-ui/react";
|
||||||
|
import {
|
||||||
|
EmptyList,
|
||||||
|
PrettyButton,
|
||||||
|
SpinnerPage,
|
||||||
|
tableEventReducer,
|
||||||
|
} from "components";
|
||||||
|
import { useHosts } from "hooks";
|
||||||
|
import { intl } from "locale";
|
||||||
|
|
||||||
|
import Table from "./Table";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
offset: 0,
|
||||||
|
limit: 10,
|
||||||
|
sortBy: [
|
||||||
|
{
|
||||||
|
id: "name",
|
||||||
|
desc: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
filters: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TableWrapperProps {
|
||||||
|
onCreateClick?: () => void;
|
||||||
|
}
|
||||||
|
function TableWrapper({ onCreateClick }: TableWrapperProps) {
|
||||||
|
const [{ offset, limit, sortBy, filters }, dispatch] = useReducer(
|
||||||
|
tableEventReducer,
|
||||||
|
initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [tableData, setTableData] = useState(null);
|
||||||
|
const { isFetching, isLoading, isError, error, data } = useHosts(
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
sortBy,
|
||||||
|
filters,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTableData(data as any);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
if (isFetching || isLoading || !tableData) {
|
||||||
|
return <SpinnerPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<Alert status="error">
|
||||||
|
<AlertIcon />
|
||||||
|
{error?.message || "Unknown error"}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFetching || isLoading || !tableData) {
|
||||||
|
return <SpinnerPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there are no items and no filters active, show the nicer empty view
|
||||||
|
if (data?.total === 0 && filters?.length === 0) {
|
||||||
|
return (
|
||||||
|
<EmptyList
|
||||||
|
title={intl.formatMessage({ id: "create-host-title" })}
|
||||||
|
summary={intl.formatMessage({ id: "create-hint" })}
|
||||||
|
createButton={
|
||||||
|
<PrettyButton mt={5} onClick={onCreateClick}>
|
||||||
|
{intl.formatMessage({ id: "lets-go" })}
|
||||||
|
</PrettyButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pagination = {
|
||||||
|
offset: data?.offset || initialState.offset,
|
||||||
|
limit: data?.limit || initialState.limit,
|
||||||
|
total: data?.total || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
data={data?.items || []}
|
||||||
|
pagination={pagination}
|
||||||
|
sortBy={sortBy}
|
||||||
|
filters={filters}
|
||||||
|
onTableEvent={dispatch}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableWrapper;
|
@ -1,95 +1,30 @@
|
|||||||
import { useEffect, useReducer, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { Alert, AlertIcon, Heading, HStack } from "@chakra-ui/react";
|
import { Heading, HStack } from "@chakra-ui/react";
|
||||||
import {
|
import { HelpDrawer, PrettyButton } from "components";
|
||||||
EmptyList,
|
|
||||||
PrettyButton,
|
|
||||||
SpinnerPage,
|
|
||||||
tableEventReducer,
|
|
||||||
} from "components";
|
|
||||||
import { useHosts } from "hooks";
|
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
import { HostCreateModal } from "modals";
|
||||||
|
|
||||||
import { HostsTable } from "./HostsTable";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
offset: 0,
|
|
||||||
limit: 10,
|
|
||||||
sortBy: [
|
|
||||||
{
|
|
||||||
id: "domain_names",
|
|
||||||
desc: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
filters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
function Hosts() {
|
function Hosts() {
|
||||||
const [{ offset, limit, sortBy, filters }, dispatch] = useReducer(
|
const [createShown, setCreateShown] = useState(false);
|
||||||
tableEventReducer,
|
|
||||||
initialState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [tableData, setTableData] = useState(null);
|
|
||||||
const { isFetching, isLoading, error, data } = useHosts(
|
|
||||||
offset,
|
|
||||||
limit,
|
|
||||||
sortBy,
|
|
||||||
filters,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTableData(data as any);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
if (error || (!tableData && !isFetching && !isLoading)) {
|
|
||||||
return (
|
|
||||||
<Alert status="error">
|
|
||||||
<AlertIcon />
|
|
||||||
{error?.message || "Unknown error"}
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFetching || isLoading || !tableData) {
|
|
||||||
return <SpinnerPage />;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When there are no items and no filters active, show the nicer empty view
|
|
||||||
if (data?.total === 0 && filters?.length === 0) {
|
|
||||||
return (
|
|
||||||
<EmptyList
|
|
||||||
title={intl.formatMessage({ id: "create-host-title" })}
|
|
||||||
summary={intl.formatMessage({ id: "create-hint" })}
|
|
||||||
createButton={
|
|
||||||
<PrettyButton mt={5}>
|
|
||||||
{intl.formatMessage({ id: "lets-go" })}
|
|
||||||
</PrettyButton>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
offset: data?.offset || initialState.offset,
|
|
||||||
limit: data?.limit || initialState.limit,
|
|
||||||
total: data?.total || 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HStack mx={6} my={4} justifyContent="space-between">
|
<HStack mx={6} my={4} justifyContent="space-between">
|
||||||
<Heading mb={2}>{intl.formatMessage({ id: "hosts.title" })}</Heading>
|
<Heading mb={2}>{intl.formatMessage({ id: "hosts.title" })}</Heading>
|
||||||
<PrettyButton size="sm">
|
<HStack>
|
||||||
{intl.formatMessage({ id: "create-host" })}
|
<HelpDrawer section="Hosts" />
|
||||||
</PrettyButton>
|
<PrettyButton size="sm" onClick={() => setCreateShown(true)}>
|
||||||
|
{intl.formatMessage({ id: "host.create" })}
|
||||||
|
</PrettyButton>
|
||||||
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HostsTable
|
<TableWrapper onCreateClick={() => setCreateShown(true)} />
|
||||||
data={data?.items || []}
|
<HostCreateModal
|
||||||
pagination={pagination}
|
isOpen={createShown}
|
||||||
sortBy={sortBy}
|
onClose={() => setCreateShown(false)}
|
||||||
filters={filters}
|
|
||||||
onTableEvent={dispatch}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user