Files
AgentHub/client/src/components/settings/MemberPolicySettings.tsx
T
nntrivi2001 497faae3bf feat(settings): admin switch to allow or block members' test runs
Settings → AI tools → Tests on this server (admin, S3 only). Stored in
/opt/agenthub/app/member-policy.json, which member-guard.js reads as the
member at each Bash call — no restart; no file = blocked. GET/PUT
/api/ai-tools/member-policy, audited, 409 ENOS3 outside S3.
2026-11-03 11:37:13 +07:00

54 lines
1.8 KiB
TypeScript

/**
* @file MemberPolicySettings — Settings → AI tools → Tests on this server:
* the admin's server-wide switch that allows or blocks members' Claude from
* running test runners and container commands on this box
* (`GET/PUT /api/ai-tools/member-policy`, `server/lib/member-policy.js`,
* enforced by `scripts/member-guard.js`). Renders nothing outside server mode
* with per-user accounts (S3), or for anyone but an admin.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { api, type MemberPolicyState } from "../../lib/api";
import { toast } from "../../lib/toast";
import { usePermissions } from "../AuthGate";
import { SettingsSection, SettingsSwitchRow } from "./SettingsRow";
export function MemberPolicySettings() {
const { t } = useTranslation("settings");
const { isAdmin, local } = usePermissions();
const [state, setState] = useState<MemberPolicyState | null>(null);
const [busy, setBusy] = useState(false);
useEffect(() => {
if (local || !isAdmin) return;
api.aiTools
.memberPolicy()
.then(setState)
.catch(() => setState(null));
}, [local, isAdmin]);
if (local || !isAdmin || !state?.available) return null;
return (
<SettingsSection id="member-policy" title={t("memberPolicy.title")}>
<SettingsSwitchRow
label={t("memberPolicy.switch")}
description={t("memberPolicy.hint")}
checked={state.allowTests}
disabled={busy}
onChange={async (on) => {
setBusy(true);
try {
setState(await api.aiTools.setMemberPolicy(on));
} catch (err) {
toast.error(err);
} finally {
setBusy(false);
}
}}
/>
</SettingsSection>
);
}