const id = s => document.getElementById(s); let configEditor; $(async () => { const gswFeatures = await fetchApi('GET', 'features'); if (gswFeatures) { await setRandomBg().catch(() => { }); setInterval(setRandomBg, 60000); let unsavedChanges = false; const confirmText = 'You have unsaved changes! Are you sure you want to '; $(window).on('beforeunload', e => { if (unsavedChanges) return confirmText + 'navigate away?'; }); $('#logout').click(e => { e.preventDefault(); if (unsavedChanges && !confirm(confirmText + 'log out?')) return; logout(); }); activateMenu(); $('body > nav > ul > li > a').click(e => { e.preventDefault(); activateMenu(e.currentTarget); }); $('#top-menu').removeClass('invisible'); $('body > footer').removeClass('d-none'); // Configuration const gswConfig = await fetchApi('GET', 'config'); if (gswConfig && gswConfig.config) { configEditor = new JSONEditor(id('config-form'), { "theme": "bootstrap4", "iconlib": "fontawesome5", "object_layout": "normal", "template": "mustache", "show_errors": "always", "required_by_default": 0, "no_additional_properties": 1, "display_required_only": 0, "remove_empty_properties": 1, "keep_oneof_values": 0, "ajax": 0, "ajaxCredentials": 0, "show_opt_in": 1, "disable_edit_json": 0, "disable_collapse": 1, "disable_properties": 1, "disable_array_add": 0, "disable_array_reorder": 1, "disable_array_delete": 0, "enable_array_copy": 1, "array_controls_top": 0, "disable_array_delete_all_rows": 1, "disable_array_delete_last_row": 1, "prompt_before_delete": 1, "schema": { "title": "Configuration", "type": "array", "default": [], "minItems": 0, "uniqueItems": true, "format": "tabs-top", "items": { "title": "Game Server", "type": "object", "required": [ "name", "type", "host", "port" ], "properties": { "name": { "title": "Name", "type": "string", "options": { "grid_columns": 12 } }, "type": { "title": "Gamedig type", "description": "Look for the GameDig Type ID in the games list.", "type": "string", "options": { "grid_columns": 6 } }, "appId": { "title": "Steam App ID", "description": "Look for the AppID in the apps list.", "type": "integer", "format": "number", "options": { "grid_columns": 6 } }, "host": { "title": "Host name or IP", "type": "string", "options": { "grid_columns": 6 } }, "port": { "title": "Port number", "type": "integer", "minimum": 1, "maximum": 65535, "format": "number", "options": { "grid_columns": 6 } }, "updateIntervalMinutes": { "title": "Update interval (minutes) [NOT IMPLEMENTED]", "type": "integer", "default": 5, "minimum": 1, "maximum": 60, "format": "range", "options": { "grid_columns": 12 } }, "graphHistoryHours": { "title": "Graph history time span (hours)", "type": "integer", "default": 12, "minimum": 1, "maximum": 24, "format": "range", "options": { "grid_columns": 12 } }, "timezoneOffset": { "title": "Time zone offset of the server", "default": 0, "type": "number", "minimum": -12, "maximum": 14, "format": "range", "options": { "grid_columns": 6, "grid_break": true } }, "discord": { "type": "array", "title": "Discord channels", "description": "The simplest way to get a discord channel ID is to enable developer mode in settings, then right clicking on the channel name will give you the Copy ID option.", "minItems": 0, "uniqueItems": true, "items": { "type": "object", "title": "Channel", "required": [ "channelId" ], "properties": { "channelId": { "title": "Channel ID", "type": "string" } } }, "format": "table", "options": { "grid_columns": 12 } }, "telegram": { "type": "array", "title": "Telegram chats", "description": "The simplest way to get a telegram chat ID is to invite @getidsbot and use /start to make it post (among other data) the chat ID.", "minItems": 0, "uniqueItems": true, "items": { "type": "object", "title": "Chat", "required": [ "chatId" ], "properties": { "chatId": { "type": "string", "title": "Chat ID" } } }, "format": "table", "options": { "grid_columns": 12 } } }, "format": "grid", "headerTemplate": "{{#self.name}}{{self.name}}{{/self.name}}{{^self.name}}{{self.type}}:{{self.host}}:{{self.port}}{{/self.name}}" } }, startval: gswConfig.config }); configEditor.on('ready', () => { addExportButton(); $('#config-form').submit(e => { e.preventDefault(); }); $('#spinner').addClass('d-none'); $('#protected-section').removeClass('d-none'); let editorCurrVal = configEditor.getValue(); let formCurrVal = actualFormValues(); $('#config-form input, #config-form textarea').keyup(() => { unsavedChanges = checkForChanges(formCurrVal); }); configEditor.on('change', () => { unsavedChanges = checkForChanges(formCurrVal); }); $('#save-config-submit').click(async e => { e.preventDefault(); if (!configEditor.isEnabled()) return; configEditor.disable(); $(e.currentTarget).addClass('_btn'); const uerr = configEditor.validate(); if (uerr.length) { notif('warning', 'Validation errors', uerr.map(err => err.path + ' ' + err.message).join('
'), undefined, 3 + uerr.length); } else { const res = await fetchApi('POST', 'config', configEditor.getValue()); if (res && res.message) { editorCurrVal = configEditor.getValue(); formCurrVal = actualFormValues(); unsavedChanges = false; dismissSaveConfig(); notif('success', '✔️ Changes saved successfully.', undefined, undefined, 3); } else notif('danger', '⚠️ Error while saving config.', (res ? res.error : undefined), undefined, 3); } $(e.currentTarget).removeClass('_btn'); configEditor.enable(); }); $('#save-config-reset').click(e => { e.preventDefault(); if (!configEditor.isEnabled()) return; configEditor.setValue(editorCurrVal, true); unsavedChanges = false; dismissSaveConfig(); if (formCurrVal !== actualFormValues()) document.location.reload(); }); }); } else notif('danger', '⚠️ Error while loading config.'); // Flush data $('button[data-api]').click(async e => { e.preventDefault(); const endpoint = e.currentTarget.dataset.api; if (confirm('Are you sure you want to call `' + endpoint + '`?')) { const res = await fetchApi('GET', endpoint); if (res && res.message) notif('success', res.message, undefined, undefined, 3); else notif('danger', (res ? res.error : undefined), undefined, undefined, 3); } }); } }); function dismissSaveConfig() { $('#save-config').addClass('animate__fadeOutDown'); setTimeout(() => { if ($('#save-config').hasClass('animate__fadeOutDown')) $('#save-config').addClass('d-none'); }, 500); } // NOTE: sadly configEditor.getValue() can not be trusted when the // change event originates from a keypress event function actualFormValues() { const formItems = $('#config-form .tab-pane:not([style*="display: none"]) input:enabled, #config-form .tab-pane:not([style*="display: none"]) textarea:enabled, #config-form .tab-pane:not([style*="display: none"]) select:enabled').toArray(); return JSON.stringify(formItems.map(e => { if (e.type === 'checkbox') return e.checked ? e.value : undefined; return e.value; })); } function checkForChanges(formCurrVal) { if (formCurrVal !== actualFormValues()) { $('#save-config').removeClass('d-none'); $('#save-config').removeClass('animate__fadeOutDown'); return true; } else { dismissSaveConfig(); return false; } }; function addExportButton() { const button_holder = configEditor.root.theme.getHeaderButtonHolder(); const exportBtn = configEditor.root.getButton('Export', 'save', 'Export As JSON File'); button_holder.appendChild(exportBtn); configEditor.root.header.parentNode.insertBefore(button_holder, configEditor.root.header.nextSibling); exportBtn.addEventListener('click', e => { e.preventDefault(); const dt = (new Date()).toISOString().slice(0, 19).replace(/\D/g, ''); const filename = dt + '-gsw.config.json'; const blob = new Blob([JSON.stringify(configEditor.getValue(), null, 2)], { type: 'application/json;charset=utf-8' }); if (window.navigator && window.navigator.msSaveOrOpenBlob) window.navigator.msSaveOrOpenBlob(blob, filename); else { const a = document.createElement('a'); a.download = filename; a.href = URL.createObjectURL(blob); a.dataset.downloadurl = ['text/plain', a.download, a.href].join(':'); a.dispatchEvent(new MouseEvent('click', { 'view': window, 'bubbles': true, 'cancelable': false })); } }, false); } function logout() { $(window).off('beforeunload'); $('#save-config').addClass('d-none'); $('#top-menu').addClass('invisible'); $('#protected-section').addClass('d-none'); $('#spinner').addClass('d-none'); $('#bye').removeClass('d-none'); $('body > footer').addClass('d-none'); if (configEditor) configEditor.destroy(); window.localStorage.removeItem('btoken'); } function notif(type, html1, html2, html3, dismissable) { let closeTimeout = 0; if (typeof dismissable === 'number') { closeTimeout = dismissable; dismissable = true; } const el = $('
', { class: 'alert alert-dismissible fade show shadow alert-' + type, role: 'alert' }); el.attr('role', 'alert'); if (html2) { const h4 = $('

', { class: 'alert-heading', html: html1 }); el.append(h4); const p1 = $('

', { html: html2 }); el.append(p1); if (html3) { el.append($('


')); const p2 = $('

', { class: 'mb-0', html: html3 }); el.append(p2); } } else el.html(html1); if (dismissable !== false) { const button = $('