diff --git a/UIMod/onboard_bundled/assets/css/home.css b/UIMod/onboard_bundled/assets/css/home.css index 84723b40..76b86a83 100644 --- a/UIMod/onboard_bundled/assets/css/home.css +++ b/UIMod/onboard_bundled/assets/css/home.css @@ -204,6 +204,7 @@ /* Backups */ #backups { + position: relative; margin-top: 40px; background-color: rgba(0, 255, 171, 0.05); padding: 20px; @@ -216,6 +217,19 @@ transform: translateY(-2px); } +#backupRefreshButton { + position: absolute; + top: 20px; + right: 20px; + padding: 5px 10px; + font-size: 1.3rem; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; +} + .backup-item { background-color: rgba(0, 0, 0, 0.4); padding: 15px; @@ -229,7 +243,11 @@ line-height: 1.6; } -.backup-item:hover { +.backup-item.animate-in { + animation: slideIn 0.5s ease-out forwards; +} + +.backup-item:hover, .backup-item.animate-in:hover { background-color: rgba(0, 0, 0, 0.6); border-color: var(--primary); transform: translateX(5px); @@ -252,14 +270,13 @@ color: #000; } -@keyframes blink { - - 0%, +@keyframes slideIn { + 0% { + opacity: 0; + transform: scale(0.95) translateX(-20px); + } 100% { opacity: 1; - } - - 50% { - opacity: 0; + transform: scale(1) translateX(0); } } \ No newline at end of file diff --git a/UIMod/onboard_bundled/assets/css/info-notice.css b/UIMod/onboard_bundled/assets/css/info-notice.css new file mode 100644 index 00000000..22f11d08 --- /dev/null +++ b/UIMod/onboard_bundled/assets/css/info-notice.css @@ -0,0 +1,108 @@ +.info-notice { + border: 1px solid #4a90e2; + border-radius: 8px; + padding: 15px; + margin: 15px 0; + background: rgba(74, 144, 226, 0.1); + color: #e0e0e0; + box-shadow: 0 2px 8px rgba(74, 144, 226, 0.2); +} + +.info-notice h3 { + margin: 0; + font-size: 1.1em; + display: flex; + align-items: center; + gap: 8px; + color: #4a90e2; + cursor: pointer; +} + +.info-notice h3::after { + content: 'โ–ถ'; + font-size: 0.9em; + transition: transform 0.2s ease; +} + +.info-notice.open h3::after { + transform: rotate(90deg); +} + +.info-notice .collapsible-content { + display: none; + margin-top: 12px; +} + +.info-notice.open .collapsible-content { + display: block; +} + +.info-notice p { + margin: 8px 0; + line-height: 1.4; + font-size: 0.9em; +} + +.info-notice a { + color: #4a90e2; + text-decoration: underline; +} + +.info-notice a:hover { + color: #7bb3f0; +} + +.notice-icon { + display: inline-block; + font-size: 1.1em; +} + +.update { + border: 1px solid #af534c; + border-radius: 8px; + padding: 12px; + margin: 10px 0; + background: #784a47; + color: #c8e6c9; + box-shadow: 0 2px 6px rgba(76, 175, 80, 0.2); +} + +.update h3 { + margin: 0 0 8px 0; + font-size: 1em; + display: flex; + align-items: center; + gap: 6px; + color: #4caf50; +} + +.update p { + margin: 5px 0; + font-size: 0.85em; + line-height: 1.3; +} + +.status-good { + color: #4caf50; + font-weight: 600; +} + +@media (max-width: 768px) { + .info-notice { + margin: 10px 5px; + padding: 12px; + } + + .info-notice h3 { + font-size: 1em; + } + + .info-notice p { + font-size: 0.85em; + } + + .positive-update { + margin: 8px 5px; + padding: 10px; + } +} \ No newline at end of file diff --git a/UIMod/onboard_bundled/assets/css/popup.css b/UIMod/onboard_bundled/assets/css/popup.css new file mode 100644 index 00000000..6b304c2a --- /dev/null +++ b/UIMod/onboard_bundled/assets/css/popup.css @@ -0,0 +1,78 @@ +@import '/static/css/variables.css'; + +.popup { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(8px); + justify-content: center; + align-items: center; + z-index: 1000; +} + +.popup-content { + background-color: var(--bg-dark); + padding: 20px; + border-radius: 10px; + text-align: center; + max-width: 30vw; + box-shadow: 0 5px 30px var(--primary-glow); + overflow: hidden; +} +.popup-content h2 { + margin: 10px 0; + font-size: 1.5em; +} + +.popup-content p { + margin: 10px 0; +} + +.popup-content button { + margin-top: 15px; + padding: 10px 20px; + color: white; + border: none; + border-radius: 3px; + cursor: pointer; +} + +.popup.error .popup-content { + border-left: 2px solid #ff4d4d; +} + +.popup.error button { + background-color: #ff4d4d; +} + +.popup.error button:hover { + background-color: #e60000; +} + +.popup.success .popup-content { + border-left: 2px solid #28a745; +} + +.popup.success button { + background-color: #28a745; +} + +.popup.success button:hover { + background-color: #218838; +} + +.popup.info .popup-content { + border-left: 2px solid #17a2b8; +} + +.popup.info button { + background-color: #17a2b8; +} + +.popup.info button:hover { + background-color: #138496; +} \ No newline at end of file diff --git a/UIMod/onboard_bundled/assets/css/tabs.css b/UIMod/onboard_bundled/assets/css/tabs.css index 133284c9..735ec239 100644 --- a/UIMod/onboard_bundled/assets/css/tabs.css +++ b/UIMod/onboard_bundled/assets/css/tabs.css @@ -7,34 +7,34 @@ .tab-buttons { display: flex; - border-bottom: 2px solid var(--primary); margin-bottom: 0; } .tab-button { background: rgba(0, 0, 0, 0.3); border: 2px solid var(--primary-dim); - border-bottom: none; + border-bottom: 2px solid #00000000; padding: 10px 20px; margin-right: 5px; color: var(--primary); cursor: pointer; - border-radius: 8px 8px 0 0; + border-radius: 8px 8px 8px 8px; opacity: 0.7; transition: all var(--transition-normal); font-size: 0.9rem; + margin-bottom: -2px; } .tab-button:hover { background-color: rgba(0, 255, 171, 0.1); box-shadow: 0 -4px 10px rgba(0, 255, 171, 0.3); + border-bottom: 2px solid var(--primary-dim); } .tab-button.active { background-color: rgba(0, 255, 171, 0.2); opacity: 1; box-shadow: 0 -4px 10px rgba(0, 255, 171, 0.5); - border-color: var(--primary); } .tab-content { diff --git a/UIMod/onboard_bundled/assets/js/popup.js b/UIMod/onboard_bundled/assets/js/popup.js new file mode 100644 index 00000000..9b018c86 --- /dev/null +++ b/UIMod/onboard_bundled/assets/js/popup.js @@ -0,0 +1,34 @@ +function showPopup(status, message) { + const popup = document.getElementById('universalPopup'); + const popupTitle = document.getElementById('popupTitle'); + const popupMessage = document.getElementById('popupMessage'); + + popup.className = 'popup'; + popupTitle.textContent = ''; + popupMessage.textContent = message; + + switch(status.toLowerCase()) { + case 'error': + popup.classList.add('error'); + popupTitle.textContent = 'Error'; + break; + case 'success': + popup.classList.add('success'); + popupTitle.textContent = 'Success'; + break; + case 'info': + popup.classList.add('info'); + popupTitle.textContent = 'Info'; + break; + default: + popup.classList.add('info'); + popupTitle.textContent = 'Info'; + } + + popup.style.display = 'flex'; +} + +function closePopup() { + const popup = document.getElementById('universalPopup'); + popup.style.display = 'none'; +} \ No newline at end of file diff --git a/UIMod/onboard_bundled/assets/js/server-api.js b/UIMod/onboard_bundled/assets/js/server-api.js index 9ac55440..f7300812 100644 --- a/UIMod/onboard_bundled/assets/js/server-api.js +++ b/UIMod/onboard_bundled/assets/js/server-api.js @@ -25,13 +25,11 @@ function toggleServer(endpoint) { function triggerSteamCMD() { const status = document.getElementById('status'); status.hidden = false; - typeTextWithCallback(status, 'Triggering SteamCMD, please wait. SteamCMD will print log output only to the CLI ', 20, () => { + typeTextWithCallback(status, 'Running SteamCMD, please wait... ', 20, () => { fetch('/api/v2/steamcmd/run') .then(response => response.json()) .then(data => { - typeTextWithCallback(status, data.message, 20, () => { - setTimeout(() => status.hidden = true, 10000); - }); + showPopup("info", data.message); }) .catch(err => { typeTextWithCallback(status, 'Error: Failed to trigger SteamCMD', 20, () => { @@ -42,7 +40,6 @@ function triggerSteamCMD() { }); } -// Backup management function fetchBackups() { fetch('/api/v2/backups?mode=classic') .then(response => response.text()) @@ -53,11 +50,18 @@ function fetchBackups() { if (data.trim() === "No valid backup files found.") { backupList.textContent = data; } else { - data.split('\n').filter(Boolean).forEach(backup => { + let animationCount = 0; // Track number of animated items + data.split('\n').filter(Boolean).forEach((backup) => { const li = document.createElement('li'); li.className = 'backup-item'; li.innerHTML = `${backup} `; backupList.appendChild(li); + if (animationCount < 20) { + setTimeout(() => { + li.classList.add('animate-in'); + }, animationCount * 100); + animationCount++; + } }); } }) diff --git a/UIMod/onboard_bundled/assets/js/ui-utils.js b/UIMod/onboard_bundled/assets/js/ui-utils.js index b8a9ba21..d5561ff1 100644 --- a/UIMod/onboard_bundled/assets/js/ui-utils.js +++ b/UIMod/onboard_bundled/assets/js/ui-utils.js @@ -81,4 +81,11 @@ function getEventClassName(eventText) { return checks.find(([text, , condition]) => condition ? eventText.includes(text) && eventText.includes(condition) : eventText.includes(text) )?.[1] || ''; -} \ No newline at end of file +} + +document.querySelectorAll('.info-notice h3').forEach(header => { + header.addEventListener('click', () => { + const notice = header.parentElement; + notice.classList.toggle('open'); + }); +}); \ No newline at end of file diff --git a/UIMod/onboard_bundled/assets/script.js b/UIMod/onboard_bundled/assets/script.js deleted file mode 100644 index b171f63a..00000000 --- a/UIMod/onboard_bundled/assets/script.js +++ /dev/null @@ -1,520 +0,0 @@ -// /static/script.js -document.addEventListener('DOMContentLoaded', () => { - window.GPUSaverEnabled = localStorage.getItem('GPUSaverEnabled') === 'true' || false; - typeText(document.querySelector('h1'), 30); - setupTabs(); - fetchDetectionEvents(); - fetchBackups(); - handleConsole(); - pollServerStatus(); - // Create planets with size, orbit radius, speed, and color - const planetContainer = document.getElementById('planet-container'); - createPlanet(planetContainer, 80, 650, 34, 'rgba(200, 100, 50, 0.7)'); - createPlanet(planetContainer, 50, 1000, 46, 'rgba(100, 200, 150, 0.5)'); - createPlanet(planetContainer, 30, 1250, 63, 'rgba(50, 150, 250, 0.6)'); - createPlanet(planetContainer, 70, 400, 28, 'rgba(200, 150, 200, 0.7)'); - console.warn("If you see errors for sscm.js or sscm.css, you may want to enable SSCM."); - -}); - -// Global references to EventSource objects -let outputEventSource = null; -let detectionEventSource = null; - -// Utility function for typing text -function typeText(element, speed) { - // Check if typing is already in progress - if (element.dataset.isTyping === 'true') { - // Optionally, clear the previous timeout (requires storing it) - clearTimeout(element.dataset.timeoutId); - } - - const fullText = element.textContent; - element.textContent = ''; - element.dataset.isTyping = 'true'; // Mark as typing - let i = 0; - - const typeChar = () => { - if (i < fullText.length) { - element.textContent += fullText.charAt(i++); - const timeoutId = setTimeout(typeChar, speed); - element.dataset.timeoutId = timeoutId; // Store timeout ID - } else { - element.dataset.isTyping = 'false'; // Done typing - delete element.dataset.timeoutId; - } - }; - typeChar(); -} - -// Utility function for typing text with a callback -function typeTextWithCallback(element, text, speed, callback) { - if (element.dataset.isTyping === 'true') { - clearTimeout(element.dataset.timeoutId); - } - - element.textContent = ''; - element.dataset.isTyping = 'true'; - let i = 0; - - const typeChar = () => { - if (i < text.length) { - element.textContent += text.charAt(i++); - const timeoutId = setTimeout(typeChar, speed); - element.dataset.timeoutId = timeoutId; - } else { - element.dataset.isTyping = 'false'; - delete element.dataset.timeoutId; - if (callback) setTimeout(callback, 50); - } - }; - typeChar(); -} - -// Tab management -function setupTabs() { - showTab('console-tab'); -} - -function showTab(tabId) { - document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active')); - document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); - const tab = document.getElementById(tabId); - tab.classList.add('active'); - document.querySelector(`.tab-button[onclick*="showTab('${tabId}')"]`).classList.add('active'); -} - -// Server control functions -function startServer() { - toggleServer('/start'); -} - -function stopServer() { - toggleServer('/stop'); -} - -function toggleServer(endpoint) { - const status = document.getElementById('status'); - fetch(endpoint) - .then(response => response.text()) - .then(data => { - status.hidden = false; - typeTextWithCallback(status, data, 20, () => { - setTimeout(() => status.hidden = true, 10000); - }); - }) - .catch(err => console.error(`Failed to ${endpoint}:`, err)); -} - -// EventSource management -function closeEventSources() { - [outputEventSource, detectionEventSource].forEach(source => { - if (source) { - source.close(); - console.log(`${source === outputEventSource ? 'Output' : 'Detection events'} stream closed`); - } - }); - outputEventSource = detectionEventSource = null; -} - -function navigateTo(url) { - closeEventSources(); - window.location.href = url; -} - -// Detection events streaming -function fetchDetectionEvents() { - const maxMessages = 500; - const detectionConsole = document.getElementById('detection-console'); - - const connect = () => { - detectionEventSource = new EventSource('/events'); - - detectionEventSource.onmessage = event => { - const message = document.createElement('div'); - message.className = `detection-event ${getEventClassName(event.data)}`; - - const timestamp = document.createElement('span'); - timestamp.className = 'event-timestamp'; - timestamp.textContent = `${new Date().toLocaleTimeString()}: `; - - const content = document.createElement('span'); - content.textContent = event.data; - - message.append(timestamp, content); - detectionConsole.appendChild(message); - - while (detectionConsole.childElementCount > maxMessages) { - detectionConsole.firstChild.remove(); - } - detectionConsole.scrollTop = detectionConsole.scrollHeight; - - const detectionTab = document.getElementById('detection-tab'); - if (!detectionTab.classList.contains('active')) { - const tabButton = document.querySelector('.tab-button[onclick*="detection-tab"]'); - tabButton.classList.add('notification'); - setTimeout(() => tabButton.classList.remove('notification'), 3000); - } - }; - - detectionEventSource.onopen = () => console.log("Detection events stream connected"); - - detectionEventSource.onerror = () => { - console.error("Detection events stream disconnected"); - detectionEventSource.close(); - detectionEventSource = null; - if (window.location.pathname === '/') { - setTimeout(connect, 2000); - } - }; - }; - connect(); -} - -function getEventClassName(eventText) { - const checks = [ - ['Server is ready', 'event-server-ready'], - ['Server is starting', 'event-server-starting'], - ['Server error', 'event-server-error'], - ['Player', 'connecting', 'event-player-connecting'], - ['Player', 'ready', 'event-player-ready'], - ['Player', 'disconnected', 'event-player-disconnect'], - ['World Saved', 'event-world-saved'], - ['Exception', 'event-exception'] - ]; - - return checks.find(([text, , condition]) => - condition ? eventText.includes(text) && eventText.includes(condition) : eventText.includes(text) - )?.[1] || ''; -} - -// Backup management -function fetchBackups() { - fetch('/api/v2/backups?mode=classic') - .then(response => response.text()) - .then(data => { - const backupList = document.getElementById('backupList'); - backupList.innerHTML = ''; - - if (data.trim() === "No valid backup files found.") { - backupList.textContent = data; - } else { - data.split('\n').filter(Boolean).forEach(backup => { - const li = document.createElement('li'); - li.className = 'backup-item'; - li.innerHTML = `${backup} `; - backupList.appendChild(li); - }); - } - }) - .catch(err => console.error("Failed to fetch backups:", err)); -} - -function extractIndex(backupText) { - return backupText.match(/Index: (\d+)/)?.[1] || null; -} - -function restoreBackup(index) { - const status = document.getElementById('status'); - fetch(`/api/v2/backups/restore?index=${index}`) - .then(response => response.text()) - .then(data => { - status.hidden = false; - typeTextWithCallback(status, data, 20, () => { - setTimeout(() => status.hidden = true, 30000); - }); - }) - .catch(err => console.error(`Failed to restore backup ${index}:`, err)); -} - -// Console initialization with SSE stream setup -function handleConsole() { - const consoleElement = document.getElementById('console'); - consoleElement.innerHTML = ''; - const bootTitle = "Interface initializing..."; - const bootCompleteMessage = "Interface ready.๐ŸŽฎ Happy gaming! ๐ŸŽฎ"; - const bugChance = Math.random(); - const bugMessage = "ERROR: Nuclear parts in airflow detected! Initiating repair sequence..."; - - const funMessages = [ - "Calibrating quantum flux capacitors...", - "Initializing player happiness modules...", - "Checking for monsters under the server...", - "Brewing coffee for the CPU...", - "Charging laser sharks...", - "Teaching AI to say 'please' and 'thank you'...", - "Polishing pixels to a mirror shine...", - "Convincing electrons to flow in the right direction...", - "Rebooting atmospheric systems for the 17th time...", - "Attempting to locate your body after that last airlock malfunction...", - "Converting oxygen to errors at alarming efficiency...", - "Persuading physics engine to acknowledge gravity exists...", - "Calculating ways your base will catastrophically depressurize...", - "Optimizing unity garbage collection (good luck with that)...", - "Aligning planetary rotation with server tick rate...", - "Patching holes in space-time continuum and your habitat...", - "Convincing solar panels that 'sun' is not just a theoretical concept...", - "Negotiating peace treaty between logic circuits and the laws of thermodynamics...", - "Compressing atmosphere until your CPU begs for mercy...", - "Measuring distance between you and nearest fatal bug...", - "Attempting to explain 'pipe networks' to confused server hamsters...", - "Calculating probability of survival (spoiler: it's low)...", - "Wrangling rogue Unity instances back into containment...", - "Sacrificing RAM to the gods of stable framerates...", - "Convincing electrons to flow in the right direction... nope, the power grid's borked.", - "Patching hull breaches with duct tape and prayers...", - "Recalculating O2 levels... wait, why is it all CO2 now?", - "Spinning up the fabricator... hope it doesnโ€™t eat the server this time.", - "Debugging Unity physics... object launched into orbit, send help.", - "Warming up the furnace... or just setting the base on fire, 50/50 shot.", - "Rerouting pipes... because who needs logical fluid dynamics anyway?", - "Loading terrain... oh look, itโ€™s floating 3 meters above the ground again.", - "Processing ore... into a fine paste of lag and despair.", - "Stabilizing frame rate... lol, just kidding, welcome to 12 FPS city.", - "Checking for updates... new bug introduced, feature still broken!", - "Assembling solar tracker... now itโ€™s tracking the admin instead.", - "Balancing gas mixtures... kaboom imminent, run you fool!" - ]; - - const addMessage = (text, color, style = 'normal') => { - const div = document.createElement('div'); - div.textContent = text; - div.style.color = color; - div.style.fontStyle = style; - consoleElement.appendChild(div); - consoleElement.scrollTop = consoleElement.scrollHeight; - }; - - // Dynamically create SSCM command input - const createCommandInput = async () => { - try { - // Make API call to check if SSCM is enabled - const response = await fetch('/api/v2/SSCM/enabled', { - method: 'GET', - headers: { - 'Accept': 'application/json' - } - }); - - // If status is not 200, exit the function - if (response.status !== 200) { - console.log('SSCM is not enabled, status:', response.status); - return; - } - - // Proceed to create command input UI if status is 200 - console.log("Creating command input..."); - const commandContainer = document.createElement('div'); - commandContainer.className = 'sscm-command-container'; - - const prompt = document.createElement('span'); - prompt.className = 'prompt'; - prompt.textContent = '>'; - - const input = document.createElement('input'); - input.id = 'sscm-command-input'; - input.type = 'text'; - input.placeholder = 'Enter command..'; - input.setAttribute('autocomplete', 'off'); - - const suggestions = document.createElement('div'); - suggestions.id = 'sscm-autocomplete-suggestions'; - suggestions.className = 'sscm-suggestions'; - - commandContainer.append(prompt, input, suggestions); - consoleElement.appendChild(commandContainer); - } catch (error) { - console.error('Error checking SSCM enabled status:', error); - return; // Exit on error - } - }; - - // Start with initializing message - typeTextWithCallback(consoleElement, bootTitle, 30, () => { - // Show two funny messages while connecting - const messageIndex1 = Math.floor(Math.random() * funMessages.length); - addMessage(funMessages[messageIndex1], '#0af', 'italic'); - - let messageIndex2; - do { - messageIndex2 = Math.floor(Math.random() * funMessages.length); - } while (messageIndex2 === messageIndex1); - addMessage(funMessages[messageIndex2], '#0af', 'italic'); - - // Set up the persistent console stream - outputEventSource = new EventSource('/console'); - - // Persistent message handler - outputEventSource.onmessage = event => { - const message = document.createElement('div'); - message.textContent = event.data; - consoleElement.insertBefore(message, consoleElement.querySelector('.sscm-command-container')); // Insert before input - // Auto-scroll only if at bottom - if (consoleElement.scrollTop + consoleElement.clientHeight >= consoleElement.scrollHeight - 10) { - consoleElement.scrollTop = consoleElement.scrollHeight; - } - }; - - outputEventSource.onopen = () => { - console.log("Console stream connected"); - finishInitialization(); - }; - - outputEventSource.onerror = () => { - console.error("Console stream disconnected"); - outputEventSource.close(); - outputEventSource = null; - addMessage("Warning: Console stream unavailable. Retrying...", '#ff0'); - if (window.location.pathname === '/') { - setTimeout(() => { - if (!outputEventSource) { - // Re-run setup to reconnect - consoleElement.innerHTML = ''; // Clear console for fresh start - handleConsole(); - } - }, 2000); - } - }; - }); - - function finishInitialization() { - if (bugChance < 0.05) { - addMessage(bugMessage, 'red'); - setTimeout(() => { - addMessage("Repair complete. Continuing initialization...", 'green'); - completeBoot(); - }, 1000); - } else { - completeBoot(); - } - } - - function completeBoot() { - setTimeout(() => { - createCommandInput(); // Add input after boot - addMessage(bootCompleteMessage, '#0f0'); - consoleElement.scrollTop = consoleElement.scrollHeight; - }, 500); - } -} - -function createPlanet(container, size, orbitRadius, speed, color) { - const orbit = document.createElement('div'); - orbit.classList.add('orbit'); - orbit.style.width = `${orbitRadius * 2}px`; - orbit.style.height = `${orbitRadius * 2}px`; - orbit.style.position = 'absolute'; - orbit.style.left = '50%'; - orbit.style.top = '50%'; - orbit.style.transform = 'translate(-50%, -50%)'; - - // Add random delay to start animation at different points - const randomDelay = -(Math.random() * speed); // Negative delay to offset start - orbit.style.animation = `orbit ${speed}s linear infinite ${randomDelay}s`; - - const planet = document.createElement('div'); - planet.classList.add('planet'); - planet.style.width = `${size}px`; - planet.style.height = `${size}px`; - planet.style.position = 'absolute'; - planet.style.left = '0%'; - planet.style.top = '50%'; - planet.style.backgroundColor = color; - planet.style.borderRadius = '50%'; - planet.style.boxShadow = `0 0 20px ${color}`; - - orbit.appendChild(planet); - container.appendChild(orbit); -} - - -function pollServerStatus() { - window.gamserverstate = false; - const statusInterval = setInterval(() => { - fetch('/api/v2/server/status') - .then(response => response.json()) - .then(data => { - updateStatusIndicator(data.isRunning); - if (data.uuid) { - localStorage.setItem('gameserverrunID', data.uuid); - } - }) - .catch(err => { - console.error("Failed to fetch server status:", err); - updateStatusIndicator(false, true); // Set error state - }); - }, 3500); // Poll every 3.5 seconds (adjusted from 1000 to reduce server load checking the status each time) - - // Store the interval ID so we can clear it if needed - window.statusPollingInterval = statusInterval; -} - -function updateStatusIndicator(isRunning, isError = false) { - const indicator = document.getElementById('status-indicator'); - - if (isError) { - indicator.className = 'status-indicator error'; - indicator.title = 'Error fetching server status'; - window.gamserverstate = false; - return; - } - - if (isRunning) { - indicator.className = 'status-indicator online'; - indicator.title = 'Server is running'; - window.gamserverstate = true; - } else { - indicator.className = 'status-indicator offline'; - indicator.title = 'Server is offline'; - window.gamserverstate = false; - } -} - -function resourceSaver(pause) { - // Get space background once outside the loop - const spaceBackground = document.getElementById('space-background'); - - // Handle animation states for all elements - document.querySelectorAll('*').forEach(element => { - element.style.animationPlayState = pause ? 'paused' : 'running'; - }); - - // Fade the space background in/out instead of abrupt display change - if (pause) { - // Fade out - spaceBackground.style.transition = 'opacity 0.5s ease'; - spaceBackground.style.opacity = '0'; - // Only hide it after the fade completes - setTimeout(() => { - if (document.hasFocus() === false) { // Double-check we're still unfocused - spaceBackground.style.display = 'none'; - } - }, 500); - } else { - // Make it visible first, then fade in - spaceBackground.style.display = 'block'; - // Use setTimeout to ensure the display change is processed before starting the fade - setTimeout(() => { - spaceBackground.style.transition = 'opacity 0.5s ease'; - spaceBackground.style.opacity = '1'; - }, 10); - } -} - -function toggleGPUSaver() { - window.GPUSaverEnabled = !window.GPUSaverEnabled; - localStorage.setItem('GPUSaverEnabled', window.GPUSaverEnabled); -} - -// Event listeners for window focus and blur -window.addEventListener('focus', () => { - if (window.GPUSaverEnabled) { - resourceSaver(false); // Resume animations when page is in focus - } -}); - -window.addEventListener('blur', () => { - if (window.GPUSaverEnabled) { - resourceSaver(true); // Pause animations when page loses focus - } -}); \ No newline at end of file diff --git a/UIMod/onboard_bundled/localization/en-US.json b/UIMod/onboard_bundled/localization/en-US.json index 9ddbceb5..ac8e12ee 100644 --- a/UIMod/onboard_bundled/localization/en-US.json +++ b/UIMod/onboard_bundled/localization/en-US.json @@ -12,7 +12,7 @@ "UIText_API_Info": "API Endpoint Reference", "UIText_Copyright": "Copyright", "UIText_Copyright1": "Licensed under", - "UIText_Copyright2": "Proprietary License." + "UIText_Copyright2": "Proprietary License" }, "config": { "UIText_ServerConfig": "Server Configuration", @@ -284,6 +284,7 @@ "UIText_Finalize_SubmitButton": "Return to Start", "UIText_Finalize_SkipButton": "Skip Authentication", "UIText_Login_Title": "Stationeers Server UI", + "UIText_Login_HeaderTitle": "Login", "UIText_Login_PrimaryLabel": "Username", "UIText_Login_SecondaryLabel": "Password", "UIText_Login_PrimaryPlaceholder": "Enter Username", diff --git a/UIMod/onboard_bundled/ui/index.html b/UIMod/onboard_bundled/ui/index.html index 2e62b574..d6b14440 100644 --- a/UIMod/onboard_bundled/ui/index.html +++ b/UIMod/onboard_bundled/ui/index.html @@ -1,5 +1,6 @@ + @@ -11,102 +12,22 @@ + + - +
+
@@ -140,17 +61,18 @@

