(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 [
'
',
'
Sign in first, then refresh this panel to view current IP and device data.
',
'
'
].join("");
}
function renderErrorPanel(message) {
return [
'',
'
' + 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 [
'',
'
',
'
',
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, "'");
}
})();