diff --git a/frontend/package.json b/frontend/package.json index 1a057f54..7aefa5d0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@types/react": "18.0.15", "@types/react-dom": "18.0.6", "@types/react-router-dom": "5.3.3", + "@types/react-syntax-highlighter": "^15.5.6", "@types/react-table": "^7.7.12", "@types/styled-components": "5.1.25", "@typescript-eslint/eslint-plugin": "^5.30.6", @@ -57,6 +58,7 @@ "react-query": "^3.39.1", "react-router-dom": "^6.3.0", "react-scripts": "5.0.1", + "react-syntax-highlighter": "^15.5.0", "react-table": "7.8.0", "rooks": "5.11.8", "tmp": "^0.2.1", diff --git a/frontend/src/api/npm/getUpstreamNginxConfig.ts b/frontend/src/api/npm/getUpstreamNginxConfig.ts new file mode 100644 index 00000000..f106344d --- /dev/null +++ b/frontend/src/api/npm/getUpstreamNginxConfig.ts @@ -0,0 +1,12 @@ +import * as api from "./base"; + +export async function getUpstreamNginxConfig( + id: number, + params = {}, +): Promise { + const { result } = await api.get({ + url: `/upstreams/${id}/nginx-config`, + params, + }); + return result; +} diff --git a/frontend/src/api/npm/index.ts b/frontend/src/api/npm/index.ts index 5bccc13f..8c72bf00 100644 --- a/frontend/src/api/npm/index.ts +++ b/frontend/src/api/npm/index.ts @@ -13,6 +13,7 @@ export * from "./getHosts"; export * from "./getNginxTemplates"; export * from "./getSettings"; export * from "./getToken"; +export * from "./getUpstreamNginxConfig"; export * from "./getUpstreams"; export * from "./getUser"; export * from "./getUsers"; diff --git a/frontend/src/components/Table/Formatters.tsx b/frontend/src/components/Table/Formatters.tsx index d5bd58b9..513e884c 100644 --- a/frontend/src/components/Table/Formatters.tsx +++ b/frontend/src/components/Table/Formatters.tsx @@ -13,6 +13,8 @@ import { Monospace, RowAction, RowActionsMenu } from "components"; import { intl } from "locale"; import getNiceDNSProvider from "modules/Acmesh"; +const errorColor = "red.400"; + function ActionsFormatter(rowActions: RowAction[]) { const formatCell = (instance: any) => { return ; @@ -24,7 +26,7 @@ function ActionsFormatter(rowActions: RowAction[]) { function BooleanFormatter() { const formatCell = ({ value }: any) => { return ( - + {value ? "true" : "false"} ); @@ -86,7 +88,7 @@ function CertificateStatusFormatter() { let color = "cyan.500"; switch (value) { case "failed": - color = "red.400"; + color = errorColor; break; case "provided": color = "green.400"; @@ -137,7 +139,7 @@ function DisabledFormatter() { const formatCell = ({ value, row }: any) => { if (row?.original?.isDisabled) { return ( - + {value} @@ -173,7 +175,7 @@ function DomainsFormatter() { ); } - return No domains!; + return No domains!; }; return formatCell; @@ -191,7 +193,9 @@ function HostStatusFormatter() { const formatCell = ({ row }: any) => { if (row.original.isDisabled) { return ( - {intl.formatMessage({ id: "disabled" })} + + {intl.formatMessage({ id: "disabled" })} + ); } @@ -209,7 +213,9 @@ function HostStatusFormatter() { if (row.original.certificate.status === "error") { return ( - {intl.formatMessage({ id: "error" })} + + {intl.formatMessage({ id: "error" })} + ); } @@ -255,9 +261,21 @@ function UpstreamStatusFormatter() { } if (value === "error") { return ( - - {intl.formatMessage({ id: "error" })} - + + + + {intl.formatMessage({ id: "error" })} + + + + + +
+								{row?.original?.errorMessage}
+							
+
+
+
); } }; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 2d150a2a..35331a0d 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -9,6 +9,7 @@ export * from "./useHealth"; export * from "./useHosts"; export * from "./useNginxTemplates"; export * from "./useSettings"; +export * from "./useUpstreamNginxConfig"; export * from "./useUpstreams"; export * from "./useUser"; export * from "./useUsers"; diff --git a/frontend/src/hooks/useUpstreamNginxConfig.ts b/frontend/src/hooks/useUpstreamNginxConfig.ts new file mode 100644 index 00000000..8dc28b44 --- /dev/null +++ b/frontend/src/hooks/useUpstreamNginxConfig.ts @@ -0,0 +1,19 @@ +import { getUpstreamNginxConfig } from "api/npm"; +import { useQuery } from "react-query"; + +const fetchUpstreamNginxConfig = (id: any) => { + return getUpstreamNginxConfig(id); +}; + +const useUpstreamNginxConfig = (id: number, options = {}) => { + return useQuery( + ["upstream-nginx-config", id], + () => fetchUpstreamNginxConfig(id), + { + staleTime: 30 * 1000, // 30 seconds + ...options, + }, + ); +}; + +export { useUpstreamNginxConfig }; diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index 239c9bed..e937eb58 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -1,4 +1,7 @@ { + "access-list.create": { + "defaultMessage": "Create Access List" + }, "access-lists.title": { "defaultMessage": "Access Lists" }, @@ -242,6 +245,9 @@ "action.edit": { "defaultMessage": "Edit" }, + "action.nginx-config": { + "defaultMessage": "View Nginx Config" + }, "action.set-password": { "defaultMessage": "Set Password" }, @@ -372,7 +378,7 @@ "defaultMessage": "Wildcard Support" }, "create-access-list-title": { - "defaultMessage": "Create Access List" + "defaultMessage": "There are no Access Lists" }, "create-certificate": { "defaultMessage": "Create Certificate" @@ -398,9 +404,6 @@ "create-host-title": { "defaultMessage": "There are no Proxy Hosts" }, - "create-upstream": { - "defaultMessage": "Create Upstream" - }, "create-upstream-title": { "defaultMessage": "There are no Upstreams" }, @@ -521,6 +524,9 @@ "general-settings.title": { "defaultMessage": "General Settings" }, + "nginx-config": { + "defaultMessage": "Nginx Config" + }, "nginx-templates.title": { "defaultMessage": "Nginx Templates" }, @@ -653,6 +659,9 @@ "unhealthy.title": { "defaultMessage": "Nginx Proxy Manager is unhealthy" }, + "upstream.create": { + "defaultMessage": "Create Upstream" + }, "upstreams.title": { "defaultMessage": "Upstreams" }, diff --git a/frontend/src/modals/AccessListCreateModal.tsx b/frontend/src/modals/AccessListCreateModal.tsx new file mode 100644 index 00000000..e0604e10 --- /dev/null +++ b/frontend/src/modals/AccessListCreateModal.tsx @@ -0,0 +1,222 @@ +// TODO +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 AccessListCreateModalProps { + isOpen: boolean; + onClose: () => void; +} +function AccessListCreateModal({ + isOpen, + onClose, +}: AccessListCreateModalProps) { + 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 ( + + + + + {({ isSubmitting }) => ( +
+ + {intl.formatMessage({ id: "certificate-authority.create" })} + + + + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.name", + })} + + + {form.errors.name} + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.acmesh-server", + })} + + + + {form.errors.acmeshServer} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.ca-bundle", + })} + + + + {form.errors.caBundle} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.max-domains", + })} + + + + {form.errors.maxDomains} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.has-wildcard-support", + })} + + + {form.errors.isWildcardSupported} + + + )} + + + + + + {intl.formatMessage({ id: "form.save" })} + + + + + )} +
+
+
+ ); +} + +export { AccessListCreateModal }; diff --git a/frontend/src/modals/UpstreamCreateModal.tsx b/frontend/src/modals/UpstreamCreateModal.tsx new file mode 100644 index 00000000..0aeb7561 --- /dev/null +++ b/frontend/src/modals/UpstreamCreateModal.tsx @@ -0,0 +1,219 @@ +// TODO +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 UpstreamCreateModalProps { + isOpen: boolean; + onClose: () => void; +} +function UpstreamCreateModal({ isOpen, onClose }: UpstreamCreateModalProps) { + 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 ( + + + + + {({ isSubmitting }) => ( +
+ + {intl.formatMessage({ id: "certificate-authority.create" })} + + + + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.name", + })} + + + {form.errors.name} + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.acmesh-server", + })} + + + + {form.errors.acmeshServer} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.ca-bundle", + })} + + + + {form.errors.caBundle} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.max-domains", + })} + + + + {form.errors.maxDomains} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.has-wildcard-support", + })} + + + {form.errors.isWildcardSupported} + + + )} + + + + + + {intl.formatMessage({ id: "form.save" })} + + + + + )} +
+
+
+ ); +} + +export { UpstreamCreateModal }; diff --git a/frontend/src/modals/UpstreamEditModal.tsx b/frontend/src/modals/UpstreamEditModal.tsx new file mode 100644 index 00000000..0ca9daf4 --- /dev/null +++ b/frontend/src/modals/UpstreamEditModal.tsx @@ -0,0 +1,241 @@ +// TODO +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 { useCertificateAuthority, useSetCertificateAuthority } from "hooks"; +import { intl } from "locale"; +import { validateNumber, validateString } from "modules/Validations"; + +interface UpstreamEditModalProps { + editId: number; + isOpen: boolean; + onClose: () => void; +} +function UpstreamEditModal({ + editId, + isOpen, + onClose, +}: UpstreamEditModalProps) { + const toast = useToast(); + const { status, data } = useCertificateAuthority(editId); + 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 ( + { + onClose(); + }} + closeOnOverlayClick={false}> + + + {status === "loading" ? ( + // todo nicer +