Stationeers Server UI v{{.Version}} ({{.Branch}})

{{.UIText_Backup_Manager}}

+
@@ -52,7 +54,7 @@ And the "best part?" The Demo current is not on the V5 but V4, so if you are con 2. ๐Ÿ“ Place in empty folder and run it on Linux or Windows (chmod +x on linux) 3. ๐ŸŒ Access UI at `https://<>:8443` 4. ๐Ÿ“š See [First-Time Setup](https://github.com/JacksonTheMaster/StationeersServerUI/wiki/First-Time-Setup) in the wiki -5. ๐Ÿ”’ If you set this up too quickly and are in search of the Default Username/Password, see [Security Considerations](https://github.com/JacksonTheMaster/StationeersServerUI/wiki/Security-Considerations) for more details! +5. ๐Ÿ“– Read the [Wiki](https://github.com/JacksonTheMaster/StationeersServerUI/wiki) and follow the chained pages (links at bottom of page)! ## What is This? diff --git a/src/cli/terminalmsg.go b/src/cli/terminalmsg.go index 9c89873d..4d9c87a5 100644 --- a/src/cli/terminalmsg.go +++ b/src/cli/terminalmsg.go @@ -36,7 +36,7 @@ func PrintStartupMessage() { fmt.Println(" โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•") // Web UI info - fmt.Println("\n ๐ŸŒ Web UI available at: https://localhost:8443 (default) or https://:8443") + fmt.Println("\n ๐ŸŒ Web UI available at: https://localhost:8443 (default) or https://:" + config.SSUIWebPort) fmt.Println("\n ๐ŸŒ Support available at: https://discord.gg/8n3vN92MyJ") // Quote diff --git a/src/config/config.go b/src/config/config.go index 15710012..2f592b5b 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -11,7 +11,7 @@ import ( var ( // All configuration variables can be found in vars.go - Version = "5.5.8" + Version = "5.5.9" Branch = "release" ) @@ -74,6 +74,8 @@ type JsonConfig struct { IsConsoleEnabled *bool `json:"IsConsoleEnabled"` LanguageSetting string `json:"LanguageSetting"` AutoStartServerOnStartup *bool `json:"AutoStartServerOnStartup"` + AdditionalLoginHeaderText string `json:"AdditionalLoginHeaderText"` + SSUIWebPort string `json:"SSUIWebPort"` } type CustomDetection struct { @@ -160,6 +162,8 @@ func applyConfig(cfg *JsonConfig) { GamePort = getString(cfg.GamePort, "GAME_PORT", "27016") UpdatePort = getString(cfg.UpdatePort, "UPDATE_PORT", "27015") LanguageSetting = getString(cfg.LanguageSetting, "LANGUAGE_SETTING", "en-US") + AdditionalLoginHeaderText = getString(cfg.AdditionalLoginHeaderText, "ADDITIONAL_LOGIN_HEADER_TEXT", "") + SSUIWebPort = getString(cfg.SSUIWebPort, "SSUI_WEB_PORT", "8443") upnpEnabledVal := getBool(cfg.UPNPEnabled, "UPNP_ENABLED", false) UPNPEnabled = upnpEnabledVal diff --git a/src/config/vars.go b/src/config/vars.go index 995d45e7..26df6c36 100644 --- a/src/config/vars.go +++ b/src/config/vars.go @@ -51,24 +51,25 @@ var ( // Logging, debugging and misc var ( - IsDebugMode bool //only used for pprof server, keep it like this and check the log level instead. Debug = 10 - CreateSSUILogFile bool - LogLevel int - LogMessageBuffer string - IsFirstTimeSetup bool - BufferFlushTicker *time.Ticker - SSEMessageBufferSize = 2000 - MaxSSEConnections = 20 - GameServerAppID = "600760" - ExePath string - GameBranch string - SubsystemFilters []string - GameServerUUID uuid.UUID // Assined at startup to the current instance of the server we are managing. Currently unused. - AutoRestartServerTimer string - IsConsoleEnabled bool - LogClutterToConsole bool // surpresses clutter mono logs from the gameserver - LanguageSetting string - AutoStartServerOnStartup bool + IsDebugMode bool //only used for pprof server, keep it like this and check the log level instead. Debug = 10 + CreateSSUILogFile bool + LogLevel int + LogMessageBuffer string + IsFirstTimeSetup bool + BufferFlushTicker *time.Ticker + SSEMessageBufferSize = 2000 + MaxSSEConnections = 20 + GameServerAppID = "600760" + ExePath string + GameBranch string + SubsystemFilters []string + GameServerUUID uuid.UUID // Assined at startup to the current instance of the server we are managing. Currently unused. + AutoRestartServerTimer string + IsConsoleEnabled bool + LogClutterToConsole bool // surpresses clutter mono logs from the gameserver + LanguageSetting string + AutoStartServerOnStartup bool + AdditionalLoginHeaderText string ) // Discord integration @@ -109,6 +110,7 @@ var ( JwtKey string AuthTokenLifetime int Users map[string]string + SSUIWebPort string ) // SSUI Updates diff --git a/src/core/loader/afterstart.go b/src/core/loader/afterstart.go index 9f1c3027..d5ad20b6 100644 --- a/src/core/loader/afterstart.go +++ b/src/core/loader/afterstart.go @@ -2,6 +2,7 @@ package loader import ( "github.com/JacksonTheMaster/StationeersServerUI/v5/src/config" + "github.com/JacksonTheMaster/StationeersServerUI/v5/src/discordrpc" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/managers/gamemgr" "github.com/JacksonTheMaster/StationeersServerUI/v5/src/setup" @@ -29,4 +30,6 @@ func AfterStartComplete() { gamemgr.InternalStartServer() } setup.SetupAutostartScripts() + // start discordrpc in a separate goroutine + discordrpc.StartDiscordRPC() } diff --git a/src/discordrpc/discordrpc.go b/src/discordrpc/discordrpc.go new file mode 100644 index 00000000..1f4d0d49 --- /dev/null +++ b/src/discordrpc/discordrpc.go @@ -0,0 +1,28 @@ +package discordrpc + +import ( + "time" + + "github.com/JacksonTheMaster/StationeersServerUI/v5/src/logger" + "github.com/jacksonthemaster/discordrichpresence" +) + +// StartDiscordRPC starts the Discord Rich Presence client in a non-blocking manner. +// It returns the client so the caller can manage its lifecycle (e.g., call Close()). +func StartDiscordRPC() (*discordrichpresence.Client, error) { + client := discordrichpresence.NewClient("1408848834875887669") + activity := discordrichpresence.NewActivity(). + State("Managing a Stationeers Server"). + Details("Your one-stop-shop for running a Stationeers server"). + StartTime(time.Now()). + LargeImage("logo", "The easy to use Stationeers Dedicated Server Manager"). + SmallImage("rocket", "Online and Active"). + Type(0). + Build() + if err := client.StartWithActivity(activity, 30*time.Second); err != nil { + return nil, err + } + + logger.Core.Debug("Discord Rich Presence started successfully!") + return client, nil +} diff --git a/src/localization/localization.go b/src/localization/localization.go index 49baa043..512871b0 100644 --- a/src/localization/localization.go +++ b/src/localization/localization.go @@ -120,6 +120,6 @@ func GetString(key string) string { } // Return key as final fallback - logger.Localization.Warn("Translation not found for key: " + key) + logger.Localization.Debug("Translation not found for key: " + key) return key } diff --git a/src/managers/backupmgr/restore.go b/src/managers/backupmgr/restore.go index ed0fed23..f02a1046 100644 --- a/src/managers/backupmgr/restore.go +++ b/src/managers/backupmgr/restore.go @@ -1,7 +1,9 @@ package backupmgr import ( + "archive/zip" "fmt" + "io" "os" "path/filepath" "strings" @@ -56,7 +58,7 @@ func (m *BackupManager) RestoreBackup(index int) error { existingFile := filepath.Join(saveDir, file.Name()) // Move existing .save file to SafeBackupDir with timestamp to avoid overwrites timestamp := time.Now().Format("2006-01-02_15-04-05") - savedPreviousHeadSaveFilePath := filepath.Join(m.config.SafeBackupDir, fmt.Sprintf("%s_%s_%s", "oldHeadSaveBackup", timestamp, file.Name())) + savedPreviousHeadSaveFilePath := filepath.Join(m.config.SafeBackupDir, fmt.Sprintf("%s_%s_%s", "pre-restore-HEAD-", timestamp, file.Name())) if err := os.Rename(existingFile, savedPreviousHeadSaveFilePath); err != nil { return fmt.Errorf("failed to move existing HEAD .save file %s to %s: %w", existingFile, savedPreviousHeadSaveFilePath, err) } @@ -64,12 +66,123 @@ func (m *BackupManager) RestoreBackup(index int) error { } } - // Now copy the new .save file - if err := copyFile(backupFile, destFile); err != nil { + // Create temp directory for mod time shenenigans (https://discordapp.com/channels/276525882049429515/392080751648178188/1407157281606336602) + tempDir := filepath.Join("./saves", m.config.WorldName, "tmp") + if err := os.MkdirAll(tempDir, os.ModePerm); err != nil { + return fmt.Errorf("failed to create temp directory %s: %w", tempDir, err) + } + defer os.RemoveAll(tempDir) + + // Extract .save (zip) file to tempDir + r, err := zip.OpenReader(backupFile) + if err != nil { + return fmt.Errorf("failed to open zip reader for %s: %w", backupFile, err) + } + defer r.Close() + + for _, f := range r.File { + path := filepath.Join(tempDir, f.Name) + if f.FileInfo().IsDir() { + if err := os.MkdirAll(path, f.Mode()); err != nil { + m.revertRestore(restoredFiles) + return fmt.Errorf("failed to create directory %s: %w", path, err) + } + continue + } + + if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { + m.revertRestore(restoredFiles) + return fmt.Errorf("failed to create parent directory for %s: %w", path, err) + } + + outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + m.revertRestore(restoredFiles) + return fmt.Errorf("failed to create file %s: %w", path, err) + } + + rc, err := f.Open() + if err != nil { + outFile.Close() + m.revertRestore(restoredFiles) + return fmt.Errorf("failed to open file in zip %s: %w", f.Name, err) + } + + if _, err := io.Copy(outFile, rc); err != nil { + rc.Close() + outFile.Close() + m.revertRestore(restoredFiles) + return fmt.Errorf("failed to extract file %s: %w", path, err) + } + rc.Close() + outFile.Close() + } + + // Modify timestamps of extracted files to current system time + now := time.Now() + if err := filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + return os.Chtimes(path, now, now) + }); err != nil { + m.revertRestore(restoredFiles) + return fmt.Errorf("failed to modify timestamps in %s: %w", tempDir, err) + } + + // Create new .save (zip) file at destFile with updated timestamps + dest, err := os.Create(destFile) + if err != nil { + m.revertRestore(restoredFiles) + return fmt.Errorf("failed to create destination .save file %s: %w", destFile, err) + } + defer dest.Close() + + w := zip.NewWriter(dest) + defer w.Close() + + if err := filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + relPath, err := filepath.Rel(tempDir, path) + if err != nil { + return fmt.Errorf("failed to get relative path for %s: %w", path, err) + } + relPath = filepath.ToSlash(relPath) + + // Create zip entry with current system timestamp + fw, err := w.CreateHeader(&zip.FileHeader{ + Name: relPath, + Method: zip.Deflate, + Modified: now, + }) + if err != nil { + return fmt.Errorf("failed to create zip entry %s: %w", relPath, err) + } + + srcFile, err := os.Open(path) + if err != nil { + return fmt.Errorf("failed to open file %s: %w", path, err) + } + defer srcFile.Close() + + if _, err := io.Copy(fw, srcFile); err != nil { + return fmt.Errorf("failed to write file %s to zip: %w", relPath, err) + } + return nil + }); err != nil { m.revertRestore(restoredFiles) return fmt.Errorf("failed to restore .save file %s: %w", backupFile, err) } restoredFiles[destFile] = backupFile + return nil // restore and mod time shenanigans successful, no need to return an error } else { // Old-style trio (world_meta.xml, world.xml, world.bin) files := []struct { diff --git a/src/setup/steamcmd.go b/src/setup/steamcmd.go index 7db0d37a..962176a2 100644 --- a/src/setup/steamcmd.go +++ b/src/setup/steamcmd.go @@ -1,6 +1,7 @@ package setup import ( + "fmt" "io" "os" "os/exec" @@ -26,24 +27,25 @@ const ( ) // InstallAndRunSteamCMD installs and runs SteamCMD based on the platform (Windows/Linux). -// It automatically detects the OS and calls the appropriate installation function. -func InstallAndRunSteamCMD() { +// It returns the exit status of the SteamCMD execution and any error encountered. +func InstallAndRunSteamCMD() (int, error) { if config.Branch == "indev-no-steamcmd" { logger.Install.Info("๐Ÿ” Detected indev-no-steamcmd branch, skipping SteamCMD installation") - return + return 0, nil } if runtime.GOOS == "windows" { - installSteamCMDWindows() + return installSteamCMDWindows() } else if runtime.GOOS == "linux" { - installSteamCMDLinux() + return installSteamCMDLinux() } else { - logger.Install.Error("โŒ SteamCMD installation is not supported on this OS.\n") - return + err := fmt.Errorf("SteamCMD installation is not supported on this OS") + logger.Install.Error("โŒ " + err.Error() + "\n") + return -1, err } } -func installSteamCMD(platform string, steamCMDDir string, downloadURL string, extractFunc ExtractorFunc) { +func installSteamCMD(platform string, steamCMDDir string, downloadURL string, extractFunc ExtractorFunc) (int, error) { // Check if SteamCMD is already installed if _, err := os.Stat(steamCMDDir); os.IsNotExist(err) { logger.Install.Warn("โš ๏ธ SteamCMD not found for " + platform + ", downloading...\n") @@ -51,7 +53,7 @@ func installSteamCMD(platform string, steamCMDDir string, downloadURL string, ex // Create SteamCMD directory if err := createSteamCMDDirectory(steamCMDDir); err != nil { logger.Install.Error("โŒ Error creating SteamCMD directory: " + err.Error() + "\n") - return + return -1, err } // Ensure cleanup on failure @@ -66,55 +68,54 @@ func installSteamCMD(platform string, steamCMDDir string, downloadURL string, ex // Install required libraries if err := installRequiredLibraries(); err != nil { logger.Install.Error("โŒ Error installing required libraries: " + err.Error() + "\n") - return + return -1, err } // Download and extract SteamCMD if err := downloadAndExtractSteamCMD(downloadURL, steamCMDDir, extractFunc); err != nil { logger.Install.Error("โŒ " + err.Error() + "\n") - return + return -1, err } // Set executable permissions for SteamCMD files if err := setExecutablePermissions(steamCMDDir); err != nil { logger.Install.Error("โŒ Error setting executable permissions: " + err.Error() + "\n") - return + return -1, err } // Verify the steamcmd binary if err := verifySteamCMDBinary(steamCMDDir); err != nil { logger.Install.Error("โŒ " + err.Error() + "\n") - return + return -1, err } // Mark installation as successful success = true logger.Install.Info("โœ… SteamCMD installed successfully.\n") } else { - logger.Install.Info("โœ… SteamCMD is already installed.") } - // Run SteamCMD - runSteamCMD(steamCMDDir) + // Run SteamCMD and return its exit status and error + return runSteamCMD(steamCMDDir) } // installSteamCMDLinux downloads and installs SteamCMD on Linux. -func installSteamCMDLinux() { - installSteamCMD("Linux", SteamCMDLinuxDir, SteamCMDLinuxURL, untarWrapper) +func installSteamCMDLinux() (int, error) { + return installSteamCMD("Linux", SteamCMDLinuxDir, SteamCMDLinuxURL, untarWrapper) } // installSteamCMDWindows downloads and installs SteamCMD on Windows. -func installSteamCMDWindows() { - installSteamCMD("Windows", SteamCMDWindowsDir, SteamCMDWindowsURL, unzip) +func installSteamCMDWindows() (int, error) { + return installSteamCMD("Windows", SteamCMDWindowsDir, SteamCMDWindowsURL, unzip) } -// runSteamCMD runs the SteamCMD command to update the game. -func runSteamCMD(steamCMDDir string) { +// runSteamCMD runs the SteamCMD command to update the game and returns its exit status and any error. +func runSteamCMD(steamCMDDir string) (int, error) { currentDir, err := os.Getwd() if err != nil { logger.Install.Error("โŒ Error getting current working directory: " + err.Error() + "\n") - return + return -1, err } logger.Install.Debug("โœ… Current working directory: " + currentDir + "\n") @@ -122,7 +123,7 @@ func runSteamCMD(steamCMDDir string) { if runtime.GOOS != "windows" { if err := setExecutablePermissions(steamCMDDir); err != nil { logger.Install.Error("โŒ Error setting executable permissions, your Steamcmd install might be broken: " + err.Error() + "\n") - return + return -1, err } } @@ -142,10 +143,15 @@ func runSteamCMD(steamCMDDir string) { } err = cmd.Run() if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + logger.Install.Error("โŒ SteamCMD exited unsuccessfully: " + err.Error() + "\n") + return exitErr.ExitCode(), err + } logger.Install.Error("โŒ Error running SteamCMD: " + err.Error() + "\n") - return + return -1, err } logger.Install.Info("โœ… SteamCMD executed successfully.\n") + return 0, nil } // buildSteamCMDCommand constructs the SteamCMD command based on the OS. diff --git a/src/web/TwoBoxForm.go b/src/web/TwoBoxForm.go index 4fc7efe7..fbeb3a9b 100644 --- a/src/web/TwoBoxForm.go +++ b/src/web/TwoBoxForm.go @@ -468,7 +468,7 @@ func ServeTwoBoxFormTemplate(w http.ResponseWriter, r *http.Request) { default: data.Title = localization.GetString("UIText_Login_Title") - data.HeaderTitle = localization.GetString("UIText_Login_HeaderTitle") + data.HeaderTitle = localization.GetString("UIText_Login_HeaderTitle") + config.AdditionalLoginHeaderText data.PrimaryLabel = localization.GetString("UIText_Login_PrimaryLabel") data.SecondaryLabel = localization.GetString("UIText_Login_SecondaryLabel") data.PrimaryPlaceholderText = localization.GetString("UIText_Login_PrimaryPlaceholder") diff --git a/src/web/http.go b/src/web/http.go index 38f5f4ee..b246b933 100644 --- a/src/web/http.go +++ b/src/web/http.go @@ -162,13 +162,18 @@ func HandleRunSteamCMD(w http.ResponseWriter, r *http.Request) { time.Sleep(10000 * time.Millisecond) } logger.Core.Info("Running SteamCMD") - setup.InstallAndRunSteamCMD() + _, err := setup.InstallAndRunSteamCMD() // Update last execution time lastSteamCMDExecution = time.Now() // Success: return 202 Accepted and JSON - w.WriteHeader(http.StatusAccepted) + w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{"statuscode": "202", "status": "Accepted", "message": "SteamCMD ran successfully."}) + if err == nil { + json.NewEncoder(w).Encode(map[string]string{"statuscode": "202", "status": "Success", "message": "SteamCMD ran successfully, gameserver files are up-to-date!"}) + return + } + // Failure: return 202 Accepted and JSON with the error message + json.NewEncoder(w).Encode(map[string]string{"statuscode": "202", "status": "Failed", "message": "SteamCMD ran unsuccessfully:" + err.Error()}) } diff --git a/src/web/start.go b/src/web/start.go index d2e5c93e..95476f93 100644 --- a/src/web/start.go +++ b/src/web/start.go @@ -90,7 +90,7 @@ func StartWebServer(wg *sync.WaitGroup) { logger.Web.Error("Error setting up TLS certificates: " + err.Error()) //os.Exit(1) } - err := http.ListenAndServeTLS("0.0.0.0:8443", config.TLSCertPath, config.TLSKeyPath, mux) + err := http.ListenAndServeTLS("0.0.0.0:"+config.SSUIWebPort, config.TLSCertPath, config.TLSKeyPath, mux) if err != nil { logger.Web.Error("Error starting HTTPS server: " + err.Error()) }