feat: Manage API tokens from settings

A card in the Access group beside Members and single sign-on rather than a
new nav entry — /settings/instance was folded back in for exactly this
reason. The plaintext is shown once in a well block and never again.

Tokens outside a newly tightened lifetime policy are flagged rather than
broken, because the policy governs issuance, not existing credentials.
This commit is contained in:
2026-08-12 14:58:57 +00:00
parent 3b4c87a292
commit 182752d9ab
3 changed files with 442 additions and 1 deletions
+14
View File
@@ -10,6 +10,7 @@ import { Field } from "@/components/settings/Field";
import { Group } from "@/components/settings/Group";
import { SectionCard } from "@/components/settings/SectionCard";
import { MembersCard } from "@/components/settings/MembersCard";
import { ApiTokensCard } from "@/components/settings/ApiTokensCard";
import { AuthProvidersCard } from "@/components/settings/AuthProvidersCard";
const numberInputClass =
@@ -144,6 +145,7 @@ export default function SettingsPage() {
const [thresholdMinutes, setThresholdMinutes] = useState(5);
const [logRetentionDays, setLogRetentionDays] = useState(30);
const [offlineChannelIds, setOfflineChannelIds] = useState<string[]>([]);
const [apiTokenMaxDays, setApiTokenMaxDays] = useState(0);
const toast = useToast();
useEffect(() => {
@@ -151,6 +153,7 @@ export default function SettingsPage() {
setThresholdMinutes(settings.alerts.offline_threshold_minutes || 5);
setLogRetentionDays(settings.workflow_log_retention_days ?? 30);
setOfflineChannelIds(settings.alerts.offline_channel_ids ?? []);
setApiTokenMaxDays(settings.api_token_max_days ?? 0);
}, [settings]);
// The one place the in-progress form is turned into a payload. Both the
@@ -163,6 +166,7 @@ export default function SettingsPage() {
offline_channel_ids: offlineChannelIds,
},
workflow_log_retention_days: logRetentionDays,
api_token_max_days: apiTokenMaxDays,
};
}
@@ -220,6 +224,7 @@ export default function SettingsPage() {
<div className="space-y-10">
<Group label="Access">
<MembersCard />
<ApiTokensCard />
<AuthProvidersCard
localLoginEnabled={settings?.local_login_enabled ?? true}
onLocalLoginChange={(v) => {
@@ -291,6 +296,15 @@ export default function SettingsPage() {
<Field label="Log retention (days)" hint="0 = keep forever. Applies to per-run step output logs.">
<input type="number" min={0} value={logRetentionDays} onChange={(e) => setLogRetentionDays(Number(e.target.value))} className={numberInputClass} />
</Field>
<div className="mt-6">
<Field
label="Maximum API token lifetime (days)"
hint="0 means no cap, and tokens may be created with no expiry. Changing this affects new tokens only."
>
<input type="number" min={0} value={apiTokenMaxDays} onChange={(e) => setApiTokenMaxDays(Number(e.target.value))} className={numberInputClass} />
</Field>
</div>
</SectionCard>
</div>