diff --git a/ui/src/pages/Devices.tsx b/ui/src/pages/Devices.tsx
index b5370b74f..fc06fa1fb 100644
--- a/ui/src/pages/Devices.tsx
+++ b/ui/src/pages/Devices.tsx
@@ -15,7 +15,9 @@ import {
} from "react-admin";
import OnlineIcon from "@mui/icons-material/CheckCircleOutline";
import HighlightOffIcon from "@mui/icons-material/HighlightOff";
+import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import { useTheme } from "@mui/material/styles";
+import { Tooltip } from "@mui/material";
const DeviceListBulkActions = () => (
@@ -131,56 +133,16 @@ const DeviceShowLayout: FC = () => {
reference="users"
link="show"
/>
-
+
+
+
+
+
+
);
};
-
-//
-// export const DeviceShow: FC = () => (
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//
-// );
diff --git a/ui/src/pages/SecurityGroups/SecurityGroupEditRules.tsx b/ui/src/pages/SecurityGroups/SecurityGroupEditRules.tsx
index 72bafd0ea..2df029ac0 100644
--- a/ui/src/pages/SecurityGroups/SecurityGroupEditRules.tsx
+++ b/ui/src/pages/SecurityGroups/SecurityGroupEditRules.tsx
@@ -1,3 +1,4 @@
+import React, { useEffect, useState } from "react";
import {
Button,
ButtonGroup,
@@ -11,19 +12,17 @@ import {
MenuItem,
Tooltip,
} from "@mui/material";
+import * as Mui from "@mui/material";
+import Autocomplete from "@mui/material/Autocomplete";
+import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import {
- IpProtocol,
+ ProtocolAliases,
SecurityGroup,
SecurityRule,
UpdateSecurityGroup,
} from "./SecurityGroupStructs";
import { fetchJson, backend } from "../../common/Api";
-import React, { useEffect, useState } from "react";
import Notifications from "../../common/Notifications";
-import * as Mui from "@mui/material";
-import Autocomplete from "@mui/material/Autocomplete";
-import { validateProtocolAndIpRange } from "../../common/IpHelpers";
-import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
interface EditRulesProps {
groupName: string;
@@ -90,6 +89,19 @@ const EditRules: React.FC
= ({
"success" | "error" | "info" | null
>(null);
+ const [ipRangeInputValue, setIpRangeInputValue] = useState("");
+
+ useEffect(() => {
+ // Initialize tempPortValues whenever secRule changes
+ const initialPortValues = secRule.map(
+ (rule) =>
+ `${rule.from_port}${
+ rule.to_port !== rule.from_port ? `-${rule.to_port}` : ""
+ }`,
+ );
+ setTempPortValues(initialPortValues);
+ }, [secRule]);
+
// Message box errors for table edits
const [fieldErrors, setFieldErrors] = useState<
{ from_port?: string; to_port?: string }[]
@@ -179,24 +191,6 @@ const EditRules: React.FC = ({
};
// TODO: Implement for v4/v6 sanity checks against protocol and family. Tracked in issue #1445
- const handleIpRangeChange = (newValue: string[], index: number) => {
- const updatedRule = {
- ...secRule[index],
- ip_ranges: newValue,
- };
-
- try {
- validateProtocolAndIpRange(updatedRule.ip_protocol, newValue);
- } catch (error: any) {
- if (error instanceof Error) {
- setNotificationType("error");
- setNotificationMessage(error.message);
- }
- return; // Skip updating the rule if validation fails
- }
-
- onRuleChange(index, updatedRule);
- };
const validatePortRange = (
port: number,
@@ -209,79 +203,49 @@ const EditRules: React.FC = ({
const [tempPortValues, setTempPortValues] = useState([]);
- const handleProtocolChange = (
- e: Mui.SelectChangeEvent,
- index: number,
- ) => {
- const aliasProtocol = e.target.value as IpProtocol;
- let updatedRule = { ...secRule[index], ip_protocol: aliasProtocol };
- switch (aliasProtocol) {
- case "SSH":
- updatedRule = {
- ...updatedRule,
- from_port: 22,
- to_port: 22,
- ip_protocol: "tcp",
- };
- break;
- case "HTTP":
- updatedRule = {
- ...updatedRule,
- from_port: 80,
- to_port: 80,
- ip_protocol: "tcp",
- };
- break;
- case "HTTPS":
- updatedRule = {
- ...updatedRule,
- from_port: 443,
- to_port: 443,
- ip_protocol: "tcp",
- };
- break;
- case "PostgreSQL":
- updatedRule = {
- ...updatedRule,
- from_port: 5432,
- to_port: 5432,
- ip_protocol: "tcp",
- };
- break;
- case "MySQL":
- updatedRule = {
- ...updatedRule,
- from_port: 3306,
- to_port: 3306,
- ip_protocol: "tcp",
- };
- break;
- case "SMB":
- updatedRule = {
- ...updatedRule,
- from_port: 445,
- to_port: 445,
- ip_protocol: "tcp",
- };
- break;
- case "icmpv4":
- case "icmpv6":
- updatedRule = { ...updatedRule, from_port: 0, to_port: 0 };
- break;
- case "icmp": // ALL ICMP
- updatedRule = {
- ...updatedRule,
- from_port: 0,
- to_port: 0,
- ip_ranges: [],
- };
- break;
+ const handleProtocolChange = (e: Mui.SelectChangeEvent, index: number) => {
+ const selectedProtocol = e.target.value;
+ let updatedRule = { ...secRule[index], ip_protocol: selectedProtocol };
+
+ if (ProtocolAliases[selectedProtocol]) {
+ const { port, type } = ProtocolAliases[selectedProtocol];
+ updatedRule = {
+ ...updatedRule,
+ from_port: port,
+ to_port: port,
+ ip_protocol: type,
+ };
+ } else if (["icmpv4", "icmpv6"].includes(selectedProtocol)) {
+ updatedRule = { ...updatedRule, from_port: 0, to_port: 0 };
}
onRuleChange(index, updatedRule);
};
- const isPredefinedRule = (protocol: IpProtocol): boolean => {
+ const getProtocolNameByPorts = (
+ from_port: number,
+ to_port: number,
+ ip_protocol: string,
+ ): string | null => {
+ console.debug(
+ `Looking for protocol with from_port: ${from_port}, to_port: ${to_port}, ip_protocol: ${ip_protocol}`,
+ );
+ // Loop through the ProtocolAliases to find a match, either a proto with a defined set
+ // of ports e.g., SSH or a protocol such as TCP with a port of 0-0, should always match
+ for (const [key, value] of Object.entries(ProtocolAliases)) {
+ if (
+ value.port === from_port &&
+ value.port === to_port &&
+ value.type === ip_protocol
+ ) {
+ console.log(`Found match: ${key}`);
+ return key;
+ }
+ }
+ return null;
+ };
+
+ const isPredefinedRule = (protocol: string): boolean => {
return [
"SSH",
"HTTP",
@@ -293,26 +257,12 @@ const EditRules: React.FC = ({
"icmpv4",
"icmpv6",
"ip",
+ "TCP",
+ "UDP",
].includes(protocol);
};
- // map predefined protocol names to their corresponding port ranges for display render only, rules get sent with ip_protocol:tcp
- const getPredefinedProtocolName = (
- from_port: number,
- to_port: number,
- ): IpProtocol | undefined => {
- const predefinedProtocols: { [key: string]: IpProtocol } = {
- "22-22": "SSH",
- "80-80": "HTTP",
- "443-443": "HTTPS",
- "5432-5432": "PostgreSQL",
- "3306-3306": "MySQL",
- "445-445": "SMB",
- };
- return predefinedProtocols[`${from_port}-${to_port}`];
- };
-
- const isUnmodifiableIpRange = (protocol: IpProtocol): boolean => {
+ const isUnmodifiableIpRange = (protocol: string): boolean => {
return ["ip", "icmp"].includes(protocol);
};
@@ -341,17 +291,36 @@ const EditRules: React.FC = ({
-
+
IP Protocol
-
- Port Range
-
-
- IP Ranges
+
+
+ Port Range
+
+
+
+
- Defined IP Ranges
+
+ IP Ranges
+
+
+
+
Action
@@ -372,12 +341,13 @@ const EditRules: React.FC = ({
>
- {/* From Port - Starting Port Range */}
+ {/* Port Range Column*/}
{
const newTempPortValues = [...tempPortValues];
newTempPortValues[index] = `${rule.from_port}${
@@ -463,85 +420,87 @@ const EditRules: React.FC = ({
disabled={isPredefinedRule(rule.ip_protocol)}
/>
- {/*User Defined IP Ranges*/}
-
- {
- const newValue = e.target.value
- .split(",")
- .map((item) => item.trim());
- const updatedRule = { ...rule, ip_ranges: newValue };
- onRuleChange(index, updatedRule);
- }}
- style={{
- backgroundColor: isUnmodifiableIpRange(rule.ip_protocol)
- ? "#E8F4F9"
- : "transparent",
- }}
- disabled={isUnmodifiableIpRange(rule.ip_protocol)}
- />
-
- {/*PreDefined IP Ranges*/}
+ {/*IP Ranges Column*/}
-
- typeof option === "string" ? option : option.title
- }
- getOptionDisabled={(option) =>
- isUnmodifiableIpRange(rule.ip_protocol)
- }
- value={rule.ip_ranges || []}
- onChange={(_, newValue) => {
- const updatedRule = {
- ...rule,
- ip_ranges: newValue.map((item) =>
- typeof item === "string" ? item : item.value,
- ),
- };
- onRuleChange(index, updatedRule);
- }}
- renderInput={(params) => (
-
- )}
- />
+
+ {
+ console.debug(
+ "onInputChange - newInputValue:",
+ newInputValue,
+ );
+ setIpRangeInputValue(newInputValue);
+ }}
+ disabled={isUnmodifiableIpRange(rule.ip_protocol)}
+ options={[
+ "::/0",
+ "0.0.0.0/0",
+ {
+ title: "Organization IPv4",
+ value: "100.64.0.0/10",
+ },
+ { title: "Organization IPv6", value: "0200::/8" },
+ ]}
+ getOptionLabel={(option) =>
+ typeof option === "string" ? option : option.title
+ }
+ getOptionDisabled={(option) =>
+ isUnmodifiableIpRange(rule.ip_protocol)
+ }
+ value={rule.ip_ranges || []}
+ freeSolo
+ autoHighlight // Highlight the first match as the user types
+ onChange={(_, newValue) => {
+ console.debug("onChange - newValue:", newValue);
+ // If the protocol is unmodifiable, clear the IP ranges for this rule
+ if (isUnmodifiableIpRange(rule.ip_protocol)) {
+ const updatedRule = { ...rule, ip_ranges: [] };
+ onRuleChange(index, updatedRule);
+ setIpRangeInputValue("");
+ return;
+ }
+ // Otherwise, update the IP ranges as normal
+ const updatedIpRanges = newValue.map((item) =>
+ typeof item === "string" || typeof item === "number"
+ ? item
+ : item.value,
+ );
+ const updatedRule = {
+ ...rule,
+ ip_ranges: updatedIpRanges,
+ };
+ onRuleChange(index, updatedRule);
+ setIpRangeInputValue(""); // Clear the input value
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+
+ {/*Actions Column*/}
= ({
const newRules = [...rules];
if (field === "ip_protocol" && typeof value === "string") {
- newRules[index].ip_protocol = value as IpProtocol;
+ newRules[index].ip_protocol = value;
} else if (
(field === "from_port" || field === "to_port") &&
typeof value === "number"