### Initialize Server Uptime and Details Display Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/parts/details_stats.html Sets up the display for server start time and uptime on page load. It fetches the server start time, formats it, and starts an interval to update the uptime display every second. ```javascript let uptime = document.querySelector('#uptime'); let started = document.querySelector('#started'); let startedUTC; let startedLocal; let uptimeLoop; document.body.onload = (() => { console.log('calculateTime'); startedUTC = "{{ data['server_stats']['started'] }}"; if (startedUTC != 'False') { console.log('started utc:', startedUTC); startedUTC = moment.utc(startedUTC, 'YYYY-MM-DD HH:mm:ss'); var browserUTCOffset = moment().utcOffset(); // This is in minutes startedLocal = startedUTC.utcOffset(browserUTCOffset); startedLocalFormatted = startedLocal.format('YYYY-MM-DD HH:mm:ss'); console.log('started local time:', startedLocalFormatted); started.textContent = startedLocalFormatted } var calculateUptime = () => { var msdiff = moment().diff(startedLocal); var diff = moment.duration(msdiff); uptime.textContent = durationToHumanizedString(diff); } if (uptime != null && started != null) { console.log('startedLocal', startedLocal) if (startedLocal) { calculateUptime(); uptimeLoop = setInterval(calculateUptime, 1000); } } initParser('input_motd', 'input_motd'); }); ``` -------------------------------- ### Initiate Server Backup Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup.html Starts a server backup process by sending a POST request to the server API. It disables the backup button upon successful initiation. ```javascript async function backup_started(backup_id) { const token = getCookie("_xsrf") console.log(backup_id) let res = await fetch(`/api/v2/servers/${serverId}/action/backup_server/${backup_id}/`, { method: 'POST', headers: { 'X-XSRFToken': token } }); let responseData = await res.json(); if (responseData.status === "ok") { console.log(responseData); $("#backup_button").prop('disabled', true) } else { bootbox.alert({ title: responseData.error, message: responseData.error_data }); } return; } ``` -------------------------------- ### Start and Monitor Crafty Controller with Docker Compose Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/README.md Starts the Crafty Controller container in detached mode and follows its logs. This command should be run after defining your `docker-compose.yml`. ```sh docker-compose up -d && docker-compose logs -f ``` -------------------------------- ### Initialize Server Status and Button States Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_term.html Parses the server's running status and initializes the state of start, restart, and stop buttons based on whether the server is online. ```javascript // Convert running to lower case (example: 'True' converts to 'true') and // then to boolean via JSON.parse() let online = JSON.parse("{{ data\[\'server_stats\' 1] ['running'] }}".toLowerCase()); let startBtn = document.querySelector('#start-btn'); let restartBtn = document.querySelector('#restart-btn'); let stopBtn = document.querySelector('#stop-btn'); //{% if data\[\'permissions\' 1] ['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() }); } ``` -------------------------------- ### Initiate Server Backup Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup_edit.html Sends a POST request to the API to start a server backup. It includes the CSRF token in the headers and handles the API response to enable/disable the backup button or show errors. ```javascript async function backup_started() { const token = getCookie("_xsrf") let res = await fetch(`/api/v2/servers/${serverId}/action/backup_server/${backup_id}`, { method: 'POST', headers: { 'X-XSRFToken': token } }); let responseData = await res.json(); if (responseData.status === "ok") { console.log(responseData); $("#backup_button").prop('disabled', true) } else { bootbox.alert({ title: responseData.error, message: responseData.error_data }); } return; } ``` -------------------------------- ### Tree Reset Navigation Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/server/bedrock_wizard.html Handles the click event for the tree reset button, navigating the user to the server setup step. ```javascript $(".tree-reset").on("click", function () { location.href = "/server/bedrock_step1"; }); ``` -------------------------------- ### Play Button Click Handler Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/dashboard.html Handles the click event for the 'play' button, initiating a server start command and displaying a loading indicator. It also sets a timeout for the operation. ```javascript $(document).ready(function () { console.log('ready for JS!') $(".play_button").click(function () { server_id = $(this).attr("data-id"); send_command(server_id, 'start_server'); bootbox.alert({ backdrop: true, title: '{% raw translate("dashboard", "sendingCommand", data["lang"]) %}', message: '
  {% raw translate("dashboard", "bePatientStart", data["lang"]) %}
