zitinexus-router-script/UI/assets/js/app.js

416 lines
14 KiB
JavaScript

/**
* 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 = '<span class="spinner"></span>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);