SHiNE-server/shine-UI/js/pages/settings-view.js

196 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { renderHeader } from '../components/header.js';
import { addAppLogEntry, authService, closeCurrentSessionAndSignOut, state } from '../state.js';
import {
canInstallPwa,
isStandalonePwaMode,
onPwaInstallAvailabilityChange,
promptPwaInstall,
} from '../services/pwa-install-service.js';
import { initPwaPush } from '../services/pwa-push-service.js';
export const pageMeta = { id: 'settings-view', title: 'Настройки' };
function formatBuildStamp(rawValue) {
const value = String(rawValue || '').trim();
if (!/^\d{14}$/.test(value)) return value;
const yyyy = value.slice(0, 4);
const mm = value.slice(4, 6);
const dd = value.slice(6, 8);
const hh = value.slice(8, 10);
const min = value.slice(10, 12);
const ss = value.slice(12, 14);
return `${yyyy}-${mm}-${dd}/${hh}:${min}__${ss}`;
}
function formatVersionForUi(rawValue) {
const value = String(rawValue || '').trim();
if (!value) return 'n/a';
const formatted = formatBuildStamp(value);
if (formatted && formatted !== value) {
return `${formatted} (${value})`;
}
return value;
}
export function render({ navigate }) {
const screen = document.createElement('section');
screen.className = 'stack';
let isDisposed = false;
screen.append(
renderHeader({
title: 'Настройки',
leftAction: { label: '←', onClick: () => navigate('profile-view') },
}),
);
const card = document.createElement('div');
card.className = 'card stack';
card.innerHTML = `
<button class="text-btn" type="button" id="settings-device">Устройства</button>
<button class="text-btn" type="button" id="settings-servers">Настройки серверов</button>
<button class="text-btn" type="button" id="settings-language">Язык / Language</button>
<button class="text-btn" type="button" id="settings-app-log">Лог приложения</button>
<button class="text-btn" type="button" id="settings-pwa-diagnostics">Диагностика PWA / Push</button>
<button class="text-btn" type="button" id="settings-signout">Завершить текущий сеанс</button>
<button class="text-btn" type="button" id="settings-pwa-install">Зарегистрировать PWA</button>
`;
card.querySelector('#settings-device').addEventListener('click', () => navigate('device-view'));
card.querySelector('#settings-servers').addEventListener('click', () => navigate('server-settings-view'));
card.querySelector('#settings-language').addEventListener('click', () => navigate('language-view'));
card.querySelector('#settings-app-log').addEventListener('click', () => navigate('app-log-view'));
card.querySelector('#settings-pwa-diagnostics').addEventListener('click', () => navigate('pwa-diagnostics-view'));
const signOutBtn = card.querySelector('#settings-signout');
const pwaInstallBtn = card.querySelector('#settings-pwa-install');
const syncPwaButtonLabel = () => {
if (isStandalonePwaMode()) {
pwaInstallBtn.textContent = 'PWA установлено (проверить WebPush)';
return;
}
if (canInstallPwa()) {
pwaInstallBtn.textContent = 'Зарегистрировать PWA';
return;
}
pwaInstallBtn.textContent = 'Как установить PWA';
};
const unsubscribeInstallAvailability = onPwaInstallAvailabilityChange(() => {
syncPwaButtonLabel();
});
syncPwaButtonLabel();
signOutBtn.addEventListener('click', async () => {
const confirmed = window.confirm(
'Завершить текущую сессию на сервере, отключиться, очистить локальные данные и перейти на стартовый экран?'
);
if (!confirmed) return;
signOutBtn.disabled = true;
try {
addAppLogEntry({
level: 'info',
source: 'session',
message: `Запрошено завершение текущей сессии: ${state.session.sessionId || 'unknown'}`,
});
await closeCurrentSessionAndSignOut({
infoMessage: 'Сеанс завершён. Выполните вход заново.',
});
} finally {
signOutBtn.disabled = false;
}
});
pwaInstallBtn.addEventListener('click', async () => {
pwaInstallBtn.disabled = true;
try {
await initPwaPush({
authService,
onLog: (entry) => addAppLogEntry(entry),
});
if (canInstallPwa()) {
const result = await promptPwaInstall();
const accepted = result.outcome === 'accepted';
addAppLogEntry({
level: 'info',
source: 'pwa-install',
message: accepted ? 'Пользователь принял установку PWA' : 'Пользователь отклонил установку PWA',
details: { outcome: result.outcome || 'unknown' },
});
if (accepted) {
window.alert('Установка PWA подтверждена. Проверьте приложение на главном экране устройства.');
}
} else if (!isStandalonePwaMode()) {
window.alert('Для установки откройте меню браузера и выберите "Установить приложение" или "Добавить на главный экран".');
} else {
window.alert('PWA уже установлено. WebPush перерегистрирован.');
}
} catch (error) {
addAppLogEntry({
level: 'warn',
source: 'pwa-install',
message: 'Не удалось зарегистрировать PWA/WebPush',
details: { error: error?.message || 'unknown' },
});
window.alert(`Ошибка регистрации PWA: ${error?.message || 'unknown'}`);
} finally {
pwaInstallBtn.disabled = false;
syncPwaButtonLabel();
}
});
const versionCard = document.createElement('div');
versionCard.className = 'card stack';
const title = document.createElement('p');
title.className = 'field-label';
title.textContent = 'Версии';
const clientVersion = document.createElement('p');
clientVersion.className = 'meta-muted';
clientVersion.textContent = `Клиент: ${formatVersionForUi(window.__SHINE_CLIENT_VERSION__)}`;
const uiBuild = document.createElement('p');
uiBuild.className = 'meta-muted';
uiBuild.textContent = `Сборка UI: ${formatVersionForUi(window.__SHINE_BUILD_HASH__)}`;
const serverVersion = document.createElement('p');
serverVersion.className = 'meta-muted';
serverVersion.textContent = 'Сервер: загружается...';
versionCard.append(title, clientVersion, uiBuild, serverVersion);
void (async () => {
try {
let value = '';
try {
const pingResp = await authService.ws.request('Ping', { ts: Date.now() }, 7000);
value = String(pingResp?.payload?.serverVersion || pingResp?.serverVersion || '').trim();
} catch {
// fallback below
}
if (!value) {
const infoResp = await authService.ws.request('GetServerInfo', {});
value = String(infoResp?.payload?.version || '').trim();
}
if (!isDisposed) serverVersion.textContent = `Сервер: ${formatVersionForUi(value)}`;
} catch {
if (!isDisposed) {
serverVersion.textContent = 'Сервер: недоступно';
}
}
})();
screen.append(card);
screen.cleanup = () => {
isDisposed = true;
unsubscribeInstallAvailability();
};
screen.append(versionCard);
return screen;
}