Add Name Modal On Add

This commit is contained in:
Raven Scott 2024-11-30 06:53:07 -05:00
parent f995c44057
commit ad67461cf5
2 changed files with 228 additions and 149 deletions

352
app.js
View File

@ -103,39 +103,50 @@ function waitForPeerResponse(expectedMessageFragment, timeout = 900000) {
}); });
} }
// Utility functions for managing cookies function saveConnections() {
function setCookie(name, value, days = 365) { console.log('[DEBUG] Saving connections:', connections);
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
const expires = `expires=${date.toUTCString()}`;
document.cookie = `${name}=${encodeURIComponent(value)};${expires};path=/`;
}
function getCookie(name) { const serializableConnections = {};
const cookies = document.cookie.split('; ');
for (let i = 0; i < cookies.length; i++) {
const [key, value] = cookies[i].split('=');
if (key === name) return decodeURIComponent(value);
}
return null;
}
function deleteCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
// Load connections from cookies
function loadConnections() {
const savedConnections = getCookie('connections');
const connections = savedConnections ? JSON.parse(savedConnections) : {};
// Recreate the topic Buffer from the hex string
for (const topicId in connections) { for (const topicId in connections) {
const { topicHex } = connections[topicId]; const { topic, topicHex, connectionName } = connections[topicId];
if (!serializableConnections[topicId] && topicHex) {
serializableConnections[topicId] = {
topicHex,
connectionName: connectionName || 'Unnamed Connection',
topic: b4a.toString(topic, 'hex'),
};
} else {
console.warn(`[WARN] Skipping duplicate or invalid connection: ${topicId}`);
}
}
localStorage.setItem('connections', JSON.stringify(serializableConnections));
}
function loadConnections() {
// Clear any previously loaded connections in memory
Object.keys(connections).forEach((topicId) => {
delete connections[topicId];
});
const savedConnections = localStorage.getItem('connections');
const connectionsData = savedConnections ? JSON.parse(savedConnections) : {};
for (const topicId in connectionsData) {
const { topicHex, connectionName } = connectionsData[topicId];
if (!topicHex) {
console.warn(`[WARN] Skipping connection with missing topicHex: ${topicId}`);
continue;
}
console.log(`[DEBUG] Loading connection: ${topicHex}, Name: ${connectionName}`);
connections[topicId] = { connections[topicId] = {
topic: b4a.from(topicHex, 'hex'), topic: b4a.from(topicHex, 'hex'),
topicHex, topicHex,
peer: null, // Initialize additional properties connectionName: connectionName || 'Unnamed Connection',
peer: null,
swarm: null, swarm: null,
}; };
} }
@ -144,19 +155,44 @@ function loadConnections() {
} }
// Save connections to cookies function renderConnections() {
function saveConnections() { console.log('[DEBUG] Rendering connections in the UI...');
const serializableConnections = {}; connectionList.innerHTML = ''; // Clear the current list
for (const topicId in connections) { Object.keys(connections).forEach((topicId) => {
const { topic, topicHex } = connections[topicId]; // Only serialize simple properties const { topicHex, connectionName } = connections[topicId];
serializableConnections[topicId] = {
topicHex,
topic: b4a.toString(topic, 'hex'), // Convert Buffer to hex string
};
}
setCookie('connections', JSON.stringify(serializableConnections)); // Render the connection
const connectionItem = document.createElement('li');
connectionItem.className = 'list-group-item d-flex align-items-center justify-content-between';
connectionItem.dataset.topicId = topicId;
connectionItem.innerHTML = `
<span>
<span class="connection-status status-disconnected"></span>${connectionName || 'Unnamed Connection'} (${topicId})
</span>
<button class="btn btn-sm btn-danger disconnect-btn">
<i class="fas fa-plug"></i>
</button>
`;
connectionItem.querySelector('span').addEventListener('click', () => switchConnection(topicId));
connectionItem.querySelector('.disconnect-btn').addEventListener('click', (e) => {
e.stopPropagation();
disconnectConnection(topicId, connectionItem);
});
connectionList.appendChild(connectionItem);
});
console.log('[DEBUG] Connections rendered successfully.');
}
function deleteConnections() {
localStorage.removeItem('connections');
} }
@ -175,47 +211,19 @@ const resetConnectionsBtn = document.createElement('button');
resetConnectionsBtn.textContent = 'Reset Connections'; resetConnectionsBtn.textContent = 'Reset Connections';
resetConnectionsBtn.className = 'btn btn-danger w-100 mt-2'; resetConnectionsBtn.className = 'btn btn-danger w-100 mt-2';
resetConnectionsBtn.addEventListener('click', () => { resetConnectionsBtn.addEventListener('click', () => {
console.log('[INFO] Resetting connections and clearing cookies.'); console.log('[INFO] Resetting connections and clearing local storage.');
Object.keys(connections).forEach((topicId) => { Object.keys(connections).forEach((topicId) => {
disconnectConnection(topicId); disconnectConnection(topicId);
}); });
deleteCookie('connections'); deleteConnections();
resetConnectionsView(); resetConnectionsView();
showWelcomePage(); showWelcomePage();
toggleResetButtonVisibility(); // Ensure button visibility is updated toggleResetButtonVisibility();
}); });
document.getElementById('sidebar').appendChild(resetConnectionsBtn); document.getElementById('sidebar').appendChild(resetConnectionsBtn);
// Initialize the app
console.log('[INFO] Client app initialized');
// Load connections from cookies and restore them
document.addEventListener('DOMContentLoaded', () => {
const savedConnections = loadConnections();
console.log('[INFO] Restoring saved connections:', savedConnections);
// Restore saved connections
Object.keys(savedConnections).forEach((topicId) => {
let topicHex = savedConnections[topicId].topic;
// Ensure topicHex is a string
if (typeof topicHex !== 'string') {
topicHex = b4a.toString(topicHex, 'hex');
}
addConnection(topicHex);
});
if (Object.keys(connections).length > 0) {
hideWelcomePage();
} else {
showWelcomePage();
}
assertVisibility(); // Ensure visibility reflects the restored connections
});
// Show Status Indicator // Show Status Indicator
// Modify showStatusIndicator to recreate it dynamically // Modify showStatusIndicator to recreate it dynamically
function showStatusIndicator(message = 'Processing...') { function showStatusIndicator(message = 'Processing...') {
@ -358,47 +366,88 @@ addConnectionForm.addEventListener('submit', (e) => {
const topicHex = newConnectionTopic.value.trim(); const topicHex = newConnectionTopic.value.trim();
if (topicHex) { if (topicHex) {
addConnection(topicHex); openConnectionNameModal(topicHex); // Open the modal to ask for the connection name
newConnectionTopic.value = ''; newConnectionTopic.value = '';
} }
}); });
function addConnection(topicHex) {
console.log(`[DEBUG] Adding connection with topic: ${topicHex}`);
if (Object.keys(connections).length === 0) { function openConnectionNameModal(topicHex) {
hideWelcomePage(); const connectionNameModalElement = document.getElementById('connectionNameModal');
} const connectionNameModal = new bootstrap.Modal(connectionNameModalElement);
const connectionNameInput = document.getElementById('connection-name-input');
const saveConnectionNameBtn = document.getElementById('save-connection-name-btn');
const topic = b4a.from(topicHex, 'hex'); // Clear the input and show the modal
connectionNameInput.value = '';
connectionNameModal.show();
// Add event listener for the save button
saveConnectionNameBtn.onclick = () => {
const connectionName = connectionNameInput.value.trim();
if (!connectionName) {
showAlert('danger', 'Please enter a connection name.');
return;
}
// Hide the modal and add the connection
connectionNameModal.hide();
addConnection(topicHex, connectionName);
};
}
function addConnection(topicHex, connectionName) {
const topicId = topicHex.substring(0, 12); const topicId = topicHex.substring(0, 12);
connections[topicId] = { topic, peer: null, swarm: null, topicHex }; // Check if the connection exists
saveConnections(); // Save updated connections to cookies if (connections[topicId]) {
console.warn(`[WARN] Connection with topic ${topicHex} already exists.`);
if (!connections[topicId].swarm || !connections[topicId].peer) {
console.log(`[INFO] Reinitializing connection: ${topicHex}`);
connections[topicId].swarm = new Hyperswarm();
const topic = b4a.from(topicHex, 'hex');
connections[topicId].topic = topic;
const connectionItem = document.createElement('li'); const swarm = connections[topicId].swarm;
connectionItem.className = 'list-group-item d-flex align-items-center justify-content-between'; swarm.join(topic, { client: true, server: false });
connectionItem.dataset.topicId = topicId;
connectionItem.innerHTML = `
<span>
<span class="connection-status status-disconnected"></span>${topicId}
</span>
<button class="btn btn-sm btn-danger disconnect-btn">
<i class="fas fa-plug"></i>
</button>
`;
connectionItem.querySelector('span').addEventListener('click', () => switchConnection(topicId)); swarm.on('connection', (peer) => {
connectionItem.querySelector('.disconnect-btn').addEventListener('click', (e) => { console.log(`[INFO] Connected to peer for topic: ${topicHex}`);
e.stopPropagation(); if (connections[topicId].peer) {
disconnectConnection(topicId, connectionItem); peer.destroy();
}); return;
refreshContainerStats(); }
connections[topicId].peer = peer;
updateConnectionStatus(topicId, true);
connectionList.appendChild(connectionItem); peer.on('data', (data) => handlePeerData(data, topicId, peer));
peer.on('close', () => {
console.log(`[INFO] Peer disconnected for topic: ${topicId}`);
updateConnectionStatus(topicId, false);
if (window.activePeer === peer) {
window.activePeer = null;
dashboard.classList.add('hidden');
containerList.innerHTML = '';
stopStatsInterval();
}
});
if (!window.activePeer) {
window.activePeer = connections[topicId].peer;
} else {
console.warn(`[WARN] Switching active peer. Current: ${window.activePeer}, New: ${connections[topicId].peer}`);
}
});
}
renderConnections(); // Ensure the sidebar list is updated
return;
}
console.log(`[DEBUG] Adding connection with topic: ${topicHex} and name: ${connectionName}`);
const topic = b4a.from(topicHex, 'hex');
const swarm = new Hyperswarm(); const swarm = new Hyperswarm();
connections[topicId].swarm = swarm; connections[topicId] = { topic, topicHex, connectionName, peer: null, swarm };
swarm.join(topic, { client: true, server: false }); swarm.join(topic, { client: true, server: false });
@ -418,47 +467,55 @@ function addConnection(topicHex) {
window.activePeer = null; window.activePeer = null;
dashboard.classList.add('hidden'); dashboard.classList.add('hidden');
containerList.innerHTML = ''; containerList.innerHTML = '';
stopStatsInterval(); // Stop stats polling stopStatsInterval();
} }
}); });
if (!window.activePeer) { if (!window.activePeer) {
switchConnection(topicId); switchConnection(topicId);
} }
startStatsInterval();
}); });
// Collapse the sidebar after adding a connection saveConnections();
const sidebar = document.getElementById('sidebar'); renderConnections(); // Ensure the sidebar list is updated
const collapseSidebarBtn = document.getElementById('collapse-sidebar-btn');
if (!sidebar.classList.contains('collapsed')) {
sidebar.classList.add('collapsed');
collapseSidebarBtn.innerHTML = '&gt;';
console.log('[DEBUG] Sidebar collapsed after adding connection');
}
} }
// Initialize connections from cookies on page load
// Initialize the app
console.log('[INFO] Client app initialized');
// Load connections from cookies and restore them
// Initialize the app
console.log('[INFO] Client app initialized');
// Load connections from cookies and restore them
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
console.log('[INFO] Initializing the app...');
const savedConnections = loadConnections(); const savedConnections = loadConnections();
console.log('[INFO] Loading saved connections:', savedConnections); console.log('[INFO] Restoring saved connections:', savedConnections);
Object.keys(savedConnections).forEach((topicId) => { Object.keys(savedConnections).forEach((topicId) => {
const topicHex = savedConnections[topicId].topic; const { topicHex, connectionName } = savedConnections[topicId];
addConnection(topicHex); addConnection(topicHex, connectionName); // Initialize each connection
}); });
if (Object.keys(connections).length > 0) { if (Object.keys(connections).length > 0) {
hideWelcomePage(); hideWelcomePage();
startStatsInterval(); // Start stats polling for active peers const firstConnection = Object.keys(connections)[0];
switchConnection(firstConnection); // Auto-switch to the first connection
} else { } else {
showWelcomePage(); showWelcomePage();
} }
assertVisibility(); console.log('[INFO] App initialized successfully.');
}); });
function disconnectConnection(topicId, connectionItem) { function disconnectConnection(topicId, connectionItem) {
const connection = connections[topicId]; const connection = connections[topicId];
if (!connection) { if (!connection) {
@ -466,20 +523,6 @@ function disconnectConnection(topicId, connectionItem) {
return; return;
} }
// Clean up terminals
if (window.openTerminals[topicId]) {
console.log(`[INFO] Closing terminals for topic: ${topicId}`);
window.openTerminals[topicId].forEach((terminalId) => {
try {
cleanUpTerminal(terminalId);
} catch (err) {
console.error(`[ERROR] Failed to clean up terminal ${terminalId}: ${err.message}`);
}
});
delete window.openTerminals[topicId];
}
// Destroy the peer and swarm
if (connection.peer) { if (connection.peer) {
connection.peer.destroy(); connection.peer.destroy();
} }
@ -487,35 +530,28 @@ function disconnectConnection(topicId, connectionItem) {
connection.swarm.destroy(); connection.swarm.destroy();
} }
// Remove from global connections
delete connections[topicId]; delete connections[topicId];
// Save the updated connections to cookies
saveConnections(); saveConnections();
// Remove the connection item from the UI
if (connectionItem) { if (connectionItem) {
connectionList.removeChild(connectionItem); connectionList.removeChild(connectionItem);
} }
// Reset the connection title if this was the active peer
if (window.activePeer === connection.peer) { if (window.activePeer === connection.peer) {
window.activePeer = null; window.activePeer = null;
const connectionTitle = document.getElementById('connection-title'); const connectionTitle = document.getElementById('connection-title');
if (connectionTitle) { if (connectionTitle) {
connectionTitle.textContent = 'Choose a Connection'; // Reset the title connectionTitle.textContent = 'Choose a Connection';
} }
const dashboard = document.getElementById('dashboard'); const dashboard = document.getElementById('dashboard');
if (dashboard) { if (dashboard) {
dashboard.classList.add('hidden'); dashboard.classList.add('hidden');
} }
resetContainerList();
resetContainerList(); // Clear containers
} }
// Show welcome page if no connections remain renderConnections(); // Ensure the sidebar list is updated
if (Object.keys(connections).length === 0) { if (Object.keys(connections).length === 0) {
showWelcomePage(); showWelcomePage();
} }
@ -565,38 +601,56 @@ function resetConnectionsView() {
// Update connection status // Update connection status
function updateConnectionStatus(topicId, isConnected) { function updateConnectionStatus(topicId, isConnected) {
if (!connections[topicId]) {
console.error(`[ERROR] No connection found for topic: ${topicId}`);
return;
}
const connectionItem = document.querySelector(`[data-topic-id="${topicId}"] .connection-status`); const connectionItem = document.querySelector(`[data-topic-id="${topicId}"] .connection-status`);
if (connectionItem) { if (connectionItem) {
connectionItem.className = `connection-status ${isConnected ? 'status-connected' : 'status-disconnected'}`; connectionItem.className = `connection-status ${isConnected ? 'status-connected' : 'status-disconnected'}`;
} }
console.log(`[DEBUG] Connection ${topicId} status updated to: ${isConnected ? 'connected' : 'disconnected'}`);
} }
setInterval(() => {
Object.keys(connections).forEach((topicId) => {
const connection = connections[topicId];
if (connection.peer && !connection.peer.destroyed) {
updateConnectionStatus(topicId, true);
} else {
updateConnectionStatus(topicId, false);
}
});
}, 1000); // Adjust interval as needed
// Switch between connections // Switch between connections
function switchConnection(topicId) { function switchConnection(topicId) {
const connection = connections[topicId]; const connection = connections[topicId];
if (!connection || !connection.peer) { if (!connection || !connection.peer) {
console.error('[ERROR] No connection found or no active peer.'); console.warn(`[WARN] No active peer for connection: ${topicId}`);
showWelcomePage(); return; // Skip switching if no active peer is found
stopStatsInterval(); // Stop stats interval if no active peer
return;
} }
// Update the active peer console.log(`[INFO] Switching to connection: ${topicId}`);
window.activePeer = connection.peer; window.activePeer = connection.peer;
// Clear container list before loading new data
resetContainerList(); resetContainerList();
const connectionTitle = document.getElementById('connection-title');
if (connectionTitle) {
connectionTitle.textContent = connection.connectionName || 'Unnamed Connection';
}
console.log(`[INFO] Switched to connection: ${topicId}`); hideWelcomePage();
// Start the stats interval
startStatsInterval(); startStatsInterval();
sendCommand('listContainers');
sendCommand('listContainers'); // Request containers for the new connection
} }
// Attach switchConnection to the global window object // Attach switchConnection to the global window object
window.switchConnection = switchConnection; window.switchConnection = switchConnection;

View File

@ -449,6 +449,29 @@
</div> </div>
</div> </div>
<!-- Connection Name Modal -->
<div class="modal fade" id="connectionNameModal" tabindex="-1" aria-labelledby="connectionNameModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="connectionNameModalLabel">Enter Connection Name</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="connection-name-input" class="form-label">Connection Name</label>
<input type="text" class="form-control" id="connection-name-input" placeholder="Enter a name for the connection">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="save-connection-name-btn" class="btn btn-primary">Save</button>
</div>
</div>
</div>
</div>
<!-- Status Indicator Overlay --> <!-- Status Indicator Overlay -->
<div id="status-indicator" <div id="status-indicator"
class="position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-dark bg-opacity-75" class="position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-dark bg-opacity-75"
@ -461,6 +484,8 @@
</div> </div>
</div> </div>
<!-- Alert Container --> <!-- Alert Container -->
<div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3" <div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3"
style="z-index: 1051; max-width: 90%;"></div> style="z-index: 1051; max-width: 90%;"></div>