' }); setTimeout(finishTimeout, 60000); }); function finishTimeout() { warnServer("It seems this is taking a while...it's possible you're using UBlock or a similar ad blocker and it's causing some of our connections to not make it to the server." } }); ``` -------------------------------- ### Tree Reset Navigation Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/server/hytale_wizard.html Handles click events on elements with the class 'tree-reset' to navigate to the server setup step 1 page. ```javascript $(".tree-reset").on("click", function () { location.href = "/server/hytale_step1"; }); ``` -------------------------------- ### Create Passkey Registration Flow Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/panel_edit_user_passkey.html Initiates the passkey registration process by sending a POST request to the server to get a challenge. It then uses the WebAuthn API to create a public key credential and prompts the user for a name before verifying the registration with the server. ```javascript const userId = new URLSearchParams(document.location.search).get('id'); const token = getCookie("_xsrf"); const errorTitle = "{{ translate('error', 'error', data['lang']) }}"; // Create Passkey on button click $("#createPasskeyButton").click(async function () { // Start registration let res = await fetch(`/api/v2/users/${userId}/passkeys/`, { method: 'POST', headers: { 'X-XSRFToken': token }, }); if (!res.ok) { bootbox.alert({ title: errorTitle, message: "{{ translate('passkey', 'registrationFailed', data['lang']) }}" }); return; } let responseData = await res.json(); if (responseData.status !== "ok") { bootbox.alert({ title: responseData.error, message: responseData.error_data || responseData.error }); return; } const challengeId = responseData.data.challenge_id; const options = responseData.data.options; // Convert base64url to ArrayBuffer options.challenge = base64URLToBuffer(options.challenge); options.user.id = base64URLToBuffer(options.user.id); if (options.excludeCredentials) { options.excludeCredentials = options.excludeCredentials.map(cred => ({ ...cred, id: base64URLToBuffer(cred.id) })); } let credential; try { credential = await navigator.credentials.create({ publicKey: options }); } catch (err) { let errorMessage = err.message; if (err.name === 'SecurityError' || err.name === 'NotSupportedError') { errorMessage = "{{ translate('passkey', 'insecureContext', data['lang']) }}"; } else if (err.name === 'NotAllowedError') { errorMessage = "{{ translate('passkey', 'cancelled', data['lang']) }}"; } bootbox.alert({ title: "{{ translate('passkey', 'registrationFailed', data['lang']) }}", message: errorMessage }); return; } // Prompt for passkey name bootbox.prompt({ title: $("#createPasskeyButton").data("register-title"), value: "{{ translate('passkey', 'defaultName', data['lang']) }}", callback: async function(name) { if (name === null) { return; } // Prepare credential for server const credentialJSON = { id: credential.id, rawId: bufferToBase64URL(credential.rawId), type: credential.type, response: { clientDataJSON: bufferToBase64URL(credential.response.clientDataJSON), attestationObject: bufferToBase64URL(credential.response.attestationObject) } }; if (credential.response.getTransports) { credentialJSON.response.transports = credential.response.getTransports(); } // Verify registration let verifyRes = await fetch(`/api/v2/users/${userId}/passkeys/${challengeId}/verify/`, { method: 'POST', headers: { 'X-XSRFToken': token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name || "Passkey", credential: credentialJSON }) }); if (!verifyRes.ok && verifyRes.status >= 500) { bootbox.alert({ title: errorTitle, message: "{{ translate('passkey', 'registrationFailed', data['lang']) }}" }); return; } let verifyData = await verifyRes.json(); if (verifyData.status === "ok") { bootbox.alert({ title: "{{ ``` -------------------------------- ### Manage API Key Actions (Delete, Get Token) Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/panel_edit_user_apikeys.html Handles click events for deleting an API key and retrieving an API token. It uses AJAX to communicate with the backend API and displays results or errors using bootbox. ```javascript $(document).ready(function () { console.log("ready!"); $('.delete-api-key').click(async function () { let keyId = $(this).data("key-id"); let token = getCookie("_xsrf"); let res = await fetch(`/api/v2/users/${userId}/key/${keyId}`, { method: 'DELETE', headers: { 'X-XSRFToken': token }, }); let responseData = await res.json(); if (responseData.status === "ok") { location.reload() } else { bootbox.alert({ title: responseData.error, message: responseData.error_data }); } }) $('.get-a-token').click(async function () { let keyId = $(this).data("key-id"); let keyName = $(this).data("key-name"); let token = getCookie("_xsrf"); let res = await fetch(`/api/v2/users/${userId}/key/${keyId}`, { method: 'GET', headers: { 'X-XSRFToken': token }, }); let responseData = await res.json(); if (responseData.status === "ok") { bootbox.alert({ title: `API token for ${escape(keyName)}`, message: `Here is an API token for ${escape(keyName)}:\n
${responseData.data}
` }); } else { bootbox.alert({ title: responseData.error, message: responseData.error_data }); } }); }); ``` -------------------------------- ### Display Server Status and Details Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/parts/details_stats.html Conditionally displays server status (online, crashed, offline), start time, and uptime based on the server's state. Also shows CPU, memory, player counts, and version. ```html {% if data['server_stats']['running'] %} **{{ translate('serverStats', 'serverStatus', data['lang']) }}:** {{ translate('serverStats', 'online', data['lang']) }} **{{ translate('serverStats', 'serverStarted', data['lang']) }}:** {{ data['server_stats']['started'] }} **{{ translate('serverStats', 'serverUptime', data['lang']) }}:** {{ translate('serverStats', 'errorCalculatingUptime', data['lang']) }} {% elif data['server_stats']['crashed'] %} **{{ translate('serverStats', 'serverStatus', data['lang'])}:** {{ translate('dashboard', 'crashed', data['lang']) }} **{{ translate('serverStats', 'serverStarted', data['lang']) }}:** {{ translate('dashboard', 'crashed',data['lang']) }} **{{ translate('serverStats', 'serverUptime', data['lang']) }}:** {{ translate('dashboard', 'crashed', data['lang']) }} {% else %} **{{ translate('serverStats', 'serverStatus', data['lang']) }}:** {{ translate('serverStats', 'offline', data['lang']) }} **{{ translate('serverStats', 'serverStarted', data['lang']) }}:** {{ translate('serverStats', 'offline', data['lang']) }} **{{ translate('serverStats', 'serverUptime', data['lang']) }}:** {{ translate('serverStats', 'offline', data['lang']) }} {% end %} **{{ translate('serverStats', 'serverTimeZone', data['lang']) }}:** {{ data['serverTZ'] }} **{{ translate('serverStats', 'cpuUsage', data['lang']) }}:** {{ data['server_stats']['cpu'] }}% **{{ translate('serverStats', 'memUsage', data['lang']) }}:** {{ data['server_stats']['mem'] }} {% if data['server_stats']['int_ping_results'] %} **{{ translate('serverStats', 'players', data['lang']) }}:** {{ data['server_stats']['online'] }} / {{ data['server_stats']['max'] }} {% else %} **{{ translate('serverStats', 'players', data['lang']) }}:** 0/0 {% end %} {% if data['server_stats']['version'] != 'False' %} **{{ translate('serverStats', 'version', data['lang']) }}:** {{ data['server_stats']['version'] }} **{{ translate('serverStats', 'description', data['lang']) }}:** {{ translate('serverStats', 'loadingMotd', data['lang']) }} {% else %} **{{ translate('serverStats', 'version', data['lang']) }}:** {{ translate('serverStats', 'unableToConnect', data['lang']) }} **{{ translate('serverStats', 'description', data['lang']) }}:** {{ translate('serverStats', 'unableToConnect', data['lang']) }} {% end %} **Server Type: {{data['server_stats']['server_type']}}** ``` -------------------------------- ### Handle Backup Explanation Click Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup.html Attaches a click event listener to elements with the class 'backup-explain' to display detailed explanations using bootbox. ```javascript $(".backup-explain").on("click", function () { bootbox.alert($(this).data("explain")); }); ``` -------------------------------- ### Initiate Immediate Backup Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup_edit.html Handles the click event for the 'Backup Now' button to initiate an immediate backup process. ```javascript $("#backup_now_button").click(function () { backup_started(); }); ``` -------------------------------- ### Get Cookie Function Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_webhook_edit.html Retrieves a cookie by its name. This is used for obtaining the CSRF token for security. ```javascript function getCookie(name) { var r = document.cookie.match("\\b" + name + "=(\[^;]\*)\\b"); return r ? r[1] : undefined; } ``` -------------------------------- ### Jinja2 Conditional Logic Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/base.html Example of Jinja2 conditional statements for rendering content based on data attributes. ```html {% if not data.get("mfa", None) and not data.get("password\_auth\_disabled", False) %} {% end %} ``` -------------------------------- ### Document Ready and WebSocket Initialization Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_term.html Initializes logging and sets up the WebSocket listener for new log lines when the document is ready. ```javascript $(document).ready(function () { console.log("ready!"); get_server_log() webSocket.on('vterm_new_line', new_line_handler) }); ``` -------------------------------- ### Get CSRF Token Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_schedules.html A utility function to retrieve the CSRF token from browser cookies, used for security in requests. ```javascript function getCookie(name) { var r = document.cookie.match(String.raw`\b` + name + String.raw`=([^;]*)`) return r ? r[1] : undefined; } ``` -------------------------------- ### Initiate Server Backup Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup.html Attaches a click event to elements with the class 'backup_now_button' to initiate a server backup. It logs the action and calls the `backup_started` function with the backup identifier. ```JavaScript }); $(".backup_now_button").click(function () { console.log("Backup started") backup_started($(this).data('backup')); }); }); ``` -------------------------------- ### Get CSRF Cookie Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_term.html Utility function to retrieve the CSRF token cookie, used for authenticated API requests. ```javascript function getCookie(name) { let r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } ``` -------------------------------- ### Initialize Chart.js for Server Metrics Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_metrics.html Sets up a Chart.js instance to display server metrics with dual Y-axes and time-based X-axis. It configures datasets for CPU, RAM (percentage and GB), and player counts, enabling decimation for performance and interactive zoom/pan functionality. ```javascript function serverCutoff(hours) { return moment(serverNow().subtract(hours, 'hours').format(dateFormat), dateFormat).valueOf(); } const timestamps = rawDates.map(d => parseServerTime(d)); const playerData = timestamps.map((t, i) => ({x: t, y: rawPlayers[i]})); const ramPercentData = timestamps.map((t, i) => ({x: t, y: rawRamPercent[i]})); const ramGbData = timestamps.map((t, i) => ({x: t, y: rawRamGb[i]})); const cpuData = timestamps.map((t, i) => ({x: t, y: rawCpu[i]})); // Compute decimation samples based on chart width (~1 point per 3 CSS pixels) function getDecimationSamples() { var canvas = document.getElementById("lineChart"); var width = canvas.clientWidth || canvas.offsetWidth || 800; return Math.max(60, Math.round(width / 3)); } // Create chart with dual Y-axes and time-based x-axis var ctxL = document.getElementById("lineChart").getContext('2d'); var hist_chart = new Chart(ctxL, { type: 'line', data: { datasets: [ { label: "CPU %", data: cpuData, borderColor: 'rgba(255, 175, 0, .7)', borderWidth: 2, lineTension: 0, spanGaps: false, yAxisID: 'y-left', }, { label: "RAM %", data: ramPercentData, borderColor: 'rgba(33, 150, 243, .8)', borderWidth: 2, lineTension: 0, spanGaps: false, yAxisID: 'y-left', }, { label: "RAM GB", data: ramGbData, borderColor: 'rgba(0, 188, 212, .9)', borderWidth: 2, lineTension: 0, spanGaps: false, yAxisID: 'y-right', }, { label: "Players", data: playerData, borderColor: 'rgba(136, 98, 224, .7)', borderWidth: 2, lineTension: 0, spanGaps: false, yAxisID: 'y-right', }, ] }, options: { maintainAspectRatio: false, parsing: false, onResize(chart, size) { var newSamples = Math.max(60, Math.round(size.width / 3)); chart.options.plugins.decimation.samples = newSamples; chart.options.plugins.decimation.threshold = newSamples; }, plugins: { decimation: { enabled: true, algorithm: 'lttb', samples: getDecimationSamples(), threshold: getDecimationSamples(), }, zoom: { zoom: { onZoom() { document.getElementById("reset-button").classList.remove("d-none"); document.getElementById("reset-button").classList.add("d-block"); zoomed = true; }, wheel: { enabled: true, modifierKey: 'shift', }, drag: { enabled: true, modifierKey: "shift" }, pinch: { enabled: true }, mode: 'x', }, pan: { enabled: true, mode: "x", threshold: 1, } }, tooltip: { callbacks: { label: function(context) { let label = context.dataset.label || ''; if (label) label += ': '; // Format based on metric type if (context.dataset.label === 'RAM GB') { label += parseFloat(context.parsed.y).toFixed(2) + ' GB'; } else if (context.dataset.label === 'Players') { label += Math.round(context.parsed.y); } else { label += context.parsed.y.toFixed(1) + '%'; } return label; } } } }, responsive: true, scales: { 'y-left': { type: 'linear', position: 'left', min: 0, title: { display: true, text: 'Percentage (%)' }, grid: { display: true } }, 'y-right': { type: 'linear', position: 'right', min: 0, title: { display: true, text: 'Count / GB' }, grid: { display: false } }, x: { type: 'time', time: { displayFormats: { minute: 'HH:mm', hour: 'MMM D, HH:mm', day: 'MMM D', } }, position: 'bottom', } } } }); $(window).ready(function () { $('body').click(function () { $('.hints').popover("hide"); }); }); // WebSocket updates and reset button const samplingTiers = {% raw json_encode(data.get('sampling_tiers', [])) %}; const samplingFallbackDivisor = {{ data.get('sampling_fallback_divisor', 12) }}; // Match live update frequency to historical data interval. // The backend records stats every 30 seconds; adaptive sampling then // keeps every Nth point depending on the selected time range. ``` -------------------------------- ### Get Directory View Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup.html Retrieves the path from the target element's parent and calls `getTreeView` if the directory has not been previously clicked. ```JavaScript function getDirView(event) { let path = event.target.parentElement.getAttribute("data-path"); if (document.getElementById(path).classList.contains('clicked')) { return; } else { getTreeView(path); } } ``` -------------------------------- ### Initialize Document Ready Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_config.html Logs a message to the console when the DOM is fully loaded and ready. ```javascript $(document).ready(function () { console.log("ready!"); }); ``` -------------------------------- ### Get CSRF Token Cookie Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_config.html Retrieves the CSRF token from the browser's cookies, essential for authenticated API requests. ```javascript function getCookie(name) { var r = document.cookie.match("\\b" + name + "=(\[^;\\\]*)\\b"); return r ? r\[1\] : undefined; } ``` -------------------------------- ### JavaScript: Get Random Number (Exclusive Max) Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/base.html Generates a random floating-point number between a minimum (inclusive) and maximum (exclusive). ```javascript /** * Returns a random number between min (inclusive) and max (exclusive) */ function getRandomArbitrary(min, max) { return Math.random() * (max - min) + min; } ``` -------------------------------- ### Confirm Server Deletion (Basic) Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_config.html Displays a Bootbox confirmation dialog before proceeding with server deletion. If confirmed, it calls the `deleteServer` function. ```javascript function deleteConfirm() { path = "{{data\[\'server_stats\'\ Indian\server_id'\server_name'\path'\}}"; name = "{{data\[\'server_stats\'\ Indian\server_id'\server_name'\server_name'\}}"; bootbox.confirm({ size: "", title: "{% raw translate('serverConfig', 'deleteServerQuestion', data\[\'lang\'\ Indian) %}", closeButton: false, message: "{% raw translate('serverConfig', 'deleteServerQuestionMessage', data\[\'lang\'\ Indian) %}", buttons: { confirm: { label: "{{ translate('serverConfig', 'yesDelete', data\[\'lang\'\ Indian) }}", className: 'btn-danger', }, cancel: { label: "{{ translate('serverConfig', 'noDelete', data\[\'lang\'\ Indian) }}", className: 'btn-link', } }, callback: function (result) { if (!result) { return; return; } else { deleteServer(); } } }); } ``` -------------------------------- ### Initialize Popovers and Handle Small Screens Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/config_json.html Initializes popovers and shows them on small screens when the document is ready. Hides popovers when the body is clicked. ```javascript $(document).ready(function () { $(' [data-toggle="popover"]').popover(); if ($(window).width() < 1000) { $('.too_small').popover("show"); $('.too_small2').popover("show"); } }); $(window).ready(function () { $('body').click(function () { $('.too_small').popover("hide"); $('.too_small2').popover("hide"); }); }); ``` -------------------------------- ### JavaScript Cookie Retrieval Function Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/server/wizard.html Retrieves the value of a specified cookie. This is often used to get CSRF tokens for authenticated requests. ```javascript function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trimStart(); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } ``` -------------------------------- ### Include Main Menu Partial Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/base.html Includes the 'main_menu.html' partial template. ```html {% include main_menu.html %} ``` -------------------------------- ### Get Server ID and XSRF Token Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup.html Retrieves the server ID from the URL and a secure XSRF token from cookies for API requests. ```javascript const serverId = 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; } ``` -------------------------------- ### Get Cookie Utility Function Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/panel_edit_user.html Retrieves a cookie by its name from the browser's document cookies. This is used for obtaining the XSRF token. ```javascript function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*) \\b"); return r ? r[1] : undefined; } ``` -------------------------------- ### Initialize Announcements and Style MFA Link Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/notify.html On document ready, fetches announcements and applies styling to the MFA link based on the computed background color of the root element. ```javascript $(document).ready(function () { getAnnouncements(); const bg = getComputedStyle(document.documentElement) .getPropertyValue("--dropdown-bg") .trim(); if (isLight(bg)) { $("#mfa-link").addClass("mfa-light"); $("#mfa-link").removeClass("mfa-dark"); } }) ``` -------------------------------- ### Submit Server Update Configuration (Basic) Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_update_center.html Submits basic server update configuration, including server JAR, type, and version. It then updates the executable path. ```javascript if ($("#ba").val() === "0") { let payload = { "category": $("#server_jar").val(), "type": $("#server_type").val(), "version": $("#server").val() } let formDataJsonString = JSON.stringify(payload); if ($("#server_jar").val() && $("#server_type").val() && $("#server").val()) { //Only send the first payload if it exists let res = await fetch(`/api/v2/servers/${serverId}/update/config/`, { method: 'PATCH', body: formDataJsonString, headers: { 'X-XSRFToken': token }, }); let responseData = await res.json() if (responseData.status === "ok") { $("#update-url").text(responseData.data["executable_update_url"]) } else { bootbox.alert({ title: responseData.error, message: responseData.error_data }); } } payload = { "executable": $("#executable").val() }; formDataJsonString = JSON.stringify(payload); let res1 = await fetch(`/api/v2/servers/${serverId}/`, { method: 'PATCH', body: formDataJsonString, headers: { 'X-XSRFToken': token }, }); let responseData1 = await res1.json() if (responseData1.status === "ok") { $("#span_executable").text($("#executable").val()) } else { bootbox.alert({ title: responseData1.error, message: responseData1.error_data }); } } ``` -------------------------------- ### JavaScript: Get Cookie by Name Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/base.html Utility function to retrieve a cookie's value from the browser, used for Tornado's XSRF protection. ```javascript //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(String.raw`\b` + name + String.raw`=([^;]*)\b`); return r ? r[1] : undefined; } ``` -------------------------------- ### Sidebar Initialization and Resize Handling Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/main_menu.html Initializes sidebar behavior on document ready and sets up a debounced resize handler for responsive adjustments. ```javascript $(document).ready(function () { sidebarResizeHandler(null); $(window).on( 'resize', debounce(sidebarResizeHandler, 25, true) ); }); ``` -------------------------------- ### JavaScript: Get Random Integer (Inclusive Max) Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/base.html Generates a random integer between a minimum and maximum, both inclusive. Avoids non-uniform distribution from Math.round(). ```javascript /** * Returns a random integer between min (inclusive) and max (inclusive). * The value is no lower than min (or the next integer greater than min * if min isn't an integer) and no greater than max (or the next integer * lower than max if max isn't an integer). * Using Math.round() will give you a non-uniform distribution! */ function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } ``` -------------------------------- ### Initialize Popovers and Handle Small Screen Display Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_schedules.html Initializes popovers and adjusts table visibility based on screen width. Hides the main schedule table and shows the mini version on screens smaller than 1000px. ```javascript $(document).ready(function () { $(' [data-toggle="popover"]').popover(); if ($(globalThis).width() < 1000) { $('.too_small').popover("show"); document.getElementById('schedule_table_wrapper').hidden = true; document.getElementById('mini_schedule_table_wrapper').hidden = false; } }); ``` -------------------------------- ### Get CSRF Cookie Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_admin_controls.html Retrieves the value of a specified cookie, commonly used for Cross-Site Request Forgery (CSRF) protection in web applications. ```javascript function getCookie(name) { var r = document.cookie.match("\\b" + name + "=(\[^;\\]*)\\b"); return r ? r[1] : undefined; } ``` -------------------------------- ### Get Cookie for XSRF Protection Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/panel_edit_user_apikeys.html Utility function to retrieve cookies, specifically used for obtaining the XSRF token required for secure requests. ```javascript function getCookie(name) { var r = document.cookie.match("\\b" + name + "=(\[^;]\*)\\b"); return r ? r[1] : undefined; } ``` -------------------------------- ### Create or Update Server Backup Configuration Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup_edit.html Handles the creation or update of a server backup configuration. It formats form data into JSON and makes a PATCH or POST request to the server API. Redirects to the server detail page on success or shows an error message. ```javascript delete formDataObject.root_path console.log(formDataObject); // Format the plain form data as JSON let formDataJsonString = JSON.stringify(formDataObject, replacer); console.log(formDataJsonString); let url = `/api/v2/servers/${serverId}/backups/backup/${backup_id}/` let method = "PATCH" if (!backup_id) { url = `/api/v2/servers/${serverId}/backups/` method = "POST"; } let res = await fetch(url, { method: method, headers: { 'X-XSRFToken': token }, body: formDataJsonString, }); let responseData = await res.json(); if (responseData.status === "ok") { window.location.href = `/panel/server_detail?id=${serverId}&subpage=backup`; } else { bootbox.alert({ title: responseData.error, message: responseData.error_data }); }); ``` -------------------------------- ### Conditional Server Creation Menu Item Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/main_menu.html Renders a 'New Server' menu item only if the user has the 'Server_Creation' permission. ```html {% if data['crafty_permissions']['Server_Creation'] in data['user_crafty_permissions'] %}*   [{{ translate('sidebar', 'newServer', data['lang']) }}](/server/step1) {% end %} ``` -------------------------------- ### Countdown Timer for Cooldown Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/public/login.html JavaScript function to start and manage a countdown timer for login cooldown periods. It updates a display element with the remaining time. ```javascript function startCountdown(minutes, seconds) { let totalSeconds = parseInt(minutes) * 60 + parseInt(seconds); const interval = setInterval(() => { const mins = Math.floor(totalSeconds / 60); const secs = totalSeconds % 60; const formattedTime = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; $("#cooldown").html(formattedTime); // Stop timer at 0 if (totalSeconds === 0) { clearInterval(interval); $("#error-field").html("") } totalSeconds--; }, 1000); } ``` -------------------------------- ### Get CSRF Cookie Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/parts/details_stats.html Retrieves the CSRF token cookie, which is used for security purposes in web applications, particularly with Tornado's XSRF protection. ```javascript function getCookie(name) { var r = document.cookie.match("\\b" + name + "=(\[^;]\*)\\b"); return r ? r[1] : undefined; } const token = getCookie("_xsrf") ``` -------------------------------- ### JavaScript WebSocket Initialization and Reconnection Logic Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/base.html Sets up WebSocket connection, handles messages, errors, and automatic reconnection with exponential backoff. Includes logic to display a warning if WebSockets are required but closed. ```javascript let usingWebSockets = false; let webSocket = null; let websocketTimeoutId = null; // {% if request.protocol == 'https' %} usingWebSockets = true; let listenEvents = [] ; let wsOpen = false; /** * @type {number | null} reconnectorId An interval ID for the reconnector. */ let reconnectorId = null; let failedConnectionCounter = 0; // https://stackoverflow.com/a/37038217/15388424 const wsPageQueryParams = 'page_query_params=' + encodeURIComponent(location.search) const wsPage = 'page=' + encodeURIComponent(location.pathname) const sendWssError = () => wsOpen || warn( 'WebSockets are required for Crafty to work. This websocket connection has been closed. Are you using a reverse proxy?', link = 'https://docs.craftycontrol.com/pages/getting-started/proxies/', link_msg = "See our documentation for details", className = 'wssError' ) function startWebSocket() { console.log('%c[Crafty Controller] %cConnecting the WebSocket', 'font-weight: 900; color: #800080;', 'font-weight: 900; color: #eee;'); try { var wsInternal = new WebSocket('wss://' + location.host + '/ws?' + wsPage + '&' + wsPageQueryParams); wsInternal.onopen = function () { console.log('opened WebSocket connection:', wsInternal) wsOpen = true; failedConnectionCounter = 0; if (typeof reconnectorId === 'number') { document.querySelectorAll('.wssError').forEach(el => el.remove()) clearInterval(reconnectorId); reconnectorId = null; } }; wsInternal.onmessage = function (rawMessage) { var message = JSON.parse(rawMessage.data); console.log('got message: ', message) listenEvents .filter(listenedEvent => listenedEvent.event == message.event) .forEach(listenedEvent => listenedEvent.callback(message.data)) }; wsInternal.onerror = function (errorEvent) { console.error('WebSocket Error', errorEvent); }; wsInternal.onclose = function (closeEvent) { wsOpen = false; console.log('Closed WebSocket', closeEvent); if (!document.hidden) { if (typeof reconnectorId !== 'number') { setTimeout(sendWssError, 7000); } console.info("Reconnecting with a timeout of", (getRandomArbitrary(0, 2 ** failedConnectionCounter - 1) + 5) * 1000, "milliseconds"); // Discard old websocket and create a new one in 5 seconds wsInternal = null reconnectorId = setTimeout(startWebSocket, (getRandomArbitrary(0, 2 ** failedConnectionCounter - 1) + 5) * 1000) failedConnectionCounter++; } }; webSocket = { on: function (event, callback) { console.log('registered ' + event + ' event'); listenEvents.push({ event: event, callback: callback }) }, emit: function (event, data) { var message = { event: event, data: data } wsInternal.send(JSON.stringify(message)); }, close: function (code, reason) { wsInternal.close(code, reason); }, getStatus: function () { return wsInternal.readyState; } } } catch (error) { console.error('Error while making websocket helpers', error); usingWebSockets = false; } } startWebSocket(); // {% else %} ``` -------------------------------- ### Get Server ID from URL Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_admin_controls.html Extracts the server ID from the URL's query parameters. This is used to identify the target server for subsequent operations. ```javascript const serverId = new URLSearchParams(document.location.search).get('id') ``` -------------------------------- ### Build and Run Crafty Controller from Source using Docker Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/README.md Builds a Docker image from the current directory (containing the Dockerfile) and then runs it as a container. This is for users who want to build from the cloned repository. ```sh # REMEMBER, Build your image first! $ docker build . -t crafty $ docker run \ --name crafty_container \ --detach \ --restart always \ -P 5520-5550:5520-5550/udp \ -p 8000:8000 \ -p 8443:8443 \ -p 8123:8123 \ -p 19132:19132/udp \ -p 25500-25600:25500-25600 \ -e TZ=Etc/UTC \ -v "/$(pwd)/docker/backups:/crafty/backups" \ -v "/$(pwd)/docker/logs:/crafty/logs" \ -v "/$(pwd)/docker/servers:/crafty/servers" \ -v "/$(pwd)/docker/config:/crafty/app/config" \ -v "/$(pwd)/docker/import:/crafty/import" \ crafty ``` -------------------------------- ### Get CSRF Token Cookie Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_webhooks.html Retrieves the CSRF token from browser cookies. This function is essential for authenticating POST and PATCH requests to the server, ensuring security. ```javascript function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*) \\b"); return r ? r[1] : undefined; } ``` -------------------------------- ### Initialize Popovers and Handle Small Screens Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/activity_logs.html Initializes popovers and shows a specific popover when the window width is less than 1000px. This is useful for displaying contextual information on smaller displays. ```javascript $(document).ready(function () { $('["data-toggle"="popover"]').popover(); if ($(window).width() < 1000) { $('.too_small').popover("show"); } }); ``` -------------------------------- ### Initialize and Display File Tree Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup_edit.html This snippet initializes the file tree view after a delay, handling potential modal backdrop cleanup and setting the initial path for the tree. It's used when the server backup edit panel is loaded. ```javascript $("#root_files_button").data('server_path') console.log($("#root_files_button").data('server_path')) const token = getCookie("\_xsrf"); var dialog = bootbox.dialog({ message: '

Please wait while we gather your files...

', closeButton: false }); 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', path) getTreeView(path); show_file_tree(); }, 5000); } else { bootbox.alert("You must input a path before selecting this button"); } }); ``` -------------------------------- ### Start Image Rotation Animation Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/activity_logs.html Initiates the image rotation animation by calling the `rotateImage` function with a 360-degree rotation after a 2-second delay. This is typically used for introductory animations. ```javascript $(document).ready(function () { setTimeout(function () { rotateImage(360); }, 2000); }); ``` -------------------------------- ### Get Server and Backup IDs Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup_edit.html Retrieves the server ID and backup ID from the URL query parameters. These IDs are crucial for API calls related to server backups. ```javascript const serverId = new URLSearchParams(document.location.search).get('id') const backup_id = new URLSearchParams(document.location.search).get('backup_id') ``` -------------------------------- ### Toggle Backup Configuration Visibility Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup_edit.html Handles the click event for a 'Show Configuration' button to toggle the visibility of the backup configuration box and related elements. ```javascript $("#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(); }); ``` -------------------------------- ### Password Reset API Call Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/public/login.html Asynchronous JavaScript function to call the password reset API. It makes a GET request and displays the response data in an alert box. ```javascript async function resetPass() { let res = await fetch(`/api/v2/crafty/resetPass/`, { method: 'GET', }); let responseData = await res.json(); console.log(responseData); bootbox.alert(responseData.data) } ``` -------------------------------- ### Document Ready Event Handlers Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/server_backup_edit.html Sets up event listeners when the DOM is ready. This includes handlers for backup explanation popups, cancel button navigation, and WebSocket 'backup_status' events for real-time progress updates. ```javascript $(document).ready(function () { $(".backup-explain").on("click", function (e) { e.preventDefault(); bootbox.alert($(this).data("explain")); }); $(".cancel-button").on("click", function () { location.href = `/panel/server_detail?id=${serverId}&subpage=backup` }); webSocket.on('backup_status', function (backup) { text = ``; $("#${backup.backup_id}_status").show() if (backup.percent >= 100) { $("#${backup.backup_id}_status").hide() setTimeout(function () { window.location.reload(1); }, 5000); } else { text = `
${backup.percent}%
` $("#${backup.backup_id}_status").html(text); } }); }) ``` -------------------------------- ### Initialize Popovers and Handle Window Resize Source: https://gitlab.com/crafty-controller/crafty-4/-/blob/master/app/frontend/templates/panel/panel_config.html Initializes popovers on elements with 'data-toggle="popover"' and shows/hides specific popovers ('too_small', 'too_small2') based on the window width, particularly for smaller screens. ```javascript $(document).ready(function () { $('["data-toggle"="popover"]').popover(); if ($(window).width() < 1000) { $('.too_small').popover("show"); $('.too_small2').popover("show"); } }); $(window).ready(function () { $('body').click(function () { $('.too_small').popover("hide"); $('.too_small2').popover("hide"); }); }); $(window).resize(function () { // This will execute whenever the window is resized if ($(window).width() < 1000) { $('.too_small').popover("show"); } else { $('.too_small').popover("hide"); } // New width if ($(window).width() < 1000) { $('.too_small2').popover("show"); } else { $('.too_small2').popover("hide"); } // New width }); ```