mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-12-06 00:16:49 +00:00
Compare commits
17 Commits
4bd545c88e
...
39c9bbb167
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39c9bbb167 | ||
|
|
30c2781a02 | ||
|
|
53e78dcc17 | ||
|
|
62092b2ddc | ||
|
|
2c26ed8b11 | ||
|
|
e3f5cd9a58 | ||
|
|
fba14817e7 | ||
|
|
6825a9773b | ||
|
|
8bc3078d87 | ||
|
|
8aeb2fa661 | ||
|
|
5e7276e65b | ||
|
|
2bcb942f93 | ||
|
|
b3dac3df08 | ||
|
|
64c5a863f8 | ||
|
|
fd1d33444a | ||
|
|
6fa2d6a98a | ||
|
|
e88d55f1d2 |
@@ -1,7 +1,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nginxproxymanager.com/github.png">
|
<img src="https://nginxproxymanager.com/github.png">
|
||||||
<br><br>
|
<br><br>
|
||||||
<img src="https://img.shields.io/badge/version-2.13.2-green.svg?style=for-the-badge">
|
<img src="https://img.shields.io/badge/version-2.13.3-green.svg?style=for-the-badge">
|
||||||
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
||||||
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -370,7 +370,7 @@
|
|||||||
"leaseweb": {
|
"leaseweb": {
|
||||||
"name": "LeaseWeb",
|
"name": "LeaseWeb",
|
||||||
"package_name": "certbot-dns-leaseweb",
|
"package_name": "certbot-dns-leaseweb",
|
||||||
"version": "~=1.0.1",
|
"version": "~=1.0.3",
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"credentials": "dns_leaseweb_api_token = 01234556789",
|
"credentials": "dns_leaseweb_api_token = 01234556789",
|
||||||
"full_plugin_name": "dns-leaseweb"
|
"full_plugin_name": "dns-leaseweb"
|
||||||
@@ -399,6 +399,14 @@
|
|||||||
"credentials": "dns_luadns_email = user@example.com\ndns_luadns_token = 0123456789abcdef0123456789abcdef",
|
"credentials": "dns_luadns_email = user@example.com\ndns_luadns_token = 0123456789abcdef0123456789abcdef",
|
||||||
"full_plugin_name": "dns-luadns"
|
"full_plugin_name": "dns-luadns"
|
||||||
},
|
},
|
||||||
|
"mchost24": {
|
||||||
|
"name": "MC-HOST24",
|
||||||
|
"package_name": "certbot-dns-mchost24",
|
||||||
|
"version": "",
|
||||||
|
"dependencies": "",
|
||||||
|
"credentials": "# Obtain API token using https://github.com/JoeJoeTV/mchost24-api-python\ndns_mchost24_api_token=<insert obtained API token here>",
|
||||||
|
"full_plugin_name": "dns-mchost24"
|
||||||
|
},
|
||||||
"mijnhost": {
|
"mijnhost": {
|
||||||
"name": "mijn.host",
|
"name": "mijn.host",
|
||||||
"package_name": "certbot-dns-mijn-host",
|
"package_name": "certbot-dns-mijn-host",
|
||||||
|
|||||||
@@ -216,6 +216,11 @@ const internalNginx = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For redirection hosts, if the scheme is not http or https, set it to $scheme
|
||||||
|
if (nice_host_type === "redirection_host" && ['http', 'https'].indexOf(host.forward_scheme.toLowerCase()) === -1) {
|
||||||
|
host.forward_scheme = "$scheme";
|
||||||
|
}
|
||||||
|
|
||||||
if (host.locations) {
|
if (host.locations) {
|
||||||
//logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
|
//logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2));
|
||||||
origLocations = [].concat(host.locations);
|
origLocations = [].concat(host.locations);
|
||||||
|
|||||||
50
backend/migrations/20251111090000_redirect_auto_scheme.js
Normal file
50
backend/migrations/20251111090000_redirect_auto_scheme.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { migrate as logger } from "../logger.js";
|
||||||
|
|
||||||
|
const migrateName = "redirect_auto_scheme";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate
|
||||||
|
*
|
||||||
|
* @see http://knexjs.org/#Schema
|
||||||
|
*
|
||||||
|
* @param {Object} knex
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
const up = (knex) => {
|
||||||
|
logger.info(`[${migrateName}] Migrating Up...`);
|
||||||
|
|
||||||
|
return knex.schema
|
||||||
|
.table("redirection_host", async (table) => {
|
||||||
|
// change the column default from $scheme to auto
|
||||||
|
await table.string("forward_scheme").notNull().defaultTo("auto").alter();
|
||||||
|
await knex('redirection_host')
|
||||||
|
.where('forward_scheme', '$scheme')
|
||||||
|
.update({ forward_scheme: 'auto' });
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info(`[${migrateName}] redirection_host Table altered`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo Migrate
|
||||||
|
*
|
||||||
|
* @param {Object} knex
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
const down = (knex) => {
|
||||||
|
logger.info(`[${migrateName}] Migrating Down...`);
|
||||||
|
|
||||||
|
return knex.schema
|
||||||
|
.table("redirection_host", async (table) => {
|
||||||
|
await table.string("forward_scheme").notNull().defaultTo("$scheme").alter();
|
||||||
|
await knex('redirection_host')
|
||||||
|
.where('forward_scheme', 'auto')
|
||||||
|
.update({ forward_scheme: '$scheme' });
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
logger.info(`[${migrateName}] redirection_host Table altered`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { up, down };
|
||||||
@@ -37,7 +37,7 @@ const setupDefaultUser = async () => {
|
|||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
is_deleted: 0,
|
is_deleted: 0,
|
||||||
email: email,
|
email: initialAdminEmail,
|
||||||
name: "Administrator",
|
name: "Administrator",
|
||||||
nickname: "Admin",
|
nickname: "Admin",
|
||||||
avatar: "",
|
avatar: "",
|
||||||
@@ -53,7 +53,7 @@ const setupDefaultUser = async () => {
|
|||||||
.insert({
|
.insert({
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
type: "password",
|
type: "password",
|
||||||
secret: password,
|
secret: initialAdminPassword,
|
||||||
meta: {},
|
meta: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
auth_basic "Authorization required";
|
auth_basic "Authorization required";
|
||||||
auth_basic_user_file /data/access/{{ access_list_id }};
|
auth_basic_user_file /data/access/{{ access_list_id }};
|
||||||
|
|
||||||
{% if access_list.pass_auth == 0 or access_list.pass_auth == true %}
|
{% if access_list.pass_auth == 0 or access_list.pass_auth == false %}
|
||||||
proxy_set_header Authorization "";
|
proxy_set_header Authorization "";
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { lazy, Suspense } from "react";
|
import { lazy, Suspense } from "react";
|
||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
import { ErrorNotFound, LoadingPage, Page, SiteContainer, SiteFooter, SiteHeader, Unhealthy } from "src/components";
|
import {
|
||||||
|
ErrorNotFound,
|
||||||
|
LoadingPage,
|
||||||
|
Page,
|
||||||
|
SiteContainer,
|
||||||
|
SiteFooter,
|
||||||
|
SiteHeader,
|
||||||
|
SiteMenu,
|
||||||
|
Unhealthy,
|
||||||
|
} from "src/components";
|
||||||
import { useAuthState } from "src/context";
|
import { useAuthState } from "src/context";
|
||||||
import { useHealth } from "src/hooks";
|
import { useHealth } from "src/hooks";
|
||||||
|
|
||||||
@@ -46,6 +55,7 @@ function Router() {
|
|||||||
<Page>
|
<Page>
|
||||||
<div>
|
<div>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
|
<SiteMenu />
|
||||||
</div>
|
</div>
|
||||||
<SiteContainer>
|
<SiteContainer>
|
||||||
<Suspense fallback={<LoadingPage noLogo />}>
|
<Suspense fallback={<LoadingPage noLogo />}>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { IconLock, IconLogout, IconUser } from "@tabler/icons-react";
|
import { IconLock, IconLogout, IconUser } from "@tabler/icons-react";
|
||||||
import cs from "classnames";
|
import { LocalePicker, NavLink, ThemeSwitcher } from "src/components";
|
||||||
import { useState } from "react";
|
|
||||||
import { LocalePicker, NavLink, SiteMenu, ThemeSwitcher } from "src/components";
|
|
||||||
import { useAuthState } from "src/context";
|
import { useAuthState } from "src/context";
|
||||||
import { useUser } from "src/hooks";
|
import { useUser } from "src/hooks";
|
||||||
import { T } from "src/locale";
|
import { T } from "src/locale";
|
||||||
@@ -12,110 +10,105 @@ export function SiteHeader() {
|
|||||||
const { data: currentUser } = useUser("me");
|
const { data: currentUser } = useUser("me");
|
||||||
const isAdmin = currentUser?.roles.includes("admin");
|
const isAdmin = currentUser?.roles.includes("admin");
|
||||||
const { logout } = useAuthState();
|
const { logout } = useAuthState();
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<header className="navbar navbar-expand-md d-print-none">
|
||||||
<header className="navbar navbar-expand-md d-print-none">
|
<div className="container-xl">
|
||||||
<div className="container-xl">
|
<button
|
||||||
<button
|
className="navbar-toggler"
|
||||||
className={cs("navbar-toggler", { collapsed: !expanded })}
|
type="button"
|
||||||
type="button"
|
data-bs-toggle="collapse"
|
||||||
data-bs-toggle="collapse"
|
data-bs-target="#navbar-menu"
|
||||||
data-bs-target="#navbar-menu"
|
aria-controls="navbar-menu"
|
||||||
aria-controls="navbar-menu"
|
aria-expanded="false"
|
||||||
aria-expanded={expanded}
|
aria-label="Toggle navigation"
|
||||||
aria-label="Toggle navigation"
|
>
|
||||||
onClick={() => setExpanded(!expanded)}
|
<span className="navbar-toggler-icon" />
|
||||||
>
|
</button>
|
||||||
<span className="navbar-toggler-icon" />
|
<div className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||||
</button>
|
<NavLink to="/">
|
||||||
<div className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
<div className={styles.logo}>
|
||||||
<NavLink to="/">
|
<img
|
||||||
<div className={styles.logo}>
|
src="/images/logo-no-text.svg"
|
||||||
<img
|
width={40}
|
||||||
src="/images/logo-no-text.svg"
|
height={40}
|
||||||
width={40}
|
className="navbar-brand-image"
|
||||||
height={40}
|
alt="Logo"
|
||||||
className="navbar-brand-image"
|
/>
|
||||||
alt="Logo"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
Nginx Proxy Manager
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
<div className="navbar-nav flex-row order-md-last">
|
|
||||||
<div className="d-none d-md-flex">
|
|
||||||
<div className="nav-item">
|
|
||||||
<LocalePicker />
|
|
||||||
</div>
|
|
||||||
<div className="nav-item">
|
|
||||||
<ThemeSwitcher />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="nav-item d-none d-md-flex me-3">
|
Nginx Proxy Manager
|
||||||
<div className="nav-item dropdown">
|
</NavLink>
|
||||||
<a
|
</div>
|
||||||
href="/"
|
<div className="navbar-nav flex-row order-md-last">
|
||||||
className="nav-link d-flex lh-1 p-0 px-2"
|
<div className="d-none d-md-flex">
|
||||||
data-bs-toggle="dropdown"
|
<div className="nav-item">
|
||||||
aria-label="Open user menu"
|
<LocalePicker />
|
||||||
>
|
</div>
|
||||||
<span
|
<div className="nav-item">
|
||||||
className="avatar avatar-sm"
|
<ThemeSwitcher />
|
||||||
style={{
|
</div>
|
||||||
backgroundImage: `url(${currentUser?.avatar || "/images/default-avatar.jpg"})`,
|
</div>
|
||||||
}}
|
<div className="nav-item d-none d-md-flex me-3">
|
||||||
/>
|
<div className="nav-item dropdown">
|
||||||
<div className="d-none d-xl-block ps-2">
|
<a
|
||||||
<div>{currentUser?.nickname}</div>
|
href="/"
|
||||||
<div className="mt-1 small text-secondary">
|
className="nav-link d-flex lh-1 p-0 px-2"
|
||||||
<T id={isAdmin ? "role.admin" : "role.standard-user"} />
|
data-bs-toggle="dropdown"
|
||||||
</div>
|
aria-label="Open user menu"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="avatar avatar-sm"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${currentUser?.avatar || "/images/default-avatar.jpg"})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="d-none d-xl-block ps-2">
|
||||||
|
<div>{currentUser?.nickname}</div>
|
||||||
|
<div className="mt-1 small text-secondary">
|
||||||
|
<T id={isAdmin ? "role.admin" : "role.standard-user"} />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
<div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
|
||||||
<a
|
|
||||||
href="?"
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
showUserModal("me");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconUser width={18} />
|
|
||||||
<T id="user.edit-profile" />
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="?"
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
showChangePasswordModal("me");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconLock width={18} />
|
|
||||||
<T id="user.change-password" />
|
|
||||||
</a>
|
|
||||||
<div className="dropdown-divider" />
|
|
||||||
<a
|
|
||||||
href="?"
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
logout();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconLogout width={18} />
|
|
||||||
<T id="user.logout" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
|
<div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
|
||||||
|
<a
|
||||||
|
href="?"
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
showUserModal("me");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconUser width={18} />
|
||||||
|
<T id="user.edit-profile" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="?"
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
showChangePasswordModal("me");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconLock width={18} />
|
||||||
|
<T id="user.change-password" />
|
||||||
|
</a>
|
||||||
|
<div className="dropdown-divider" />
|
||||||
|
<a
|
||||||
|
href="?"
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
logout();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconLogout width={18} />
|
||||||
|
<T id="user.logout" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</div>
|
||||||
<SiteMenu mobileExpanded={expanded} setMobileExpanded={setExpanded} />
|
</header>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,11 +175,7 @@ const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
export function SiteMenu() {
|
||||||
mobileExpanded?: boolean;
|
|
||||||
setMobileExpanded?: (expanded: boolean) => void;
|
|
||||||
}
|
|
||||||
export function SiteMenu({ mobileExpanded = false, setMobileExpanded }: Props) {
|
|
||||||
// This is hacky AF. But that's the price of using a non-react UI kit.
|
// This is hacky AF. But that's the price of using a non-react UI kit.
|
||||||
const closeMenus = () => {
|
const closeMenus = () => {
|
||||||
const navMenus = document.querySelectorAll(".nav-item.dropdown");
|
const navMenus = document.querySelectorAll(".nav-item.dropdown");
|
||||||
@@ -189,13 +185,12 @@ export function SiteMenu({ mobileExpanded = false, setMobileExpanded }: Props) {
|
|||||||
if (dropdown) {
|
if (dropdown) {
|
||||||
dropdown.classList.remove("show");
|
dropdown.classList.remove("show");
|
||||||
}
|
}
|
||||||
setMobileExpanded?.(false);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="navbar-expand-md">
|
<header className="navbar-expand-md">
|
||||||
<div className={cn("collapse", "navbar-collapse", { show: mobileExpanded })} id="navbar-menu">
|
<div className="collapse navbar-collapse" id="navbar-menu">
|
||||||
<div className="navbar">
|
<div className="navbar">
|
||||||
<div className="container-xl">
|
<div className="container-xl">
|
||||||
<div className="row flex-column flex-md-row flex-fill align-items-center">
|
<div className="row flex-column flex-md-row flex-fill align-items-center">
|
||||||
|
|||||||
@@ -169,6 +169,7 @@
|
|||||||
"public": "Public",
|
"public": "Public",
|
||||||
"redirection-host": "Redirection Host",
|
"redirection-host": "Redirection Host",
|
||||||
"redirection-host.forward-domain": "Forward Domain",
|
"redirection-host.forward-domain": "Forward Domain",
|
||||||
|
"redirection-host.forward-http-code": "HTTP Code",
|
||||||
"redirection-hosts": "Redirection Hosts",
|
"redirection-hosts": "Redirection Hosts",
|
||||||
"redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}",
|
"redirection-hosts.count": "{count} {count, plural, one {Redirection Host} other {Redirection Hosts}}",
|
||||||
"role.admin": "Administrator",
|
"role.admin": "Administrator",
|
||||||
|
|||||||
@@ -509,6 +509,9 @@
|
|||||||
"redirection-host.forward-domain": {
|
"redirection-host.forward-domain": {
|
||||||
"defaultMessage": "Forward Domain"
|
"defaultMessage": "Forward Domain"
|
||||||
},
|
},
|
||||||
|
"redirection-host.forward-http-code": {
|
||||||
|
"defaultMessage": "HTTP Code"
|
||||||
|
},
|
||||||
"redirection-hosts": {
|
"redirection-hosts": {
|
||||||
"defaultMessage": "Redirection Hosts"
|
"defaultMessage": "Redirection Hosts"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
|||||||
required
|
required
|
||||||
{...field}
|
{...field}
|
||||||
>
|
>
|
||||||
<option value="$scheme">Auto</option>
|
<option value="auto">Auto</option>
|
||||||
<option value="http">http</option>
|
<option value="http">http</option>
|
||||||
<option value="https">https</option>
|
<option value="https">https</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -212,6 +212,36 @@ const RedirectionHostModal = EasyModal.create(({ id, visible, remove }: Props) =
|
|||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Field name="forwardHttpCode">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label" htmlFor="forwardHttpCode">
|
||||||
|
<T id="redirection-host.forward-http-code" />
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="forwardHttpCode"
|
||||||
|
className={`form-control ${form.errors.forwardHttpCode && form.touched.forwardHttpCode ? "is-invalid" : ""}`}
|
||||||
|
required
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
<option value="300">300 Multiple choices</option>
|
||||||
|
<option value="301">301 Moved permanently</option>
|
||||||
|
<option value="302">302 Moved temporarily</option>
|
||||||
|
<option value="303">303 See other</option>
|
||||||
|
<option value="307">307 Temporary redirect</option>
|
||||||
|
<option value="308">308 Permanent redirect</option>
|
||||||
|
</select>
|
||||||
|
{form.errors.forwardHttpCode ? (
|
||||||
|
<div className="invalid-feedback">
|
||||||
|
{form.errors.forwardHttpCode &&
|
||||||
|
form.touched.forwardHttpCode
|
||||||
|
? form.errors.forwardHttpCode
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<h4 className="py-2">
|
<h4 className="py-2">
|
||||||
<T id="options" />
|
<T id="options" />
|
||||||
|
|||||||
Reference in New Issue
Block a user