Fix up Automatic Updates via Websocket | peer.directory
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -297,16 +297,12 @@ async function checkAndBroadcastDomainChanges() {
|
||||
for (const domainName of removedDomains) {
|
||||
broadcastUpdate({
|
||||
type: 'domain-removed',
|
||||
domain: domainName
|
||||
domain: domainName,
|
||||
data: { domain: domainName }
|
||||
});
|
||||
}
|
||||
|
||||
// Send update message with full state as fallback
|
||||
broadcastUpdate({
|
||||
type: 'update',
|
||||
data: currentState,
|
||||
changedDomains: [...addedDomains.map(d => d.domain), ...changedDomains.map(d => d.domain)]
|
||||
});
|
||||
// Note: Removed bulk updates for changed domains - only using individual add/remove updates
|
||||
}
|
||||
} catch (err) {
|
||||
sdk.log.error('peer.directory', `Error checking domain changes: ${err.message}`);
|
||||
@@ -368,16 +364,7 @@ function setupWebSocketHandlers() {
|
||||
}
|
||||
});
|
||||
|
||||
// Setup periodic updates as fallback (reduced frequency since we have event-driven updates)
|
||||
updateInterval = setInterval(async () => {
|
||||
if (sdk.websocket.getClientCount() > 0) {
|
||||
const state = await getSystemState();
|
||||
broadcastUpdate({
|
||||
type: 'update',
|
||||
data: state
|
||||
});
|
||||
}
|
||||
}, 30000); // Update every 30 seconds as fallback (event-driven updates handle most changes)
|
||||
// Removed periodic bulk updates - only using individual add/remove updates now
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -355,7 +355,7 @@ th {
|
||||
|
||||
tbody tr {
|
||||
border-top: 1px solid var(--border-color);
|
||||
transition: background var(--transition-base);
|
||||
transition: background var(--transition-base), opacity var(--transition-base), transform var(--transition-base);
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
@@ -597,25 +597,80 @@ a:hover {
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Smooth Update Animations */
|
||||
@keyframes fadeInUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOutDown {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes highlightPulse {
|
||||
0% {
|
||||
background-color: rgba(99, 102, 241, 0.1);
|
||||
box-shadow: 0 0 0 0 rgba(99, 102, 241, 0.2);
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(99, 102, 241, 0.2);
|
||||
box-shadow: 0 0 20px 5px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
100% {
|
||||
background-color: inherit;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr.fade-in {
|
||||
animation: fadeInUp 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
tbody tr.fade-out {
|
||||
animation: fadeOutDown 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
tbody tr.highlight {
|
||||
animation: highlightPulse 1.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
/* Table container update animation */
|
||||
.table-container.updating {
|
||||
transition: box-shadow var(--transition-slow);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
|
||||
th, td {
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
@@ -115,9 +115,17 @@
|
||||
</span>`;
|
||||
}
|
||||
|
||||
function renderDomain(domainObj) {
|
||||
function renderDomain(domainObj, options = {}) {
|
||||
const tr = document.createElement('tr');
|
||||
|
||||
|
||||
// Add animation classes if specified
|
||||
if (options.animateIn) {
|
||||
tr.classList.add('fade-in');
|
||||
}
|
||||
if (options.highlight) {
|
||||
tr.classList.add('highlight');
|
||||
}
|
||||
|
||||
// Domain cell with link
|
||||
const domainCell = document.createElement('td');
|
||||
const domainLink = document.createElement('a');
|
||||
@@ -125,20 +133,20 @@
|
||||
domainLink.textContent = domainObj.domain;
|
||||
domainLink.target = '_blank';
|
||||
domainCell.appendChild(domainLink);
|
||||
|
||||
|
||||
// Hash cell
|
||||
const hashCell = document.createElement('td');
|
||||
hashCell.style.cssText = 'word-break: break-all; color: var(--text-tertiary);';
|
||||
hashCell.textContent = domainObj.hash || 'none';
|
||||
|
||||
|
||||
// Type cell with badge
|
||||
const typeCell = document.createElement('td');
|
||||
typeCell.innerHTML = getTypeBadge(domainObj.type);
|
||||
|
||||
|
||||
tr.appendChild(domainCell);
|
||||
tr.appendChild(hashCell);
|
||||
tr.appendChild(typeCell);
|
||||
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
@@ -316,14 +324,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderDomains(domains, append = false) {
|
||||
function renderDomains(domains, append = false, animationOptions = {}) {
|
||||
const domainList = document.getElementById('domainList');
|
||||
const tableContainer = document.querySelector('.table-container');
|
||||
|
||||
|
||||
if (!append) {
|
||||
domainList.innerHTML = '';
|
||||
}
|
||||
|
||||
|
||||
if (domains.length === 0 && !append) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = '<td colspan="3" class="p-8 text-center" style="color: var(--text-tertiary);">No domains found.</td>';
|
||||
@@ -334,12 +342,22 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
domains.forEach(domainObj => {
|
||||
const tr = renderDomain(domainObj);
|
||||
|
||||
domains.forEach((domainObj, index) => {
|
||||
const options = {
|
||||
animateIn: animationOptions.animateAll || (animationOptions.animateIndices && animationOptions.animateIndices.includes(index)),
|
||||
highlight: animationOptions.highlightAll || (animationOptions.highlightIndices && animationOptions.highlightIndices.includes(index))
|
||||
};
|
||||
const tr = renderDomain(domainObj, options);
|
||||
|
||||
if (animationOptions.animateAll && animationOptions.cascade) {
|
||||
// Add cascading delay for bulk animations
|
||||
tr.style.animationDelay = `${index * 50}ms`;
|
||||
}
|
||||
|
||||
domainList.appendChild(tr);
|
||||
});
|
||||
|
||||
|
||||
// Add 'few-rows' class if there are 5 or fewer total rows for better appearance
|
||||
if (tableContainer) {
|
||||
const totalRows = domainList.querySelectorAll('tr').length;
|
||||
@@ -484,162 +502,17 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Dynamic domain update function
|
||||
function updateDomainsDynamically(newDomains) {
|
||||
const searchQuery = document.getElementById('searchInput').value;
|
||||
const isSearching = searchQuery.length > 0;
|
||||
|
||||
// Create maps for efficient lookups
|
||||
const currentDomainMap = new Map(allDomains.map(d => [d.domain, d]));
|
||||
const newDomainMap = new Map(newDomains.map(d => [d.domain, d]));
|
||||
|
||||
// Track changes
|
||||
const addedDomains = [];
|
||||
const removedDomains = [];
|
||||
const changedDomains = [];
|
||||
|
||||
// Find added and changed domains
|
||||
for (const newDomain of newDomains) {
|
||||
const currentDomain = currentDomainMap.get(newDomain.domain);
|
||||
if (!currentDomain) {
|
||||
addedDomains.push(newDomain);
|
||||
} else if (JSON.stringify(currentDomain) !== JSON.stringify(newDomain)) {
|
||||
changedDomains.push(newDomain);
|
||||
}
|
||||
}
|
||||
|
||||
// Find removed domains
|
||||
for (const currentDomain of allDomains) {
|
||||
if (!newDomainMap.has(currentDomain.domain)) {
|
||||
removedDomains.push(currentDomain);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the master domain list
|
||||
allDomains = [...newDomains];
|
||||
|
||||
// Apply current sorting
|
||||
sortDomains(currentSort);
|
||||
|
||||
// Show brief visual feedback for changes
|
||||
if (addedDomains.length > 0 || removedDomains.length > 0 || changedDomains.length > 0) {
|
||||
showUpdateIndicator();
|
||||
}
|
||||
|
||||
console.log(`Domain updates: +${addedDomains.length} -${removedDomains.length} ~${changedDomains.length}`);
|
||||
|
||||
// If not searching, update the visible list dynamically
|
||||
if (!isSearching) {
|
||||
updateVisibleListDynamically(addedDomains, removedDomains, changedDomains);
|
||||
} else {
|
||||
// If searching, re-apply filter and update
|
||||
const filtered = allDomains.filter(d =>
|
||||
d.domain.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
(d.hash && d.hash.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
);
|
||||
// For search mode, do a clean re-render to maintain consistency
|
||||
renderDomains(filtered.slice(0, currentLimit), false);
|
||||
updatePaginationInfo(1, filtered.length, currentLimit);
|
||||
}
|
||||
}
|
||||
|
||||
// Update visible list dynamically without full re-render
|
||||
function updateVisibleListDynamically(addedDomains, removedDomains, changedDomains) {
|
||||
const domainList = document.getElementById('domainList');
|
||||
const tableContainer = document.querySelector('.table-container');
|
||||
|
||||
// Update total count
|
||||
totalDomains = allDomains.length;
|
||||
|
||||
// Get current visible domains (first currentLimit items after sorting)
|
||||
const visibleDomains = allDomains.slice(0, currentLimit);
|
||||
|
||||
// For bulk updates with many changes, do a clean re-render to maintain consistency
|
||||
// For smaller changes, the individual add/remove functions handle it seamlessly
|
||||
if (addedDomains.length + removedDomains.length + changedDomains.length > 3) {
|
||||
// Too many changes, do a clean re-render
|
||||
renderDomains(visibleDomains, false);
|
||||
} else {
|
||||
// Handle individual changes seamlessly
|
||||
// Note: individual add/remove handlers already updated the UI
|
||||
}
|
||||
|
||||
updatePaginationInfo(1, totalDomains, currentLimit);
|
||||
|
||||
// Update few-rows class if needed
|
||||
if (tableContainer) {
|
||||
const totalRows = domainList.querySelectorAll('tr').length;
|
||||
if (totalRows <= 5) {
|
||||
tableContainer.classList.add('few-rows');
|
||||
} else {
|
||||
tableContainer.classList.remove('few-rows');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add domain to table seamlessly without re-rendering
|
||||
function seamlesslyAddDomain(domainData) {
|
||||
const domainList = document.getElementById('domainList');
|
||||
const tableContainer = document.querySelector('.table-container');
|
||||
|
||||
// Only add if we have fewer than currentLimit visible rows
|
||||
const visibleRows = Array.from(domainList.children).filter(row =>
|
||||
row.style.display !== 'none' && !row.classList.contains('filtered-out')
|
||||
);
|
||||
|
||||
if (visibleRows.length < currentLimit) {
|
||||
// Create and insert the new row
|
||||
const newRow = renderDomain(domainData);
|
||||
domainList.appendChild(newRow);
|
||||
|
||||
// Update few-rows class if needed
|
||||
const totalRows = domainList.querySelectorAll('tr').length;
|
||||
if (totalRows <= 5) {
|
||||
tableContainer.classList.add('few-rows');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove domain from table seamlessly without re-rendering
|
||||
function seamlesslyRemoveDomain(domainName) {
|
||||
const domainList = document.getElementById('domainList');
|
||||
const tableContainer = document.querySelector('.table-container');
|
||||
|
||||
// Find and remove the row
|
||||
const rows = Array.from(domainList.children);
|
||||
const rowToRemove = rows.find(row => {
|
||||
const link = row.querySelector('a');
|
||||
return link && link.textContent === domainName;
|
||||
});
|
||||
|
||||
if (rowToRemove) {
|
||||
// Add fade-out animation before removing
|
||||
rowToRemove.style.transition = 'opacity 0.2s ease-out';
|
||||
rowToRemove.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
if (rowToRemove.parentNode) {
|
||||
rowToRemove.parentNode.removeChild(rowToRemove);
|
||||
|
||||
// Update few-rows class if needed
|
||||
const totalRows = domainList.querySelectorAll('tr').length;
|
||||
if (totalRows > 5) {
|
||||
tableContainer.classList.remove('few-rows');
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
// Show brief visual indicator for updates
|
||||
function showUpdateIndicator() {
|
||||
// Add a subtle highlight effect to the table container
|
||||
// Add a smooth highlight effect to the table container
|
||||
const tableContainer = document.querySelector('.table-container');
|
||||
if (tableContainer) {
|
||||
tableContainer.classList.add('updating');
|
||||
tableContainer.style.boxShadow = 'var(--shadow-glow)';
|
||||
setTimeout(() => {
|
||||
tableContainer.style.boxShadow = 'var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.05)';
|
||||
}, 500);
|
||||
tableContainer.classList.remove('updating');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,21 +576,39 @@
|
||||
// Handle individual domain additions
|
||||
window.wsClient.on('domain-added', (data) => {
|
||||
console.log('Domain added via WebSocket:', data);
|
||||
if (data.domain && data.data) {
|
||||
if (data && data.domain) {
|
||||
// Add the domain to our list if not already present
|
||||
const exists = allDomains.some(d => d.domain === data.domain);
|
||||
if (!exists) {
|
||||
allDomains.push(data.data);
|
||||
console.log('Adding domain:', data);
|
||||
allDomains.push(data);
|
||||
sortDomains(currentSort);
|
||||
console.log('Domains after add:', allDomains.map(d => ({domain: d.domain, type: d.type})));
|
||||
showUpdateIndicator();
|
||||
totalDomains = allDomains.length;
|
||||
|
||||
// Update UI seamlessly if not searching
|
||||
// Smoothly animate the new domain into view
|
||||
const searchQuery = document.getElementById('searchInput').value;
|
||||
if (!searchQuery) {
|
||||
seamlesslyAddDomain(data.data);
|
||||
updatePaginationInfo(1, allDomains.length, currentLimit);
|
||||
totalDomains = allDomains.length;
|
||||
// For non-search mode, find where the new domain appears in the sorted list
|
||||
const visibleDomains = allDomains.slice(0, currentLimit);
|
||||
const newDomainIndex = visibleDomains.findIndex(d => d.domain === data.domain);
|
||||
|
||||
if (newDomainIndex >= 0) {
|
||||
// New domain is in visible range, animate it in
|
||||
renderDomains(visibleDomains, false, {
|
||||
animateIndices: [newDomainIndex]
|
||||
});
|
||||
} else {
|
||||
// New domain is not in visible range, just update normally
|
||||
handleDomainsFromWebSocket(currentPage, currentLimit, currentSort, searchQuery, false);
|
||||
}
|
||||
} else {
|
||||
// For search mode, just update normally
|
||||
handleDomainsFromWebSocket(currentPage, currentLimit, currentSort, searchQuery, false);
|
||||
}
|
||||
} else {
|
||||
console.log('Domain already exists:', data.domain);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -726,32 +617,48 @@
|
||||
window.wsClient.on('domain-removed', (data) => {
|
||||
console.log('Domain removed via WebSocket:', data);
|
||||
if (data.domain) {
|
||||
// Remove the domain from our list
|
||||
const initialLength = allDomains.length;
|
||||
allDomains = allDomains.filter(d => d.domain !== data.domain);
|
||||
// First, animate the existing row out if it's visible
|
||||
const domainList = document.getElementById('domainList');
|
||||
const rows = Array.from(domainList.children);
|
||||
const rowToRemove = rows.find(row => {
|
||||
const link = row.querySelector('a');
|
||||
return link && link.textContent === data.domain;
|
||||
});
|
||||
|
||||
if (allDomains.length !== initialLength) {
|
||||
// Don't re-sort on removal, just update UI
|
||||
showUpdateIndicator();
|
||||
|
||||
// Update UI seamlessly if not searching
|
||||
const searchQuery = document.getElementById('searchInput').value;
|
||||
if (!searchQuery) {
|
||||
seamlesslyRemoveDomain(data.domain);
|
||||
updatePaginationInfo(1, allDomains.length, currentLimit);
|
||||
totalDomains = allDomains.length;
|
||||
}
|
||||
if (rowToRemove) {
|
||||
// Animate the row out
|
||||
rowToRemove.classList.add('fade-out');
|
||||
rowToRemove.addEventListener('animationend', () => {
|
||||
// After animation completes, update the data and re-render
|
||||
updateDataAfterRemoval(data.domain);
|
||||
}, { once: true });
|
||||
} else {
|
||||
// Row not visible, just update data directly
|
||||
updateDataAfterRemoval(data.domain);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle real-time updates
|
||||
window.wsClient.on('update', (data) => {
|
||||
console.log('WebSocket update received:', data);
|
||||
if (data.domains) {
|
||||
updateDomainsDynamically(data.domains);
|
||||
function updateDataAfterRemoval(domainName) {
|
||||
// Remove the domain from our list
|
||||
const initialLength = allDomains.length;
|
||||
console.log('Removing domain:', domainName);
|
||||
console.log('Domains before remove:', allDomains.map(d => ({domain: d.domain, type: d.type})));
|
||||
allDomains = allDomains.filter(d => d.domain !== domainName);
|
||||
console.log('Domains after remove:', allDomains.map(d => ({domain: d.domain, type: d.type})));
|
||||
|
||||
if (allDomains.length !== initialLength) {
|
||||
// Re-sort after removal to maintain consistency
|
||||
sortDomains(currentSort);
|
||||
showUpdateIndicator();
|
||||
totalDomains = allDomains.length;
|
||||
|
||||
// Re-render the visible list to respect sort order and pagination
|
||||
const searchQuery = document.getElementById('searchInput').value;
|
||||
handleDomainsFromWebSocket(currentPage, currentLimit, currentSort, searchQuery, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Handle connection status changes
|
||||
window.wsClient.on('connected', () => {
|
||||
|
||||
Reference in New Issue
Block a user