Add IP filtering feature with UI components and translations

This commit is contained in:
Tudor Stanciu 2025-07-13 17:07:48 +03:00
parent e11001d68e
commit 71277d1e83
8 changed files with 192 additions and 1 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "reverse-proxy-frontend", "name": "reverse-proxy-frontend",
"version": "1.4.11", "version": "1.4.12",
"private": true, "private": true,
"description": "Reverse proxy frontend application", "description": "Reverse proxy frontend application",
"author": { "author": {

View File

@ -110,6 +110,17 @@
"CertificateChainErrors": "Bypass certificate chain errors", "CertificateChainErrors": "Bypass certificate chain errors",
"CertificateChainErrorsDescription": "With this option is enabled, the reverse proxy will not validate the SSL certificate chain of the upstream server. This is useful when the upstream server's certificate chain is incomplete or invalid." "CertificateChainErrorsDescription": "With this option is enabled, the reverse proxy will not validate the SSL certificate chain of the upstream server. This is useful when the upstream server's certificate chain is incomplete or invalid."
} }
},
"IpFiltering": {
"Label": "IP filtering",
"Tooltip": "IPFiltering is used to filter the IP addresses that can access the forward. It can be used to allow or deny access to specific IP addresses or ranges of IP addresses.",
"Mode": "Mode",
"RulesCount": "Rules count",
"Rules": {
"Title": "Rules",
"IpAddress": "IP address",
"Description": "Description"
}
} }
} }
}, },

View File

@ -101,6 +101,17 @@
"CertificateChainErrors": "Ocolește erorile lanțului de certificate", "CertificateChainErrors": "Ocolește erorile lanțului de certificate",
"CertificateChainErrorsDescription": "Cu această opțiune este activată, proxy-ul nu va valida lanțul de certificate SSL al serverului din amonte. Acest lucru este util atunci când lanțul de certificate al serverului din amonte este incomplet sau invalid." "CertificateChainErrorsDescription": "Cu această opțiune este activată, proxy-ul nu va valida lanțul de certificate SSL al serverului din amonte. Acest lucru este util atunci când lanțul de certificate al serverului din amonte este incomplet sau invalid."
} }
},
"IpFiltering": {
"Label": "Filtrare IP",
"Tooltip": "Filtrarea IP este utilizată pentru a filtra adresele IP care pot accesa redirecționarea. Poate fi utilizată pentru a permite sau a refuza accesul la anumite adrese IP sau intervale de adrese IP.",
"Mode": "Mod",
"RulesCount": "Număr reguli",
"Rules": {
"Title": "Reguli",
"IpAddress": "Adresă IP",
"Description": "Descriere"
}
} }
} }
}, },

View File

@ -7,6 +7,7 @@ import KeyOverwriteCard from "./keyOverwrite/KeyOverwriteCard";
import SpecificHeadersCard from "./specificHeaders/SpecificHeadersCard"; import SpecificHeadersCard from "./specificHeaders/SpecificHeadersCard";
import UpstreamSchemeCard from "./upstreamScheme/UpstreamSchemeCard"; import UpstreamSchemeCard from "./upstreamScheme/UpstreamSchemeCard";
import SslPolicyCard from "./sslPolicy/SslPolicyCard"; import SslPolicyCard from "./sslPolicy/SslPolicyCard";
import IpFilteringCard from "./ipFiltering/IpFilteringCard";
const ForwardOptionsAdvancedComponent = ({ options }) => { const ForwardOptionsAdvancedComponent = ({ options }) => {
return ( return (
@ -53,6 +54,12 @@ const ForwardOptionsAdvancedComponent = ({ options }) => {
<br /> <br />
</> </>
)} )}
{options.ipFiltering && (
<>
<IpFilteringCard data={options.ipFiltering} />
<br />
</>
)}
</> </>
); );
}; };

View File

@ -0,0 +1,26 @@
import React from "react";
import PropTypes from "prop-types";
import ExpandableCard from "../../../../../../components/common/ExpandableCard";
import { useTranslation } from "react-i18next";
import BlockIcon from "@material-ui/icons/Block";
import IpFilteringSummary from "./IpFilteringSummary";
const IpFilteringCard = ({ data }) => {
const { t } = useTranslation();
return (
<ExpandableCard
Icon={<BlockIcon />}
title={t("Forward.Options.IpFiltering.Label")}
subtitle={t("Forward.Options.IpFiltering.Tooltip")}
expandable={false}
Summary={<IpFilteringSummary data={data} />}
/>
);
};
IpFilteringCard.propTypes = {
data: PropTypes.object.isRequired
};
export default IpFilteringCard;

