238 lines
		
	
	
		
			No EOL
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			No EOL
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * @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);
 | 
						|
}); |