Moved v3 code from NginxProxyManager/nginx-proxy-manager-3 to NginxProxyManager/nginx-proxy-manager

This commit is contained in:
Jamie Curnow
2022-05-12 08:47:31 +10:00
parent 4db34f5894
commit 2110ecc382
830 changed files with 38168 additions and 36635 deletions

View File

@@ -0,0 +1,225 @@
import { Avatar, Badge, Text, Tooltip } from "@chakra-ui/react";
import { RowAction, RowActionsMenu } from "components";
import { intl } from "locale";
import getNiceDNSProvider from "modules/Acmesh";
function ActionsFormatter(rowActions: RowAction[]) {
const formatCell = (instance: any) => {
return <RowActionsMenu data={instance.row.original} actions={rowActions} />;
};
return formatCell;
}
function BooleanFormatter() {
const formatCell = ({ value }: any) => {
return (
<Badge color={value ? "cyan.500" : "red.400"}>
{value ? "true" : "false"}
</Badge>
);
};
return formatCell;
}
function CapabilitiesFormatter() {
const formatCell = ({ row, value }: any) => {
const style = {} as any;
if (row?.original?.isDisabled) {
style.textDecoration = "line-through";
}
if (row?.original?.isSystem) {
return (
<Badge color="orange.400" style={style}>
{intl.formatMessage({ id: "capability.system" })}
</Badge>
);
}
if (value?.indexOf("full-admin") !== -1) {
return (
<Badge color="teal.300" style={style}>
{intl.formatMessage({ id: "capability.full-admin" })}
</Badge>
);
}
if (value?.length) {
const strs: string[] = [];
value.map((c: string) => {
strs.push(intl.formatMessage({ id: `capability.${c}` }));
return null;
});
return (
<Tooltip label={strs.join(", \n")}>
<Badge color="cyan.500" style={style}>
{intl.formatMessage(
{ id: "capability-count" },
{ count: value.length },
)}
</Badge>
</Tooltip>
);
}
return null;
};
return formatCell;
}
function CertificateStatusFormatter() {
const formatCell = ({ value }: any) => {
return (
<Badge color={value ? "cyan.500" : "red.400"}>
{value
? intl.formatMessage({ id: "ready" })
: intl.formatMessage({ id: "setup-required" })}
</Badge>
);
};
return formatCell;
}
function DisabledFormatter() {
const formatCell = ({ value, row }: any) => {
if (row?.original?.isDisabled) {
return (
<Text color="red.500">
<Tooltip label={intl.formatMessage({ id: "user.disabled" })}>
{value}
</Tooltip>
</Text>
);
}
return value;
};
return formatCell;
}
function DNSProviderFormatter() {
const formatCell = ({ value }: any) => {
return getNiceDNSProvider(value);
};
return formatCell;
}
function DomainsFormatter() {
const formatCell = ({ value }: any) => {
if (value?.length > 0) {
return (
<>
{value.map((dom: string, idx: number) => {
return (
<Badge key={`domain-${idx}`} color="yellow.400">
{dom}
</Badge>
);
})}
</>
);
}
return <Badge color="red.400">No domains!</Badge>;
};
return formatCell;
}
function GravatarFormatter() {
const formatCell = ({ value }: any) => {
return <Avatar size="sm" src={value} />;
};
return formatCell;
}
function HostStatusFormatter() {
const formatCell = ({ row }: any) => {
if (row.original.isDisabled) {
return (
<Badge color="red.400">{intl.formatMessage({ id: "disabled" })}</Badge>
);
}
if (row.original.certificateId) {
if (row.original.certificate.status === "provided") {
return (
<Badge color="green.400">
{row.original.sslForced
? intl.formatMessage({ id: "https-only" })
: intl.formatMessage({ id: "http-https" })}
</Badge>
);
}
if (row.original.certificate.status === "error") {
return (
<Tooltip label={row.original.certificate.errorMessage}>
<Badge color="red.400">{intl.formatMessage({ id: "error" })}</Badge>
</Tooltip>
);
}
return (
<Badge color="cyan.400">
{intl.formatMessage({
id: `certificate.${row.original.certificate.status}`,
})}
</Badge>
);
}
return (
<Badge color="orange.400">
{intl.formatMessage({ id: "http-only" })}
</Badge>
);
};
return formatCell;
}
function HostTypeFormatter() {
const formatCell = ({ value }: any) => {
return intl.formatMessage({ id: `host-type.${value}` });
};
return formatCell;
}
function IDFormatter() {
const formatCell = ({ value }: any) => {
return <span className="text-muted">{value}</span>;
};
return formatCell;
}
function SecondsFormatter() {
const formatCell = ({ value }: any) => {
return intl.formatMessage({ id: "seconds" }, { seconds: value });
};
return formatCell;
}
export {
ActionsFormatter,
BooleanFormatter,
CapabilitiesFormatter,
CertificateStatusFormatter,
DisabledFormatter,
DNSProviderFormatter,
DomainsFormatter,
GravatarFormatter,
HostStatusFormatter,
HostTypeFormatter,
IDFormatter,
SecondsFormatter,
};

