agent-zero/plugins/_model_config/webui/api-keys.html
Alessandro 64427163b5 Avoid password prompts for credential fields
Render API keys, access tokens, client secrets, and similar non-password credentials as masked text inputs so browsers do not offer to save them as passwords. Preserve readable placeholders and reveal controls while leaving genuine password fields unchanged.
2026-08-26 22:09:39 +02:00

135 lines
4.1 KiB
HTML

<html>
<head>
<title>Manage API Keys</title>
</head>
<body>
<script type="module">
import { store } from "/plugins/_model_config/webui/model-config-store.js";
</script>
<div x-data>
<template x-if="$store.modelConfig">
<div x-data="{
loading: true,
saving: false,
error: '',
get hasChanges() {
const dirty = $store.modelConfig.apiKeyDirty;
return Object.keys(dirty).some((p) => dirty[p]);
},
async init() {
await $store.modelConfig.ensureLoaded();
$store.modelConfig.resetApiKeyDrafts();
await $store.modelConfig.refreshApiKeyStatus();
this.loading = false;
},
async reveal(provider) {
try {
await $store.modelConfig.revealApiKey(provider);
} catch (e) {
this.error = e?.message || 'Failed to reveal API key.';
}
},
async saveAndClose() {
this.saving = true;
this.error = '';
try {
await $store.modelConfig.persistAllDirtyApiKeys();
await $store.modelConfig.refreshApiKeyStatus();
window.closeModal?.();
} catch (e) {
this.error = e?.message || 'Failed to save API keys.';
} finally {
this.saving = false;
}
}
}"
x-init="init()">
<div class="api-keys-section">
<div class="section-title">API Keys</div>
<div class="section-description">
API keys for model providers and services used by Agent Zero. You can set multiple API keys separated by a comma (,). They will be used in round-robin fashion.<br>
For more information about Agent Zero Venice provider, see <a href="http://agent-zero.ai/?community/api-dashboard/about" target="_blank">Agent Zero Venice</a>.
</div>
<div x-show="error" class="plugin-settings-error" style="margin-top: 1rem;">
<x-icon name="error"></x-icon>
<span x-text="error"></span>
</div>
<div x-show="loading" style="text-align:center; padding: 20px;">
<x-icon class="spinning" name="progress_activity"></x-icon>
</div>
<div x-show="!loading">
<template x-for="provider in $store.modelConfig.allProviders" :key="provider.value">
<div class="field">
<div class="field-label">
<div class="field-title" x-text="provider.label"></div>
</div>
<div class="field-control" style="position:relative;" x-data="{ showKey: false }">
<input type="text"
:class="{ 'masked-text-input': !showKey }"
:value="$store.modelConfig.apiKeyValues[provider.value]"
:placeholder="provider.has_key ? '••••••••••••' : ''"
autocomplete="off"
@input="$store.modelConfig.setApiKeyValue(provider.value, $el.value)"
style="padding-right:32px;" />
<x-icon class="eye-toggle"
@click="
showKey = !showKey;
if (showKey && !$store.modelConfig.apiKeyValues[provider.value] && provider.has_key) {
reveal(provider.value);
}
"
:name="showKey ? 'visibility' : 'visibility_off'"></x-icon>
</div>
</div>
</template>
</div>
</div>
<div class="modal-footer" data-modal-footer>
<button class="btn btn-ok"
@click="saveAndClose()"
:disabled="loading || saving || !hasChanges">
Save
</button>
<button class="btn btn-cancel"
@click="window.closeModal?.()"
:disabled="saving">
Close
</button>
</div>
</div>
</template>
</div>
<style>
.api-keys-section {
border: 1px solid var(--color-border);
border-radius: 8px;
padding: 16px;
}
.api-keys-section .section-title {
margin-top: 0;
}
.eye-toggle {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
cursor: pointer;
user-select: none;
opacity: 0.6;
z-index: 1;
}
.eye-toggle:hover {
opacity: 1;
}
</style>
</body>
</html>