mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-08-06 16:27:41 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user