fixed script new8
This commit is contained in:
parent
03ba182fb8
commit
2617475c0c
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
17
UI/README.md
17
UI/README.md
|
|
@ -335,20 +335,23 @@ This UI is fully compatible with the ZitiNexus Portal API and replicates all fun
|
||||||
### File Structure
|
### File Structure
|
||||||
```
|
```
|
||||||
UI/
|
UI/
|
||||||
├── public/
|
├── public/ # Web-accessible files (DOCUMENT ROOT)
|
||||||
│ ├── index.php # Login page
|
│ ├── index.php # Login page
|
||||||
│ └── dashboard.php # Main dashboard
|
│ ├── dashboard.php # Main dashboard
|
||||||
├── includes/
|
│ ├── 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
|
│ ├── config.php # Configuration and utilities
|
||||||
│ ├── auth.php # Authentication handler
|
│ ├── auth.php # Authentication handler
|
||||||
│ ├── api_client.php # API communication
|
│ ├── api_client.php # API communication
|
||||||
│ └── enrollment.php # Core enrollment logic
|
│ └── enrollment.php # Core enrollment logic
|
||||||
├── assets/
|
├── test/ # Testing and debugging files
|
||||||
│ ├── css/style.css # Styling
|
|
||||||
│ └── js/app.js # Frontend JavaScript
|
|
||||||
├── logs/ # UI-specific logs
|
├── logs/ # UI-specific logs
|
||||||
├── temp/ # Temporary files
|
├── temp/ # Temporary files
|
||||||
└── README.md # This file
|
├── *.sh # Shell scripts (install, troubleshoot, etc.)
|
||||||
|
└── *.md # Documentation files
|
||||||
```
|
```
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
|
||||||
287
UI/dashboard.php
287
UI/dashboard.php
|
|
@ -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>
|
|
||||||
|
|
@ -30,16 +30,118 @@ class EnrollmentManager {
|
||||||
private function reportProgress($step, $message, $percentage = null) {
|
private function reportProgress($step, $message, $percentage = null) {
|
||||||
logMessage('INFO', "[$step] $message");
|
logMessage('INFO', "[$step] $message");
|
||||||
|
|
||||||
|
// Store progress in session for AJAX polling
|
||||||
|
$this->updateSessionProgress($step, $message, $percentage);
|
||||||
|
|
||||||
if ($this->progressCallback && is_callable($this->progressCallback)) {
|
if ($this->progressCallback && is_callable($this->progressCallback)) {
|
||||||
call_user_func($this->progressCallback, $step, $message, $percentage);
|
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
|
* Main enrollment process
|
||||||
*/
|
*/
|
||||||
public function enrollRouter($hashKey, $apiEndpoint = null) {
|
public function enrollRouter($hashKey, $apiEndpoint = null) {
|
||||||
try {
|
try {
|
||||||
|
// Initialize progress tracking
|
||||||
|
$this->initializeProgress();
|
||||||
|
|
||||||
if ($apiEndpoint) {
|
if ($apiEndpoint) {
|
||||||
$this->apiClient = new ApiClient($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() {
|
public function getSystemStatus() {
|
||||||
$status = [
|
$status = [
|
||||||
|
|
@ -536,38 +638,130 @@ EOF;
|
||||||
'ziti_version' => '',
|
'ziti_version' => '',
|
||||||
'service_active' => false,
|
'service_active' => false,
|
||||||
'config_exists' => false,
|
'config_exists' => false,
|
||||||
'certificates_exist' => false
|
'certificates_exist' => false,
|
||||||
|
'package_installed' => false,
|
||||||
|
'last_checked' => time()
|
||||||
];
|
];
|
||||||
|
|
||||||
// Get hostname
|
// Get hostname
|
||||||
executeCommand('hostname', $status['hostname']);
|
executeCommand('hostname', $status['hostname']);
|
||||||
$status['hostname'] = trim($status['hostname']);
|
$status['hostname'] = trim($status['hostname']);
|
||||||
|
|
||||||
// Check if ziti command exists and get version
|
// Enhanced Ziti installation check
|
||||||
if ($this->checkCommand('ziti')) {
|
$zitiInstallationStatus = $this->checkZitiInstallation();
|
||||||
executeCommand('ziti version 2>/dev/null | head -n1', $status['ziti_version']);
|
$status['ziti_status'] = $zitiInstallationStatus['status'];
|
||||||
$status['ziti_version'] = trim($status['ziti_version']);
|
$status['ziti_version'] = $zitiInstallationStatus['version'];
|
||||||
$status['ziti_status'] = 'installed';
|
$status['package_installed'] = $zitiInstallationStatus['package_installed'];
|
||||||
} else {
|
|
||||||
$status['ziti_status'] = 'not_installed';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check service status
|
// Check service status with more thorough checking
|
||||||
$output = '';
|
$serviceStatus = $this->checkServiceStatus();
|
||||||
if (executeCommand('systemctl is-active ziti-router.service 2>/dev/null', $output)) {
|
$status['service_active'] = $serviceStatus['active'];
|
||||||
$status['service_active'] = trim($output) === 'active';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check configuration files
|
// Check configuration files
|
||||||
$status['config_exists'] = file_exists(ROUTER_CONFIG);
|
$status['config_exists'] = file_exists(ROUTER_CONFIG);
|
||||||
|
|
||||||
// Check certificates
|
// Check certificates with sudo (since they're root-owned)
|
||||||
if (is_dir(CERTS_DIR)) {
|
$status['certificates_exist'] = $this->checkCertificatesExist();
|
||||||
$certFiles = glob(CERTS_DIR . '/*.cert');
|
|
||||||
$status['certificates_exist'] = !empty($certFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $status;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
|
||||||
106
UI/index.php
106
UI/index.php
|
|
@ -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>
|
|
||||||
|
|
@ -10,6 +10,7 @@ class EnrollmentUI {
|
||||||
'REGISTER', 'CONFIG', 'ENROLL', 'SERVICE', 'START', 'REPORT', 'COMPLETE'
|
'REGISTER', 'CONFIG', 'ENROLL', 'SERVICE', 'START', 'REPORT', 'COMPLETE'
|
||||||
];
|
];
|
||||||
this.currentStep = 0;
|
this.currentStep = 0;
|
||||||
|
this.progressPollingInterval = null;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +56,7 @@ class EnrollmentUI {
|
||||||
const refreshBtn = document.getElementById('refreshStatus');
|
const refreshBtn = document.getElementById('refreshStatus');
|
||||||
if (refreshBtn) {
|
if (refreshBtn) {
|
||||||
refreshBtn.addEventListener('click', () => {
|
refreshBtn.addEventListener('click', () => {
|
||||||
this.loadSystemStatus();
|
this.refreshSystemStatus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,6 +156,39 @@ class EnrollmentUI {
|
||||||
this.showAlert('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) {
|
updateSystemStatus(status) {
|
||||||
// Update hostname
|
// Update hostname
|
||||||
|
|
@ -241,7 +275,12 @@ class EnrollmentUI {
|
||||||
|
|
||||||
this.showProgressContainer();
|
this.showProgressContainer();
|
||||||
this.clearLogs();
|
this.clearLogs();
|
||||||
this.updateProgress(0, 'Initializing...');
|
|
||||||
|
// Clear any existing progress
|
||||||
|
await this.clearProgress();
|
||||||
|
|
||||||
|
// Start progress polling
|
||||||
|
this.startProgressPolling();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
@ -250,6 +289,7 @@ class EnrollmentUI {
|
||||||
formData.append('apiEndpoint', apiEndpointInput.value.trim());
|
formData.append('apiEndpoint', apiEndpointInput.value.trim());
|
||||||
formData.append('csrf_token', document.querySelector('input[name="csrf_token"]').value);
|
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', {
|
const response = await fetch('dashboard.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
|
|
@ -265,8 +305,6 @@ class EnrollmentUI {
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
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');
|
this.showAlert(`Router enrollment completed successfully! Router: ${result.routerName}`, 'success');
|
||||||
|
|
||||||
// Refresh system status after successful enrollment
|
// Refresh system status after successful enrollment
|
||||||
|
|
@ -279,15 +317,150 @@ class EnrollmentUI {
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Enrollment failed:', error);
|
console.error('Enrollment failed:', error);
|
||||||
this.updateProgress(null, 'Enrollment failed');
|
|
||||||
this.addLogEntry('error', `Enrollment failed: ${error.message}`);
|
this.addLogEntry('error', `Enrollment failed: ${error.message}`);
|
||||||
this.showAlert(`Enrollment failed: ${error.message}`, 'error');
|
this.showAlert(`Enrollment failed: ${error.message}`, 'error');
|
||||||
|
this.stopProgressPolling();
|
||||||
} finally {
|
} finally {
|
||||||
this.enrollmentInProgress = false;
|
this.enrollmentInProgress = false;
|
||||||
enrollBtn.disabled = false;
|
enrollBtn.disabled = false;
|
||||||
enrollBtn.innerHTML = 'Start Enrollment';
|
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() {
|
showProgressContainer() {
|
||||||
const progressContainer = document.getElementById('progressContainer');
|
const progressContainer = document.getElementById('progressContainer');
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,29 @@ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQU
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
if (isset($_GET['action']) && $_GET['action'] === 'get_status') {
|
if (isset($_GET['action']) && $_GET['action'] === 'get_status') {
|
||||||
// Get system status
|
|
||||||
$status = $enrollmentManager->getSystemStatus();
|
$status = $enrollmentManager->getSystemStatus();
|
||||||
echo json_encode($status);
|
echo json_encode($status);
|
||||||
exit;
|
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') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'enroll') {
|
||||||
// Handle enrollment request
|
|
||||||
AuthManager::requireCSRF();
|
AuthManager::requireCSRF();
|
||||||
|
|
||||||
$hashKey = sanitizeInput($_POST['hashKey'] ?? '');
|
$hashKey = sanitizeInput($_POST['hashKey'] ?? '');
|
||||||
$apiEndpoint = sanitizeInput($_POST['apiEndpoint'] ?? DEFAULT_API_ENDPOINT);
|
$apiEndpoint = sanitizeInput($_POST['apiEndpoint'] ?? DEFAULT_API_ENDPOINT);
|
||||||
|
|
||||||
// Validate inputs
|
|
||||||
if (!ApiClient::validateHashKey($hashKey)) {
|
if (!ApiClient::validateHashKey($hashKey)) {
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid hash key format']);
|
echo json_encode(['success' => false, 'error' => 'Invalid hash key format']);
|
||||||
exit;
|
exit;
|
||||||
|
|
@ -44,7 +53,6 @@ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQU
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start enrollment
|
|
||||||
$result = $enrollmentManager->enrollRouter($hashKey, $apiEndpoint);
|
$result = $enrollmentManager->enrollRouter($hashKey, $apiEndpoint);
|
||||||
echo json_encode($result);
|
echo json_encode($result);
|
||||||
exit;
|
exit;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
?>
|
||||||
|
|
@ -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.**
|
||||||
Loading…
Reference in New Issue