${routeQuota(p,snapshot)}Модели: ${providerModels(snapshot,p.provider).received ? providerModels(snapshot,p.provider).models.length : 'список ещё не получен'}
`).join('') || '
Нет подходящих подключённых аккаунтов. Подключите аккаунт на странице «Аккаунты».
';
elements.modalFooter.innerHTML=''; showModal();
elements.modalBody.querySelectorAll('[data-target-role]').forEach(target=>target.onclick=()=>{closeModal();handleDropMove(button.dataset.routeAdd,null,target.dataset.targetRole,-1);});
});
}
function initSnapshotSettings() {
// The snapshot intentionally has no server settings. Never present defaults
// as fetched values or write untouched defaults back to the server.
for (const id of ['setting-server-host','setting-server-port','setting-server-token-input','setting-quota-interval']) {
const input = document.getElementById(id);
input.disabled = true;
input.title = 'Н/Д: настройки сервера не входят в /api/snapshot';
if (input.tagName === 'SELECT') input.innerHTML = '';
else input.placeholder = 'Н/Д: нет в снапшоте';
}
document.getElementById('setting-token-status-badge').textContent = 'Н/Д: не передаётся';
for (const id of ['setting-quota-threshold-percent','setting-quota-threshold-action','setting-email-masking-mode']) {
const input = document.getElementById(id);
const unknown = document.createElement('option');
unknown.value = ''; unknown.textContent = 'Н/Д: текущее значение не передано'; unknown.selected = true; unknown.disabled = true;
input.prepend(unknown);
}
document.getElementById('setting-source-mode').innerHTML = '';
document.getElementById('setting-source-mode').disabled = true;
document.getElementById('setting-source-mode').closest('.setting-row').querySelector('.setting-desc').textContent = 'Рабочие данные поступают только из снапшота. Автоматической подмены демонстрационными данными нет.';
}
document.addEventListener('DOMContentLoaded', initSnapshotSettings);
function arrangeSettingsPanels() {
const view = document.getElementById('view-settings');
const first = view.querySelector('.settings-card');
const groups = [
['Общие настройки',['setting-theme']],
['Управление квотами',['setting-account-check-interval','setting-quota-interval','setting-quota-threshold-percent','setting-quota-threshold-action']],
['Безопасность и API',['setting-server-host','setting-server-token-input','setting-email-masking-mode']],
];
for (const [title,ids] of groups) {
const card = document.createElement('section'); card.className='settings-card';
const heading=document.createElement('h2'); heading.className='settings-group-title'; heading.textContent=title; card.append(heading);
for (const id of ids) card.append(document.getElementById(id).closest('.setting-row'));
if (title === 'Управление квотами') card.append(document.getElementById('btn-save-hub-settings').closest('.settings-actions'));
view.insertBefore(card, first);
}
first.remove();
}
document.addEventListener('DOMContentLoaded', arrangeSettingsPanels);
function renderHealthPanels(snapshot) {
const box=document.getElementById('health-workspace');
if (!box) return;
const providers=snapshot.providers || [];
box.innerHTML=`
Подключения провайдеров
${providers.map(provider=>`
${escapeHtml(provider.provider_name)}
${provider.connected_count} подключено · ${provider.online_count} готово
`).join('') || '
Подключений нет
'}
Хранилища и температуры
Загрузка системного диска показана выше.
Н/Д: API не передаёт список томов, температуры и состояние резервного копирования.
Состояние в реальном времени
Активных вызовов
${snapshot.metrics?.active_calls_total ?? 'Н/Д'}
Снапшот
${snapshot.is_stale ? 'Устарел' : 'Актуален'}
Н/Д: API не передаёт uptime отдельных служб и историю сетевой активности.