Add container management features: implement update, delete with data, and delete while preserving data functionalities. Update views, URLs, and templates for enhanced user interaction and data safety. Introduce modals for container updates and deletion options, improving overall user experience.

This commit is contained in:
Master3395
2025-09-18 22:16:42 +02:00
parent 7a84819361
commit 317b75bb9a
16 changed files with 1540 additions and 19 deletions

View File

@@ -91,8 +91,15 @@ class FTPManager:
except:
path = 'None'
# Handle custom quota settings
try:
customQuotaSize = int(data.get('customQuotaSize', 0))
enableCustomQuota = data.get('enableCustomQuota', False)
except:
customQuotaSize = 0
enableCustomQuota = False
result = FTPUtilities.submitFTPCreation(domainName, userName, password, path, admin.userName, api)
result = FTPUtilities.submitFTPCreation(domainName, userName, password, path, admin.userName, api, customQuotaSize, enableCustomQuota)
if result[0] == 1:
data_ret = {'status': 1, 'creatFTPStatus': 1, 'error_message': 'None'}
@@ -233,10 +240,19 @@ class FTPManager:
checker = 0
for items in records:
# Determine display quota
if items.custom_quota_enabled:
quota_display = f"{items.custom_quota_size}MB (Custom)"
else:
quota_display = f"{items.quotasize}MB (Package Default)"
dic = {'id': items.id,
'user': items.user,
'dir': items.dir,
'quotasize': str(items.quotasize) + "MB",
'quotasize': quota_display,
'custom_quota_enabled': items.custom_quota_enabled,
'custom_quota_size': items.custom_quota_size,
'package_quota': items.domain.package.diskSpace,
}
if checker == 0:
@@ -284,6 +300,42 @@ class FTPManager:
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def updateFTPQuota(self):
try:
userID = self.request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'listFTPAccounts') == 0:
return ACLManager.loadErrorJson('updateQuotaStatus', 0)
data = json.loads(self.request.body)
userName = data['ftpUserName']
customQuotaSize = int(data.get('customQuotaSize', 0))
enableCustomQuota = data.get('enableCustomQuota', False)
admin = Administrator.objects.get(pk=userID)
ftp = Users.objects.get(user=userName)
if currentACL['admin'] == 1:
pass
elif ftp.domain.admin != admin:
return ACLManager.loadErrorJson()
result = FTPUtilities.updateFTPQuota(userName, customQuotaSize, enableCustomQuota)
if result[0] == 1:
data_ret = {'status': 1, 'updateQuotaStatus': 1, 'error_message': "None"}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
else:
data_ret = {'status': 0, 'updateQuotaStatus': 0, 'error_message': result[1]}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'updateQuotaStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def installPureFTPD(self):
def pureFTPDServiceName():

View File

@@ -0,0 +1,23 @@
# Generated migration for FTP custom quota fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ftp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='users',
name='custom_quota_enabled',
field=models.BooleanField(default=False, help_text='Enable custom quota for this FTP user'),
),
migrations.AddField(
model_name='users',
name='custom_quota_size',
field=models.IntegerField(default=0, help_text='Custom quota size in MB (0 = use package default)'),
),
]

View File

@@ -16,6 +16,9 @@ class Users(models.Model):
dlbandwidth = models.IntegerField(db_column='DLBandwidth') # Field name made lowercase.
date = models.DateField(db_column='Date') # Field name made lowercase.
lastmodif = models.CharField(db_column='LastModif', max_length=255) # Field name made lowercase.
# New fields for individual quota management
custom_quota_enabled = models.BooleanField(default=False, help_text="Enable custom quota for this FTP user")
custom_quota_size = models.IntegerField(default=0, help_text="Custom quota size in MB (0 = use package default)")
class Meta:
db_table = 'users'

View File

