/** * ZitiNexus Router Enrollment UI JavaScript */ class EnrollmentUI { constructor() { this.enrollmentInProgress = false; this.progressSteps = [ 'INIT', 'REQUIREMENTS', 'INSTALL', 'DIRECTORIES', 'REGISTER', 'CONFIG', 'ENROLL', 'SERVICE', 'START', 'REPORT', 'COMPLETE' ]; this.currentStep = 0; 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.loadSystemStatus(); }); } // 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'); } } 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; this.currentStep = 0; // Update UI enrollBtn.disabled = true; enrollBtn.innerHTML = 'Starting Enrollment...'; this.showProgressContainer(); this.clearLogs(); this.updateProgress(0, 'Initializing...'); 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.updateProgress(100, '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.updateProgress(null, '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'; } } showProgressContainer() { const progressContainer = document.getElementById('progressContainer'); if (progressContainer) { progressContainer.classList.add('active'); } } updateProgress(percentage, message) { // Update progress bar const progressFill = document.getElementById('progressFill'); if (progressFill && percentage !== null) { progressFill.style.width = `${percentage}%`; } // Update current step if (message) { this.addLogEntry('info', message); } // Update progress steps this.updateProgressSteps(); } updateProgressSteps() { const progressSteps = document.querySelectorAll('.progress-step'); progressSteps.forEach((step, index) => { step.classList.remove('active', 'completed', 'error'); if (index < this.currentStep) { step.classList.add('completed'); } else if (index === this.currentStep) { step.classList.add('active'); } }); } 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); } } // Utility method to format file sizes formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Utility method to format timestamps formatTimestamp(timestamp) { return new Date(timestamp * 1000).toLocaleString(); } } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.enrollmentUI = new EnrollmentUI(); }); // Add CSS classes for input validation 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); } `; document.head.appendChild(style);