All checks were successful
continuous-integration/drone/push Build is passing
141 lines
3.3 KiB
TypeScript
141 lines
3.3 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
import React from "react";
|
|
import { TextInput } from "./TextInput";
|
|
|
|
export function IPInput(p: {
|
|
label: string;
|
|
editable: boolean;
|
|
value?: string;
|
|
onValueChange?: (newVal: string | undefined) => void;
|
|
version: 4 | 6;
|
|
}): React.ReactElement {
|
|
const { onValueChange, ...props } = p;
|
|
return (
|
|
<TextInput
|
|
onValueChange={(v) => {
|
|
onValueChange?.(p.version === 4 ? sanitizeIpV4(v) : sanitizeIpV6(v));
|
|
}}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function IPInputWithMask(p: {
|
|
label: string;
|
|
editable: boolean;
|
|
ipAndMask?: string;
|
|
ip?: string;
|
|
mask?: number;
|
|
onValueChange?: (ip?: string, mask?: number, ipAndMask?: string) => void;
|
|
version: 4 | 6;
|
|
}): React.ReactElement {
|
|
const showSlash = React.useRef(!!p.mask);
|
|
|
|
const currValue =
|
|
p.ipAndMask ??
|
|
`${p.ip ?? ""}${p.mask || showSlash.current ? "/" : ""}${p.mask ?? ""}`;
|
|
|
|
const { onValueChange, ...props } = p;
|
|
return (
|
|
<TextInput
|
|
onValueChange={(v) => {
|
|
showSlash.current = false;
|
|
if (!v) {
|
|
onValueChange?.(undefined, undefined, undefined);
|
|
return;
|
|
}
|
|
|
|
const split = v.split("/");
|
|
const ip =
|
|
p.version === 4 ? sanitizeIpV4(split[0]) : sanitizeIpV6(split[0]);
|
|
let mask = undefined;
|
|
|
|
if (split.length > 1) {
|
|
showSlash.current = true;
|
|
mask = sanitizeMask(p.version, split[1]);
|
|
}
|
|
|
|
onValueChange?.(
|
|
ip,
|
|
mask,
|
|
mask || showSlash.current ? `${ip}/${mask ?? ""}` : ip
|
|
);
|
|
}}
|
|
value={currValue}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function sanitizeIpV4(s: string | undefined): string | undefined {
|
|
if (s === "" || s === undefined) return s;
|
|
|
|
const split = s.split(".");
|
|
if (split.length > 4) split.splice(4);
|
|
|
|
let needAnotherIteration = false;
|
|
|
|
const res = split
|
|
.map((v) => {
|
|
if (v === "") return "";
|
|
|
|
const num = Number(v);
|
|
if (isNaN(num) || num < 0) return "0";
|
|
if (num > 255) {
|
|
needAnotherIteration = true;
|
|
return v.slice(0, 2) + "." + v.slice(2);
|
|
}
|
|
return num.toString();
|
|
})
|
|
.join(".");
|
|
|
|
return needAnotherIteration ? sanitizeIpV4(res) : res;
|
|
}
|
|
|
|
function sanitizeIpV6(s: string | undefined): string | undefined {
|
|
if (s === "" || s === undefined) return s;
|
|
|
|
const split = s.split(":");
|
|
if (split.length > 8) split.splice(8);
|
|
|
|
let needAnotherIteration = false;
|
|
|
|
let res = split
|
|
.map((e) => {
|
|
if (e === "") return e;
|
|
|
|
const num = parseInt(e, 16);
|
|
if (isNaN(num)) return "0";
|
|
|
|
const s = num.toString(16);
|
|
if (num > 0xffff) {
|
|
needAnotherIteration = true;
|
|
return s.slice(0, 4) + ":" + s.slice(4);
|
|
}
|
|
|
|
return s;
|
|
})
|
|
.join(":");
|
|
|
|
const firstIndex = res.indexOf("::");
|
|
let nextIndex = res.lastIndexOf("::");
|
|
while (nextIndex !== firstIndex) {
|
|
res = res.slice(0, nextIndex) + res.slice(nextIndex + 1);
|
|
nextIndex = res.lastIndexOf("::");
|
|
}
|
|
|
|
return needAnotherIteration ? sanitizeIpV6(res) : res;
|
|
}
|
|
|
|
function sanitizeMask(version: 4 | 6, mask?: string): number | undefined {
|
|
if (!mask) return undefined;
|
|
|
|
const value = Math.floor(Number(mask));
|
|
|
|
if (version === 4) {
|
|
return value < 0 || value > 32 ? 32 : value;
|
|
} else {
|
|
return value < 0 || value > 128 ? 128 : value;
|
|
}
|
|
}
|