loading

+ ) : ( + + {({ isSubmitting }) => ( +
+ + {intl.formatMessage({ id: "certificate-authority.edit" })} + + + + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.name", + })} + + + + {form.errors.name} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.acmesh-server", + })} + + + + {form.errors.acmeshServer} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.ca-bundle", + })} + + + + {form.errors.caBundle} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.max-domains", + })} + + + + {form.errors.maxDomains} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.has-wildcard-support", + })} + + + {form.errors.isWildcardSupported} + + + )} + + + + + + {intl.formatMessage({ id: "form.save" })} + + + + + )} +
+ )} +
+
+ ); +} + +export { UpstreamEditModal }; diff --git a/frontend/src/modals/UpstreamNginxConfigModal.tsx b/frontend/src/modals/UpstreamNginxConfigModal.tsx new file mode 100644 index 00000000..be0a868d --- /dev/null +++ b/frontend/src/modals/UpstreamNginxConfigModal.tsx @@ -0,0 +1,62 @@ +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, +} from "@chakra-ui/react"; +import { useUpstreamNginxConfig } from "hooks"; +import { intl } from "locale"; +import { Light as SyntaxHighlighter } from "react-syntax-highlighter"; +import sh from "react-syntax-highlighter/dist/esm/languages/hljs/bash"; +import nord from "react-syntax-highlighter/dist/esm/styles/hljs/nord"; + +interface UpstreamNginxConfigModalProps { + upstreamId: number; + isOpen: boolean; + onClose: () => void; +} +function UpstreamNginxConfigModal({ + isOpen, + onClose, + upstreamId, +}: UpstreamNginxConfigModalProps) { + const { isLoading, data } = useUpstreamNginxConfig(upstreamId); + SyntaxHighlighter.registerLanguage("bash", sh); + + return ( + + + + {isLoading ? ( + "loading" + ) : ( + <> + + {intl.formatMessage({ id: "nginx-config" })} + + + + + {data || ""} + + + + )} + + + ); +} + +export { UpstreamNginxConfigModal }; diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts index e3e0b45a..b4a3bc2d 100644 --- a/frontend/src/modals/index.ts +++ b/frontend/src/modals/index.ts @@ -1,3 +1,4 @@ +export * from "./AccessListCreateModal"; export * from "./CertificateAuthorityCreateModal"; export * from "./CertificateAuthorityEditModal"; export * from "./ChangePasswordModal"; @@ -5,5 +6,8 @@ export * from "./DNSProviderCreateModal"; // export * from "./DNSProviderEditModal.tsx.disabled"; export * from "./ProfileModal"; export * from "./SetPasswordModal"; +export * from "./UpstreamCreateModal"; +export * from "./UpstreamEditModal"; +export * from "./UpstreamNginxConfigModal"; export * from "./UserCreateModal"; export * from "./UserEditModal"; diff --git a/frontend/src/pages/AccessLists/index.tsx b/frontend/src/pages/AccessLists/index.tsx index 3096742c..7c957e47 100644 --- a/frontend/src/pages/AccessLists/index.tsx +++ b/frontend/src/pages/AccessLists/index.tsx @@ -1,15 +1,33 @@ -import { Heading } from "@chakra-ui/react"; +import { useState } from "react"; + +import { Heading, HStack } from "@chakra-ui/react"; +import { HelpDrawer, PrettyButton } from "components"; import { intl } from "locale"; +import { AccessListCreateModal } from "modals"; import TableWrapper from "./TableWrapper"; function AccessLists() { + const [createShown, setCreateShown] = useState(false); + return ( <> - - {intl.formatMessage({ id: "access-lists.title" })} - - + + + {intl.formatMessage({ id: "access-lists.title" })} + + + + setCreateShown(true)}> + {intl.formatMessage({ id: "access-list.create" })} + + + + setCreateShown(true)} /> + setCreateShown(false)} + /> ); } diff --git a/frontend/src/pages/Upstreams/UpstreamsTable.tsx b/frontend/src/pages/Upstreams/Table.tsx similarity index 72% rename from frontend/src/pages/Upstreams/UpstreamsTable.tsx rename to frontend/src/pages/Upstreams/Table.tsx index 2156e24d..7c130687 100644 --- a/frontend/src/pages/Upstreams/UpstreamsTable.tsx +++ b/frontend/src/pages/Upstreams/Table.tsx @@ -1,48 +1,40 @@ -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { tableEvents, ActionsFormatter, GravatarFormatter, - UpstreamStatusFormatter, IDFormatter, TableFilter, TableLayout, TablePagination, TableSortBy, TextFilter, + UpstreamStatusFormatter, } from "components"; import { intl } from "locale"; -import { FiEdit } from "react-icons/fi"; +import { UpstreamEditModal, UpstreamNginxConfigModal } from "modals"; +import { FiEdit, FiHardDrive } from "react-icons/fi"; import { useSortBy, useFilters, useTable, usePagination } from "react-table"; -const rowActions = [ - { - title: intl.formatMessage({ id: "action.edit" }), - onClick: (e: any, data: any) => { - alert(JSON.stringify(data, null, 2)); - }, - icon: , - show: (data: any) => !data.isSystem, - }, -]; - -export interface UpstreamsTableProps { +export interface TableProps { data: any; pagination: TablePagination; sortBy: TableSortBy[]; filters: TableFilter[]; onTableEvent: any; } -function UpstreamsTable({ +function Table({ data, pagination, onTableEvent, sortBy, filters, -}: UpstreamsTableProps) { +}: TableProps) { + const [editId, setEditId] = useState(0); + const [configId, setConfigId] = useState(0); const [columns, tableData] = useMemo(() => { - const columns: any[] = [ + const columns: any = [ { accessor: "user.gravatarUrl", Cell: GravatarFormatter(), @@ -73,8 +65,19 @@ function UpstreamsTable({ { id: "actions", accessor: "id", - Cell: ActionsFormatter(rowActions), className: "w-80", + Cell: ActionsFormatter([ + { + title: intl.formatMessage({ id: "action.edit" }), + onClick: (e: any, { id }: any) => setEditId(id), + icon: , + }, + { + title: intl.formatMessage({ id: "action.nginx-config" }), + onClick: (e: any, { id }: any) => setConfigId(id), + icon: , + }, + ]), }, ]; return [columns, data]; @@ -150,7 +153,25 @@ function UpstreamsTable({ }); }, [onTableEvent, tableInstance.state.filters]); - return ; + return ( + <> + + {editId ? ( + setEditId(0)} + /> + ) : null} + {configId ? ( + setConfigId(0)} + /> + ) : null} + + ); } -export { UpstreamsTable }; +export default Table; diff --git a/frontend/src/pages/Upstreams/TableWrapper.tsx b/frontend/src/pages/Upstreams/TableWrapper.tsx new file mode 100644 index 00000000..2818c5a9 --- /dev/null +++ b/frontend/src/pages/Upstreams/TableWrapper.tsx @@ -0,0 +1,87 @@ +import { useEffect, useReducer, useState } from "react"; + +import { Alert, AlertIcon } from "@chakra-ui/react"; +import { EmptyList, SpinnerPage, tableEventReducer } from "components"; +import { useUpstreams } 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 } = useUpstreams( + offset, + limit, + sortBy, + filters, + ); + + useEffect(() => { + setTableData(data as any); + }, [data]); + + if (isFetching || isLoading || !tableData) { + return ; + } + + if (isError) { + return ( + + + {error?.message || "Unknown error"} + + ); + } + + if (isFetching || isLoading || !tableData) { + return ; + } + + // When there are no items and no filters active, show the nicer empty view + if (data?.total === 0 && filters?.length === 0) { + return ( + + ); + } + + const pagination = { + offset: data?.offset || initialState.offset, + limit: data?.limit || initialState.limit, + total: data?.total || 0, + }; + + return ( + + ); +} + +export default TableWrapper; diff --git a/frontend/src/pages/Upstreams/index.tsx b/frontend/src/pages/Upstreams/index.tsx index 1cecc9c8..07a2a225 100644 --- a/frontend/src/pages/Upstreams/index.tsx +++ b/frontend/src/pages/Upstreams/index.tsx @@ -1,80 +1,14 @@ -import { useEffect, useReducer, useState } from "react"; +import { useState } from "react"; -import { Alert, AlertIcon, Heading, HStack } from "@chakra-ui/react"; -import { - EmptyList, - PrettyButton, - SpinnerPage, - tableEventReducer, -} from "components"; -import { useUpstreams } from "hooks"; +import { Heading, HStack } from "@chakra-ui/react"; +import { HelpDrawer, PrettyButton } from "components"; import { intl } from "locale"; +import { UpstreamCreateModal } from "modals"; -import { UpstreamsTable } from "./UpstreamsTable"; - -const initialState = { - offset: 0, - limit: 10, - sortBy: [ - { - id: "name", - desc: false, - }, - ], - filters: [], -}; +import TableWrapper from "./TableWrapper"; function Upstreams() { - const [{ offset, limit, sortBy, filters }, dispatch] = useReducer( - tableEventReducer, - initialState, - ); - - const [tableData, setTableData] = useState(null); - const { isFetching, isLoading, error, data } = useUpstreams( - offset, - limit, - sortBy, - filters, - ); - - useEffect(() => { - setTableData(data as any); - }, [data]); - - if (error || (!tableData && !isFetching && !isLoading)) { - return ( - - - {error?.message || "Unknown error"} - - ); - } - - if (isFetching || isLoading || !tableData) { - return ; - } - - // When there are no items and no filters active, show the nicer empty view - if (data?.total === 0 && filters?.length === 0) { - return ( - - {intl.formatMessage({ id: "lets-go" })} - - } - /> - ); - } - - const pagination = { - offset: data?.offset || initialState.offset, - limit: data?.limit || initialState.limit, - total: data?.total || 0, - }; + const [createShown, setCreateShown] = useState(false); return ( <> @@ -82,16 +16,17 @@ function Upstreams() { {intl.formatMessage({ id: "upstreams.title" })} - - {intl.formatMessage({ id: "create-upstream" })} - + + + setCreateShown(true)}> + {intl.formatMessage({ id: "upstream.create" })} + + - setCreateShown(true)} /> + setCreateShown(false)} /> ); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 23375f89..b8c82678 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1034,6 +1034,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.3.1": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd" + integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.18.6", "@babel/template@^7.3.3": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" @@ -3108,6 +3115,13 @@ "@types/history" "^4.7.11" "@types/react" "*" +"@types/react-syntax-highlighter@^15.5.6": + version "15.5.6" + resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz#77c95e6b74d2be23208fcdcf187b93b47025f1b1" + integrity sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg== + dependencies: + "@types/react" "*" + "@types/react-table@^7.7.12": version "7.7.12" resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.12.tgz#628011d3cb695b07c678704a61f2f1d5b8e567fd" @@ -4289,11 +4303,26 @@ char-regex@^2.0.0: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e" integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw== +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + character-entities@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + check-types@^11.1.1: version "11.1.2" resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" @@ -4430,6 +4459,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + comma-separated-tokens@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98" @@ -5792,6 +5826,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fault@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" + integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== + dependencies: + format "^0.2.0" + faye-websocket@^0.11.3: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" @@ -5969,6 +6010,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + formik@^2.2.9: version "2.2.9" resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" @@ -6363,11 +6409,27 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hast-util-parse-selector@^2.0.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" + integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== + hast-util-whitespace@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c" integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg== +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -6378,6 +6440,11 @@ hey-listen@^1.0.8: resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68" integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q== +highlight.js@^10.4.1, highlight.js@~10.7.0: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + history@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" @@ -6710,6 +6777,19 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -6761,6 +6841,11 @@ is-date-object@^1.0.1: dependencies: has-tostringtag "^1.0.0" +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -6788,6 +6873,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -7853,6 +7943,14 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowlight@^1.17.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" + integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw== + dependencies: + fault "^1.0.0" + highlight.js "~10.7.0" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -8858,6 +8956,18 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -9569,6 +9679,16 @@ pretty-format@^28.0.0, pretty-format@^28.1.3: ansi-styles "^5.0.0" react-is "^18.0.0" +prismjs@^1.27.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +prismjs@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -9616,6 +9736,13 @@ prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-information@^5.0.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + property-information@^6.0.0: version "6.1.1" resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22" @@ -9968,6 +10095,17 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" +react-syntax-highlighter@^15.5.0: + version "15.5.0" + resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" + integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg== + dependencies: + "@babel/runtime" "^7.3.1" + highlight.js "^10.4.1" + lowlight "^1.17.0" + prismjs "^1.27.0" + refractor "^3.6.0" + react-table@7.8.0: version "7.8.0" resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.8.0.tgz#07858c01c1718c09f7f1aed7034fcfd7bda907d2" @@ -10050,6 +10188,15 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +refractor@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" + integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA== + dependencies: + hastscript "^6.0.0" + parse-entities "^2.0.0" + prismjs "~1.27.0" + regenerate-unicode-properties@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" @@ -10062,6 +10209,11 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -10656,6 +10808,11 @@ sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + space-separated-tokens@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b" @@ -12106,7 +12263,7 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.2: +xtend@^4.0.0, xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==