View File

@@ -0,0 +1,70 @@
import { ReactNode } from "react";
import {
Menu,
MenuButton,
MenuList,
MenuItem,
IconButton,
} from "@chakra-ui/react";
import { FiMoreVertical } from "react-icons/fi";
// A row action is a single menu item for the actions column
export interface RowAction {
title: string;
onClick: (e: any, data: any) => any;
show?: (data: any) => any;
disabled?: (data: any) => any;
icon?: any;
}
interface RowActionsProps {
/**
* Row Data
*/
data: any;
/**
* Actions
*/
actions: RowAction[];
}
function RowActionsMenu({ data, actions }: RowActionsProps) {
const elms: ReactNode[] = [];
actions.map((action) => {
if (!action.show || action.show(data)) {
const disabled = action.disabled && action.disabled(data);
elms.push(
<MenuItem
key={`action-${action.title}`}
icon={action.icon}
isDisabled={disabled}
onClick={(e: any) => {
action.onClick(e, data);
}}>
{action.title}
</MenuItem>,
);
}
return null;
});
if (!elms.length) {
return null;
}
return (
<div style={{ textAlign: "right" }}>
<Menu>
<MenuButton
as={IconButton}
aria-label="Actions"
icon={<FiMoreVertical />}
variant="outline"
/>
<MenuList>{elms}</MenuList>
</Menu>
</div>
);
}
export { RowActionsMenu };

View File

@@ -0,0 +1,57 @@
export interface TablePagination {
limit: number;
offset: number;
total: number;
}
export interface TableSortBy {
id: string;
desc: boolean;
}
export interface TableFilter {
id: string;
value: any;
}
const tableEvents = {
FILTERS_CHANGED: "FILTERS_CHANGED",
PAGE_CHANGED: "PAGE_CHANGED",
PAGE_SIZE_CHANGED: "PAGE_SIZE_CHANGED",
TOTAL_COUNT_CHANGED: "TOTAL_COUNT_CHANGED",
SORT_CHANGED: "SORT_CHANGED",
};
const tableEventReducer = (state: any, { type, payload }: any) => {
switch (type) {
case tableEvents.PAGE_CHANGED:
return {
...state,
offset: payload * state.limit,
};
case tableEvents.PAGE_SIZE_CHANGED:
return {
...state,
limit: payload,
};
case tableEvents.TOTAL_COUNT_CHANGED:
return {
...state,
total: payload,
};
case tableEvents.SORT_CHANGED:
return {
...state,
sortBy: payload,
};
case tableEvents.FILTERS_CHANGED:
return {
...state,
filters: payload,
};
default:
throw new Error(`Unhandled action type: ${type}`);
}
};
export { tableEvents, tableEventReducer };

View File

