This commit is contained in:
Mikan
2026-06-19 16:31:45 +03:00
parent d0d1f003ae
commit 5a78def096
21 changed files with 1250 additions and 548 deletions

View File

@@ -6,7 +6,7 @@ import type { LlmLog, SettingsOut } from "@/types";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { Card, CardBody, CardHeader } from "@/components/ui/Card";
import { Save, ArrowLeft, Activity, Users, Zap } from "lucide-react";
import { Save, ArrowLeft, Activity, Users, Zap, Ban, CheckCircle2 } from "lucide-react";
export function AdminPanelPage() {
const { t } = useTranslation();
@@ -22,6 +22,7 @@ export function AdminPanelPage() {
const [error, setError] = useState("");
const [embeddingTest, setEmbeddingTest] = useState<null | { ok: boolean; msg: string }>(null);
const [testingEmbeddings, setTestingEmbeddings] = useState(false);
const [userActionError, setUserActionError] = useState("");
useEffect(() => {
(async () => {
@@ -110,6 +111,16 @@ export function AdminPanelPage() {
}
};
const toggleUserActive = async (userId: string, currentActive: boolean) => {
setUserActionError("");
try {
const updated = await adminApi.setUserActive(userId, !currentActive);
setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, is_active: updated.is_active } : u)));
} catch (err: any) {
setUserActionError(err.response?.data?.detail || t("errors.unknown"));
}
};
if (loading) {
return <div className="p-8 text-center text-ink-400">{t("common.loading")}</div>;
}
@@ -302,23 +313,17 @@ export function AdminPanelPage() {
</Card>
<Card>
<CardHeader title={t("admin.trigger_settings")} />
<CardHeader title={t("admin.trigger_settings")} subtitle={t("admin.trigger_settings_desc")} />
<CardBody>
<div className="grid grid-cols-2 gap-3 items-end">
<label className="flex items-center gap-2 text-xs text-ink-300">
<input
type="checkbox"
checked={!!values["triggers.enabled"]}
onChange={(e) => setValues({ ...values, "triggers.enabled": e.target.checked })}
/>
{t("admin.triggers_enabled")}
</label>
<NumberInput
label={t("admin.triggers_check_interval")}
value={values["triggers.check_interval"]}
onChange={(v) => setValues({ ...values, "triggers.check_interval": v })}
<label className="flex items-center gap-2 text-xs text-ink-300">
<input
type="checkbox"
checked={!!values["triggers.enabled"]}
onChange={(e) => setValues({ ...values, "triggers.enabled": e.target.checked })}
/>
</div>
{t("admin.triggers_enabled")}
</label>
<p className="text-xs text-ink-500 mt-2">{t("admin.triggers_enabled_desc")}</p>
</CardBody>
</Card>
@@ -382,6 +387,9 @@ export function AdminPanelPage() {
<Card>
<CardHeader title={t("admin.users")} subtitle={`${users.length} users`} />
<CardBody>
{userActionError && (
<p className="text-sm text-red-400 mb-3">{userActionError}</p>
)}
<div className="overflow-x-auto">
<table className="w-full text-xs">
<thead>
@@ -390,7 +398,8 @@ export function AdminPanelPage() {
<th className="py-2 pr-3">Username</th>
<th className="py-2 pr-3">Role</th>
<th className="py-2 pr-3">Active</th>
<th className="py-2">Created</th>
<th className="py-2 pr-3">Created</th>
<th className="py-2 text-right">{t("admin.users_actions")}</th>
</tr>
</thead>
<tbody>
@@ -412,9 +421,32 @@ export function AdminPanelPage() {
<span className="text-red-400"></span>
)}
</td>
<td className="py-2 text-ink-400">
<td className="py-2 pr-3 text-ink-400">
{new Date(u.created_at).toLocaleDateString()}
</td>
<td className="py-2 text-right">
{u.is_admin ? (
<span className="text-ink-500 text-xs"></span>
) : u.is_active ? (
<Button
size="sm"
variant="ghost"
onClick={() => toggleUserActive(u.id, u.is_active)}
>
<Ban size={12} className="mr-1" />
{t("admin.users_ban")}
</Button>
) : (
<Button
size="sm"
variant="ghost"
onClick={() => toggleUserActive(u.id, u.is_active)}
>
<CheckCircle2 size={12} className="mr-1" />
{t("admin.users_unban")}
</Button>
)}
</td>
</tr>
))}
</tbody>