Files
openpanel/templates/admini/files/ftp.html
2024-10-25 01:44:26 +02:00

531 lines
20 KiB
HTML

{% extends 'base.html' %}
{% block content %}
<!-- "Add New Domain" form -->
<form action="/ftp/add" method="post" id="addFTPForm" style="display: none;">
<div class="col-md-12">
<div class="card mb-3" style="">
<div class="card-body">
<div class="row">
<div class="col-4">
<label for="domain_url">{{ _('Username:') }}</label>
<div class="input-group">
<input type="text" class="form-control" name="username" pattern="[A-Za-z0-9]+" id="new_ftp_username" placeholder="" required>
<div class="input-group-append">
<span class="input-group-text" data-toggle="tooltip" data-placement="bottom" title="{{ _('FTP username must end with Dot followed by your OpenPanel username') }} (example.{{ current_username }})." name="path">.{{ current_username }}</span>
</div>
</div>
</div>
<div class="col-4">
<label for="domain_name">{{ _('Pasword:') }}</label>
<div class="input-group">
<input type="password" value="" id="admin_password" name="password" class="form-control" required
minlength="8"
pattern="^(?=.*?[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@.\-_=\+]).{8,}$"
title="Password must be at least 8 characters long, contain at least one uppercase letter, one lowercase letter, one digit, and at least one special character from @.-_=+">
<div class="input-group-append">
<button class="btn btn-outline-success" type="button" id="generatePassword">
{{ _("Generate") }}
</button>
<button class="btn btn-outline-secondary" type="button" id="togglePassword">
<i class="bi bi-eye"></i>
</button>
</div>
</div>
</div>
<div class="col-4">
<label for="domain_name">{{ _('Path (folder):') }}</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" data-toggle="tooltip" data-placement="bottom" title="{{ _('Path must be under your home directory') }} /home/{{ current_username }}/">/home/{{ current_username }}/</span>
</div>
<input type="text" name="path" id="new_user_path" class="form-control">
</div>
</div>
</div>
<p class="lead mt-2">
<button type="submit" class="btn btn-lg btn-primary">{{ _('Add Account') }}</button>
</p>
</div>
</div>
</div>
</form>
<style>
thead {
border: 1px solid rgb(90 86 86 / 11%);
}
th {
text-transform: uppercase;
font-weight: 400;
}
[data-bs-theme=light] .domain_link {
color: black;
}
.domain_link {
border-bottom: 1px dashed #999;
text-decoration: none;
}
</style>
<p>{{ _('Use your FTP account with an FTP client such as ') }}<a data-e2e="link" data-component="link" tabindex="0" href="https://filezilla-project.org" role="link" target="_blank">FileZilla</a> {{ _('to transfer files to and from your website.') }}</p>
<button type="button" id="hiddenModalTrigger" style="display: none;" data-toggle="modal" data-target="#addFtpAccountModal"></button>
<div class="table-responsive">
<table class="table table-hover" id="ftp-accounts-table">
<thead>
<tr>
<th>{{ _('Username (Login)') }}</th>
<th data-toggle="tooltip" data-placement="bottom" title="{{ _('FTP sser has permissions to access only files and folders under this path.') }}" class="desktop-only">{{ _('Path') }}</th>
<!-- <th>Usage/Quota</th> -->
<th>{{ _('Actions') }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<script>
$(document).ready(function() {
// Function to fetch FTP accounts data via AJAX
function fetchFtpAccounts() {
$.ajax({
url: '/ftp/list',
method: 'GET',
dataType: 'json',
success: function(data) {
// Clear existing table content
$('#ftp-accounts-table tbody').empty();
// Check if data contains 'ftp_accounts' key
if ('ftp_accounts' in data) {
// Iterate through the FTP accounts and add rows
$.each(data.ftp_accounts, function(index, account) {
// Extract data from the account object
var username = account.username;
var path = account.path;
let strippedPath = path.replace('/home/{{ current_username }}/', '');
var usageQuota = account.usageQuota;
// Create a row with buttons for each FTP account
var row = '<tr class="domain_row">' +
'<td><i class="bi bi-person-fill"></i> <span id="databaseUsername">' + username + '</span><small class="mobile-only"><br><a href="/files/' + strippedPath + '"><i style="color: orange;" class="bi bi-folder-fill"></i> <span class="domain_link">/files/' + path + '</span></a></small></td>' +
'<td class="desktop-only"><a href="/files/' + strippedPath + '"><i style="color: orange;" class="bi bi-folder-fill"></i> <span class="domain_link" id="databasePath">' + path + '</span></a><button class="btn btn-transparent" style="float: right" type="button" data-bs-toggle="modal" data-bs-target="#changePathModal""><i class="bi bi-pencil-fill"></i><span class="desktop-only"> Change Path</span></button>' + '</td>' +
//'<td>' + usageQuota + '</td>' +
'<td>' +
'<div class="d-flex gap-2 mt-3 mt-md-0">' +
'<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#changePasswordModal"><i class="bi bi-key-fill"></i> Change Password</button>' +
'<form method="POST" action="{{ url_for("del_ftp_account") }}">' +
'<input type="hidden" name="username" value="' + username + '">' +
'<button class="btn btn-danger" type="button" onclick="confirmDelete(this);"><i class="bi bi-trash3"></i> Delete</button>' +
'</form>' +
//'<button class="btn btn-primary">Change Quota</button>' +
//'<button class="btn btn-primary" type="button">Change Path</button>' +
'</div>' +
'</td>' +
'</tr>';
$('#ftp-accounts-table tbody').append(row);
});
} else {
// Handle the case where the JSON response is unexpected
var errorMsg = '<tr><td colspan="4">No FTP accounts.</td></tr>';
$('#ftp-accounts-table tbody').append(errorMsg);
}
},
error: function(error) {
// Handle the AJAX error
console.error('Error fetching FTP accounts:', error);
var errorMsg = '<tr><td colspan="4">Error fetching FTP accounts data.</td></tr>';
$('#ftp-accounts-table tbody').append(errorMsg);
}
});
}
// load accounts
fetchFtpAccounts();
// add search
initializeFTPManagement();
// change password modal
changePassModal();
// change path modal
changePathModal();
});
</script>
</div>
<!-- Change Password Modal -->
<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-labelledby="changePasswordModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="changePasswordModalLabel">{{_('Change Password')}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label=" {{ _('Close') }}"></button>
</div>
<div class="modal-body">
<!-- Change Password Form Content -->
<form id="changePasswordForm" method="POST" action="{{ url_for('chn_ftp_pass') }}">
<div class="form-group">
<label for="username">{{_('FTP user')}}</label>
<input type="text" name="username" class="form-control" placeholder=" {{ _('User') }}" required disabled>
<input type="text" name="username" class="form-control" placeholder=" {{ _('User') }}" required hidden>
</div>
<div class="form-group">
<label for="new_password">{{_('New Password')}}</label>
<div class="input-group">
<input type="password" value="" id="new_password" name="new_password" class="form-control" required
minlength="8"
pattern="^(?=.*?[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@.\-_=\+]).{8,}$"
title="Password must be at least 8 characters long, contain at least one uppercase letter, one lowercase letter, one digit, and at least one special character from @.-_=+">
<div class="input-group-append">
<button class="btn btn-outline-success" type="button" id="generatePasswordinModal">
{{ _("Generate") }}
</button>
<button class="btn btn-outline-secondary" type="button" id="togglePasswordinModal">
<i class="bi bi-eye"></i>
</button>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="submit" form="changePasswordForm" class="btn btn-primary">{{_('Change Password')}}</button>
</div>
</div>
</div>
</div>
<!-- Change Path Modal -->
<div class="modal fade" id="changePathModal" tabindex="-1" aria-labelledby="changePathModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="changePathModalLabel">{{_('Change Path')}}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label=" {{ _('Close') }}"></button>
</div>
<div class="modal-body">
<!-- Change changePathModal Form Content -->
<form id="changePathForm" method="POST" action="{{ url_for('chn_ftp_path') }}">
<div class="form-group">
<label for="username">{{_('FTP user')}}</label>
<input type="text" name="username" class="form-control" placeholder=" {{ _('User') }}" required disabled>
<input type="text" name="username" class="form-control" placeholder=" {{ _('User') }}" required hidden>
</div>
<div class="form-group">
<label for="path">{{_('Current Path')}}</label>
<input type="text" name="current_path" class="form-control" value="" required disabled>
</div>
<div class="form-group">
<label for="new_path">{{_('New Path')}}</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text" data-toggle="tooltip" data-placement="bottom" title="{{ _('Path must be under your home directory') }} /home/{{ current_username }}/">/home/{{ current_username }}/</span>
</div>
<input type="text" name="new_path" class="form-control">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="submit" form="changePathForm" class="btn btn-primary">{{_('Change Path')}}</button>
</div>
</div>
</div>
</div>
</section>
<footer class="main-footer btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="{{ _('Searh') }}">
<input type="text" class="d-none form-control" placeholder="{{ _('Search accounts') }}" id="searchFTPInput">
{{ _('FTP server') }}:&nbsp; <b>{% if dedicated_ip != _("Unknown") %}{{ dedicated_ip }}{% else %}{{ server_ip }}{% endif %}</b>
</div>
<div class="col-4">
{{ _('FTP port') }}: <b>21</b>
</div>
<div class="ms-auto" role="group" aria-label="{{ _('Actions') }}">
<button type="button" class="btn btn-primary d-flex align-items-center gap-2" id="showAddFTPFormBtn"><i class="bi bi-plus-lg"></i> <span class="desktop-only">{{ _('Add Account') }}</span></button>
</div>
</footer>
<script>
// CREATE USER
const addFTPForm = document.getElementById("addFTPForm");
const showAddFTPFormBtn = document.getElementById("showAddFTPFormBtn"); // Corrected ID
// Ensure the addFTPForm exists
if (addFTPForm && showAddFTPFormBtn) {
showAddFTPFormBtn.addEventListener("click", function() {
// Toggle display of addFTPForm
addFTPForm.style.display = addFTPForm.style.display === "none" ? "block" : "none";
});
} else {
console.error("add new user button not found.");
}
// CHANGE PASSWORD
function changePassModal() {
// Handle the modal show event
$('#changePasswordModal').on('show.bs.modal', function (event) {
// Get the button that triggered the modal
var button = $(event.relatedTarget);
// Find the closest <tr> element to the button
var row = button.closest('tr');
var databaseUsername = row.find('#databaseUsername').text();
$('#changePasswordForm [name="username"]').val(databaseUsername);
});
};
// CHANGE PATH
function changePathModal() {
// Handle the modal show event
$('#changePathModal').on('show.bs.modal', function (event) {
// Get the button that triggered the modal
var button = $(event.relatedTarget);
// Find the closest <tr> element to the button
var row = button.closest('tr');
var databaseUsername = row.find('#databaseUsername').text();
var databasePath = row.find('#databasePath').text();
$('#changePathForm [name="username"]').val(databaseUsername);
$('#changePathForm [name="current_path"]').val(databasePath);
});
};
// DELETE USER
function confirmDelete(button) {
var countdown = 5;
var countdownActive = true; // Variable to track countdown status
// Change the button style and text
$(button).removeClass('btn-danger').addClass('btn-dark').html('<i class="bi bi-trash3-fill"></i> Confirm <span class="btn-indicator btn-indicator-mini bg-danger">' + countdown + '</span>');
// Interval to update countdown
var intervalId = setInterval(function () {
countdown--;
// Update the countdown value in the button text
$(button).find('.btn-indicator-mini').text(countdown);
// Remove the onclick event to prevent further changes on subsequent clicks
$(button).removeAttr('onclick');
// If countdown reaches 0, revert the button, clear the interval, and set countdownActive to false
if (countdown === 0) {
clearInterval(intervalId);
revertButton(button);
countdownActive = false;
}
}, 1000);
// Add a click event to the confirm button
$(button).on('click', function () {
// Check if countdown is active before allowing form submission
if (countdownActive) {
// Submit the parent form when the button is clicked during the countdown
$(button).closest('form').submit();
}
});
}
// Function to revert the button to its initial state
function revertButton(button) {
$(button).removeClass('btn-dark').addClass('btn-danger').html('<i class="bi bi-trash3"></i> Delete');
$(button).attr('onclick', 'confirmDelete(this);');
}
// SEARCH FTP USERS
function initializeFTPManagement() {
// Get reference to the search input
const searchFTPInput = document.getElementById("searchFTPInput");
// Get all domain rows (FTP account rows) to be used for filtering
const domainRows = document.querySelectorAll("#ftp-accounts-table tbody .domain_row");
// Handle search input changes
searchFTPInput.addEventListener("input", function() {
const searchTerm = searchFTPInput.value.trim().toLowerCase();
// Loop through domain rows and hide/show based on search term
domainRows.forEach(function(row) {
// Get the username span inside the td
const username = row.querySelector("td span").textContent.toLowerCase();
// Get the path element inside the second column
const pathElement = row.querySelector("td.desktop-only span");
const path = pathElement ? pathElement.textContent.toLowerCase() : '';
// Show row if search term matches username or path, otherwise hide it
if (username.includes(searchTerm) || path.includes(searchTerm)) {
row.style.display = "table-row";
} else {
row.style.display = "none";
}
});
});
}
// GENERATE PASS FOR NEW USER AND CHANGE PASS FORMS
function generateRandomStrongPassword(length) {
const lowercase = "abcdefghijklmnopqrstuvwxyz";
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const digits = "0123456789";
const special = "@.-_=+";
const allChars = lowercase + uppercase + digits + special;
let password = "";
// Ensure the password contains at least one of each required type
password += lowercase.charAt(Math.floor(Math.random() * lowercase.length));
password += uppercase.charAt(Math.floor(Math.random() * uppercase.length));
password += digits.charAt(Math.floor(Math.random() * digits.length));
password += special.charAt(Math.floor(Math.random() * special.length));
// Generate the rest of the password
for (let i = 4; i < length; i++) {
const randomIndex = Math.floor(Math.random() * allChars.length);
password += allChars.charAt(randomIndex);
}
// Shuffle the password to avoid predictable patterns
return password.split('').sort(() => 0.5 - Math.random()).join('');
}
// NEW USER FORM
function generateInitiallyFTPPassword() {
const generatedPassword = generateRandomStrongPassword(16);
document.getElementById("admin_password").value = generatedPassword;
document.getElementById("new_password").value = generatedPassword;
}
generateInitiallyFTPPassword();
document.getElementById("generatePassword").addEventListener("click", function() {
const generatedPassword = generateRandomStrongPassword(16);
document.getElementById("admin_password").value = generatedPassword;
const passwordField = document.getElementById("admin_password");
if (passwordField.type === "password") {
passwordField.type = "text";
}
});
document.getElementById("togglePassword").addEventListener("click", function() {
const passwordField = document.getElementById("admin_password");
if (passwordField.type === "password") {
passwordField.type = "text";
} else {
passwordField.type = "password";
}
});
// EDIT PASSWORD MODAL
document.getElementById("generatePasswordinModal").addEventListener("click", function() {
const generatedPassword = generateRandomStrongPassword(16);
document.getElementById("new_password").value = generatedPassword;
const passwordField = document.getElementById("new_password");
if (passwordField.type === "password") {
passwordField.type = "text";
}
});
document.getElementById("togglePasswordinModal").addEventListener("click", function() {
const passwordField = document.getElementById("new_password");
if (passwordField.type === "password") {
passwordField.type = "text";
} else {
passwordField.type = "password";
}
});
</script>
{% endblock %}