2025-10-14 16:04:37 +03:00
|
|
|
/**
|
|
|
|
|
* @file script.js
|
|
|
|
|
* @version 0.1.0
|
|
|
|
|
*/
|
|
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
class TerminalCard {
|
2025-10-30 10:00:52 +03:00
|
|
|
constructor(folder) {
|
|
|
|
|
this.folder = folder;
|
2025-10-30 22:41:27 +03:00
|
|
|
this.data = { status: 'stopped' };
|
2025-10-30 10:00:52 +03:00
|
|
|
this.$el = $(`[data-folder="${folder}"]`); // jQuery-объект элемента
|
2025-10-24 02:13:35 +03:00
|
|
|
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')
|
|
|
|
|
}
|
2025-10-14 22:04:04 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
onStartBefore() {
|
|
|
|
|
this.$btnStart
|
|
|
|
|
.prop('disabled', true)
|
|
|
|
|
.find('i').removeClass('fa-play').addClass('fa-spinner fa-spin');
|
|
|
|
|
}
|
2025-10-14 22:04:04 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
onStartAfter() {
|
|
|
|
|
this.$btnStart.prop('disabled', true).find('i').removeClass('fa-spinner fa-spin').addClass('fa-play');
|
|
|
|
|
this.$btnStop.prop('disabled', false);
|
|
|
|
|
}
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
onStartCancel() {
|
|
|
|
|
this.$btnStart.prop('disabled', false);
|
|
|
|
|
}
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
onStopBefore() {
|
|
|
|
|
this.$btnStop.prop('disabled', true).find('i').removeClass('fa-pause').addClass('fa-spinner fa-spin');
|
|
|
|
|
}
|
2025-10-14 22:04:04 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
onStopAfter() {
|
|
|
|
|
this.$btnStart.prop('disabled', false);
|
|
|
|
|
this.$btnStop.prop('disabled', true).find('i').removeClass('fa-spinner fa-spin').addClass('fa-pause');;
|
|
|
|
|
}
|
2025-10-14 22:04:04 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
onStopCancel() {
|
|
|
|
|
this.$btnStop.prop('disabled', false);
|
|
|
|
|
}
|
2025-10-10 10:16:59 +03:00
|
|
|
|
2025-10-30 22:41:27 +03:00
|
|
|
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}`;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
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}`);
|
2025-10-30 22:41:27 +03:00
|
|
|
|
|
|
|
|
this.$el.find('.time').text(this.formatDateTime(data.last_update));
|
|
|
|
|
|
2025-10-24 15:36:01 +03:00
|
|
|
this.$el.find('.balance').text(new Intl.NumberFormat('ru-RU', {
|
2025-10-30 22:41:27 +03:00
|
|
|
minimumFractionDigits: 2,
|
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
|
useGrouping: true
|
|
|
|
|
}).format(data.account.balance) + ' USD');
|
|
|
|
|
|
2025-10-24 15:36:01 +03:00
|
|
|
this.$el.find('.equity').text(new Intl.NumberFormat('ru-RU', {
|
2025-10-30 22:41:27 +03:00
|
|
|
minimumFractionDigits: 2,
|
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
|
useGrouping: true
|
|
|
|
|
}).format(data.account.equity));
|
|
|
|
|
|
2025-10-24 15:36:01 +03:00
|
|
|
this.$el.find('.profit').text(new Intl.NumberFormat('ru-RU', {
|
2025-10-30 22:41:27 +03:00
|
|
|
minimumFractionDigits: 2,
|
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
|
useGrouping: true,
|
|
|
|
|
signDisplay: 'always'
|
|
|
|
|
}).format(data.account.profit))
|
2025-10-24 15:36:01 +03:00
|
|
|
.removeClass('neg')
|
|
|
|
|
.addClass((a, b) => (data.account.profit < 0 ? 'neg' : ''));
|
2025-10-30 22:41:27 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
this.$el.find('.profit-pct').text(
|
|
|
|
|
new Intl.NumberFormat('en-EN', {
|
|
|
|
|
style: 'percent',
|
|
|
|
|
minimumFractionDigits: 2,
|
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
|
useGrouping: true,
|
|
|
|
|
signDisplay: 'always'
|
2025-10-24 15:36:01 +03:00
|
|
|
}).format(profitPct))
|
|
|
|
|
.removeClass('neg')
|
|
|
|
|
.addClass((a, b) => (data.account.profit < 0 ? 'neg' : ''));
|
2025-10-24 02:13:35 +03:00
|
|
|
|
2025-10-30 22:41:27 +03:00
|
|
|
this.$el.find('.margin').text(new Intl.NumberFormat('ru-RU', {
|
|
|
|
|
minimumFractionDigits: 2,
|
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
|
useGrouping: true
|
|
|
|
|
}).format(data.account.margin));
|
|
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
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');
|
2025-10-14 14:16:34 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
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);
|
|
|
|
|
|
2025-10-30 22:41:27 +03:00
|
|
|
if (data.status == 'running') {
|
2025-10-24 02:13:35 +03:00
|
|
|
this.onStartAfter();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.data = data
|
|
|
|
|
}
|
2025-10-21 16:08:01 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
showLoadedState() {
|
|
|
|
|
this.$el.removeClass('loading-card').addClass('loaded-card');
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-21 16:08:01 +03:00
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
const cards = {};
|
2025-10-21 16:08:01 +03:00
|
|
|
let updateInterval;
|
|
|
|
|
|
2025-10-21 21:24:09 +03:00
|
|
|
$(document).ready(function () {
|
2025-10-21 16:08:01 +03:00
|
|
|
// Загрузка данных при открытии страницы
|
2025-10-30 10:00:52 +03:00
|
|
|
terminals.forEach(function (folder) {
|
|
|
|
|
cards[folder] = new TerminalCard(folder);
|
|
|
|
|
fetchTerminalInfo(folder);
|
2025-10-21 16:08:01 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Запуск автоматического обновления каждые 10 секунд
|
2025-10-21 21:24:09 +03:00
|
|
|
updateInterval = setInterval(function () {
|
2025-10-30 10:00:52 +03:00
|
|
|
terminals.forEach(function (folder) {
|
|
|
|
|
fetchTerminalInfo(folder);
|
2025-10-21 16:08:01 +03:00
|
|
|
});
|
2025-10-21 21:24:09 +03:00
|
|
|
}, 10000);
|
2025-10-21 16:08:01 +03:00
|
|
|
});
|
|
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
|
|
|
|
|
|
2025-10-30 10:00:52 +03:00
|
|
|
async function fetchTerminalInfo(folder) {
|
|
|
|
|
$.post(`/instances/${folder}`, function (data) {
|
2025-10-21 16:08:01 +03:00
|
|
|
if (data.error) {
|
|
|
|
|
console.error("Ошибка:", data.error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 10:00:52 +03:00
|
|
|
const card = cards[folder];
|
2025-10-24 02:13:35 +03:00
|
|
|
card.updateData(data);
|
|
|
|
|
card.showLoadedState();
|
2025-10-21 16:08:01 +03:00
|
|
|
})
|
2025-10-21 21:24:09 +03:00
|
|
|
.fail(function (xhr, status, error) {
|
|
|
|
|
console.error("Ошибка при получении данных:", error);
|
|
|
|
|
// Опционально: показать ошибку
|
|
|
|
|
});
|
2025-10-21 16:08:01 +03:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 10:00:52 +03:00
|
|
|
function startTerminal(folder) {
|
|
|
|
|
cards[folder].onStartBefore();
|
|
|
|
|
// Здесь вызов POST /start/{folder}
|
|
|
|
|
$.post(`/start/${folder}`, function (data) {
|
|
|
|
|
// toastr.success(`Terminal ${folder} started`);
|
2025-10-24 02:13:35 +03:00
|
|
|
// Обновляем статус кнопки
|
2025-10-30 10:00:52 +03:00
|
|
|
fetchTerminalInfo(folder);
|
2025-10-30 22:41:27 +03:00
|
|
|
//cards[folder].onStartAfter();
|
2025-10-24 02:13:35 +03:00
|
|
|
})
|
|
|
|
|
.fail(function () {
|
2025-10-30 10:00:52 +03:00
|
|
|
toastr.error(`Не удалось запустить терминал ${folder}`);
|
|
|
|
|
cards[folder].onStartCancel();
|
2025-10-24 02:13:35 +03:00
|
|
|
});
|
|
|
|
|
}
|
2025-10-21 16:08:01 +03:00
|
|
|
|
2025-10-30 10:00:52 +03:00
|
|
|
function stopTerminal(folder) {
|
|
|
|
|
cards[folder].onStopBefore();
|
|
|
|
|
$.post(`/stop/${folder}`, function (data) {
|
|
|
|
|
//toastr.success(`Terminal ${folder} stopped`);
|
2025-10-24 02:13:35 +03:00
|
|
|
// Обновляем статус кнопки
|
2025-10-30 10:00:52 +03:00
|
|
|
fetchTerminalInfo(folder);
|
|
|
|
|
cards[folder].onStopAfter();
|
2025-10-24 02:13:35 +03:00
|
|
|
})
|
|
|
|
|
.fail(function () {
|
2025-10-30 10:00:52 +03:00
|
|
|
toastr.error(`Не удалось запустить терминал ${folder}`);
|
|
|
|
|
cards[folder].onStopCancel();
|
2025-10-24 02:13:35 +03:00
|
|
|
});
|
2025-10-21 16:08:01 +03:00
|
|
|
}
|
|
|
|
|
|
2025-10-24 02:13:35 +03:00
|
|
|
|
|
|
|
|
|
2025-10-21 16:08:01 +03:00
|
|
|
// Остановить обновления при закрытии вкладки (опционально)
|
2025-10-21 21:24:09 +03:00
|
|
|
$(window).on("beforeunload", function () {
|
2025-10-21 16:08:01 +03:00
|
|
|
clearInterval(updateInterval);
|
2025-10-21 21:24:09 +03:00
|
|
|
});
|