497faae3bf
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.
54 lines
1.8 KiB
TypeScript
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>
|
|
);
|
|
}
|