mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2026-09-24 13:50:20 +01:00
Merge branch 'develop' into feat/collapsible-custom-locations
This commit is contained in:
+73
-90
@@ -1,92 +1,75 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"!**/dist/**/*"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 120,
|
||||
"formatWithErrors": true
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": {
|
||||
"level": "on",
|
||||
"options": {
|
||||
"groups": [
|
||||
":BUN:",
|
||||
":NODE:",
|
||||
[
|
||||
"npm:*",
|
||||
"npm:*/**"
|
||||
],
|
||||
":PACKAGE_WITH_PROTOCOL:",
|
||||
":URL:",
|
||||
":PACKAGE:",
|
||||
[
|
||||
"/src/*",
|
||||
"/src/**"
|
||||
],
|
||||
[
|
||||
"/**"
|
||||
],
|
||||
[
|
||||
"#*",
|
||||
"#*/**"
|
||||
],
|
||||
":PATH:"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"correctness": {
|
||||
"useUniqueElementIds": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off",
|
||||
"noArrayIndexKey": "off"
|
||||
},
|
||||
"performance": {
|
||||
"noDelete": "off"
|
||||
},
|
||||
"nursery": "off",
|
||||
"a11y": {
|
||||
"useSemanticElements": "off",
|
||||
"useValidAnchor": "off"
|
||||
},
|
||||
"style": {
|
||||
"noParameterAssign": "error",
|
||||
"useAsConstAssertion": "error",
|
||||
"useDefaultParameterLast": "error",
|
||||
"useEnumInitializers": "error",
|
||||
"useSelfClosingElements": "error",
|
||||
"useSingleVarDeclarator": "error",
|
||||
"noUnusedTemplateLiteral": "error",
|
||||
"useNumberNamespace": "error",
|
||||
"noInferrableTypes": "error",
|
||||
"noUselessElse": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
"$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "!**/dist/**/*"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 120,
|
||||
"formatWithErrors": true
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": {
|
||||
"level": "on",
|
||||
"options": {
|
||||
"groups": [
|
||||
":BUN:",
|
||||
":NODE:",
|
||||
["npm:*", "npm:*/**"],
|
||||
":PACKAGE_WITH_PROTOCOL:",
|
||||
":URL:",
|
||||
":PACKAGE:",
|
||||
["/src/*", "/src/**"],
|
||||
["/**"],
|
||||
["#*", "#*/**"],
|
||||
":PATH:"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"preset": "recommended",
|
||||
"correctness": {
|
||||
"useUniqueElementIds": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off",
|
||||
"noArrayIndexKey": "off"
|
||||
},
|
||||
"performance": {
|
||||
"noDelete": "off"
|
||||
},
|
||||
"nursery": "off",
|
||||
"a11y": {
|
||||
"useSemanticElements": "off",
|
||||
"useValidAnchor": "off"
|
||||
},
|
||||
"style": {
|
||||
"noParameterAssign": "error",
|
||||
"useAsConstAssertion": "error",
|
||||
"useDefaultParameterLast": "error",
|
||||
"useEnumInitializers": "error",
|
||||
"useSelfClosingElements": "error",
|
||||
"useSingleVarDeclarator": "error",
|
||||
"noUnusedTemplateLiteral": "error",
|
||||
"useNumberNamespace": "error",
|
||||
"noInferrableTypes": "error",
|
||||
"noUselessElse": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-25
@@ -17,50 +17,51 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/core": "^1.4.0",
|
||||
"@tabler/icons-react": "^3.44.0",
|
||||
"@tanstack/react-query": "^5.101.0",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tabler/icons-react": "^3.46.0",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"@tanstack/react-table": "^9.1.2",
|
||||
"@uiw/react-textarea-code-editor": "^3.1.1",
|
||||
"classnames": "^2.5.1",
|
||||
"country-flag-icons": "^1.6.17",
|
||||
"country-flag-icons": "^1.6.20",
|
||||
"date-fns": "^4.4.0",
|
||||
"ez-modal-react": "^1.0.5",
|
||||
"formik": "^2.4.9",
|
||||
"generate-password-browser": "^1.1.0",
|
||||
"humps": "^2.0.1",
|
||||
"query-string": "^9.4.0",
|
||||
"react": "^19.2.7",
|
||||
"query-string": "^9.5.0",
|
||||
"react": "^19.2.8",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.2.7",
|
||||
"react-intl": "^10.1.11",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-intl": "^10.1.22",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.16.0",
|
||||
"react-qr-code": "^2.2.0",
|
||||
"react-router-dom": "^7.18.2",
|
||||
"react-select": "^5.10.2",
|
||||
"react-toastify": "^11.1.0",
|
||||
"rooks": "^9.8.0"
|
||||
"rooks": "^9.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"@formatjs/cli": "^6.16.6",
|
||||
"@tanstack/react-query-devtools": "^5.100.14",
|
||||
"@biomejs/biome": "^2.5.10",
|
||||
"@formatjs/cli": "^6.16.19",
|
||||
"@tanstack/react-query-devtools": "^5.101.4",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/country-flag-icons": "^1.2.2",
|
||||
"@types/humps": "^2.0.6",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/react": "^19.2.16",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"happy-dom": "^20.9.0",
|
||||
"postcss": "^8.5.15",
|
||||
"@vitejs/plugin-react": "^6.1.0",
|
||||
"happy-dom": "^20.11.6",
|
||||
"postcss": "^8.5.26",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"sass": "^1.100.0",
|
||||
"sass": "^1.103.1",
|
||||
"tmp": "^0.2.7",
|
||||
"typescript": "6.0.3",
|
||||
"vite": "^8.0.16",
|
||||
"vite-plugin-checker": "^0.14.1",
|
||||
"vitest": "^4.1.8"
|
||||
"typescript": "7.0.2",
|
||||
"vite": "^8.2.2",
|
||||
"vite-plugin-checker": "^0.14.5",
|
||||
"vitest": "^4.1.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import type { Table as ReactTable, RowData } from "@tanstack/react-table";
|
||||
import cn from "classnames";
|
||||
import type { ReactNode } from "react";
|
||||
import { Button, HasPermission } from "src/components";
|
||||
import type { Features } from "src/components/Table/features";
|
||||
import { T } from "src/locale";
|
||||
import { type ADMIN, MANAGE, type Permission, type Section } from "src/modules/Permissions";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
interface Props<TData extends RowData> {
|
||||
tableInstance: ReactTable<Features, TData>;
|
||||
onNew?: () => void;
|
||||
isFiltered?: boolean;
|
||||
object: string;
|
||||
@@ -16,7 +17,7 @@ interface Props {
|
||||
permissionSection?: Section | typeof ADMIN;
|
||||
permission?: Permission;
|
||||
}
|
||||
function EmptyData({
|
||||
function EmptyData<TData extends RowData>({
|
||||
tableInstance,
|
||||
onNew,
|
||||
isFiltered,
|
||||
@@ -26,7 +27,7 @@ function EmptyData({
|
||||
customAddBtn,
|
||||
permissionSection,
|
||||
permission,
|
||||
}: Props) {
|
||||
}: Props<TData>) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
|
||||
@@ -65,8 +65,12 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
|
||||
value={client.directive}
|
||||
onChange={(e) => handleChange(idx, "directive", e.target.value)}
|
||||
>
|
||||
<option value="allow"><T id="action.allow" /></option>
|
||||
<option value="deny"><T id="action.deny" /></option>
|
||||
<option value="allow">
|
||||
<T id="action.allow" />
|
||||
</option>
|
||||
<option value="deny">
|
||||
<T id="action.deny" />
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
<input
|
||||
@@ -81,16 +85,13 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-1">
|
||||
<a
|
||||
role="button"
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-danger p-0"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleRemove(idx);
|
||||
}}
|
||||
onClick={() => handleRemove(idx)}
|
||||
>
|
||||
<IconX size={16} />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -112,7 +113,9 @@ export function AccessClientFields({ initialValues, name = "clients" }: Props) {
|
||||
value="deny"
|
||||
disabled
|
||||
>
|
||||
<option value="deny"><T id="action.deny" /></option>
|
||||
<option value="deny">
|
||||
<T id="action.deny" />
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
<input
|
||||
|
||||
@@ -82,16 +82,13 @@ export function BasicAuthFields({ initialValues, name = "items" }: Props) {
|
||||
/>
|
||||
</div>
|
||||
<div className="col-1">
|
||||
<a
|
||||
role="button"
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-danger p-0"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleRemove(idx);
|
||||
}}
|
||||
onClick={() => handleRemove(idx)}
|
||||
>
|
||||
<IconX size={16} />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -10,7 +10,13 @@ interface Props {
|
||||
requireDomainNames?: boolean; // used for streams
|
||||
color?: string;
|
||||
}
|
||||
export function SSLOptionsFields({ forHttp = true, forProxyHost = false, forceDNSForNew, requireDomainNames, color = "bg-cyan" }: Props) {
|
||||
export function SSLOptionsFields({
|
||||
forHttp = true,
|
||||
forProxyHost = false,
|
||||
forceDNSForNew,
|
||||
requireDomainNames,
|
||||
color = "bg-cyan",
|
||||
}: Props) {
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
const v: any = values || {};
|
||||
|
||||
@@ -116,10 +122,12 @@ export function SSLOptionsFields({ forHttp = true, forProxyHost = false, forceDN
|
||||
</div>
|
||||
);
|
||||
|
||||
const getHttpAdvancedOptions = () =>(
|
||||
const getHttpAdvancedOptions = () => (
|
||||
<div>
|
||||
<details>
|
||||
<summary className="mb-1"><T id="domains.advanced" /></summary>
|
||||
<summary className="mb-1">
|
||||
<T id="domains.advanced" />
|
||||
</summary>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<Field name="trustForwardedProto">
|
||||
|
||||
@@ -72,7 +72,10 @@ export function SiteHeader() {
|
||||
<div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
||||
<div className="d-md-none">
|
||||
{/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: This div is not interactive. */}
|
||||
<div className="p-2 pb-1 pe-1 d-flex align-items-center" onClick={e => e.stopPropagation()}>
|
||||
<div
|
||||
className="p-2 pb-1 pe-1 d-flex align-items-center"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="ps-2 pe-1 me-auto">
|
||||
<div>{currentUser?.nickname}</div>
|
||||
<div className="mt-1 small text-secondary text-nowrap">
|
||||
|
||||
@@ -143,7 +143,6 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
|
||||
className="nav-link dropdown-toggle"
|
||||
href={item.to}
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-auto-close="outside"
|
||||
aria-expanded="false"
|
||||
role="button"
|
||||
>
|
||||
@@ -176,13 +175,14 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
|
||||
};
|
||||
|
||||
export function SiteMenu() {
|
||||
const closeMenu = () => setTimeout(() => {
|
||||
const navbarToggler = document.querySelector<HTMLElement>(".navbar-toggler");
|
||||
const navbarMenu = document.querySelector("#navbar-menu");
|
||||
if (navbarToggler && navbarMenu?.classList.contains("show")) {
|
||||
navbarToggler.click();
|
||||
}
|
||||
}, 300);
|
||||
const closeMenu = () =>
|
||||
setTimeout(() => {
|
||||
const navbarToggler = document.querySelector<HTMLElement>(".navbar-toggler");
|
||||
const navbarMenu = document.querySelector("#navbar-menu");
|
||||
if (navbarToggler && navbarMenu?.classList.contains("show")) {
|
||||
navbarToggler.click();
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return (
|
||||
<header className="navbar-expand-md">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import type { Table as ReactTable, RowData } from "@tanstack/react-table";
|
||||
import type { Features } from "./features";
|
||||
|
||||
interface Props {
|
||||
tableInstance: ReactTable<any>;
|
||||
interface Props<TData extends RowData> {
|
||||
tableInstance: ReactTable<Features, TData>;
|
||||
}
|
||||
function EmptyRow({ tableInstance }: Props) {
|
||||
function EmptyRow<TData extends RowData>({ tableInstance }: Props<TData>) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={tableInstance.getVisibleFlatColumns().length}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { flexRender } from "@tanstack/react-table";
|
||||
import { flexRender, type RowData } from "@tanstack/react-table";
|
||||
import type { TableLayoutProps } from "src/components";
|
||||
import { EmptyRow } from "./EmptyRow";
|
||||
|
||||
function TableBody<T>(props: TableLayoutProps<T>) {
|
||||
function TableBody<T extends RowData>(props: TableLayoutProps<T>) {
|
||||
const { tableInstance, extraStyles, emptyState } = props;
|
||||
const rows = tableInstance.getRowModel().rows;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IconArrowsSort, IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
import { flexRender } from "@tanstack/react-table";
|
||||
import { flexRender, type RowData } from "@tanstack/react-table";
|
||||
import type { TableLayoutProps } from "src/components";
|
||||
|
||||
function TableHeader<T>(props: TableLayoutProps<T>) {
|
||||
function TableHeader<T extends RowData>(props: TableLayoutProps<T>) {
|
||||
const { tableInstance } = props;
|
||||
const headerGroups = tableInstance.getHeaderGroups();
|
||||
|
||||
|
||||
@@ -61,4 +61,4 @@ const tableEventReducer = (state: any, { type, payload }: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { tableEvents, tableEventReducer };
|
||||
export { tableEventReducer, tableEvents };
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import type { Table as ReactTable } from "@tanstack/react-table";
|
||||
import type { Table as ReactTable, RowData } from "@tanstack/react-table";
|
||||
import type { Features } from "./features";
|
||||
import { TableBody } from "./TableBody";
|
||||
import { TableHeader } from "./TableHeader";
|
||||
|
||||
interface TableLayoutProps<TFields> {
|
||||
tableInstance: ReactTable<TFields>;
|
||||
interface TableLayoutProps<TFields extends RowData> {
|
||||
tableInstance: ReactTable<Features, TFields>;
|
||||
emptyState?: React.ReactNode;
|
||||
extraStyles?: {
|
||||
row: (rowData: TFields) => any | undefined;
|
||||
};
|
||||
}
|
||||
function TableLayout<TFields>(props: TableLayoutProps<TFields>) {
|
||||
function TableLayout<TFields extends RowData>(props: TableLayoutProps<TFields>) {
|
||||
const hasRows = props.tableInstance.getRowModel().rows.length > 0;
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
columnVisibilityFeature,
|
||||
createSortedRowModel,
|
||||
metaHelper,
|
||||
rowSortingFeature,
|
||||
tableFeatures,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
interface ColumnMeta {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface TableMeta {
|
||||
isFetching?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared TanStack Table v9 feature registration for every table in the app.
|
||||
* Sorting and column visibility are used (or their APIs are called
|
||||
* unconditionally, e.g. `getVisibleFlatColumns`/`getVisibleCells`) by the
|
||||
* shared TableLayout/TableHeader/TableBody/EmptyData components, so every
|
||||
* table instance must register them even when a particular table doesn't
|
||||
* wire up controlled sorting state itself.
|
||||
*/
|
||||
const features = tableFeatures({
|
||||
rowSortingFeature,
|
||||
sortedRowModel: createSortedRowModel(),
|
||||
columnVisibilityFeature,
|
||||
columnMeta: metaHelper<ColumnMeta>(),
|
||||
tableMeta: metaHelper<TableMeta>(),
|
||||
});
|
||||
|
||||
type Features = typeof features;
|
||||
|
||||
export { type ColumnMeta, type Features, features, type TableMeta };
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./Formatter";
|
||||
export * from "./features";
|
||||
export * from "./TableHeader";
|
||||
export * from "./TableHelpers";
|
||||
export * from "./TableLayout";
|
||||
|
||||
@@ -27,73 +27,73 @@ import langList from "./lang/lang-list.json";
|
||||
// not the country code
|
||||
// Remember when adding to this list, also update check-locales.js script
|
||||
const localeOptions = [
|
||||
["en", "en-US", langEn],
|
||||
["de", "de-DE", langDe],
|
||||
["es", "es-ES", langEs],
|
||||
["et", "et-EE", langEt],
|
||||
["pt", "pt-PT", langPt],
|
||||
["fr", "fr-FR", langFr],
|
||||
["ga", "ga-IE", langGa],
|
||||
["ja", "ja-JP", langJa],
|
||||
["it", "it-IT", langIt],
|
||||
["nl", "nl-NL", langNl],
|
||||
["pl", "pl-PL", langPl],
|
||||
["ru", "ru-RU", langRu],
|
||||
["sk", "sk-SK", langSk],
|
||||
["cs", "cs-CZ", langCs],
|
||||
["vi", "vi-VN", langVi],
|
||||
["zh", "zh-CN", langZh],
|
||||
["ko", "ko-KR", langKo],
|
||||
["bg", "bg-BG", langBg],
|
||||
["id", "id-ID", langId],
|
||||
["tr", "tr-TR", langTr],
|
||||
["hu", "hu-HU", langHu],
|
||||
["no", "no-NO", langNo],
|
||||
["en", "en-US", langEn],
|
||||
["de", "de-DE", langDe],
|
||||
["es", "es-ES", langEs],
|
||||
["et", "et-EE", langEt],
|
||||
["pt", "pt-PT", langPt],
|
||||
["fr", "fr-FR", langFr],
|
||||
["ga", "ga-IE", langGa],
|
||||
["ja", "ja-JP", langJa],
|
||||
["it", "it-IT", langIt],
|
||||
["nl", "nl-NL", langNl],
|
||||
["pl", "pl-PL", langPl],
|
||||
["ru", "ru-RU", langRu],
|
||||
["sk", "sk-SK", langSk],
|
||||
["cs", "cs-CZ", langCs],
|
||||
["vi", "vi-VN", langVi],
|
||||
["zh", "zh-CN", langZh],
|
||||
["ko", "ko-KR", langKo],
|
||||
["bg", "bg-BG", langBg],
|
||||
["id", "id-ID", langId],
|
||||
["tr", "tr-TR", langTr],
|
||||
["hu", "hu-HU", langHu],
|
||||
["no", "no-NO", langNo],
|
||||
];
|
||||
|
||||
const loadMessages = (locale?: string): typeof langList & typeof langEn => {
|
||||
const thisLocale = (locale || "en").slice(0, 2);
|
||||
const thisLocale = (locale || "en").slice(0, 2);
|
||||
|
||||
// ensure this lang exists in localeOptions above, otherwise fallback to en
|
||||
if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) {
|
||||
return Object.assign({}, langList, langEn);
|
||||
}
|
||||
// ensure this lang exists in localeOptions above, otherwise fallback to en
|
||||
if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) {
|
||||
return Object.assign({}, langList, langEn);
|
||||
}
|
||||
|
||||
return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]);
|
||||
return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]);
|
||||
};
|
||||
|
||||
const getFlagCodeForLocale = (locale?: string) => {
|
||||
const thisLocale = (locale || "en").slice(0, 2);
|
||||
const thisLocale = (locale || "en").slice(0, 2);
|
||||
|
||||
// only add to this if your flag is different from the locale code
|
||||
const specialCases: Record<string, string> = {
|
||||
ja: "jp", // Japan
|
||||
zh: "cn", // China
|
||||
vi: "vn", // Vietnam
|
||||
ko: "kr", // Korea
|
||||
cs: "cz", // Czechia
|
||||
ga: "ie", // Ireland (Irish)
|
||||
};
|
||||
// only add to this if your flag is different from the locale code
|
||||
const specialCases: Record<string, string> = {
|
||||
ja: "jp", // Japan
|
||||
zh: "cn", // China
|
||||
vi: "vn", // Vietnam
|
||||
ko: "kr", // Korea
|
||||
cs: "cz", // Czechia
|
||||
ga: "ie", // Ireland (Irish)
|
||||
};
|
||||
|
||||
if (specialCases[thisLocale]) {
|
||||
return specialCases[thisLocale].toUpperCase();
|
||||
}
|
||||
return thisLocale.toUpperCase();
|
||||
if (specialCases[thisLocale]) {
|
||||
return specialCases[thisLocale].toUpperCase();
|
||||
}
|
||||
return thisLocale.toUpperCase();
|
||||
};
|
||||
|
||||
const getLocale = (short = false) => {
|
||||
let loc = window.localStorage.getItem("locale");
|
||||
if (!loc) {
|
||||
loc = document.documentElement.lang;
|
||||
}
|
||||
if (short) {
|
||||
return loc.slice(0, 2);
|
||||
}
|
||||
// finally, fallback
|
||||
if (!loc) {
|
||||
loc = "en";
|
||||
}
|
||||
return loc;
|
||||
let loc = window.localStorage.getItem("locale");
|
||||
if (!loc) {
|
||||
loc = document.documentElement.lang;
|
||||
}
|
||||
if (short) {
|
||||
return loc.slice(0, 2);
|
||||
}
|
||||
// finally, fallback
|
||||
if (!loc) {
|
||||
loc = "en";
|
||||
}
|
||||
return loc;
|
||||
};
|
||||
|
||||
const cache = createIntlCache();
|
||||
@@ -102,41 +102,41 @@ const initialMessages = loadMessages(getLocale());
|
||||
let intl = createIntl({ locale: getLocale(), messages: initialMessages }, cache);
|
||||
|
||||
const changeLocale = (locale: string): void => {
|
||||
const messages = loadMessages(locale);
|
||||
intl = createIntl({ locale, messages }, cache);
|
||||
window.localStorage.setItem("locale", locale);
|
||||
document.documentElement.lang = locale;
|
||||
const messages = loadMessages(locale);
|
||||
intl = createIntl({ locale, messages }, cache);
|
||||
window.localStorage.setItem("locale", locale);
|
||||
document.documentElement.lang = locale;
|
||||
};
|
||||
|
||||
// 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,
|
||||
tData,
|
||||
id,
|
||||
data,
|
||||
tData,
|
||||
}: {
|
||||
id: string;
|
||||
data?: Record<string, string | number | undefined>;
|
||||
tData?: Record<string, string>;
|
||||
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>
|
||||
);
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
//console.log("L:", localeOptions);
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
fromUnixTime,
|
||||
type IntlFormatFormatOptions,
|
||||
intlFormat,
|
||||
parseISO,
|
||||
} from "date-fns";
|
||||
import { fromUnixTime, type IntlFormatFormatOptions, intlFormat, parseISO } from "date-fns";
|
||||
|
||||
const isUnixTimestamp = (value: unknown): boolean => {
|
||||
if (typeof value !== "number" && typeof value !== "string") return false;
|
||||
|
||||
@@ -3,4 +3,4 @@ export * as Certificates from "./Certificates.md";
|
||||
export * as DeadHosts from "./DeadHosts.md";
|
||||
export * as ProxyHosts from "./ProxyHosts.md";
|
||||
export * as RedirectionHosts from "./RedirectionHosts.md";
|
||||
export * as Streams from "./Streams.md";
|
||||
export * as Streams from "./Streams.md";
|
||||
|
||||
@@ -22,18 +22,17 @@ import * as hu from "./hu/index";
|
||||
|
||||
const items: any = { en, de, pt, es, et, ja, sk, cs, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu };
|
||||
|
||||
|
||||
const fallbackLang = "en";
|
||||
|
||||
export const getHelpFile = (lang: string, section: string): string => {
|
||||
if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") {
|
||||
return items[lang][section].default;
|
||||
}
|
||||
// Fallback to English
|
||||
if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") {
|
||||
return items[fallbackLang][section].default;
|
||||
}
|
||||
throw new Error(`Cannot load help doc for ${lang}-${section}`);
|
||||
if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") {
|
||||
return items[lang][section].default;
|
||||
}
|
||||
// Fallback to English
|
||||
if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") {
|
||||
return items[fallbackLang][section].default;
|
||||
}
|
||||
throw new Error(`Cannot load help doc for ${lang}-${section}`);
|
||||
};
|
||||
|
||||
export default items;
|
||||
|
||||
@@ -4,4 +4,3 @@ export * as DeadHosts from "./DeadHosts.md";
|
||||
export * as ProxyHosts from "./ProxyHosts.md";
|
||||
export * as RedirectionHosts from "./RedirectionHosts.md";
|
||||
export * as Streams from "./Streams.md";
|
||||
|
||||
|
||||
@@ -4,4 +4,3 @@ export * as DeadHosts from "./DeadHosts.md";
|
||||
export * as ProxyHosts from "./ProxyHosts.md";
|
||||
export * as RedirectionHosts from "./RedirectionHosts.md";
|
||||
export * as Streams from "./Streams.md";
|
||||
|
||||
|
||||
@@ -1,4 +1,61 @@
|
||||
{
|
||||
"2fa.backup-codes-remaining": {
|
||||
"defaultMessage": "Verbleibende Backup-Codes: {count}"
|
||||
},
|
||||
"2fa.backup-warning": {
|
||||
"defaultMessage": "Bewahre diese Backup-Codes an einem sicheren Ort auf. Jeder Code kann nur einmal verwendet werden."
|
||||
},
|
||||
"2fa.disable": {
|
||||
"defaultMessage": "Zwei-Faktor-Authentifizierung deaktivieren"
|
||||
},
|
||||
"2fa.disable-confirm": {
|
||||
"defaultMessage": "2FA deaktivieren"
|
||||
},
|
||||
"2fa.disable-warning": {
|
||||
"defaultMessage": "Das Deaktivieren der Zwei-Faktor-Authentifizierung macht dein Konto weniger sicher."
|
||||
},
|
||||
"2fa.disabled": {
|
||||
"defaultMessage": "Deaktiviert"
|
||||
},
|
||||
"2fa.done": {
|
||||
"defaultMessage": "Ich habe meine Backup-Codes gespeichert"
|
||||
},
|
||||
"2fa.enable": {
|
||||
"defaultMessage": "Zwei-Faktor-Authentifizierung aktivieren"
|
||||
},
|
||||
"2fa.enabled": {
|
||||
"defaultMessage": "Aktiviert"
|
||||
},
|
||||
"2fa.enter-code": {
|
||||
"defaultMessage": "Verifizierungscode eingeben"
|
||||
},
|
||||
"2fa.enter-code-disable": {
|
||||
"defaultMessage": "Verifizierungscode zum Deaktivieren eingeben"
|
||||
},
|
||||
"2fa.regenerate": {
|
||||
"defaultMessage": "Neu generieren"
|
||||
},
|
||||
"2fa.regenerate-backup": {
|
||||
"defaultMessage": "Backup-Codes neu generieren"
|
||||
},
|
||||
"2fa.regenerate-instructions": {
|
||||
"defaultMessage": "Gib einen Verifizierungscode ein, um neue Backup-Codes zu generieren. Deine alten Codes werden dadurch ungültig."
|
||||
},
|
||||
"2fa.secret-key": {
|
||||
"defaultMessage": "Geheimer Schlüssel"
|
||||
},
|
||||
"2fa.setup-instructions": {
|
||||
"defaultMessage": "Scanne diesen QR-Code mit deiner Authenticator-App oder gib den geheimen Schlüssel manuell ein."
|
||||
},
|
||||
"2fa.status": {
|
||||
"defaultMessage": "Status"
|
||||
},
|
||||
"2fa.title": {
|
||||
"defaultMessage": "Zwei-Faktor-Authentifizierung"
|
||||
},
|
||||
"2fa.verify-enable": {
|
||||
"defaultMessage": "Verifizieren und aktivieren"
|
||||
},
|
||||
"access-list": {
|
||||
"defaultMessage": "Zugriffsliste"
|
||||
},
|
||||
@@ -23,6 +80,9 @@
|
||||
"access-list.public.subtitle": {
|
||||
"defaultMessage": "Keine Authentifizierung erforderlich"
|
||||
},
|
||||
"access-list.rule-source.placeholder": {
|
||||
"defaultMessage": "192.168.1.100 oder 192.168.1.0/24 oder 2001:0db8::/32"
|
||||
},
|
||||
"access-list.satisfy-any": {
|
||||
"defaultMessage": "Satisfy Any"
|
||||
},
|
||||
@@ -38,12 +98,18 @@
|
||||
"action.add-location": {
|
||||
"defaultMessage": "Pfad hinzufügen"
|
||||
},
|
||||
"action.allow": {
|
||||
"defaultMessage": "Erlauben"
|
||||
},
|
||||
"action.close": {
|
||||
"defaultMessage": "Schließen"
|
||||
},
|
||||
"action.delete": {
|
||||
"defaultMessage": "Löschen"
|
||||
},
|
||||
"action.deny": {
|
||||
"defaultMessage": "Verweigern"
|
||||
},
|
||||
"action.disable": {
|
||||
"defaultMessage": "Deaktivieren"
|
||||
},
|
||||
@@ -68,6 +134,9 @@
|
||||
"auditlogs": {
|
||||
"defaultMessage": "Protokolle"
|
||||
},
|
||||
"auto": {
|
||||
"defaultMessage": "Automatisch"
|
||||
},
|
||||
"cancel": {
|
||||
"defaultMessage": "Abbrechen"
|
||||
},
|
||||
@@ -128,6 +197,9 @@
|
||||
"certificates.dns.provider": {
|
||||
"defaultMessage": "DNS Provider"
|
||||
},
|
||||
"certificates.dns.provider.placeholder": {
|
||||
"defaultMessage": "Anbieter auswählen..."
|
||||
},
|
||||
"certificates.dns.warning": {
|
||||
"defaultMessage": "Dieser Abschnitt erfordert einige Kenntnisse über Certbot und seine DNS-Plugins. Bitte konsultieren Sie die jeweilige Plugin-Dokumentation."
|
||||
},
|
||||
@@ -275,6 +347,9 @@
|
||||
"domain-names.wildcards-not-supported": {
|
||||
"defaultMessage": "Wildcards werden für diese Zertifizierungsstelle nicht unterstützt."
|
||||
},
|
||||
"domains.advanced": {
|
||||
"defaultMessage": "Erweitert"
|
||||
},
|
||||
"domains.force-ssl": {
|
||||
"defaultMessage": "Erzwinge SSL"
|
||||
},
|
||||
@@ -287,6 +362,9 @@
|
||||
"domains.http2-support": {
|
||||
"defaultMessage": "HTTP/2 Support"
|
||||
},
|
||||
"domains.trust-forwarded-proto": {
|
||||
"defaultMessage": "Upstream Forwarded-Proto-Header vertrauen"
|
||||
},
|
||||
"domains.use-dns": {
|
||||
"defaultMessage": "Nutze DNS Challenge"
|
||||
},
|
||||
@@ -354,7 +432,7 @@
|
||||
"defaultMessage": "Pfad beibehalten"
|
||||
},
|
||||
"host.flags.protocols": {
|
||||
"defaultMessage": "Protokole"
|
||||
"defaultMessage": "Protokolle"
|
||||
},
|
||||
"host.flags.websockets-upgrade": {
|
||||
"defaultMessage": "Websockets Support"
|
||||
@@ -383,6 +461,21 @@
|
||||
"loading": {
|
||||
"defaultMessage": "Laden…"
|
||||
},
|
||||
"login.2fa-code": {
|
||||
"defaultMessage": "Verifizierungscode"
|
||||
},
|
||||
"login.2fa-code-placeholder": {
|
||||
"defaultMessage": "Code eingeben"
|
||||
},
|
||||
"login.2fa-description": {
|
||||
"defaultMessage": "Gib den Code aus deiner Authenticator-App ein"
|
||||
},
|
||||
"login.2fa-title": {
|
||||
"defaultMessage": "Zwei-Faktor-Authentifizierung"
|
||||
},
|
||||
"login.2fa-verify": {
|
||||
"defaultMessage": "Verifizieren"
|
||||
},
|
||||
"login.title": {
|
||||
"defaultMessage": "Anmelden"
|
||||
},
|
||||
@@ -530,6 +623,24 @@
|
||||
"redirection-hosts.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}"
|
||||
},
|
||||
"redirection-hosts.http-code.300": {
|
||||
"defaultMessage": "300 Mehrere Auswahlmöglichkeiten"
|
||||
},
|
||||
"redirection-hosts.http-code.301": {
|
||||
"defaultMessage": "301 Dauerhaft verschoben"
|
||||
},
|
||||
"redirection-hosts.http-code.302": {
|
||||
"defaultMessage": "302 Vorübergehend verschoben"
|
||||
},
|
||||
"redirection-hosts.http-code.303": {
|
||||
"defaultMessage": "303 Siehe andere"
|
||||
},
|
||||
"redirection-hosts.http-code.307": {
|
||||
"defaultMessage": "307 Temporäre Umleitung"
|
||||
},
|
||||
"redirection-hosts.http-code.308": {
|
||||
"defaultMessage": "308 Permanente Umleitung"
|
||||
},
|
||||
"role.admin": {
|
||||
"defaultMessage": "Administrator"
|
||||
},
|
||||
@@ -587,6 +698,9 @@
|
||||
"stream.forward-host": {
|
||||
"defaultMessage": "Forward Host"
|
||||
},
|
||||
"stream.forward-host.placeholder": {
|
||||
"defaultMessage": "example.com oder 10.0.0.1 oder 2001:db8:3333:4444:5555:6666:7777:8888"
|
||||
},
|
||||
"stream.incoming-port": {
|
||||
"defaultMessage": "Incoming Port"
|
||||
},
|
||||
@@ -605,6 +719,9 @@
|
||||
"test": {
|
||||
"defaultMessage": "Test"
|
||||
},
|
||||
"update-available": {
|
||||
"defaultMessage": "Update verfügbar: {latestVersion}"
|
||||
},
|
||||
"user": {
|
||||
"defaultMessage": "User"
|
||||
},
|
||||
@@ -647,10 +764,13 @@
|
||||
"user.switch-light": {
|
||||
"defaultMessage": "Zum Light Mode wechseln"
|
||||
},
|
||||
"user.two-factor": {
|
||||
"defaultMessage": "Zwei-Faktor-Auth"
|
||||
},
|
||||
"username": {
|
||||
"defaultMessage": "Benutzername"
|
||||
},
|
||||
"users": {
|
||||
"defaultMessage": "Benutzer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,11 +70,7 @@ const DNSCertificateModal = EasyModal.create(({ visible, remove }: InnerModalPro
|
||||
<label htmlFor="keyType" className="form-label">
|
||||
<T id="certificates.key-type" />
|
||||
</label>
|
||||
<select
|
||||
id="keyType"
|
||||
className="form-select"
|
||||
{...field}
|
||||
>
|
||||
<select id="keyType" className="form-select" {...field}>
|
||||
<option value="rsa">
|
||||
<T id="certificates.key-type-rsa" />
|
||||
</option>
|
||||
|
||||
@@ -151,11 +151,7 @@ const HTTPCertificateModal = EasyModal.create(({ visible, remove }: InnerModalPr
|
||||
<label htmlFor="keyType" className="form-label">
|
||||
<T id="certificates.key-type" />
|
||||
</label>
|
||||
<select
|
||||
id="keyType"
|
||||
className="form-select"
|
||||
{...field}
|
||||
>
|
||||
<select id="keyType" className="form-select" {...field}>
|
||||
<option value="rsa">
|
||||
<T id="certificates.key-type-rsa" />
|
||||
</option>
|
||||
|
||||
@@ -162,7 +162,9 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
required
|
||||
{...field}
|
||||
>
|
||||
<option value="auto"><T id="auto" /></option>
|
||||
<option value="auto">
|
||||
<T id="auto" />
|
||||
</option>
|
||||
<option value="http">http</option>
|
||||
<option value="https">https</option>
|
||||
</select>
|
||||
@@ -224,12 +226,24 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
||||
required
|
||||
{...field}
|
||||
>
|
||||
<option value="300"><T id="redirection-hosts.http-code.300" /></option>
|
||||
<option value="301"><T id="redirection-hosts.http-code.301" /></option>
|
||||
<option value="302"><T id="redirection-hosts.http-code.302" /></option>
|
||||
<option value="303"><T id="redirection-hosts.http-code.303" /></option>
|
||||
<option value="307"><T id="redirection-hosts.http-code.307" /></option>
|
||||
<option value="308"><T id="redirection-hosts.http-code.308" /></option>
|
||||
<option value="300">
|
||||
<T id="redirection-hosts.http-code.300" />
|
||||
</option>
|
||||
<option value="301">
|
||||
<T id="redirection-hosts.http-code.301" />
|
||||
</option>
|
||||
<option value="302">
|
||||
<T id="redirection-hosts.http-code.302" />
|
||||
</option>
|
||||
<option value="303">
|
||||
<T id="redirection-hosts.http-code.303" />
|
||||
</option>
|
||||
<option value="307">
|
||||
<T id="redirection-hosts.http-code.307" />
|
||||
</option>
|
||||
<option value="308">
|
||||
<T id="redirection-hosts.http-code.308" />
|
||||
</option>
|
||||
</select>
|
||||
{form.errors.forwardHttpCode ? (
|
||||
<div className="invalid-feedback">
|
||||
|
||||
@@ -154,7 +154,9 @@ const StreamModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
type="text"
|
||||
className={`form-control ${form.errors.forwardingHost && form.touched.forwardingHost ? "is-invalid" : ""}`}
|
||||
required
|
||||
placeholder={intl.formatMessage({ id: "stream.forward-host.placeholder" })}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "stream.forward-host.placeholder",
|
||||
})}
|
||||
{...field}
|
||||
/>
|
||||
{form.errors.forwardingHost ? (
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.qrcode {
|
||||
max-width: "200px";
|
||||
height: "auto";
|
||||
border: 6px solid #fff;
|
||||
}
|
||||
@@ -3,16 +3,12 @@ import { Field, Form, Formik } from "formik";
|
||||
import { type ReactNode, useCallback, useEffect, useState } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import {
|
||||
disable2FA,
|
||||
enable2FA,
|
||||
get2FAStatus,
|
||||
regenerateBackupCodes,
|
||||
start2FASetup,
|
||||
} from "src/api/backend";
|
||||
import QRCode from "react-qr-code";
|
||||
import { disable2FA, enable2FA, get2FAStatus, regenerateBackupCodes, start2FASetup } from "src/api/backend";
|
||||
import { Button } from "src/components";
|
||||
import { T } from "src/locale";
|
||||
import { validateString } from "src/modules/Validations";
|
||||
import styles from "./TwoFactorModal.module.css";
|
||||
|
||||
type Step = "loading" | "status" | "setup" | "verify" | "backup" | "disable";
|
||||
|
||||
@@ -137,12 +133,7 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
)}
|
||||
</div>
|
||||
{!isEnabled ? (
|
||||
<Button
|
||||
fullWidth
|
||||
color="azure"
|
||||
onClick={handleStartSetup}
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
<Button fullWidth color="azure" onClick={handleStartSetup} isLoading={isSubmitting}>
|
||||
<T id="2fa.enable" />
|
||||
</Button>
|
||||
) : (
|
||||
@@ -166,12 +157,7 @@ const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => {
|
||||
<T id="2fa.setup-instructions" />
|
||||
</p>
|
||||
<div className="text-center mb-3">
|
||||
<img
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(setupData.otpauthUrl)}`}
|
||||
alt="QR Code"
|
||||
className="img-fluid"
|
||||
style={{ maxWidth: "200px" }}
|
||||
/>
|
||||
<QRCode value={setupData.otpauthUrl} size={200} className={styles.qrcode} />
|
||||
</div>
|
||||
<label className="mb-3 d-block">
|
||||
<span className="form-label small text-muted">
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { IconDotsVertical, IconEdit, IconTrash } from "@tabler/icons-react";
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { createColumnHelper, useTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { AccessList } from "src/api/backend";
|
||||
import { EmptyData, GravatarFormatter, HasPermission, ValueWithDateFormatter } from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import { ACCESS_LISTS, MANAGE } from "src/modules/Permissions";
|
||||
@@ -16,7 +17,7 @@ interface Props {
|
||||
onNew?: () => void;
|
||||
}
|
||||
export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, onNew }: Props) {
|
||||
const columnHelper = createColumnHelper<AccessList>();
|
||||
const columnHelper = createColumnHelper<Features, AccessList>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: any) => row.owner, {
|
||||
@@ -114,11 +115,10 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
[columnHelper, onEdit, onDelete],
|
||||
);
|
||||
|
||||
const tableInstance = useReactTable<AccessList>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { createColumnHelper, useTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { AuditLog } from "src/api/backend";
|
||||
import { EventFormatter, GravatarFormatter } from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
|
||||
@@ -11,10 +12,10 @@ interface Props {
|
||||
onSelectItem?: (id: number) => void;
|
||||
}
|
||||
export default function Table({ data, isFetching, onSelectItem }: Props) {
|
||||
const columnHelper = createColumnHelper<AuditLog>();
|
||||
const columnHelper = createColumnHelper<Features, AuditLog>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: AuditLog) => row.user, {
|
||||
columnHelper.accessor((row: any) => row.user, {
|
||||
id: "user.avatar",
|
||||
cell: (info: any) => {
|
||||
const value = info.getValue();
|
||||
@@ -24,7 +25,7 @@ export default function Table({ data, isFetching, onSelectItem }: Props) {
|
||||
className: "w-1",
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor((row: AuditLog) => row, {
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
id: "objectType",
|
||||
header: intl.formatMessage({ id: "column.event" }),
|
||||
cell: (info: any) => {
|
||||
@@ -55,11 +56,10 @@ export default function Table({ data, isFetching, onSelectItem }: Props) {
|
||||
[columnHelper, onSelectItem],
|
||||
);
|
||||
|
||||
const tableInstance = useReactTable<AuditLog>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconDotsVertical, IconDownload, IconRefresh, IconTrash } from "@tabler/icons-react";
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { createColumnHelper, useTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { Certificate } from "src/api/backend";
|
||||
import {
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
GravatarFormatter,
|
||||
HasPermission,
|
||||
} from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import { showCustomCertificateModal, showDNSCertificateModal, showHTTPCertificateModal } from "src/modals";
|
||||
@@ -24,7 +25,7 @@ interface Props {
|
||||
onDownload?: (id: number) => void;
|
||||
}
|
||||
export default function Table({ data, isFetching, onDelete, onRenew, onDownload, isFiltered }: Props) {
|
||||
const columnHelper = createColumnHelper<Certificate>();
|
||||
const columnHelper = createColumnHelper<Features, Certificate>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: any) => row.owner, {
|
||||
@@ -164,11 +165,10 @@ export default function Table({ data, isFetching, onDelete, onRenew, onDownload,
|
||||
[columnHelper, onDelete, onRenew, onDownload],
|
||||
);
|
||||
|
||||
const tableInstance = useReactTable<Certificate>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
@@ -193,9 +193,7 @@ export default function Login() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="card card-md">
|
||||
<div className="card-body">
|
||||
{twoFactorChallenge ? <TwoFactorForm /> : <LoginForm />}
|
||||
</div>
|
||||
<div className="card-body">{twoFactorChallenge ? <TwoFactorForm /> : <LoginForm />}</div>
|
||||
</div>
|
||||
<div className="text-center text-secondary mt-3">{getVersion()}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react";
|
||||
import {
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
type SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { createColumnHelper, type SortingState, useTable } from "@tanstack/react-table";
|
||||
import { useMemo, useState } from "react";
|
||||
import type { DeadHost } from "src/api/backend";
|
||||
import {
|
||||
@@ -16,6 +10,7 @@ import {
|
||||
HasPermission,
|
||||
TrueFalseFormatter,
|
||||
} from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import { DEAD_HOSTS, MANAGE } from "src/modules/Permissions";
|
||||
@@ -30,7 +25,7 @@ interface Props {
|
||||
onNew?: () => void;
|
||||
}
|
||||
export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew, isFiltered }: Props) {
|
||||
const columnHelper = createColumnHelper<DeadHost>();
|
||||
const columnHelper = createColumnHelper<Features, DeadHost>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: any) => row.owner, {
|
||||
@@ -47,7 +42,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
id: "domainNames",
|
||||
header: intl.formatMessage({ id: "column.source" }),
|
||||
sortingFn: (a, b) => {
|
||||
sortFn: (a, b) => {
|
||||
const aVal = a.original.domainNames?.[0] ?? "";
|
||||
const bVal = b.original.domainNames?.[0] ?? "";
|
||||
return aVal.localeCompare(bVal);
|
||||
@@ -143,14 +138,12 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
const tableInstance = useReactTable<DeadHost>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
state: { sorting },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react";
|
||||
import {
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
type SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { createColumnHelper, type SortingState, useTable } from "@tanstack/react-table";
|
||||
import { useMemo, useState } from "react";
|
||||
import type { ProxyHost } from "src/api/backend";
|
||||
import {
|
||||
@@ -17,6 +11,7 @@ import {
|
||||
HasPermission,
|
||||
TrueFalseFormatter,
|
||||
} from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import { MANAGE, PROXY_HOSTS } from "src/modules/Permissions";
|
||||
@@ -31,7 +26,7 @@ interface Props {
|
||||
onNew?: () => void;
|
||||
}
|
||||
export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew, isFiltered }: Props) {
|
||||
const columnHelper = createColumnHelper<ProxyHost>();
|
||||
const columnHelper = createColumnHelper<Features, ProxyHost>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: any) => row.owner, {
|
||||
@@ -48,7 +43,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
id: "domainNames",
|
||||
header: intl.formatMessage({ id: "column.source" }),
|
||||
sortingFn: (a, b) => {
|
||||
sortFn: (a, b) => {
|
||||
const aVal = a.original.domainNames?.[0] ?? "";
|
||||
const bVal = b.original.domainNames?.[0] ?? "";
|
||||
return aVal.localeCompare(bVal);
|
||||
@@ -61,7 +56,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
id: "forwardHost",
|
||||
header: intl.formatMessage({ id: "column.destination" }),
|
||||
sortingFn: (a, b) => {
|
||||
sortFn: (a, b) => {
|
||||
const aVal = `${a.original.forwardHost}:${a.original.forwardPort}`;
|
||||
const bVal = `${b.original.forwardHost}:${b.original.forwardPort}`;
|
||||
return aVal.localeCompare(bVal);
|
||||
@@ -165,14 +160,12 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
const tableInstance = useReactTable<ProxyHost>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
state: { sorting },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react";
|
||||
import {
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
type SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { createColumnHelper, type SortingState, useTable } from "@tanstack/react-table";
|
||||
import { useMemo, useState } from "react";
|
||||
import type { RedirectionHost } from "src/api/backend";
|
||||
import {
|
||||
@@ -16,6 +10,7 @@ import {
|
||||
HasPermission,
|
||||
TrueFalseFormatter,
|
||||
} from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import { MANAGE, REDIRECTION_HOSTS } from "src/modules/Permissions";
|
||||
@@ -30,7 +25,7 @@ interface Props {
|
||||
onNew?: () => void;
|
||||
}
|
||||
export default function Table({ data, isFetching, onEdit, onDelete, onDisableToggle, onNew, isFiltered }: Props) {
|
||||
const columnHelper = createColumnHelper<RedirectionHost>();
|
||||
const columnHelper = createColumnHelper<Features, RedirectionHost>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: any) => row.owner, {
|
||||
@@ -47,7 +42,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
id: "domainNames",
|
||||
header: intl.formatMessage({ id: "column.source" }),
|
||||
sortingFn: (a, b) => {
|
||||
sortFn: (a, b) => {
|
||||
const aVal = a.original.domainNames?.[0] ?? "";
|
||||
const bVal = b.original.domainNames?.[0] ?? "";
|
||||
return aVal.localeCompare(bVal);
|
||||
@@ -164,14 +159,12 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
const tableInstance = useReactTable<RedirectionHost>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
state: { sorting },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { IconDotsVertical, IconEdit, IconPower, IconTrash } from "@tabler/icons-react";
|
||||
import {
|
||||
createColumnHelper,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
type SortingState,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import { createColumnHelper, type SortingState, useTable } from "@tanstack/react-table";
|
||||
import { useMemo, useState } from "react";
|
||||
import type { Stream } from "src/api/backend";
|
||||
import {
|
||||
@@ -16,6 +10,7 @@ import {
|
||||
TrueFalseFormatter,
|
||||
ValueWithDateFormatter,
|
||||
} from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
import { MANAGE, STREAMS } from "src/modules/Permissions";
|
||||
@@ -30,7 +25,7 @@ interface Props {
|
||||
onNew?: () => void;
|
||||
}
|
||||
export default function Table({ data, isFetching, isFiltered, onEdit, onDelete, onDisableToggle, onNew }: Props) {
|
||||
const columnHelper = createColumnHelper<Stream>();
|
||||
const columnHelper = createColumnHelper<Features, Stream>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: any) => row.owner, {
|
||||
@@ -47,7 +42,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
id: "incomingPort",
|
||||
header: intl.formatMessage({ id: "column.incoming-port" }),
|
||||
sortingFn: (a, b) => (a.original.incomingPort ?? 0) - (b.original.incomingPort ?? 0),
|
||||
sortFn: (a, b) => (a.original.incomingPort ?? 0) - (b.original.incomingPort ?? 0),
|
||||
cell: (info: any) => {
|
||||
const value = info.getValue();
|
||||
return <ValueWithDateFormatter value={value.incomingPort} createdOn={value.createdOn} />;
|
||||
@@ -56,7 +51,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
id: "forwardHttpCode",
|
||||
header: intl.formatMessage({ id: "column.destination" }),
|
||||
sortingFn: (a, b) => {
|
||||
sortFn: (a, b) => {
|
||||
const aVal = `${a.original.forwardingHost}:${a.original.forwardingPort}`;
|
||||
const bVal = `${b.original.forwardingHost}:${b.original.forwardingPort}`;
|
||||
return aVal.localeCompare(bVal);
|
||||
@@ -174,14 +169,12 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
|
||||
const tableInstance = useReactTable<Stream>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
state: { sorting },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
IconShield,
|
||||
IconTrash,
|
||||
} from "@tabler/icons-react";
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import { createColumnHelper, useTable } from "@tanstack/react-table";
|
||||
import { useMemo } from "react";
|
||||
import type { User } from "src/api/backend";
|
||||
import {
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
TrueFalseFormatter,
|
||||
ValueWithDateFormatter,
|
||||
} from "src/components";
|
||||
import { type Features, features } from "src/components/Table/features";
|
||||
import { TableLayout } from "src/components/Table/TableLayout";
|
||||
import { intl, T } from "src/locale";
|
||||
|
||||
@@ -47,7 +48,7 @@ export default function Table({
|
||||
onNewUser,
|
||||
onLoginAs,
|
||||
}: Props) {
|
||||
const columnHelper = createColumnHelper<User>();
|
||||
const columnHelper = createColumnHelper<Features, User>();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor((row: any) => row, {
|
||||
@@ -216,11 +217,10 @@ export default function Table({
|
||||
],
|
||||
);
|
||||
|
||||
const tableInstance = useReactTable<User>({
|
||||
const tableInstance = useTable({
|
||||
features,
|
||||
columns,
|
||||
data,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
rowCount: data.length,
|
||||
meta: {
|
||||
isFetching,
|
||||
},
|
||||
|
||||
+870
-679
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user