feat: implement fully dynamic real-time domains list via WebSocket
Add WebSocket broadcasting of complete domains list from server to clients Implement periodic domain list updates (10-second intervals) Fix reconnection issues where domains list remained empty after p2ns restart Add proper state management for WebSocket disconnect/reconnect scenarios Enhance empty state messages to show "Disconnected" status during outages Reset scroll/pagination state when receiving WebSocket domain updates Add client-server request/response mechanism for immediate domain list refresh Ensure domains list updates in real-time during consensus resolution Remove conflicting HTTP fetches during reconnection to prevent race conditions
This commit is contained in:
@@ -2,15 +2,120 @@ const WebSocket = require('ws');
|
|||||||
const { logDebug, logError, logInfo, logWarn } = require('../../infrastructure/logger');
|
const { logDebug, logError, logInfo, logWarn } = require('../../infrastructure/logger');
|
||||||
const state = require('../../infrastructure/state');
|
const state = require('../../infrastructure/state');
|
||||||
const { metrics } = require('../../maintenance/metrics');
|
const { metrics } = require('../../maintenance/metrics');
|
||||||
|
const { getAllEntries, getHashForDomain, getConsensusState } = require('../../core/core');
|
||||||
|
const { getPersistentPublicKey } = require('../../infrastructure/utils');
|
||||||
|
|
||||||
const adminWss = new WebSocket.Server({ noServer: true });
|
const adminWss = new WebSocket.Server({ noServer: true });
|
||||||
const adminClients = new Set();
|
const adminClients = new Set();
|
||||||
let healthBroadcastInterval = null;
|
let healthBroadcastInterval = null;
|
||||||
|
let domainsBroadcastInterval = null;
|
||||||
|
|
||||||
|
// Fetch the complete resolved domains list
|
||||||
|
async function getResolvedDomainsList() {
|
||||||
|
try {
|
||||||
|
// Use fresh data (bypass cache) to ensure latest consensus state
|
||||||
|
const allEntries = await getAllEntries(state.dnsPass, false);
|
||||||
|
const domainClaimants = new Map();
|
||||||
|
for (const entry of allEntries) {
|
||||||
|
if (entry.key.startsWith('claim:')) {
|
||||||
|
const parts = entry.key.split(':');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
const domain = parts[1];
|
||||||
|
const claimant = parts[2];
|
||||||
|
if (!domainClaimants.has(domain)) domainClaimants.set(domain, new Set());
|
||||||
|
domainClaimants.get(domain).add(claimant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const localWriter = getPersistentPublicKey();
|
||||||
|
const domains = new Set(domainClaimants.keys());
|
||||||
|
const resolved = [];
|
||||||
|
for (const domain of domains) {
|
||||||
|
const hash = await getHashForDomain(domain) || 'none';
|
||||||
|
const isLocal = localWriter ? domainClaimants.get(domain)?.has(localWriter) || false : false;
|
||||||
|
|
||||||
|
let isOwner = false;
|
||||||
|
let consensusState = null;
|
||||||
|
try {
|
||||||
|
consensusState = await getConsensusState(domain);
|
||||||
|
isOwner = localWriter ? consensusState.resolvedClaimant === localWriter : false;
|
||||||
|
} catch (err) {
|
||||||
|
logDebug('Admin', `Error checking ownership/consensus for ${domain}: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine consensus status, including conflict detection
|
||||||
|
let consensusStatus = null;
|
||||||
|
if (consensusState) {
|
||||||
|
if (isLocal && !isOwner && consensusState.status === 'resolved') {
|
||||||
|
// Conflict: user has local claim but another claimant won
|
||||||
|
consensusStatus = 'conflict';
|
||||||
|
} else {
|
||||||
|
consensusStatus = consensusState.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved.push({ domain, hash, isLocal, isOwner, consensusState, consensusStatus });
|
||||||
|
}
|
||||||
|
let internalDomains = ['p2ns.admin'];
|
||||||
|
try {
|
||||||
|
const { getInternalDomains } = require('../../plugins/plugin-handler');
|
||||||
|
internalDomains = await getInternalDomains();
|
||||||
|
} catch (err) {
|
||||||
|
// Fallback if plugin system not available
|
||||||
|
}
|
||||||
|
// Only add internal domains that aren't already in the resolved list
|
||||||
|
for (const d of internalDomains) {
|
||||||
|
if (!resolved.some(r => r.domain === d)) {
|
||||||
|
resolved.push({ domain: d, hash: 'internal', isLocal: true, isOwner: true, consensusStatus: 'internal' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved.sort((a, b) => a.domain.localeCompare(b.domain));
|
||||||
|
} catch (err) {
|
||||||
|
logError('Admin', `Failed to fetch resolved domains list: ${err.message}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adminWss.on('connection', (ws) => {
|
adminWss.on('connection', (ws) => {
|
||||||
logDebug('Admin', 'WebSocket client connected');
|
logDebug('Admin', 'WebSocket client connected');
|
||||||
adminClients.add(ws);
|
adminClients.add(ws);
|
||||||
|
|
||||||
|
// Send initial domain list
|
||||||
|
getResolvedDomainsList().then(domains => {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'domains-list',
|
||||||
|
domains: domains,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
logError('Admin', `Error sending initial domains list: ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle incoming messages
|
||||||
|
ws.on('message', (message) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(message);
|
||||||
|
if (data.type === 'request-domains') {
|
||||||
|
// Client requested domains list
|
||||||
|
getResolvedDomainsList().then(domains => {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'domains-list',
|
||||||
|
domains: domains,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
logError('Admin', `Error sending requested domains list: ${err.message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logError('Admin', `Error handling WebSocket message: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Handle close event
|
// Handle close event
|
||||||
ws.on('close', () => {
|
ws.on('close', () => {
|
||||||
adminClients.delete(ws);
|
adminClients.delete(ws);
|
||||||
@@ -43,6 +148,13 @@ function broadcast(msg) {
|
|||||||
client.send(JSON.stringify(msg));
|
client.send(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is an update-database message, also broadcast the domains list
|
||||||
|
if (msg.type === 'update-database') {
|
||||||
|
setTimeout(() => {
|
||||||
|
broadcastDomainsList();
|
||||||
|
}, 100); // Small delay to ensure the update-database message is processed first
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllWebSockets() {
|
function closeAllWebSockets() {
|
||||||
@@ -61,6 +173,9 @@ function closeAllWebSockets() {
|
|||||||
// Stop health broadcasts
|
// Stop health broadcasts
|
||||||
stopHealthBroadcasts();
|
stopHealthBroadcasts();
|
||||||
|
|
||||||
|
// Stop domains broadcasts
|
||||||
|
stopDomainsBroadcasts();
|
||||||
|
|
||||||
// Close the WebSocket server
|
// Close the WebSocket server
|
||||||
try {
|
try {
|
||||||
adminWss.close();
|
adminWss.close();
|
||||||
@@ -124,15 +239,61 @@ function broadcastHealth() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start periodic domains broadcasts
|
||||||
|
function startDomainsBroadcasts() {
|
||||||
|
if (domainsBroadcastInterval) return;
|
||||||
|
|
||||||
|
// Broadcast immediately
|
||||||
|
broadcastDomainsList();
|
||||||
|
|
||||||
|
// Then every 10 seconds
|
||||||
|
domainsBroadcastInterval = setInterval(() => {
|
||||||
|
broadcastDomainsList();
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
logDebug('Admin', 'Domains broadcasts started');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop domains broadcasts
|
||||||
|
function stopDomainsBroadcasts() {
|
||||||
|
if (domainsBroadcastInterval) {
|
||||||
|
clearInterval(domainsBroadcastInterval);
|
||||||
|
domainsBroadcastInterval = null;
|
||||||
|
logDebug('Admin', 'Domains broadcasts stopped');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast domains list to all connected clients
|
||||||
|
function broadcastDomainsList() {
|
||||||
|
if (adminClients.size === 0) return;
|
||||||
|
|
||||||
|
getResolvedDomainsList().then(domains => {
|
||||||
|
broadcast({
|
||||||
|
type: 'domains-list',
|
||||||
|
domains: domains,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
logError('Admin', `Error broadcasting domains list: ${err.message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Start health broadcasts when module loads
|
// Start health broadcasts when module loads
|
||||||
startHealthBroadcasts();
|
startHealthBroadcasts();
|
||||||
|
|
||||||
|
// Start domains broadcasts when module loads
|
||||||
|
startDomainsBroadcasts();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
adminWss,
|
adminWss,
|
||||||
adminClients,
|
adminClients,
|
||||||
broadcast,
|
broadcast,
|
||||||
closeAllWebSockets,
|
closeAllWebSockets,
|
||||||
startHealthBroadcasts,
|
startHealthBroadcasts,
|
||||||
stopHealthBroadcasts
|
stopHealthBroadcasts,
|
||||||
|
startDomainsBroadcasts,
|
||||||
|
stopDomainsBroadcasts,
|
||||||
|
broadcastDomainsList,
|
||||||
|
getResolvedDomainsList
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -136,11 +136,21 @@ function genericRenderInfiniteScroll(tabId) {
|
|||||||
const emptyRow = document.createElement('tr');
|
const emptyRow = document.createElement('tr');
|
||||||
emptyRow.className = 'border-b';
|
emptyRow.className = 'border-b';
|
||||||
const colCount = tabId === 'host-servers' ? 7 : (tabId === 'host-clients' ? 6 : (tabId === 'domains' ? 4 : 3));
|
const colCount = tabId === 'host-servers' ? 7 : (tabId === 'host-clients' ? 6 : (tabId === 'domains' ? 4 : 3));
|
||||||
|
let message = '';
|
||||||
|
let subMessage = '';
|
||||||
|
if (tabId === 'domains' && !window.wsConnected) {
|
||||||
|
message = 'Disconnected';
|
||||||
|
subMessage = 'Reconnecting to P2NS server...';
|
||||||
|
} else {
|
||||||
|
message = `No ${tabId === 'host-servers' ? 'servers' : tabId === 'host-clients' ? 'clients' : 'items'} found`;
|
||||||
|
subMessage = tabId === 'host-servers' ? 'Create a server to get started' : tabId === 'host-clients' ? 'Create a client to get started' : 'Try adjusting your search';
|
||||||
|
}
|
||||||
|
|
||||||
emptyRow.innerHTML = `<td colspan="${colCount}" class="p-8 text-center theme-text-tertiary">
|
emptyRow.innerHTML = `<td colspan="${colCount}" class="p-8 text-center theme-text-tertiary">
|
||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<span class="text-4xl">📭</span>
|
<span class="text-4xl">${tabId === 'domains' && !window.wsConnected ? '🔌' : '📭'}</span>
|
||||||
<span class="text-lg font-semibold">No ${tabId === 'host-servers' ? 'servers' : tabId === 'host-clients' ? 'clients' : 'items'} found</span>
|
<span class="text-lg font-semibold">${message}</span>
|
||||||
<span class="text-sm">${tabId === 'host-servers' ? 'Create a server to get started' : tabId === 'host-clients' ? 'Create a client to get started' : 'Try adjusting your search'}</span>
|
<span class="text-sm">${subMessage}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>`;
|
</td>`;
|
||||||
container.appendChild(emptyRow);
|
container.appendChild(emptyRow);
|
||||||
|
|||||||
@@ -49,14 +49,36 @@ function connectWebSocket() {
|
|||||||
window.wsReconnectAttempts = 0;
|
window.wsReconnectAttempts = 0;
|
||||||
stopPollingFallback();
|
stopPollingFallback();
|
||||||
if (window.updateStatus) window.updateStatus();
|
if (window.updateStatus) window.updateStatus();
|
||||||
|
|
||||||
|
// Refresh data on reconnection to ensure everything is up to date
|
||||||
if (window.activeTab === 'host' && window.genericFetch) {
|
if (window.activeTab === 'host' && window.genericFetch) {
|
||||||
window.genericFetch('host-servers', true);
|
window.genericFetch('host-servers', true);
|
||||||
window.genericFetch('host-clients', true);
|
window.genericFetch('host-clients', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start periodic domains updates as fallback
|
||||||
|
if (window.startDomainsUpdates) {
|
||||||
|
window.startDomainsUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request domains list from server
|
||||||
|
if (window.ws && window.ws.readyState === WebSocket.OPEN) {
|
||||||
|
window.ws.send(JSON.stringify({ type: 'request-domains' }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.ws.onclose = () => {
|
window.ws.onclose = () => {
|
||||||
window.wsConnected = false;
|
window.wsConnected = false;
|
||||||
|
// Stop periodic domains updates when disconnected
|
||||||
|
if (window.stopDomainsUpdates) {
|
||||||
|
window.stopDomainsUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear domains data and show disconnected state when WebSocket disconnects
|
||||||
|
window.domainsData = [];
|
||||||
|
if (window.activeTab === 'domains' && window.genericFilter) {
|
||||||
|
window.genericFilter('domains');
|
||||||
|
}
|
||||||
if (reconnectTimeout) {
|
if (reconnectTimeout) {
|
||||||
clearTimeout(reconnectTimeout);
|
clearTimeout(reconnectTimeout);
|
||||||
reconnectTimeout = null;
|
reconnectTimeout = null;
|
||||||
@@ -151,14 +173,44 @@ function connectWebSocket() {
|
|||||||
if (window.genericFetch) window.genericFetch('settings', true);
|
if (window.genericFetch) window.genericFetch('settings', true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (data.type === 'domains-list') {
|
||||||
|
// Update domains data directly from WebSocket
|
||||||
|
if (data.domains) {
|
||||||
|
window.domainsData = data.domains;
|
||||||
|
|
||||||
|
// Reset infinite scroll state when receiving fresh WebSocket data
|
||||||
|
if (window.infiniteScrollState && window.infiniteScrollState['domains']) {
|
||||||
|
window.infiniteScrollState['domains'].loadedCount = 0;
|
||||||
|
window.infiniteScrollState['domains'].lastQuery = '';
|
||||||
|
// Disconnect existing observer
|
||||||
|
if (window.infiniteScrollState['domains'].observer) {
|
||||||
|
window.infiniteScrollState['domains'].observer.disconnect();
|
||||||
|
window.infiniteScrollState['domains'].observer = null;
|
||||||
|
}
|
||||||
|
// Clear container
|
||||||
|
const container = document.getElementById('domainsTable');
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render if domains tab is active
|
||||||
|
if (window.activeTab === 'domains' && window.genericFilter) {
|
||||||
|
window.genericFilter('domains');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (window.updateMap && window.updateMap[data.type]) {
|
if (window.updateMap && window.updateMap[data.type]) {
|
||||||
if (typeof window.updateMap[data.type] === 'function') {
|
if (typeof window.updateMap[data.type] === 'function') {
|
||||||
window.updateMap[data.type]();
|
window.updateMap[data.type]();
|
||||||
} else {
|
} else {
|
||||||
window.updateMap[data.type].forEach(tab => {
|
window.updateMap[data.type].forEach(tab => {
|
||||||
// Always fetch data in background, but only render if tab is active
|
// Always fetch data in background, but only render if tab is active
|
||||||
|
// For domains tab, always render when active to ensure real-time updates
|
||||||
|
const shouldRender = tab === 'domains' ? window.activeTab === 'domains' : (window.activeTab === 'host' || window.activeTab === tab);
|
||||||
if (window.genericFetch) {
|
if (window.genericFetch) {
|
||||||
window.genericFetch(tab, window.activeTab === 'host' || window.activeTab === tab);
|
window.genericFetch(tab, shouldRender);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -291,6 +343,25 @@ function stopStatusUpdates() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startDomainsUpdates() {
|
||||||
|
if (window.domainsUpdateInterval) {
|
||||||
|
clearInterval(window.domainsUpdateInterval);
|
||||||
|
}
|
||||||
|
// Refresh domains data every 30 seconds as fallback for missed WebSocket updates
|
||||||
|
window.domainsUpdateInterval = setInterval(() => {
|
||||||
|
if (window.wsConnected && window.genericFetch) {
|
||||||
|
window.genericFetch('domains', window.activeTab === 'domains');
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopDomainsUpdates() {
|
||||||
|
if (window.domainsUpdateInterval) {
|
||||||
|
clearInterval(window.domainsUpdateInterval);
|
||||||
|
window.domainsUpdateInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make functions globally accessible
|
// Make functions globally accessible
|
||||||
window.connectWebSocket = connectWebSocket;
|
window.connectWebSocket = connectWebSocket;
|
||||||
window.startPollingFallback = startPollingFallback;
|
window.startPollingFallback = startPollingFallback;
|
||||||
@@ -299,6 +370,8 @@ window.cleanupWebSocket = cleanupWebSocket;
|
|||||||
window.updateStatus = updateStatus;
|
window.updateStatus = updateStatus;
|
||||||
window.startStatusUpdates = startStatusUpdates;
|
window.startStatusUpdates = startStatusUpdates;
|
||||||
window.stopStatusUpdates = stopStatusUpdates;
|
window.stopStatusUpdates = stopStatusUpdates;
|
||||||
|
window.startDomainsUpdates = startDomainsUpdates;
|
||||||
|
window.stopDomainsUpdates = stopDomainsUpdates;
|
||||||
|
|
||||||
// Initialize WebSocket connection
|
// Initialize WebSocket connection
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|||||||
@@ -52,11 +52,23 @@ function genericRenderPaginated(tabId) {
|
|||||||
const emptyRow = document.createElement('tr');
|
const emptyRow = document.createElement('tr');
|
||||||
emptyRow.className = 'border-b';
|
emptyRow.className = 'border-b';
|
||||||
const colCount = tabId === 'host-servers' ? 7 : (tabId === 'host-clients' ? 6 : (tabId === 'local-dns' ? 5 : 3));
|
const colCount = tabId === 'host-servers' ? 7 : (tabId === 'host-clients' ? 6 : (tabId === 'local-dns' ? 5 : 3));
|
||||||
|
let message = '';
|
||||||
|
let subMessage = '';
|
||||||
|
let icon = '📭';
|
||||||
|
if (tabId === 'domains' && !window.wsConnected) {
|
||||||
|
message = 'Disconnected';
|
||||||
|
subMessage = 'Reconnecting to P2NS server...';
|
||||||
|
icon = '🔌';
|
||||||
|
} else {
|
||||||
|
message = `No ${tabId === 'host-servers' ? 'servers' : tabId === 'host-clients' ? 'clients' : 'items'} found`;
|
||||||
|
subMessage = tabId === 'host-servers' ? 'Create a server to get started' : tabId === 'host-clients' ? 'Create a client to get started' : 'Try adjusting your search';
|
||||||
|
}
|
||||||
|
|
||||||
emptyRow.innerHTML = `<td colspan="${colCount}" class="p-8 text-center text-gray-500 dark:text-gray-400">
|
emptyRow.innerHTML = `<td colspan="${colCount}" class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||||
<div class="flex flex-col items-center gap-2">
|
<div class="flex flex-col items-center gap-2">
|
||||||
<span class="text-4xl">📭</span>
|
<span class="text-4xl">${icon}</span>
|
||||||
<span class="text-lg font-semibold">No ${tabId === 'host-servers' ? 'servers' : tabId === 'host-clients' ? 'clients' : 'items'} found</span>
|
<span class="text-lg font-semibold">${message}</span>
|
||||||
<span class="text-sm">${tabId === 'host-servers' ? 'Create a server to get started' : tabId === 'host-clients' ? 'Create a client to get started' : 'Try adjusting your search'}</span>
|
<span class="text-sm">${subMessage}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>`;
|
</td>`;
|
||||||
container.appendChild(emptyRow);
|
container.appendChild(emptyRow);
|
||||||
|
|||||||
@@ -49,14 +49,36 @@ function connectWebSocket() {
|
|||||||
window.wsReconnectAttempts = 0;
|
window.wsReconnectAttempts = 0;
|
||||||
stopPollingFallback();
|
stopPollingFallback();
|
||||||
if (window.updateStatus) window.updateStatus();
|
if (window.updateStatus) window.updateStatus();
|
||||||
|
|
||||||
|
// Refresh data on reconnection to ensure everything is up to date
|
||||||
if (window.activeTab === 'host' && window.genericFetch) {
|
if (window.activeTab === 'host' && window.genericFetch) {
|
||||||
window.genericFetch('host-servers', true);
|
window.genericFetch('host-servers', true);
|
||||||
window.genericFetch('host-clients', true);
|
window.genericFetch('host-clients', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start periodic domains updates as fallback
|
||||||
|
if (window.startDomainsUpdates) {
|
||||||
|
window.startDomainsUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request domains list from server
|
||||||
|
if (window.ws && window.ws.readyState === WebSocket.OPEN) {
|
||||||
|
window.ws.send(JSON.stringify({ type: 'request-domains' }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.ws.onclose = () => {
|
window.ws.onclose = () => {
|
||||||
window.wsConnected = false;
|
window.wsConnected = false;
|
||||||
|
// Stop periodic domains updates when disconnected
|
||||||
|
if (window.stopDomainsUpdates) {
|
||||||
|
window.stopDomainsUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear domains data and show disconnected state when WebSocket disconnects
|
||||||
|
window.domainsData = [];
|
||||||
|
if (window.activeTab === 'domains' && window.genericFilter) {
|
||||||
|
window.genericFilter('domains');
|
||||||
|
}
|
||||||
if (reconnectTimeout) {
|
if (reconnectTimeout) {
|
||||||
clearTimeout(reconnectTimeout);
|
clearTimeout(reconnectTimeout);
|
||||||
reconnectTimeout = null;
|
reconnectTimeout = null;
|
||||||
@@ -143,14 +165,33 @@ function connectWebSocket() {
|
|||||||
if (window.genericFetch) window.genericFetch('settings', true);
|
if (window.genericFetch) window.genericFetch('settings', true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (data.type === 'domains-list') {
|
||||||
|
// Update domains data directly from WebSocket
|
||||||
|
if (data.domains) {
|
||||||
|
window.domainsData = data.domains;
|
||||||
|
|
||||||
|
// Reset pagination state when receiving fresh WebSocket data
|
||||||
|
if (window.paginationState && window.paginationState['domains']) {
|
||||||
|
window.paginationState['domains'].current = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render if domains tab is active
|
||||||
|
if (window.activeTab === 'domains' && window.genericFilter) {
|
||||||
|
window.genericFilter('domains');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (window.updateMap && window.updateMap[data.type]) {
|
if (window.updateMap && window.updateMap[data.type]) {
|
||||||
if (typeof window.updateMap[data.type] === 'function') {
|
if (typeof window.updateMap[data.type] === 'function') {
|
||||||
window.updateMap[data.type]();
|
window.updateMap[data.type]();
|
||||||
} else {
|
} else {
|
||||||
window.updateMap[data.type].forEach(tab => {
|
window.updateMap[data.type].forEach(tab => {
|
||||||
// Always fetch data in background, but only render if tab is active
|
// Always fetch data in background, but only render if tab is active
|
||||||
|
// For domains tab, always render when active to ensure real-time updates
|
||||||
|
const shouldRender = tab === 'domains' ? window.activeTab === 'domains' : (window.activeTab === 'host' || window.activeTab === tab);
|
||||||
if (window.genericFetch) {
|
if (window.genericFetch) {
|
||||||
window.genericFetch(tab, window.activeTab === 'host' || window.activeTab === tab);
|
window.genericFetch(tab, shouldRender);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -252,6 +293,25 @@ function stopStatusUpdates() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startDomainsUpdates() {
|
||||||
|
if (window.domainsUpdateInterval) {
|
||||||
|
clearInterval(window.domainsUpdateInterval);
|
||||||
|
}
|
||||||
|
// Refresh domains data every 30 seconds as fallback for missed WebSocket updates
|
||||||
|
window.domainsUpdateInterval = setInterval(() => {
|
||||||
|
if (window.wsConnected && window.genericFetch) {
|
||||||
|
window.genericFetch('domains', window.activeTab === 'domains');
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopDomainsUpdates() {
|
||||||
|
if (window.domainsUpdateInterval) {
|
||||||
|
clearInterval(window.domainsUpdateInterval);
|
||||||
|
window.domainsUpdateInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make functions globally accessible
|
// Make functions globally accessible
|
||||||
window.connectWebSocket = connectWebSocket;
|
window.connectWebSocket = connectWebSocket;
|
||||||
window.startPollingFallback = startPollingFallback;
|
window.startPollingFallback = startPollingFallback;
|
||||||
@@ -260,6 +320,8 @@ window.cleanupWebSocket = cleanupWebSocket;
|
|||||||
window.updateStatus = updateStatus;
|
window.updateStatus = updateStatus;
|
||||||
window.startStatusUpdates = startStatusUpdates;
|
window.startStatusUpdates = startStatusUpdates;
|
||||||
window.stopStatusUpdates = stopStatusUpdates;
|
window.stopStatusUpdates = stopStatusUpdates;
|
||||||
|
window.startDomainsUpdates = startDomainsUpdates;
|
||||||
|
window.stopDomainsUpdates = stopDomainsUpdates;
|
||||||
|
|
||||||
// Initialize WebSocket connection
|
// Initialize WebSocket connection
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|||||||
Reference in New Issue
Block a user