View File

@ -0,0 +1,90 @@
import React from "react";
import PropTypes from "prop-types";
import ExpandableCard from "../../../../../../components/common/ExpandableCard";
import { useTranslation } from "react-i18next";
import { List } from "@material-ui/icons";
import { makeStyles } from "@material-ui/core/styles";
import {
Table,
TableBody,
TableContainer,
TableHead,
TableRow,
Paper
} from "@material-ui/core";
import styles from "../../../../../../components/common/styles/tableStyles";
import {
StyledTableCell,
StyledTableRow
} from "../../../../../../components/common/MaterialTable";
import InputIcon from "@material-ui/icons/Input";
import BlockIcon from "@material-ui/icons/Block";
const useStyles = makeStyles(styles);
const IpFilteringRules = ({ mode, rules }) => {
const isAllowedMode = mode === "Allow";
const classes = useStyles();
const { t } = useTranslation();
return (
<ExpandableCard
Icon={<List />}
iconVariant="rounded"
title={t("Forward.Options.IpFiltering.Rules.Title")}
smallHeader
Content={
<TableContainer component={Paper}>
<Table
className={classes.narrowTable}
size="small"
aria-label="customized table"
>
<TableHead>
<TableRow>
<StyledTableCell>
{t("Forward.Options.IpFiltering.Rules.IpAddress")}
</StyledTableCell>
<StyledTableCell>
{t("Forward.Options.IpFiltering.Rules.Description")}
</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
<>
{rules.map(rule => {
return (
<StyledTableRow key={rules.indexOf(rule)}>
<StyledTableCell style={{ width: "20%" }}>
{isAllowedMode ? (
<InputIcon
fontSize="small"
style={{ marginRight: 8, color: "#34964fff" }}
/>
) : (
<BlockIcon
fontSize="small"
style={{ marginRight: 8, color: "#e74c3c" }}
/>
)}
{rule.ipAddress}
</StyledTableCell>
<StyledTableCell>{rule.description}</StyledTableCell>
</StyledTableRow>
);
})}
</>
</TableBody>
</Table>
</TableContainer>
}
/>
);
};
IpFilteringRules.propTypes = {
mode: PropTypes.string.isRequired,
rules: PropTypes.array.isRequired
};
export default IpFilteringRules;

View File

@ -0,0 +1,41 @@
import React from "react";
import PropTypes from "prop-types";
import { Grid } from "@material-ui/core";
import { useTranslation } from "react-i18next";
import ActiveIcon from "../../../../../../components/common/ActiveIcon";
import { makeStyles } from "@material-ui/core/styles";
import styles from "../../../../../../components/common/styles/gridStyles";
import IpFilteringRules from "./IpFilteringRules";
const useStyles = makeStyles(styles);
const IpFilteringSummary = ({ data }) => {
const { t } = useTranslation();
const classes = useStyles();
return (
<>
<Grid container>
<Grid item xs={6} sm={3} md={3}>
{`${t("General.Enabled")}: `}
<ActiveIcon active={data.on} />
</Grid>
<Grid item xs={6} sm={3} md={3}>
{`${t("Forward.Options.IpFiltering.Mode")}: `}
<span className={classes.value}>{data.mode}</span>
</Grid>
<Grid item xs={6} sm={3} md={3}>
{`${t("Forward.Options.IpFiltering.RulesCount")}: `}
<span className={classes.value}>{data.rules?.length || 0}</span>
</Grid>
</Grid>
<br />
<IpFilteringRules mode={data.mode} rules={data.rules} />
</>
);
};
IpFilteringSummary.propTypes = {
data: PropTypes.object.isRequired
};
export default IpFilteringSummary;

View File

@ -63,6 +63,11 @@ const ForwardOptionsComponent = ({ title, options }) => {
label: "Forward.Options.SslPolicy.Label", label: "Forward.Options.SslPolicy.Label",
tooltip: "Forward.Options.SslPolicy.Tooltip", tooltip: "Forward.Options.SslPolicy.Tooltip",
active: !!options.sslPolicy active: !!options.sslPolicy
},
{
label: "Forward.Options.IpFiltering.Label",
tooltip: "Forward.Options.IpFiltering.Tooltip",
active: !!options.ipFiltering
} }
], ],
[options] [options]