feat(pool): an admin's switch moves a user's Claude login at once
PUT /api/accounts/pins (and agenthub accounts pin) now writes a pinned Claude login into the user's home right away (claude-pool.switchHome): every home's newer pair is copied back first, the member's own login is never written over, and no pane is restarted — running sessions take the new pair at their next credentials read. A live home stays on the login it holds, so unpinning does not flip it back. The Users page calls it "Switch a user's account" and says what happened.
This commit is contained in:
@@ -8,8 +8,9 @@
|
||||
* `.credentials.json`, or a `claude setup-token` token; for Codex an OpenAI
|
||||
* API key or a pasted `auth.json` — or removes one, each time with their own
|
||||
* password again; an analyst only reads. The secret only goes IN: the server
|
||||
* never sends it back. An admin can also pin a user to one account
|
||||
* (`PUT /api/accounts/pins`): that user's new sessions skip the balancer.
|
||||
* never sends it back. An admin can also switch a user to one account
|
||||
* (`PUT /api/accounts/pins`): that user's sessions skip the balancer, and a
|
||||
* Claude login goes into their home at once — no restart.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -291,11 +292,14 @@ export function AccountPoolCard({ isAdmin }: { isAdmin: boolean }) {
|
||||
pool={pool}
|
||||
initial={pinning}
|
||||
onClose={() => setPinning(null)}
|
||||
onDone={(user, account) => {
|
||||
toast.success(
|
||||
account
|
||||
? t("pool.pinDone.pinned", { user, account })
|
||||
: t("pool.pinDone.unpinned", { user })
|
||||
onDone={(user, account, switched) => {
|
||||
const key = !account
|
||||
? "unpinned"
|
||||
: switched === "switched" || switched === "own-login" || switched === "expired"
|
||||
? switched
|
||||
: "pinned";
|
||||
(key === "own-login" || key === "expired" ? toast.info : toast.success)(
|
||||
t(`pool.pinDone.${key}`, { user, account })
|
||||
);
|
||||
setPinning(null);
|
||||
void load();
|
||||
@@ -307,8 +311,8 @@ export function AccountPoolCard({ isAdmin }: { isAdmin: boolean }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pin a user to one account of a tool, or unpin them ("None"). Opened on an
|
||||
* existing pin it starts on that user with "None", so one click unpins.
|
||||
* Switch a user to one account of a tool, or back to automatic. Opened on an
|
||||
* existing pin it starts on that user with "Automatic", so one click unpins.
|
||||
*/
|
||||
function PinUserDialog({
|
||||
pool,
|
||||
@@ -319,7 +323,7 @@ function PinUserDialog({
|
||||
pool: AccountPool;
|
||||
initial: { tool: PoolTool; userId?: number };
|
||||
onClose: () => void;
|
||||
onDone: (user: string, account: string | null) => void;
|
||||
onDone: (user: string, account: string | null, switched: string | null) => void;
|
||||
}) {
|
||||
const { t } = useTranslation("users");
|
||||
const [tool, setTool] = useState<PoolTool>(initial.tool);
|
||||
@@ -348,7 +352,7 @@ function PinUserDialog({
|
||||
setError(null);
|
||||
try {
|
||||
const r = await api.accountPool.pin(tool, userId, account || null, current);
|
||||
onDone(r.pin.username, r.pin.account);
|
||||
onDone(r.pin.username, r.pin.account, r.switched ?? null);
|
||||
} catch (e) {
|
||||
setError(errorText(t, e));
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user