Console
Offline
Move one of the nodes here
`,
iconSize: [12, 12],
iconAnchor: [6, 6]
});
};
// Add markers for each node and collect them with node data
const markers = [];
const markerMap = {}; // Map node names to markers
nodes.forEach(node => {
const icon = createNodeIcon(node.online);
const marker = L.marker([node.lat, node.lng], { icon: icon })
.addTo(map)
.bindPopup(node.name + (node.online ? ' (Online)' : ' (Offline)'));
marker._nodeData = node;
markers.push(marker);
markerMap[node.name] = marker;
});
// Store marker map for updates
mapContainer._leafletMarkerMap = markerMap;
// Fit map to show all nodes with padding
if (markers.length > 0) {
const group = new L.featureGroup(markers);
map.fitBounds(group.getBounds().pad(0.1), {
maxZoom: 8,
padding: [30, 30]
});
}
// Store map instance for use in click handlers
mapContainer._leafletMap = map;
mapContainer._leafletMarkers = markers;
mapContainer.dataset.mapInitialized = 'true';
// Invalidate map size after a short delay to ensure proper rendering
setTimeout(() => {
map.invalidateSize();
// Re-fit bounds after size is validated
if (markers.length > 0) {
const group = new L.featureGroup(markers);
map.fitBounds(group.getBounds().pad(0.1), {
maxZoom: 8,
padding: [30, 30]
});
}
}, 100);
} else {
// Leaflet not ready yet, retry map initialization later
setTimeout(() => {
if (!mapContainer._leafletMap) {
initNodesMapWindow();
}
}, 100);
// Continue to initialize dragging even if map isn't ready
}
}
const dragHandle = document.querySelector(`.drag-handle[data-platform="${platform}"]`);
const container = windowEl?.parentElement;
if (!dragHandle || !container) {
// Elements might not be ready yet, retry
setTimeout(initNodesMapWindow, 50);
return;
}
// Skip dragging initialization if already done (for view transitions, allow reinitialization)
// Check if event listeners are already attached by checking if dragHandle has a data attribute
if (windowEl.dataset.draggingInitialized === 'true' && mapExists) {
return;
}
windowEl.dataset.draggingInitialized = 'true';
let isDragging = false;
let initialX = 0;
let initialY = 0;
function dragStart(e) {
if (e.button !== 0) return;
isDragging = true;
const rect = windowEl.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// Calculate where we clicked relative to the window
initialX = e.clientX - rect.left;
initialY = e.clientY - rect.top;
// Remove positioning classes that might conflict
windowEl.classList.remove('-translate-x-1/2', '-translate-y-1/2', 'bottom-8', 'left-8');
windowEl.classList.add('translate-x-0', 'translate-y-0');
// Get current position
const currentLeft = rect.left - containerRect.left;
const currentTop = rect.top - containerRect.top;
// Set initial positions using explicit pixel values
windowEl.style.left = currentLeft + 'px';
windowEl.style.top = currentTop + 'px';
windowEl.style.bottom = 'auto';
windowEl.style.right = 'auto';
windowEl.classList.add('user-select-none');
document.body.style.cursor = 'grabbing';
}
function drag(e) {
if (!isDragging) return;
e.preventDefault();
const containerRect = container.getBoundingClientRect();
const windowRect = windowEl.getBoundingClientRect();
// Calculate new position
const newX = e.clientX - containerRect.left - initialX;
const newY = e.clientY - containerRect.top - initialY;
// Keep window within container bounds
const maxX = containerRect.width - windowRect.width;
const maxY = containerRect.height - windowRect.height;
const boundedX = Math.max(0, Math.min(newX, maxX));
const boundedY = Math.max(0, Math.min(newY, maxY));
windowEl.style.left = boundedX + 'px';
windowEl.style.top = boundedY + 'px';
}
function dragEnd() {
if (!isDragging) return;
isDragging = false;
windowEl.classList.remove('user-select-none');
document.body.style.cursor = '';
}
dragHandle.addEventListener('mousedown', dragStart);
if (!_docDrag) {
_docDrag = drag;
_docDragEnd = dragEnd;
document.addEventListener('mousemove', _docDrag);
document.addEventListener('mouseup', _docDragEnd);
document.addEventListener('mouseleave', _docDragEnd);
}
// Drag and drop functionality for nodes
let draggedElement = null;
// Make nodes draggable
const draggableNodes = windowEl.querySelectorAll('.draggable-node');
draggableNodes.forEach(node => {
node.addEventListener('dragstart', function(e) {
draggedElement = this;
this.style.opacity = '0.5';
this.style.cursor = 'grabbing';
document.body.style.cursor = 'grabbing';
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
});
node.addEventListener('dragend', function() {
this.style.opacity = '1';
this.style.cursor = '';
document.body.style.cursor = '';
});
// Click to zoom to node
node.addEventListener('click', function() {
const lat = parseFloat(this.dataset.nodeLat);
const lng = parseFloat(this.dataset.nodeLng);
if (mapContainer._leafletMap && !isNaN(lat) && !isNaN(lng)) {
const map = mapContainer._leafletMap;
map.setView([lat, lng], 6, {
animate: true,
duration: 0.5
});
const markerMap = mapContainer._leafletMarkerMap;
if (markerMap && markerMap[this.dataset.nodeName]) {
markerMap[this.dataset.nodeName].openPopup();
}
}
});
});
// Drop zones
const dropZones = windowEl.querySelectorAll('.node-section');
dropZones.forEach(zone => {
zone.addEventListener('dragover', function(e) {
e.preventDefault();
const targetSection = this.dataset.section;
const isOnline = targetSection === 'online';
// Check if offline section already has a node
if (!isOnline) {
const existingOfflineNodes = this.querySelectorAll('.draggable-node');
if (existingOfflineNodes.length > 0 && draggedElement && draggedElement.dataset.online !== 'false') {
e.dataTransfer.dropEffect = 'none';
this.style.backgroundColor = 'rgba(239, 68, 68, 0.1)';
return;
}
}
e.dataTransfer.dropEffect = 'move';
this.style.backgroundColor = 'rgba(34, 197, 94, 0.1)';
});
zone.addEventListener('dragleave', function() {
this.style.backgroundColor = '';
});
zone.addEventListener('drop', function(e) {
e.preventDefault();
this.style.backgroundColor = '';
if (draggedElement) {
const targetSection = this.dataset.section;
const isOnline = targetSection === 'online';
const nodeName = draggedElement.dataset.nodeName;
// Prevent moving to offline if it already has a node (unless moving from offline)
if (!isOnline) {
const existingOfflineNodes = this.querySelectorAll('.draggable-node');
if (existingOfflineNodes.length > 0 && draggedElement.dataset.online !== 'false') {
draggedElement.style.opacity = '1';
draggedElement = null;
return;
}
}
// Remove placeholder message if it exists
const placeholder = this.querySelector('.offline-placeholder');
if (placeholder) {
placeholder.remove();
}
// Move the node element
this.appendChild(draggedElement);
// Update node data
draggedElement.dataset.online = isOnline;
// Update visual appearance
const indicator = draggedElement.querySelector('div[class*="rounded-full"]');
const label = draggedElement.querySelector('span');
if (isOnline) {
indicator.className = 'w-2 h-2 rounded-full bg-green-500 shrink-0';
indicator.style.backgroundColor = '#22c55e';
// Update border classes
draggedElement.classList.remove('border-white/5', 'hover:border-white/10');
draggedElement.classList.add('border-green-500/30', 'hover:border-green-500/50');
label.className = 'text-white/80 text-[10px] font-medium whitespace-nowrap';
} else {
indicator.className = 'w-2 h-2 rounded-full bg-gray-500 shrink-0';
indicator.style.backgroundColor = '#6b7280';
// Update border classes
draggedElement.classList.remove('border-green-500/30', 'hover:border-green-500/50');
draggedElement.classList.add('border-white/5', 'hover:border-white/10');
label.className = 'text-white/60 text-[10px] font-medium whitespace-nowrap';
}
// Update marker on map
const markerMap = mapContainer._leafletMarkerMap;
if (markerMap && markerMap[nodeName]) {
const marker = markerMap[nodeName];
const L = window.Leaflet;
const newIcon = L.divIcon({
className: 'custom-node-marker',
html: `
`,
iconSize: [12, 12],
iconAnchor: [6, 6]
});
marker.setIcon(newIcon);
marker.setPopupContent(nodeName + (isOnline ? ' (Online)' : ' (Offline)'));
marker._nodeData.online = isOnline;
}
// Add placeholder back to offline section if it becomes empty
if (isOnline) {
const offlineSection = windowEl.querySelector('[data-section="offline"]');
const offlineNodes = offlineSection?.querySelectorAll('.draggable-node');
if (offlineSection && (!offlineNodes || offlineNodes.length === 0)) {
const placeholder = document.createElement('div');
placeholder.className = 'w-full flex items-center justify-center text-white/40 text-xs italic py-1 offline-placeholder';
placeholder.textContent = 'Move one of the nodes here';
offlineSection.appendChild(placeholder);
}
}
// Dispatch event with updated node states
const allNodes = windowEl.querySelectorAll('.draggable-node');
const nodeStates = Array.from(allNodes).map(node => ({
name: node.dataset.nodeName,
online: node.dataset.online === 'true'
}));
const event = new CustomEvent('nodes-state-changed', {
detail: { nodes: nodeStates, platform: platform }
});
document.dispatchEvent(event);
draggedElement = null;
}
});
});
}
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initNodesMapWindow);
} else {
initNodesMapWindow();
}
// Also initialize after view transitions
document.addEventListener('astro:page-load', initNodesMapWindow);
document.addEventListener('astro:after-swap', initNodesMapWindow);
document.addEventListener('astro:before-swap', () => {
if (_docDrag) {
document.removeEventListener('mousemove', _docDrag);
document.removeEventListener('mouseup', _docDragEnd);
document.removeEventListener('mouseleave', _docDragEnd);
_docDrag = null;
_docDragEnd = null;
}
});
})();
Terminal
$ nslookup your.app.domain
Addresses:
45.76.142.123 dallas.your.cloud
185.199.108.42 paris.your.cloud
178.154.213.87 moscow.your.cloud
$