{% extends ../base.html %} {% block meta %} {% end %} {% block title %}Crafty Controller - {{ translate('serverDetails', 'serverDetails', data['lang']) }}{% end %} {% block content %} <div class="content-wrapper"> <!-- Page Title Header Starts--> <div class="row page-title-header"> <div class="col-12"> <div class="page-header"> <h4 class="page-title"> {{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }} <br /> <small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small> </h4> </div> </div> </div> <!-- Page Title Header Ends--> {% include "parts/details_stats.html %} <div class="row"> <div class="col-sm-12 grid-margin"> <div class="card"> <div class="card-body pt-0"> <span class="d-none d-sm-block"> {% include "parts/server_controls_list.html %} </span> <span class="d-block d-sm-none"> {% include "parts/m_server_controls_list.html %} </span> <div class="col-md-12"> <button id="to-bottom" style="visibility: hidden; float: right;" class="btn btn-outline-success" hidden>{{ translate('serverDetails', 'reset', data['lang']) }}</button> <div class="input-group"> <div id="virt_console" class="" style="width: 100%; font-size: .8em; padding: 5px 10px; border: 1px solid var(--outline); background-color:var(--card-banner-bg);height:500px; overflow: scroll;"> </div> </div> <br /> <div style="gap: 0.5rem;" class="input-group flex-wrap"> <input style="min-width: 10rem;" type="text" class="form-control" id="server_command" name="server_command" placeholder="{{ translate('serverTerm', 'commandInput', data['lang']) }}" autofocus=""> <span class="input-group-btn ml-5"> <button id="submit" class="btn btn-sm btn-info" type="button">{{ translate('serverTerm', 'sendCommand', data['lang']) }}</button> </span> </div> {% if data['permissions']['Commands'] in data['user_permissions'] %} {% if data['importing'] and data['server_stats']['running']%} <div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible"> <button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{translate('serverTerm', 'installing', data['lang']) }}</button> <button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> </div> {% elif data['server_stats']['updating']%} <div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible"> <button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'updating', data['lang']) }}</button> <button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> </div> {% elif data['waiting_start'] %} <div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible"> <button onclick="" id="start-btn" style="max-width: 7rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled" data-toggle="tooltip" title="{{ translate('serverTerm', 'delay-explained', data['lang'])}}">{{ translate('serverTerm', 'starting', data['lang']) }}</button> <button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> </div> {% elif data['importing'] %} <div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible"> <button onclick="" id="start-btn" style="max-width: 12rem; white-space: nowrap;" class="btn btn-secondary m-1 flex-grow-1 disabled"><i class="fa fa-spinner fa-spin"></i> {{ translate('serverTerm', 'importing', data['lang']) }}</button> <button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button> </div> {% else %} <div id="control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible"> <button onclick="send_command(serverId, 'start_server');" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate('serverTerm', 'start', data['lang']) }}</button> <button onclick="send_command(serverId, 'restart_server');" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate('serverTerm', 'restart', data['lang']) %}</button> <button onclick="send_command(serverId, 'stop_server');" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1">{{ translate('serverTerm', 'stop', data['lang']) }}</button> </div> {% end %} {% end %} </div> </div> </div> </div> </div> </div> <style> #virt_console::-webkit-scrollbar { display: none; } /* Hide scrollbar for IE, Edge and Firefox */ #virt_console { -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace; white-space: pre-wrap; } </style> <!-- content-wrapper ends --> {% end %} {% block js %} <script> const serverId = new URLSearchParams(document.location.search).get('id') function send_command(serverId, command) { if (command == 'start_server') { startBtn.setAttribute('disabled', 'disabled'); restartBtn.removeAttribute('disabled'); stopBtn.removeAttribute('disabled'); } if (command == 'stop_server') { startBtn.removeAttribute('disabled'); restartBtn.setAttribute('disabled', 'disabled'); stopBtn.setAttribute('disabled', 'disabled'); } //<!-- this getCookie function is in base.html--> const token = getCookie("_xsrf"); $.ajax({ type: "POST", headers: { 'X-XSRFToken': token }, url: `/api/v2/servers/${serverId}/action/${command}`, success: function (data) { console.log("got response:"); console.log(data); } }); } if (webSocket) { webSocket.on('update_button_status', function (updateButton) { if (updateButton.isUpdating) { if (updateButton.server_id == serverId) { console.log(updateButton.isUpdating) document.getElementById('control_buttons').innerHTML = '<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "updating", data["lang"]) }}</button><button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>'; } } else if (updateButton.server_id == serverId) { window.location.reload() document.getElementById('update_control_buttons').innerHTML = '<button onclick="send_command(serverId, "start_server");" id="start-btn" style="max-width: 7rem;" class="btn btn-primary m-1 flex-grow-1">{{ translate("serverTerm", "start", data["lang"]) }}</button><button onclick="send_command(serverId, "restart_server");" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1">{% raw translate("serverTerm", "restart", data["lang"]) %}</button><button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate("serverTerm", "stop", data["lang"]) }}</button>'; } }); } // Convert running to lower case (example: 'True' converts to 'true') and // then to boolean via JSON.parse() let online = JSON.parse("{{ data['server_stats']['running'] }}".toLowerCase()); let startBtn = document.querySelector('#start-btn'); let restartBtn = document.querySelector('#restart-btn'); let stopBtn = document.querySelector('#stop-btn'); //{% if data['permissions']['Commands'] in data['user_permissions'] %} if (online) { startBtn.setAttribute('disabled', 'disabled'); restartBtn.removeAttribute('disabled'); stopBtn.removeAttribute('disabled'); } else { startBtn.removeAttribute('disabled'); restartBtn.setAttribute('disabled', 'disabled'); stopBtn.setAttribute('disabled', 'disabled'); } if (webSocket) { webSocket.on('send_start_reload', function () { location.reload() }); } //{% end %} async function get_server_log() { const token = getCookie("_xsrf") let colors = true; let res = await fetch(`/api/v2/servers/${serverId}/logs?colors=${colors}`, { method: 'GET', headers: { 'X-XSRFToken': token }, }); let responseData = await res.json(); let html = `` if (responseData.status === "ok") { for (let value of responseData.data) { html += `<span class='box'>${value}<br /></span>` } console.log('Got Log From Server') $('#virt_console').html(html); scrollConsole(); } else { bootbox.alert({ title: responseData.status, message: responseData.error }); } } function new_line_handler(data) { $('#virt_console').append(data.line) const elem = document.getElementById('virt_console'); try { if (!scrolled) { scrollConsole(); } } catch { scrollConsole(); } } //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security function getCookie(name) { let r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } $(document).ready(function () { console.log("ready!"); get_server_log() webSocket.on('vterm_new_line', new_line_handler) }); $('#server_command').on('keydown', function (e) { if (e.which == 13) { $(this).attr("disabled", "disabled"); //Disable textbox to prevent multiple submit sendConsoleCommand() $(this).removeAttr("disabled"); //Enable the textbox again if needed. $(this).focus(); } else if (e.which == 38) { e.preventDefault(); $('#server_command').val(cmdHistory.getPrev()); } else if (e.which == 40) { e.preventDefault(); $('#server_command').val(cmdHistory.getNext()); } }); $("#submit").click(function (e) { e.preventDefault(); sendConsoleCommand(); }); function scrollConsole() { let logview = $('#virt_console'); if (logview.length) logview.scrollTop(logview[0].scrollHeight - logview.height()); } async function sendConsoleCommand() { let serverCommand = $("#server_command").val() console.log(serverCommand) cmdHistory.push(serverCommand); let token = getCookie("_xsrf") let formdata = new FormData(); formdata.append('command', serverCommand) console.log('sending command: ' + serverCommand) let res = await fetch(`/api/v2/servers/${serverId}/stdin`, { method: 'POST', headers: { 'X-XSRFToken': token }, body: serverCommand, }); let responseData = await res.text(); console.log("got response:"); console.log(responseData); $("#server_command").val('') } const cmdHistory = { history: [], current: 0, push: function (cmd) { this.history.push(cmd); this.current = this.history.length - 1; }, getPrev: function () { const prevCommand = this.history[this.current]; this.current--; if (this.current < 0) this.current = 0; return prevCommand; }, getNext: function () { this.current++; if (this.current > (this.history.length - 1)) { this.current = (this.history.length - 1); return ''; } const nextCommand = this.history[this.current]; return nextCommand; } } const chkScroll = (e) => { const elem = $(e.currentTarget); if (Math.round(elem[0].scrollHeight - elem.scrollTop()) <= elem.outerHeight()) { document.getElementById("to-bottom").style.visibility = "hidden"; document.getElementById("to-bottom").hidden = true; scrolled = false; } else { document.getElementById("to-bottom").style.visibility = "visible"; document.getElementById("to-bottom").hidden = false; scrolled = true; } } const scrollToBottom = (id) => { const element = $(`#virt_console`); element.animate({ scrollTop: element.prop("scrollHeight") }, 500); } $(document).ready(() => { $('#virt_console').on('scroll', chkScroll); $('#to-bottom').on('click', scrollToBottom) }); </script> {% end %}