gh_UserManager/apps/web/app/admin/sessions/page.tsx
bermooda-company 54d5891edf user
2026-08-23 23:59:14 +03:30

127 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}