127 lines
4.8 KiB
TypeScript
127 lines
4.8 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { RefreshCw, Monitor, Globe, Shield } from "lucide-react";
|
||
import { AdminLayout } from "@/components/admin/admin-layout";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { useLanguage } from "@/components/language-provider";
|
||
|
||
interface Session {
|
||
id: string;
|
||
device_name: string;
|
||
ip_address: string;
|
||
user_agent: string;
|
||
session_type: string;
|
||
status: string;
|
||
started_at: string;
|
||
last_activity_at: string;
|
||
expires_at: string;
|
||
revoked_reason: string;
|
||
user?: { email: string; full_name: string };
|
||
}
|
||
|
||
export default function AdminSessionsPage() {
|
||
const { lang } = useLanguage();
|
||
const [sessions, setSessions] = useState<Session[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
useEffect(() => {
|
||
const token = localStorage.getItem("access_token");
|
||
if (!token) {
|
||
window.location.href = "/login?redirect=/admin/sessions";
|
||
return;
|
||
}
|
||
|
||
const fetchData = async () => {
|
||
const baseURL = process.env.NEXT_PUBLIC_API_URL;
|
||
try {
|
||
const res = await fetch(`${baseURL}/sessions/`, {
|
||
headers: { Authorization: `Bearer ${token}` },
|
||
});
|
||
const data = await res.json();
|
||
setSessions(data.results || []);
|
||
} catch (e) {
|
||
console.error(e);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
fetchData();
|
||
}, []);
|
||
|
||
const t = (fa: string, en: string) => (lang === "fa" ? fa : en);
|
||
|
||
if (loading) {
|
||
return (
|
||
<AdminLayout>
|
||
<div className="flex items-center justify-center h-64">
|
||
<RefreshCw className="h-8 w-8 animate-spin text-brand-400" />
|
||
</div>
|
||
</AdminLayout>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<AdminLayout>
|
||
<div className="space-y-6">
|
||
<h2 className="text-xl font-bold text-white">{t("نشست\u200cها", "Sessions")}</h2>
|
||
|
||
<div className="overflow-x-auto rounded-xl border border-white/10">
|
||
<table className="w-full text-sm">
|
||
<thead>
|
||
<tr className="border-b border-white/10 bg-white/[0.02]">
|
||
<th className="px-4 py-3 text-start text-xs font-medium text-slate-400">{t("کاربر", "User")}</th>
|
||
<th className="px-4 py-3 text-start text-xs font-medium text-slate-400">{t("دستگاه", "Device")}</th>
|
||
<th className="px-4 py-3 text-start text-xs font-medium text-slate-400">{t("آی\u200cپی", "IP")}</th>
|
||
<th className="px-4 py-3 text-start text-xs font-medium text-slate-400">{t("نوع", "Type")}</th>
|
||
<th className="px-4 py-3 text-start text-xs font-medium text-slate-400">{t("وضعیت", "Status")}</th>
|
||
<th className="px-4 py-3 text-start text-xs font-medium text-slate-400">{t("اقدامات", "Actions")}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{sessions.map((s) => (
|
||
<tr key={s.id} className="border-b border-white/5">
|
||
<td className="px-4 py-3">
|
||
<div className="flex items-center gap-3">
|
||
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-brand-gradient text-[10px] font-bold text-white">
|
||
{s.user?.full_name?.charAt(0) || s.user?.email?.charAt(0) || "?"}
|
||
</span>
|
||
<span className="text-white">{s.user?.full_name || s.user?.email || "—"}</span>
|
||
</div>
|
||
</td>
|
||
<td className="px-4 py-3 text-slate-300">{s.device_name || "—"}</td>
|
||
<td className="px-4 py-3 text-slate-400 font-mono">{s.ip_address}</td>
|
||
<td className="px-4 py-3">
|
||
<Badge variant="secondary" className="text-xs capitalize">
|
||
{s.session_type}
|
||
</Badge>
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
<Badge variant={s.status === "active" ? "default" : "secondary"} className="text-xs">
|
||
{s.status === "active" ? t("فعال", "Active") : t("غیرفعال", "Inactive")}
|
||
</Badge>
|
||
</td>
|
||
<td className="px-4 py-3">
|
||
{s.status === "active" && (
|
||
<Button variant="secondary" size="sm">
|
||
{t("لغو", "Revoke")}
|
||
</Button>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
{sessions.length === 0 && !loading && (
|
||
<div className="py-12 text-center text-slate-500">
|
||
{t("نشستی یافت نشد", "No sessions found")}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</AdminLayout>
|
||
);
|
||
}
|