467 lines
15 KiB
JavaScript
467 lines
15 KiB
JavaScript
/**
|
|
* 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 = '<span class="spinner"></span>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);
|