支持白名单chat设置修改

This commit is contained in:
Von committed 2026-03-12 13:15:58 +08:00
1 parent 603c07f95b
commit 06a7ef7e35
5 files changed
+238

No files matched your search

+13
View File
@@ -31,6 +31,7 @@ class ProviderController:
self._app.route("/api/providers", methods=["GET"])(auth(self.get_providers))
self._app.route("/api/providers", methods=["POST"])(auth(self.create_provider))
self._app.route("/api/providers/fetch-models", methods=["GET"])(auth(self.fetch_models))
self._app.route("/api/providers/chat-whitelist", methods=["PUT"])(auth(self.update_chat_whitelist))
self._app.route("/api/providers/<string:name>", methods=["GET"])(auth(self.get_provider))
self._app.route("/api/providers/<string:name>", methods=["PUT"])(auth(self.update_provider))
self._app.route("/api/providers/<string:name>", methods=["DELETE"])(auth(self.delete_provider))
@@ -113,3 +114,15 @@ class ProviderController:
except Exception as exc:
self._logger.error(f"Error fetching provider models: {exc}")
return jsonify({"error": str(exc)}), 500
def update_chat_whitelist(self) -> Response:
try:
payload = request.get_json(silent=True) or {}
enabled = self._provider_service.update_chat_whitelist_enabled(payload.get("enabled"))
self._logger.info("Chat whitelist updated: enabled=%s", enabled)
return jsonify({"enabled": enabled})
except ValueError as exc:
return jsonify({"error": str(exc)}), 400
except Exception as exc:
self._logger.error(f"Error updating chat whitelist: {exc}")
return jsonify({"error": str(exc)}), 500
+146
View File
@@ -26,6 +26,67 @@
gap: 10px;
}
.providers-page .toolbar-setting-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.72);
}
.providers-page .toolbar-setting-card + .toolbar-card {
margin-top: 16px;
}
.providers-page .toolbar-setting-copy {
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.providers-page .toolbar-setting-label {
color: var(--text-primary);
font-size: 13px;
font-weight: 700;
}
.providers-page .toolbar-setting-hint {
color: var(--text-secondary);
font-size: 12px;
line-height: 1.45;
}
.providers-page .toolbar-setting-action {
display: inline-flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.providers-page .toolbar-setting-status {
color: var(--text-secondary);
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
.providers-page .toolbar-setting-card[data-enabled="true"] .toolbar-setting-status {
color: var(--success);
}
.providers-page .toolbar-setting-card[data-enabled="true"] {
border-color: rgba(28, 164, 113, 0.22);
background: rgba(28, 164, 113, 0.06);
}
.providers-page .toolbar-setting-card[data-saving="true"] .toolbar-setting-status {
color: var(--primary-active);
}
.providers-page .toolbar-pill {
display: inline-flex;
align-items: center;
@@ -128,6 +189,20 @@
color: currentColor;
}
:root[data-theme="dark"] .providers-page .toolbar-setting-card {
border-color: #313b47;
background: rgba(255, 255, 255, 0.03);
}
:root[data-theme="dark"] .providers-page .toolbar-setting-card[data-enabled="true"] {
border-color: rgba(46, 160, 104, 0.42);
background: rgba(46, 160, 104, 0.1);
}
:root[data-theme="dark"] .providers-page .toolbar-setting-card[data-saving="true"] .toolbar-setting-status {
color: #9bc7ff;
}
.providers-page .providers-table tbody tr:nth-child(even) td {
background: #fbfdff;
}
@@ -226,6 +301,58 @@
white-space: nowrap;
}
.providers-page .switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
vertical-align: middle;
}
.providers-page .switch input {
opacity: 0;
width: 0;
height: 0;
}
.providers-page .slider {
position: absolute;
inset: 0;
border-radius: 999px;
border: 1px solid #c8d7e6;
background: #e7eef6;
cursor: pointer;
transition: background-color var(--transition), border-color var(--transition), opacity var(--transition);
}
.providers-page .slider::before {
content: "";
position: absolute;
left: 2px;
top: 2px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.16);
transition: transform var(--transition), background-color var(--transition);
}
.providers-page .switch input:checked + .slider {
background: #e7f5ef;
border-color: #b7dccf;
}
.providers-page .switch input:checked + .slider::before {
transform: translateX(20px);
background: var(--success);
}
.providers-page .switch input:disabled + .slider {
opacity: 0.72;
cursor: wait;
}
.providers-page .model-summary-bar {
margin-top: 10px;
display: flex;
@@ -317,6 +444,15 @@
background: #0d1117;
}
:root[data-theme="dark"] .providers-page .slider {
background: #222b36;
border-color: #30363d;
}
:root[data-theme="dark"] .providers-page .slider::before {
background: #e8eef8;
}
:root[data-theme="dark"] .providers-page .btn-edit {
background: #1b2230;
border-color: #30363d;
@@ -621,6 +757,16 @@
white-space: normal;
}
.providers-page .toolbar-setting-card {
align-items: flex-start;
flex-direction: column;
}
.providers-page .toolbar-setting-action {
width: 100%;
justify-content: space-between;
}
.providers-page .provider-form-grid {
grid-template-columns: 1fr;
}
+60
View File
@@ -8,6 +8,19 @@
{% block content %}
<section class="providers-toolbar-section">
<div class="toolbar-setting-card" id="chatWhitelistCard">
<div class="toolbar-setting-copy">
<span class="toolbar-setting-label">Chat 白名单</span>
<span class="toolbar-setting-hint">控制 Provider Chat 接口是否启用白名单限制,启用后仅允许白名单访问的用户可访问Chat</span>
</div>
<div class="toolbar-setting-action">
<span class="toolbar-setting-status" id="chatWhitelistStatus">已禁用</span>
<label class="switch toolbar-switch" aria-label="切换 Chat 白名单">
<input type="checkbox" id="chatWhitelistToggle" onchange="toggleChatWhitelist(this.checked)">
<span class="slider"></span>
</label>
</div>
</div>
<div class="toolbar-card">
<div class="toolbar-summary">
<span class="toolbar-pill toolbar-pill-primary">共 <strong id="providerCount">0</strong> 个 Provider</span>
@@ -124,11 +137,14 @@
{% block page_scripts %}
<script src="/static/js/ui-message.js"></script>
<script>
const initialChatWhitelistEnabled = {{ 'true' if chat_whitelist_enabled else 'false' }};
let providers = [];
let editingProviderName = null;
let providerModalInstance = null;
let modelDetailModalInstance = null;
let activeModelProvider = null;
let chatWhitelistEnabled = initialChatWhitelistEnabled;
let chatWhitelistSaving = false;
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
@@ -163,6 +179,49 @@
return decodeURIComponent(encodedName);
}
function updateChatWhitelistUi() {
const card = document.getElementById('chatWhitelistCard');
const status = document.getElementById('chatWhitelistStatus');
const toggle = document.getElementById('chatWhitelistToggle');
if (!card || !status || !toggle) return;
card.dataset.enabled = chatWhitelistEnabled ? 'true' : 'false';
card.dataset.saving = chatWhitelistSaving ? 'true' : 'false';
toggle.checked = !!chatWhitelistEnabled;
toggle.disabled = !!chatWhitelistSaving;
status.textContent = chatWhitelistSaving
? '保存中...'
: (chatWhitelistEnabled ? '已启用' : '已禁用');
}
async function toggleChatWhitelist(enabled) {
const previous = chatWhitelistEnabled;
chatWhitelistEnabled = !!enabled;
chatWhitelistSaving = true;
updateChatWhitelistUi();
try {
const response = await fetch('/api/providers/chat-whitelist', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: !!enabled })
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || '更新 Chat 白名单失败');
}
chatWhitelistEnabled = !!result.enabled;
showMessage(`Chat 白名单已${chatWhitelistEnabled ? '启用' : '禁用'}`, 'success');
} catch (error) {
console.error('更新 Chat 白名单失败:', error);
chatWhitelistEnabled = previous;
showMessage(error.message || '更新 Chat 白名单失败', 'error');
} finally {
chatWhitelistSaving = false;
updateChatWhitelistUi();
}
}
function getModelPreview(models, limit = 6) {
const items = Array.isArray(models) ? models : [];
const visible = items.slice(0, limit);
@@ -457,6 +516,7 @@
document.addEventListener('DOMContentLoaded', function() {
initThemeToggle();
initTooltips();
updateChatWhitelistUi();
const modalElement = document.getElementById('providerModal');
providerModalInstance = new bootstrap.Modal(modalElement);
modalElement.addEventListener('hidden.bs.modal', function() {
+1
View File
@@ -54,6 +54,7 @@ class WebController:
return render_template(
"providers.html",
active_page="providers",
chat_whitelist_enabled=self._config_manager.is_chat_whitelist_enabled(),
current_username=self._get_current_username(),
auth_enabled=self._auth_service.is_auth_enabled(),
)
+18
View File
@@ -151,6 +151,24 @@ class ProviderService:
"fetched_models": fetched_models,
}
def update_chat_whitelist_enabled(self, enabled: Any) -> bool:
parsed_enabled = self._parse_optional_bool(enabled)
if parsed_enabled is None:
raise ValueError("Whitelist enabled flag is required")
config = self._load_config()
chat_config = config.get("chat")
if chat_config is None:
chat_config = {}
config["chat"] = chat_config
if not isinstance(chat_config, dict):
raise ValueError("Config field 'chat' must be an object")
chat_config["whitelist_enabled"] = parsed_enabled
self._write_config(config)
self._reload_callback()
return parsed_enabled
def _load_config(self) -> Dict[str, Any]:
with open(self._config_path, "r", encoding="utf-8") as file:
data = yaml.safe_load(file)