Upload folder using huggingface_hub
Browse files- client/src/pages/Profile.tsx +2 -95
client/src/pages/Profile.tsx
CHANGED
|
@@ -479,101 +479,8 @@ const Manage: React.FC = () => {
|
|
| 479 |
</div>
|
| 480 |
</div>
|
| 481 |
|
| 482 |
-
{/* Login Summary */}
|
| 483 |
-
|
| 484 |
-
<div className="flex items-center justify-between mb-4">
|
| 485 |
-
<h2 className="text-lg font-medium text-gray-900">Login Summary</h2>
|
| 486 |
-
<div className="space-x-2 flex items-center">
|
| 487 |
-
<select
|
| 488 |
-
value={summaryRole}
|
| 489 |
-
onChange={async (e) => {
|
| 490 |
-
const role = e.target.value;
|
| 491 |
-
setSummaryRole(role);
|
| 492 |
-
// Immediate local filter from the best available source
|
| 493 |
-
const base = (allLoginSummary && allLoginSummary.length > 0) ? allLoginSummary : loginSummary;
|
| 494 |
-
const immediate = role === 'all' ? base : base.filter((s: any) => s.role === role);
|
| 495 |
-
setLoginSummary(immediate);
|
| 496 |
-
// Fire-and-forget refresh to sync with server
|
| 497 |
-
fetchLoginSummary(summaryRange, role);
|
| 498 |
-
}}
|
| 499 |
-
className="px-2 py-1.5 text-sm border rounded-md"
|
| 500 |
-
>
|
| 501 |
-
<option value="all">All roles</option>
|
| 502 |
-
<option value="admin">Admin</option>
|
| 503 |
-
<option value="student">Student</option>
|
| 504 |
-
<option value="visitor">Visitor</option>
|
| 505 |
-
</select>
|
| 506 |
-
<button
|
| 507 |
-
onClick={async () => {
|
| 508 |
-
await fetchLoginSummary(summaryRange, summaryRole);
|
| 509 |
-
}}
|
| 510 |
-
className="bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1.5 rounded-md text-sm"
|
| 511 |
-
>
|
| 512 |
-
Refresh
|
| 513 |
-
</button>
|
| 514 |
-
<button
|
| 515 |
-
onClick={async () => {
|
| 516 |
-
try {
|
| 517 |
-
const resp = await api.get(`/api/auth/admin/login-summary?format=csv&sinceMs=${summaryRange}&role=${summaryRole}` as any, { responseType: 'blob' } as any);
|
| 518 |
-
const blob = new Blob([resp.data], { type: 'text/csv' });
|
| 519 |
-
const url = window.URL.createObjectURL(blob);
|
| 520 |
-
const link = document.createElement('a');
|
| 521 |
-
link.href = url;
|
| 522 |
-
link.download = 'login-summary.csv';
|
| 523 |
-
document.body.appendChild(link);
|
| 524 |
-
link.click();
|
| 525 |
-
document.body.removeChild(link);
|
| 526 |
-
window.URL.revokeObjectURL(url);
|
| 527 |
-
} catch {}
|
| 528 |
-
}}
|
| 529 |
-
className="bg-gray-600 hover:bg-gray-700 text-white px-3 py-1.5 rounded-md text-sm"
|
| 530 |
-
>
|
| 531 |
-
Download CSV
|
| 532 |
-
</button>
|
| 533 |
-
</div>
|
| 534 |
-
</div>
|
| 535 |
-
{loadingSummary ? (
|
| 536 |
-
<div className="text-gray-500">Loading...</div>
|
| 537 |
-
) : (
|
| 538 |
-
<div className="overflow-x-auto">
|
| 539 |
-
<table className="min-w-full text-sm">
|
| 540 |
-
<thead>
|
| 541 |
-
<tr className="text-left text-gray-600">
|
| 542 |
-
<th className="py-2 pr-4">Email</th>
|
| 543 |
-
<th className="py-2 pr-4">Role</th>
|
| 544 |
-
<th className="py-2 pr-4">Start</th>
|
| 545 |
-
<th className="py-2 pr-4">Last Seen</th>
|
| 546 |
-
<th className="py-2 pr-4">Duration</th>
|
| 547 |
-
</tr>
|
| 548 |
-
</thead>
|
| 549 |
-
<tbody>
|
| 550 |
-
{(showAllSummary ? loginSummary : loginSummary.slice(0, 5)).map((s, idx) => (
|
| 551 |
-
<tr key={idx} className="border-t">
|
| 552 |
-
<td className="py-2 pr-4 whitespace-nowrap">{s.email}</td>
|
| 553 |
-
<td className="py-2 pr-4">{s.role}</td>
|
| 554 |
-
<td className="py-2 pr-4 whitespace-nowrap">{new Date(s.startAt).toLocaleString()}</td>
|
| 555 |
-
<td className="py-2 pr-4 whitespace-nowrap">{new Date(s.lastSeen).toLocaleString()}</td>
|
| 556 |
-
<td className="py-2 pr-4">{Math.round((s.durationMs||0)/60000)} min</td>
|
| 557 |
-
</tr>
|
| 558 |
-
))}
|
| 559 |
-
{loginSummary.length === 0 && (
|
| 560 |
-
<tr>
|
| 561 |
-
<td colSpan={5} className="py-2 text-gray-500">No sessions in the selected period.</td>
|
| 562 |
-
</tr>
|
| 563 |
-
)}
|
| 564 |
-
</tbody>
|
| 565 |
-
</table>
|
| 566 |
-
{loginSummary.length > 5 && (
|
| 567 |
-
<div className="mt-2">
|
| 568 |
-
<button onClick={() => setShowAllSummary(v => !v)} className="text-sm text-indigo-700 underline">
|
| 569 |
-
{showAllSummary ? 'Show first 5' : `Show all (${loginSummary.length})`}
|
| 570 |
-
</button>
|
| 571 |
-
</div>
|
| 572 |
-
)}
|
| 573 |
-
</div>
|
| 574 |
-
)}
|
| 575 |
-
</div>
|
| 576 |
-
|
| 577 |
{/* Admin Management Sections */}
|
| 578 |
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
| 579 |
{/* User Management */}
|
|
|
|
| 479 |
</div>
|
| 480 |
</div>
|
| 481 |
|
| 482 |
+
{/* Login Summary removed as requested */}
|
| 483 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
{/* Admin Management Sections */}
|
| 485 |
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
| 486 |
{/* User Management */}
|