Add public IPv4 and IPv6 addresses to the dashboard

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/62c62847-d567-4968-a098-0a0bd2b3fa96.jpg
This commit is contained in:
alphaeusmote
2025-04-10 00:39:50 +00:00
4 changed files with 155 additions and 34 deletions
@@ -33,6 +33,11 @@ export function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
href: "/",
icon: <LayoutDashboard className="w-5 h-5 mr-3" />,
},
{
title: "Organizations",
href: "/organizations",
icon: <Users className="w-5 h-5 mr-3" />,
},
{
title: "Domains",
href: "/domains",
@@ -127,9 +132,9 @@ export function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
<nav className="flex-1 py-4 px-2 space-y-1 overflow-y-auto">
{navItems.map((item) => (
<Link key={item.href} href={item.href}>
<a
<div
className={cn(
"flex items-center px-3 py-2 text-sm font-medium rounded-md",
"flex items-center px-3 py-2 text-sm font-medium rounded-md cursor-pointer",
location === item.href
? "bg-primary text-primary-foreground"
: "hover:bg-accent hover:text-accent-foreground"
@@ -138,7 +143,7 @@ export function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
>
{item.icon}
{item.title}
</a>
</div>
</Link>
))}
@@ -149,9 +154,9 @@ export function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
</div>
{filteredAdminItems.map((item) => (
<Link key={item.href} href={item.href}>
<a
<div
className={cn(
"mt-1 flex items-center px-3 py-2 text-sm font-medium rounded-md",
"mt-1 flex items-center px-3 py-2 text-sm font-medium rounded-md cursor-pointer",
location === item.href
? "bg-primary text-primary-foreground"
: "hover:bg-accent hover:text-accent-foreground"
@@ -160,7 +165,7 @@ export function MobileSidebar({ isOpen, onClose }: MobileSidebarProps) {
>
{item.icon}
{item.title}
</a>
</div>
</Link>
))}
</div>
+16 -28
View File
@@ -1,35 +1,23 @@
import { Button } from "@/components/ui/button";
import { ChevronLeft } from "lucide-react";
import { useLocation } from "wouter";
import { Link } from 'wouter';
import { Button } from '@/components/ui/button';
import { ArrowLeft } from 'lucide-react';
interface BackButtonProps {
to?: string;
fallbackPath?: string;
className?: string;
href: string;
label?: string;
}
export function BackButton({ to, fallbackPath = "/", className = "" }: BackButtonProps) {
const [_, setLocation] = useLocation();
const handleBack = () => {
if (to) {
setLocation(to);
} else if (window.history.length > 2) {
window.history.back();
} else {
setLocation(fallbackPath);
}
};
export function BackButton({ href, label = 'Back' }: BackButtonProps) {
return (
<Button
variant="ghost"
size="sm"
onClick={handleBack}
className={`flex items-center gap-1 px-2 ${className}`}
>
<ChevronLeft className="h-4 w-4" />
<span>Back</span>
</Button>
<Link href={href}>
<Button
variant="ghost"
size="sm"
className="mb-4 gap-1 pl-0 hover:pl-1 transition-all"
>
<ArrowLeft className="h-4 w-4" />
{label}
</Button>
</Link>
);
}
@@ -0,0 +1,122 @@
import { useState, useEffect } from 'react';
import { Card, CardContent, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { Network, Info, Copy } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { toast } from '@/hooks/use-toast';
export function PublicIpCard() {
const [ipv4, setIpv4] = useState<string | null>(null);
const [ipv6, setIpv6] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
const fetchPublicIp = async () => {
try {
setLoading(true);
// Fetch IPv4
const ipv4Response = await fetch('https://api.ipify.org?format=json')
.then(res => res.json())
.catch(() => ({ ip: null }));
// Fetch IPv6 (might not be available for all users)
const ipv6Response = await fetch('https://api64.ipify.org?format=json')
.then(res => res.json())
.catch(() => ({ ip: null }));
setIpv4(ipv4Response.ip);
setIpv6(ipv6Response.ip);
setError(false);
} catch (err) {
console.error('Error fetching public IP:', err);
setError(true);
} finally {
setLoading(false);
}
};
fetchPublicIp();
// Refresh every 5 minutes
const interval = setInterval(fetchPublicIp, 300000);
return () => clearInterval(interval);
}, []);
const copyToClipboard = (text: string | null) => {
if (text) {
navigator.clipboard.writeText(text);
toast({
title: 'Copied to clipboard',
description: text,
});
}
};
return (
<Card>
<CardContent className="pt-6">
<div className="flex items-center mb-4">
<Network className="h-5 w-5 text-primary mr-2" />
<CardTitle className="text-lg font-semibold">Public IP Addresses</CardTitle>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 ml-1">
<Info className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p>Your current public IP addresses as seen from the internet.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="space-y-3">
<div>
<div className="flex items-center justify-between mb-1">
<span className="text-sm text-muted-foreground">IPv4 Address</span>
{ipv4 && (
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={() => copyToClipboard(ipv4)}
>
<Copy className="h-3.5 w-3.5" />
</Button>
)}
</div>
<div className="bg-card border rounded-md p-3 text-sm font-mono">
{loading ? 'Loading...' : error ? 'Error fetching IP' : ipv4 || 'N/A'}
</div>
</div>
<Separator />
<div>
<div className="flex items-center justify-between mb-1">
<span className="text-sm text-muted-foreground">IPv6 Address</span>
{ipv6 && (
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={() => copyToClipboard(ipv6)}
>
<Copy className="h-3.5 w-3.5" />
</Button>
)}
</div>
<div className="bg-card border rounded-md p-3 text-sm font-mono overflow-x-auto">
{loading ? 'Loading...' : error ? 'Error fetching IP' : ipv6 || 'N/A'}
</div>
</div>
</div>
</CardContent>
</Card>
);
}
+6
View File
@@ -4,6 +4,7 @@ import { MainLayout } from "@/components/layouts/main-layout";
import { DomainStatusCard } from "@/components/domain/domain-status-card";
import { DomainTable } from "@/components/domain/domain-table";
import { RecentActivity } from "@/components/activity/recent-activity";
import { PublicIpCard } from "@/components/shared/public-ip-card";
import { DnsUpdateChart } from "@/components/charts/dns-update-chart";
import { ProviderDistributionChart } from "@/components/charts/provider-distribution-chart";
import { RecordTypeChart } from "@/components/charts/record-type-chart";
@@ -77,6 +78,11 @@ export default function DashboardPage() {
/>
</div>
{/* Public IP Information */}
<div className="mb-8">
<PublicIpCard />
</div>
{/* Recent Activity and DNS Update Chart */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<div className="lg:col-span-2">