@@ -0,0 +1,246 @@
import { ReactNode } from "react";
import {
ButtonGroup,
Center,
Flex,
HStack,
IconButton,
Link,
Select,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
VStack,
} from "@chakra-ui/react";
import { TablePagination } from "components";
import { intl } from "locale";
import {
FiChevronsLeft,
FiChevronLeft,
FiChevronsRight,
FiChevronRight,
FiChevronDown,
FiChevronUp,
FiX,
} from "react-icons/fi";
export interface TableLayoutProps {
pagination: TablePagination;
getTableProps: any;
getTableBodyProps: any;
headerGroups: any;
rows: any;
prepareRow: any;
gotoPage: any;
canPreviousPage: any;
previousPage: any;
canNextPage: any;
setPageSize: any;
nextPage: any;
pageCount: any;
pageOptions: any;
visibleColumns: any;
setAllFilters: any;
state: any;
}
function TableLayout({
pagination,
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
gotoPage,
canPreviousPage,
previousPage,
canNextPage,
setPageSize,
nextPage,
pageCount,
pageOptions,
visibleColumns,
setAllFilters,
state,
}: TableLayoutProps) {
const currentPage = state.pageIndex + 1;
const getPageList = () => {
const list = [];
for (let x = 0; x < pageOptions.length; x++) {
list.push(
<option
key={`table-pagination-page-${x}`}
value={x + 1}
selected={currentPage === x + 1}>
{x + 1}
</option>,
);
}
return list;
};
const renderEmpty = (): ReactNode => {
return (
<Tr>
<Td colSpan={visibleColumns.length}>
<Center>
{state?.filters?.length
? intl.formatMessage(
{ id: "tables.no-items-with-filters" },
{ count: state.filters.length },
)
: intl.formatMessage({ id: "tables.no-items" })}
</Center>
</Td>
</Tr>
);
};
return (
<>
<Table {...getTableProps()}>
<Thead>
{headerGroups.map((headerGroup: any) => (
<Tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column: any) => (
<Th
userSelect="none"
className={column.className}
isNumeric={column.isNumeric}>
<Flex alignItems="center">
<HStack mx={6} justifyContent="space-between">
<Text
{...column.getHeaderProps(
column.sortable && column.getSortByToggleProps(),
)}>
{column.render("Header")}
</Text>
{column.sortable && column.isSorted ? (
column.isSortedDesc ? (
<FiChevronDown />
) : (
<FiChevronUp />
)
) : null}
{column.Filter ? column.render("Filter") : null}
</HStack>
</Flex>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody {...getTableBodyProps()}>
{rows.length
? rows.map((row: any) => {
prepareRow(row);
return (
<Tr {...row.getRowProps()}>
{row.cells.map((cell: any) => (
<Td
{...cell.getCellProps([
{
className: cell.column.className,
},
])}>
{cell.render("Cell")}
</Td>
))}
</Tr>
);
})
: renderEmpty()}
</Tbody>
</Table>
<HStack mx={6} my={4} justifyContent="space-between">
<VStack align="left">
<Text color="gray.500">
{rows.length
? intl.formatMessage(
{ id: "tables.pagination-counts" },
{
start: pagination.offset + 1,
end: Math.min(
pagination.total,
pagination.offset + pagination.limit,
),
total: pagination.total,
},
)
: null}
</Text>
{state?.filters?.length ? (
<Link onClick={() => setAllFilters([])}>
<HStack>
<FiX display="inline-block" />
<Text>
{intl.formatMessage(
{ id: "tables.clear-all-filters" },
{ count: state.filters.length },
)}
</Text>
</HStack>
</Link>
) : null}
</VStack>
<nav>
<ButtonGroup size="sm" isAttached>
<IconButton
aria-label={intl.formatMessage({
id: "tables.pagination-previous",
})}
size="sm"
icon={<FiChevronsLeft />}
isDisabled={!canPreviousPage}
onClick={() => gotoPage(0)}
/>
<IconButton
aria-label={intl.formatMessage({
id: "tables.pagination-previous",
})}
size="sm"
icon={<FiChevronLeft />}
isDisabled={!canPreviousPage}
onClick={() => previousPage()}
/>
<Select
size="sm"
variant="filled"
borderRadius={0}
defaultValue={currentPage}
disabled={!canPreviousPage && !canNextPage}
aria-label={intl.formatMessage({
id: "tables.pagination-select",
})}>
{getPageList()}
</Select>
<IconButton
aria-label={intl.formatMessage({
id: "tables.pagination-next",
})}
size="sm"
icon={<FiChevronRight />}
isDisabled={!canNextPage}
onClick={() => nextPage()}
/>
<IconButton
aria-label={intl.formatMessage({
id: "tables.pagination-next",
})}
size="sm"
icon={<FiChevronsRight />}
isDisabled={!canNextPage}
onClick={() => gotoPage(pageCount - 1)}
/>
</ButtonGroup>
</nav>
</HStack>
</>
);
}
export { TableLayout };

View File

@@ -0,0 +1,142 @@
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverArrow,
IconButton,
FormControl,
FormErrorMessage,
Input,
Stack,
ButtonGroup,
Button,
useDisclosure,
Select,
} from "@chakra-ui/react";
import { PrettyButton } from "components";
import { Formik, Form, Field } from "formik";
import { intl } from "locale";
import { validateString } from "modules/Validations";
import FocusLock from "react-focus-lock";
import { FiFilter } from "react-icons/fi";
function TextFilter({ column: { filterValue, setFilter } }: any) {
const { onOpen, onClose, isOpen } = useDisclosure();
const onSubmit = (values: any, { setSubmitting }: any) => {
setFilter(values);
setSubmitting(false);
onClose();
};
const clearFilter = () => {
setFilter(undefined);
onClose();
};
const isFiltered = (): boolean => {
return !(typeof filterValue === "undefined" || filterValue === "");
};
return (
<Popover
isOpen={isOpen}
onOpen={onOpen}
onClose={onClose}
placement="right">
<PopoverTrigger>
<IconButton
variant="unstyled"
size="sm"
color={isFiltered() ? "orange.400" : ""}
icon={<FiFilter />}
aria-label="Filter"
/>
</PopoverTrigger>
<PopoverContent p={5}>
<FocusLock returnFocus persistentFocus={false}>
<PopoverArrow />
<Formik
initialValues={
{
modifier: filterValue?.modifier || "contains",
value: filterValue?.value,
} as any
}
onSubmit={onSubmit}>
{({ isSubmitting }) => (
<Form>
<Stack spacing={4}>
<Field name="modifier">
{({ field, form }: any) => (
<FormControl
isRequired
isInvalid={
form.errors.modifier && form.touched.modifier
}>
<Select
{...field}
size="sm"
id="modifier"
defaultValue="contains">
<option value="contains">
{intl.formatMessage({ id: "filter.contains" })}
</option>
<option value="equals">
{intl.formatMessage({ id: "filter.exactly" })}
</option>
<option value="starts">
{intl.formatMessage({ id: "filter.starts" })}
</option>
<option value="ends">
{intl.formatMessage({ id: "filter.ends" })}
</option>
</Select>
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="value" validate={validateString(1, 50)}>
{({ field, form }: any) => (
<FormControl
isRequired
isInvalid={form.errors.value && form.touched.value}>
<Input
{...field}
size="sm"
placeholder={intl.formatMessage({
id: "filter.placeholder",
})}
autoComplete="off"
/>
<FormErrorMessage>{form.errors.value}</FormErrorMessage>
</FormControl>
)}
</Field>
<ButtonGroup d="flex" justifyContent="flex-end">
<Button
size="sm"
variant="outline"
onClick={clearFilter}
isLoading={isSubmitting}>
{intl.formatMessage({
id: "filter.clear",
})}
</Button>
<PrettyButton size="sm" isLoading={isSubmitting}>
{intl.formatMessage({
id: "filter.apply",
})}
</PrettyButton>
</ButtonGroup>
</Stack>
</Form>
)}
</Formik>
</FocusLock>
</PopoverContent>
</Popover>
);
}
export { TextFilter };

View File

@@ -0,0 +1,5 @@
export * from "./Formatters";
export * from "./RowActionsMenu";
export * from "./TableHelpers";
export * from "./TableLayout";
export * from "./TextFilter";

View File

@@ -0,0 +1,130 @@
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-table#configuration-using-declaration-merging
import {
UseColumnOrderInstanceProps,
UseColumnOrderState,
UseExpandedHooks,
UseExpandedInstanceProps,
UseExpandedOptions,
UseExpandedRowProps,
UseExpandedState,
UseFiltersColumnOptions,
UseFiltersColumnProps,
UseFiltersInstanceProps,
UseFiltersOptions,
UseFiltersState,
UseGlobalFiltersColumnOptions,
UseGlobalFiltersInstanceProps,
UseGlobalFiltersOptions,
UseGlobalFiltersState,
UseGroupByCellProps,
UseGroupByColumnOptions,
UseGroupByColumnProps,
UseGroupByHooks,
UseGroupByInstanceProps,
UseGroupByOptions,
UseGroupByRowProps,
UseGroupByState,
UsePaginationInstanceProps,
UsePaginationOptions,
UsePaginationState,
UseResizeColumnsColumnOptions,
UseResizeColumnsColumnProps,
UseResizeColumnsOptions,
UseResizeColumnsState,
UseRowSelectHooks,
UseRowSelectInstanceProps,
UseRowSelectOptions,
UseRowSelectRowProps,
UseRowSelectState,
UseRowStateCellProps,
UseRowStateInstanceProps,
UseRowStateOptions,
UseRowStateRowProps,
UseRowStateState,
UseSortByColumnOptions,
UseSortByColumnProps,
UseSortByHooks,
UseSortByInstanceProps,
UseSortByOptions,
UseSortByState,
} from "react-table";
declare module "react-table" {
// take this file as-is, or comment out the sections that don't apply to your plugin configuration
export interface TableOptions<
D extends Record<string, unknown>,
> extends UseExpandedOptions<D>,
UseFiltersOptions<D>,
UseGlobalFiltersOptions<D>,
UseGroupByOptions<D>,
UsePaginationOptions<D>,
UseResizeColumnsOptions<D>,
UseRowSelectOptions<D>,
UseRowStateOptions<D>,
UseSortByOptions<D>,
// note that having Record here allows you to add anything to the options, this matches the spirit of the
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
// feature set, this is a safe default.
Record<string, any> {}
export interface Hooks<
D extends Record<string, unknown> = Record<string, unknown>,
> extends UseExpandedHooks<D>,
UseGroupByHooks<D>,
UseRowSelectHooks<D>,
UseSortByHooks<D> {}
export interface TableInstance<
D extends Record<string, unknown> = Record<string, unknown>,
> extends UseColumnOrderInstanceProps<D>,
UseExpandedInstanceProps<D>,
UseFiltersInstanceProps<D>,
UseGlobalFiltersInstanceProps<D>,
UseGroupByInstanceProps<D>,
UsePaginationInstanceProps<D>,
UseRowSelectInstanceProps<D>,
UseRowStateInstanceProps<D>,
UseSortByInstanceProps<D> {}
export interface TableState<
D extends Record<string, unknown> = Record<string, unknown>,
> extends UseColumnOrderState<D>,
UseExpandedState<D>,
UseFiltersState<D>,
UseGlobalFiltersState<D>,
UseGroupByState<D>,
UsePaginationState<D>,
UseResizeColumnsState<D>,
UseRowSelectState<D>,
UseRowStateState<D>,
UseSortByState<D> {}
export interface ColumnInterface<
D extends Record<string, unknown> = Record<string, unknown>,
> extends UseFiltersColumnOptions<D>,
UseGlobalFiltersColumnOptions<D>,
UseGroupByColumnOptions<D>,
UseResizeColumnsColumnOptions<D>,
UseSortByColumnOptions<D> {}
export interface ColumnInstance<
D extends Record<string, unknown> = Record<string, unknown>,
> extends UseFiltersColumnProps<D>,
UseGroupByColumnProps<D>,
UseResizeColumnsColumnProps<D>,
UseSortByColumnProps<D> {}
export interface Cell<
D extends Record<string, unknown> = Record<string, unknown>,
// V = any,
> extends UseGroupByCellProps<D>,
UseRowStateCellProps<D> {}
export interface Row<
D extends Record<string, unknown> = Record<string, unknown>,
> extends UseExpandedRowProps<D>,
UseGroupByRowProps<D>,
UseRowSelectRowProps<D>,
UseRowStateRowProps<D> {}
}