/** * @file script.js * @version 0.1.0 */ class TerminalCard { constructor(folder) { this.folder = folder; this.data = { status: 'stopped' }; this.$el = $(`[data-folder="${folder}"]`); // jQuery-объект элемента this.$loadingContent = this.$el.find('.loading-content'); this.$loadedContent = this.$el.find('.loaded-content'); this.$btnStart = this.$el.find('.btn-start'); this.$btnStop = this.$el.find('.btn-stop'); this.$status = this.$el.find('.status') } onStartBefore() { this.$btnStart .prop('disabled', true) .find('i').removeClass('fa-play').addClass('fa-spinner fa-spin'); } onStartAfter() { this.$btnStart.prop('disabled', true).find('i').removeClass('fa-spinner fa-spin').addClass('fa-play'); this.$btnStop.prop('disabled', false); } onStartCancel() { this.$btnStart.prop('disabled', false); } onStopBefore() { this.$btnStop.prop('disabled', true).find('i').removeClass('fa-pause').addClass('fa-spinner fa-spin'); } onStopAfter() { this.$btnStart.prop('disabled', false); this.$btnStop.prop('disabled', true).find('i').removeClass('fa-spinner fa-spin').addClass('fa-pause');; } onStopCancel() { this.$btnStop.prop('disabled', false); } formatDateTime(dateTimeStr) { const date = new Date(dateTimeStr); const now = new Date(); // Проверяем, был ли момент времени сегодня const isToday = date.toDateString() === now.toDateString(); if (isToday) { // Формат "HH:MM:SS" const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${hours}:${minutes}:${seconds}`; } // Иначе формат "YYYY-MM-DD HH:MM:SS" const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } updateData(data) { // Обновляем содержимое карточки if (data.account) { let profitPct = (data.account.profit / data.account.balance) // Обновляем данные в загруженном шаблоне this.$el.find('.account').text(`...` + `${data.account.login}`.slice(-4)) .attr('title', `${data.account.server}: ${data.account.login}`); this.$el.find('.time').text(this.formatDateTime(data.last_update)); this.$el.find('.balance').text(new Intl.NumberFormat('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2, useGrouping: true }).format(data.account.balance) + ' USD'); this.$el.find('.equity').text(new Intl.NumberFormat('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2, useGrouping: true }).format(data.account.equity)); this.$el.find('.profit').text(new Intl.NumberFormat('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2, useGrouping: true, signDisplay: 'always' }).format(data.account.profit)) .removeClass('neg') .addClass((a, b) => (data.account.profit < 0 ? 'neg' : '')); this.$el.find('.profit-pct').text( new Intl.NumberFormat('en-EN', { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2, useGrouping: true, signDisplay: 'always' }).format(profitPct)) .removeClass('neg') .addClass((a, b) => (data.account.profit < 0 ? 'neg' : '')); this.$el.find('.margin').text(new Intl.NumberFormat('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2, useGrouping: true }).format(data.account.margin)); this.$el.find('.company').addClass(`company-${data.account.server}`) .attr('title', data.account.company); } else { // Обновляем данные в загруженном шаблоне this.$loadedContent.find('.account').text(data.login); this.$loadedContent.find('.server').text(data.server); } if (data.status == 'running') { this.$btnStart.prop('disabled', true); this.$btnStop.prop('disabled', false); } else { this.$btnStart.prop('disabled', false); this.$btnStop.prop('disabled', true); } const icon = this.$status.find('i'); const desc = this.$status.find('.description'); icon.removeClass('fa-circle-check fa-stop fa-circle-exclamation fa-spin'); if (data.last_error.code == 1) { this.$status.removeClass('text-danger text-secondary').addClass('text-success'); icon.addClass('fa-circle-check') } else if (data.last_error.code == 0) { this.$status.removeClass('text-danger text-success').addClass('text-secondary'); icon.addClass('fa-stop') } else { this.$status.removeClass('text-success text-secondary').addClass('text-danger'); icon.addClass('fa-circle-exclamation') } desc.text(data.last_error.description); if (data.status == 'running') { this.onStartAfter(); } this.data = data } showLoadedState() { this.$el.removeClass('loading-card').addClass('loaded-card'); } } const cards = {}; let updateInterval; $(document).ready(function () { // Загрузка данных при открытии страницы terminals.forEach(function (folder) { cards[folder] = new TerminalCard(folder); fetchTerminalInfo(folder); }); // Запуск автоматического обновления каждые 10 секунд updateInterval = setInterval(function () { terminals.forEach(function (folder) { fetchTerminalInfo(folder); }); }, 10000); }); async function fetchTerminalInfo(folder) { $.post(`/instances/${folder}`, function (data) { if (data.error) { console.error("Ошибка:", data.error); return; } const card = cards[folder]; card.updateData(data); card.showLoadedState(); }) .fail(function (xhr, status, error) { console.error("Ошибка при получении данных:", error); // Опционально: показать ошибку }); } function startTerminal(folder) { cards[folder].onStartBefore(); // Здесь вызов POST /start/{folder} $.post(`/start/${folder}`, function (data) { // toastr.success(`Terminal ${folder} started`); // Обновляем статус кнопки fetchTerminalInfo(folder); //cards[folder].onStartAfter(); }) .fail(function () { toastr.error(`Не удалось запустить терминал ${folder}`); cards[folder].onStartCancel(); }); } function stopTerminal(folder) { cards[folder].onStopBefore(); $.post(`/stop/${folder}`, function (data) { //toastr.success(`Terminal ${folder} stopped`); // Обновляем статус кнопки fetchTerminalInfo(folder); cards[folder].onStopAfter(); }) .fail(function () { toastr.error(`Не удалось запустить терминал ${folder}`); cards[folder].onStopCancel(); }); } // Остановить обновления при закрытии вкладки (опционально) $(window).on("beforeunload", function () { clearInterval(updateInterval); });