@@ -72,6 +72,8 @@ app.controller('createFTPAccount', function ($scope, $http) {
ftpUserName: ftpUserName,
passwordByPass: ftpPassword,
path: path,
enableCustomQuota: $scope.enableCustomQuota || false,
customQuotaSize: $scope.customQuotaSize || 0,
};
var config = {
@@ -155,6 +157,13 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.generatedPasswordView = true;
};
// Quota management functions
$scope.toggleCustomQuota = function() {
if (!$scope.enableCustomQuota) {
$scope.customQuotaSize = 0;
}
};
});
/* Java script code to create account ends here */
@@ -333,6 +342,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.ftpLoading = false;
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
$scope.quotaManagementBox = true;
$scope.notificationsBox = true;
var globalFTPUsername = "";
@@ -493,6 +503,101 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.generatedPasswordView = true;
};
// Quota management functions
$scope.manageQuota = function (record) {
$scope.recordsFetched = true;
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = false;
$scope.quotaManagementBox = false;
$scope.notificationsBox = true;
$scope.ftpUsername = record.user;
globalFTPUsername = record.user;
// Set current quota info
$scope.currentQuotaInfo = record.quotasize;
$scope.packageQuota = record.package_quota;
$scope.enableCustomQuotaEdit = record.custom_quota_enabled;
$scope.customQuotaSizeEdit = record.custom_quota_size || 0;
};
$scope.toggleCustomQuotaEdit = function() {
if (!$scope.enableCustomQuotaEdit) {
$scope.customQuotaSizeEdit = 0;
}
};
$scope.updateQuotaBtn = function () {
$scope.ftpLoading = true;
url = "/ftp/updateFTPQuota";
var data = {
ftpUserName: globalFTPUsername,
customQuotaSize: parseInt($scope.customQuotaSizeEdit) || 0,
enableCustomQuota: $scope.enableCustomQuotaEdit || false,
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
if (response.data.updateQuotaStatus == 1) {
$scope.notificationsBox = false;
$scope.quotaUpdated = false;
$scope.ftpLoading = false;
$scope.domainFeteched = $scope.selectedDomain;
// Refresh the records to show updated quota
populateCurrentRecords();
// Show success notification
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Success!',
text: 'FTP quota updated successfully.',
type: 'success'
});
}
} else {
$scope.notificationsBox = false;
$scope.quotaUpdateFailed = false;
$scope.ftpLoading = false;
$scope.errorMessage = response.data.error_message;
// Show error notification
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
});
}
}
}
function cantLoadInitialDatas(response) {
$scope.notificationsBox = false;
$scope.couldNotConnect = false;
$scope.ftpLoading = false;
// Show error notification
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Error!',
text: 'Could not connect to server.',
type: 'error'
});
}
}
};
});

View File

