first commit
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Sync to CNB / sync (push) Has been cancelled
Delete old workflow runs / del_runs (push) Has been cancelled
Upstream Sync / Sync latest commits from upstream repo (push) Has been cancelled

This commit is contained in:
2026-01-30 16:57:44 +08:00
commit 3d175d75af
119 changed files with 35834 additions and 0 deletions

201
app/ip-radix/page.tsx Normal file
View File

@@ -0,0 +1,201 @@
"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Network, Copy } from "lucide-react";
import { toast } from "sonner";
// --- Helper Functions ---
// IPv4
const ipv4ToInt = (ip: string) =>
ip.split('.').reduce((acc, octet) => (acc << 8n) + BigInt(parseInt(octet, 10)), 0n);
const intToIpv4 = (int: bigint) =>
[(int >> 24n) & 0xffn, (int >> 16n) & 0xffn, (int >> 8n) & 0xffn, int & 0xffn].join('.');
// IPv6 (simplified)
const ipv6ToBigInt = (ip: string) => {
// Expansion of ::
if(ip.includes('::')) {
const parts = ip.split('::');
const left = parts[0].split(':').filter(Boolean);
const right = parts[1].split(':').filter(Boolean);
const missing = 8 - (left.length + right.length);
const expanded = [...left, ...Array(missing).fill('0'), ...right];
ip = expanded.join(':');
}
const parts = ip.split(':');
let result = 0n;
for (const part of parts) {
result = (result << 16n) + BigInt(parseInt(part || '0', 16));
}
return result;
};
const bigIntToIpv6 = (int: bigint) => {
let parts = [];
for (let i = 0; i < 8; i++) {
parts.unshift((int & 0xffffn).toString(16));
int >>= 16n;
}
// Simple compression (not full RFC 5952 compliant but good enough for display)
return parts.join(':').replace(/(^|:)0(:0)*:0(:|$)/, '::');
};
export default function IpRadixPage() {
const [ip, setIp] = useState("192.168.1.1");
const [type, setType] = useState<"IPv4" | "IPv6">("IPv4");
const [decimal, setDecimal] = useState("");
const [hex, setHex] = useState("");
const [binary, setBinary] = useState("");
const [errorV4, setErrorV4] = useState("");
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
toast.success("已复制");
} catch {
toast.error("复制失败");
}
};
useEffect(() => {
if (!ip.trim()) return;
try {
let val: bigint;
if (ip.includes(':')) {
setType("IPv6");
val = ipv6ToBigInt(ip);
} else {
setType("IPv4");
// Basic IPv4 validation
if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) {
if (/^\d+$/.test(ip)) {
// Treat as decimal input
val = BigInt(ip);
} else {
throw new Error("Invalid Format");
}
} else {
val = ipv4ToInt(ip);
}
}
setErrorV4("");
setDecimal(val.toString());
setHex("0x" + val.toString(16).toUpperCase());
setBinary(val.toString(2));
} catch {
setDecimal("---");
setHex("---");
setBinary("---");
setErrorV4("无效的 IP 地址格式");
}
}, [ip]);
return (
<div className="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="flex items-center space-x-4 border-b pb-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-linear-to-br from-cyan-500 to-blue-600 shadow-lg">
<Network className="h-6 w-6 text-white" />
</div>
<div>
<h1 className="text-2xl font-bold tracking-tight">IP </h1>
<p className="text-muted-foreground">IPv4/IPv6 </p>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<Card className="lg:col-span-2">
<CardHeader>
<CardTitle> IP </CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<Label>IPv4, IPv6 </Label>
<div className="flex gap-2">
<Input
value={ip}
onChange={(e) => setIp(e.target.value)}
placeholder="例如: 192.168.1.1 或 2001:db8::1"
className="font-mono text-lg"
/>
</div>
{errorV4 && <p className="text-sm text-destructive">{errorV4}</p>}
<p className="text-xs text-muted-foreground">
IPv4 / IPv6
</p>
</div>
</CardContent>
</Card>
{/* Results */}
<Card>
<CardHeader>
<CardTitle className="text-base text-muted-foreground"> ({type})</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="p-4 bg-muted/40 rounded-lg font-mono text-lg break-all flex justify-between items-start">
<span>{type === 'IPv4' ? intToIpv4(BigInt(decimal || 0)) : bigIntToIpv6(BigInt(decimal || 0))}</span>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0 ml-2" onClick={() => copyToClipboard(type === 'IPv4' ? intToIpv4(BigInt(decimal || 0)) : bigIntToIpv6(BigInt(decimal || 0)))}>
<Copy className="h-3 w-3" />
</Button>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base text-muted-foreground"> (Decimal)</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="p-4 bg-muted/40 rounded-lg font-mono text-lg break-all flex justify-between items-start">
<span>{decimal}</span>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0 ml-2" onClick={() => copyToClipboard(decimal)}>
<Copy className="h-3 w-3" />
</Button>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base text-muted-foreground"> (Hex)</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="p-4 bg-muted/40 rounded-lg font-mono text-lg break-all flex justify-between items-start">
<span>{hex}</span>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0 ml-2" onClick={() => copyToClipboard(hex)}>
<Copy className="h-3 w-3" />
</Button>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base text-muted-foreground"> (Binary)</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="p-4 bg-muted/40 rounded-lg font-mono text-sm break-all flex justify-between items-start max-h-40 overflow-y-auto">
<span>{binary}</span>
<Button variant="ghost" size="icon" className="h-6 w-6 shrink-0 ml-2" onClick={() => copyToClipboard(binary)}>
<Copy className="h-3 w-3" />
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
);
}