You've already forked nginx-proxy-manager
mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-11-14 11:42:26 +03:00
Tidy up
- Add help docs for most sections - Add translations documentation - Fix up todos - Remove german translation
This commit is contained in:
@@ -4,7 +4,6 @@ import type { AccessList } from "./models";
|
||||
export async function createAccessList(item: AccessList): Promise<AccessList> {
|
||||
return await api.post({
|
||||
url: "/nginx/access-lists",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { DeadHost } from "./models";
|
||||
export async function createDeadHost(item: DeadHost): Promise<DeadHost> {
|
||||
return await api.post({
|
||||
url: "/nginx/dead-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { ProxyHost } from "./models";
|
||||
export async function createProxyHost(item: ProxyHost): Promise<ProxyHost> {
|
||||
return await api.post({
|
||||
url: "/nginx/proxy-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { RedirectionHost } from "./models";
|
||||
export async function createRedirectionHost(item: RedirectionHost): Promise<RedirectionHost> {
|
||||
return await api.post({
|
||||
url: "/nginx/redirection-hosts",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { Stream } from "./models";
|
||||
export async function createStream(item: Stream): Promise<Stream> {
|
||||
return await api.post({
|
||||
url: "/nginx/streams",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ export interface NewUser {
|
||||
export async function createUser(item: NewUser, noAuth?: boolean): Promise<User> {
|
||||
return await api.post({
|
||||
url: "/users",
|
||||
// todo: only use whitelist of fields for this data
|
||||
data: item,
|
||||
noAuth,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createIntl, createIntlCache } from "react-intl";
|
||||
import langDe from "./lang/de.json";
|
||||
import langEn from "./lang/en.json";
|
||||
import langFa from "./lang/fa.json";
|
||||
import langList from "./lang/lang-list.json";
|
||||
@@ -9,15 +8,12 @@ import langList from "./lang/lang-list.json";
|
||||
// Remember when adding to this list, also update check-locales.js script
|
||||
const localeOptions = [
|
||||
["en", "en-US"],
|
||||
["de", "de-DE"],
|
||||
["fa", "fa-IR"],
|
||||
];
|
||||
|
||||
const loadMessages = (locale?: string): typeof langList & typeof langEn => {
|
||||
const thisLocale = locale || "en";
|
||||
switch (thisLocale.slice(0, 2)) {
|
||||
case "de":
|
||||
return Object.assign({}, langList, langEn, langDe);
|
||||
case "fa":
|
||||
return Object.assign({}, langList, langEn, langFa);
|
||||
default:
|
||||
@@ -27,9 +23,6 @@ const loadMessages = (locale?: string): typeof langList & typeof langEn => {
|
||||
|
||||
const getFlagCodeForLocale = (locale?: string) => {
|
||||
switch (locale) {
|
||||
case "de-DE":
|
||||
case "de":
|
||||
return "DE";
|
||||
case "fa-IR":
|
||||
case "fa":
|
||||
return "IR";
|
||||
|
||||
@@ -1,23 +1,48 @@
|
||||
# Internationalisation support
|
||||
|
||||
## Before you start
|
||||
|
||||
It's highly recommended that you spin up a development instance of this project
|
||||
on your docker capable server. It's pretty easy:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/NginxProxyManager/nginx-proxy-manager.git
|
||||
cd nginx-proxy-manager
|
||||
./scripts/start-dev -f
|
||||
```
|
||||
|
||||
Then after a while, you can access http://yourserverip:3081
|
||||
|
||||
This stack will watch the file system for changes, especially to language files,
|
||||
and reload the site you have open in the browser.
|
||||
|
||||
|
||||
## Adding new translations
|
||||
|
||||
Modify the files in the `src` folder. Follow the conventions already there.
|
||||
|
||||
When the development stack is running, it will sort the locale lang files
|
||||
for you when you save.
|
||||
|
||||
|
||||
## After making changes
|
||||
|
||||
You will need to run `yarn locale-compile` in this frontend folder for
|
||||
If you're NOT running the development stack, you will need to run
|
||||
`yarn locale-compile` in the `frontend` folder for
|
||||
the new translations to be compiled into the `lang` folder.
|
||||
|
||||
When running in dev mode, this should automatically happen within Vite.
|
||||
|
||||
## Adding a whole new language
|
||||
|
||||
There's a fair bit you'll need to touch. Here's a list that may
|
||||
not be complete by the time you're reading this:
|
||||
|
||||
- frontend/src/locale/src/[yourlang].json
|
||||
- frontend/src/locale/src/lang-list.json
|
||||
- frontend/src/locale/src/HelpDoc/*
|
||||
- frontend/src/locale/IntlProvider.tsx
|
||||
|
||||
|
||||
## Checking for missing translations in other languages
|
||||
## Checking for missing translations in languages
|
||||
|
||||
Run `node check-locales.cjs` in this frontend folder.
|
||||
|
||||
|
||||
## Adding new languages
|
||||
|
||||
todo
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"dashboard": "Armaturenbrett"
|
||||
}
|
||||
@@ -100,7 +100,11 @@
|
||||
"error.invalid-auth": "Invalid email or password",
|
||||
"error.invalid-domain": "Invalid domain: {domain}",
|
||||
"error.invalid-email": "Invalid email address",
|
||||
"error.max-character-length": "Maximum length is {max} character{max, plural, one {} other {s}}",
|
||||
"error.max-domains": "Too many domains, max is {max}",
|
||||
"error.maximum": "Maximum is {max}",
|
||||
"error.min-character-length": "Minimum length is {min} character{min, plural, one {} other {s}}",
|
||||
"error.minimum": "Minimum is {min}",
|
||||
"error.passwords-must-match": "Passwords must match",
|
||||
"error.required": "This is required",
|
||||
"expires.on": "Expires: {date}",
|
||||
|
||||
7
frontend/src/locale/src/HelpDoc/en/AccessLists.md
Normal file
7
frontend/src/locale/src/HelpDoc/en/AccessLists.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## What is an Access List?
|
||||
|
||||
Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication.
|
||||
|
||||
You can configure multiple client rules, usernames and passwords for a single Access List and then apply that to one or more _Proxy Hosts_.
|
||||
|
||||
This is most useful for forwarded web services that do not have authentication mechanisms built in or when you want to protect from unknown clients.
|
||||
32
frontend/src/locale/src/HelpDoc/en/Certificates.md
Normal file
32
frontend/src/locale/src/HelpDoc/en/Certificates.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Certificates Help
|
||||
|
||||
### HTTP Certificate
|
||||
|
||||
A HTTP validated certificate means Let's Encrypt servers will
|
||||
attempt to reach your domains over HTTP (not HTTPS!) and if successful, they
|
||||
will issue your certificate.
|
||||
|
||||
For this method, you will have to have a _Proxy Host_ created for your domains(s) that
|
||||
is accessible with HTTP and pointing to this Nginx installation. After a certificate
|
||||
has been given, you can modify the _Proxy Host_ to also use this certificate for HTTPS
|
||||
connections. However, the _Proxy Host_ will still need to be configured for HTTP access
|
||||
in order for the certificate to renew.
|
||||
|
||||
This process _does not_ support wildcard domains.
|
||||
|
||||
### DNS Certificate
|
||||
|
||||
A DNS validated certificate requires you to use a DNS Provider plugin. This DNS
|
||||
Provider will be used to create temporary records on your domain and then Let's
|
||||
Encrypt will query those records to be sure you're the owner and if successful, they
|
||||
will issue your certificate.
|
||||
|
||||
You do not need a _Proxy Host_ to be created prior to requesting this type of
|
||||
certificate. Nor do you need to have your _Proxy Host_ configured for HTTP access.
|
||||
|
||||
This process _does_ support wildcard domains.
|
||||
|
||||
### Custom Certificate
|
||||
|
||||
Use this option to upload your own SSL Certificate, as provided by your own
|
||||
Certificate Authority.
|
||||
10
frontend/src/locale/src/HelpDoc/en/DeadHosts.md
Normal file
10
frontend/src/locale/src/HelpDoc/en/DeadHosts.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## What is a 404 Host?
|
||||
|
||||
A 404 Host is simply a host setup that shows a 404 page.
|
||||
|
||||
This can be useful when your domain is listed in search engines and you want
|
||||
to provide a nicer error page or specifically to tell the search indexers that
|
||||
the domain pages no longer exist.
|
||||
|
||||
Another benefit of having this host is to track the logs for hits to it and
|
||||
view the referrers.
|
||||
7
frontend/src/locale/src/HelpDoc/en/ProxyHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/en/ProxyHosts.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## What is a Proxy Host?
|
||||
|
||||
A Proxy Host is the incoming endpoint for a web service that you want to forward.
|
||||
|
||||
It provides optional SSL termination for your service that might not have SSL support built in.
|
||||
|
||||
Proxy Hosts are the most common use for the Nginx Proxy Manager.
|
||||
7
frontend/src/locale/src/HelpDoc/en/RedirectionHosts.md
Normal file
7
frontend/src/locale/src/HelpDoc/en/RedirectionHosts.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## What is a Redirection Host?
|
||||
|
||||
A Redirection Host will redirect requests from the incoming domain and push the
|
||||
viewer to another domain.
|
||||
|
||||
The most common reason to use this type of host is when your website changes
|
||||
domains but you still have search engine or referrer links pointing to the old domain.
|
||||
6
frontend/src/locale/src/HelpDoc/en/Streams.md
Normal file
6
frontend/src/locale/src/HelpDoc/en/Streams.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## What is a Stream?
|
||||
|
||||
A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP
|
||||
traffic directly to another computer on the network.
|
||||
|
||||
If you're running game servers, FTP or SSH servers this can come in handy.
|
||||
6
frontend/src/locale/src/HelpDoc/en/index.ts
Normal file
6
frontend/src/locale/src/HelpDoc/en/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * as AccessLists from "./AccessLists.md";
|
||||
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";
|
||||
20
frontend/src/locale/src/HelpDoc/index.ts
Normal file
20
frontend/src/locale/src/HelpDoc/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// import * as de from "./de/index";
|
||||
// import * as fa from "./fa/index";
|
||||
import * as en from "./en/index";
|
||||
|
||||
const items: any = { en };
|
||||
|
||||
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}`);
|
||||
};
|
||||
|
||||
export default items;
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"dashboard": {
|
||||
"defaultMessage": "Armaturenbrett"
|
||||
}
|
||||
}
|
||||
@@ -302,9 +302,21 @@
|
||||
"error.invalid-email": {
|
||||
"defaultMessage": "Invalid email address"
|
||||
},
|
||||
"error.max-character-length": {
|
||||
"defaultMessage": "Maximum length is {max} character{max, plural, one {} other {s}}"
|
||||
},
|
||||
"error.max-domains": {
|
||||
"defaultMessage": "Too many domains, max is {max}"
|
||||
},
|
||||
"error.maximum": {
|
||||
"defaultMessage": "Maximum is {max}"
|
||||
},
|
||||
"error.min-character-length": {
|
||||
"defaultMessage": "Minimum length is {min} character{min, plural, one {} other {s}}"
|
||||
},
|
||||
"error.minimum": {
|
||||
"defaultMessage": "Minimum is {min}"
|
||||
},
|
||||
"error.passwords-must-match": {
|
||||
"defaultMessage": "Passwords must match"
|
||||
},
|
||||
|
||||
@@ -52,7 +52,27 @@ const DeleteConfirmModal = EasyModal.create(
|
||||
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
|
||||
{error}
|
||||
</Alert>
|
||||
{children}
|
||||
<div className="text-center mb-3">
|
||||
<svg
|
||||
role="img"
|
||||
aria-label="warning icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="icon mb-2 text-danger icon-lg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M12 9v2m0 4v.01" />
|
||||
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-center mb-3">{children}</div>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
|
||||
|
||||
54
frontend/src/modals/HelpModal.tsx
Normal file
54
frontend/src/modals/HelpModal.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import cn from "classnames";
|
||||
import EasyModal, { type InnerModalProps } from "ez-modal-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Modal from "react-bootstrap/Modal";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Button } from "src/components";
|
||||
import { getLocale, T } from "src/locale";
|
||||
import { getHelpFile } from "src/locale/src/HelpDoc";
|
||||
|
||||
interface Props extends InnerModalProps {
|
||||
section: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const showHelpModal = (section: string, color?: string) => {
|
||||
EasyModal.show(HelpModal, { section, color });
|
||||
};
|
||||
|
||||
const HelpModal = EasyModal.create(({ section, color, visible, remove }: Props) => {
|
||||
const [markdownText, setMarkdownText] = useState("");
|
||||
const lang = getLocale(true);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const docFile = getHelpFile(lang, section) as any;
|
||||
fetch(docFile)
|
||||
.then((response) => response.text())
|
||||
.then(setMarkdownText);
|
||||
} catch (ex: any) {
|
||||
setMarkdownText(`**ERROR:** ${ex.message}`);
|
||||
}
|
||||
}, [lang, section]);
|
||||
|
||||
return (
|
||||
<Modal show={visible} onHide={remove}>
|
||||
<Modal.Body>
|
||||
<ReactMarkdown>{markdownText}</ReactMarkdown>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button
|
||||
type="button"
|
||||
actionType="primary"
|
||||
className={cn("ms-auto", color ? `btn-${color}` : null)}
|
||||
data-bs-dismiss="modal"
|
||||
onClick={remove}
|
||||
>
|
||||
<T id="action.close" />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export { showHelpModal };
|
||||
@@ -5,6 +5,7 @@ export * from "./DeadHostModal";
|
||||
export * from "./DeleteConfirmModal";
|
||||
export * from "./DNSCertificateModal";
|
||||
export * from "./EventDetailsModal";
|
||||
export * from "./HelpModal";
|
||||
export * from "./HTTPCertificateModal";
|
||||
export * from "./PermissionsModal";
|
||||
export * from "./ProxyHostModal";
|
||||
|
||||
@@ -11,12 +11,10 @@ const validateString = (minLength = 0, maxLength = 0) => {
|
||||
return intl.formatMessage({ id: "error.required" });
|
||||
}
|
||||
if (minLength && value.length < minLength) {
|
||||
// TODO: i18n
|
||||
return `Minimum length is ${minLength} character${minLength === 1 ? "" : "s"}`;
|
||||
return intl.formatMessage({ id: "error.min-character-length" }, { min: minLength });
|
||||
}
|
||||
if (maxLength && (typeof value === "undefined" || value.length > maxLength)) {
|
||||
// TODO: i18n
|
||||
return `Maximum length is ${maxLength} character${maxLength === 1 ? "" : "s"}`;
|
||||
return intl.formatMessage({ id: "error.max-character-length" }, { max: maxLength });
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -33,12 +31,10 @@ const validateNumber = (min = -1, max = -1) => {
|
||||
return intl.formatMessage({ id: "error.required" });
|
||||
}
|
||||
if (min > -1 && int < min) {
|
||||
// TODO: i18n
|
||||
return `Minimum is ${min}`;
|
||||
return intl.formatMessage({ id: "error.minimum" }, { min });
|
||||
}
|
||||
if (max > -1 && int > max) {
|
||||
// TODO: i18n
|
||||
return `Maximum is ${max}`;
|
||||
return intl.formatMessage({ id: "error.maximum" }, { max });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
cell: (info: any) => <T id="proxy-hosts.count" data={{ count: info.getValue() }} />,
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "id", // todo: not needed for a display?
|
||||
id: "id",
|
||||
cell: (info: any) => {
|
||||
return (
|
||||
<span className="dropdown">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { IconHelp, IconSearch } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
import { deleteAccessList } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useAccessLists } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { showAccessListModal, showDeleteConfirmModal } from "src/modals";
|
||||
import { showAccessListModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
@@ -47,9 +47,10 @@ export default function TableWrapper() {
|
||||
<T id="access-lists" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
{data?.length ? (
|
||||
<div className="input-group input-group-flat w-auto">
|
||||
<span className="input-group-text input-group-text-sm">
|
||||
<IconSearch size={16} />
|
||||
@@ -62,12 +63,17 @@ export default function TableWrapper() {
|
||||
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Button size="sm" onClick={() => showHelpModal("AccessLists", "cyan")}>
|
||||
<IconHelp size={20} />
|
||||
</Button>
|
||||
{data?.length ? (
|
||||
<Button size="sm" className="btn-cyan" onClick={() => showAccessListModal("new")}>
|
||||
<T id="object.add" tData={{ object: "access-list" }} />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
@@ -77,7 +83,7 @@ export default function TableWrapper() {
|
||||
onEdit={(id: number) => showAccessListModal(id)}
|
||||
onDelete={(id: number) =>
|
||||
showDeleteConfirmModal({
|
||||
title: "access.delete.title",
|
||||
title: <T id="object.delete" tData={{ object: "access-list" }} />,
|
||||
onConfirm: () => handleDelete(id),
|
||||
invalidations: [["access-lists"], ["access-list", id]],
|
||||
children: <T id="object.delete.content" tData={{ object: "access-list" }} />,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { IconHelp, IconSearch } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
import { deleteCertificate, downloadCertificate } from "src/api/backend";
|
||||
import { LoadingPage } from "src/components";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useCertificates } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import {
|
||||
showCustomCertificateModal,
|
||||
showDeleteConfirmModal,
|
||||
showDNSCertificateModal,
|
||||
showHelpModal,
|
||||
showHTTPCertificateModal,
|
||||
showRenewCertificateModal,
|
||||
} from "src/modals";
|
||||
@@ -69,9 +70,10 @@ export default function TableWrapper() {
|
||||
<T id="certificates" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
{data?.length ? (
|
||||
<div className="input-group input-group-flat w-auto">
|
||||
<span className="input-group-text input-group-text-sm">
|
||||
<IconSearch size={16} />
|
||||
@@ -84,6 +86,11 @@ export default function TableWrapper() {
|
||||
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Button size="sm" onClick={() => showHelpModal("Certificates", "pink")}>
|
||||
<IconHelp size={20} />
|
||||
</Button>
|
||||
{data?.length ? (
|
||||
<div className="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
@@ -126,9 +133,9 @@ export default function TableWrapper() {
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
|
||||
@@ -116,12 +116,7 @@ const Dashboard = () => {
|
||||
<code>{`Todo:
|
||||
|
||||
- check mobile
|
||||
- use statuses for table formatters where applicable: https://docs.tabler.io/ui/components/statuses
|
||||
- add help docs for host types
|
||||
- REDO SCREENSHOTS in docs folder
|
||||
- search codebase for "TODO"
|
||||
- update documentation to add development notes for translations
|
||||
- double check output of access field selection on proxy host dialog, after access lists are completed
|
||||
- check permissions in all places
|
||||
|
||||
More for api, then implement here:
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "id", // todo: not needed for a display?
|
||||
id: "id",
|
||||
cell: (info: any) => {
|
||||
return (
|
||||
<span className="dropdown">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { IconHelp, IconSearch } from "@tabler/icons-react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
@@ -6,7 +6,7 @@ import { deleteDeadHost, toggleDeadHost } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useDeadHosts } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { showDeadHostModal, showDeleteConfirmModal } from "src/modals";
|
||||
import { showDeadHostModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
@@ -56,9 +56,10 @@ export default function TableWrapper() {
|
||||
<T id="dead-hosts" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
{data?.length ? (
|
||||
<div className="input-group input-group-flat w-auto">
|
||||
<span className="input-group-text input-group-text-sm">
|
||||
<IconSearch size={16} />
|
||||
@@ -71,12 +72,17 @@ export default function TableWrapper() {
|
||||
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Button size="sm" onClick={() => showHelpModal("DeadHosts", "red")}>
|
||||
<IconHelp size={20} />
|
||||
</Button>
|
||||
{data?.length ? (
|
||||
<Button size="sm" className="btn-red" onClick={() => showDeadHostModal("new")}>
|
||||
<T id="object.add" tData={{ object: "dead-host" }} />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { IconHelp, IconSearch } from "@tabler/icons-react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
@@ -6,7 +6,7 @@ import { deleteProxyHost, toggleProxyHost } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useProxyHosts } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { showDeleteConfirmModal, showProxyHostModal } from "src/modals";
|
||||
import { showDeleteConfirmModal, showHelpModal, showProxyHostModal } from "src/modals";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
@@ -59,9 +59,10 @@ export default function TableWrapper() {
|
||||
<T id="proxy-hosts" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
{data?.length ? (
|
||||
<div className="input-group input-group-flat w-auto">
|
||||
<span className="input-group-text input-group-text-sm">
|
||||
<IconSearch size={16} />
|
||||
@@ -74,12 +75,17 @@ export default function TableWrapper() {
|
||||
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Button size="sm" onClick={() => showHelpModal("ProxyHosts", "lime")}>
|
||||
<IconHelp size={20} />
|
||||
</Button>
|
||||
{data?.length ? (
|
||||
<Button size="sm" className="btn-lime" onClick={() => showProxyHostModal("new")}>
|
||||
<T id="object.add" tData={{ object: "proxy-host" }} />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { IconHelp, IconSearch } from "@tabler/icons-react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
@@ -6,7 +6,7 @@ import { deleteRedirectionHost, toggleRedirectionHost } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useRedirectionHosts } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { showDeleteConfirmModal, showRedirectionHostModal } from "src/modals";
|
||||
import { showDeleteConfirmModal, showHelpModal, showRedirectionHostModal } from "src/modals";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
@@ -59,9 +59,10 @@ export default function TableWrapper() {
|
||||
<T id="redirection-hosts" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
{data?.length ? (
|
||||
<div className="input-group input-group-flat w-auto">
|
||||
<span className="input-group-text input-group-text-sm">
|
||||
<IconSearch size={16} />
|
||||
@@ -74,6 +75,11 @@ export default function TableWrapper() {
|
||||
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Button size="sm" onClick={() => showHelpModal("RedirectionHosts", "yellow")}>
|
||||
<IconHelp size={20} />
|
||||
</Button>
|
||||
{data?.length ? (
|
||||
<Button
|
||||
size="sm"
|
||||
className="btn-yellow"
|
||||
@@ -81,9 +87,9 @@ export default function TableWrapper() {
|
||||
>
|
||||
<T id="object.add" tData={{ object: "redirection-host" }} />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "id", // todo: not needed for a display?
|
||||
id: "id",
|
||||
cell: (info: any) => {
|
||||
return (
|
||||
<span className="dropdown">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconSearch } from "@tabler/icons-react";
|
||||
import { IconHelp, IconSearch } from "@tabler/icons-react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
@@ -6,7 +6,7 @@ import { deleteStream, toggleStream } from "src/api/backend";
|
||||
import { Button, LoadingPage } from "src/components";
|
||||
import { useStreams } from "src/hooks";
|
||||
import { T } from "src/locale";
|
||||
import { showDeleteConfirmModal, showStreamModal } from "src/modals";
|
||||
import { showDeleteConfirmModal, showHelpModal, showStreamModal } from "src/modals";
|
||||
import { showObjectSuccess } from "src/notifications";
|
||||
import Table from "./Table";
|
||||
|
||||
@@ -61,9 +61,10 @@ export default function TableWrapper() {
|
||||
<T id="streams" />
|
||||
</h2>
|
||||
</div>
|
||||
{data?.length ? (
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
|
||||
<div className="col-md-auto col-sm-12">
|
||||
<div className="ms-auto d-flex flex-wrap btn-list">
|
||||
{data?.length ? (
|
||||
<div className="input-group input-group-flat w-auto">
|
||||
<span className="input-group-text input-group-text-sm">
|
||||
<IconSearch size={16} />
|
||||
@@ -76,12 +77,17 @@ export default function TableWrapper() {
|
||||
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<Button size="sm" onClick={() => showHelpModal("Streams", "blue")}>
|
||||
<IconHelp size={20} />
|
||||
</Button>
|
||||
{data?.length ? (
|
||||
<Button size="sm" className="btn-blue" onClick={() => showStreamModal("new")}>
|
||||
<T id="object.add" tData={{ object: "stream" }} />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
|
||||
Reference in New Issue
Block a user