fixed script new8

This commit is contained in:
Edmund Tan 2025-07-23 00:44:16 +08:00
parent 03ba182fb8
commit 2617475c0c
20 changed files with 919 additions and 429 deletions

View File

@ -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
<link rel="stylesheet" href="assets/css/style.css">
<script src="assets/js/app.js"></script>
```
### **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)
<link rel="stylesheet" href="../public/assets/css/style.css">
```
## 📋 **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
<link rel="stylesheet" href="assets/css/style.css">
<script src="assets/js/app.js"></script>
```
Following these guidelines ensures a clean, maintainable, and professional codebase that's easy to understand and extend.

View File

@ -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.

View File

@ -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

View File

@ -1,287 +0,0 @@
<?php
require_once 'includes/auth.php';
require_once 'includes/enrollment.php';
AuthManager::requireAuth();
$currentUser = AuthManager::getCurrentUser();
$enrollmentManager = new EnrollmentManager();
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
header('Content-Type: application/json');
if (isset($_GET['action']) && $_GET['action'] === 'get_status') {
$status = $enrollmentManager->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();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo APP_NAME; ?> - Dashboard</title>
<link rel="stylesheet" href="/assets/css/style.css">
</head>
<body>
<div class="dashboard-container">
<header class="header">
<div class="header-content">
<h1 class="header-title"><?php echo APP_NAME; ?></h1>
<div class="header-actions">
<div class="user-info">
Welcome, <strong><?php echo htmlspecialchars($currentUser['username']); ?></strong>
<span class="text-xs">
| Logged in: <?php echo date('M j, Y g:i A', $currentUser['login_time']); ?>
</span>
</div>
<button id="refreshStatus" class="btn btn-secondary" title="Refresh System Status">
🔄 Refresh
</button>
<a href="index.php?action=logout" class="btn btn-secondary">
Logout
</a>
</div>
</div>
</header>
<main class="main-content">
<div class="card">
<div class="card-header">
<h2 class="card-title">System Status</h2>
</div>
<div class="card-body">
<div class="status-grid">
<div class="status-item">
<div id="zitiStatusIcon" class="status-icon <?php echo $systemStatus['ziti_status'] === 'installed' ? 'success' : 'error'; ?>">
<?php echo $systemStatus['ziti_status'] === 'installed' ? '✓' : '✗'; ?>
</div>
<div class="status-content">
<h4>Ziti CLI Status</h4>
<p id="zitiStatus">
<?php
if ($systemStatus['ziti_status'] === 'installed') {
echo 'Installed (' . htmlspecialchars($systemStatus['ziti_version']) . ')';
} else {
echo 'Not Installed';
}
?>
</p>
</div>
</div>
<div class="status-item">
<div id="serviceStatusIcon" class="status-icon <?php echo $systemStatus['service_active'] ? 'success' : 'error'; ?>">
<?php echo $systemStatus['service_active'] ? '▶' : '⏹'; ?>
</div>
<div class="status-content">
<h4>Router Service</h4>
<p id="serviceStatus">
<?php echo $systemStatus['service_active'] ? 'Running' : 'Stopped'; ?>
</p>
</div>
</div>
<div class="status-item">
<div id="configStatusIcon" class="status-icon <?php
if ($systemStatus['config_exists'] && $systemStatus['certificates_exist']) {
echo 'success';
} elseif ($systemStatus['config_exists']) {
echo 'warning';
} else {
echo 'error';
}
?>">
<?php
if ($systemStatus['config_exists'] && $systemStatus['certificates_exist']) {
echo '⚙';
} elseif ($systemStatus['config_exists']) {
echo '⚠';
} else {
echo '✗';
}
?>
</div>
<div class="status-content">
<h4>Configuration</h4>
<p id="configStatus">
<?php
if ($systemStatus['config_exists'] && $systemStatus['certificates_exist']) {
echo 'Configured';
} elseif ($systemStatus['config_exists']) {
echo 'Partial';
} else {
echo 'Not Configured';
}
?>
</p>
</div>
</div>
<div class="status-item">
<div class="status-icon success">
🖥
</div>
<div class="status-content">
<h4>Hostname</h4>
<p id="hostname"><?php echo htmlspecialchars($systemStatus['hostname']); ?></p>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Router Enrollment</h2>
</div>
<div class="card-body">
<form id="enrollmentForm" class="enrollment-form">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="form-row">
<div class="form-group">
<label for="apiEndpoint" class="form-label">API Endpoint</label>
<input
type="url"
id="apiEndpoint"
name="apiEndpoint"
class="form-input"
value="<?php echo DEFAULT_API_ENDPOINT; ?>"
placeholder="https://backend.zitinexus.com"
required
>
<small class="text-sm text-secondary">ZitiNexus Portal API endpoint</small>
</div>
<div class="form-group">
<label for="hashKey" class="form-label">Hash Key</label>
<input
type="text"
id="hashKey"
name="hashKey"
class="form-input"
placeholder="32-character hexadecimal hash key"
maxlength="32"
pattern="[a-fA-F0-9]{32}"
required
>
<small class="text-sm text-secondary">Router enrollment hash key from ZitiNexus Portal</small>
</div>
</div>
<div class="form-group-full">
<button type="submit" id="enrollBtn" class="btn btn-primary">
Start Enrollment
</button>
<button type="button" id="clearLogs" class="btn btn-secondary" style="margin-left: 1rem;">
Clear Logs
</button>
</div>
</form>
<div id="progressContainer" class="progress-container">
<div class="progress-bar">
<div id="progressFill" class="progress-fill"></div>
</div>
<div class="progress-steps">
<div class="progress-step">Initialize</div>
<div class="progress-step">Requirements</div>
<div class="progress-step">Install</div>
<div class="progress-step">Directories</div>
<div class="progress-step">Register</div>
<div class="progress-step">Configure</div>
<div class="progress-step">Enroll</div>
<div class="progress-step">Service</div>
<div class="progress-step">Start</div>
<div class="progress-step">Report</div>
<div class="progress-step">Complete</div>
</div>
<div id="logContainer" class="log-container">
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Information</h2>
</div>
<div class="card-body">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem;">
<div>
<h4 class="font-medium mb-2">How to Use</h4>
<ol class="text-sm text-secondary" style="padding-left: 1.5rem;">
<li>Obtain a hash key from the ZitiNexus Portal by creating a router enrollment</li>
<li>Enter the API endpoint (default is pre-filled)</li>
<li>Paste the 32-character hash key</li>
<li>Click "Start Enrollment" to begin the process</li>
<li>Monitor the progress and logs for status updates</li>
</ol>
</div>
<div>
<h4 class="font-medium mb-2">System Requirements</h4>
<ul class="text-sm text-secondary" style="padding-left: 1.5rem;">
<li>Ubuntu 22.04 or 24.04 LTS</li>
<li>Root/sudo access required</li>
<li>Internet connectivity</li>
<li>systemctl available</li>
<li>curl and jq packages (auto-installed)</li>
</ul>
</div>
<div>
<h4 class="font-medium mb-2">File Locations</h4>
<ul class="text-sm text-secondary" style="padding-left: 1.5rem;">
<li><code>/etc/zitirouter/</code> - Configuration directory</li>
<li><code>/etc/zitirouter/certs/</code> - Certificates</li>
<li><code>/var/log/ziti-router-enrollment.log</code> - Enrollment log</li>
<li><code>/etc/systemd/system/ziti-router.service</code> - Service file</li>
</ul>
</div>
<div>
<h4 class="font-medium mb-2">Service Management</h4>
<ul class="text-sm text-secondary" style="padding-left: 1.5rem;">
<li><code>systemctl status ziti-router</code> - Check status</li>
<li><code>systemctl start ziti-router</code> - Start service</li>
<li><code>systemctl stop ziti-router</code> - Stop service</li>
<li><code>journalctl -u ziti-router -f</code> - View logs</li>
</ul>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="/assets/js/app.js"></script>
</body>
</html>

View File

@ -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();
}
}
?>

View File

@ -1,106 +0,0 @@
<?php
require_once 'includes/auth.php';
if (isAuthenticated() && isSessionValid()) {
header('Location: dashboard.php');
exit;
}
$message = '';
$messageType = '';
if (isset($_GET['error'])) {
switch ($_GET['error']) {
case 'session_expired':
$message = 'Your session has expired. Please log in again.';
$messageType = 'warning';
break;
default:
$message = 'An error occurred. Please try again.';
$messageType = 'error';
break;
}
}
if (isset($_GET['message'])) {
switch ($_GET['message']) {
case 'logged_out':
$message = 'You have been logged out successfully.';
$messageType = 'info';
break;
}
}
if (isset($loginError)) {
$message = $loginError;
$messageType = 'error';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo APP_NAME; ?> - Login</title>
<link rel="stylesheet" href="/assets/css/style.css">
</head>
<body>
<div class="login-container">
<div class="login-card">
<div class="login-header">
<h1><?php echo APP_NAME; ?></h1>
<p>Router Enrollment Management Interface</p>
<p class="text-sm text-secondary">Version <?php echo APP_VERSION; ?></p>
</div>
<?php if ($message): ?>
<div class="alert alert-<?php echo $messageType; ?>">
<?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>
<form method="POST" action="index.php">
<input type="hidden" name="action" value="login">
<div class="form-group">
<label for="username" class="form-label">Username</label>
<input
type="text"
id="username"
name="username"
class="form-input"
required
autocomplete="username"
value="<?php echo isset($_POST['username']) ? htmlspecialchars($_POST['username']) : ''; ?>"
>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<input
type="password"
id="password"
name="password"
class="form-input"
required
autocomplete="current-password"
>
</div>
<button type="submit" class="btn btn-primary btn-full">
Sign In
</button>
</form>
<div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e0e0e0; text-align: center;">
<p class="text-sm text-secondary">
Default credentials: <strong>admin</strong> / <strong>admin123</strong>
</p>
<p class="text-xs text-secondary" style="margin-top: 0.5rem;">
Please change the default password in production
</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -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();
});
}
@ -156,6 +157,39 @@ class EnrollmentUI {
}
}
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');
@ -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,9 +317,9 @@ 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;
@ -289,6 +327,141 @@ class EnrollmentUI {
}
}
/**
* 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');
if (progressContainer) {

View File

@ -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;

23
UI/public/progress.php Normal file
View File

@ -0,0 +1,23 @@
<?php
require_once '../includes/auth.php';
AuthManager::requireAuth();
header('Content-Type: application/json');
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
// Get current progress from session
$progress = $_SESSION['enrollment_progress'] ?? [
'step' => '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);
?>

82
UI/test/README.md Normal file
View File

@ -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.**