(function () { "use strict"; const theme = window.NEBULA_THEME || {}; const loader = document.getElementById('nebula-loader'); const appRoot = document.getElementById('app'); applyCustomBackground(); mountShell(); mountMonitor(); hideLoaderWhenReady(); function mountShell() { if (!appRoot || document.getElementById("nebula-app-shell")) { return; } const shell = document.createElement('div'); shell.className = 'app-shell nebula-app-shell'; shell.id = 'nebula-app-shell'; shell.innerHTML = renderShellChrome(); const parent = appRoot.parentNode; parent.insertBefore(shell, appRoot); var appStage = shell.querySelector(".nebula-app-stage"); if (appStage) { appStage.appendChild(appRoot); } } function mountMonitor() { const rail = document.getElementById('nebula-side-rail'); const panel = document.createElement('aside'); panel.className = 'nebula-monitor'; panel.id = 'nebula-monitor'; panel.innerHTML = renderLoadingPanel(); if (rail) { rail.appendChild(panel); } else { document.body.appendChild(panel); } panel.addEventListener('click', (event) => { const actionEl = event.target.closest('[data-nebula-action]'); if (!actionEl) { return; } const action = actionEl.getAttribute('data-nebula-action'); if (action === 'refresh') { loadDeviceOverview(panel); } if (action === 'toggle') { panel.classList.toggle('is-collapsed'); } }); loadDeviceOverview(panel); window.setInterval(function () { loadDeviceOverview(panel, true); }, 30000); } async function loadDeviceOverview(panel, silent) { if (!silent) { panel.innerHTML = renderLoadingPanel(); } try { const response = await fetch('/api/v1/user/user-online-devices/get-ip', { method: 'GET', headers: getRequestHeaders(), credentials: 'same-origin' }); if (response.status === 401 || response.status === 403) { panel.innerHTML = renderGuestPanel(); return; } if (!response.ok) { throw new Error('Unable to load device overview'); } const resJson = await response.json(); const data = resJson && resJson.data ? resJson.data : {}; console.log('[Nebula] Device overview data:', data); // Diagnostic log const overview = data.session_overview || data; panel.innerHTML = renderPanel(overview); } catch (error) { panel.innerHTML = renderErrorPanel(error.message || 'Unable to load device overview'); } } function renderShellChrome() { return [ '
', '
', '
', '
', '

' + escapeHtml(theme.title || "Nebula") + '

', '

' + escapeHtml(theme.description || "Refined user workspace") + '

', '
', '
', '
', 'Nebula Theme', 'Platform Control Surface', '
', '
', '
', '
', 'User Console', '

Subscriptions, sessions, and live access in one view.

', '

' + escapeHtml((theme.config && theme.config.slogan) || "Current IP visibility, online devices, and the original workflow remain available.") + '

', '
', '
LiveCurrent ingress overview
', '
NativeOriginal features retained
', '
RefreshAuto-updated every 30 seconds
', '
', '
', '', '
', '
', '
', '', '
' ].join(""); } function renderLoadingPanel() { return [ '
', '

Live Access Monitor

Checking online IPs, devices, and active sessions...
', '
', '
', '
', '
Loading...
', '
' ].join(""); } function renderGuestPanel() { return [ '
', '

Live Access Monitor

The panel is ready, but this browser session is not authenticated for the API yet.
', '
', '
', '
', '
Sign in first, then refresh this panel to view current IP and device data.
', '
' ].join(""); } function renderErrorPanel(message) { return [ '
', '

Live Access Monitor

The panel loaded, but the device overview endpoint did not respond as expected.
', '
', '
', '
', '
' + escapeHtml(message) + '
', '
' ].join(""); } function renderPanel(data) { const ips = Array.isArray(data.online_ips) ? data.online_ips : []; const sessions = Array.isArray(data.sessions) ? data.sessions.slice(0, 4) : []; return [ '
', '

Live Access Monitor

' + escapeHtml((theme.config && theme.config.slogan) || 'Current IP and session visibility') + '
', '
', '
', '
', '
', '
', metric(String(data.online_ip_count || 0), 'Current IPs'), metric(String(data.online_device_count || 0), 'Online devices'), metric(String(data.active_session_count || 0), 'Stored sessions'), metric(formatLimit(data.device_limit), 'Device limit'), '
', '
Last online: ' + formatDate(data.last_online_at) + '
', '
', '
', '
Online IP addresses
', ips.length ? '
' + ips.map((ip) => { return '
' + escapeHtml(ip) + '
'; }).join('') : '
No IP data reported yet.
', '
', '
', '
Recent sessions
', sessions.length ? '
' + sessions.map((session) => { return [ '
', '' + escapeHtml(session.name || ('Session #' + session.id)) + (session.is_current ? ' ยท current' : '') + '', '
Last used: ' + formatDate(session.last_used_at) + '
', '
' ].join(''); }).join('') : '
No session records available.
', '
', '
' ].join(''); } function metric(value, label) { return '
' + escapeHtml(value) + '' + escapeHtml(label) + '
'; } function getRequestHeaders() { const headers = { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }; const token = getStoredToken(); if (token) { headers.Authorization = token; } return headers; } function hideLoaderWhenReady() { if (!loader) { return; } var hide = function () { loader.classList.add("is-hidden"); window.setTimeout(function () { if (loader && loader.parentNode) { loader.parentNode.removeChild(loader); } }, 350); }; if (appRoot && appRoot.children.length > 0) { hide(); return; } var observer = new MutationObserver(function () { if (appRoot && appRoot.children.length > 0) { observer.disconnect(); hide(); } }); if (appRoot) { observer.observe(appRoot, { childList: true }); } window.setTimeout(hide, 6000); } function applyCustomBackground() { if (!theme.config || !theme.config.backgroundUrl) { return; } document.body.style.backgroundImage = 'linear-gradient(180deg, rgba(5, 13, 22, 0.76), rgba(5, 13, 22, 0.9)), url("' + String(theme.config.backgroundUrl).replace(/"/g, '\\"') + '")'; document.body.style.backgroundSize = "cover"; document.body.style.backgroundPosition = "center"; document.body.style.backgroundAttachment = "fixed"; } function getStoredToken() { const candidates = []; const directKeys = ['access_token', 'auth_data', '__nebula_auth_data__', 'token', 'auth_token']; collectStorageValues(window.localStorage, directKeys, candidates); collectStorageValues(window.sessionStorage, directKeys, candidates); collectCookieValues(candidates); collectGlobalValues(candidates); for (let i = 0; i < candidates.length; i += 1) { const token = extractToken(candidates[i]); if (token) { return token; } } return ''; } function collectStorageValues(storage, keys, target) { if (!storage) { return; } for (var i = 0; i < keys.length; i += 1) { pushCandidate(storage.getItem(keys[i]), target); } } function collectCookieValues(target) { if (!document.cookie) { return; } const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i += 1) { const part = cookies[i].split('='); if (part.length < 2) { continue; } const key = String(part[0] || '').trim(); if (key.indexOf('token') !== -1 || key.indexOf('auth') !== -1) { pushCandidate(decodeURIComponent(part.slice(1).join('=')), target); } } } function collectGlobalValues(target) { pushCandidate(window.__INITIAL_STATE__, target); pushCandidate(window.g_initialProps, target); pushCandidate(window.g_app, target); } function pushCandidate(value, target) { if (value === null || typeof value === "undefined" || value === "") { return; } target.push(value); } function extractToken(value, depth) { if (depth > 4 || value === null || typeof value === "undefined") { return ""; } if (typeof value === 'string') { const trimmed = value.trim(); if (!trimmed) { return ''; } if (trimmed.indexOf('Bearer ') === 0) { return trimmed; } if (/^[A-Za-z0-9\-_=]+\.[A-Za-z0-9\-_=]+(\.[A-Za-z0-9\-_.+/=]+)?$/.test(trimmed)) { return 'Bearer ' + trimmed; } if (/^[A-Za-z0-9\-_.+/=]{24,}$/.test(trimmed) && trimmed.indexOf('{') === -1) { return 'Bearer ' + trimmed; } if ((trimmed.charAt(0) === '{' && trimmed.charAt(trimmed.length - 1) === '}') || (trimmed.charAt(0) === '[' && trimmed.charAt(trimmed.length - 1) === ']')) { try { return extractToken(JSON.parse(trimmed), (depth || 0) + 1); } catch (error) { return ''; } } return ''; } if (Array.isArray(value)) { for (var i = 0; i < value.length; i += 1) { var nestedToken = extractToken(value[i], (depth || 0) + 1); if (nestedToken) { return nestedToken; } } return ""; } if (typeof value === 'object') { const keys = ['access_token', 'auth_data', 'token', 'Authorization', 'authorization']; for (let j = 0; j < keys.length; j += 1) { if (Object.prototype.hasOwnProperty.call(value, keys[j])) { const directToken = extractToken(value[keys[j]], (depth || 0) + 1); if (directToken) { return directToken; } } } for (const key in value) { if (!Object.prototype.hasOwnProperty.call(value, key)) { continue; } const token = extractToken(value[key], (depth || 0) + 1); if (token) { return token; } } } return ""; } function formatDate(value) { if (!value) { return "-"; } if (typeof value === "number") { return new Date(value * 1000).toLocaleString(); } var parsed = Date.parse(value); if (Number.isNaN(parsed)) { return "-"; } return new Date(parsed).toLocaleString(); } function formatLimit(value) { if (value === null || typeof value === "undefined" || value === "") { return "Unlimited"; } return String(value); } function escapeHtml(value) { return String(value || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } })();