Переделать UI дополнительного pairing-пароля
This commit is contained in:
parent
9a489801c5
commit
d6c5757dfa
@ -22,6 +22,9 @@
|
|||||||
- убедиться, что без онлайн доверённой сессии новое устройство сразу получает явную ошибку и код вообще не создаётся;
|
- убедиться, что без онлайн доверённой сессии новое устройство сразу получает явную ошибку и код вообще не создаётся;
|
||||||
- убедиться, что countdown под кодом убывает в реальном времени;
|
- убедиться, что countdown под кодом убывает в реальном времени;
|
||||||
- убедиться, что кнопка `Отмена` на новом устройстве действительно снимает заявку и она пропадает у доверённого устройства без ожидания TTL.
|
- убедиться, что кнопка `Отмена` на новом устройстве действительно снимает заявку и она пропадает у доверённого устройства без ожидания TTL.
|
||||||
|
- убедиться, что на экране `Подключить по коду` блок дополнительного пароля показывает два понятных состояния: пароль не задан / пароль установлен;
|
||||||
|
- убедиться, что `Задать пароль` и `Изменить пароль` открывают верхний диалог с двумя полями и кнопками-глазами;
|
||||||
|
- убедиться, что `Убрать пароль` не выключает pairing целиком, а переводит его в режим без дополнительного пароля.
|
||||||
|
|
||||||
- ожидаемый результат:
|
- ожидаемый результат:
|
||||||
- новое устройство получает код, доверённое устройство видит ту же заявку и может её подтвердить или отклонить;
|
- новое устройство получает код, доверённое устройство видит ту же заявку и может её подтвердить или отклонить;
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.200
|
client.version=1.2.201
|
||||||
server.version=1.2.189
|
server.version=1.2.190
|
||||||
|
|||||||
@ -55,7 +55,7 @@ import * as serverSettingsView from './pages/server-settings-view.js';
|
|||||||
import * as toolsSettingsView from './pages/tools-settings-view.js';
|
import * as toolsSettingsView from './pages/tools-settings-view.js';
|
||||||
import * as deviceView from './pages/device-view.js?v=202606131435';
|
import * as deviceView from './pages/device-view.js?v=202606131435';
|
||||||
import * as connectDeviceView from './pages/connect-device-view.js?v=202606142055';
|
import * as connectDeviceView from './pages/connect-device-view.js?v=202606142055';
|
||||||
import * as devicePairingView from './pages/device-pairing-view.js?v=202606150045';
|
import * as devicePairingView from './pages/device-pairing-view.js?v=202606151000';
|
||||||
import * as deviceQrView from './pages/device-qr-view.js';
|
import * as deviceQrView from './pages/device-qr-view.js';
|
||||||
import * as deviceCameraView from './pages/device-camera-view.js';
|
import * as deviceCameraView from './pages/device-camera-view.js';
|
||||||
import * as showKeysView from './pages/show-keys-view.js';
|
import * as showKeysView from './pages/show-keys-view.js';
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import { toUserMessage } from '../services/ui-error-texts.js';
|
|||||||
|
|
||||||
export const pageMeta = { id: 'device-pairing-view', title: 'Подключить по коду' };
|
export const pageMeta = { id: 'device-pairing-view', title: 'Подключить по коду' };
|
||||||
|
|
||||||
|
const PAIRING_PASSWORD_STATE_PREFIX = 'shine_pairing_password_state_v1';
|
||||||
|
|
||||||
function setStatus(statusEl, message, kind = 'info') {
|
function setStatus(statusEl, message, kind = 'info') {
|
||||||
statusEl.classList.toggle('is-unavailable', kind === 'error');
|
statusEl.classList.toggle('is-unavailable', kind === 'error');
|
||||||
statusEl.classList.toggle('is-available', kind !== 'error');
|
statusEl.classList.toggle('is-available', kind !== 'error');
|
||||||
@ -71,6 +73,48 @@ function requestCardHtml(request) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makePasswordToggleIcons() {
|
||||||
|
return {
|
||||||
|
eye: `
|
||||||
|
<svg class="key-toggle-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M2.4 12s3.6-6.5 9.6-6.5S21.6 12 21.6 12s-3.6 6.5-9.6 6.5S2.4 12 2.4 12Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
|
||||||
|
<circle cx="12" cy="12" r="2.9" fill="none" stroke="currentColor" stroke-width="1.8"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
eyeOff: `
|
||||||
|
<svg class="key-toggle-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M3 4l18 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
||||||
|
<path d="M2.4 12s3.6-6.5 9.6-6.5c2.4 0 4.5.8 6.1 1.9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.6 12s-3.6 6.5-9.6 6.5c-2.4 0-4.5-.8-6.1-1.9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function localPairingPasswordStateKey(login, serverUrl) {
|
||||||
|
return `${PAIRING_PASSWORD_STATE_PREFIX}:${String(serverUrl || '').trim()}:${String(login || '').trim().toLowerCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLocalPairingPasswordState(login, serverUrl) {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(localPairingPasswordStateKey(login, serverUrl));
|
||||||
|
if (!raw) return false;
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return !!parsed?.hasPassword;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLocalPairingPasswordState(login, serverUrl, hasPassword) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(localPairingPasswordStateKey(login, serverUrl), JSON.stringify({
|
||||||
|
hasPassword: !!hasPassword,
|
||||||
|
updatedAtMs: Date.now(),
|
||||||
|
}));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
export function render({ navigate }) {
|
export function render({ navigate }) {
|
||||||
const screen = document.createElement('section');
|
const screen = document.createElement('section');
|
||||||
screen.className = 'stack';
|
screen.className = 'stack';
|
||||||
@ -78,6 +122,9 @@ export function render({ navigate }) {
|
|||||||
let requests = [];
|
let requests = [];
|
||||||
let cleanupEvent = () => {};
|
let cleanupEvent = () => {};
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
|
let settingsBusy = false;
|
||||||
|
let pairingPasswordConfigured = loadLocalPairingPasswordState(state.session.login, state.entrySettings.shineServer);
|
||||||
|
let dialogMode = '';
|
||||||
|
|
||||||
screen.append(
|
screen.append(
|
||||||
renderHeader({
|
renderHeader({
|
||||||
@ -88,22 +135,7 @@ export function render({ navigate }) {
|
|||||||
|
|
||||||
const settingsCard = document.createElement('div');
|
const settingsCard = document.createElement('div');
|
||||||
settingsCard.className = 'card stack';
|
settingsCard.className = 'card stack';
|
||||||
settingsCard.innerHTML = `
|
const passwordIcons = makePasswordToggleIcons();
|
||||||
<p class="field-label">Пароль подключения</p>
|
|
||||||
<label class="checkbox-row">
|
|
||||||
<input type="checkbox" id="pairing-use-password" />
|
|
||||||
использовать доп. пароль
|
|
||||||
</label>
|
|
||||||
<label class="stack">
|
|
||||||
<span class="meta-muted" id="pairing-password-help">Если включено, новое устройство должно будет ввести этот пароль перед получением кода.</span>
|
|
||||||
<input class="input" id="pairing-password" type="password" autocomplete="new-password" placeholder="Новый pairing-пароль" />
|
|
||||||
</label>
|
|
||||||
<div class="row">
|
|
||||||
<button class="primary-btn" type="button" id="enable-pairing-btn">Включить / обновить</button>
|
|
||||||
<button class="ghost-btn" type="button" id="disable-pairing-btn">Выключить</button>
|
|
||||||
</div>
|
|
||||||
<p class="meta-muted">Чтобы включить pairing без пароля: оставьте галочку выключенной и нажмите "Включить / обновить". Чтобы включить pairing с паролем: включите галочку, введите пароль и нажмите ту же кнопку.</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const keySummaryCard = document.createElement('div');
|
const keySummaryCard = document.createElement('div');
|
||||||
keySummaryCard.className = 'card stack';
|
keySummaryCard.className = 'card stack';
|
||||||
@ -129,26 +161,169 @@ export function render({ navigate }) {
|
|||||||
const status = document.createElement('p');
|
const status = document.createElement('p');
|
||||||
status.className = 'status-line is-unavailable';
|
status.className = 'status-line is-unavailable';
|
||||||
status.style.display = 'none';
|
status.style.display = 'none';
|
||||||
|
|
||||||
const passwordInput = settingsCard.querySelector('#pairing-password');
|
|
||||||
const usePasswordInput = settingsCard.querySelector('#pairing-use-password');
|
|
||||||
const passwordHelpEl = settingsCard.querySelector('#pairing-password-help');
|
|
||||||
const enableBtn = settingsCard.querySelector('#enable-pairing-btn');
|
|
||||||
const disableBtn = settingsCard.querySelector('#disable-pairing-btn');
|
|
||||||
const keySummaryEl = keySummaryCard.querySelector('#pairing-key-summary');
|
const keySummaryEl = keySummaryCard.querySelector('#pairing-key-summary');
|
||||||
const codeFilterInput = requestsCard.querySelector('#pairing-code-filter');
|
const codeFilterInput = requestsCard.querySelector('#pairing-code-filter');
|
||||||
const refreshBtn = requestsCard.querySelector('#refresh-pairing-requests');
|
const refreshBtn = requestsCard.querySelector('#refresh-pairing-requests');
|
||||||
const requestsListEl = requestsCard.querySelector('#pairing-requests-list');
|
const requestsListEl = requestsCard.querySelector('#pairing-requests-list');
|
||||||
|
|
||||||
const syncPasswordUi = () => {
|
const passwordDialog = document.createElement('div');
|
||||||
const usePassword = !!usePasswordInput.checked;
|
passwordDialog.hidden = true;
|
||||||
passwordInput.parentElement.style.display = usePassword ? '' : 'none';
|
passwordDialog.style.position = 'fixed';
|
||||||
passwordHelpEl.textContent = usePassword
|
passwordDialog.style.inset = '0';
|
||||||
? 'Если включено, новое устройство должно будет ввести этот пароль перед получением кода.'
|
passwordDialog.style.zIndex = '30';
|
||||||
: 'Если выключено, новое устройство сможет входить без доп. пароля.';
|
passwordDialog.innerHTML = `
|
||||||
if (!usePassword) {
|
<div style="position:absolute; inset:0; background:rgba(5,9,16,0.72); backdrop-filter:blur(4px);" data-action="close-dialog"></div>
|
||||||
passwordInput.value = '';
|
<div class="card stack" style="position:absolute; left:16px; right:16px; top:24px; gap:12px; box-shadow:var(--shadow);">
|
||||||
|
<div class="row" style="align-items:flex-start;">
|
||||||
|
<div class="stack" style="gap:6px; flex:1;">
|
||||||
|
<p class="field-label" id="pairing-dialog-title">Задать дополнительный пароль</p>
|
||||||
|
<p class="meta-muted" id="pairing-dialog-text">Дополнительный пароль не даёт права на подключение сам по себе. Он только отсекает лишние заявки, чтобы посторонние не могли засыпать ваш аккаунт запросами. Обычно он не нужен, поэтому при желании можно задать и что-то простое, что легко запомнить.</p>
|
||||||
|
</div>
|
||||||
|
<button class="ghost-btn" type="button" data-action="close-dialog">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
<label class="stack">
|
||||||
|
<span class="field-label">Пароль</span>
|
||||||
|
<div class="inline-input-row">
|
||||||
|
<input class="input key-input" id="pairing-dialog-password" type="password" autocomplete="new-password" placeholder="Введите дополнительный пароль" />
|
||||||
|
<button class="icon-btn key-toggle-btn" type="button" id="pairing-dialog-password-toggle" aria-label="Показать пароль" title="Показать пароль">${passwordIcons.eyeOff}</button>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="stack">
|
||||||
|
<span class="field-label">Подтвердите пароль</span>
|
||||||
|
<div class="inline-input-row">
|
||||||
|
<input class="input key-input" id="pairing-dialog-password-confirm" type="password" autocomplete="new-password" placeholder="Повторите пароль" />
|
||||||
|
<button class="icon-btn key-toggle-btn" type="button" id="pairing-dialog-password-confirm-toggle" aria-label="Показать пароль" title="Показать пароль">${passwordIcons.eyeOff}</button>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<div class="row" style="flex-wrap:wrap;">
|
||||||
|
<button class="primary-btn" type="button" id="pairing-dialog-save">Сохранить пароль</button>
|
||||||
|
<button class="ghost-btn" type="button" data-action="close-dialog">Отмена</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
screen.append(passwordDialog);
|
||||||
|
|
||||||
|
const dialogTitleEl = passwordDialog.querySelector('#pairing-dialog-title');
|
||||||
|
const dialogTextEl = passwordDialog.querySelector('#pairing-dialog-text');
|
||||||
|
const dialogPasswordInput = passwordDialog.querySelector('#pairing-dialog-password');
|
||||||
|
const dialogPasswordConfirmInput = passwordDialog.querySelector('#pairing-dialog-password-confirm');
|
||||||
|
const dialogSaveBtn = passwordDialog.querySelector('#pairing-dialog-save');
|
||||||
|
const dialogPasswordToggleBtn = passwordDialog.querySelector('#pairing-dialog-password-toggle');
|
||||||
|
const dialogPasswordConfirmToggleBtn = passwordDialog.querySelector('#pairing-dialog-password-confirm-toggle');
|
||||||
|
|
||||||
|
const bindPasswordToggle = (input, button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if (input.type === 'password') {
|
||||||
|
input.type = 'text';
|
||||||
|
button.innerHTML = passwordIcons.eye;
|
||||||
|
button.setAttribute('aria-label', 'Скрыть пароль');
|
||||||
|
button.title = 'Скрыть пароль';
|
||||||
|
} else {
|
||||||
|
input.type = 'password';
|
||||||
|
button.innerHTML = passwordIcons.eyeOff;
|
||||||
|
button.setAttribute('aria-label', 'Показать пароль');
|
||||||
|
button.title = 'Показать пароль';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
bindPasswordToggle(dialogPasswordInput, dialogPasswordToggleBtn);
|
||||||
|
bindPasswordToggle(dialogPasswordConfirmInput, dialogPasswordConfirmToggleBtn);
|
||||||
|
|
||||||
|
const openPasswordDialog = (mode) => {
|
||||||
|
dialogMode = mode;
|
||||||
|
dialogTitleEl.textContent = mode === 'change'
|
||||||
|
? 'Изменить дополнительный пароль'
|
||||||
|
: 'Задать дополнительный пароль';
|
||||||
|
dialogTextEl.textContent =
|
||||||
|
'Дополнительный пароль не даёт права на подключение сам по себе. Он только отсекает лишние заявки, чтобы посторонние не могли засыпать ваш аккаунт запросами. Обычно он не нужен, поэтому при желании можно задать и что-то простое, что легко запомнить.';
|
||||||
|
dialogPasswordInput.value = '';
|
||||||
|
dialogPasswordConfirmInput.value = '';
|
||||||
|
dialogPasswordInput.type = 'password';
|
||||||
|
dialogPasswordConfirmInput.type = 'password';
|
||||||
|
dialogPasswordToggleBtn.innerHTML = passwordIcons.eyeOff;
|
||||||
|
dialogPasswordConfirmToggleBtn.innerHTML = passwordIcons.eyeOff;
|
||||||
|
passwordDialog.hidden = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closePasswordDialog = () => {
|
||||||
|
dialogMode = '';
|
||||||
|
passwordDialog.hidden = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSettingsBusy = (flag) => {
|
||||||
|
settingsBusy = flag;
|
||||||
|
renderSettingsCard();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSettingsCard = () => {
|
||||||
|
settingsCard.innerHTML = '';
|
||||||
|
|
||||||
|
const title = document.createElement('p');
|
||||||
|
title.className = 'field-label';
|
||||||
|
title.textContent = 'Дополнительный пароль';
|
||||||
|
|
||||||
|
const stateText = document.createElement('p');
|
||||||
|
stateText.className = 'meta-muted';
|
||||||
|
stateText.textContent = pairingPasswordConfigured
|
||||||
|
? 'Установлен дополнительный пароль для подключения через другое устройство.'
|
||||||
|
: 'Дополнительный пароль для подключения через другое устройство не задан.';
|
||||||
|
|
||||||
|
const note = document.createElement('p');
|
||||||
|
note.className = 'meta-muted';
|
||||||
|
note.textContent = pairingPasswordConfigured
|
||||||
|
? 'Этот пароль не даёт права на вход сам по себе. Он только отсекает лишние заявки до того, как пользователь подтвердит подключение на доверённом устройстве.'
|
||||||
|
: 'Сейчас подключение работает без дополнительного пароля. Обычно этого достаточно. Если хотите, можно добавить простой пароль только как защиту от лишних заявок.';
|
||||||
|
|
||||||
|
const actions = document.createElement('div');
|
||||||
|
actions.className = 'row';
|
||||||
|
actions.style.flexWrap = 'wrap';
|
||||||
|
|
||||||
|
if (pairingPasswordConfigured) {
|
||||||
|
const changeBtn = document.createElement('button');
|
||||||
|
changeBtn.className = 'primary-btn';
|
||||||
|
changeBtn.type = 'button';
|
||||||
|
changeBtn.textContent = 'Изменить пароль';
|
||||||
|
changeBtn.disabled = settingsBusy;
|
||||||
|
changeBtn.addEventListener('click', () => openPasswordDialog('change'));
|
||||||
|
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.className = 'ghost-btn';
|
||||||
|
removeBtn.type = 'button';
|
||||||
|
removeBtn.textContent = 'Убрать пароль';
|
||||||
|
removeBtn.disabled = settingsBusy;
|
||||||
|
removeBtn.addEventListener('click', async () => {
|
||||||
|
setSettingsBusy(true);
|
||||||
|
try {
|
||||||
|
const payload = await authService.upsertEspPairingSettings({
|
||||||
|
enabled: true,
|
||||||
|
passwordHash: '',
|
||||||
|
ttlSeconds: 180,
|
||||||
|
});
|
||||||
|
pairingPasswordConfigured = false;
|
||||||
|
saveLocalPairingPasswordState(state.session.login, state.entrySettings.shineServer, false);
|
||||||
|
setAuthInfo(`Подключение по коду без дополнительного пароля включено. TTL: ${payload?.ttlSeconds || 180} сек.`);
|
||||||
|
setStatus(status, 'Дополнительный пароль убран. Подключение по коду теперь работает без него.', 'info');
|
||||||
|
} catch (error) {
|
||||||
|
const message = toUserMessage(error, 'Не удалось убрать дополнительный пароль.');
|
||||||
|
setAuthError(message);
|
||||||
|
setStatus(status, message, 'error');
|
||||||
|
} finally {
|
||||||
|
setSettingsBusy(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
actions.append(changeBtn, removeBtn);
|
||||||
|
} else {
|
||||||
|
const setBtn = document.createElement('button');
|
||||||
|
setBtn.className = 'primary-btn';
|
||||||
|
setBtn.type = 'button';
|
||||||
|
setBtn.textContent = 'Задать дополнительный пароль';
|
||||||
|
setBtn.disabled = settingsBusy;
|
||||||
|
setBtn.addEventListener('click', () => openPasswordDialog('set'));
|
||||||
|
actions.append(setBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settingsCard.append(title, stateText, note, actions);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderRequests = () => {
|
const renderRequests = () => {
|
||||||
@ -201,10 +376,7 @@ export function render({ navigate }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setButtonsBusy = (flag) => {
|
const setButtonsBusy = (flag) => {
|
||||||
enableBtn.disabled = flag;
|
|
||||||
disableBtn.disabled = flag;
|
|
||||||
refreshBtn.disabled = flag;
|
refreshBtn.disabled = flag;
|
||||||
usePasswordInput.disabled = flag;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const approveRequest = async (request, mode) => {
|
const approveRequest = async (request, mode) => {
|
||||||
@ -223,61 +395,48 @@ export function render({ navigate }) {
|
|||||||
await reloadRequests({ silent: true });
|
await reloadRequests({ silent: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
usePasswordInput.addEventListener('change', syncPasswordUi);
|
passwordDialog.addEventListener('click', (event) => {
|
||||||
|
|
||||||
settingsCard.addEventListener('click', async (event) => {
|
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
if (!(target instanceof HTMLElement)) return;
|
if (!(target instanceof HTMLElement)) return;
|
||||||
|
if (target.dataset.action === 'close-dialog') {
|
||||||
|
closePasswordDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (target.id === 'enable-pairing-btn') {
|
dialogSaveBtn.addEventListener('click', async () => {
|
||||||
const usePassword = !!usePasswordInput.checked;
|
const password = String(dialogPasswordInput.value || '');
|
||||||
const password = String(passwordInput.value || '');
|
const confirm = String(dialogPasswordConfirmInput.value || '');
|
||||||
if (usePassword && !password) {
|
const currentMode = dialogMode;
|
||||||
setStatus(status, 'Введите pairing-пароль.', 'error');
|
if (!password) {
|
||||||
return;
|
setStatus(status, 'Введите дополнительный пароль.', 'error');
|
||||||
}
|
|
||||||
setButtonsBusy(true);
|
|
||||||
try {
|
|
||||||
const passwordHash = usePassword
|
|
||||||
? await deriveEspPairingPasswordHash(state.session.login, password)
|
|
||||||
: '';
|
|
||||||
const payload = await authService.upsertEspPairingSettings({
|
|
||||||
enabled: true,
|
|
||||||
passwordHash,
|
|
||||||
ttlSeconds: 180,
|
|
||||||
});
|
|
||||||
setAuthInfo(`Подключение по коду включено. TTL: ${payload?.ttlSeconds || 180} сек.`);
|
|
||||||
setStatus(status, usePassword
|
|
||||||
? 'Подключение по коду включено с доп. паролем.'
|
|
||||||
: 'Подключение по коду включено без доп. пароля.', 'info');
|
|
||||||
passwordInput.value = '';
|
|
||||||
} catch (error) {
|
|
||||||
const message = toUserMessage(error, 'Не удалось включить pairing.');
|
|
||||||
setAuthError(message);
|
|
||||||
setStatus(status, message, 'error');
|
|
||||||
} finally {
|
|
||||||
setButtonsBusy(false);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (password !== confirm) {
|
||||||
if (target.id === 'disable-pairing-btn') {
|
setStatus(status, 'Пароли не совпадают.', 'error');
|
||||||
setButtonsBusy(true);
|
return;
|
||||||
try {
|
}
|
||||||
await authService.upsertEspPairingSettings({
|
dialogSaveBtn.disabled = true;
|
||||||
enabled: false,
|
try {
|
||||||
passwordHash: '',
|
const passwordHash = await deriveEspPairingPasswordHash(state.session.login, password);
|
||||||
ttlSeconds: 180,
|
const payload = await authService.upsertEspPairingSettings({
|
||||||
});
|
enabled: true,
|
||||||
setAuthInfo('Подключение по коду выключено.');
|
passwordHash,
|
||||||
setStatus(status, 'Подключение по коду выключено.', 'info');
|
ttlSeconds: 180,
|
||||||
} catch (error) {
|
});
|
||||||
const message = toUserMessage(error, 'Не удалось выключить pairing.');
|
pairingPasswordConfigured = true;
|
||||||
setAuthError(message);
|
saveLocalPairingPasswordState(state.session.login, state.entrySettings.shineServer, true);
|
||||||
setStatus(status, message, 'error');
|
closePasswordDialog();
|
||||||
} finally {
|
renderSettingsCard();
|
||||||
setButtonsBusy(false);
|
setAuthInfo(`Подключение по коду включено с дополнительным паролем. TTL: ${payload?.ttlSeconds || 180} сек.`);
|
||||||
}
|
setStatus(status, currentMode === 'change'
|
||||||
|
? 'Дополнительный пароль изменён.'
|
||||||
|
: 'Дополнительный пароль задан.', 'info');
|
||||||
|
} catch (error) {
|
||||||
|
const message = toUserMessage(error, 'Не удалось сохранить дополнительный пароль.');
|
||||||
|
setAuthError(message);
|
||||||
|
setStatus(status, message, 'error');
|
||||||
|
} finally {
|
||||||
|
dialogSaveBtn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -322,7 +481,7 @@ export function render({ navigate }) {
|
|||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
syncPasswordUi();
|
renderSettingsCard();
|
||||||
await loadSavedKeys();
|
await loadSavedKeys();
|
||||||
await reloadRequests({ silent: true });
|
await reloadRequests({ silent: true });
|
||||||
cleanupEvent = authService.onEvent('IncomingEspPairingRequest', () => {
|
cleanupEvent = authService.onEvent('IncomingEspPairingRequest', () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user