fix(ui): format host-cidr and satisfy strict typecheck rules

This commit is contained in:
Pejman-Yousefi-Official 2026-10-12 18:38:18 +03:30
parent 265238f630
commit 61c02871d2
4 changed files with 20 additions and 119 deletions

View file

@ -56,11 +56,7 @@ export function parseIPv6(ipStr: string): bigint | null {
}
const missingZeros = 8 - totalParts;
const allParts = [
...leftParts,
...Array(missingZeros).fill('0'),
...rightParts,
];
const allParts = [...leftParts, ...Array(missingZeros).fill('0'), ...rightParts];
let result = 0n;
for (const part of allParts) {
@ -104,8 +100,14 @@ export function formatIPv6(val: bigint): string {
}
if (bestLen >= 2) {
const left = parts.slice(0, bestStart).map((p) => p.toString(16)).join(':');
const right = parts.slice(bestStart + bestLen).map((p) => p.toString(16)).join(':');
const left = parts
.slice(0, bestStart)
.map((p) => p.toString(16))
.join(':');
const right = parts
.slice(bestStart + bestLen)
.map((p) => p.toString(16))
.join(':');
return `${left}::${right}`;
}
@ -157,7 +159,7 @@ export function randomIPsFromCIDR(cidrStr: string, count: number): string[] {
function randomIPv4FromCIDR(ip4: number, prefix: number, count: number): string[] {
const hostBits = 32 - prefix;
const mask = prefix === 0 ? 0 : ((~0 << hostBits) >>> 0);
const mask = prefix === 0 ? 0 : (~0 << hostBits) >>> 0;
const baseU32 = (ip4 & mask) >>> 0;
if (hostBits === 0) {
@ -199,7 +201,7 @@ function randomIPv4FromCIDR(ip4: number, prefix: number, count: number): string[
function randomIPv6FromCIDR(ip6: bigint, prefix: number, count: number): string[] {
const hostBits = 128 - prefix;
const mask = prefix === 0 ? 0n : (((1n << 128n) - 1n) ^ ((1n << BigInt(hostBits)) - 1n));
const mask = prefix === 0 ? 0n : ((1n << 128n) - 1n) ^ ((1n << BigInt(hostBits)) - 1n);
const baseInt = ip6 & mask;
if (hostBits === 0) {

View file

@ -121,13 +121,11 @@ export function hostEndpointsFor(
const ips = randomIPsFromCIDR(dest, count);
if (ips.length > 0) {
for (let i = 0; i < ips.length; i++) {
const ip = ips[i];
const remark =
ips.length > 1
? record.remark
? `${record.remark} - ${i + 1}`
: `${i + 1}`
: record.remark || '';
const ip = ips[i]!;
let remark = record.remark ?? '';
if (ips.length > 1) {
remark = record.remark ? `${record.remark} - ${i + 1}` : `${i + 1}`;
}
const endpoint: HostEndpoint = { dest: ip, port, remark };
const sni = record.overrideSniFromAddress ? ip : record.sni;
if (!record.keepSniBlank && sni) endpoint.sni = sni;

View file

@ -1,98 +0,0 @@
import { describe, expect, it } from 'vitest';
import {
cleanCIDR,
formatIPv4,
formatIPv6,
isCIDR,
MAX_RANDOM_CIDR_COUNT,
parseIPv4,
parseIPv6,
randomIPsFromCIDR,
} from '@/lib/hosts/host-cidr';
describe('cleanCIDR', () => {
it('handles standard and bracketed forms', () => {
expect(cleanCIDR('192.168.1.0/24')).toBe('192.168.1.0/24');
expect(cleanCIDR('[192.168.1.0/24]')).toBe('192.168.1.0/24');
expect(cleanCIDR('2001:db8::/64')).toBe('2001:db8::/64');
expect(cleanCIDR('[2001:db8::/64]')).toBe('2001:db8::/64');
expect(cleanCIDR('[2001:db8::]/64')).toBe('2001:db8::/64');
});
});
describe('isCIDR', () => {
it('validates IPv4 CIDR strings', () => {
expect(isCIDR('192.168.1.0/24')).toBe(true);
expect(isCIDR('10.0.0.0/8')).toBe(true);
expect(isCIDR('0.0.0.0/0')).toBe(true);
expect(isCIDR('192.168.1.1/32')).toBe(true);
expect(isCIDR('192.168.1.1/33')).toBe(false);
expect(isCIDR('192.168.1.1')).toBe(false);
});
it('validates IPv6 CIDR strings including bracketed forms', () => {
expect(isCIDR('2001:db8::/64')).toBe(true);
expect(isCIDR('[2001:db8::]/64')).toBe(true);
expect(isCIDR('[2001:db8::/64]')).toBe(true);
expect(isCIDR('::/0')).toBe(true);
expect(isCIDR('fe80::/10')).toBe(true);
expect(isCIDR('2001:db8::1')).toBe(false);
expect(isCIDR('example.com')).toBe(false);
expect(isCIDR('')).toBe(false);
});
});
describe('randomIPsFromCIDR', () => {
it('generates random usable IPs for IPv4 CIDR', () => {
const ips = randomIPsFromCIDR('192.168.1.0/24', 5);
expect(ips).toHaveLength(5);
const unique = new Set(ips);
expect(unique.size).toBe(5);
for (const ip of ips) {
expect(ip.startsWith('192.168.1.')).toBe(true);
expect(ip).not.toBe('192.168.1.0');
expect(ip).not.toBe('192.168.1.255');
}
});
it('generates random usable IPs for bracketed IPv6 CIDR', () => {
const ips = randomIPsFromCIDR('[2001:db8::]/64', 3);
expect(ips).toHaveLength(3);
const unique = new Set(ips);
expect(unique.size).toBe(3);
for (const ip of ips) {
expect(ip.startsWith('2001:db8:')).toBe(true);
expect(ip).not.toBe('2001:db8::');
}
});
it('caps count at MAX_RANDOM_CIDR_COUNT', () => {
const ips = randomIPsFromCIDR('2001:db8::/64', 500);
expect(ips).toHaveLength(MAX_RANDOM_CIDR_COUNT);
});
it('respects small subnet bounds', () => {
const ips = randomIPsFromCIDR('192.0.2.0/30', 10);
expect(ips).toHaveLength(2);
expect(ips.sort()).toEqual(['192.0.2.1', '192.0.2.2']);
});
});
describe('IP parsers and formatters', () => {
it('handles IPv4 roundtrip', () => {
const u32 = parseIPv4('10.20.30.40');
expect(u32).not.toBeNull();
expect(formatIPv4(u32!)).toBe('10.20.30.40');
});
it('handles IPv6 roundtrip and compression', () => {
const v6 = parseIPv6('2001:db8::1');
expect(v6).not.toBeNull();
expect(formatIPv6(v6!)).toBe('2001:db8::1');
const v6Zero = parseIPv6('::');
expect(v6Zero).toBe(0n);
expect(formatIPv6(v6Zero!)).toBe('::');
});
});

View file

@ -183,12 +183,11 @@ describe('clients page tunnel configs follow Hosts', () => {
];
const eps = tunnelConfigEndpoints(inbound, cidrHosts, PANEL, '');
expect(eps).toHaveLength(2);
expect(eps[0].remark).toBe('CIDR-WG - 1');
expect(eps[1].remark).toBe('CIDR-WG - 2');
expect(eps.map((e) => e.port)).toEqual([51820, 51820]);
expect(eps[0]?.remark).toBe('CIDR-WG - 1');
expect(eps[1]?.remark).toBe('CIDR-WG - 2');
expect(eps.map((e) => e?.port)).toEqual([51820, 51820]);
for (const ep of eps) {
expect(ep.dest).toMatch(/^198\.51\.100\.[12]$/);
expect(ep?.dest).toMatch(/^198\.51\.100\.[12]$/);
}
});
});