This commit is contained in:
parent
cfa7f127c0
commit
1a3edce184
|
@ -5,7 +5,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "NEXT_PUBLIC_API_URL=http://localhost:3001 next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import ExtensionDetails from './extension-details';
|
import ExtensionDetails from './extension-details';
|
||||||
import ApiKeys from './api-keys';
|
import ApiKeys from './api-keys';
|
||||||
import CallHistory from './call-history';
|
import { RegisteredDevices } from './registered-devices';
|
||||||
import { LogOut, User, Phone, Voicemail } from 'lucide-react';
|
import { LogOut, User, Phone, Voicemail } from 'lucide-react';
|
||||||
import { ActiveCalls } from './active-calls';
|
import { ActiveCalls } from './active-calls';
|
||||||
import { SipCredentialsCard } from './SipCredentialsCard';
|
import { SipCredentialsCard } from './SipCredentialsCard';
|
||||||
|
@ -227,7 +227,7 @@ export default function DashboardClient() {
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<main className="lg:col-span-2 space-y-8">
|
<main className="lg:col-span-2 space-y-8">
|
||||||
<ActiveCalls />
|
<ActiveCalls />
|
||||||
<CallHistory />
|
<RegisteredDevices />
|
||||||
<ApiKeys />
|
<ApiKeys />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
114
src/components/dashboard/registered-devices.jsx
Normal file
114
src/components/dashboard/registered-devices.jsx
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { RefreshCw, Smartphone, Server, Wifi } from 'lucide-react';
|
||||||
|
import { TbPlugConnectedX } from 'react-icons/tb';
|
||||||
|
|
||||||
|
function DeviceCard({ device }) {
|
||||||
|
const ping = parseFloat(device.pingMs);
|
||||||
|
let pingColor = 'text-green-400';
|
||||||
|
if (ping > 150) pingColor = 'text-yellow-400';
|
||||||
|
if (ping > 300) pingColor = 'text-red-400';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-gray-900/80 p-4 rounded-lg border border-gray-800 space-y-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Smartphone className="h-5 w-5 text-gray-400" />
|
||||||
|
<span className="font-mono text-sm text-white truncate" title={device.useragent}>{device.useragent}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center text-sm">
|
||||||
|
<div className="flex items-center gap-2 text-gray-400">
|
||||||
|
<Server className="h-4 w-4" />
|
||||||
|
<span>{device.ip}:{device.port}</span>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center gap-2 font-semibold ${pingColor}`}>
|
||||||
|
<Wifi className="h-4 w-4" />
|
||||||
|
<span>{ping.toFixed(2)}ms</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RegisteredDevices() {
|
||||||
|
const { token } = useAuth();
|
||||||
|
const [devices, setDevices] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||||
|
|
||||||
|
const fetchDevices = useCallback(async (isManualRefresh = false) => {
|
||||||
|
if (!token) return;
|
||||||
|
if (isManualRefresh || isInitialLoad) {
|
||||||
|
setLoading(true);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/extensions/me/endpoint`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
setDevices(Array.isArray(data) ? data : []);
|
||||||
|
} else {
|
||||||
|
setDevices([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch registered devices", error);
|
||||||
|
setDevices([]);
|
||||||
|
} finally {
|
||||||
|
if (isInitialLoad || isManualRefresh) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
if (isInitialLoad) {
|
||||||
|
setIsInitialLoad(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [token, isInitialLoad]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (token) {
|
||||||
|
fetchDevices();
|
||||||
|
const interval = setInterval(() => fetchDevices(false), 10000); // Refresh every 10 seconds
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [token, fetchDevices]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-gray-950/50 border-gray-800">
|
||||||
|
<CardHeader className="flex flex-row justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<CardTitle>Registered Devices</CardTitle>
|
||||||
|
<CardDescription>A list of your currently connected devices.</CardDescription>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="icon" onClick={() => fetchDevices(true)} disabled={loading}>
|
||||||
|
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
|
||||||
|
</Button>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{loading ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Skeleton className="h-20 w-full" />
|
||||||
|
<Skeleton className="h-20 w-full" />
|
||||||
|
</div>
|
||||||
|
) : devices.length > 0 ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{devices.map(device => (
|
||||||
|
<DeviceCard key={device.uri} device={device} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-12 text-gray-500 flex flex-col items-center justify-center">
|
||||||
|
<TbPlugConnectedX className="mx-auto h-12 w-12 mb-4 text-gray-600" />
|
||||||
|
<h3 className="text-lg font-semibold text-gray-300">No Registered Devices</h3>
|
||||||
|
<p className="text-sm">When you connect a SIP client, it will appear here.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue