auth / client /src /tabs /AdminTab.jsx
Piyush1225's picture
UPDATE: UI and client assets
808332c
import { useState } from 'react';
import { req, saveToken, ENDPOINTS } from '../api';
import { Card, CardHeader, Callout, FormGroup, ResponseBox } from '../components/ui';
const { AUTH, ADMIN, RISK } = ENDPOINTS;
function EmailStatusBody({ data }) {
if (!data) return <p className="text-muted text-sm">Click Refresh to check email configuration.</p>;
const configured = data.configured;
const fields = data.fields || {};
return (
<div>
<div className="flex items-center gap-2 mb-3">
<span className={`pill ${configured ? 'pill-ok' : 'pill-err'}`}>
{configured ? 'βœ” Configured' : '✘ Not Configured'}
</span>
{data.mail_port && (
<span className="text-sm text-muted">
Port: {data.mail_port} &nbsp;Β·&nbsp; STARTTLS: {data.starttls ? 'Yes' : 'No'}
</span>
)}
</div>
{Object.entries(fields).map(([k, v]) => (
<div className="env-row" key={k}>
<span className="env-key">ADAPTIVEAUTH_{k}</span>
<span className={`pill ${v ? 'pill-ok' : 'pill-err'}`}>{v ? 'Set' : 'Missing'}</span>
</div>
))}
{!configured && data.setup_instructions && (
<Callout type="warn" style={{ marginTop: 12 }}>
<strong>Setup:</strong> Create a <code>.env</code> file with the missing fields above.<br />
<code style={{ background: 'none', border: 'none', padding: 0, color: 'inherit', display: 'block', marginTop: 4 }}>
{data.setup_instructions}
</code>
</Callout>
)}
</div>
);
}
export default function AdminTab({ onTokenSave }) {
const [adminLoginResp, setAdminLoginResp] = useState(null);
const [statsData, setStatsData] = useState(null);
const [emailData, setEmailData] = useState(null);
const [adminUserResp, setAdminUserResp] = useState(null);
const [adminRiskResp, setAdminRiskResp] = useState(null);
const [statsResp, setStatsResp] = useState(null);
const [adminUserId, setAdminUserId] = useState('');
const [loading, setLoading] = useState({});
const setLoad = (k, v) => setLoading(p => ({ ...p, [k]: v }));
const quickAdminLogin = async () => {
setLoad('login', true);
const r = await req(`${AUTH}/login`, 'POST', { email: 'demo.admin@adaptive.demo', password: 'Admin@Demo456!' }, false);
setAdminLoginResp(r);
if (r.ok && r.data?.access_token) { saveToken(r.data.access_token); onTokenSave?.(); }
setLoad('login', false);
};
const loadAdminStats = async () => {
setLoad('stats', true);
const r = await req(`${ADMIN}/statistics`);
if (r.ok) setStatsData(r.data);
setLoad('stats', false);
};
const checkEmailStatus = async () => {
setLoad('email', true);
const r = await req(`${ADMIN}/email-status`);
if (r.ok) setEmailData(r.data);
else setEmailData(null);
setLoad('email', false);
};
const userCall = async (url, method = 'GET') => {
setLoad('user', true);
setAdminUserResp(await req(url, method));
setLoad('user', false);
};
const riskCall = async (url) => {
setLoad('risk', true);
setAdminRiskResp(await req(url));
setLoad('risk', false);
};
const adminBlock = async () => {
if (!adminUserId) { alert('Enter user ID.'); return; }
await userCall(`${ADMIN}/users/${adminUserId}/block`, 'POST');
};
const adminUnblock = async () => {
if (!adminUserId) { alert('Enter user ID.'); return; }
await userCall(`${ADMIN}/users/${adminUserId}/unblock`, 'POST');
};
const STAT_ITEMS = [
{ key: 'total_users', label: 'Total Users', color: 'var(--info)' },
{ key: 'active_sessions', label: 'Active Sessions',color: 'var(--success)' },
{ key: 'high_risk_events_today',label: 'High Risk Today',color: 'var(--danger)' },
{ key: 'failed_logins_today', label: 'Failed Logins', color: 'var(--warn)' },
];
return (
<div>
<Callout type="warn">
Admin endpoints require an admin JWT token. Login with{' '}
<code>demo.admin@adaptive.demo</code> / <code>Admin@Demo456!</code> first.
</Callout>
{/* Quick Admin Login */}
<Card>
<CardHeader icon="πŸ”‘">Quick Admin Login</CardHeader>
<div className="flex items-center gap-3 flex-wrap">
<span className="text-sm text-2">
<code>demo.admin@adaptive.demo</code> / <code>Admin@Demo456!</code>
</span>
<button className="btn btn-primary btn-sm" onClick={quickAdminLogin} disabled={loading.login}>
{loading.login ? '…' : 'Login as Admin'}
</button>
</div>
<ResponseBox result={adminLoginResp} />
</Card>
{/* Stats */}
<div className="flex items-center gap-3 mb-3">
<button className="btn btn-ghost btn-sm" onClick={loadAdminStats} disabled={loading.stats}>
{loading.stats ? '…' : 'πŸ”„ Load Statistics'}
</button>
</div>
<div className="grid-4 mb-4">
{STAT_ITEMS.map(s => (
<div className="stat-box" key={s.key}>
<div className="stat-num" style={{ color: s.color }}>
{statsData ? (statsData[s.key] ?? 'β€”') : 'β€”'}
</div>
<div className="stat-label">{s.label}</div>
</div>
))}
</div>
{/* Email Status */}
<Card>
<CardHeader
icon="βœ‰οΈ"
actions={
<button className="btn btn-ghost btn-sm" onClick={checkEmailStatus} disabled={loading.email}>
{loading.email ? '…' : 'πŸ”„ Refresh'}
</button>
}
>
Email Service Status
</CardHeader>
<EmailStatusBody data={emailData} />
</Card>
<div className="grid-2">
{/* User Management */}
<Card>
<CardHeader icon="πŸ‘₯">User Management</CardHeader>
<div className="flex flex-wrap gap-2 mb-3">
<button className="btn btn-ghost btn-sm" onClick={() => userCall(`${ADMIN}/users`)}>List Users</button>
<button className="btn btn-ghost btn-sm" onClick={() => userCall(`${ADMIN}/sessions`)}>List Sessions</button>
</div>
<FormGroup label="User ID">
<input type="number" value={adminUserId} onChange={e => setAdminUserId(e.target.value)} placeholder="User ID" />
</FormGroup>
<div className="flex gap-2">
<button className="btn btn-success btn-sm flex-1" onClick={adminUnblock} disabled={loading.user}>Unblock</button>
<button className="btn btn-danger btn-sm flex-1" onClick={adminBlock} disabled={loading.user}>Block</button>
</div>
<ResponseBox result={adminUserResp} />
</Card>
{/* Risk Events */}
<Card>
<CardHeader icon="⚠️">Risk Events &amp; Anomalies</CardHeader>
<div className="flex flex-wrap gap-2 mb-3">
<button className="btn btn-warn btn-sm" onClick={() => riskCall(`${ADMIN}/risk-events`)} disabled={loading.risk}>Risk Events</button>
<button className="btn btn-danger btn-sm" onClick={() => riskCall(`${ADMIN}/anomalies`)} disabled={loading.risk}>Active Anomalies</button>
<button className="btn btn-ghost btn-sm" onClick={() => riskCall(`${RISK}/overview`)} disabled={loading.risk}>Overview</button>
</div>
<ResponseBox result={adminRiskResp} />
</Card>
</div>
{/* Risk Stats */}
<Card>
<CardHeader icon="πŸ“ˆ">Risk Statistics</CardHeader>
<div className="flex gap-2 mb-3">
{['day','week','month'].map(p => (
<button key={p} className="btn btn-ghost btn-sm" onClick={async () => {
setLoad('period', true);
setStatsResp(await req(`${ADMIN}/risk-statistics?period=${p}`));
setLoad('period', false);
}} disabled={loading.period}>
{p.charAt(0).toUpperCase() + p.slice(1)}
</button>
))}
</div>
<ResponseBox result={statsResp} />
</Card>
</div>
);
}