@@ -372,6 +372,49 @@
background-color: var(--bg-hover, #f8f9ff) !important;
color: var(--accent-color, #5b5fcf) !important;
}
.quota-settings {
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 8px;
padding: 1.5rem;
}
.form-check {
display: flex;
align-items: center;
gap: 0.75rem;
}
.form-check-input {
width: 18px;
height: 18px;
margin: 0;
}
.form-check-label {
margin: 0;
font-weight: 500;
color: var(--text-primary, #1e293b);
cursor: pointer;
}
.custom-quota-input {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color, #e8e9ff);
}
.input-group-text {
background: var(--bg-secondary, #fff);
border: 1px solid var(--border-color, #e8e9ff);
color: var(--text-secondary, #64748b);
font-weight: 500;
}
.package-quota-info {
margin-top: 1rem;
}
</style>
<div class="modern-container" ng-controller="createFTPAccount">
@@ -483,6 +526,40 @@
type="text" class="form-control" ng-model="ftpPath">
</div>
<div ng-hide="ftpDetails" class="form-group">
<label class="form-label">{% trans "Disk Quota Settings" %}</label>
<div class="quota-settings">
<div class="form-check" style="margin-bottom: 1rem;">
<input type="checkbox" class="form-check-input" id="enableCustomQuota"
ng-model="enableCustomQuota" ng-change="toggleCustomQuota()">
<label class="form-check-label" for="enableCustomQuota">
<i class="fas fa-hdd"></i>
{% trans "Enable custom disk quota for this FTP user" %}
</label>
</div>
<div ng-show="enableCustomQuota" class="custom-quota-input">
<label class="form-label">{% trans "Custom Quota Size (MB)" %}</label>
<div class="input-group">
<input type="number" class="form-control" ng-model="customQuotaSize"
placeholder="{% trans 'Enter quota size in MB' %}" min="1">
<span class="input-group-text">MB</span>
</div>
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block; font-size: 0.875rem;">
<i class="fas fa-info-circle"></i>
{% trans "Leave unchecked to use the package's default quota" %}
</small>
</div>
<div ng-hide="enableCustomQuota" class="package-quota-info">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
{% trans "This FTP user will use the package's default disk quota" %}
</div>
</div>
</div>
</div>
<div style="margin-top: 2rem;">
<button type="button" ng-click="createFTPAccount()" class="btn-primary">
<i class="fas fa-plus-circle"></i>

View File

@@ -343,6 +343,53 @@
transform: translateX(0);
}
}
.quota-settings {
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 8px;
padding: 1.5rem;
}
.form-check {
display: flex;
align-items: center;
gap: 0.75rem;
}
.form-check-input {
width: 18px;
height: 18px;
margin: 0;
}
.form-check-label {
margin: 0;
font-weight: 500;
color: var(--text-primary, #1e293b);
cursor: pointer;
}
.custom-quota-input {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-color, #e8e9ff);
}
.input-group-text {
background: var(--bg-secondary, #fff);
border: 1px solid var(--border-color, #e8e9ff);
color: var(--text-secondary, #64748b);
font-weight: 500;
}
.package-quota-info {
margin-top: 1rem;
}
.current-quota-info {
margin-bottom: 1rem;
}
</style>
<div class="modern-container" ng-controller="listFTPAccounts">
@@ -440,16 +487,70 @@
</div>
</div>
<!-- Quota Management Section -->
<div ng-hide="quotaManagementBox" class="password-section">
<h3><i class="fas fa-hdd"></i> {% trans "Manage Quota for" %} {$ ftpUsername $}</h3>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label class="form-label">{% trans "Current Quota" %}</label>
<div class="current-quota-info">
<span class="quota-badge" ng-bind="currentQuotaInfo"></span>
</div>
</div>
<div class="form-group">
<div class="form-check" style="margin-bottom: 1rem;">
<input type="checkbox" class="form-check-input" id="enableCustomQuotaEdit"
ng-model="enableCustomQuotaEdit" ng-change="toggleCustomQuotaEdit()">
<label class="form-check-label" for="enableCustomQuotaEdit">
<i class="fas fa-hdd"></i>
{% trans "Enable custom disk quota for this FTP user" %}
</label>
</div>
<div ng-show="enableCustomQuotaEdit" class="custom-quota-input">
<label class="form-label">{% trans "Custom Quota Size (MB)" %}</label>
<div class="input-group">
<input type="number" class="form-control" ng-model="customQuotaSizeEdit"
placeholder="{% trans 'Enter quota size in MB' %}" min="1">
<span class="input-group-text">MB</span>
</div>
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block; font-size: 0.875rem;">
<i class="fas fa-info-circle"></i>
{% trans "Package default quota:" %} {$ packageQuota $}MB
</small>
</div>
<div ng-hide="enableCustomQuotaEdit" class="package-quota-info">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
{% trans "This FTP user will use the package's default disk quota" %}
</div>
</div>
</div>
<div style="margin-top: 1.5rem;">
<button type="button" ng-click="updateQuotaBtn()" class="btn-primary">
<i class="fas fa-save"></i>
{% trans "Update Quota" %}
</button>
</div>
</div>
</div>
</div>
<!-- FTP Accounts Table -->
<div ng-hide="ftpAccounts">
<table class="ftp-table">
<thead>
<tr>
<th style="width: 8%;">{% trans "ID" %}</th>
<th style="width: 25%;">{% trans "User Name" %}</th>
<th style="width: 35%;">{% trans "Directory" %}</th>
<th style="width: 20%;">{% trans "User Name" %}</th>
<th style="width: 30%;">{% trans "Directory" %}</th>
<th style="width: 15%;">{% trans "Quota" %}</th>
<th style="width: 17%; text-align: center;">{% trans "Actions" %}</th>
<th style="width: 27%; text-align: center;">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
@@ -469,10 +570,16 @@
<span class="quota-badge" ng-bind="record.quotasize"></span>
</td>
<td style="text-align: center;">
<button type="button" ng-click="changePassword(record.user)" class="btn-action">
<i class="fas fa-key"></i>
{% trans "Change Password" %}
</button>
<div style="display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap;">
<button type="button" ng-click="changePassword(record.user)" class="btn-action">
<i class="fas fa-key"></i>
{% trans "Password" %}
</button>
<button type="button" ng-click="manageQuota(record)" class="btn-action">
<i class="fas fa-hdd"></i>
{% trans "Quota" %}
</button>
</div>
</td>
</tr>
</tbody>

View File

@@ -15,4 +15,5 @@ urlpatterns = [
path('listFTPAccounts', views.listFTPAccounts, name='listFTPAccounts'),
path('getAllFTPAccounts', views.getAllFTPAccounts, name='getAllFTPAccounts'),
path('changePassword', views.changePassword, name='changePassword'),
path('updateFTPQuota', views.updateFTPQuota, name='updateFTPQuota'),
]

View File

@@ -214,5 +214,12 @@ def changePassword(request):
return coreResult
except KeyError:
return redirect(loadLoginPage)
def updateFTPQuota(request):
try:
fm = FTPManager(request)
return fm.updateFTPQuota()
except KeyError:
return redirect(loadLoginPage)