further updates

This commit is contained in:
Raven Scott
2025-11-24 16:28:21 -05:00
parent 6469f620e6
commit 4bf3381270
6 changed files with 428 additions and 321 deletions
+2 -50
View File
@@ -106,57 +106,9 @@ export function removeProgressBar(id) {
}
/**
* Show operation queue status
* @param {string} operation - Operation name
* @param {string} status - Status (pending, processing, completed, failed)
* @deprecated showOperationStatus has been replaced with the notification system
* Use notificationManager.add() from './notifications.js' instead
*/
export function showOperationStatus(operation, status) {
const statusContainer = document.getElementById('operation-status') || createOperationStatusContainer();
const statusItem = document.createElement('div');
statusItem.className = `operation-status-item status-${status}`;
statusItem.innerHTML = `
<span class="operation-name">${operation}</span>
<span class="operation-status-badge badge bg-${getStatusColor(status)}">${status}</span>
`;
statusContainer.appendChild(statusItem);
// Auto-remove completed items after 3 seconds
if (status === 'completed' || status === 'failed') {
setTimeout(() => {
statusItem.remove();
}, 3000);
}
}
/**
* Create operation status container if it doesn't exist
* @returns {HTMLElement} - Status container
*/
function createOperationStatusContainer() {
const container = document.createElement('div');
container.id = 'operation-status';
container.className = 'operation-status-container position-fixed top-0 end-0 m-3';
container.style.zIndex = '1060';
document.body.appendChild(container);
return container;
}
/**
* Get status color for badge
* @param {string} status - Status
* @returns {string} - Bootstrap color class
*/
function getStatusColor(status) {
const colors = {
pending: 'secondary',
processing: 'primary',
completed: 'success',
failed: 'danger'
};
return colors[status] || 'secondary';
}
+22 -1
View File
@@ -10,13 +10,21 @@ class NotificationManager {
constructor() {
this.notifications = [];
this.listeners = [];
this.loadFromStorage();
this._storageLoaded = false;
// Defer loading from storage to not block initialization
if (typeof requestIdleCallback !== 'undefined') {
requestIdleCallback(() => this.loadFromStorage(), { timeout: 1000 });
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(() => this.loadFromStorage(), 0);
}
}
/**
* Load notifications from localStorage
*/
loadFromStorage() {
if (this._storageLoaded) return;
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
@@ -29,9 +37,13 @@ class NotificationManager {
// Limit to most recent notifications
this.notifications = this.notifications.slice(-MAX_NOTIFICATIONS);
}
this._storageLoaded = true;
// Notify listeners after loading
this.notify();
} catch (err) {
console.error('[ERROR] Failed to load notifications from storage:', err);
this.notifications = [];
this._storageLoaded = true;
}
}
@@ -165,6 +177,10 @@ class NotificationManager {
* @returns {number} Number of unread notifications
*/
getUnreadCount() {
// Ensure storage is loaded
if (!this._storageLoaded) {
this.loadFromStorage();
}
return this.notifications.filter(n => !n.read).length;
}
@@ -174,6 +190,11 @@ class NotificationManager {
* @returns {Array} Filtered notifications
*/
getNotifications(filters = {}) {
// Ensure storage is loaded
if (!this._storageLoaded) {
this.loadFromStorage();
}
let filtered = [...this.notifications];
if (filters.type && filters.type !== 'all') {
+33 -40
View File
@@ -1,6 +1,6 @@
// Import dependencies first (ES6 imports must be at top)
import { showOperationStatus } from './loadingStates.js';
import { closeAllModals, showStatusIndicator, hideStatusIndicator, showAlert } from './uiUtils.js';
import notificationManager from './notifications.js';
// DOM Elements
const templateList = document.getElementById('template-list');
@@ -750,19 +750,20 @@ deployForm.addEventListener('submit', async (e) => {
return;
}
// Safely get container name with fallback
const containerName = (formData && formData.containerName) ? String(formData.containerName) : 'container';
// Close modal immediately before async operation
if (typeof closeAllModals === 'function') {
closeAllModals();
}
closeDeployModal();
// Add notification for container creation
notificationManager.add('info', `Creating container "${containerName}"...`, { autoDismiss: false });
showStatusIndicator('Deploying container...');
try {
// Safely get container name with fallback
const containerName = (formData && formData.containerName) ? String(formData.containerName) : 'container';
showStatusIndicator('Deploying container...');
try {
if (typeof showOperationStatus === 'function') {
showOperationStatus(`Deploying ${containerName}`, 'processing');
}
} catch (e) {
console.warn('[WARN] Failed to show operation status:', e);
}
// Deploy container with proper error handling
const successResponse = await deployDockerContainer(formData);
@@ -773,24 +774,13 @@ deployForm.addEventListener('submit', async (e) => {
hideStatusIndicator();
try {
if (typeof showOperationStatus === 'function') {
showOperationStatus(`Deploying ${containerName}`, 'completed');
}
} catch (e) {
console.warn('[WARN] Failed to show operation status:', e);
}
// Close all modals including the deploy modal
if (typeof closeAllModals === 'function') {
closeAllModals();
}
closeDeployModal();
// Safely extract success message
const successMessage = (successResponse && successResponse.message)
? String(successResponse.message)
: 'Container deployed successfully!';
// Update notification to success
notificationManager.add('success', `Container "${containerName}" created successfully!`);
showAlert('success', successMessage);
} catch (error) {
// Safely extract error message
@@ -813,17 +803,8 @@ deployForm.addEventListener('submit', async (e) => {
console.warn('[WARN] Failed to hide status indicator:', e);
}
// Safely get container name for error status
try {
const containerName = (formData && formData.containerName)
? String(formData.containerName)
: 'container';
if (typeof showOperationStatus === 'function') {
showOperationStatus(`Deploying ${containerName}`, 'failed');
}
} catch (e) {
console.warn('[WARN] Failed to show operation status:', e);
}
// Update notification to error (containerName already defined above)
notificationManager.add('danger', `Failed to create container "${containerName}"`);
showAlert('danger', errorMessage);
}
@@ -831,8 +812,20 @@ deployForm.addEventListener('submit', async (e) => {
// Save template functionality removed to match working version
// Initialize templates on load
document.addEventListener('DOMContentLoaded', fetchTemplates);
// Initialize templates on load - defer to not block initialization
if (typeof requestIdleCallback !== 'undefined') {
document.addEventListener('DOMContentLoaded', () => {
requestIdleCallback(() => {
fetchTemplates();
}, { timeout: 2000 });
});
} else {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
fetchTemplates();
}, 500);
});
}
// Duplicate modal array management functions
let duplicatePortCounter = 0;
+37 -7
View File
@@ -6,11 +6,11 @@
import notificationManager from './notifications.js';
/**
* Close all open Bootstrap modals
* Close all open Bootstrap modals and clean up any lingering backdrops
*/
export function closeAllModals() {
// Find and hide all open modals
const modals = document.querySelectorAll('.modal.show');
const modals = document.querySelectorAll('.modal.show, .modal[style*="display"]');
modals.forEach(modal => {
const modalInstance = bootstrap.Modal.getInstance(modal);
if (modalInstance) {
@@ -20,7 +20,29 @@ export function closeAllModals() {
const newInstance = new bootstrap.Modal(modal);
newInstance.hide();
}
// Also directly hide the modal element as fallback
modal.classList.remove('show');
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
modal.removeAttribute('aria-modal');
});
// Remove any lingering modal backdrops
const backdrops = document.querySelectorAll('.modal-backdrop');
backdrops.forEach(backdrop => {
backdrop.remove();
});
// Clean up body classes and styles
document.body.classList.remove('modal-open');
document.body.style.paddingRight = '';
document.body.style.overflow = '';
// Also handle custom terminal modal if it exists
const terminalModal = document.getElementById('terminal-modal');
if (terminalModal && terminalModal.style.display !== 'none') {
terminalModal.style.display = 'none';
}
}
/**
@@ -44,14 +66,22 @@ export function showStatusIndicator(message = 'Processing...') {
/**
* Hide status indicator overlay
* Safe to call before DOM is ready
*/
export function hideStatusIndicator() {
const statusIndicator = document.getElementById('status-indicator');
if (statusIndicator) {
console.log('[DEBUG] Hiding status indicator');
statusIndicator.remove();
// Use requestAnimationFrame to ensure DOM is ready, or check immediately if already loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
const statusIndicator = document.getElementById('status-indicator');
if (statusIndicator) {
statusIndicator.remove();
}
});
} else {
console.error('[ERROR] Status indicator element not found!');
const statusIndicator = document.getElementById('status-indicator');
if (statusIndicator) {
statusIndicator.remove();
}
}
}