mt5-manager/static/manager.js
Yuriy Bykov Yuriy Bykov 930b46b3c1 Добавить создание терминалов, обновление сервиса и документацию
- реализовать создание portable-экземпляров и настройку счетов
- обновить интерфейс и добавить обновление проекта через Git
- добавить сборку и распространение сервиса
- организовать материалы статей и описать актуальную версию
- запрашивать тестовый пароль интерактивно и расширить проверки
2026-10-29 14:39:39 +03:00
86 lines
3.9 KiB
JavaScript
/** @file manager.js @version 0.2.0 */
function managerIcon(name) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.classList.add('icon');
svg.setAttribute('aria-hidden', 'true');
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
use.setAttribute('href', '#icon-' + name);
svg.append(use);
return svg;
}
async function managerRequest(url, options = {}) {
const response = await fetch(url, options);
let data;
try { data = await response.json(); }
catch { throw new Error('Сервер вернул неожиданный ответ. Попробуйте обновить страницу.'); }
if (!response.ok) {
throw new Error(typeof data.detail === 'string' ? data.detail : 'Проверьте введённые значения.');
}
return data;
}
function managerMessage(text, type = 'info') {
const element = document.getElementById('operation-message');
if (element.dataset.message === text && element.dataset.type === type && !element.classList.contains('is-hidden')) return;
element.dataset.message = text;
element.dataset.type = type;
element.className = 'notice notice-' + type;
const indicator = type === 'info' ? document.createElement('span') : managerIcon(type === 'success' ? 'check' : 'terminal');
if (type === 'info') { indicator.className = 'spinner'; indicator.setAttribute('aria-hidden', 'true'); }
const copy = document.createElement('span');
copy.className = 'notice-copy';
copy.textContent = text;
const close = document.createElement('button');
close.type = 'button';
close.className = 'icon-button';
close.setAttribute('aria-label', 'Скрыть сообщение');
close.append(managerIcon('close'));
close.addEventListener('click', () => element.classList.add('is-hidden'));
element.replaceChildren(indicator, copy, close);
}
function managerFormError(form, text = '') {
const element = form.querySelector('.form-feedback');
if (!element) return;
element.textContent = text;
element.classList.toggle('is-hidden', !text);
}
async function managerJob(job) {
while (true) {
const warnings = (job.warnings || []).join(' ');
if (job.status === 'error') throw new Error(job.error);
if (job.status === 'done') {
managerMessage(job.folder + ': готово.' + (warnings ? ' ' + warnings : ''), warnings ? 'warning' : 'success');
return job;
}
managerMessage(job.folder + ': ' + job.stage + (warnings ? '. ' + warnings : ''), warnings ? 'warning' : 'info');
await new Promise(resolve => setTimeout(resolve, 1000));
job = await managerRequest('/api/jobs/' + encodeURIComponent(job.id));
}
}
function managerJson(method, data) {
return {method, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data)};
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-dialog-open]').forEach(button => {
button.addEventListener('click', () => {
const dialog = document.getElementById(button.dataset.dialogOpen);
managerFormError(dialog);
dialog.showModal();
});
});
document.querySelectorAll('[data-dialog-close]').forEach(button => {
button.addEventListener('click', () => button.closest('dialog').close());
});
document.querySelectorAll('dialog').forEach(dialog => {
dialog.addEventListener('click', event => { if (event.target === dialog) {
const rect = dialog.getBoundingClientRect();
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close();
} });
dialog.addEventListener('close', () => dialog.querySelectorAll('input[type=password]').forEach(input => { input.value = ''; }));
});
});