Add home directory management features: Introduce models and views for managing user home directories, including dynamic home directory assignment during user creation. Update frontend to allow selection of home directories and display relevant information. Enhance backend logic for home directory migration and statistics retrieval, improving overall user management capabilities.

This commit is contained in:
Master3395
2025-09-20 21:50:22 +02:00
parent 25fa25d877
commit 53aea56136
19 changed files with 2593 additions and 10 deletions

View File

@@ -0,0 +1,533 @@
# Home Directory Management Guide for CyberPanel
## Overview
The Home Directory Management feature allows CyberPanel administrators to manage multiple home directories across different storage volumes (e.g., `/home`, `/home2`, `/home3`). This enables storage balancing, helps avoid upgrading main volume plans by utilizing cheaper additional volumes, and provides better resource distribution across your server.
## Table of Contents
1. [Features](#features)
2. [Getting Started](#getting-started)
3. [Managing Home Directories](#managing-home-directories)
4. [User Management](#user-management)
5. [Website Management Integration](#website-management-integration)
6. [Storage Balancing](#storage-balancing)
7. [User Migration](#user-migration)
8. [Troubleshooting](#troubleshooting)
9. [Best Practices](#best-practices)
10. [Advanced Configuration](#advanced-configuration)
## Features
### Core Capabilities
- **Multiple Home Directories**: Manage `/home`, `/home2`, `/home3`, etc.
- **Automatic Detection**: Auto-discover existing home directories
- **Storage Monitoring**: Real-time space usage and availability tracking
- **User Distribution**: Balance users across different storage volumes
- **Website Integration**: Manage home directories directly from website modification
- **User Migration**: Move users between home directories seamlessly
- **Storage Analytics**: Detailed usage statistics and recommendations
### Benefits
- **Cost Optimization**: Use cheaper additional volumes instead of upgrading main storage
- **Performance**: Distribute load across multiple storage devices
- **Scalability**: Easy expansion as your server grows
- **Flexibility**: Choose optimal storage for different user types
- **Monitoring**: Real-time visibility into storage usage
## Getting Started
### Prerequisites
- CyberPanel v2.5.5 or higher
- Administrator access
- Multiple storage volumes available (optional but recommended)
- Sufficient disk space for home directories
### Initial Setup
1. **Access Home Directory Management**
- Log in to CyberPanel admin panel
- Navigate to **User Management****Home Directory Management**
2. **Auto-Detect Existing Directories**
- Click **"Detect Directories"** button
- System will automatically find `/home`, `/home2`, `/home3`, etc.
- Review detected directories and their status
3. **Configure Default Settings**
- Set default home directory (usually `/home`)
- Configure storage limits and user limits
- Enable/disable directories as needed
### Database Migration (For Developers)
```bash
cd /usr/local/CyberCP
python manage.py makemigrations userManagment
python manage.py migrate
```
### Technical Implementation Details
#### Core Components
- **Models** (`models.py`): `HomeDirectory` and `UserHomeMapping` models
- **Management** (`homeDirectoryManager.py`): Core functionality for operations
- **Utilities** (`homeDirectoryUtils.py`): Helper functions for path resolution
- **Views** (`homeDirectoryViews.py`): Admin interface and API endpoints
- **Templates**: Management interfaces and user creation forms
#### File Structure
```
cyberpanel/userManagment/
├── models.py # Database models
├── homeDirectoryManager.py # Core management logic
├── homeDirectoryUtils.py # Utility functions
├── homeDirectoryViews.py # API endpoints
├── templates/userManagment/
│ ├── homeDirectoryManagement.html
│ ├── userMigration.html
│ └── createUser.html (updated)
└── migrations/
└── 0001_home_directories.py
```
## Managing Home Directories
### Adding New Home Directories
#### Method 1: Automatic Detection
```bash
# The system automatically detects directories matching pattern /home[0-9]*
# No manual intervention required
```
#### Method 2: Manual Creation
1. **Create Directory on Server**
```bash
sudo mkdir /home2
sudo chown root:root /home2
sudo chmod 755 /home2
```
2. **Detect in CyberPanel**
- Go to Home Directory Management
- Click **"Detect Directories"**
- New directory will appear in the list
### Configuring Home Directories
#### Basic Configuration
- **Name**: Display name for the directory
- **Path**: Full system path (e.g., `/home2`)
- **Description**: Optional description for identification
- **Status**: Active/Inactive
- **Default**: Set as default for new users
#### Advanced Settings
- **Maximum Users**: Limit number of users per directory
- **Storage Quota**: Set storage limits (if supported by filesystem)
- **Priority**: Directory selection priority for auto-assignment
### Directory Status Management
#### Active Directories
- Available for new user assignments
- Included in auto-selection algorithms
- Monitored for storage usage
#### Inactive Directories
- Not available for new users
- Existing users remain unaffected
- Useful for maintenance or decommissioning
## User Management
### Creating Users with Home Directory Selection
1. **Navigate to User Creation**
- Go to **User Management** → **Create User**
2. **Select Home Directory**
- Choose from dropdown list of available directories
- View real-time storage information
- Select "Auto-select" for automatic assignment
3. **Review Assignment**
- System shows selected directory details
- Displays available space and user count
- Confirms assignment before creation
### Auto-Assignment Logic
The system automatically selects the best home directory based on:
- **Available Space**: Prioritizes directories with more free space
- **User Count**: Balances users across directories
- **Directory Status**: Only considers active directories
- **User Limits**: Respects maximum user limits per directory
### Manual User Assignment
1. **Access User Migration**
- Go to **User Management** → **User Migration**
2. **Select User and Target Directory**
- Choose user to migrate
- Select destination home directory
- Review migration details
3. **Execute Migration**
- System moves user data
- Updates all references
- Verifies successful migration
## Website Management Integration
### Modifying Website Home Directory
1. **Access Website Modification**
- Go to **Websites** → **Modify Website**
- Select website to modify
2. **Change Home Directory**
- Select new home directory from dropdown
- View current and available options
- See real-time storage information
3. **Save Changes**
- System migrates website owner
- Updates all website references
- Maintains website functionality
### Website Owner Migration
When changing a website's home directory:
- **User Data**: Website owner is migrated to new directory
- **Website Files**: All website files are moved
- **Database References**: All database references updated
- **Permissions**: File permissions maintained
- **Services**: Email, FTP, and other services updated
## Storage Balancing
### Monitoring Storage Usage
#### Real-Time Statistics
- **Total Space**: Combined storage across all directories
- **Available Space**: Free space available
- **Usage Percentage**: Visual progress bars
- **User Distribution**: Users per directory
#### Storage Alerts
- **Low Space Warning**: When directory approaches capacity
- **Full Directory Alert**: When directory reaches maximum users
- **Performance Impact**: Storage performance recommendations
### Balancing Strategies
#### Automatic Balancing
- **New User Assignment**: Distributes users evenly
- **Space-Based Selection**: Prioritizes directories with more space
- **Load Distribution**: Spreads load across multiple volumes
#### Manual Balancing
- **User Migration**: Move users between directories
- **Directory Management**: Enable/disable directories
- **Capacity Planning**: Monitor and plan for growth
## User Migration
### Migration Process
1. **Pre-Migration Checks**
- Verify source and destination directories
- Check available space
- Validate user permissions
2. **Data Migration**
- Copy user home directory
- Update system references
- Verify data integrity
3. **Post-Migration Verification**
- Test user access
- Verify website functionality
- Update service configurations
### Migration Safety
#### Backup Recommendations
```bash
# Always backup before migration
sudo tar -czf /backup/user-backup-$(date +%Y%m%d).tar.gz /home/username
```
#### Rollback Procedures
- Keep original data until migration verified
- Maintain backup of system configurations
- Document all changes made
### Migration Commands
#### Manual Migration (Advanced Users)
```bash
# Stop user services
sudo systemctl stop user@username
# Copy user data
sudo rsync -av /home/username/ /home2/username/
# Update user home in system
sudo usermod -d /home2/username username
# Update CyberPanel database
# (Use web interface for this)
```
## Troubleshooting
### Common Issues
#### 1. Directory Not Detected
**Problem**: New home directory not appearing in list
**Solution**:
```bash
# Check directory exists and has correct permissions
ls -la /home2
sudo chown root:root /home2
sudo chmod 755 /home2
# Refresh detection in CyberPanel
# Click "Detect Directories" button
```
#### 2. Migration Fails
**Problem**: User migration fails with errors
**Solution**:
- Check available space in destination
- Verify user permissions
- Review CyberPanel logs
- Ensure destination directory is active
#### 3. Website Access Issues
**Problem**: Website not accessible after migration
**Solution**:
- Check file permissions
- Verify web server configuration
- Update virtual host settings
- Restart web server services
#### 4. Storage Not Updating
**Problem**: Storage statistics not reflecting changes
**Solution**:
- Click "Refresh Stats" button
- Check filesystem mount status
- Verify directory accessibility
- Review system logs
### Diagnostic Commands
#### Check Directory Status
```bash
# List all home directories
ls -la /home*
# Check disk usage
df -h /home*
# Check permissions
ls -la /home*/username
```
#### Verify User Assignments
```bash
# Check user home directories
getent passwd | grep /home
# Check CyberPanel database
# (Use web interface or database tools)
```
#### Monitor Storage Usage
```bash
# Real-time disk usage
watch -n 5 'df -h /home*'
# Directory sizes
du -sh /home*
# User directory sizes
du -sh /home*/username
```
### Log Files
#### CyberPanel Logs
```bash
# Main CyberPanel log
tail -f /usr/local/CyberCP/logs/cyberpanel.log
# Home directory specific logs
grep "home.*directory" /usr/local/CyberCP/logs/cyberpanel.log
```
#### System Logs
```bash
# System messages
tail -f /var/log/messages
# Authentication logs
tail -f /var/log/auth.log
```
## Best Practices
### Storage Planning
#### Directory Organization
- **Primary Directory** (`/home`): Default for most users
- **Secondary Directories** (`/home2`, `/home3`): For specific user groups
- **Naming Convention**: Use descriptive names (e.g., `/home-ssd`, `/home-hdd`)
#### Capacity Management
- **Monitor Usage**: Regular storage monitoring
- **Set Alerts**: Configure low-space warnings
- **Plan Growth**: Anticipate future storage needs
- **Regular Cleanup**: Remove unused user data
### User Management
#### Assignment Strategy
- **New Users**: Use auto-assignment for balanced distribution
- **Special Cases**: Manually assign users with specific requirements
- **Regular Review**: Periodically review user distribution
#### Migration Planning
- **Schedule Migrations**: Plan during low-usage periods
- **Test First**: Verify migration process with test users
- **Communicate Changes**: Inform users of planned migrations
### Security Considerations
#### Access Control
- **Directory Permissions**: Maintain proper file permissions
- **User Isolation**: Ensure users can only access their directories
- **System Security**: Regular security updates and monitoring
#### Backup Strategy
- **Regular Backups**: Backup user data regularly
- **Migration Backups**: Always backup before migrations
- **Configuration Backups**: Backup CyberPanel configurations
## Advanced Configuration
### Custom Directory Paths
#### Non-Standard Paths
```bash
# Create custom home directory
sudo mkdir /var/home
sudo chown root:root /var/home
sudo chmod 755 /var/home
# Add to CyberPanel manually
# (Use web interface to add custom paths)
```
#### Network Storage
- **NFS Mounts**: Use NFS for network-attached storage
- **iSCSI**: Configure iSCSI for block-level storage
- **Cloud Storage**: Integrate with cloud storage solutions
### Performance Optimization
#### Storage Performance
- **SSD vs HDD**: Use SSDs for frequently accessed data
- **RAID Configuration**: Optimize RAID for performance
- **Caching**: Implement appropriate caching strategies
#### Load Balancing
- **User Distribution**: Balance users across storage devices
- **I/O Optimization**: Optimize I/O patterns
- **Monitoring**: Monitor performance metrics
### Integration with Other Features
#### Backup Integration
- **Automated Backups**: Include home directories in backup schedules
- **Incremental Backups**: Use incremental backup strategies
- **Restore Procedures**: Test restore procedures regularly
#### Monitoring Integration
- **Storage Monitoring**: Integrate with monitoring systems
- **Alerting**: Set up automated alerts
- **Reporting**: Generate regular usage reports
## API Reference
### Home Directory Management API
#### Get Home Directories
```bash
curl -X POST https://your-server:8090/userManagement/getUserHomeDirectories/ \
-H "Content-Type: application/json" \
-d '{}'
```
#### Update Home Directory
```bash
curl -X POST https://your-server:8090/userManagement/updateHomeDirectory/ \
-H "Content-Type: application/json" \
-d '{
"id": 1,
"description": "Updated description",
"max_users": 100,
"is_active": true
}'
```
#### Migrate User
```bash
curl -X POST https://your-server:8090/userManagement/migrateUser/ \
-H "Content-Type: application/json" \
-d '{
"user_id": 123,
"new_home_directory_id": 2
}'
```
### Complete API Endpoints
#### Home Directory Management
- `POST /userManagement/detectHomeDirectories/` - Detect new home directories
- `POST /userManagement/updateHomeDirectory/` - Update home directory settings
- `POST /userManagement/deleteHomeDirectory/` - Delete home directory
- `POST /userManagement/getHomeDirectoryStats/` - Get storage statistics
#### User Operations
- `POST /userManagement/getUserHomeDirectories/` - Get available home directories
- `POST /userManagement/migrateUser/` - Migrate user to different home directory
#### Website Integration
- `POST /websites/saveWebsiteChanges/` - Save website changes including home directory
- `POST /websites/getWebsiteDetails/` - Get website details including current home directory
## Support and Resources
### Documentation
- **CyberPanel Documentation**: https://cyberpanel.net/docs/
- **User Management Guide**: This guide
- **API Documentation**: Available in CyberPanel interface
### Community Support
- **CyberPanel Forums**: https://community.cyberpanel.net
- **GitHub Issues**: https://github.com/usmannasir/cyberpanel/issues
- **Discord Server**: https://discord.gg/cyberpanel
### Professional Support
- **CyberPanel Support**: Available through official channels
- **Custom Implementation**: Contact for enterprise solutions
---
**Note**: This guide covers the complete Home Directory Management feature in CyberPanel. For the latest updates and additional features, refer to the official CyberPanel documentation and community resources.
*Last updated: January 2025*

View File

@@ -46,6 +46,9 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
### 💻 Command Line Interface
- **[CLI Command Reference](CLI_COMMAND_REFERENCE.md)** - Complete reference for all CyberPanel CLI commands
### 🏠 Storage & User Management
- **[Home Directory Management Guide](HOME_DIRECTORY_MANAGEMENT_GUIDE.md)** - Complete guide for managing multiple home directories and storage balancing
### 📖 General Documentation
- **[README](../README.md)** - Main CyberPanel documentation with installation instructions and feature overview
- **[Contributing Guide](CONTRIBUTING.md)** - Guidelines for contributing to the CyberPanel project
@@ -57,7 +60,8 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
3. **Need Docker help?** Check the [Docker Command Execution Guide](Docker_Command_Execution_Guide.md)
4. **Setting up email marketing?** Follow the [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)
5. **Want to customize the interface?** Check the [Custom CSS Guide](CUSTOM_CSS_GUIDE.md)
6. **Want to contribute?** Read the [Contributing Guide](CONTRIBUTING.md)
6. **Managing multiple storage volumes?** Follow the [Home Directory Management Guide](HOME_DIRECTORY_MANAGEMENT_GUIDE.md)
7. **Want to contribute?** Read the [Contributing Guide](CONTRIBUTING.md)
## 🔍 Finding What You Need
@@ -83,6 +87,7 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
- **Docker Features**: [Docker Command Execution Guide](Docker_Command_Execution_Guide.md)
- **Security Features**: [AI Scanner Documentation](AIScannerDocs.md)
- **Email Marketing**: [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)
- **Storage Management**: [Home Directory Management Guide](HOME_DIRECTORY_MANAGEMENT_GUIDE.md)
- **Customization & Design**: [Custom CSS Guide](CUSTOM_CSS_GUIDE.md)
- **Command Line Interface**: [CLI Command Reference](CLI_COMMAND_REFERENCE.md)
- **Development**: [Contributing Guide](CONTRIBUTING.md)
@@ -93,6 +98,7 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
- Docker container management
- Command execution
- Security scanning
- Home directory management
- CLI command reference
### 🔧 **Integrations**

View File

@@ -196,9 +196,15 @@ class vhost:
command = 'mkdir -p /usr/local/lsws/Example/html/.well-known/acme-challenge'
ProcessUtilities.normalExecutioner(command)
path = "/home/" + virtualHostName
pathHTML = "/home/" + virtualHostName + "/public_html"
pathLogs = "/home/" + virtualHostName + "/logs"
# Get user's home directory dynamically
from userManagment.homeDirectoryUtils import HomeDirectoryUtils
home_path = HomeDirectoryUtils.getUserHomeDirectory(virtualHostUser)
if not home_path:
home_path = "/home" # Fallback to default
path = os.path.join(home_path, virtualHostName)
pathHTML = os.path.join(home_path, virtualHostName, "public_html")
pathLogs = os.path.join(home_path, virtualHostName, "logs")
confPath = vhost.Server_root + "/conf/vhosts/"+virtualHostName
completePathToConfigFile = confPath +"/vhost.conf"

View File

@@ -0,0 +1,230 @@
#!/usr/local/CyberCP/bin/python
import os
import sys
import json
import shutil
import subprocess
import pwd
import grp
from django.db import models
from django.conf import settings
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from plogical.processUtilities import ProcessUtilities
class HomeDirectoryManager:
"""
Manages multiple home directories for CyberPanel users
Supports /home, /home2, /home3, etc. for storage balance
"""
@staticmethod
def detectHomeDirectories():
"""
Automatically detect all available home directories
Returns list of available home directories
"""
try:
home_dirs = []
# Check for /home (default)
if os.path.exists('/home') and os.path.isdir('/home'):
home_dirs.append({
'path': '/home',
'name': 'home',
'available_space': HomeDirectoryManager.getAvailableSpace('/home'),
'total_space': HomeDirectoryManager.getTotalSpace('/home'),
'user_count': HomeDirectoryManager.getUserCount('/home')
})
# Check for /home2, /home3, etc.
for i in range(2, 10): # Check up to /home9
home_path = f'/home{i}'
if os.path.exists(home_path) and os.path.isdir(home_path):
home_dirs.append({
'path': home_path,
'name': f'home{i}',
'available_space': HomeDirectoryManager.getAvailableSpace(home_path),
'total_space': HomeDirectoryManager.getTotalSpace(home_path),
'user_count': HomeDirectoryManager.getUserCount(home_path)
})
return home_dirs
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error detecting home directories: {str(e)}")
return []
@staticmethod
def getAvailableSpace(path):
"""Get available space in bytes for a given path"""
try:
statvfs = os.statvfs(path)
return statvfs.f_frsize * statvfs.f_bavail
except:
return 0
@staticmethod
def getTotalSpace(path):
"""Get total space in bytes for a given path"""
try:
statvfs = os.statvfs(path)
return statvfs.f_frsize * statvfs.f_blocks
except:
return 0
@staticmethod
def getUserCount(path):
"""Get number of users in a home directory"""
try:
count = 0
for item in os.listdir(path):
item_path = os.path.join(path, item)
if os.path.isdir(item_path) and not item.startswith('.'):
# Check if it's a user directory (has public_html)
if os.path.exists(os.path.join(item_path, 'public_html')):
count += 1
return count
except:
return 0
@staticmethod
def getBestHomeDirectory():
"""
Automatically select the best home directory based on available space
Returns the path with most available space
"""
try:
home_dirs = HomeDirectoryManager.detectHomeDirectories()
if not home_dirs:
return '/home' # Fallback to default
# Sort by available space (descending)
home_dirs.sort(key=lambda x: x['available_space'], reverse=True)
return home_dirs[0]['path']
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error selecting best home directory: {str(e)}")
return '/home'
@staticmethod
def createUserDirectory(username, home_path, owner_uid=5003, owner_gid=5003):
"""
Create user directory in specified home path
"""
try:
user_path = os.path.join(home_path, username)
# Create user directory
if not os.path.exists(user_path):
os.makedirs(user_path, mode=0o755)
# Create public_html directory
public_html_path = os.path.join(user_path, 'public_html')
if not os.path.exists(public_html_path):
os.makedirs(public_html_path, mode=0o755)
# Create logs directory
logs_path = os.path.join(user_path, 'logs')
if not os.path.exists(logs_path):
os.makedirs(logs_path, mode=0o755)
# Set ownership
HomeDirectoryManager.setOwnership(user_path, owner_uid, owner_gid)
return True
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error creating user directory: {str(e)}")
return False
@staticmethod
def setOwnership(path, uid, gid):
"""Set ownership for a path recursively"""
try:
# Set ownership for the directory
os.chown(path, uid, gid)
# Set ownership for all contents recursively
for root, dirs, files in os.walk(path):
for d in dirs:
os.chown(os.path.join(root, d), uid, gid)
for f in files:
os.chown(os.path.join(root, f), uid, gid)
return True
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error setting ownership: {str(e)}")
return False
@staticmethod
def migrateUser(username, from_home, to_home, owner_uid=5003, owner_gid=5003):
"""
Migrate user from one home directory to another
"""
try:
from_path = os.path.join(from_home, username)
to_path = os.path.join(to_home, username)
if not os.path.exists(from_path):
return False, f"User directory {from_path} does not exist"
if os.path.exists(to_path):
return False, f"User directory {to_path} already exists"
# Create target directory structure
if not HomeDirectoryManager.createUserDirectory(username, to_home, owner_uid, owner_gid):
return False, "Failed to create target directory"
# Copy all files and directories
shutil.copytree(from_path, to_path, dirs_exist_ok=True)
# Set proper ownership
HomeDirectoryManager.setOwnership(to_path, owner_uid, owner_gid)
# Remove old directory
shutil.rmtree(from_path)
return True, "User migrated successfully"
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error migrating user: {str(e)}")
return False, str(e)
@staticmethod
def getHomeDirectoryStats():
"""
Get comprehensive statistics for all home directories
"""
try:
home_dirs = HomeDirectoryManager.detectHomeDirectories()
stats = {
'total_directories': len(home_dirs),
'total_users': sum(d['user_count'] for d in home_dirs),
'total_space': sum(d['total_space'] for d in home_dirs),
'total_available': sum(d['available_space'] for d in home_dirs),
'directories': home_dirs
}
# Calculate usage percentages
for directory in stats['directories']:
if directory['total_space'] > 0:
used_space = directory['total_space'] - directory['available_space']
directory['usage_percentage'] = (used_space / directory['total_space']) * 100
else:
directory['usage_percentage'] = 0
return stats
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error getting home directory stats: {str(e)}")
return None
@staticmethod
def formatBytes(bytes_value):
"""Convert bytes to human readable format"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if bytes_value < 1024.0:
return f"{bytes_value:.1f} {unit}"
bytes_value /= 1024.0
return f"{bytes_value:.1f} PB"

View File

@@ -0,0 +1,148 @@
#!/usr/local/CyberCP/bin/python
import os
import sys
from django.db import models
from loginSystem.models import Administrator
from .models import HomeDirectory, UserHomeMapping
from .homeDirectoryManager import HomeDirectoryManager
class HomeDirectoryUtils:
"""
Utility functions for getting user home directories
"""
@staticmethod
def getUserHomeDirectory(username):
"""
Get the home directory path for a specific user
Returns the home directory path or None if not found
"""
try:
user = Administrator.objects.get(userName=username)
try:
mapping = UserHomeMapping.objects.get(user=user)
return mapping.home_directory.path
except UserHomeMapping.DoesNotExist:
# Fallback to default home directory
default_home = HomeDirectory.objects.filter(is_default=True).first()
if default_home:
return default_home.path
else:
return HomeDirectoryManager.getBestHomeDirectory()
except Administrator.DoesNotExist:
return None
@staticmethod
def getUserHomeDirectoryObject(username):
"""
Get the home directory object for a specific user
Returns the HomeDirectory object or None if not found
"""
try:
user = Administrator.objects.get(userName=username)
try:
mapping = UserHomeMapping.objects.get(user=user)
return mapping.home_directory
except UserHomeMapping.DoesNotExist:
# Fallback to default home directory
return HomeDirectory.objects.filter(is_default=True).first()
except Administrator.DoesNotExist:
return None
@staticmethod
def getWebsitePath(domain, username=None):
"""
Get the website path for a domain, using the user's home directory
"""
if username:
home_path = HomeDirectoryUtils.getUserHomeDirectory(username)
else:
home_path = HomeDirectoryManager.getBestHomeDirectory()
if not home_path:
home_path = '/home' # Fallback
return os.path.join(home_path, domain)
@staticmethod
def getPublicHtmlPath(domain, username=None):
"""
Get the public_html path for a domain
"""
website_path = HomeDirectoryUtils.getWebsitePath(domain, username)
return os.path.join(website_path, 'public_html')
@staticmethod
def getLogsPath(domain, username=None):
"""
Get the logs path for a domain
"""
website_path = HomeDirectoryUtils.getWebsitePath(domain, username)
return os.path.join(website_path, 'logs')
@staticmethod
def updateUserHomeDirectory(username, new_home_directory_id):
"""
Update a user's home directory
"""
try:
user = Administrator.objects.get(userName=username)
home_dir = HomeDirectory.objects.get(id=new_home_directory_id)
# Update or create mapping
mapping, created = UserHomeMapping.objects.get_or_create(
user=user,
defaults={'home_directory': home_dir}
)
if not created:
mapping.home_directory = home_dir
mapping.save()
return True, "Home directory updated successfully"
except Administrator.DoesNotExist:
return False, "User not found"
except HomeDirectory.DoesNotExist:
return False, "Home directory not found"
except Exception as e:
return False, str(e)
@staticmethod
def getAllUsersInHomeDirectory(home_directory_id):
"""
Get all users assigned to a specific home directory
"""
try:
home_dir = HomeDirectory.objects.get(id=home_directory_id)
mappings = UserHomeMapping.objects.filter(home_directory=home_dir)
return [mapping.user for mapping in mappings]
except HomeDirectory.DoesNotExist:
return []
@staticmethod
def getHomeDirectoryUsageStats():
"""
Get usage statistics for all home directories
"""
home_dirs = HomeDirectory.objects.filter(is_active=True)
stats = []
for home_dir in home_dirs:
user_count = UserHomeMapping.objects.filter(home_directory=home_dir).count()
available_space = home_dir.get_available_space()
total_space = home_dir.get_total_space()
usage_percentage = home_dir.get_usage_percentage()
stats.append({
'id': home_dir.id,
'name': home_dir.name,
'path': home_dir.path,
'user_count': user_count,
'available_space': available_space,
'total_space': total_space,
'usage_percentage': usage_percentage,
'is_default': home_dir.is_default
})
return stats

View File

@@ -0,0 +1,254 @@
#!/usr/local/CyberCP/bin/python
import json
import os
import sys
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from loginSystem.views import loadLoginPage
from loginSystem.models import Administrator
from plogical.acl import ACLManager
from plogical.httpProc import httpProc
from .homeDirectoryManager import HomeDirectoryManager
from .models import HomeDirectory, UserHomeMapping
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
def loadHomeDirectoryManagement(request):
"""Load home directory management interface"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadError()
# Get all home directories
home_directories = HomeDirectory.objects.all().order_by('name')
# Get statistics
stats = HomeDirectoryManager.getHomeDirectoryStats()
proc = httpProc(request, 'userManagment/homeDirectoryManagement.html', {
'home_directories': home_directories,
'stats': stats
}, 'admin')
return proc.render()
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error loading home directory management: {str(e)}")
return ACLManager.loadError()
def detectHomeDirectories(request):
"""Detect and add new home directories"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
# Detect home directories
detected_dirs = HomeDirectoryManager.detectHomeDirectories()
added_count = 0
for dir_info in detected_dirs:
# Check if directory already exists in database
if not HomeDirectory.objects.filter(path=dir_info['path']).exists():
# Create new home directory entry
home_dir = HomeDirectory(
name=dir_info['name'],
path=dir_info['path'],
is_active=True,
is_default=(dir_info['path'] == '/home')
)
home_dir.save()
added_count += 1
return JsonResponse({
'status': 1,
'message': f'Detected and added {added_count} new home directories',
'added_count': added_count
})
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error detecting home directories: {str(e)}")
return JsonResponse({'status': 0, 'error_message': str(e)})
def updateHomeDirectory(request):
"""Update home directory settings"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
data = json.loads(request.body)
home_dir_id = data.get('id')
is_active = data.get('is_active', True)
is_default = data.get('is_default', False)
max_users = data.get('max_users', 0)
description = data.get('description', '')
try:
home_dir = HomeDirectory.objects.get(id=home_dir_id)
# If setting as default, unset other defaults
if is_default:
HomeDirectory.objects.filter(is_default=True).update(is_default=False)
home_dir.is_active = is_active
home_dir.is_default = is_default
home_dir.max_users = max_users
home_dir.description = description
home_dir.save()
return JsonResponse({'status': 1, 'message': 'Home directory updated successfully'})
except HomeDirectory.DoesNotExist:
return JsonResponse({'status': 0, 'error_message': 'Home directory not found'})
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error updating home directory: {str(e)}")
return JsonResponse({'status': 0, 'error_message': str(e)})
def deleteHomeDirectory(request):
"""Delete home directory (only if no users assigned)"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
data = json.loads(request.body)
home_dir_id = data.get('id')
try:
home_dir = HomeDirectory.objects.get(id=home_dir_id)
# Check if any users are assigned to this home directory
user_count = UserHomeMapping.objects.filter(home_directory=home_dir).count()
if user_count > 0:
return JsonResponse({
'status': 0,
'error_message': f'Cannot delete home directory. {user_count} users are assigned to it.'
})
# Don't allow deletion of /home (default)
if home_dir.path == '/home':
return JsonResponse({
'status': 0,
'error_message': 'Cannot delete the default /home directory'
})
home_dir.delete()
return JsonResponse({'status': 1, 'message': 'Home directory deleted successfully'})
except HomeDirectory.DoesNotExist:
return JsonResponse({'status': 0, 'error_message': 'Home directory not found'})
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error deleting home directory: {str(e)}")
return JsonResponse({'status': 0, 'error_message': str(e)})
def getHomeDirectoryStats(request):
"""Get home directory statistics"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
stats = HomeDirectoryManager.getHomeDirectoryStats()
return JsonResponse({'status': 1, 'stats': stats})
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error getting home directory stats: {str(e)}")
return JsonResponse({'status': 0, 'error_message': str(e)})
def getUserHomeDirectories(request):
"""Get available home directories for user creation"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1 and currentACL['createNewUser'] != 1:
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
# Get active home directories
home_dirs = HomeDirectory.objects.filter(is_active=True).order_by('name')
directories = []
for home_dir in home_dirs:
directories.append({
'id': home_dir.id,
'name': home_dir.name,
'path': home_dir.path,
'available_space': home_dir.get_available_space(),
'user_count': home_dir.get_user_count(),
'is_default': home_dir.is_default,
'description': home_dir.description
})
return JsonResponse({'status': 1, 'directories': directories})
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error getting user home directories: {str(e)}")
return JsonResponse({'status': 0, 'error_message': str(e)})
def migrateUser(request):
"""Migrate user to different home directory"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
data = json.loads(request.body)
username = data.get('username')
target_home_id = data.get('target_home_id')
try:
user = Administrator.objects.get(userName=username)
target_home = HomeDirectory.objects.get(id=target_home_id)
# Get current home directory
try:
current_mapping = UserHomeMapping.objects.get(user=user)
current_home = current_mapping.home_directory
except UserHomeMapping.DoesNotExist:
current_home = HomeDirectory.objects.filter(is_default=True).first()
if not current_home:
current_home = HomeDirectory.objects.first()
if not current_home:
return JsonResponse({'status': 0, 'error_message': 'No home directory found for user'})
# Perform migration
success, message = HomeDirectoryManager.migrateUser(
username,
current_home.path,
target_home.path
)
if success:
# Update user mapping
UserHomeMapping.objects.update_or_create(
user=user,
defaults={'home_directory': target_home}
)
return JsonResponse({'status': 1, 'message': message})
else:
return JsonResponse({'status': 0, 'error_message': message})
except Administrator.DoesNotExist:
return JsonResponse({'status': 0, 'error_message': 'User not found'})
except HomeDirectory.DoesNotExist:
return JsonResponse({'status': 0, 'error_message': 'Target home directory not found'})
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error migrating user: {str(e)}")
return JsonResponse({'status': 0, 'error_message': str(e)})

View File

@@ -0,0 +1 @@
# Management commands package

View File

@@ -0,0 +1 @@
# Management commands

View File

@@ -0,0 +1,49 @@
#!/usr/local/CyberCP/bin/python
from django.core.management.base import BaseCommand
from userManagment.homeDirectoryManager import HomeDirectoryManager
from userManagment.models import HomeDirectory
import os
class Command(BaseCommand):
help = 'Initialize home directories for CyberPanel'
def handle(self, *args, **options):
self.stdout.write('Initializing home directories...')
# Detect home directories
detected_dirs = HomeDirectoryManager.detectHomeDirectories()
if not detected_dirs:
self.stdout.write(self.style.WARNING('No home directories detected'))
return
# Create default /home if it doesn't exist
if not os.path.exists('/home'):
self.stdout.write('Creating default /home directory...')
os.makedirs('/home', mode=0o755)
detected_dirs.insert(0, {
'path': '/home',
'name': 'home',
'available_space': HomeDirectoryManager.getAvailableSpace('/home'),
'total_space': HomeDirectoryManager.getTotalSpace('/home'),
'user_count': 0
})
# Create database entries
created_count = 0
for dir_info in detected_dirs:
if not HomeDirectory.objects.filter(path=dir_info['path']).exists():
home_dir = HomeDirectory(
name=dir_info['name'],
path=dir_info['path'],
is_active=True,
is_default=(dir_info['path'] == '/home'),
description=f"Auto-detected home directory: {dir_info['path']}"
)
home_dir.save()
created_count += 1
self.stdout.write(f'Created home directory: {dir_info["name"]} ({dir_info["path"]})')
self.stdout.write(
self.style.SUCCESS(f'Successfully initialized {created_count} home directories')
)

View File

@@ -0,0 +1,48 @@
# Generated migration for home directories feature
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('loginSystem', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='HomeDirectory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Directory name (e.g., home, home2)', max_length=50, unique=True)),
('path', models.CharField(help_text='Full path to home directory', max_length=255, unique=True)),
('is_active', models.BooleanField(default=True, help_text='Whether this home directory is active')),
('is_default', models.BooleanField(default=False, help_text='Whether this is the default home directory')),
('max_users', models.IntegerField(default=0, help_text='Maximum number of users (0 = unlimited)')),
('description', models.TextField(blank=True, help_text='Description of this home directory')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Home Directory',
'verbose_name_plural': 'Home Directories',
'db_table': 'home_directories',
},
),
migrations.CreateModel(
name='UserHomeMapping',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('home_directory', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='userManagment.homedirectory')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='home_mapping', to='loginSystem.administrator')),
],
options={
'verbose_name': 'User Home Mapping',
'verbose_name_plural': 'User Home Mappings',
'db_table': 'user_home_mappings',
},
),
]

View File

@@ -1,6 +1,85 @@
# -*- coding: utf-8 -*-
from django.db import models
from loginSystem.models import Administrator
# Create your models here.
class HomeDirectory(models.Model):
"""
Model to store home directory configurations
"""
name = models.CharField(max_length=50, unique=True, help_text="Directory name (e.g., home, home2)")
path = models.CharField(max_length=255, unique=True, help_text="Full path to home directory")
is_active = models.BooleanField(default=True, help_text="Whether this home directory is active")
is_default = models.BooleanField(default=False, help_text="Whether this is the default home directory")
max_users = models.IntegerField(default=0, help_text="Maximum number of users (0 = unlimited)")
description = models.TextField(blank=True, help_text="Description of this home directory")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'home_directories'
verbose_name = 'Home Directory'
verbose_name_plural = 'Home Directories'
def __str__(self):
return f"{self.name} ({self.path})"
def get_available_space(self):
"""Get available space in bytes"""
try:
import os
statvfs = os.statvfs(self.path)
return statvfs.f_frsize * statvfs.f_bavail
except:
return 0
def get_total_space(self):
"""Get total space in bytes"""
try:
import os
statvfs = os.statvfs(self.path)
return statvfs.f_frsize * statvfs.f_blocks
except:
return 0
def get_user_count(self):
"""Get number of users in this home directory"""
try:
import os
count = 0
if os.path.exists(self.path):
for item in os.listdir(self.path):
item_path = os.path.join(self.path, item)
if os.path.isdir(item_path) and not item.startswith('.'):
# Check if it's a user directory (has public_html)
if os.path.exists(os.path.join(item_path, 'public_html')):
count += 1
return count
except:
return 0
def get_usage_percentage(self):
"""Get usage percentage"""
try:
total = self.get_total_space()
if total > 0:
used = total - self.get_available_space()
return (used / total) * 100
return 0
except:
return 0
class UserHomeMapping(models.Model):
"""
Model to map users to their home directories
"""
user = models.OneToOneField(Administrator, on_delete=models.CASCADE, related_name='home_mapping')
home_directory = models.ForeignKey(HomeDirectory, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'user_home_mappings'
verbose_name = 'User Home Mapping'
verbose_name_plural = 'User Home Mappings'
def __str__(self):
return f"{self.user.userName} -> {self.home_directory.name}"

View File

@@ -342,6 +342,21 @@
<p class="help-text">{% trans "Choose the security level for this account" %}</p>
</div>
<div class="form-group">
<label class="form-label">{% trans "Home Directory" %}</label>
<select ng-model="selectedHomeDirectory" class="form-control" ng-change="updateHomeDirectoryInfo()">
<option value="">{% trans "Auto-select (Best Available)" %}</option>
<option ng-repeat="dir in homeDirectories" value="{{dir.id}}">
{{dir.name}} ({{dir.path}}) - {{dir.available_space | filesize}} available
</option>
</select>
<p class="help-text">{% trans "Choose the home directory for this user's files" %}</p>
<div ng-show="selectedHomeDirectoryInfo" class="alert alert-info" style="margin-top: 10px;">
<strong>{{selectedHomeDirectoryInfo.name}}:</strong> {{selectedHomeDirectoryInfo.description || 'No description available'}}<br>
<small>Available Space: {{selectedHomeDirectoryInfo.available_space | filesize}} | Users: {{selectedHomeDirectoryInfo.user_count}}</small>
</div>
</div>
<div class="form-group" style="margin-top: 2rem;">
<button type="button" ng-click="createUserFunc()" class="btn-primary">
<i class="fa fa-user-plus"></i> {% trans "Create User" %}
@@ -368,4 +383,99 @@
</div>
</div>
<script>
app.controller('createUserCtr', function ($scope, $http, $timeout) {
$scope.homeDirectories = [];
$scope.selectedHomeDirectory = '';
$scope.selectedHomeDirectoryInfo = null;
// Load home directories on page load
$scope.loadHomeDirectories = function() {
$http.post('/userManagement/getUserHomeDirectories/', {})
.then(function(response) {
if (response.data.status === 1) {
$scope.homeDirectories = response.data.directories;
}
})
.catch(function(error) {
console.error('Error loading home directories:', error);
});
};
// Update home directory info when selection changes
$scope.updateHomeDirectoryInfo = function() {
if ($scope.selectedHomeDirectory) {
$scope.selectedHomeDirectoryInfo = $scope.homeDirectories.find(function(dir) {
return dir.id == $scope.selectedHomeDirectory;
});
} else {
$scope.selectedHomeDirectoryInfo = null;
}
};
// Initialize
$scope.loadHomeDirectories();
// Existing controller code...
$scope.firstName = '';
$scope.lastName = '';
$scope.email = '';
$scope.userName = '';
$scope.password = '';
$scope.websitesLimits = 0;
$scope.selectedACL = 'user';
$scope.securityLevel = 'HIGH';
$scope.userCreated = true;
$scope.userCreationFailed = true;
$scope.couldNotConnect = true;
$scope.combinedLength = true;
$scope.generatedPasswordView = true;
$scope.errorMessage = '';
$scope.generatePassword = function () {
$scope.password = Math.random().toString(36).slice(-10);
$scope.generatedPasswordView = false;
};
$scope.usePassword = function () {
$scope.generatedPasswordView = true;
};
$scope.createUserFunc = function () {
$scope.userCreated = true;
$scope.userCreationFailed = true;
$scope.couldNotConnect = true;
$scope.combinedLength = true;
if ($scope.createUser.$valid) {
if ($scope.firstName.length + $scope.lastName.length > 20) {
$scope.combinedLength = false;
return;
}
$http.post('/userManagement/submitUserCreation/', {
'firstName': $scope.firstName,
'lastName': $scope.lastName,
'email': $scope.email,
'userName': $scope.userName,
'password': $scope.password,
'websitesLimit': $scope.websitesLimits,
'selectedACL': $scope.selectedACL,
'securityLevel': $scope.securityLevel,
'selectedHomeDirectory': $scope.selectedHomeDirectory
}).then(function (data) {
if (data.data.createStatus === 1) {
$scope.userCreated = false;
} else {
$scope.userCreationFailed = false;
$scope.errorMessage = data.data.error_message;
}
}, function (data) {
$scope.couldNotConnect = false;
});
}
};
});
</script>
{% endblock %}

View File

@@ -0,0 +1,688 @@
{% extends "baseTemplate/index.html" %}
{% load static %}
{% block title %}
<title>Home Directory Management - CyberPanel</title>
{% endblock %}
{% block header_scripts %}
<style>
/* Modern page styles matching modifyACL design */
.page-wrapper {
background: transparent;
padding: 20px;
}
.page-container {
max-width: 1200px;
margin: 0 auto;
}
.page-header {
margin-bottom: 30px;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin-bottom: 8px;
}
.page-subtitle {
font-size: 14px;
color: var(--text-secondary, #8893a7);
}
/* Card styles */
.content-card {
background: var(--bg-secondary, white);
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid var(--border-color, #e8e9ff);
margin-bottom: 25px;
}
.card-title {
font-size: 18px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 10px;
}
.card-title::before {
content: '';
width: 4px;
height: 24px;
background: var(--accent-color, #5b5fcf);
border-radius: 2px;
}
/* Statistics grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 10px;
padding: 20px;
text-align: center;
transition: all 0.2s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 15px;
font-size: 20px;
color: white;
}
.stat-icon.bg-info { background: var(--info-color, #17a2b8); }
.stat-icon.bg-success { background: var(--success-color, #28a745); }
.stat-icon.bg-warning { background: var(--warning-color, #ffc107); }
.stat-icon.bg-primary { background: var(--accent-color, #5b5fcf); }
.stat-number {
font-size: 24px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin-bottom: 5px;
}
.stat-label {
font-size: 13px;
color: var(--text-secondary, #8893a7);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
/* Table styles */
.table-container {
background: var(--bg-secondary, white);
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--border-color, #e8e9ff);
}
.table {
margin: 0;
background: transparent;
}
.table thead th {
background: var(--bg-hover, #f8f9ff);
border: none;
padding: 15px 20px;
font-size: 13px;
font-weight: 700;
color: var(--text-secondary, #64748b);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.table tbody td {
border: none;
padding: 15px 20px;
border-bottom: 1px solid var(--border-color, #e8e9ff);
vertical-align: middle;
}
.table tbody tr:hover {
background: var(--bg-hover, #f8f9ff);
}
/* Badge styles */
.badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.badge-success {
background: var(--success-bg, #e8f5e9);
color: var(--success-text, #2e7d32);
}
.badge-secondary {
background: var(--secondary-bg, #f5f5f5);
color: var(--secondary-text, #6c757d);
}
.badge-primary {
background: var(--accent-color, #5b5fcf);
color: white;
}
.badge-info {
background: var(--info-bg, #e3f2fd);
color: var(--info-text, #1565c0);
}
/* Progress bar */
.progress {
height: 8px;
border-radius: 4px;
background: var(--bg-hover, #f8f9ff);
overflow: hidden;
}
.progress-bar {
border-radius: 4px;
transition: width 0.3s ease;
}
.progress-bar.bg-success { background: var(--success-color, #28a745); }
.progress-bar.bg-warning { background: var(--warning-color, #ffc107); }
.progress-bar.bg-danger { background: var(--danger-color, #dc3545); }
/* Button styles */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-primary {
background: var(--accent-color, #5b5fcf);
color: white;
}
.btn-primary:hover {
background: #4a4fc4;
transform: translateY(-1px);
}
.btn-success {
background: var(--success-color, #28a745);
color: white;
}
.btn-success:hover {
background: #218838;
transform: translateY(-1px);
}
.btn-outline-primary {
background: transparent;
color: var(--accent-color, #5b5fcf);
border: 1px solid var(--accent-color, #5b5fcf);
}
.btn-outline-primary:hover {
background: var(--accent-color, #5b5fcf);
color: white;
}
.btn-info {
background: var(--info-color, #17a2b8);
color: white;
}
.btn-danger {
background: var(--danger-color, #dc3545);
color: white;
}
.btn-sm {
padding: 6px 12px;
font-size: 12px;
}
/* Action buttons */
.btn-group {
display: flex;
gap: 5px;
}
/* Modal styles */
.modal-content {
border-radius: 10px;
border: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.modal-header {
background: var(--bg-hover, #f8f9ff);
border-bottom: 1px solid var(--border-color, #e8e9ff);
border-radius: 10px 10px 0 0;
}
.modal-title {
font-weight: 700;
color: var(--text-primary, #2f3640);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--text-secondary, #64748b);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-control {
width: 100%;
padding: 10px 14px;
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 6px;
font-size: 14px;
color: var(--text-primary, #2f3640);
background: var(--bg-secondary, white);
transition: all 0.2s ease;
}
.form-control:focus {
outline: none;
border-color: var(--accent-color, #5b5fcf);
box-shadow: 0 0 0 3px rgba(91,95,207,0.1);
}
.form-check {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.form-check-input {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: var(--accent-color, #5b5fcf);
}
.form-check-label {
font-size: 14px;
color: var(--text-primary, #2f3640);
cursor: pointer;
user-select: none;
}
/* Responsive */
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: 1fr;
}
.content-card {
padding: 20px;
}
.table-container {
overflow-x: auto;
}
}
</style>
{% endblock %}
{% block content %}
<div class="page-wrapper">
<div class="page-container">
<div class="page-header">
<h1 class="page-title">Home Directory Management</h1>
<p class="page-subtitle">Manage multiple home directories for storage balance and user distribution</p>
</div>
<!-- Statistics Overview -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon bg-info">
<i class="fas fa-folder"></i>
</div>
<div class="stat-number" id="totalDirectories">{{ stats.total_directories|default:0 }}</div>
<div class="stat-label">Total Directories</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-success">
<i class="fas fa-users"></i>
</div>
<div class="stat-number" id="totalUsers">{{ stats.total_users|default:0 }}</div>
<div class="stat-label">Total Users</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-warning">
<i class="fas fa-hdd"></i>
</div>
<div class="stat-number" id="totalSpace">{{ stats.total_space|default:0|filesizeformat }}</div>
<div class="stat-label">Total Space</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-primary">
<i class="fas fa-chart-pie"></i>
</div>
<div class="stat-number" id="availableSpace">{{ stats.total_available|default:0|filesizeformat }}</div>
<div class="stat-label">Available Space</div>
</div>
</div>
<div class="content-card">
<div class="card-title">
<i class="fas fa-server"></i>
Home Directories
<div style="margin-left: auto;">
<button type="button" class="btn btn-primary btn-sm" onclick="detectHomeDirectories()">
<i class="fas fa-search"></i> Detect Directories
</button>
<button type="button" class="btn btn-success btn-sm" onclick="refreshStats()">
<i class="fas fa-sync"></i> Refresh Stats
</button>
</div>
</div>
<div class="table-container">
<table class="table" id="homeDirectoriesTable">
<thead>
<tr>
<th>Name</th>
<th>Path</th>
<th>Users</th>
<th>Total Space</th>
<th>Available Space</th>
<th>Usage %</th>
<th>Status</th>
<th>Default</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for home_dir in home_directories %}
<tr data-id="{{ home_dir.id }}">
<td>
<strong>{{ home_dir.name }}</strong>
{% if home_dir.description %}
<br><small class="text-muted">{{ home_dir.description }}</small>
{% endif %}
</td>
<td><code>{{ home_dir.path }}</code></td>
<td>
<span class="badge badge-info">{{ home_dir.get_user_count }}</span>
{% if home_dir.max_users > 0 %}
/ {{ home_dir.max_users }}
{% endif %}
</td>
<td>{{ home_dir.get_total_space|filesizeformat }}</td>
<td>{{ home_dir.get_available_space|filesizeformat }}</td>
<td>
<div class="progress progress-sm">
<div class="progress-bar bg-{% if home_dir.get_usage_percentage > 80 %}danger{% elif home_dir.get_usage_percentage > 60 %}warning{% else %}success{% endif %}"
style="width: {{ home_dir.get_usage_percentage }}%"></div>
</div>
<small>{{ home_dir.get_usage_percentage|floatformat:1 }}%</small>
</td>
<td>
<span class="badge badge-{% if home_dir.is_active %}success{% else %}secondary{% endif %}">
{% if home_dir.is_active %}Active{% else %}Inactive{% endif %}
</span>
</td>
<td>
{% if home_dir.is_default %}
<span class="badge badge-primary">Default</span>
{% else %}
<button class="btn btn-sm btn-outline-primary" onclick="setDefault({{ home_dir.id }})">Set Default</button>
{% endif %}
</td>
<td>
<div class="btn-group">
<button class="btn btn-sm btn-info" onclick="editHomeDirectory({{ home_dir.id }})" title="Edit">
<i class="fas fa-edit"></i>
</button>
{% if not home_dir.is_default and home_dir.get_user_count == 0 %}
<button class="btn btn-sm btn-danger" onclick="deleteHomeDirectory({{ home_dir.id }})" title="Delete">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Edit Home Directory Modal -->
<div class="modal fade" id="editHomeDirectoryModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit Home Directory</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<form id="editHomeDirectoryForm">
<input type="hidden" id="editHomeDirectoryId">
<div class="form-group">
<label for="editDescription">Description</label>
<textarea class="form-control" id="editDescription" rows="3" placeholder="Optional description"></textarea>
</div>
<div class="form-group">
<label for="editMaxUsers">Maximum Users</label>
<input type="number" class="form-control" id="editMaxUsers" min="0" placeholder="0 = unlimited">
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="editIsActive">
<label class="form-check-label" for="editIsActive">Active</label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="editIsDefault">
<label class="form-check-label" for="editIsDefault">Default Directory</label>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveHomeDirectory()">Save Changes</button>
</div>
</div>
</div>
</div>
<script>
function detectHomeDirectories() {
$.ajax({
url: '/userManagement/detectHomeDirectories/',
type: 'POST',
data: JSON.stringify({}),
contentType: 'application/json',
success: function(data) {
if (data.status === 1) {
showNotification('success', data.message);
location.reload();
} else {
showNotification('error', data.error_message);
}
},
error: function() {
showNotification('error', 'Error detecting home directories');
}
});
}
function refreshStats() {
$.ajax({
url: '/userManagement/getHomeDirectoryStats/',
type: 'POST',
data: JSON.stringify({}),
contentType: 'application/json',
success: function(data) {
if (data.status === 1) {
updateStatsDisplay(data.stats);
} else {
showNotification('error', data.error_message);
}
},
error: function() {
showNotification('error', 'Error refreshing stats');
}
});
}
function updateStatsDisplay(stats) {
$('#totalDirectories').text(stats.total_directories);
$('#totalUsers').text(stats.total_users);
$('#totalSpace').text(formatBytes(stats.total_space));
$('#availableSpace').text(formatBytes(stats.total_available));
}
function editHomeDirectory(id) {
// Get home directory data and populate modal
const row = $(`tr[data-id="${id}"]`);
const name = row.find('td:first strong').text();
const path = row.find('td:nth-child(2) code').text();
$('#editHomeDirectoryId').val(id);
$('#editDescription').val(row.find('td:first small').text() || '');
$('#editIsActive').prop('checked', row.find('.badge-success').length > 0);
$('#editIsDefault').prop('checked', row.find('.badge-primary').length > 0);
$('#editHomeDirectoryModal').modal('show');
}
function saveHomeDirectory() {
const id = $('#editHomeDirectoryId').val();
const data = {
id: id,
description: $('#editDescription').val(),
max_users: parseInt($('#editMaxUsers').val()) || 0,
is_active: $('#editIsActive').is(':checked'),
is_default: $('#editIsDefault').is(':checked')
};
$.ajax({
url: '/userManagement/updateHomeDirectory/',
type: 'POST',
data: JSON.stringify(data),
contentType: 'application/json',
success: function(response) {
if (response.status === 1) {
showNotification('success', response.message);
$('#editHomeDirectoryModal').modal('hide');
location.reload();
} else {
showNotification('error', response.error_message);
}
},
error: function() {
showNotification('error', 'Error updating home directory');
}
});
}
function setDefault(id) {
if (confirm('Are you sure you want to set this as the default home directory?')) {
const data = {
id: id,
is_default: true,
is_active: true
};
$.ajax({
url: '/userManagement/updateHomeDirectory/',
type: 'POST',
data: JSON.stringify(data),
contentType: 'application/json',
success: function(response) {
if (response.status === 1) {
showNotification('success', 'Default home directory updated');
location.reload();
} else {
showNotification('error', response.error_message);
}
},
error: function() {
showNotification('error', 'Error updating default home directory');
}
});
}
}
function deleteHomeDirectory(id) {
if (confirm('Are you sure you want to delete this home directory? This action cannot be undone.')) {
$.ajax({
url: '/userManagement/deleteHomeDirectory/',
type: 'POST',
data: JSON.stringify({id: id}),
contentType: 'application/json',
success: function(response) {
if (response.status === 1) {
showNotification('success', response.message);
location.reload();
} else {
showNotification('error', response.error_message);
}
},
error: function() {
showNotification('error', 'Error deleting home directory');
}
});
}
}
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function showNotification(type, message) {
// Use CyberPanel's notification system
if (type === 'success') {
toastr.success(message);
} else {
toastr.error(message);
}
}
// Initialize DataTable
$(document).ready(function() {
$('#homeDirectoriesTable').DataTable({
"responsive": true,
"lengthChange": false,
"autoWidth": false,
"order": [[0, "asc"]]
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,259 @@
{% extends "baseTemplate/index.html" %}
{% load static %}
{% block title %}
<title>User Migration - CyberPanel</title>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">User Migration</h3>
<div class="card-tools">
<button type="button" class="btn btn-primary btn-sm" onclick="refreshData()">
<i class="fas fa-sync"></i> Refresh
</button>
</div>
</div>
<div class="card-body">
<!-- User Selection -->
<div class="row mb-4">
<div class="col-md-6">
<div class="form-group">
<label>Select User to Migrate</label>
<select class="form-control" id="userSelect" onchange="loadUserInfo()">
<option value="">Choose a user...</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label>Target Home Directory</label>
<select class="form-control" id="targetHomeSelect">
<option value="">Choose target home directory...</option>
</select>
</div>
</div>
</div>
<!-- User Info -->
<div id="userInfo" class="alert alert-info" style="display: none;">
<h5>Current User Information</h5>
<div id="userDetails"></div>
</div>
<!-- Migration Button -->
<div class="text-center">
<button type="button" class="btn btn-warning btn-lg" onclick="migrateUser()" id="migrateBtn" disabled>
<i class="fas fa-exchange-alt"></i> Migrate User
</button>
</div>
<!-- Migration Progress -->
<div id="migrationProgress" class="alert alert-info" style="display: none;">
<h5>Migration in Progress...</h5>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 100%"></div>
</div>
<p>Please wait while the user is being migrated. This may take a few minutes.</p>
</div>
<!-- Migration Result -->
<div id="migrationResult" class="alert" style="display: none;">
<h5>Migration Result</h5>
<div id="migrationMessage"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let users = [];
let homeDirectories = [];
function loadUsers() {
$.ajax({
url: '/userManagement/fetchTableUsers/',
type: 'POST',
data: JSON.stringify({}),
contentType: 'application/json',
success: function(response) {
if (response.status === 1) {
users = JSON.parse(response.data);
updateUserSelect();
}
},
error: function() {
showNotification('error', 'Error loading users');
}
});
}
function loadHomeDirectories() {
$.ajax({
url: '/userManagement/getUserHomeDirectories/',
type: 'POST',
data: JSON.stringify({}),
contentType: 'application/json',
success: function(response) {
if (response.status === 1) {
homeDirectories = response.directories;
updateHomeDirectorySelect();
}
},
error: function() {
showNotification('error', 'Error loading home directories');
}
});
}
function updateUserSelect() {
const select = document.getElementById('userSelect');
select.innerHTML = '<option value="">Choose a user...</option>';
users.forEach(function(user) {
const option = document.createElement('option');
option.value = user.name;
option.textContent = user.name + ' (' + user.owner + ')';
select.appendChild(option);
});
}
function updateHomeDirectorySelect() {
const select = document.getElementById('targetHomeSelect');
select.innerHTML = '<option value="">Choose target home directory...</option>';
homeDirectories.forEach(function(dir) {
const option = document.createElement('option');
option.value = dir.id;
option.textContent = dir.name + ' (' + dir.path + ') - ' + formatBytes(dir.available_space) + ' available';
select.appendChild(option);
});
}
function loadUserInfo() {
const username = document.getElementById('userSelect').value;
if (!username) {
document.getElementById('userInfo').style.display = 'none';
return;
}
const user = users.find(u => u.name === username);
if (user) {
document.getElementById('userDetails').innerHTML = `
<p><strong>Username:</strong> ${user.name}</p>
<p><strong>Owner:</strong> ${user.owner}</p>
<p><strong>ACL:</strong> ${user.acl}</p>
<p><strong>Disk Usage:</strong> ${user.diskUsage}</p>
<p><strong>Websites:</strong> ${user.websites}</p>
<p><strong>Status:</strong> ${user.state}</p>
`;
document.getElementById('userInfo').style.display = 'block';
}
updateMigrateButton();
}
function updateMigrateButton() {
const userSelected = document.getElementById('userSelect').value;
const homeSelected = document.getElementById('targetHomeSelect').value;
const migrateBtn = document.getElementById('migrateBtn');
if (userSelected && homeSelected) {
migrateBtn.disabled = false;
} else {
migrateBtn.disabled = true;
}
}
function migrateUser() {
const username = document.getElementById('userSelect').value;
const targetHomeId = document.getElementById('targetHomeSelect').value;
if (!username || !targetHomeId) {
showNotification('error', 'Please select both user and target home directory');
return;
}
if (!confirm(`Are you sure you want to migrate user "${username}" to the selected home directory? This action will move all user files and may take several minutes.`)) {
return;
}
// Show progress
document.getElementById('migrationProgress').style.display = 'block';
document.getElementById('migrationResult').style.display = 'none';
document.getElementById('migrateBtn').disabled = true;
$.ajax({
url: '/userManagement/migrateUser/',
type: 'POST',
data: JSON.stringify({
username: username,
target_home_id: targetHomeId
}),
contentType: 'application/json',
success: function(response) {
document.getElementById('migrationProgress').style.display = 'none';
document.getElementById('migrationResult').style.display = 'block';
document.getElementById('migrateBtn').disabled = false;
if (response.status === 1) {
document.getElementById('migrationResult').className = 'alert alert-success';
document.getElementById('migrationMessage').innerHTML = '<i class="fas fa-check-circle"></i> ' + response.message;
showNotification('success', response.message);
} else {
document.getElementById('migrationResult').className = 'alert alert-danger';
document.getElementById('migrationMessage').innerHTML = '<i class="fas fa-exclamation-circle"></i> ' + response.error_message;
showNotification('error', response.error_message);
}
},
error: function() {
document.getElementById('migrationProgress').style.display = 'none';
document.getElementById('migrationResult').style.display = 'block';
document.getElementById('migrationResult').className = 'alert alert-danger';
document.getElementById('migrationMessage').innerHTML = '<i class="fas fa-times-circle"></i> Error occurred during migration';
document.getElementById('migrateBtn').disabled = false;
showNotification('error', 'Error occurred during migration');
}
});
}
function refreshData() {
loadUsers();
loadHomeDirectories();
document.getElementById('userInfo').style.display = 'none';
document.getElementById('migrationResult').style.display = 'none';
document.getElementById('migrateBtn').disabled = true;
}
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function showNotification(type, message) {
if (type === 'success') {
toastr.success(message);
} else {
toastr.error(message);
}
}
// Initialize on page load
$(document).ready(function() {
loadUsers();
loadHomeDirectories();
// Update migrate button when selections change
document.getElementById('userSelect').addEventListener('change', updateMigrateButton);
document.getElementById('targetHomeSelect').addEventListener('change', updateMigrateButton);
});
</script>
{% endblock %}

View File

@@ -1,5 +1,6 @@
from django.urls import path
from . import views
from . import homeDirectoryViews
urlpatterns = [
path('', views.loadUserHome, name='loadUsersHome'),
@@ -27,4 +28,14 @@ urlpatterns = [
path('listUsers', views.listUsers, name='listUsers'),
path('fetchTableUsers', views.fetchTableUsers, name='fetchTableUsers'),
path('controlUserState', views.controlUserState, name='controlUserState'),
# Home Directory Management URLs
path('homeDirectoryManagement', homeDirectoryViews.loadHomeDirectoryManagement, name='homeDirectoryManagement'),
path('detectHomeDirectories', homeDirectoryViews.detectHomeDirectories, name='detectHomeDirectories'),
path('updateHomeDirectory', homeDirectoryViews.updateHomeDirectory, name='updateHomeDirectory'),
path('deleteHomeDirectory', homeDirectoryViews.deleteHomeDirectory, name='deleteHomeDirectory'),
path('getHomeDirectoryStats', homeDirectoryViews.getHomeDirectoryStats, name='getHomeDirectoryStats'),
path('getUserHomeDirectories', homeDirectoryViews.getUserHomeDirectories, name='getUserHomeDirectories'),
path('migrateUser', homeDirectoryViews.migrateUser, name='migrateUser'),
path('userMigration', views.userMigration, name='userMigration'),
]

View File

@@ -133,6 +133,7 @@ def submitUserCreation(request):
password = data['password']
websitesLimit = data['websitesLimit']
selectedACL = data['selectedACL']
selectedHomeDirectory = data.get('selectedHomeDirectory', '')
if ACLManager.CheckRegEx("^[\w'\-,.][^0-9_!¡?÷?¿/\\+=@#$%ˆ&*(){}|~<>;:[\]]{2,}$", firstName) == 0:
data_ret = {'status': 0, 'createStatus': 0, 'error_message': 'First Name can only contain alphabetic characters, and should be more than 2 characters long...'}
@@ -239,6 +240,42 @@ def submitUserCreation(request):
final_json = json.dumps(data_ret)
return HttpResponse(final_json)
# Handle home directory assignment
from .homeDirectoryManager import HomeDirectoryManager
from .models import HomeDirectory, UserHomeMapping
if selectedHomeDirectory:
# Use selected home directory
try:
home_dir = HomeDirectory.objects.get(id=selectedHomeDirectory)
home_path = home_dir.path
except HomeDirectory.DoesNotExist:
home_path = HomeDirectoryManager.getBestHomeDirectory()
else:
# Auto-select best home directory
home_path = HomeDirectoryManager.getBestHomeDirectory()
try:
home_dir = HomeDirectory.objects.get(path=home_path)
except HomeDirectory.DoesNotExist:
# Create home directory entry if it doesn't exist
home_dir = HomeDirectory.objects.create(
name=home_path.split('/')[-1],
path=home_path,
is_active=True,
is_default=(home_path == '/home')
)
# Create user directory
if HomeDirectoryManager.createUserDirectory(userName, home_path):
# Create user-home mapping
UserHomeMapping.objects.create(
user=newAdmin,
home_directory=home_dir
)
else:
# Log error but don't fail user creation
logging.CyberCPLogFileWriter.writeToFile(f"Failed to create user directory for {userName} in {home_path}")
data_ret = {'status': 1, 'createStatus': 1,
'error_message': "None"}
final_json = json.dumps(data_ret)
@@ -926,3 +963,19 @@ def controlUserState(request):
data_ret = {'status': 0, 'saveStatus': 0, 'error_message': "Not logged in as admin", }
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def userMigration(request):
"""Load user migration interface"""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadError()
proc = httpProc(request, 'userManagment/userMigration.html', {}, 'admin')
return proc.render()
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error loading user migration: {str(e)}")
return ACLManager.loadError()

View File

@@ -10730,6 +10730,39 @@ $("#modifyWebsiteLoading").hide();
$("#modifyWebsiteButton").hide();
app.controller('modifyWebsitesController', function ($scope, $http) {
// Initialize home directory variables
$scope.homeDirectories = [];
$scope.selectedHomeDirectory = '';
$scope.selectedHomeDirectoryInfo = null;
$scope.currentHomeDirectory = '';
// Load home directories on page load
$scope.loadHomeDirectories = function() {
$http.post('/userManagement/getUserHomeDirectories/', {})
.then(function(response) {
if (response.data.status === 1) {
$scope.homeDirectories = response.data.directories;
}
})
.catch(function(error) {
console.error('Error loading home directories:', error);
});
};
// Update home directory info when selection changes
$scope.updateHomeDirectoryInfo = function() {
if ($scope.selectedHomeDirectory) {
$scope.selectedHomeDirectoryInfo = $scope.homeDirectories.find(function(dir) {
return dir.id == $scope.selectedHomeDirectory;
});
} else {
$scope.selectedHomeDirectoryInfo = null;
}
};
// Initialize home directories
$scope.loadHomeDirectories();
$scope.fetchWebsites = function () {
@@ -10774,6 +10807,7 @@ app.controller('modifyWebsitesController', function ($scope, $http) {
$scope.webpacks = JSON.parse(response.data.packages);
$scope.adminNames = JSON.parse(response.data.adminNames);
$scope.currentAdmin = response.data.currentAdmin;
$scope.currentHomeDirectory = response.data.currentHomeDirectory || 'Default';
$("#webSiteDetailsToBeModified").fadeIn();
$("#websiteModifySuccess").fadeIn();
@@ -10801,6 +10835,7 @@ app.controller('modifyWebsitesController', function ($scope, $http) {
var email = $scope.adminEmail;
var phpVersion = $scope.phpSelection;
var admin = $scope.selectedAdmin;
var homeDirectory = $scope.selectedHomeDirectory;
$("#websiteModifyFailure").hide();
@@ -10817,7 +10852,8 @@ app.controller('modifyWebsitesController', function ($scope, $http) {
packForWeb: packForWeb,
email: email,
phpVersion: phpVersion,
admin: admin
admin: admin,
homeDirectory: homeDirectory
};
var config = {

View File

@@ -463,6 +463,31 @@
</select>
</div>
<div class="form-divider"></div>
<div class="form-group">
<label class="form-label">{% trans "Home Directory" %}</label>
<select ng-model="selectedHomeDirectory" class="form-control" ng-change="updateHomeDirectoryInfo()">
<option value="">-- {% trans "Select home directory" %} --</option>
<option ng-repeat="dir in homeDirectories" value="{{dir.id}}">
{{dir.name}} ({{dir.path}}) - {{dir.available_space | filesize}} available
</option>
</select>
<div class="current-value">
<span class="current-value-label">{% trans "Current:" %}</span> {$ currentHomeDirectory $}
</div>
<div ng-show="selectedHomeDirectoryInfo" class="info-box" style="margin-top: 10px;">
<i class="fas fa-info-circle"></i>
<div class="info-box-content">
<div class="info-box-title">{{selectedHomeDirectoryInfo.name}}</div>
<div class="info-box-text">
{{selectedHomeDirectoryInfo.description || 'No description available'}}<br>
<small>Available Space: {{selectedHomeDirectoryInfo.available_space | filesize}} | Users: {{selectedHomeDirectoryInfo.user_count}}</small>
</div>
</div>
</div>
</div>
<div class="button-container">
<button type="button" ng-click="modifyWebsiteFunc()" class="btn btn-primary">
<i class="fas fa-save"></i>

View File

@@ -3422,10 +3422,15 @@ context /cyberpanel_suspension_page.html {
email = modifyWeb.adminEmail
currentPack = modifyWeb.package.packageName
owner = modifyWeb.admin.userName
# Get current home directory information
from userManagment.homeDirectoryUtils import HomeDirectoryUtils
current_home = HomeDirectoryUtils.getUserHomeDirectoryObject(owner)
currentHomeDirectory = current_home.name if current_home else 'Default'
data_ret = {'status': 1, 'modifyStatus': 1, 'error_message': "None", "adminEmail": email,
"packages": json_data, "current_pack": currentPack, "adminNames": admin_data,
'currentAdmin': owner}
'currentAdmin': owner, 'currentHomeDirectory': currentHomeDirectory}
final_json = json.dumps(data_ret)
return HttpResponse(final_json)
@@ -3493,6 +3498,7 @@ context /cyberpanel_suspension_page.html {
email = data['email']
phpVersion = data['phpVersion']
newUser = data['admin']
homeDirectory = data.get('homeDirectory', '')
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'modifyWebsite') == 0:
@@ -3531,6 +3537,46 @@ context /cyberpanel_suspension_page.html {
modifyWeb.save()
# Handle home directory migration if specified
if homeDirectory:
from userManagment.homeDirectoryUtils import HomeDirectoryUtils
from userManagment.models import HomeDirectory, UserHomeMapping
try:
# Get the new home directory
new_home_dir = HomeDirectory.objects.get(id=homeDirectory)
# Get current home directory for the user
current_home = HomeDirectoryUtils.getUserHomeDirectoryObject(newUser)
if current_home and current_home.id != new_home_dir.id:
# Migrate user to new home directory
success, message = HomeDirectoryUtils.migrateUser(
newUser,
current_home.path,
new_home_dir.path
)
if success:
# Update user-home mapping
UserHomeMapping.objects.update_or_create(
user=newOwner,
defaults={'home_directory': new_home_dir}
)
else:
# Log error but don't fail the website modification
logging.CyberCPLogFileWriter.writeToFile(f"Failed to migrate user {newUser} to home directory {new_home_dir.path}: {message}")
elif not current_home:
# Create new mapping if user doesn't have one
UserHomeMapping.objects.create(
user=newOwner,
home_directory=new_home_dir
)
except Exception as e:
# Log error but don't fail the website modification
logging.CyberCPLogFileWriter.writeToFile(f"Error handling home directory change for {newUser}: {str(e)}")
## Update disk quota when package changes - Fix for GitHub issue #1442
if webpack.enforceDiskLimits:
spaceString = f'{webpack.diskSpace}M {webpack.diskSpace}M'