{% 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="row"> <div class="col-md-6 col-sm-12"> <br> <br> <form class="forms-sample" method="post" action="/panel/server_backup"> {% raw xsrf_form_html() %} <input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}"> <input type="hidden" name="subpage" value="backup"> {% if data['backing_up'] %} <div class="progress" style="height: 15px;"> <div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar" role="progressbar" style="width:{{data['backup_stats']['percent']}}%;" aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{ data['backup_stats']['percent'] }}%</div> </div> <p>Backing up <i class="fas fa-spin fa-spinner"></i> <span id="total_files">{{data['server_stats']['world_size']}}</span></p> {% end %} <br> {% if not data['backing_up'] %} <div id="backup_button" class="form-group"> <button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow', data['lang']) }}</button> </div> {% end %} <div class="form-group"> {% if data['super_user'] %} <label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang']) }}</small> </label> <input type="text" class="form-control" name="backup_path" id="backup_path" value="{{ data['server_stats']['server_id']['backup_path'] }}" placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}"> {% end %} </div> <div class="form-group"> <label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang']) }}</small> </label> <input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}"> </div> <div class="form-group"> <label for="compress" class="form-check-label ml-4 mb-4"></label> {% if data['backup_config']['compress'] %} <input type="checkbox" class="form-check-input" id="compress" name="compress" checked="" value="True">{{ translate('serverBackups', 'compress', data['lang']) }} {% else %} <input type="checkbox" class="form-check-input" id="compress" name="compress" value="True">{{ translate('serverBackups', 'compress', data['lang']) }} {% end %} </div> <div class="form-group"> <label for="shutdown" class="form-check-label ml-4 mb-4"></label> {% if data['backup_config']['shutdown'] %} <input type="checkbox" class="form-check-input" id="shutdown" name="shutdown" checked="" value="True">{{ translate('serverBackups', 'shutdown', data['lang']) }} {% else %} <input type="checkbox" class="form-check-input" id="shutdown" name="shutdown" value="True">{{ translate('serverBackups', 'shutdown', data['lang']) }} {% end %} </div> <div class="form-group"> <label for="command-check" class="form-check-label ml-4 mb-4"></label> {% if data['backup_config']['before'] %} <input type="checkbox" class="form-check-input" id="before-check" name="before-check" checked>{{ translate('serverBackups', 'before', data['lang']) }} <br> <input type="text" class="form-control" name="backup_before" id="backup_before" value="{{ data['backup_config']['before'] }}" placeholder="We enter the / for you" style="display: inline-block;"> {% else %} <input type="checkbox" class="form-check-input" id="before-check" name="before-check">{{ translate('serverBackups', 'before', data['lang']) }} <br> <input type="text" class="form-control" name="backup_before" id="backup_before" value="" placeholder="We enter the / for you." style="display: none;"> {% end %} </div> <div class="form-group"> <label for="command-check" class="form-check-label ml-4 mb-4"></label> {% if data['backup_config']['after'] %} <input type="checkbox" class="form-check-input" id="after-check" name="after-check" checked>{{ translate('serverBackups', 'after', data['lang']) }} <br> <input type="text" class="form-control" name="backup_after" id="backup_after" value="{{ data['backup_config']['after'] }}" placeholder="We enter the / for you" style="display: inline-block;"> {% else %} <input type="checkbox" class="form-check-input" id="after-check" name="after-check">{{ translate('serverBackups', 'after', data['lang']) }} <br> <input type="text" class="form-control" name="backup_after" id="backup_after" value="" placeholder="We enter the / for you." style="display: none;"> {% end %} </div> <div class="form-group"> <label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{ translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label> <br> <button class="btn btn-primary mr-2" id="root_files_button" data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{ translate('serverBackups', 'clickExclude', data['lang']) }}</button> </div> <input type="number" class="form-control" name="changed" id="changed" value="0" style="visibility: hidden;"></input> <div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups', 'excludedChoose', data['lang']) }}</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;"> <input type="checkbox" id="main-tree-input" name="root_path" value="" disabled> <span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""> <i class="far fa-folder"></i> <i class="far fa-folder-open"></i> {{ translate('serverFiles', 'files', data['lang']) }} </span> </input> </div> </div> <div class="modal-footer"> <button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverBackups', 'cancel', data['lang']) }}</button> <button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button> </div> </div> </div> </div> <button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang']) }}</button> <button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button> </form> </div> <div class="col-md-6 col-sm-12"> <div class="text-center"> <table class="table table-responsive dataTable" id="backup_table"> <h4 class="card-title">{{ translate('serverBackups', 'currentBackups', data['lang']) }}</h4> <thead> <tr> <th width="10%">{{ translate('serverBackups', 'options', data['lang']) }}</th> <th>{{ translate('serverBackups', 'path', data['lang']) }}</th> <th width="20%">{{ translate('serverBackups', 'size', data['lang']) }}</th> </tr> </thead> <tbody> {% for backup in data['backup_list'] %} <tr> <td> <a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary"> <i class="fas fa-download" aria-hidden="true"></i> {{ translate('serverBackups', 'download', data['lang']) }} </a> <br> <br> <button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}" class="btn btn-danger del_button"> <i class="fas fa-trash" aria-hidden="true"></i> {{ translate('serverBackups', 'delete', data['lang']) }} </button> <button data-file="{{ backup['path'] }}" class="btn btn-warning restore_button"> <i class="fas fa-undo-alt" aria-hidden="true"></i> {{ translate('serverBackups', 'restore', data['lang']) }} </button> </td> <td>{{ backup['path'] }}</td> <td>{{ backup['size'] }}</td> </tr> {% end %} </tbody> </table> </div> </div> </div> <div class="col-md-12 col-sm-12"> <br> <br> <div class="card-header header-sm d-flex justify-content-between align-items-center"> <h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups', data['lang']) }} <small class="text-muted ml-1"></small> </h4> </div> <br> <ul> {% for item in data['exclusions'] %} <li>{{item}}</li> <br> {% end %} </ul> </div> </div> </div> </div> </div> </div> <style> /* Remove default bullets */ .tree-view, .tree-nested { list-style-type: none; margin: 0; padding: 0; margin-left: 10px; } /* Style the items */ .tree-item, .files-tree-title { cursor: pointer; user-select: none; /* Prevent text selection */ } /* Create the caret/arrow with a unicode, and style it */ .tree-caret .fa-folder { display: inline-block; } .tree-caret .fa-folder-open { display: none; } /* Rotate the caret/arrow icon when clicked on (using JavaScript) */ .tree-caret-down .fa-folder { display: none; } .tree-caret-down .fa-folder-open { display: inline-block; } /* Hide the nested list */ .tree-nested { display: none; } </style> <!-- content-wrapper ends --> {% end %} {% block js %} <script> const server_id = new URLSearchParams(document.location.search).get('id') //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } function backup_started() { var token = getCookie("_xsrf") document.getElementById('backup_button').style.visibility = 'hidden'; var dialog = bootbox.dialog({ message: "{{ translate('serverBackups', 'backupTask', data['lang']) }}", closeButton: false }); $.ajax({ type: "POST", headers: { 'X-XSRFToken': token }, url: `/api/v2/servers/${server_id}/action/backup_server`, success: function (data) { return; }, }); return; } function del_backup(filename, id) { var token = getCookie("_xsrf") data_to_send = { file_name: filename } console.log('Sending Command to delete backup: ' + filename) $.ajax({ type: "DELETE", headers: { 'X-XSRFToken': token }, url: '/ajax/del_backup?server_id=' + id, data: { file_path: filename, id: id }, success: function (data) { location.reload(); }, }); } function restore_backup(filename, id) { var token = getCookie("_xsrf") var dialog = bootbox.dialog({ message: "<i class='fa fa-spin fa-spinner'></i> {{ translate('serverBackups', 'restoring', data['lang']) }}", closeButton: false }); console.log('Sending Command to restore backup: ' + filename) $.ajax({ type: "POST", headers: { 'X-XSRFToken': token }, url: '/ajax/restore_backup?server_id=' + id, data: { zip_file: filename, id: id }, success: function (data) { setTimeout(function () { location.href = ('/panel/dashboard'); }, 15000); }, }); } $("#before-check").on("click", function () { if ($("#before-check:checked").val()) { $("#backup_before").css("display", "inline-block"); } else { $("#backup_before").css("display", "none"); $("#backup_before").val(""); } }); $("#after-check").on("click", function () { if ($("#after-check:checked").val()) { $("#backup_after").css("display", "inline-block"); } else { $("#backup_after").css("display", "none"); $("#backup_after").val(""); } }); $(document).ready(function () { try { if ($('#backup_path').val() == '') { console.log('true') try { document.getElementById('backup_now_button').disabled = true; } catch { } } else { document.getElementById('backup_now_button').disabled = false; } } catch { try { document.getElementById('backup_now_button').disabled = false; } catch { } } console.log("ready!"); $("#backup_config_box").hide(); $("#backup_save_note").hide(); $("#show_config").click(function () { $("#backup_config_box").toggle(); $('#backup_button').hide(); $('#backup_save_note').show(); $('#backup_data').hide(); }); $('#backup_table').DataTable({ "order": [[1, "desc"]], "paging": false, "lengthChange": false, "searching": true, "ordering": true, "info": true, "autoWidth": false, "responsive": true, }); $(".del_button").click(function () { var file_to_del = $(this).data("file"); var backup_path = $(this).data('backup_path'); console.log("file to delete is" + file_to_del); bootbox.confirm({ title: "{% raw translate('serverBackups', 'destroyBackup', data['lang']) %}", message: "{{ translate('serverBackups', 'confirmDelete', data['lang']) }}", buttons: { cancel: { label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}' }, confirm: { label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}' } }, callback: function (result) { console.log(result); if (result == true) { var full_path = backup_path + '/' + file_to_del; del_backup(full_path, server_id); } } }); }); $(".restore_button").click(function () { var file_to_restore = $(this).data("file"); bootbox.confirm({ title: "{{ translate('serverBackups', 'restore', data['lang']) }} " + file_to_restore, message: "{{ translate('serverBackups', 'confirmRestore', data['lang']) }}", buttons: { cancel: { label: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}' }, confirm: { label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "restore", data['lang']) }}', className: 'btn-outline-danger' } }, callback: function (result) { console.log(result); if (result == true) { restore_backup(file_to_restore, server_id); } } }); }); $("#backup_now_button").click(function () { backup_started(); }); }); document.getElementById("modal-cancel").addEventListener("click", function () { document.getElementById("root_files_button").classList.remove('clicked'); document.getElementById("main-tree-div").innerHTML = '<input type="checkbox" id="main-tree-input" name="root_path" value="" disabled><span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path=""><i class="far fa-folder"></i><i class="far fa-folder-open"></i>{{ translate("serverFiles", "files", data["lang"]) }}</span></input>' }) document.getElementById("root_files_button").addEventListener("click", function () { if ($("#root_files_button").data('server_path') != "") { if (document.getElementById('root_files_button').classList.contains('clicked')) { show_file_tree(); return; } else { document.getElementById('root_files_button').classList.add('clicked'); document.getElementById("changed").value = 1; } path = $("#root_files_button").data('server_path') console.log($("#root_files_button").data('server_path')) var token = getCookie("_xsrf"); var dialog = bootbox.dialog({ message: '<p class="text-center mb-0"><i class="fa fa-spin fa-cog"></i> Please wait while we gather your files...</p>', closeButton: false }); $.ajax({ type: "POST", headers: { 'X-XSRFToken': token }, url: '/ajax/backup_select?id=' + server_id + '&path=' + path, }); } else { bootbox.alert("You must input a path before selecting this button"); } }); if (webSocket) { webSocket.on('send_temp_path', function (data) { setTimeout(function () { var x = document.querySelector('.bootbox'); if (x) { x.remove() } var x = document.querySelector('.modal-backdrop'); if (x) { x.remove() } document.getElementById('main-tree-input').setAttribute('value', data.path) getTreeView(data.path); show_file_tree(); }, 5000); }); } if (webSocket) { webSocket.on('backup_status', function (backup) { if (backup.percent >= 100) { document.getElementById('backup_progress_bar').innerHTML = '100%'; document.getElementById('backup_progress_bar').style.width = '100%'; setTimeout(function () { window.location.reload(1); }, 5000); } else { document.getElementById('backup_progress_bar').innerHTML = backup.percent + '%'; document.getElementById('backup_progress_bar').style.width = backup.percent + '%'; document.getElementById('total_files').innerHTML = backup.total_files; } }); } function getTreeView(path) { path = path $.ajax({ type: "GET", url: '/ajax/get_backup_tree?id=' + server_id + '&path=' + path, dataType: 'text', success: function (data) { console.log("got response:"); console.log(data); dataArr = data.split('\n'); serverDir = dataArr.shift(); // Remove & return first element (server directory) text = dataArr.join('\n'); try { document.getElementById('main-tree-div').innerHTML += text; document.getElementById('main-tree').parentElement.classList.add("clicked"); } catch { document.getElementById('files-tree').innerHTML = text; } document.getElementsByClassName('files-tree-title')[0].setAttribute('data-path', serverDir); document.getElementsByClassName('files-tree-title')[0].setAttribute('data-name', 'Files'); }, }); } function getToggleMain(event) { path = event.target.parentElement.getAttribute('data-path'); document.getElementById("files-tree").classList.toggle("d-block"); document.getElementById(path + "span").classList.toggle("tree-caret-down"); document.getElementById(path + "span").classList.toggle("tree-caret"); } function getDirView(event) { path = event.target.parentElement.getAttribute('data-path'); if (document.getElementById(path).classList.contains('clicked')) { var toggler = document.getElementById(path + "span"); if (toggler.classList.contains('files-tree-title')) { document.getElementById(path + "ul").classList.toggle("d-block"); document.getElementById(path + "span").classList.toggle("tree-caret-down"); } return; } else { $.ajax({ type: "GET", url: '/ajax/get_backup_dir?id=' + server_id + '&path=' + path, dataType: 'text', success: function (data) { console.log("got response:"); dataArr = data.split('\n'); serverDir = dataArr.shift(); // Remove & return first element (server directory) text = dataArr.join('\n'); try { document.getElementById(path + "span").classList.add('tree-caret-down'); document.getElementById(path).innerHTML += text; document.getElementById(path).classList.add("clicked"); } catch { console.log("Bad") } var toggler = document.getElementById(path); if (toggler.classList.contains('files-tree-title')) { document.getElementById(path + "span").addEventListener("click", function caretListener() { document.getElementById(path + "ul").classList.toggle("d-block"); document.getElementById(path + "span").classList.toggle("tree-caret-down"); }); } }, }); } } function show_file_tree() { $("#dir_select").modal(); } </script> {% end %}