diff --git a/UI/DEVELOPMENT_GUIDELINES.md b/UI/DEVELOPMENT_GUIDELINES.md
new file mode 100644
index 0000000..19ea76a
--- /dev/null
+++ b/UI/DEVELOPMENT_GUIDELINES.md
@@ -0,0 +1,209 @@
+# ZitiNexus Router Enrollment UI - Development Guidelines
+
+This document establishes clear guidelines for maintaining a clean, organized codebase and proper file structure.
+
+## ๐ **Project Structure**
+
+### **Current Clean Structure:**
+```
+UI/
+โโโ includes/ # Backend PHP classes and utilities
+โ โโโ auth.php # Authentication management
+โ โโโ config.php # Configuration and utilities
+โ โโโ enrollment.php # Main enrollment logic
+โ โโโ api_client.php # API communication
+โโโ public/ # Web-accessible files (DOCUMENT ROOT)
+โ โโโ index.php # Login page
+โ โโโ dashboard.php # Main dashboard
+โ โโโ progress.php # Progress polling endpoint
+โ โโโ assets/ # Static assets
+โ โโโ css/ # Stylesheets
+โ โโโ js/ # JavaScript files
+โ โโโ images/ # Images and icons
+โโโ test/ # Testing and debugging files
+โโโ logs/ # Log files (.gitkeep)
+โโโ temp/ # Temporary files (.gitkeep)
+โโโ *.md # Documentation files
+```
+
+## ๐ฏ **File Location Rules**
+
+### **Rule 1: Public Directory is Primary**
+- **ALL user-accessible files MUST be in `public/` directory**
+- Web server document root should point to `UI/public/`
+- Never create duplicate files in main UI directory
+
+### **Rule 2: Single Source of Truth**
+- **Dashboard**: Only `public/dashboard.php` exists
+- **Index/Login**: Only `public/index.php` exists
+- **Progress API**: Only `public/progress.php` exists
+- **Assets**: Only `public/assets/` directory exists
+
+### **Rule 3: Backend Logic Separation**
+- **PHP Classes**: Always in `includes/` directory
+- **Configuration**: Always in `includes/config.php`
+- **Authentication**: Always in `includes/auth.php`
+- **Business Logic**: Always in `includes/enrollment.php`
+
+### **Rule 4: Testing and Debug Files**
+- **ALL testing files**: Must be in `test/` directory
+- **Debug scripts**: Must be in `test/` directory
+- **Temporary test files**: Must be in `test/` directory
+- **Never leave debug files in main directories**
+
+## ๐ซ **What NOT to Do**
+
+### **โ NEVER Create Duplicate Files:**
+```
+โ UI/dashboard.php AND UI/public/dashboard.php
+โ UI/index.php AND UI/public/index.php
+โ UI/assets/ AND UI/public/assets/
+โ UI/progress.php AND UI/public/progress.php
+```
+
+### **โ NEVER Put Debug Files in Main Directories:**
+```
+โ UI/debug-*.php
+โ UI/test-*.php
+โ UI/public/debug-*.php
+โ UI/public/test-*.php
+```
+
+### **โ NEVER Mix Production and Test Code:**
+```
+โ Debug code in production files
+โ Test endpoints in production files
+โ Console.log statements in production JavaScript
+```
+
+## โ
**Correct Development Workflow**
+
+### **When Adding New Features:**
+
+1. **Backend Logic**: Add to appropriate file in `includes/`
+2. **Frontend Pages**: Create/modify files in `public/`
+3. **Assets**: Add to `public/assets/`
+4. **Testing**: Create test files in `test/`
+
+### **When Creating New Pages:**
+
+1. **Create in `public/` directory ONLY**
+2. **Use relative paths for includes**: `require_once '../includes/auth.php'`
+3. **Use relative paths for assets**: `href="assets/css/style.css"`
+4. **Test thoroughly before committing**
+
+### **When Debugging:**
+
+1. **Create debug files in `test/` directory**
+2. **Use descriptive names**: `test/debug-enrollment-flow.php`
+3. **Remove or move to test after debugging**
+4. **Never commit debug files to main directories**
+
+## ๐ง **Path Reference Guide**
+
+### **For Files in `public/` Directory:**
+
+```php
+// Correct includes from public files
+require_once '../includes/auth.php';
+require_once '../includes/enrollment.php';
+require_once '../includes/config.php';
+
+// Correct asset paths in public files
+
+
+```
+
+### **For Files in `test/` Directory:**
+
+```php
+// Correct includes from test files
+require_once '../includes/auth.php';
+require_once '../includes/enrollment.php';
+
+// Correct asset paths in test files (if needed)
+
+```
+
+## ๐ **Code Review Checklist**
+
+### **Before Committing:**
+
+- [ ] No duplicate files between main and public directories
+- [ ] All debug/test files are in `test/` directory
+- [ ] All user-accessible files are in `public/` directory
+- [ ] All includes use correct relative paths
+- [ ] All asset references use correct paths
+- [ ] No console.log or debug statements in production code
+- [ ] File structure follows established conventions
+
+### **File Naming Conventions:**
+
+- **Production files**: `dashboard.php`, `index.php`, `progress.php`
+- **Test files**: `test-feature-name.php`, `debug-issue-name.php`
+- **Documentation**: `FEATURE_NAME.md` (uppercase)
+- **Scripts**: `script-name.sh` (lowercase with hyphens)
+
+## ๐ฏ **Development Best Practices**
+
+### **1. Single Responsibility**
+- Each file should have a single, clear purpose
+- Separate concerns between frontend and backend
+- Keep business logic in `includes/` directory
+
+### **2. Consistent Structure**
+- Always follow the established directory structure
+- Use consistent naming conventions
+- Maintain clear separation between production and test code
+
+### **3. Clean Codebase**
+- Remove unused files regularly
+- Keep test files organized in `test/` directory
+- Document any deviations from standard structure
+
+### **4. Path Management**
+- Use relative paths consistently
+- Test paths work from intended directory structure
+- Avoid hardcoded absolute paths
+
+## ๐จ **Common Mistakes to Avoid**
+
+1. **Creating files in wrong directory**
+ - Always create user-facing files in `public/`
+ - Always create backend logic in `includes/`
+
+2. **Leaving debug files in production directories**
+ - Move all debug files to `test/` directory
+ - Clean up after debugging sessions
+
+3. **Inconsistent path references**
+ - Use correct relative paths based on file location
+ - Test all includes and asset references
+
+4. **Duplicate functionality**
+ - Avoid creating multiple versions of same file
+ - Maintain single source of truth for each feature
+
+## ๐ **Quick Reference**
+
+### **File Locations:**
+- **User Pages**: `public/` only
+- **Backend Logic**: `includes/` only
+- **Assets**: `public/assets/` only
+- **Tests/Debug**: `test/` only
+- **Documentation**: Root UI directory
+
+### **Include Paths from `public/`:**
+```php
+require_once '../includes/auth.php';
+require_once '../includes/config.php';
+require_once '../includes/enrollment.php';
+```
+
+### **Asset Paths from `public/`:**
+```html
+
+
+```
+
+Following these guidelines ensures a clean, maintainable, and professional codebase that's easy to understand and extend.
diff --git a/UI/PROGRESS_TRACKING_GUIDE.md b/UI/PROGRESS_TRACKING_GUIDE.md
new file mode 100644
index 0000000..42b52f9
--- /dev/null
+++ b/UI/PROGRESS_TRACKING_GUIDE.md
@@ -0,0 +1,191 @@
+# Progress Tracking and System Status Enhancement Guide
+
+This document describes the improvements made to the ZitiNexus Router Enrollment UI to fix progress tracking and system status checking issues.
+
+## ๐ง Issues Fixed
+
+### Issue 1: Progress UI Always Shows "Initializing"
+**Problem**: The enrollment progress UI would always show "Initializing" throughout the entire enrollment process, providing no real-time feedback to users.
+
+**Root Cause**: The original implementation used a single synchronous AJAX call that waited for the entire enrollment to complete before returning results. No progress streaming mechanism existed.
+
+**Solution**: Implemented real-time progress tracking using AJAX polling:
+- Progress data is stored in PHP sessions during enrollment
+- JavaScript polls a progress endpoint every 500ms
+- UI updates in real-time showing current step, percentage, and logs
+
+### Issue 2: System Status Not Actively Checking After Enrollment
+**Problem**: System status would show outdated information, especially after package removal/purge operations. The status checking was not thorough enough.
+
+**Root Cause**:
+- Status checking only verified if commands existed, not if packages were properly installed
+- No real-time verification of package installation status
+- Cache issues prevented fresh status checks
+
+**Solution**: Enhanced system status checking:
+- Added package detection via `dpkg` commands
+- Implemented real-time status refresh functionality
+- Better certificate checking using sudo for root-owned files
+- Force refresh capabilities to clear caches
+
+## ๐ New Features
+
+### Real-Time Progress Tracking
+
+#### Files Added/Modified:
+- `UI/progress.php` - Progress polling endpoint
+- `UI/public/progress.php` - Public directory version
+- `UI/includes/enrollment.php` - Enhanced with session-based progress tracking
+- `UI/public/assets/js/app.js` - Added AJAX polling functionality
+- `UI/dashboard.php` - Added progress management endpoints
+
+#### How It Works:
+1. **Enrollment Start**: Progress is initialized in PHP session
+2. **Progress Updates**: Each enrollment step updates session data
+3. **AJAX Polling**: JavaScript polls progress endpoint every 500ms
+4. **UI Updates**: Progress bar, steps, and logs update in real-time
+5. **Completion**: Polling stops when enrollment completes or fails
+
+#### Progress Data Structure:
+```php
+$_SESSION['enrollment_progress'] = [
+ 'step' => 'CURRENT_STEP', // Current step name
+ 'message' => 'Step description', // Current step message
+ 'percentage' => 50, // Progress percentage (0-100)
+ 'completed_steps' => ['INIT', ...], // Array of completed steps
+ 'current_step_index' => 4, // Current step index
+ 'status' => 'running', // ready, running, completed, error
+ 'error' => null, // Error message if failed
+ 'logs' => [ // Array of log entries
+ [
+ 'timestamp' => '12:34:56',
+ 'step' => 'INSTALL',
+ 'message' => 'Installing...',
+ 'type' => 'info'
+ ]
+ ]
+];
+```
+
+### Enhanced System Status Checking
+
+#### New Methods Added:
+- `checkZitiInstallation()` - Comprehensive package and command checking
+- `checkServiceStatus()` - Thorough service status verification
+- `checkCertificatesExist()` - Sudo-based certificate checking
+- `refreshSystemStatus()` - Force refresh with cache clearing
+
+#### Enhanced Status Data:
+```php
+$status = [
+ 'hostname' => 'server-name',
+ 'ziti_status' => 'installed', // installed, not_installed, broken
+ 'ziti_version' => 'v1.5.4',
+ 'service_active' => true,
+ 'config_exists' => true,
+ 'certificates_exist' => true,
+ 'package_installed' => true, // NEW: dpkg package check
+ 'last_checked' => 1640995200 // NEW: timestamp
+];
+```
+
+## ๐ API Endpoints
+
+### Progress Tracking
+- `GET /progress.php` - Get current enrollment progress
+- `GET /dashboard.php?action=clear_progress` - Clear progress data
+
+### System Status
+- `GET /dashboard.php?action=get_status` - Get current system status
+- `GET /dashboard.php?action=refresh_status` - Force refresh system status
+
+## ๐ฏ User Experience Improvements
+
+### Before:
+- โ Progress always showed "Initializing"
+- โ No real-time feedback during enrollment
+- โ System status could show stale data
+- โ No way to force refresh status
+
+### After:
+- โ
Real-time progress updates every 500ms
+- โ
Visual progress bar and step indicators
+- โ
Live log streaming during enrollment
+- โ
Enhanced system status with package verification
+- โ
Force refresh button for system status
+- โ
Better error handling and user feedback
+
+## ๐ Technical Implementation Details
+
+### Progress Polling Architecture:
+```
+User clicks "Start Enrollment"
+ โ
+JavaScript starts AJAX polling (500ms interval)
+ โ
+PHP enrollment process updates session progress
+ โ
+JavaScript fetches progress and updates UI
+ โ
+Process continues until completion/error
+ โ
+Polling stops automatically
+```
+
+### System Status Enhancement:
+```
+User clicks "Refresh Status"
+ โ
+JavaScript calls refresh_status endpoint
+ โ
+PHP clears caches and runs comprehensive checks
+ โ
+Enhanced status data returned to UI
+ โ
+UI updates with fresh system information
+```
+
+## ๐ ๏ธ Maintenance Notes
+
+### Session Management:
+- Progress data is automatically cleaned up after completion
+- Session data is limited to last 50 log entries to prevent memory issues
+- Progress can be manually cleared via API endpoint
+
+### Performance Considerations:
+- Polling interval is 500ms (configurable)
+- Polling automatically stops when not needed
+- System status auto-refreshes every 30 seconds (when not enrolling)
+
+### Error Handling:
+- Progress polling continues even if individual requests fail
+- System status gracefully handles command failures
+- User feedback provided for all error conditions
+
+## ๐ง Configuration Options
+
+### JavaScript Polling Interval:
+```javascript
+// In app.js, modify this value to change polling frequency
+this.progressPollingInterval = setInterval(async () => {
+ // ... polling logic
+}, 500); // 500ms = 0.5 seconds
+```
+
+### Session Progress Cleanup:
+```php
+// In enrollment.php, modify log retention
+if (count($progress['logs']) > 50) {
+ $progress['logs'] = array_slice($progress['logs'], -50);
+}
+```
+
+## ๐ Benefits
+
+1. **Better User Experience**: Users now see real-time progress instead of waiting blindly
+2. **Accurate System Status**: Enhanced checking provides reliable system state information
+3. **Improved Debugging**: Live logs help identify issues during enrollment
+4. **Professional Feel**: Progress indicators make the system feel more responsive
+5. **Reliability**: Better error handling and status verification improve overall reliability
+
+This enhancement transforms the enrollment experience from a "black box" operation to a transparent, user-friendly process with real-time feedback and accurate system monitoring.
diff --git a/UI/README.md b/UI/README.md
index dc34c34..7594281 100644
--- a/UI/README.md
+++ b/UI/README.md
@@ -335,20 +335,23 @@ This UI is fully compatible with the ZitiNexus Portal API and replicates all fun
### File Structure
```
UI/
-โโโ public/
+โโโ public/ # Web-accessible files (DOCUMENT ROOT)
โ โโโ index.php # Login page
-โ โโโ dashboard.php # Main dashboard
-โโโ includes/
+โ โโโ dashboard.php # Main dashboard
+โ โโโ progress.php # Progress polling endpoint
+โ โโโ assets/ # Static assets (CSS, JS, images)
+โ โโโ css/style.css # Styling
+โ โโโ js/app.js # Frontend JavaScript
+โโโ includes/ # Backend PHP classes and utilities
โ โโโ config.php # Configuration and utilities
โ โโโ auth.php # Authentication handler
โ โโโ api_client.php # API communication
โ โโโ enrollment.php # Core enrollment logic
-โโโ assets/
-โ โโโ css/style.css # Styling
-โ โโโ js/app.js # Frontend JavaScript
+โโโ test/ # Testing and debugging files
โโโ logs/ # UI-specific logs
โโโ temp/ # Temporary files
-โโโ README.md # This file
+โโโ *.sh # Shell scripts (install, troubleshoot, etc.)
+โโโ *.md # Documentation files
```
### Contributing
diff --git a/UI/dashboard.php b/UI/dashboard.php
deleted file mode 100644
index d3f5218..0000000
--- a/UI/dashboard.php
+++ /dev/null
@@ -1,287 +0,0 @@
-getSystemStatus();
- echo json_encode($status);
- exit;
- }
-
- if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'enroll') {
- AuthManager::requireCSRF();
-
- $hashKey = sanitizeInput($_POST['hashKey'] ?? '');
- $apiEndpoint = sanitizeInput($_POST['apiEndpoint'] ?? DEFAULT_API_ENDPOINT);
-
- if (!ApiClient::validateHashKey($hashKey)) {
- echo json_encode(['success' => false, 'error' => 'Invalid hash key format']);
- exit;
- }
-
- if (!ApiClient::validateApiEndpoint($apiEndpoint)) {
- echo json_encode(['success' => false, 'error' => 'Invalid API endpoint format']);
- exit;
- }
-
- $result = $enrollmentManager->enrollRouter($hashKey, $apiEndpoint);
- echo json_encode($result);
- exit;
- }
-}
-
-$systemStatus = $enrollmentManager->getSystemStatus();
-?>
-
-
-
-
-
- - Dashboard
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Ziti CLI Status
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Initialize
-
Requirements
-
Install
-
Directories
-
Register
-
Configure
-
Enroll
-
Service
-
Start
-
Report
-
Complete
-
-
-
-
-
-
-
-
-
-
-
-
-
-
How to Use
-
- - Obtain a hash key from the ZitiNexus Portal by creating a router enrollment
- - Enter the API endpoint (default is pre-filled)
- - Paste the 32-character hash key
- - Click "Start Enrollment" to begin the process
- - Monitor the progress and logs for status updates
-
-
-
-
-
System Requirements
-
- - Ubuntu 22.04 or 24.04 LTS
- - Root/sudo access required
- - Internet connectivity
- - systemctl available
- - curl and jq packages (auto-installed)
-
-
-
-
-
File Locations
-
- /etc/zitirouter/ - Configuration directory
- /etc/zitirouter/certs/ - Certificates
- /var/log/ziti-router-enrollment.log - Enrollment log
- /etc/systemd/system/ziti-router.service - Service file
-
-
-
-
-
Service Management
-
- systemctl status ziti-router - Check status
- systemctl start ziti-router - Start service
- systemctl stop ziti-router - Stop service
- journalctl -u ziti-router -f - View logs
-
-
-
-
-
-
-
-
-
-
-
diff --git a/UI/includes/enrollment.php b/UI/includes/enrollment.php
index dd0ae1f..0f0f919 100644
--- a/UI/includes/enrollment.php
+++ b/UI/includes/enrollment.php
@@ -30,16 +30,118 @@ class EnrollmentManager {
private function reportProgress($step, $message, $percentage = null) {
logMessage('INFO', "[$step] $message");
+ // Store progress in session for AJAX polling
+ $this->updateSessionProgress($step, $message, $percentage);
+
if ($this->progressCallback && is_callable($this->progressCallback)) {
call_user_func($this->progressCallback, $step, $message, $percentage);
}
}
+ /**
+ * Update session progress for AJAX polling
+ */
+ private function updateSessionProgress($step, $message, $percentage = null) {
+ $steps = [
+ 'INIT' => 0, 'REQUIREMENTS' => 1, 'INSTALL' => 2, 'DIRECTORIES' => 3,
+ 'REGISTER' => 4, 'CONFIG' => 5, 'ENROLL' => 6, 'SERVICE' => 7,
+ 'START' => 8, 'REPORT' => 9, 'COMPLETE' => 10, 'ERROR' => -1
+ ];
+
+ $currentStepIndex = $steps[$step] ?? 0;
+
+ // Initialize progress if not exists
+ if (!isset($_SESSION['enrollment_progress'])) {
+ $_SESSION['enrollment_progress'] = [
+ 'step' => 'INIT',
+ 'message' => 'Ready to start enrollment',
+ 'percentage' => 0,
+ 'completed_steps' => [],
+ 'current_step_index' => 0,
+ 'status' => 'ready',
+ 'error' => null,
+ 'logs' => []
+ ];
+ }
+
+ $progress = &$_SESSION['enrollment_progress'];
+
+ // Update progress data
+ $progress['step'] = $step;
+ $progress['message'] = $message;
+ $progress['current_step_index'] = $currentStepIndex;
+
+ if ($percentage !== null) {
+ $progress['percentage'] = $percentage;
+ }
+
+ // Update status
+ if ($step === 'ERROR') {
+ $progress['status'] = 'error';
+ $progress['error'] = $message;
+ } elseif ($step === 'COMPLETE') {
+ $progress['status'] = 'completed';
+ $progress['percentage'] = 100;
+ } else {
+ $progress['status'] = 'running';
+ }
+
+ // Add completed steps
+ if ($currentStepIndex > 0 && $step !== 'ERROR') {
+ $stepNames = array_keys($steps);
+ for ($i = 0; $i < $currentStepIndex; $i++) {
+ if (!in_array($stepNames[$i], $progress['completed_steps'])) {
+ $progress['completed_steps'][] = $stepNames[$i];
+ }
+ }
+ }
+
+ // Add log entry
+ $timestamp = date('H:i:s');
+ $progress['logs'][] = [
+ 'timestamp' => $timestamp,
+ 'step' => $step,
+ 'message' => $message,
+ 'type' => $step === 'ERROR' ? 'error' : 'info'
+ ];
+
+ // Keep only last 50 log entries
+ if (count($progress['logs']) > 50) {
+ $progress['logs'] = array_slice($progress['logs'], -50);
+ }
+ }
+
+ /**
+ * Initialize progress tracking
+ */
+ private function initializeProgress() {
+ $_SESSION['enrollment_progress'] = [
+ 'step' => 'INIT',
+ 'message' => 'Starting router enrollment process...',
+ 'percentage' => 0,
+ 'completed_steps' => [],
+ 'current_step_index' => 0,
+ 'status' => 'running',
+ 'error' => null,
+ 'logs' => []
+ ];
+ }
+
+ /**
+ * Clear progress tracking
+ */
+ public function clearProgress() {
+ unset($_SESSION['enrollment_progress']);
+ }
+
/**
* Main enrollment process
*/
public function enrollRouter($hashKey, $apiEndpoint = null) {
try {
+ // Initialize progress tracking
+ $this->initializeProgress();
+
if ($apiEndpoint) {
$this->apiClient = new ApiClient($apiEndpoint);
}
@@ -527,7 +629,7 @@ EOF;
}
/**
- * Get system status information
+ * Get system status information with enhanced package detection
*/
public function getSystemStatus() {
$status = [
@@ -536,38 +638,130 @@ EOF;
'ziti_version' => '',
'service_active' => false,
'config_exists' => false,
- 'certificates_exist' => false
+ 'certificates_exist' => false,
+ 'package_installed' => false,
+ 'last_checked' => time()
];
// Get hostname
executeCommand('hostname', $status['hostname']);
$status['hostname'] = trim($status['hostname']);
- // Check if ziti command exists and get version
- if ($this->checkCommand('ziti')) {
- executeCommand('ziti version 2>/dev/null | head -n1', $status['ziti_version']);
- $status['ziti_version'] = trim($status['ziti_version']);
- $status['ziti_status'] = 'installed';
- } else {
- $status['ziti_status'] = 'not_installed';
- }
+ // Enhanced Ziti installation check
+ $zitiInstallationStatus = $this->checkZitiInstallation();
+ $status['ziti_status'] = $zitiInstallationStatus['status'];
+ $status['ziti_version'] = $zitiInstallationStatus['version'];
+ $status['package_installed'] = $zitiInstallationStatus['package_installed'];
- // Check service status
- $output = '';
- if (executeCommand('systemctl is-active ziti-router.service 2>/dev/null', $output)) {
- $status['service_active'] = trim($output) === 'active';
- }
+ // Check service status with more thorough checking
+ $serviceStatus = $this->checkServiceStatus();
+ $status['service_active'] = $serviceStatus['active'];
// Check configuration files
$status['config_exists'] = file_exists(ROUTER_CONFIG);
- // Check certificates
- if (is_dir(CERTS_DIR)) {
- $certFiles = glob(CERTS_DIR . '/*.cert');
- $status['certificates_exist'] = !empty($certFiles);
- }
+ // Check certificates with sudo (since they're root-owned)
+ $status['certificates_exist'] = $this->checkCertificatesExist();
return $status;
}
+
+ /**
+ * Enhanced Ziti installation checking
+ */
+ private function checkZitiInstallation() {
+ $result = [
+ 'status' => 'not_installed',
+ 'version' => '',
+ 'package_installed' => false
+ ];
+
+ // Check if package is installed via dpkg
+ $dpkgOutput = '';
+ $packageInstalled = executeCommand('dpkg -l | grep -E "(openziti-router|ziti)" | grep -v "^rc"', $dpkgOutput);
+
+ if ($packageInstalled && !empty(trim($dpkgOutput))) {
+ $result['package_installed'] = true;
+
+ // Extract version from dpkg output if available
+ if (preg_match('/(\d+\.\d+\.\d+)/', $dpkgOutput, $matches)) {
+ $result['version'] = $matches[1];
+ }
+ }
+
+ // Check if command works
+ $versionOutput = '';
+ $commandWorks = executeCommand('ziti version 2>/dev/null | head -n1', $versionOutput);
+
+ if ($commandWorks && !empty(trim($versionOutput))) {
+ $result['status'] = 'installed';
+
+ // Use command output version if we don't have one from dpkg
+ if (empty($result['version'])) {
+ $result['version'] = trim($versionOutput);
+ }
+ } else if ($result['package_installed']) {
+ // Package is installed but command doesn't work - might be broken
+ $result['status'] = 'broken';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Enhanced service status checking
+ */
+ private function checkServiceStatus() {
+ $result = [
+ 'active' => false,
+ 'enabled' => false,
+ 'status' => 'unknown'
+ ];
+
+ // Check if service is active
+ $activeOutput = '';
+ if (executeCommand('systemctl is-active ziti-router.service 2>/dev/null', $activeOutput)) {
+ $result['active'] = trim($activeOutput) === 'active';
+ $result['status'] = trim($activeOutput);
+ }
+
+ // Check if service is enabled
+ $enabledOutput = '';
+ if (executeCommand('systemctl is-enabled ziti-router.service 2>/dev/null', $enabledOutput)) {
+ $result['enabled'] = trim($enabledOutput) === 'enabled';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Check if certificates exist (using sudo since they're root-owned)
+ */
+ private function checkCertificatesExist() {
+ if (!is_dir(CERTS_DIR)) {
+ return false;
+ }
+
+ // Use sudo to list certificate files since they're root-owned with 600 permissions
+ $output = '';
+ if (executeCommand("find " . CERTS_DIR . " -name '*.cert' -type f 2>/dev/null", $output)) {
+ return !empty(trim($output));
+ }
+
+ return false;
+ }
+
+ /**
+ * Force refresh system status (clear any caches)
+ */
+ public function refreshSystemStatus() {
+ // Clear any potential caches
+ clearstatcache();
+
+ // Update package database
+ executeCommand('apt-get update -qq 2>/dev/null');
+
+ return $this->getSystemStatus();
+ }
}
?>
diff --git a/UI/index.php b/UI/index.php
deleted file mode 100644
index 7ecacf8..0000000
--- a/UI/index.php
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
-
-
- - Login
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Default credentials: admin / admin123
-
-
- Please change the default password in production
-
-
-
-
-
-
diff --git a/UI/public/assets/js/app.js b/UI/public/assets/js/app.js
index 7e7df45..43b059a 100644
--- a/UI/public/assets/js/app.js
+++ b/UI/public/assets/js/app.js
@@ -10,6 +10,7 @@ class EnrollmentUI {
'REGISTER', 'CONFIG', 'ENROLL', 'SERVICE', 'START', 'REPORT', 'COMPLETE'
];
this.currentStep = 0;
+ this.progressPollingInterval = null;
this.init();
}
@@ -55,7 +56,7 @@ class EnrollmentUI {
const refreshBtn = document.getElementById('refreshStatus');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
- this.loadSystemStatus();
+ this.refreshSystemStatus();
});
}
@@ -155,6 +156,39 @@ class EnrollmentUI {
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
@@ -241,7 +275,12 @@ class EnrollmentUI {
this.showProgressContainer();
this.clearLogs();
- this.updateProgress(0, 'Initializing...');
+
+ // Clear any existing progress
+ await this.clearProgress();
+
+ // Start progress polling
+ this.startProgressPolling();
try {
const formData = new FormData();
@@ -250,6 +289,7 @@ class EnrollmentUI {
formData.append('apiEndpoint', apiEndpointInput.value.trim());
formData.append('csrf_token', document.querySelector('input[name="csrf_token"]').value);
+ // Start the enrollment process (this will run in background)
const response = await fetch('dashboard.php', {
method: 'POST',
body: formData,
@@ -265,8 +305,6 @@ class EnrollmentUI {
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
@@ -279,15 +317,150 @@ class EnrollmentUI {
} 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');
+ this.stopProgressPolling();
} finally {
this.enrollmentInProgress = false;
enrollBtn.disabled = false;
enrollBtn.innerHTML = 'Start Enrollment';
}
}
+
+ /**
+ * Start polling for progress updates
+ */
+ startProgressPolling() {
+ // Clear any existing polling
+ this.stopProgressPolling();
+
+ // Poll every 500ms for progress updates
+ this.progressPollingInterval = setInterval(async () => {
+ try {
+ const response = await fetch('progress.php', {
+ method: 'GET',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
+ });
+
+ if (response.ok) {
+ const progress = await response.json();
+ this.updateProgressFromPolling(progress);
+
+ // Stop polling if enrollment is complete or failed
+ if (progress.status === 'completed' || progress.status === 'error') {
+ this.stopProgressPolling();
+ }
+ }
+ } catch (error) {
+ console.error('Progress polling error:', error);
+ }
+ }, 500);
+ }
+
+ /**
+ * Stop progress polling
+ */
+ stopProgressPolling() {
+ if (this.progressPollingInterval) {
+ clearInterval(this.progressPollingInterval);
+ this.progressPollingInterval = null;
+ }
+ }
+
+ /**
+ * Update UI based on progress polling data
+ */
+ updateProgressFromPolling(progress) {
+ // Update progress bar
+ const progressFill = document.getElementById('progressFill');
+ if (progressFill && progress.percentage !== undefined) {
+ progressFill.style.width = `${progress.percentage}%`;
+ }
+
+ // Update current step index
+ this.currentStep = progress.current_step_index || 0;
+
+ // Update progress steps
+ this.updateProgressStepsFromData(progress);
+
+ // Add new log entries
+ if (progress.logs && Array.isArray(progress.logs)) {
+ this.updateLogsFromData(progress.logs);
+ }
+
+ // Handle completion or error
+ if (progress.status === 'completed') {
+ this.addLogEntry('success', 'Enrollment completed successfully!');
+ } else if (progress.status === 'error') {
+ this.addLogEntry('error', progress.error || 'Enrollment failed');
+ }
+ }
+
+ /**
+ * Update progress steps based on polling data
+ */
+ updateProgressStepsFromData(progress) {
+ const progressSteps = document.querySelectorAll('.progress-step');
+ const stepNames = ['INIT', 'REQUIREMENTS', 'INSTALL', 'DIRECTORIES', 'REGISTER', 'CONFIG', 'ENROLL', 'SERVICE', 'START', 'REPORT', 'COMPLETE'];
+
+ progressSteps.forEach((stepElement, index) => {
+ stepElement.classList.remove('active', 'completed', 'error');
+
+ const stepName = stepNames[index];
+
+ if (progress.completed_steps && progress.completed_steps.includes(stepName)) {
+ stepElement.classList.add('completed');
+ } else if (progress.current_step_index === index) {
+ stepElement.classList.add('active');
+ }
+
+ if (progress.status === 'error' && progress.current_step_index === index) {
+ stepElement.classList.add('error');
+ }
+ });
+ }
+
+ /**
+ * Update logs from polling data (only add new entries)
+ */
+ updateLogsFromData(logs) {
+ const logContainer = document.getElementById('logContainer');
+ if (!logContainer) return;
+
+ // Get current log count to avoid duplicates
+ const currentLogCount = logContainer.children.length;
+
+ // Add only new log entries
+ for (let i = currentLogCount; i < logs.length; i++) {
+ const log = logs[i];
+ const logEntry = document.createElement('div');
+ logEntry.className = `log-entry ${log.type}`;
+ logEntry.textContent = `[${log.timestamp}] ${log.message}`;
+
+ logContainer.appendChild(logEntry);
+ }
+
+ // Auto-scroll to bottom
+ logContainer.scrollTop = logContainer.scrollHeight;
+ }
+
+ /**
+ * Clear progress data
+ */
+ async clearProgress() {
+ try {
+ await fetch('dashboard.php?action=clear_progress', {
+ method: 'GET',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
+ });
+ } catch (error) {
+ console.error('Failed to clear progress:', error);
+ }
+ }
showProgressContainer() {
const progressContainer = document.getElementById('progressContainer');
diff --git a/UI/public/dashboard.php b/UI/public/dashboard.php
index 30db07c..57765a3 100644
--- a/UI/public/dashboard.php
+++ b/UI/public/dashboard.php
@@ -20,20 +20,29 @@ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQU
header('Content-Type: application/json');
if (isset($_GET['action']) && $_GET['action'] === 'get_status') {
- // Get system status
$status = $enrollmentManager->getSystemStatus();
echo json_encode($status);
exit;
}
+ if (isset($_GET['action']) && $_GET['action'] === 'refresh_status') {
+ $status = $enrollmentManager->refreshSystemStatus();
+ echo json_encode($status);
+ exit;
+ }
+
+ if (isset($_GET['action']) && $_GET['action'] === 'clear_progress') {
+ $enrollmentManager->clearProgress();
+ echo json_encode(['success' => true]);
+ exit;
+ }
+
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'enroll') {
- // Handle enrollment request
AuthManager::requireCSRF();
$hashKey = sanitizeInput($_POST['hashKey'] ?? '');
$apiEndpoint = sanitizeInput($_POST['apiEndpoint'] ?? DEFAULT_API_ENDPOINT);
- // Validate inputs
if (!ApiClient::validateHashKey($hashKey)) {
echo json_encode(['success' => false, 'error' => 'Invalid hash key format']);
exit;
@@ -44,7 +53,6 @@ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQU
exit;
}
- // Start enrollment
$result = $enrollmentManager->enrollRouter($hashKey, $apiEndpoint);
echo json_encode($result);
exit;
diff --git a/UI/public/progress.php b/UI/public/progress.php
new file mode 100644
index 0000000..e0e1617
--- /dev/null
+++ b/UI/public/progress.php
@@ -0,0 +1,23 @@
+ 'INIT',
+ 'message' => 'Ready to start enrollment',
+ 'percentage' => 0,
+ 'completed_steps' => [],
+ 'current_step_index' => 0,
+ 'status' => 'ready', // ready, running, completed, error
+ 'error' => null,
+ 'logs' => []
+];
+
+echo json_encode($progress);
+?>
diff --git a/UI/CLOUDSTACK_FIXES.md b/UI/test/CLOUDSTACK_FIXES.md
similarity index 100%
rename from UI/CLOUDSTACK_FIXES.md
rename to UI/test/CLOUDSTACK_FIXES.md
diff --git a/UI/test/README.md b/UI/test/README.md
new file mode 100644
index 0000000..9427f53
--- /dev/null
+++ b/UI/test/README.md
@@ -0,0 +1,82 @@
+# Test and Debug Files
+
+This directory contains all testing, debugging, and development utility files for the ZitiNexus Router Enrollment UI.
+
+## ๐ **Files in This Directory**
+
+### **Debug Files:**
+- `debug-command-execution.php` - Debug script for testing command execution
+- `debug-command-execution-public.php` - Public version of debug script
+- `debug-paths.php` - Debug script for testing file paths and includes
+
+### **Test Files:**
+- `test-assets.php` - Test script for asset loading and paths
+- `test-php-detection.sh` - Shell script for testing PHP detection
+
+## ๐ฏ **Purpose**
+
+This directory serves as a container for:
+- **Development utilities** that help during coding
+- **Debug scripts** for troubleshooting issues
+- **Test files** for validating functionality
+- **Temporary files** created during development
+
+## ๐ซ **Important Notes**
+
+- **These files are NOT part of the production system**
+- **Never reference these files from production code**
+- **These files may be removed or modified without notice**
+- **Do not rely on these files for production functionality**
+
+## ๐ง **Usage Guidelines**
+
+### **When Creating Test Files:**
+1. Always place them in this directory
+2. Use descriptive names (e.g., `test-enrollment-flow.php`)
+3. Include comments explaining the test purpose
+4. Clean up when no longer needed
+
+### **When Debugging:**
+1. Create debug files here, not in main directories
+2. Use temporary names that won't conflict
+3. Remove debug files after issue is resolved
+4. Document any permanent debug utilities
+
+## ๐ **File Descriptions**
+
+### **debug-command-execution.php**
+- Tests command execution functionality
+- Validates sudo permissions
+- Checks system command availability
+
+### **debug-paths.php**
+- Tests include paths and file references
+- Validates asset loading
+- Checks directory permissions
+
+### **test-assets.php**
+- Tests CSS and JavaScript loading
+- Validates asset paths from different locations
+- Checks for missing assets
+
+### **test-php-detection.sh**
+- Shell script for testing PHP installation
+- Validates PHP version and modules
+- Tests web server configuration
+
+## ๐งน **Maintenance**
+
+This directory should be cleaned regularly:
+- Remove obsolete test files
+- Archive useful debug scripts
+- Update documentation as needed
+- Keep only actively used utilities
+
+## โ ๏ธ **Security Note**
+
+Some files in this directory may contain:
+- Debug output that reveals system information
+- Test credentials or keys
+- Sensitive configuration details
+
+**Never deploy this directory to production servers.**
diff --git a/UI/public/debug-command-execution.php b/UI/test/debug-command-execution-public.php
similarity index 100%
rename from UI/public/debug-command-execution.php
rename to UI/test/debug-command-execution-public.php
diff --git a/UI/debug-command-execution.php b/UI/test/debug-command-execution.php
similarity index 100%
rename from UI/debug-command-execution.php
rename to UI/test/debug-command-execution.php
diff --git a/UI/debug-paths.php b/UI/test/debug-paths.php
similarity index 100%
rename from UI/debug-paths.php
rename to UI/test/debug-paths.php
diff --git a/UI/fix-permissions.sh b/UI/test/fix-permissions.sh
similarity index 100%
rename from UI/fix-permissions.sh
rename to UI/test/fix-permissions.sh
diff --git a/UI/pre-enrollment-check.php b/UI/test/pre-enrollment-check.php
similarity index 100%
rename from UI/pre-enrollment-check.php
rename to UI/test/pre-enrollment-check.php
diff --git a/UI/quick-fix-cloudstack.sh b/UI/test/quick-fix-cloudstack.sh
similarity index 100%
rename from UI/quick-fix-cloudstack.sh
rename to UI/test/quick-fix-cloudstack.sh
diff --git a/UI/test-assets.php b/UI/test/test-assets.php
similarity index 100%
rename from UI/test-assets.php
rename to UI/test/test-assets.php
diff --git a/UI/test-php-detection.sh b/UI/test/test-php-detection.sh
similarity index 100%
rename from UI/test-php-detection.sh
rename to UI/test/test-php-detection.sh
diff --git a/UI/troubleshoot.sh b/UI/test/troubleshoot.sh
similarity index 100%
rename from UI/troubleshoot.sh
rename to UI/test/troubleshoot.sh