/** * ZitiNexus Router Enrollment UI JavaScript - Simplified Version */ class EnrollmentUI { constructor() { this.enrollmentInProgress = false; this.init(); } init() { this.bindEvents(); this.loadSystemStatus(); // Auto-refresh system status every 30 seconds setInterval(() => { if (!this.enrollmentInProgress) { this.loadSystemStatus(); } }, 30000); } bindEvents() { // Enrollment form submission const enrollForm = document.getElementById('enrollmentForm'); if (enrollForm) { enrollForm.addEventListener('submit', (e) => { e.preventDefault(); this.startEnrollment(); }); } // Hash key validation const hashKeyInput = document.getElementById('hashKey'); if (hashKeyInput) { hashKeyInput.addEventListener('input', (e) => { this.validateHashKey(e.target); }); } // API endpoint validation const apiEndpointInput = document.getElementById('apiEndpoint'); if (apiEndpointInput) { apiEndpointInput.addEventListener('input', (e) => { this.validateApiEndpoint(e.target); }); } // Refresh system status button const refreshBtn = document.getElementById('refreshStatus'); if (refreshBtn) { refreshBtn.addEventListener('click', () => { this.refreshSystemStatus(); }); } // Clear logs button const clearLogsBtn = document.getElementById('clearLogs'); if (clearLogsBtn) { clearLogsBtn.addEventListener('click', () => { this.clearLogs(); }); } } validateHashKey(input) { const value = input.value.trim(); const isValid = /^[a-fA-F0-9]{32}$/.test(value); if (value.length === 0) { this.setInputState(input, 'neutral'); } else if (isValid) { this.setInputState(input, 'valid'); } else { this.setInputState(input, 'invalid', 'Hash key must be 32 hexadecimal characters'); } return isValid; } validateApiEndpoint(input) { const value = input.value.trim(); const isValid = /^https?:\/\/.+/.test(value); if (value.length === 0) { this.setInputState(input, 'neutral'); } else if (isValid) { this.setInputState(input, 'valid'); } else { this.setInputState(input, 'invalid', 'Must be a valid HTTP/HTTPS URL'); } return isValid; } setInputState(input, state, message = '') { const formGroup = input.closest('.form-group'); const feedback = formGroup.querySelector('.form-feedback') || this.createFeedbackElement(formGroup); // Remove existing state classes input.classList.remove('is-valid', 'is-invalid'); feedback.classList.remove('valid-feedback', 'invalid-feedback'); switch (state) { case 'valid': input.classList.add('is-valid'); feedback.classList.add('valid-feedback'); feedback.textContent = 'Looks good!'; feedback.style.display = 'block'; break; case 'invalid': input.classList.add('is-invalid'); feedback.classList.add('invalid-feedback'); feedback.textContent = message; feedback.style.display = 'block'; break; case 'neutral': default: feedback.style.display = 'none'; break; } } createFeedbackElement(formGroup) { const feedback = document.createElement('div'); feedback.className = 'form-feedback'; feedback.style.fontSize = '0.875rem'; feedback.style.marginTop = '0.25rem'; formGroup.appendChild(feedback); return feedback; } async loadSystemStatus() { try { const response = await fetch('dashboard.php?action=get_status', { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); this.updateSystemStatus(data); } catch (error) { console.error('Failed to load system status:', error); this.showAlert('Failed to load system status', 'error'); } } async refreshSystemStatus() { const refreshBtn = document.getElementById('refreshStatus'); const originalText = refreshBtn.innerHTML; try { // Show loading state refreshBtn.disabled = true; refreshBtn.innerHTML = '🔄 Refreshing...'; const response = await fetch('dashboard.php?action=refresh_status', { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); this.updateSystemStatus(data); this.showAlert('System status refreshed successfully', 'success'); } catch (error) { console.error('Failed to refresh system status:', error); this.showAlert('Failed to refresh system status', 'error'); } finally { // Restore button state refreshBtn.disabled = false; refreshBtn.innerHTML = originalText; } } updateSystemStatus(status) { // Update hostname const hostnameElement = document.getElementById('hostname'); if (hostnameElement) { hostnameElement.textContent = status.hostname || 'Unknown'; } // Update Ziti status const zitiStatusElement = document.getElementById('zitiStatus'); const zitiStatusIcon = document.getElementById('zitiStatusIcon'); if (zitiStatusElement && zitiStatusIcon) { if (status.ziti_status === 'installed') { zitiStatusElement.textContent = `Installed (${status.ziti_version})`; zitiStatusIcon.className = 'status-icon success'; zitiStatusIcon.innerHTML = '✓'; } else { zitiStatusElement.textContent = 'Not Installed'; zitiStatusIcon.className = 'status-icon error'; zitiStatusIcon.innerHTML = '✗'; } } // Update service status const serviceStatusElement = document.getElementById('serviceStatus'); const serviceStatusIcon = document.getElementById('serviceStatusIcon'); if (serviceStatusElement && serviceStatusIcon) { if (status.service_active) { serviceStatusElement.textContent = 'Running'; serviceStatusIcon.className = 'status-icon success'; serviceStatusIcon.innerHTML = '▶'; } else { serviceStatusElement.textContent = 'Stopped'; serviceStatusIcon.className = 'status-icon error'; serviceStatusIcon.innerHTML = '⏹'; } } // Update configuration status const configStatusElement = document.getElementById('configStatus'); const configStatusIcon = document.getElementById('configStatusIcon'); if (configStatusElement && configStatusIcon) { if (status.config_exists && status.certificates_exist) { configStatusElement.textContent = 'Configured'; configStatusIcon.className = 'status-icon success'; configStatusIcon.innerHTML = '⚙'; } else if (status.config_exists) { configStatusElement.textContent = 'Partial'; configStatusIcon.className = 'status-icon warning'; configStatusIcon.innerHTML = '⚠'; } else { configStatusElement.textContent = 'Not Configured'; configStatusIcon.className = 'status-icon error'; configStatusIcon.innerHTML = '✗'; } } } async startEnrollment() { if (this.enrollmentInProgress) { return; } const hashKeyInput = document.getElementById('hashKey'); const apiEndpointInput = document.getElementById('apiEndpoint'); const enrollBtn = document.getElementById('enrollBtn'); // Validate inputs const hashKeyValid = this.validateHashKey(hashKeyInput); const apiEndpointValid = this.validateApiEndpoint(apiEndpointInput); if (!hashKeyValid || !apiEndpointValid) { this.showAlert('Please fix the validation errors before proceeding', 'error'); return; } // Start enrollment process this.enrollmentInProgress = true; // Update UI enrollBtn.disabled = true; enrollBtn.innerHTML = 'Enrolling Router...'; // Show enrollment status this.showEnrollmentStatus(); this.clearLogs(); this.updateStatusText('Starting enrollment process...'); try { const formData = new FormData(); formData.append('action', 'enroll'); formData.append('hashKey', hashKeyInput.value.trim()); formData.append('apiEndpoint', apiEndpointInput.value.trim()); formData.append('csrf_token', document.querySelector('input[name="csrf_token"]').value); const response = await fetch('dashboard.php', { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const result = await response.json(); if (result.success) { this.updateStatusText('Enrollment completed successfully!'); this.addLogEntry('success', `Router '${result.routerName}' enrolled successfully`); this.showAlert(`Router enrollment completed successfully! Router: ${result.routerName}`, 'success'); // Refresh system status after successful enrollment setTimeout(() => { this.loadSystemStatus(); }, 2000); } else { throw new Error(result.error || 'Enrollment failed'); } } catch (error) { console.error('Enrollment failed:', error); this.updateStatusText('Enrollment failed'); this.addLogEntry('error', `Enrollment failed: ${error.message}`); this.showAlert(`Enrollment failed: ${error.message}`, 'error'); } finally { this.enrollmentInProgress = false; enrollBtn.disabled = false; enrollBtn.innerHTML = 'Start Enrollment'; } } showEnrollmentStatus() { const enrollmentStatus = document.getElementById('enrollmentStatus'); if (enrollmentStatus) { enrollmentStatus.style.display = 'block'; } } updateStatusText(text) { const statusText = document.getElementById('statusText'); if (statusText) { statusText.textContent = text; } } addLogEntry(type, message) { const logContainer = document.getElementById('logContainer'); if (!logContainer) return; const timestamp = new Date().toLocaleTimeString(); const logEntry = document.createElement('div'); logEntry.className = `log-entry ${type}`; logEntry.textContent = `[${timestamp}] ${message}`; logContainer.appendChild(logEntry); logContainer.scrollTop = logContainer.scrollHeight; } clearLogs() { const logContainer = document.getElementById('logContainer'); if (logContainer) { logContainer.innerHTML = ''; } } showAlert(message, type = 'info') { // Remove existing alerts const existingAlerts = document.querySelectorAll('.alert'); existingAlerts.forEach(alert => alert.remove()); // Create new alert const alert = document.createElement('div'); alert.className = `alert alert-${type}`; alert.textContent = message; // Insert at the top of main content const mainContent = document.querySelector('.main-content'); if (mainContent) { mainContent.insertBefore(alert, mainContent.firstChild); } // Auto-remove after 5 seconds for non-error alerts if (type !== 'error') { setTimeout(() => { if (alert.parentNode) { alert.remove(); } }, 5000); } } } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.enrollmentUI = new EnrollmentUI(); }); // Add CSS classes for input validation and spinner const style = document.createElement('style'); style.textContent = ` .form-input.is-valid { border-color: var(--success-color); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); } .form-input.is-invalid { border-color: var(--error-color); box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1); } .valid-feedback { color: var(--success-color); } .invalid-feedback { color: var(--error-color); } .enrollment-status { margin-top: 2rem; padding: 1.5rem; background: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef; } .status-message { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; font-weight: 500; color: #495057; } .spinner { width: 20px; height: 20px; border: 2px solid #e9ecef; border-top: 2px solid #007bff; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .log-container { background: #1e1e1e; color: #ffffff; padding: 1rem; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 0.875rem; max-height: 300px; overflow-y: auto; line-height: 1.4; } .log-entry { margin-bottom: 0.25rem; } .log-entry.success { color: #28a745; } .log-entry.error { color: #dc3545; } .log-entry.info { color: #17a2b8; } `; document.head.appendChild(style);