design overall

This commit is contained in:
usmannasir
2025-06-15 01:10:08 +05:00
parent 03e8bbbf54
commit f23b57053c
2075 changed files with 102714 additions and 25096 deletions

0
ApachController/ApacheController.py Executable file → Normal file
View File

0
ApachController/ApacheVhosts.py Executable file → Normal file
View File

0
ApachController/__init__.py Executable file → Normal file
View File

0
ApachController/phpApache.xml Executable file → Normal file
View File

0
CLManager/CLPackages.py Executable file → Normal file
View File

0
CLManager/templates/CLManager/cloudLinux.html Executable file → Normal file
View File

0
CLManager/templates/CLManager/createPackage.html Executable file → Normal file
View File

0
CLManager/templates/CLManager/listPackages.html Executable file → Normal file
View File

0
CLManager/templates/CLManager/listWebsites.html Executable file → Normal file
View File

0
CLManager/templates/CLManager/monitorUsage.html Executable file → Normal file
View File

0
CLManager/templates/CLManager/notAvailable.html Executable file → Normal file
View File

View File

0
CLScript/CloudLinuxAdmins.py Executable file → Normal file
View File

0
CLScript/CloudLinuxDB.py Executable file → Normal file
View File

0
CLScript/CloudLinuxDomains.py Executable file → Normal file
View File

0
CLScript/CloudLinuxPackages.py Executable file → Normal file
View File

0
CLScript/CloudLinuxResellers.py Executable file → Normal file
View File

0
CLScript/CloudLinuxUsers.py Executable file → Normal file
View File

0
CLScript/UserInfo.py Executable file → Normal file
View File

0
CLScript/panel_info.py Executable file → Normal file
View File

0
CyberCP/__init__.py Executable file → Normal file
View File

0
CyberCP/secMiddleware.py Executable file → Normal file
View File

8
CyberCP/settings.py Executable file → Normal file
View File

@@ -104,25 +104,25 @@ WSGI_APPLICATION = 'CyberCP.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases # https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'cyberpanel', 'NAME': 'cyberpanel',
'USER': 'cyberpanel', 'USER': 'cyberpanel',
'PASSWORD': 'JjWbFBFDxMI8D8', 'PASSWORD': 'SLTUIUxqhulwsh',
'HOST': 'localhost', 'HOST': 'localhost',
'PORT': '' 'PORT':''
}, },
'rootdb': { 'rootdb': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysql', 'NAME': 'mysql',
'USER': 'root', 'USER': 'root',
'PASSWORD': 'JjWbFBFDxMI8D8', 'PASSWORD': 'SLTUIUxqhulwsh',
'HOST': 'localhost', 'HOST': 'localhost',
'PORT': '', 'PORT': '',
}, },
} }
DATABASE_ROUTERS = ['backup.backupRouter.backupRouter'] DATABASE_ROUTERS = ['backup.backupRouter.backupRouter']
# Password validation # Password validation

0
CyberCP/urls.py Executable file → Normal file
View File

0
CyberCP/wsgi.py Executable file → Normal file
View File

View File

@@ -4,7 +4,7 @@ app.controller('createIncrementalBackups', function ($scope, $http, $timeout) {
$scope.destination = true; $scope.destination = true;
$scope.backupButton = true; $scope.backupButton = true;
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.runningBackup = true; $scope.runningBackup = true;
$scope.restoreSt = true; $scope.restoreSt = true;
@@ -48,7 +48,7 @@ app.controller('createIncrementalBackups', function ($scope, $http, $timeout) {
$scope.destination = false; $scope.destination = false;
$scope.runningBackup = false; $scope.runningBackup = false;
$scope.backupButton = false; $scope.backupButton = false;
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.fileName = response.data.fileName; $scope.fileName = response.data.fileName;
$scope.status = response.data.status; $scope.status = response.data.status;
$scope.populateCurrentRecords(); $scope.populateCurrentRecords();
@@ -66,7 +66,7 @@ app.controller('createIncrementalBackups', function ($scope, $http, $timeout) {
} else { } else {
$timeout.cancel(); $timeout.cancel();
$scope.cyberpanelLoadingBottom = true; $scope.cyberpanelLoadingBottom = true;
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.backupButton = false; $scope.backupButton = false;
} }
@@ -157,7 +157,7 @@ app.controller('createIncrementalBackups', function ($scope, $http, $timeout) {
$scope.tempPath = response.data.tempPath; $scope.tempPath = response.data.tempPath;
getBackupStatus(); getBackupStatus();
} else { } else {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
new PNotify({ new PNotify({
title: 'Operation Failed!', title: 'Operation Failed!',
text: response.data.error_message, text: response.data.error_message,
@@ -295,7 +295,7 @@ app.controller('createIncrementalBackups', function ($scope, $http, $timeout) {
function ListInitialDatas(response) { function ListInitialDatas(response) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
if (response.data.status === 1) { if (response.data.status === 1) {
$scope.jobs = response.data.data; $scope.jobs = response.data.data;
} else { } else {
@@ -367,7 +367,7 @@ app.controller('createIncrementalBackups', function ($scope, $http, $timeout) {
app.controller('incrementalDestinations', function ($scope, $http) { app.controller('incrementalDestinations', function ($scope, $http) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.sftpHide = true; $scope.sftpHide = true;
$scope.awsHide = true; $scope.awsHide = true;
@@ -386,7 +386,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
$scope.populateCurrentRecords = function () { $scope.populateCurrentRecords = function () {
$scope.cyberpanelLoading = false; $scope.cyberpanelLoading = true;
url = "/IncrementalBackups/populateCurrentRecords"; url = "/IncrementalBackups/populateCurrentRecords";
@@ -413,7 +413,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
function ListInitialDatas(response) { function ListInitialDatas(response) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
if (response.data.status === 1) { if (response.data.status === 1) {
$scope.records = response.data.data; $scope.records = response.data.data;
} else { } else {
@@ -427,7 +427,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
} }
function cantLoadInitialDatas(response) { function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
new PNotify({ new PNotify({
title: 'Operation Failed!', title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page', text: 'Could not connect to server, please refresh this page',
@@ -438,7 +438,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
}; };
$scope.addDestination = function (type) { $scope.addDestination = function (type) {
$scope.cyberpanelLoading = false; $scope.cyberpanelLoading = true;
url = "/IncrementalBackups/addDestination"; url = "/IncrementalBackups/addDestination";
@@ -469,7 +469,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
function ListInitialDatas(response) { function ListInitialDatas(response) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.populateCurrentRecords(); $scope.populateCurrentRecords();
if (response.data.status === 1) { if (response.data.status === 1) {
new PNotify({ new PNotify({
@@ -488,7 +488,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
} }
function cantLoadInitialDatas(response) { function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
new PNotify({ new PNotify({
title: 'Operation Failed!', title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page', text: 'Could not connect to server, please refresh this page',
@@ -499,7 +499,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
}; };
$scope.removeDestination = function (type, ipAddress) { $scope.removeDestination = function (type, ipAddress) {
$scope.cyberpanelLoading = false; $scope.cyberpanelLoading = true;
url = "/IncrementalBackups/removeDestination"; url = "/IncrementalBackups/removeDestination";
@@ -520,7 +520,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
function ListInitialDatas(response) { function ListInitialDatas(response) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.populateCurrentRecords(); $scope.populateCurrentRecords();
if (response.data.status === 1) { if (response.data.status === 1) {
new PNotify({ new PNotify({
@@ -539,7 +539,7 @@ app.controller('incrementalDestinations', function ($scope, $http) {
} }
function cantLoadInitialDatas(response) { function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
new PNotify({ new PNotify({
title: 'Operation Failed!', title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page', text: 'Could not connect to server, please refresh this page',
@@ -1073,7 +1073,7 @@ app.controller('restoreRemoteBackupsInc', function ($scope, $http, $timeout) {
$scope.destination = false; $scope.destination = false;
$scope.runningBackup = false; $scope.runningBackup = false;
$scope.backupButton = false; $scope.backupButton = false;
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.fileName = response.data.fileName; $scope.fileName = response.data.fileName;
$scope.status = response.data.status; $scope.status = response.data.status;
$scope.populateCurrentRecords(); $scope.populateCurrentRecords();
@@ -1096,7 +1096,7 @@ app.controller('restoreRemoteBackupsInc', function ($scope, $http, $timeout) {
} else { } else {
$timeout.cancel(); $timeout.cancel();
$scope.cyberpanelLoadingBottom = true; $scope.cyberpanelLoadingBottom = true;
$scope.cyberpanelLoading = true; $scope.cyberpanelLoading = false;
$scope.backupButton = false; $scope.backupButton = false;
} }

0
IncBackups/templates/IncBackups/backupSchedule.html Executable file → Normal file
View File

772
IncBackups/templates/IncBackups/createBackup.html Executable file → Normal file
View File

@@ -1,227 +1,593 @@
{% extends "baseTemplate/index.html" %} {% extends "baseTemplate/index.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Create Incremental Backup" %}{% endblock %} {% block title %}{% trans "Create Incremental Backup" %}{% endblock %}
{% block header_scripts %}
<style>
/* Page Specific Styles */
.backup-wrapper {
background: transparent;
padding: 20px;
}
.backup-container {
max-width: 1200px;
margin: 0 auto;
}
/* Page Header */
.page-header {
background: white;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
}
.page-header h1 {
font-size: 24px;
font-weight: 700;
color: #2f3640;
margin: 0 0 10px 0;
display: flex;
align-items: center;
gap: 15px;
}
.page-header .icon {
width: 48px;
height: 48px;
background: #5b5fcf;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
box-shadow: 0 4px 12px rgba(91,95,207,0.3);
}
.page-header p {
font-size: 14px;
color: #64748b;
margin: 0;
line-height: 1.6;
}
.docs-link {
display: inline-flex;
align-items: center;
gap: 6px;
color: #5b5fcf;
text-decoration: none;
font-size: 13px;
font-weight: 600;
transition: all 0.3s ease;
margin-top: 10px;
}
.docs-link:hover {
color: #4b4fbf;
transform: translateX(2px);
}
/* Content Section */
.content-section {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
}
.section-title {
font-size: 18px;
font-weight: 700;
color: #2f3640;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title::before {
content: '';
width: 4px;
height: 24px;
background: #5b5fcf;
border-radius: 2px;
}
/* Form Styles */
.form-group {
margin-bottom: 25px;
}
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: #2f3640;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-control {
width: 100%;
padding: 12px 16px;
font-size: 14px;
border: 1px solid #e8e9ff;
border-radius: 8px;
background: #f8f9ff;
color: #2f3640;
transition: all 0.3s ease;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
background: white;
box-shadow: 0 0 0 3px rgba(91,95,207,0.1);
}
.form-select {
width: 100%;
padding: 12px 16px;
font-size: 14px;
border: 1px solid #e8e9ff;
border-radius: 8px;
background: #f8f9ff;
color: #2f3640;
transition: all 0.3s ease;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: #5b5fcf;
background: white;
box-shadow: 0 0 0 3px rgba(91,95,207,0.1);
}
/* Checkbox Styles */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 15px;
}
.checkbox-wrapper {
display: flex;
align-items: center;
padding: 15px;
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.checkbox-wrapper:hover {
background: #f0f0ff;
border-color: #5b5fcf;
}
.checkbox-wrapper input[type="checkbox"] {
width: 18px;
height: 18px;
margin-right: 12px;
cursor: pointer;
}
.checkbox-label {
font-size: 14px;
color: #2f3640;
font-weight: 500;
cursor: pointer;
user-select: none;
}
/* Button Styles */
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary:hover {
background: #4b4fbf;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(91,95,207,0.3);
}
.btn-primary:disabled {
background: #94a3b8;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* Status Textarea */
.status-textarea {
width: 100%;
padding: 15px;
font-size: 13px;
font-family: 'Monaco', 'Consolas', monospace;
line-height: 1.5;
border: 1px solid #e8e9ff;
border-radius: 8px;
background: #f8f9ff;
color: #2f3640;
resize: vertical;
min-height: 200px;
}
/* Table Styles */
.backups-table {
width: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e8e9ff;
}
.backups-table th {
background: #f8f9ff;
padding: 15px;
text-align: left;
font-size: 12px;
font-weight: 700;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid #e8e9ff;
}
.backups-table td {
padding: 15px;
font-size: 14px;
color: #2f3640;
border-bottom: 1px solid #f0f0ff;
}
.backups-table tr:last-child td {
border-bottom: none;
}
.backups-table tr:hover {
background: #f8f9ff;
}
.action-btn {
background: #5b5fcf;
color: white;
border: none;
padding: 6px 16px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 6px;
text-decoration: none;
}
.action-btn:hover {
background: #4b4fbf;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(91,95,207,0.3);
color: white;
text-decoration: none;
}
.delete-btn {
background: #ef4444;
color: white;
border: none;
padding: 6px 16px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.delete-btn:hover {
background: #dc2626;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(239,68,68,0.3);
}
/* Modal Styles */
.modal-content {
border: none;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
}
.modal-header {
background: #f8f9ff;
border-bottom: 1px solid #e8e9ff;
border-radius: 12px 12px 0 0;
padding: 20px;
}
.modal-title {
font-size: 18px;
font-weight: 700;
color: #2f3640;
}
.modal-body {
padding: 25px;
}
.modal-footer {
background: #f8f9ff;
border-top: 1px solid #e8e9ff;
border-radius: 0 0 12px 12px;
padding: 15px 20px;
}
/* Loading Spinner */
.loading-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #5b5fcf;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.8s linear infinite;
margin-left: 8px;
}
/* Angular cloak */
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Empty State */
.empty-state {
text-align: center;
padding: 40px;
color: #8893a7;
}
.empty-state i {
font-size: 48px;
color: #e8e9ff;
margin-bottom: 15px;
}
.empty-state p {
font-size: 14px;
margin: 0;
}
/* Responsive */
@media (max-width: 768px) {
.backup-wrapper {
padding: 15px;
}
.content-section {
padding: 20px;
}
.backups-table {
font-size: 12px;
}
.backups-table th,
.backups-table td {
padding: 10px;
}
}
</style>
{% endblock %}
{% block content %} {% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
{% load static %} <div class="backup-wrapper">
<div class="backup-container" ng-controller="createIncrementalBackups" ng-init="cyberpanelLoading=false" ng-cloak>
{% get_current_language as LANGUAGE_CODE %} <!-- Page Header -->
<!-- Current language: {{ LANGUAGE_CODE }} --> <div class="page-header">
<h1>
<div class="container"> <div class="icon">
<div id="page-title"> <i class="fas fa-shield-alt"></i>
<h2>{% trans "Backup Website" %} - <a target="_blank" href="https://cyberpanel.net/docs/2-create-restore-incremental-backups/" </div>
style="height: 23px;line-height: 21px;" {% trans "Create Incremental Backup" %}
class="btn btn-border btn-alt border-red btn-link font-red" title=""><span>{% trans "Backup Docs" %}</span></a> </h1>
</h2> <p>{% trans "Create incremental backups for your websites with efficient storage usage and quick restore capabilities." %}</p>
<p>{% trans "This page can be used to create incremental backups for your websites." %}</p> <a href="https://cyberpanel.net/docs/2-create-restore-incremental-backups/" target="_blank" class="docs-link">
<i class="fas fa-book"></i>
{% trans "View Documentation" %}
<i class="fas fa-external-link-alt" style="font-size: 11px;"></i>
</a>
</div> </div>
<div ng-controller="createIncrementalBackups" class="panel"> <!-- Configuration Section -->
<div class="panel-body"> <div class="content-section">
<h3 class="title-hero"> <h2 class="section-title">{% trans "Backup Configuration" %}</h2>
{% trans "Backup Website" %} <img ng-hide="cyberpanelLoading"
src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row"> <form>
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select ng-change="fetchDetails()" ng-model="websiteToBeBacked" class="form-select">
<option value="">{% trans "Choose a website to backup" %}</option>
{% for items in websiteList %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
<div ng-hide="destination" class="form-group">
<label class="form-label">{% trans "Backup Destination" %}</label>
<select ng-change="destinationSelection()" ng-model="backupDestinations" class="form-select">
<option value="">{% trans "Select backup destination" %}</option>
{% for items in destinations %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
<div class="form-group"> <div ng-hide="destination" class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Website" %} </label> <label class="form-label">{% trans "Backup Content" %}</label>
<div class="col-sm-6"> <div class="checkbox-group">
<select ng-change="fetchDetails()" ng-model="websiteToBeBacked" class="form-control"> <div class="checkbox-wrapper">
{% for items in websiteList %} <input ng-model="websiteData" type="checkbox" id="backup-data" checked>
<option>{{ items }}</option> <label for="backup-data" class="checkbox-label">{% trans "Website Data" %}</label>
{% endfor %}
</select>
</div>
</div> </div>
<div class="checkbox-wrapper">
<div ng-hide="destination" class="form-group"> <input ng-model="websiteDatabases" type="checkbox" id="backup-databases">
<label class="col-sm-3 control-label">{% trans "Destination" %}</label> <label for="backup-databases" class="checkbox-label">{% trans "Databases" %}</label>
<div class="col-sm-6">
<select ng-change="destinationSelection()" ng-model="backupDestinations"
class="form-control">
{% for items in destinations %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div> </div>
<div class="checkbox-wrapper">
<div ng-hide="destination" class="form-group"> <input ng-model="websiteEmails" type="checkbox" id="backup-emails">
<label class="col-sm-3 control-label">{% trans "Backup Content" %}</label> <label for="backup-emails" class="checkbox-label">{% trans "Email Accounts" %}</label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="websiteData" type="checkbox" value="">
Data
</label>
</div>
</div>
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="websiteDatabases" type="checkbox" value="">
Databases
</label>
</div>
</div>
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="websiteEmails" type="checkbox" value="">
Emails
</label>
</div>
</div>
<!---
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="websiteSSLs" type="checkbox" value="">
SSL Certificates
</label>
</div>
</div> -->
</div> </div>
</div>
</div>
<!---- if Backup is running -----> <!-- Backup Status -->
<div ng-hide="runningBackup" class="form-group">
<label class="form-label">{% trans "Backup Progress" %}</label>
<textarea ng-model="status" class="status-textarea" readonly></textarea>
</div>
<div ng-hide="runningBackup" class="form-group"> <!-- Create Backup Button -->
<div class="col-sm-12"> <div ng-hide="backupButton" class="form-group" style="text-align: center; margin-top: 30px;">
<div class="col-sm-12"> <button type="button" ng-click="createBackup()" class="btn-primary" ng-disabled="cyberpanelLoading">
<textarea ng-model="status" class="form-control" rows="10"></textarea> <i class="fas fa-shield-alt" ng-if="!cyberpanelLoading"></i>
</div> <i class="fas fa-spinner fa-spin" ng-if="cyberpanelLoading"></i>
</div> <span ng-if="!cyberpanelLoading">{% trans "Create Backup" %}</span>
<span ng-if="cyberpanelLoading">{% trans "Creating Backup..." %}</span>
</button>
</div>
</form>
</div>
<!-- Existing Backups -->
<div class="content-section">
<h2 class="section-title">{% trans "Existing Backups" %}</h2>
<div ng-if="!records || records.length === 0" class="empty-state">
<i class="fas fa-folder-open"></i>
<p>{% trans "No backups created yet" %}</p>
</div>
<table class="backups-table" ng-if="records && records.length > 0">
<thead>
<tr>
<th>{% trans "Backup ID" %}</th>
<th>{% trans "Date Created" %}</th>
<th style="text-align: center;">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td>
<i class="fas fa-archive" style="color: #5b5fcf; margin-right: 8px;"></i>
<span ng-bind="record.id"></span>
</td>
<td ng-bind="record.date"></td>
<td style="text-align: center;">
<a ng-click="restore(record.id)" data-toggle="modal" data-target="#settings"
class="action-btn" title="Restore">
<i class="fas fa-undo"></i>
{% trans "Restore Points" %}
</a>
<button type="button" class="delete-btn" ng-click="deleteBackup(record.id)" style="margin-left: 10px;">
<i class="fas fa-trash-alt"></i>
{% trans "Delete" %}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Restore Points Modal -->
<div id="settings" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">
{% trans "Restore Points" %}
<span class="loading-spinner" ng-show="cyberpanelLoading"></span>
</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<table class="backups-table">
<thead>
<tr>
<th>{% trans "Job ID" %}</th>
<th>{% trans "Snapshot ID" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Destination" %}</th>
<th style="text-align: center;">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="job in jobs track by $index">
<td ng-bind="job.id"></td>
<td ng-bind="job.snapshotid"></td>
<td ng-bind="job.type"></td>
<td ng-bind="job.destination"></td>
<td style="text-align: center;">
<button ng-click="restorePoint(job.id, 0)" class="action-btn">
<i class="fas fa-undo"></i>
{% trans "Restore" %}
</button>
</td>
</tr>
</tbody>
</table>
<div ng-hide="restoreSt" style="margin-top: 20px;">
<label class="form-label">{% trans "Restore Progress" %}</label>
<textarea ng-model="status" class="status-textarea" rows="7" readonly></textarea>
</div> </div>
</div>
<div class="modal-footer">
<!---- if Backup is running------> <button type="button" class="btn btn-secondary" data-dismiss="modal">
{% trans "Close" %}
<div ng-hide="backupButton" class="form-group"> </button>
<label class="col-sm-3 control-label"></label> </div>
<div class="col-sm-4">
<button type="button" ng-click="createBackup()"
class="btn btn-primary btn-lg btn-block">{% trans "Create Backup" %}</button>
</div>
</div>
<!------ List of records --------------->
<div class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Restore" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td ng-bind="record.id"></td>
<td ng-bind="record.date"></td>
<td>
<a ng-click="restore(record.id)" data-toggle="modal" data-target="#settings"
ng-click='deleteCLPackage()'
class="btn btn-border btn-alt border-green btn-link font-green"
title=""><span>Restore Points</span></a>
<div id="settings" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
&times;
</button>
<h4 class="modal-title">Restore Points
<img ng-hide="cyberpanelLoading"
src="{% static 'images/loading.gif' %}">
</h4>
</div>
<div class="modal-body">
<table class="table">
<thead>
<tr>
<th>{% trans "Job ID" %}</th>
<th>{% trans "Snapshot ID" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Destination" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="job in jobs track by $index">
<td ng-bind="job.id"></td>
<td ng-bind="job.snapshotid"></td>
<td ng-bind="job.type"></td>
<td ng-bind="job.destination"></td>
<td>
<a ng-click="restorePoint(job.id, 0)" class="btn btn-border btn-alt border-green btn-link font-green"
title=""><span>Restore</span></a>
</td>
</tr>
</tbody>
</table>
<div ng-hide="restoreSt" class="form-group">
<div class="col-sm-12">
<div class="col-sm-12">
<textarea ng-model="status"
class="form-control"
rows="7"></textarea>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" ng-disabled="savingSettings"
class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
</td>
<a href="">
<td ng-click="deleteBackup(record.id)"><img
src="{% static 'images/delete.png' %}"></td>
</a>
</tr>
</tbody>
</table>
</div>
</div>
<!------ List of records --------------->
</form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<script src="{% static 'IncBackups/IncBackups.js' %}"></script>
{% endblock %} {% endblock %}

View File

@@ -1,184 +1,503 @@
{% extends "baseTemplate/index.html" %} {% extends "baseTemplate/index.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Set up Backup Destinations" %}{% endblock %} {% block title %}{% trans "Set up Backup Destinations" %}{% endblock %}
{% block header_scripts %}
<style>
/* Page Specific Styles */
.backup-wrapper {
background: transparent;
padding: 20px;
}
.backup-container {
max-width: 1200px;
margin: 0 auto;
}
/* Page Header */
.page-header {
background: white;
border-radius: 12px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
}
.page-header h1 {
font-size: 24px;
font-weight: 700;
color: #2f3640;
margin: 0 0 10px 0;
display: flex;
align-items: center;
gap: 15px;
}
.page-header .icon {
width: 48px;
height: 48px;
background: #5b5fcf;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
box-shadow: 0 4px 12px rgba(91,95,207,0.3);
}
.page-header p {
font-size: 14px;
color: #64748b;
margin: 0;
line-height: 1.6;
}
.docs-link {
display: inline-flex;
align-items: center;
gap: 6px;
color: #5b5fcf;
text-decoration: none;
font-size: 13px;
font-weight: 600;
transition: all 0.3s ease;
margin-top: 10px;
}
.docs-link:hover {
color: #4b4fbf;
transform: translateX(2px);
}
/* Content Section */
.content-section {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
}
.section-title {
font-size: 18px;
font-weight: 700;
color: #2f3640;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title::before {
content: '';
width: 4px;
height: 24px;
background: #5b5fcf;
border-radius: 2px;
}
/* Form Styles */
.form-group {
margin-bottom: 25px;
}
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: #2f3640;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-control {
width: 100%;
padding: 12px 16px;
font-size: 14px;
border: 1px solid #e8e9ff;
border-radius: 8px;
background: #f8f9ff;
color: #2f3640;
transition: all 0.3s ease;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
background: white;
box-shadow: 0 0 0 3px rgba(91,95,207,0.1);
}
.form-select {
width: 100%;
padding: 12px 16px;
font-size: 14px;
border: 1px solid #e8e9ff;
border-radius: 8px;
background: #f8f9ff;
color: #2f3640;
transition: all 0.3s ease;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: #5b5fcf;
background: white;
box-shadow: 0 0 0 3px rgba(91,95,207,0.1);
}
.form-help {
font-size: 12px;
color: #8893a7;
margin-top: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.form-help i {
font-size: 13px;
color: #5b5fcf;
}
/* Button Styles */
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary:hover {
background: #4b4fbf;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(91,95,207,0.3);
}
.btn-primary:disabled {
background: #94a3b8;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* Angular cloak */
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
/* Loading Spinner */
.loading-spinner {
display: none;
width: 16px;
height: 16px;
border: 2px solid #ffffff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.8s linear infinite;
margin-left: 8px;
}
.loading-spinner.ng-hide {
display: none !important;
}
/* Show spinner only when Angular explicitly shows it */
.loading-spinner[style*="display: none;"] {
display: none !important;
}
.loading-spinner.ng-hide-add,
.loading-spinner.ng-hide-remove {
display: none !important;
}
.btn-primary .loading-spinner:not([style*="display: none;"]):not(.ng-hide) {
display: inline-block !important;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Table Styles */
.destinations-table {
width: 100%;
margin-top: 30px;
background: white;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e8e9ff;
}
.destinations-table th {
background: #f8f9ff;
padding: 15px;
text-align: left;
font-size: 12px;
font-weight: 700;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid #e8e9ff;
}
.destinations-table td {
padding: 15px;
font-size: 14px;
color: #2f3640;
border-bottom: 1px solid #f0f0ff;
}
.destinations-table tr:last-child td {
border-bottom: none;
}
.destinations-table tr:hover {
background: #f8f9ff;
}
.delete-btn {
background: #ef4444;
color: white;
border: none;
padding: 6px 16px;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.delete-btn:hover {
background: #dc2626;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(239,68,68,0.3);
}
/* Empty State */
.empty-state {
text-align: center;
padding: 40px;
color: #8893a7;
}
.empty-state i {
font-size: 48px;
color: #e8e9ff;
margin-bottom: 15px;
}
.empty-state p {
font-size: 14px;
margin: 0;
}
/* Alert Box */
.alert-box {
background: #f0f0ff;
border: 1px solid #e8e9ff;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.alert-box i {
color: #5b5fcf;
font-size: 18px;
flex-shrink: 0;
margin-top: 2px;
}
.alert-box-content {
flex: 1;
}
.alert-box-title {
font-size: 14px;
font-weight: 600;
color: #2f3640;
margin-bottom: 4px;
}
.alert-box-text {
font-size: 13px;
color: #64748b;
line-height: 1.5;
}
/* Responsive */
@media (max-width: 768px) {
.backup-wrapper {
padding: 15px;
}
.content-section {
padding: 20px;
}
.destinations-table {
font-size: 12px;
}
.destinations-table th,
.destinations-table td {
padding: 10px;
}
}
</style>
{% endblock %}
{% block content %} {% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
{% load static %} <div class="backup-wrapper">
<div class="backup-container" ng-controller="incrementalDestinations">
<!-- Page Header -->
{% get_current_language as LANGUAGE_CODE %} <div class="page-header">
<!-- Current language: {{ LANGUAGE_CODE }} --> <h1>
<div class="icon">
<div class="container"> <i class="fas fa-cloud-upload-alt"></i>
<div id="page-title"> </div>
<h2>{% trans "Set up Incremental Backup Destinations" %} - <a target="_blank" {% trans "Set up Incremental Backup Destinations" %}
href="https://cyberpanel.net/docs/1-add-remove-destinations-for-incremental-backups/" </h1>
style="height: 23px;line-height: 21px;" <p>{% trans "Configure your backup destinations for incremental backups. Currently supporting AWS S3 storage." %}</p>
class="btn btn-border btn-alt border-red btn-link font-red" <a href="https://cyberpanel.net/docs/1-add-remove-destinations-for-incremental-backups/" target="_blank" class="docs-link">
title=""><span>{% trans "Remote Backups" %}</span></a> <i class="fas fa-book"></i>
</h2> {% trans "View Documentation" %}
<p>{% trans "On this page you can set up your Backup destinations. (SFTP and AWS)" %}</p> <i class="fas fa-external-link-alt" style="font-size: 11px;"></i>
</a>
</div> </div>
<div ng-controller="incrementalDestinations" class="panel"> <!-- Configuration Section -->
<div class="panel-body"> <div class="content-section">
<h3 class="title-hero"> <h2 class="section-title">{% trans "Add Backup Destination" %}</h2>
{% trans "Set up Backup Destinations." %} <img ng-hide="cyberpanelLoading"
src="{% static 'images/loading.gif' %}"> <div class="alert-box">
</h3> <i class="fas fa-info-circle"></i>
<div class="example-box-wrapper"> <div class="alert-box-content">
<div class="alert-box-title">{% trans "Important Information" %}</div>
<div class="alert-box-text">{% trans "Incremental backups allow you to save storage space and bandwidth by only backing up changes since the last backup." %}</div>
<form action="/" class="form-horizontal bordered-row">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Type" %} </label>
<div class="col-sm-6">
<select ng-change="fetchDetails()" ng-model="destinationType" class="form-control">
{# <option>SFTP</option>#}
<option>AWS</option>
</select>
</div>
</div>
<!--- SFTP --->
<div ng-hide="sftpHide" class="form-group">
<label class="col-sm-3 control-label">{% trans "IP Address" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="IPAddress" required>
</div>
</div>
<div ng-hide="sftpHide" class="form-group">
<label class="col-sm-3 control-label">{% trans "Password" %}</label>
<div class="col-sm-6">
<input placeholder="" type="password" class="form-control" ng-model="password" required>
</div>
</div>
<div ng-hide="sftpHide" class="form-group">
<label class="col-sm-3 control-label">{% trans "Port" %}</label>
<div class="col-sm-6">
<input placeholder="{% trans "Backup server SSH Port, leave empty for 22." %}"
type="text" class="form-control" ng-model="backupSSHPort" required>
</div>
</div>
<div ng-hide="sftpHide" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="addDestination('SFTP')"
class="btn btn-primary btn-lg btn-block">{% trans "Add Destination" %}</button>
</div>
</div>
<!--- SFTP --->
<!------ List of Destinations --------------->
<div ng-hide="sftpHide" class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>{% trans "IP" %}</th>
<th>{% trans "Port" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td ng-bind="record.ip"></td>
<td ng-bind="record.port"></td>
<td ng-click="removeDestination('SFTP',record.ip)"><img src="{% static 'images/delete.png' %}">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!------ List of records --------------->
<!--- SFTP End --->
<!--- AWS Start --->
<div ng-hide="awsHide" class="form-group">
<label class="col-sm-3 control-label">{% trans "AWS_ACCESS_KEY_ID" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="AWS_ACCESS_KEY_ID" required>
</div>
</div>
<div ng-hide="awsHide" class="form-group">
<label class="col-sm-3 control-label">{% trans "AWS_SECRET_ACCESS_KEY" %}</label>
<div class="col-sm-6">
<input placeholder="" type="password" class="form-control" ng-model="AWS_SECRET_ACCESS_KEY" required>
</div>
</div>
<div ng-hide="awsHide" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="addDestination('AWS')"
class="btn btn-primary btn-lg btn-block">{% trans "Add Destination" %}</button>
</div>
</div>
<!--- SFTP --->
<!------ List of Destinations --------------->
<div ng-hide="awsHide" class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>{% trans "AWS_ACCESS_KEY_ID" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td ng-bind="record.AWS_ACCESS_KEY_ID"></td>
<td ng-click="removeDestination('AWS', record.AWS_ACCESS_KEY_ID)"><img src="{% static 'images/delete.png' %}">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!------ List of records --------------->
<!--- AWS End --->
</form>
</div> </div>
</div> </div>
<form>
<div class="form-group">
<label class="form-label">{% trans "Destination Type" %}</label>
<select ng-change="fetchDetails()" ng-model="destinationType" class="form-select">
<option value="">{% trans "Select destination type" %}</option>
<option value="AWS">AWS S3</option>
</select>
<div class="form-help">
<i class="fas fa-lightbulb"></i>
{% trans "Choose your backup storage provider" %}
</div>
</div>
<!-- AWS Configuration -->
<div ng-hide="awsHide">
<div class="form-group">
<label class="form-label">{% trans "AWS Access Key ID" %}</label>
<input type="text" class="form-control" ng-model="AWS_ACCESS_KEY_ID" placeholder="{% trans 'Enter your AWS access key' %}" required>
<div class="form-help">
<i class="fas fa-key"></i>
{% trans "You can find this in your AWS IAM console" %}
</div>
</div>
<div class="form-group">
<label class="form-label">{% trans "AWS Secret Access Key" %}</label>
<input type="password" class="form-control" ng-model="AWS_SECRET_ACCESS_KEY" placeholder="{% trans 'Enter your AWS secret key' %}" required>
<div class="form-help">
<i class="fas fa-lock"></i>
{% trans "Keep this key secure and never share it" %}
</div>
</div>
<button type="button" ng-click="addDestination('AWS')" class="btn-primary" ng-disabled="cyberpanelLoading">
<i class="fas fa-plus-circle" ng-hide="cyberpanelLoading"></i>
<i class="fas fa-spinner fa-spin" ng-show="cyberpanelLoading" ng-cloak></i>
<span ng-hide="cyberpanelLoading">{% trans "Add Destination" %}</span>
<span ng-show="cyberpanelLoading" ng-cloak>{% trans "Adding..." %}</span>
</button>
</div>
</form>
</div> </div>
<!-- Existing Destinations -->
<div class="content-section" ng-hide="awsHide">
<h2 class="section-title">{% trans "Configured Destinations" %}</h2>
<div ng-if="!records || records.length === 0" class="empty-state">
<i class="fas fa-cloud-slash"></i>
<p>{% trans "No backup destinations configured yet" %}</p>
</div>
<table class="destinations-table" ng-if="records && records.length > 0">
<thead>
<tr>
<th>{% trans "AWS Access Key ID" %}</th>
<th style="text-align: right;">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td>
<i class="fas fa-key" style="color: #5b5fcf; margin-right: 8px;"></i>
<span ng-bind="record.AWS_ACCESS_KEY_ID"></span>
</td>
<td style="text-align: right;">
<button type="button" class="delete-btn" ng-click="removeDestination('AWS', record.AWS_ACCESS_KEY_ID)">
<i class="fas fa-trash-alt"></i>
{% trans "Delete" %}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div>
<script src="{% static 'IncBackups/IncBackups.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize the page with AWS selected by default
setTimeout(function() {
var scope = angular.element(document.querySelector('[ng-controller="incrementalDestinations"]')).scope();
if (scope && scope.fetchDetails) {
scope.$apply(function() {
scope.destinationType = 'AWS';
scope.fetchDetails();
});
}
}, 500);
});
</script>
{% endblock %} {% endblock %}

View File

0
WebTerminal/static/WebTerminal/main.js Executable file → Normal file
View File

0
WebTerminal/static/WebTerminal/term.js Executable file → Normal file
View File

0
WebTerminal/static/WebTerminal/ws.js Executable file → Normal file
View File

0
WebTerminal/static/images/loading.gif Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

0
WebTerminal/templates/WebTerminal/WebTerminal.html Executable file → Normal file
View File

0
WebTerminal/urls.py Executable file → Normal file
View File

0
api/__init__.py Executable file → Normal file
View File

0
api/admin.py Executable file → Normal file
View File

0
api/apps.py Executable file → Normal file
View File

0
api/migrations/__init__.py Executable file → Normal file
View File

0
api/models.py Executable file → Normal file
View File

0
api/tests.py Executable file → Normal file
View File

0
api/urls.py Executable file → Normal file
View File

0
api/views.py Executable file → Normal file
View File

0
backup/__init__.py Executable file → Normal file
View File

0
backup/admin.py Executable file → Normal file
View File

0
backup/apps.py Executable file → Normal file
View File

0
backup/backupManager.py Executable file → Normal file
View File

0
backup/backupRouter.py Executable file → Normal file
View File

0
backup/migrations/__init__.py Executable file → Normal file
View File

0
backup/models.py Executable file → Normal file
View File

0
backup/pluginManager.py Executable file → Normal file
View File

0
backup/signals.py Executable file → Normal file
View File

0
backup/static/backup/backup.js Executable file → Normal file
View File

0
backup/templates/backup/OneClickBackupSchedule.html Executable file → Normal file
View File

727
backup/templates/backup/backup.html Executable file → Normal file
View File

@@ -1,167 +1,614 @@
{% extends "baseTemplate/index.html" %} {% extends "baseTemplate/index.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Backup Website" %}{% endblock %} {% block title %}{% trans "Backup Website - CyberPanel" %}{% endblock %}
{% block content %} {% block content %}
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container"> <style>
<div id="page-title"> .modern-container {
<h2>{% trans "Backup Website" %} - <a target="_blank" href="http://go.cyberpanel.net/backup" max-width: 1400px;
style="height: 23px;line-height: 21px;" margin: 0 auto;
class="btn btn-border btn-alt border-red btn-link font-red" padding: 2rem;
title=""><span>{% trans "Backup Docs" %}</span></a></h2> }
<p>{% trans "This page can be used to Backup your websites" %}</p>
.page-header {
text-align: center;
margin-bottom: 3rem;
padding: 3rem 0;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-radius: 20px;
animation: fadeInDown 0.5s ease-out;
}
.page-title {
font-size: 3rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
.page-subtitle {
font-size: 1.25rem;
color: #64748b;
margin-bottom: 1.5rem;
}
.header-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-primary:hover {
background: #4547a9;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(91, 95, 207, 0.4);
color: white;
}
.btn-secondary {
background: #fff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-secondary:hover {
background: #f8f9ff;
border-color: #5b5fcf;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(91, 95, 207, 0.2);
}
.btn-danger {
background: #ef4444;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.825rem;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 10px 40px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
overflow: hidden;
margin-bottom: 2rem;
animation: fadeInUp 0.5s ease-out;
}
.card-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
padding: 1.5rem 2rem;
border-bottom: 1px solid #e8e9ff;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-body {
padding: 2rem;
}
.promo-banner {
background: linear-gradient(135deg, #5b5fcf 0%, #8187ff 100%);
color: white;
padding: 1.5rem 2rem;
border-radius: 12px;
margin-bottom: 2rem;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 4px 20px rgba(91, 95, 207, 0.3);
}
.promo-text {
font-size: 1rem;
font-weight: 500;
}
.promo-link {
background: white;
color: #5b5fcf;
padding: 0.5rem 1.5rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
}
.promo-link:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.form-section {
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #1e293b;
font-size: 0.875rem;
}
.form-control {
width: 100%;
padding: 0.875rem 1rem;
border: 1px solid #e8e9ff;
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: #fff;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1);
}
.running-backup-card {
background: #fef3c7;
border: 1px solid #fde68a;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.running-status {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.status-info {
display: flex;
align-items: center;
gap: 1rem;
}
.status-icon {
width: 40px;
height: 40px;
background: #fbbf24;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
animation: pulse 2s infinite;
}
.status-details h4 {
margin: 0;
color: #92400e;
font-size: 1rem;
}
.status-details p {
margin: 0;
color: #92400e;
font-size: 0.875rem;
opacity: 0.8;
}
.backup-table {
width: 100%;
background: white;
border-radius: 12px;
overflow: hidden;
border: 1px solid #e8e9ff;
}
.backup-table thead {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
}
.backup-table th {
padding: 1rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e8e9ff;
}
.backup-table td {
padding: 1rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #f3f4f6;
}
.backup-table tbody tr:hover {
background: #f8f9ff;
}
.backup-table tbody tr:last-child td {
border-bottom: none;
}
.file-badge {
background: #e0e7ff;
color: #5b5fcf;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
font-family: monospace;
}
.size-badge {
background: #f3f4f6;
color: #1e293b;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
}
.status-complete {
background: #d1fae5;
color: #065f46;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.alert {
padding: 1rem 1.5rem;
border-radius: 12px;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
animation: slideInRight 0.3s ease-out;
}
.alert-success {
background: #d1fae5;
color: #065f46;
border: 1px solid #a7f3d0;
}
.alert-danger {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fecaca;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 3px solid #e8e9ff;
border-top-color: #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
margin-left: 1rem;
}
.button-group {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
margin-top: 2rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.promo-banner {
flex-direction: column;
text-align: center;
gap: 1rem;
}
.button-group {
flex-direction: column;
}
.button-group button {
width: 100%;
}
}
</style>
<script>
// Ensure elements are shown when website is selected
document.addEventListener('DOMContentLoaded', function() {
var selectElement = document.getElementById('create-backup-select');
if (selectElement) {
selectElement.addEventListener('change', function() {
if (this.value && this.value !== '') {
// Show all elements with destinationHide class
var elements = document.querySelectorAll('.destinationHide');
elements.forEach(function(el) {
el.style.display = 'block';
});
}
});
}
});
</script>
<div class="modern-container" ng-controller="backupWebsiteControl">
<div class="page-header">
<h1 class="page-title">
<i class="fas fa-download"></i>
{% trans "Backup Website" %}
</h1>
<p class="page-subtitle">{% trans "Create and manage backups of your websites to ensure data safety" %}</p>
<div class="header-actions">
<a href="http://go.cyberpanel.net/backup" target="_blank" class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Backup Documentation" %}
</a>
</div>
</div> </div>
<div ng-controller="backupWebsiteControl" class="panel"> <!-- Promo Banner -->
<div class="panel-body"> <div class="promo-banner">
<h3 class="title-hero"> <span class="promo-text">
{% trans "Backup Website" %} <img ng-hide="backupLoading" src="{% static 'images/loading.gif' %}"> <i class="fas fa-shield-alt" style="margin-right: 0.5rem;"></i>
</h3> {% trans "Configure automatic backups to our secure servers in 60 seconds" %}
<div class="alert alert-warning"> </span>
<p>Configure automatic backups to our secure servers in 60 seconds. <a <a href="/backup/OneClickBackups" class="promo-link">
href="/backup/OneClickBackups">Set-up now.</a></p> <i class="fas fa-rocket"></i>
</div> {% trans "Set-up now" %}
<div class="example-box-wrapper"> </a>
</div>
<form action="/" class="form-horizontal bordered-row"> <div class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-archive"></i>
{% trans "Create New Backup" %}
<span ng-hide="backupLoading" class="loading-spinner"></span>
</h2>
</div>
<div class="card-body">
<form action="/" method="post">
<div class="form-group"> <div class="form-section">
<label class="col-sm-3 control-label">{% trans "Select Website" %} </label> <div class="row">
<div class="col-sm-6"> <div class="col-md-6">
<select id="create-backup-select" ng-model="websiteToBeBacked" class="form-control"> <div class="form-group">
{% for items in websiteList %} <label class="form-label">{% trans "Select Website" %}</label>
<option>{{ items }}</option> <select id="create-backup-select" ng-model="websiteToBeBacked" ng-change="fetchDetails()" class="form-control">
{% endfor %} <option value="">{% trans "Choose a website..." %}</option>
</select> {% for items in websiteList %}
</div> <option>{{ items }}</option>
</div> {% endfor %}
</select>
<div class="form-group destinationHide">
<label class="col-sm-3 control-label">{% trans "Destination" %}</label>
<div class="col-sm-6">
<select ng-change="destinationSelection()" ng-model="backupDestinations"
class="form-control">
<option>{% trans "Home" %}</option>
</select>
</div>
</div>
<!---- if Backup is running ----->
<div ng-hide="runningBackup" class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th></th>
<th>{% trans "File Name" %}</th>
<th>{% trans "Status" %} <img ng-hide="backupLoadingBottom"
src="{% static 'images/loading.gif' %}"></th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans "Running" %}</td>
<td>{$ fileName $}</td>
<td style="color: red"><strong>{$ status $}</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
<!---- if Backup is running------>
<div class="form-group destinationHide">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="createBackup()" id="createBackup"
class="btn btn-primary btn-lg btn-block">{% trans "Create Backup" %}</button>
</div>
</div>
<div ng-hide="cancelButton" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="cancelBackup()"
class="btn btn-primary btn-lg btn-block">{% trans "Cancel Backup" %}</button>
</div>
</div>
<!------ List of records --------------->
<div class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "File" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Size" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td ng-bind="record.id"></td>
<td ng-bind="record.file"></td>
<td ng-bind="record.date"></td>
<td ng-bind="record.size"></td>
<td ng-bind="record.status"></td>
<a href="">
<td ng-click="deleteBackup(record.id)"><img
src="{% static 'images/delete.png' %}"></td>
</a>
</tr>
</tbody>
</table>
</div>
</div>
<!------ List of records --------------->
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<div id="websiteDeleteFailure" class="alert alert-danger">
<p>{% trans "Cannot delete website, Error message: " %}{$ errorMessage $}</p>
</div> </div>
</div>
<div class="col-md-6">
<div class="form-group destinationHide">
<label class="form-label">{% trans "Destination" %}</label>
<select ng-change="destinationSelection()" ng-model="backupDestinations" class="form-control">
<option>{% trans "Home" %}</option>
</select>
</div>
</div>
</div>
</div>
<div id="websiteDeleteSuccess" class="alert alert-success">
<p>Website <strong>{$ deletedWebsite $}</strong> {% trans "Successfully Deleted" %} <!-- Running Backup Status -->
<div ng-hide="runningBackup" class="running-backup-card">
<div class="running-status">
<div class="status-info">
<div class="status-icon">
<i class="fas fa-sync-alt fa-spin" style="color: white;"></i>
</div>
<div class="status-details">
<h4>{% trans "Backup in Progress" %}</h4>
<p>
<strong>{% trans "File:" %}</strong> {$ fileName $} |
<strong>{% trans "Status:" %}</strong> {$ status $}
<span ng-hide="backupLoadingBottom" class="loading-spinner" style="margin-left: 0.5rem;"></span>
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div>
</form> <div class="button-group">
<button type="button" ng-click="createBackup()" id="createBackup" class="btn-primary destinationHide">
<i class="fas fa-play"></i>
</div> {% trans "Create Backup" %}
</button>
<button type="button" ng-click="cancelBackup()" ng-hide="cancelButton" class="btn-danger">
<i class="fas fa-stop"></i>
{% trans "Cancel Backup" %}
</button>
</div>
</form>
</div> </div>
</div> </div>
<!-- Existing Backups -->
<div class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-history"></i>
{% trans "Existing Backups" %}
</h2>
</div>
<div class="card-body" style="padding: 0;">
<table class="backup-table">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "File" %}</th>
<th>{% trans "Date" %}</th>
<th>{% trans "Size" %}</th>
<th>{% trans "Status" %}</th>
<th style="text-align: center;">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td><strong ng-bind="record.id"></strong></td>
<td>
<span class="file-badge" ng-bind="record.file"></span>
</td>
<td>
<i class="fas fa-calendar-alt" style="color: #64748b; margin-right: 0.5rem;"></i>
<span ng-bind="record.date"></span>
</td>
<td>
<span class="size-badge" ng-bind="record.size"></span>
</td>
<td>
<span class="status-complete" ng-bind="record.status"></span>
</td>
<td style="text-align: center;">
<button type="button" ng-click="deleteBackup(record.id)" class="btn-danger">
<i class="fas fa-trash"></i>
{% trans "Delete" %}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Alert Messages -->
<div id="websiteDeleteFailure" class="alert alert-danger" style="display: none;">
<i class="fas fa-exclamation-circle"></i>
{% trans "Cannot delete backup. Error message:" %} {$ errorMessage $}
</div>
<div id="websiteDeleteSuccess" class="alert alert-success" style="display: none;">
<i class="fas fa-check-circle"></i>
{% trans "Backup" %} <strong>{$ deletedWebsite $}</strong> {% trans "successfully deleted" %}
</div>
</div> </div>
{% endblock %} {% endblock %}

800
backup/templates/backup/backupDestinations.html Executable file → Normal file
View File

@@ -1,218 +1,652 @@
{% extends "baseTemplate/index.html" %} {% extends "baseTemplate/index.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Set up Backup Destinations" %}{% endblock %} {% block title %}{% trans "Backup Destinations - CyberPanel" %}{% endblock %}
{% block content %} {% block content %}
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container"> <style>
<div id="page-title"> .modern-container {
<h2>{% trans "Set up Backup Destinations" %} - <a target="_blank" max-width: 1400px;
href="https://cyberpanel.net/KnowledgeBase/home/add-destination-scheduled-local-sftp-remote-backups/" margin: 0 auto;
style="height: 23px;line-height: 21px;" padding: 2rem;
class="btn btn-border btn-alt border-red btn-link font-red" }
title=""><span>{% trans "Remote Backups" %}</span></a>
</h2> .page-header {
<p>{% trans "On this page you can set up your Backup destinations. (SFTP)" %}</p> text-align: center;
margin-bottom: 3rem;
padding: 3rem 0;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-radius: 20px;
animation: fadeInDown 0.5s ease-out;
}
.page-title {
font-size: 3rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
.page-subtitle {
font-size: 1.25rem;
color: #64748b;
margin-bottom: 1.5rem;
}
.header-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-primary:hover {
background: #4547a9;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(91, 95, 207, 0.4);
color: white;
}
.btn-secondary {
background: #fff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-secondary:hover {
background: #f8f9ff;
border-color: #5b5fcf;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(91, 95, 207, 0.2);
}
.btn-danger {
background: #ef4444;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.825rem;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
.promo-banner {
background: linear-gradient(135deg, #5b5fcf 0%, #8187ff 100%);
color: white;
padding: 1.5rem 2rem;
border-radius: 12px;
margin-bottom: 2rem;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 4px 20px rgba(91, 95, 207, 0.3);
}
.promo-text {
font-size: 1rem;
font-weight: 500;
}
.promo-link {
background: white;
color: #5b5fcf;
padding: 0.5rem 1.5rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s ease;
}
.promo-link:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 10px 40px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
overflow: hidden;
margin-bottom: 2rem;
animation: fadeInUp 0.5s ease-out;
}
.card-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
padding: 1.5rem 2rem;
border-bottom: 1px solid #e8e9ff;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-body {
padding: 2rem;
}
.destination-type-selector {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 12px;
padding: 2rem;
margin-bottom: 2rem;
text-align: center;
}
.destination-tabs {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 2rem;
}
.tab-button {
padding: 0.75rem 2rem;
background: #fff;
border: 2px solid #e8e9ff;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
color: #64748b;
}
.tab-button.active {
background: #5b5fcf;
color: white;
border-color: #5b5fcf;
}
.tab-button:hover:not(.active) {
border-color: #5b5fcf;
color: #5b5fcf;
}
.form-section {
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #1e293b;
font-size: 0.875rem;
}
.form-control {
width: 100%;
padding: 0.875rem 1rem;
border: 1px solid #e8e9ff;
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: #fff;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1);
}
.destination-table {
width: 100%;
background: white;
border-radius: 12px;
overflow: hidden;
border: 1px solid #e8e9ff;
margin-top: 2rem;
}
.destination-table thead {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
}
.destination-table th {
padding: 1rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e8e9ff;
}
.destination-table td {
padding: 1rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #f3f4f6;
}
.destination-table tbody tr:hover {
background: #f8f9ff;
}
.destination-table tbody tr:last-child td {
border-bottom: none;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 3px solid #e8e9ff;
border-top-color: #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
margin-left: 1rem;
}
.info-badge {
background: #e0e7ff;
color: #5b5fcf;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
font-family: monospace;
}
.port-badge {
background: #f3f4f6;
color: #1e293b;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
}
.destination-icon {
font-size: 2.5rem;
color: #5b5fcf;
margin-bottom: 1rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.promo-banner {
flex-direction: column;
text-align: center;
gap: 1rem;
}
.destination-tabs {
flex-direction: column;
}
.tab-button {
width: 100%;
}
}
</style>
<div class="modern-container" ng-controller="backupDestinations" ng-init="destinationType='local'">
<div class="page-header">
<h1 class="page-title">
<i class="fas fa-cloud-upload-alt"></i>
{% trans "Backup Destinations" %}
</h1>
<p class="page-subtitle">{% trans "Configure local and remote destinations for your backups" %}</p>
<div class="header-actions">
<a href="https://cyberpanel.net/KnowledgeBase/home/add-destination-scheduled-local-sftp-remote-backups/"
target="_blank"
class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Remote Backup Guide" %}
</a>
</div>
</div> </div>
<div ng-controller="backupDestinations" class="panel"> <!-- Promo Banner -->
<div class="panel-body"> <div class="promo-banner">
<h3 class="title-hero"> <span class="promo-text">
{% trans "Set up Backup Destinations." %} <img ng-hide="cyberpanelLoading" <i class="fas fa-shield-alt" style="margin-right: 0.5rem;"></i>
src="{% static 'images/loading.gif' %}"> {% trans "Configure automatic backups to our secure servers in 60 seconds" %}
</h3> </span>
<div class="alert alert-warning"> <a href="/backup/OneClickBackups" class="promo-link">
<p>Configure automatic backups to our secure servers in 60 seconds. <a <i class="fas fa-rocket"></i>
href="/backup/OneClickBackups">Set-up now.</a></p> {% trans "Set-up now" %}
</div> </a>
<div class="example-box-wrapper"> </div>
<form action="/" class="form-horizontal bordered-row"> <!-- Main Configuration Card -->
<div class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-cog"></i>
{% trans "Configure Backup Destination" %}
<span ng-hide="cyberpanelLoading" class="loading-spinner"></span>
</h2>
</div>
<div class="card-body">
<form action="/" method="post">
<!-- Destination Type Selector -->
<div class="destination-type-selector">
<div class="destination-icon">
<i class="fas fa-server"></i>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Type" %} </label> <label class="form-label">
<div class="col-sm-6"> <i class="fas fa-check-square" style="margin-right: 0.5rem;"></i>
<select ng-change="fetchDetails()" ng-model="destinationType" class="form-control"> {% trans "Select Destination Type" %}
<option>local</option> </label>
<option>SFTP</option> <div class="destination-tabs">
</select> <button type="button"
class="tab-button"
ng-class="{'active': destinationType === 'local'}"
ng-click="destinationType = 'local'; fetchDetails()">
<i class="fas fa-hdd" style="margin-right: 0.5rem;"></i>
{% trans "Local Storage" %}
</button>
<button type="button"
class="tab-button"
ng-class="{'active': destinationType === 'SFTP'}"
ng-click="destinationType = 'SFTP'; fetchDetails()">
<i class="fas fa-network-wired" style="margin-right: 0.5rem;"></i>
{% trans "SFTP Server" %}
</button>
</div> </div>
</div> </div>
</div>
<!--- SFTP ---> <!-- SFTP Configuration -->
<div ng-hide="sftpHide" class="form-section">
<div ng-hide="sftpHide" class="form-group"> <h3 style="font-size: 1.1rem; font-weight: 600; color: #1e293b; margin-bottom: 1.5rem;">
<label class="col-sm-3 control-label">{% trans "Name" %}</label> <i class="fas fa-network-wired" style="color: #5b5fcf; margin-right: 0.5rem;"></i>
<div class="col-sm-6"> {% trans "SFTP Server Configuration" %}
<input type="text" class="form-control" ng-model="name" required> </h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">{% trans "Name" %}</label>
<input type="text" class="form-control" ng-model="name" placeholder="My SFTP Backup" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">{% trans "IP Address" %}</label>
<input type="text" class="form-control" ng-model="IPAddress" placeholder="192.168.1.100" required>
</div>
</div> </div>
</div> </div>
<div ng-hide="sftpHide" class="form-group"> <div class="row">
<label class="col-sm-3 control-label">{% trans "IP Address" %}</label> <div class="col-md-6">
<div class="col-sm-6"> <div class="form-group">
<input type="text" class="form-control" ng-model="IPAddress" required> <label class="form-label">{% trans "Username" %}</label>
<input type="text" class="form-control" ng-model="userName" placeholder="backup_user" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">{% trans "Password" %}</label>
<input type="password" class="form-control" ng-model="password" placeholder="••••••••" required>
</div>
</div> </div>
</div> </div>
<div ng-hide="sftpHide" class="form-group"> <div class="row">
<label class="col-sm-3 control-label">{% trans "Username" %}</label> <div class="col-md-6">
<div class="col-sm-6"> <div class="form-group">
<input type="text" class="form-control" ng-model="userName" required> <label class="form-label">{% trans "SSH Port" %}</label>
<input type="text" class="form-control" ng-model="backupSSHPort"
placeholder="{% trans "Default: 22" %}">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">{% trans "Remote Path" %}</label>
<input type="text" class="form-control" ng-model="path"
placeholder="/home/backups" required>
</div>
</div> </div>
</div> </div>
<div style="text-align: center; margin-top: 2rem;">
<button type="button" ng-click="addDestination('SFTP')" class="btn-primary">
<i class="fas fa-plus-circle"></i>
{% trans "Add SFTP Destination" %}
</button>
</div>
</div>
<div ng-hide="sftpHide" class="form-group"> <!-- Local Configuration -->
<label class="col-sm-3 control-label">{% trans "Password" %}</label> <div ng-hide="localHide" class="form-section">
<div class="col-sm-6"> <h3 style="font-size: 1.1rem; font-weight: 600; color: #1e293b; margin-bottom: 1.5rem;">
<input placeholder="" type="password" class="form-control" ng-model="password" required> <i class="fas fa-hdd" style="color: #5b5fcf; margin-right: 0.5rem;"></i>
{% trans "Local Storage Configuration" %}
</h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">{% trans "Name" %}</label>
<input type="text" class="form-control" ng-model="name"
placeholder="Local Backup Storage" required>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">{% trans "Local Path" %}</label>
<input type="text" class="form-control" ng-model="localPath"
placeholder="/backup/local" required>
</div>
</div> </div>
</div> </div>
<div ng-hide="sftpHide" class="form-group"> <div style="text-align: center; margin-top: 2rem;">
<label class="col-sm-3 control-label">{% trans "Port" %}</label> <button type="button" ng-click="addDestination('local')" class="btn-primary">
<div class="col-sm-6"> <i class="fas fa-plus-circle"></i>
<input placeholder="{% trans "Backup server SSH Port, leave empty for 22." %}" {% trans "Add Local Destination" %}
type="text" class="form-control" ng-model="backupSSHPort" required> </button>
</div>
</div> </div>
</div>
</form>
</div>
</div>
<div ng-hide="sftpHide" class="form-group"> <!-- SFTP Destinations Table -->
<label class="col-sm-3 control-label">{% trans "Path" %}</label> <div ng-hide="sftpHide" class="main-card">
<div class="col-sm-6"> <div class="card-header">
<input placeholder="Path on remote server to store backups." type="text" <h2 class="card-title">
class="form-control" ng-model="path" required> <i class="fas fa-list"></i>
</div> {% trans "Configured SFTP Destinations" %}
</div> </h2>
</div>
<div ng-hide="sftpHide" class="form-group"> <div class="card-body" style="padding: 0;">
<label class="col-sm-3 control-label"></label> <div class="table-responsive">
<div class="col-sm-4"> <table class="destination-table">
<button type="button" ng-click="addDestination('SFTP')" <thead>
class="btn btn-primary btn-lg btn-block">{% trans "Add Destination" %}</button> <tr>
<th>{% trans "Name" %}</th>
</div> <th>{% trans "Server IP" %}</th>
</div> <th>{% trans "Username" %}</th>
<th>{% trans "Path" %}</th>
<!--- SFTP ---> <th>{% trans "Port" %}</th>
<th style="text-align: center;">{% trans "Actions" %}</th>
</tr>
<!------ List of Destinations ---------------> </thead>
<tbody>
<div ng-hide="sftpHide" class="form-group"> <tr ng-repeat="record in records track by $index">
<td>
<div class="col-sm-12"> <strong ng-bind="record.name"></strong>
</td>
<table class="table"> <td>
<thead> <span class="info-badge" ng-bind="record.ip"></span>
<tr> </td>
<th>{% trans "Name" %}</th> <td>
<th>{% trans "IP" %}</th> <i class="fas fa-user" style="color: #64748b; margin-right: 0.5rem;"></i>
<th>{% trans "Username" %}</th> <span ng-bind="record.username"></span>
<th>{% trans "Path" %}</th> </td>
<th>{% trans "Port" %}</th> <td>
<th>{% trans "Delete" %}</th> <i class="fas fa-folder" style="color: #64748b; margin-right: 0.5rem;"></i>
</tr> <span ng-bind="record.path"></span>
</thead> </td>
<tbody> <td>
<tr ng-repeat="record in records track by $index"> <span class="port-badge" ng-bind="record.port"></span>
<td ng-bind="record.name"></td> </td>
<td ng-bind="record.ip"></td> <td style="text-align: center;">
<td ng-bind="record.username"></td> <button type="button"
<td ng-bind="record.path"></td> ng-click="removeDestination('SFTP', record.name)"
<td ng-bind="record.port"></td> class="btn-danger">
<td ng-click="removeDestination('SFTP', record.name)"><img <i class="fas fa-trash"></i>
src="{% static 'images/delete.png' %}"> {% trans "Delete" %}
</td> </button>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
</div>
<!------ List of records --------------->
<!--- SFTP End --->
<!--- AWS Start --->
<div ng-hide="localHide" class="form-group">
<label class="col-sm-3 control-label">{% trans "Name" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="name" required>
</div>
</div>
<div ng-hide="localHide" class="form-group">
<label class="col-sm-3 control-label">{% trans "Local Path" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" ng-model="localPath" required>
</div>
</div>
<div ng-hide="localHide" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="addDestination('local')"
class="btn btn-primary btn-lg btn-block">{% trans "Add Destination" %}</button>
</div>
</div>
<!--- SFTP --->
<!------ List of Destinations --------------->
<div ng-hide="localHide" class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Path" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td ng-bind="record.name"></td>
<td ng-bind="record.path"></td>
<td ng-click="removeDestination('local', record.name)"><img
src="{% static 'images/delete.png' %}">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!------ List of records --------------->
<!--- AWS End --->
</form>
</div> </div>
</div> </div>
</div> </div>
<!-- Local Destinations Table -->
<div ng-hide="localHide" class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-list"></i>
{% trans "Configured Local Destinations" %}
</h2>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-responsive">
<table class="destination-table">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Storage Path" %}</th>
<th style="text-align: center;">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td>
<strong ng-bind="record.name"></strong>
</td>
<td>
<i class="fas fa-folder-open" style="color: #5b5fcf; margin-right: 0.5rem;"></i>
<span style="font-family: monospace; background: #f3f4f6; padding: 0.25rem 0.5rem; border-radius: 4px;"
ng-bind="record.path"></span>
</td>
<td style="text-align: center;">
<button type="button"
ng-click="removeDestination('local', record.name)"
class="btn-danger">
<i class="fas fa-trash"></i>
{% trans "Delete" %}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div> </div>
<script>
// Initialize destination type to ensure proper display
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
var scope = angular.element(document.querySelector('[ng-controller="backupDestinations"]')).scope();
if (scope && !scope.destinationType) {
scope.$apply(function() {
scope.destinationType = 'local';
scope.fetchDetails();
});
}
}, 100);
});
</script>
{% endblock %} {% endblock %}

0
backup/templates/backup/backupLogs.html Executable file → Normal file
View File

953
backup/templates/backup/backupSchedule.html Executable file → Normal file
View File

@@ -3,310 +3,697 @@
{% block title %}{% trans "Schedule Backup - CyberPanel" %}{% endblock %} {% block title %}{% trans "Schedule Backup - CyberPanel" %}{% endblock %}
{% block content %} {% block content %}
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<style>
.modern-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.page-header {
text-align: center;
margin-bottom: 3rem;
padding: 3rem 0;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-radius: 20px;
animation: fadeInDown 0.5s ease-out;
}
.page-title {
font-size: 3rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
.page-subtitle {
font-size: 1.25rem;
color: #64748b;
margin-bottom: 1.5rem;
}
.header-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-primary:hover {
background: #4547a9;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(91, 95, 207, 0.4);
color: white;
}
.btn-secondary {
background: #fff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-secondary:hover {
background: #f8f9ff;
border-color: #5b5fcf;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(91, 95, 207, 0.2);
}
.btn-danger {
background: #ef4444;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.825rem;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
.btn-gray {
background: #6b7280;
color: white;
border: none;
padding: 0.5rem 1.5rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.btn-gray:hover {
background: #4b5563;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(107, 114, 128, 0.4);
}
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 10px 40px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
overflow: hidden;
margin-bottom: 2rem;
animation: fadeInUp 0.5s ease-out;
}
.card-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
padding: 1.5rem 2rem;
border-bottom: 1px solid #e8e9ff;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-body {
padding: 2rem;
}
.form-section {
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #1e293b;
font-size: 0.875rem;
}
.form-control {
width: 100%;
padding: 0.875rem 1rem;
border: 1px solid #e8e9ff;
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: #fff;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1);
}
.schedule-info-card {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.schedule-table {
width: 100%;
background: white;
border-radius: 12px;
overflow: hidden;
border: 1px solid #e8e9ff;
}
.schedule-table thead {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
}
.schedule-table th {
padding: 1rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e8e9ff;
}
.schedule-table td {
padding: 1rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #f3f4f6;
}
.schedule-table tbody tr:hover {
background: #f8f9ff;
}
.schedule-table tbody tr:last-child td {
border-bottom: none;
}
.status-badge {
background: #d1fae5;
color: #065f46;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.frequency-badge {
background: #e0e7ff;
color: #5b5fcf;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 3px solid #e8e9ff;
border-top-color: #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
margin-left: 1rem;
}
.action-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.job-selector {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.modal-content {
border-radius: 16px;
overflow: hidden;
border: none;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.modal-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-bottom: 1px solid #e8e9ff;
padding: 1.5rem 2rem;
}
.modal-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
}
.modal-body {
padding: 2rem;
}
.log-table {
width: 100%;
background: #f8f9ff;
border-radius: 8px;
overflow: hidden;
}
.log-table th {
background: #e8e9ff;
padding: 0.75rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
}
.log-table td {
padding: 0.75rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #e8e9ff;
}
.log-type {
font-weight: 600;
color: #5b5fcf;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.action-buttons {
flex-direction: column;
}
.action-buttons button {
width: 100%;
}
}
</style>
{% get_current_language as LANGUAGE_CODE %} <div class="modern-container" ng-controller="scheduleBackup">
<!-- Current language: {{ LANGUAGE_CODE }} --> <div class="page-header">
<h1 class="page-title">
<div ng-controller="scheduleBackup" class="container"> <i class="fas fa-clock"></i>
<div id="page-title"> {% trans "Schedule Backup" %}
<h2>{% trans "Schedule Backup" %} - <a target="_blank" </h1>
href="https://cyberpanel.net/KnowledgeBase/home/schedule-backups-local-or-sftp/" <p class="page-subtitle">{% trans "Schedule automatic backups to localhost or remote servers" %}</p>
style="height: 23px;line-height: 21px; text-decoration: underline" <div class="header-actions">
class="btn btn-border btn-alt border-red btn-link font-red" <a href="https://cyberpanel.net/KnowledgeBase/home/schedule-backups-local-or-sftp/"
title=""><span>{% trans "Remote Backups" %}</span></a> target="_blank"
</h2> class="btn-secondary">
<p>{% trans "On this page you can schedule Backups to localhost or remote server (If you have added one)" %}</p> <i class="fas fa-book"></i>
{% trans "Remote Backups Guide" %}
</a>
</div> </div>
</div>
<div class="panel"> <!-- Create New Schedule Card -->
<div class="panel-body"> <div class="main-card">
<h3 class="title-hero"> <div class="card-header">
{% trans "Create New Backup Schedule" %} <img ng-hide="cyberPanelLoading" <h2 class="card-title">
src="{% static 'images/loading.gif' %}" alt="cyberPanelLoading"> <i class="fas fa-plus-circle"></i>
</h3> {% trans "Create New Backup Schedule" %}
<div class="example-box-wrapper"> <span ng-hide="cyberPanelLoading" class="loading-spinner"></span>
</h2>
</div>
<form action="/" class="form-horizontal bordered-row"> <div class="card-body">
<form action="/" method="post">
<div class="row">
<div class="col-md-6">
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Destination" %}</label> <label class="form-label">
<div class="col-sm-6"> <i class="fas fa-server" style="margin-right: 0.5rem;"></i>
<select ng-model="selectedAccountAdd" class="form-control"> {% trans "Select Destination" %}
{% for items in destinations %} </label>
<option>{{ items }}</option> <select ng-model="selectedAccountAdd" class="form-control">
{% endfor %} <option value="">{% trans "Choose a destination..." %}</option>
{% for items in destinations %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">
<i class="fas fa-tag" style="margin-right: 0.5rem;"></i>
{% trans "Schedule Name" %}
</label>
<input type="text" class="form-control" ng-model="name"
placeholder="{% trans 'e.g., Daily Website Backup' %}" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">
<i class="fas fa-sync-alt" style="margin-right: 0.5rem;"></i>
{% trans "Backup Frequency" %}
</label>
<select ng-model="backupFrequency" class="form-control">
<option>Never</option>
<option>Daily</option>
<option>Weekly</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">
<i class="fas fa-history" style="margin-right: 0.5rem;"></i>
{% trans "Backup Retention (0 = unlimited)" %}
</label>
<input ng-model="backupRetention" type="number" value="0"
class="form-control" min="0">
</div>
</div>
</div>
<div style="text-align: center; margin-top: 2rem;">
<button type="button" ng-click="addSchedule()" class="btn-primary">
<i class="fas fa-calendar-plus"></i>
{% trans "Add Schedule" %}
</button>
</div>
</form>
</div>
</div>
<!-- Manage Existing Schedules Card -->
<div class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-tasks"></i>
{% trans "Manage Existing Backup Schedules" %}
<span ng-hide="cyberPanelLoading" class="loading-spinner"></span>
</h2>
</div>
<div class="card-body">
<!-- Destination and Job Selector -->
<div class="job-selector">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">
<i class="fas fa-server" style="margin-right: 0.5rem;"></i>
{% trans "Select Destination" %}
</label>
<select ng-change="fetchJobs()" ng-model="selectedAccount" class="form-control">
<option value="">{% trans "Choose a destination..." %}</option>
{% for items in destinations %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6" ng-hide="jobsHidden">
<div class="form-group">
<label class="form-label">
<i class="fas fa-briefcase" style="margin-right: 0.5rem;"></i>
{% trans "Select Job" %}
</label>
<div style="display: flex; gap: 1rem;">
<select ng-change="fetchWebsites()" ng-model="selectedJob" class="form-control">
<option value="">{% trans "Choose a job..." %}</option>
<option ng-repeat="job in jobs track by $index">{$ job $}</option>
</select> </select>
<button ng-hide="driveHidden" type="button" ng-click="deleteAccount()" class="btn-danger">
<i class="fas fa-trash"></i>
{% trans "Delete" %}
</button>
</div> </div>
</div> </div>
</div>
</div>
</div>
<div class="form-group"> <!-- Schedule Info -->
<label class="col-sm-3 control-label">{% trans "Name" %}</label> <div ng-hide="driveHidden" class="schedule-info-card">
<div class="col-sm-6"> <table class="schedule-table">
<input type="text" class="form-control" ng-model="name" required> <thead>
</div> <tr>
</div> <th>{% trans "Last Run" %}</th>
<th>{% trans "All Sites" %}</th>
<th>{% trans "Frequency" %} <span style="font-weight: normal;">({$ currently $})</span></th>
<div class="form-group"> <th>{% trans "Retention" %} <span style="font-weight: normal;">({$ currently $})</span></th>
<label class="col-sm-3 control-label">{% trans "Select Backup Frequency" %}</label> <th>{% trans "Current Status" %}</th>
<div class="col-sm-6"> </tr>
<select ng-model="backupFrequency" class="form-control"> </thead>
<tbody>
<tr>
<td><i class="fas fa-calendar-check" style="color: #5b5fcf; margin-right: 0.5rem;"></i>{$ lastRun $}</td>
<td><span class="frequency-badge">{$ allSites $}</span></td>
<td>
<select ng-change="changeFrequency()" ng-model="backupFrequency"
class="form-control" style="width: auto; min-width: 120px;">
<option>Never</option> <option>Never</option>
<option>Daily</option> <option>Daily</option>
<option>Weekly</option> <option>Weekly</option>
</select> </select>
</td>
<td>
<input ng-model="backupRetention" ng-change="changeFrequency()"
type="number" min="0"
class="form-control" style="width: 100px;">
</td>
<td><span class="status-badge">{$ currentStatus $}</span></td>
</tr>
</tbody>
</table>
<div style="margin-top: 1.5rem;">
<button data-toggle="modal" data-target="#backupLogs"
type="button" ng-click="fetchLogs()" class="btn-gray">
<i class="fas fa-file-alt"></i>
{% trans "View Logs" %}
</button>
</div>
</div>
<!-- Site Management -->
<div ng-hide="driveHidden">
<div class="form-section">
<div class="form-group">
<label class="form-label">
<i class="fas fa-globe" style="margin-right: 0.5rem;"></i>
{% trans "Add Sites for Backup" %}
</label>
<div style="display: flex; gap: 1rem; align-items: center;">
<select ng-model="selectedWebsite" class="form-control" style="flex: 1;">
<option value="">{% trans "Choose a website..." %}</option>
{% for items in websites %}
<option>{{ items }}</option>
{% endfor %}
</select>
<div class="action-buttons" style="flex: 0;">
<button type="button" ng-click="addSite('one')" class="btn-primary">
<i class="fas fa-plus"></i>
{% trans "Add Site" %}
</button>
<button type="button" ng-click="addSite('all')" class="btn-primary">
<i class="fas fa-plus-circle"></i>
{% trans "Add All" %}
</button>
</div> </div>
</div> </div>
</div>
</div>
<div class="form-group"> <!-- Sites Table -->
<label class="col-sm-3 control-label">{% trans "Select Backup Retention. Leave 0 for no limit" %}</label> <div style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
<div class="col-sm-9"> <select ng-model="recordsToShow" ng-change="fetchWebsites()"
<div class="number"> class="form-control" style="width: auto;">
<label> <option>10</option>
<input ng-model="backupRetention" type="number" value="0"> <option>50</option>
</label> <option>100</option>
</div> </select>
</div> </div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="addSchedule()"
class="btn btn-primary btn-lg btn-block">{% trans "Add Schedule" %}</button>
</div>
</div>
</form>
<table class="schedule-table">
<thead>
<tr>
<th>{% trans "Website" %}</th>
<th style="text-align: center;">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="website in websites track by $index">
<td>
<i class="fas fa-globe" style="color: #5b5fcf; margin-right: 0.5rem;"></i>
<strong ng-bind="website.name"></strong>
</td>
<td style="text-align: center;">
<button type="button" ng-click="deleteSite(website.name)" class="btn-danger">
<i class="fas fa-times"></i>
{% trans "Remove" %}
</button>
</td>
</tr>
</tbody>
</table>
<!-- Pagination -->
<div style="display: flex; justify-content: center; margin-top: 2rem;">
<select ng-model="currentPage" class="form-control"
ng-change="fetchWebsites()" style="width: auto;">
<option ng-repeat="page in pagination">{$ $index + 1 $}</option>
</select>
</div> </div>
</div> </div>
</div> </div>
<div class="panel">
<div class="panel-body">
<h3 class="title-hero">
{% trans "Manage Existing Backup Schedules" %} <img ng-hide="cyberPanelLoading"
src="{% static 'images/loading.gif' %}" alt="cyberPanelLoading">
</h3>
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Destination" %}</label>
<div class="col-sm-6">
<select ng-change="fetchJobs()" ng-model="selectedAccount" class="form-control">
{% for items in destinations %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div ng-hide="jobsHidden" class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Job" %}</label>
<div class="col-sm-6">
<select ng-change="fetchWebsites()" ng-model="selectedJob" class="form-control">
<option ng-repeat="job in jobs track by $index">{$ job $}</option>
</select>
</div>
<button ng-hide="driveHidden" type="button" ng-click="deleteAccount()"
class="btn btn-danger">{% trans "Delete" %}</button>
</div>
<div ng-hide="driveHidden" class="form-group">
<label class="col-sm-3 control-label">{% trans "Add Sites for Backup" %}</label>
<div class="col-sm-6">
<select ng-model="selectedWebsite" class="form-control">
{% for items in websites %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
<button type="button" ng-click="addSite('one')"
class="btn btn-primary">{% trans "Add Site" %}</button>
<button type="button" ng-click="addSite('all')"
class="btn btn-primary">{% trans "Add All" %}</button>
</div>
<div ng-hide="driveHidden" class="form-group">
<div style="border: 1px solid green;" class="col-sm-12">
<table style="margin-top: 2%" class="table">
<thead>
<tr>
<th>Last Run</th>
<th>All Sites</th>
<th>Frequency ({$ currently $})</th>
<th>Retention ({$ currently $})</th>
<th>Current Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>{$ lastRun $}</td>
<td>{$ allSites $}</td>
<td>
<select ng-change="changeFrequency()" ng-model="backupFrequency"
class="form-control">
<option>Never</option>
<option>Daily</option>
<option>Weekly</option>
</select>
</td>
<td>{$ currentStatus $}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div style="border: 1px solid green;" ng-hide="driveHidden" class="form-group">
<div class="row">
<div style="margin-left: 2%" class="col-sm-3">
<button data-toggle="modal" data-target="#backupLogs" ng-hide="driveHidden"
type="button" ng-click="fetchLogs()"
class="btn btn-gray">{% trans "View Logs" %}</button>
<div id="backupLogs" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;
</button>
<h4 class="modal-title">{% trans "Backup logs" %} <img
ng-hide="cyberPanelLoading"
src="{% static 'images/loading.gif' %}">
</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm-9">
</div>
<div class="col-sm-2">
<div class="form-group">
<select ng-model="recordsToShowLogs"
ng-change="fetchLogs()"
class="form-control" id="example-select">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
</div>
</div>
<table style="margin: 0px; padding-bottom: 2%" class="table">
<thead>
<tr>
<th>Type</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="log in logs track by $index">
<td ng-bind="log.type"></td>
<td ng-bind="log.message"></td>
</tr>
</tbody>
</table>
<div style="margin-top: 2%" class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8">
</div>
<div class="col-md-3">
<div class="form-group">
<select ng-model="currentPageLogs"
class="form-control"
ng-change="fetchLogs()">
<option ng-repeat="page in paginationLogs">
{$
$index + 1
$}
</option>
</select>
</div>
</div>
</div> <!-- end row -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
</div>
<div class="col-sm-2">
<div class="form-group">
<select ng-model="recordsToShow"
ng-change="fetchWebsites()"
class="form-control" id="example-select">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
</div>
</div>
<table style="margin-top: 2%" class="table">
<thead>
<tr>
<th>Sites</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="website in websites track by $index">
<td ng-bind="website.name"></td>
<td>
<button type="button" ng-click="deleteSite(website.name)"
class="btn btn-danger">{% trans "Delete" %}</button>
</td>
</tr>
</tbody>
</table>
<div style="margin-top: 2%" class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8">
</div>
<div class="col-md-3">
<div class="form-group">
<select ng-model="currentPage" class="form-control"
ng-change="fetchWebsites()">
<option ng-repeat="page in pagination">{$ $index + 1
$}
</option>
</select>
</div>
</div>
</div> <!-- end row -->
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div> </div>
<!-- Logs Modal -->
<div id="backupLogs" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">
<i class="fas fa-file-alt" style="margin-right: 0.5rem;"></i>
{% trans "Backup Logs" %}
<span ng-hide="cyberPanelLoading" class="loading-spinner" style="width: 16px; height: 16px;"></span>
</h4>
<button type="button" class="close" data-dismiss="modal" style="font-size: 1.5rem;">&times;</button>
</div>
<div class="modal-body">
<div style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
<select ng-model="recordsToShowLogs" ng-change="fetchLogs()"
class="form-control" style="width: auto;">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
{% endblock %} <table class="log-table">
<thead>
<tr>
<th style="width: 100px;">{% trans "Type" %}</th>
<th>{% trans "Message" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="log in logs track by $index">
<td class="log-type" ng-bind="log.type"></td>
<td ng-bind="log.message"></td>
</tr>
</tbody>
</table>
<div style="display: flex; justify-content: center; margin-top: 2rem;">
<select ng-model="currentPageLogs" class="form-control"
ng-change="fetchLogs()" style="width: auto;">
<option ng-repeat="page in paginationLogs">{$ $index + 1 $}</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

902
backup/templates/backup/googleDrive.html Executable file → Normal file
View File

@@ -3,261 +3,687 @@
{% block title %}{% trans "Google Drive Backups - CyberPanel" %}{% endblock %} {% block title %}{% trans "Google Drive Backups - CyberPanel" %}{% endblock %}
{% block content %} {% block content %}
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<style>
.modern-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.page-header {
text-align: center;
margin-bottom: 3rem;
padding: 3rem 0;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-radius: 20px;
animation: fadeInDown 0.5s ease-out;
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: url('data:image/svg+xml;utf8,<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h100v100H0z" fill="none"/><path d="M0 50h100M50 0v100" stroke="%23e8e9ff" stroke-width="0.5"/></svg>') repeat;
transform: rotate(45deg);
opacity: 0.5;
}
.page-title {
font-size: 3rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
position: relative;
z-index: 1;
}
.google-drive-icon {
width: 60px;
height: 60px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.page-subtitle {
font-size: 1.25rem;
color: #64748b;
margin-bottom: 1.5rem;
position: relative;
z-index: 1;
}
.header-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
position: relative;
z-index: 1;
}
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-primary:hover {
background: #4547a9;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(91, 95, 207, 0.4);
color: white;
}
.btn-secondary {
background: #fff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-secondary:hover {
background: #f8f9ff;
border-color: #5b5fcf;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(91, 95, 207, 0.2);
}
.btn-danger {
background: #ef4444;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.825rem;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
.btn-gray {
background: #6b7280;
color: white;
border: none;
padding: 0.5rem 1.5rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.btn-gray:hover {
background: #4b5563;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(107, 114, 128, 0.4);
}
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 10px 40px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
overflow: hidden;
margin-bottom: 2rem;
animation: fadeInUp 0.5s ease-out;
}
.card-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
padding: 1.5rem 2rem;
border-bottom: 1px solid #e8e9ff;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-body {
padding: 2rem;
}
.account-selector {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.form-section {
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #1e293b;
font-size: 0.875rem;
}
.form-control {
width: 100%;
padding: 0.875rem 1rem;
border: 1px solid #e8e9ff;
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: #fff;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1);
}
.gdrive-table {
width: 100%;
background: white;
border-radius: 12px;
overflow: hidden;
border: 1px solid #e8e9ff;
}
.gdrive-table thead {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
}
.gdrive-table th {
padding: 1rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e8e9ff;
}
.gdrive-table td {
padding: 1rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #f3f4f6;
}
.gdrive-table tbody tr:hover {
background: #f8f9ff;
}
.gdrive-table tbody tr:last-child td {
border-bottom: none;
}
.frequency-badge {
background: #e0e7ff;
color: #5b5fcf;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
}
.retention-info {
background: #fef3c7;
border: 1px solid #fde68a;
border-radius: 12px;
padding: 1rem 1.5rem;
margin-bottom: 2rem;
display: flex;
align-items: center;
gap: 1rem;
}
.retention-info i {
color: #f59e0b;
font-size: 1.25rem;
}
.retention-info p {
margin: 0;
color: #92400e;
font-size: 0.875rem;
}
.retention-info a {
color: #5b5fcf;
font-weight: 600;
text-decoration: none;
}
.retention-info a:hover {
text-decoration: underline;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 3px solid #e8e9ff;
border-top-color: #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
margin-left: 1rem;
}
.modal-content {
border-radius: 16px;
overflow: hidden;
border: none;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.modal-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-bottom: 1px solid #e8e9ff;
padding: 1.5rem 2rem;
}
.modal-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
}
.modal-body {
padding: 2rem;
}
.modal-footer {
border-top: 1px solid #e8e9ff;
padding: 1.5rem 2rem;
background: #f8f9ff;
}
.log-table {
width: 100%;
background: #f8f9ff;
border-radius: 8px;
overflow: hidden;
}
.log-table th {
background: #e8e9ff;
padding: 0.75rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
}
.log-table td {
padding: 0.75rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #e8e9ff;
}
.log-type {
font-weight: 600;
color: #5b5fcf;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.google-drive-icon {
width: 48px;
height: 48px;
}
}
</style>
{% get_current_language as LANGUAGE_CODE %} <div class="modern-container" ng-controller="googleDrive">
<!-- Current language: {{ LANGUAGE_CODE }} --> <div class="page-header">
<h1 class="page-title">
<div class="container"> <div class="google-drive-icon">
<div id="page-title"> <img src="https://upload.wikimedia.org/wikipedia/commons/1/12/Google_Drive_icon_%282020%29.svg"
<h2>{% trans "Set up Google Drive Backups" %} - <a target="_blank" alt="Google Drive" style="width: 36px; height: 36px;">
href="https://cyberpanel.net/docs/backup-to-google-drive/" </div>
style="height: 23px;line-height: 21px;" {% trans "Google Drive Backups" %}
class="btn btn-border btn-alt border-red btn-link font-red" </h1>
title=""><span>{% trans "Remote Backups" %}</span></a> <p class="page-subtitle">{% trans "Set up and manage automatic backups to Google Drive" %}</p>
</h2> <div class="header-actions">
<p>{% trans "On this page you can set up and manage Google Drive Backups." %}</p> <a href="https://cyberpanel.net/docs/backup-to-google-drive/"
target="_blank"
class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Documentation" %}
</a>
<button type="button" class="btn-primary" data-toggle="modal" data-target="#setupGdrive">
<i class="fas fa-plus-circle"></i>
{% trans "Setup New Account" %}
</button>
</div> </div>
</div>
<div ng-controller="googleDrive" class="panel"> <!-- Main Configuration Card -->
<div class="panel-body"> <div class="main-card">
<h3 class="title-hero"> <div class="card-header">
{% trans "On this page you can set up and manage Google Drive Backups." %} <img <h2 class="card-title">
ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}"> <a <i class="fas fa-cog"></i>
class="btn btn-border btn-alt border-blue-alt btn-link font-blue-alt" {% trans "Google Drive Backup Configuration" %}
href="#" <span ng-hide="cyberPanelLoading" class="loading-spinner"></span>
title="" data-toggle="modal" </h2>
data-target="#setupGdrive"><span>{% trans "Setup new Account" %}</span></a> </div>
<div id="setupGdrive" class="modal fade" role="dialog"> <div class="card-body">
<div class="modal-dialog"> <!-- Account Selector -->
<!-- Modal content--> <div class="account-selector">
<div class="modal-content"> <div class="form-group">
<div class="modal-header"> <label class="form-label">
<button type="button" class="close" data-dismiss="modal">&times; <i class="fas fa-user-circle" style="margin-right: 0.5rem;"></i>
</button> {% trans "Select Google Drive Account" %}
<h4 class="modal-title">{% trans "Set up account" %}</h4> </label>
</div> <div style="display: flex; gap: 1rem; align-items: center;">
<div class="modal-body"> <select ng-change="fetchWebsites()" ng-model="selectedAccount" class="form-control">
<option value="">{% trans "Choose an account..." %}</option>
{% for items in accounts %}
<option>{{ items }}</option>
{% endfor %}
</select>
<button ng-hide="driveHidden" type="button" ng-click="deleteAccount()" class="btn-danger">
<i class="fas fa-trash"></i>
{% trans "Delete" %}
</button>
</div>
</div>
</div>
<form name="containerSettingsForm" action="/" class="form-horizontal"> <!-- Backup Settings -->
<div ng-hide="driveHidden">
<div ng-hide="installationDetailsForm" class="form-group"> <div class="row">
<label class="col-sm-3 control-label">{% trans "Account Name" %}</label> <div class="col-md-6">
<div class="col-sm-6"> <div class="form-group">
<input name="accountName" type="text" class="form-control" <label class="form-label">
ng-model="accountName"> <i class="fas fa-sync-alt" style="margin-right: 0.5rem;"></i>
</div> {% trans "Backup Frequency" %}
</div> </label>
<select ng-change="changeFrequency()" ng-model="backupFrequency" class="form-control">
</form> <option>Never</option>
<option>Daily</option>
</div> <option>Weekly</option>
<div class="modal-footer"> </select>
<button type="button" class="btn btn-primary" <small class="form-text text-muted" style="margin-top: 0.5rem;">
ng-click="setupAccount()">Save <img {% trans "Currently" %}: <span class="frequency-badge">{$ currently $}</span>
ng-hide="cyberPanelLoading" </small>
src="{% static 'images/loading.gif' %}">
</button>
<button type="button" ng-disabled="savingSettings"
class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div> </div>
</div> </div>
</h3> <div class="col-md-6">
<div class="example-box-wrapper">
<form action="/" class="form-horizontal bordered-row">
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Drive Account" %}</label> <label class="form-label">
<div class="col-sm-6"> <i class="fas fa-history" style="margin-right: 0.5rem;"></i>
<select ng-change="fetchWebsites()" ng-model="selectedAccount" class="form-control"> {% trans "Backup File Retention" %}
{% for items in accounts %} </label>
<option>{{ items }}</option> <select id="fileretention" ng-model="Retentiontime" ng-change="changeRetention()" class="form-control">
{% endfor %} <option value="1d">1 day</option>
</select> <option value="1w">1 week</option>
</div> <option value="1m">1 month</option>
<button ng-hide="driveHidden" type="button" ng-click="deleteAccount()" <option value="6m">6 months</option>
class="btn btn-danger">{% trans "Delete" %}</button> </select>
</div> </div>
</div>
</div>
<div ng-hide="driveHidden" class="form-group"> <!-- Retention Info -->
<label class="col-sm-3 control-label">{% trans "Select Backup Frequency" %}</label> <div class="retention-info">
<div class="col-sm-6"> <i class="fas fa-info-circle"></i>
<select ng-change="changeFrequency()" ng-model="backupFrequency" class="form-control"> <p>
<option>Never</option> {% trans "Backup retention is a" %}
<option>Daily</option> <a href="https://cyberpanel.net/cyberpanel-addons" target="_blank">{% trans "paid feature" %}</a>.
<option>Weekly</option> {% trans "Upgrade to manage how long backups are stored." %}
</select> </p>
</div> </div>
Currently: {$ currently $}
<!-- Site Management -->
<div class="form-section">
<div class="form-group">
<label class="form-label">
<i class="fas fa-globe" style="margin-right: 0.5rem;"></i>
{% trans "Add Sites for Backup" %}
</label>
<div style="display: flex; gap: 1rem; align-items: center;">
<select ng-model="selectedWebsite" class="form-control">
<option value="">{% trans "Choose a website..." %}</option>
{% for items in websites %}
<option>{{ items }}</option>
{% endfor %}
</select>
<button type="button" ng-click="addSite()" class="btn-primary">
<i class="fas fa-plus"></i>
{% trans "Add Site" %}
</button>
</div> </div>
</div>
</div>
<div ng-hide="driveHidden" class="form-group"> <!-- Action Buttons -->
<label class="col-sm-3 control-label">{% trans "Add Sites for Backup" %}</label> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<div class="col-sm-6"> <button data-toggle="modal" data-target="#backupLogs"
<select ng-model="selectedWebsite" class="form-control"> type="button" ng-click="fetchLogs()" class="btn-gray">
{% for items in websites %} <i class="fas fa-file-alt"></i>
<option>{{ items }}</option> {% trans "View Logs" %}
{% endfor %} </button>
</select> <select ng-model="recordsToShow" ng-change="fetchWebsites()"
</div> class="form-control" style="width: auto;">
<button type="button" ng-click="addSite()" <option>10</option>
class="btn btn-primary">{% trans "Add Site" %}</button> <option>50</option>
</div> <option>100</option>
</select>
</div>
<div ng-hide="driveHidden" id="checkret" class="form-group">
<label class="col-sm-3 control-label">{% trans "Backup File Retention" %} (<a href="https://cyberpanel.net/cyberpanel-addons">Paid Feature</a> )</label>
<div class="col-sm-6">
<select id="fileretention" ng-model="Retentiontime" ng-change="changeRetention()" class="form-control">
<option value="1d">1 days</option>
<option value="1w"> 1 week</option>
<option value="1m"> 1 month</option>
<option value="6m"> 6 months</option>
</select>
</div>
</div>
<div ng-hide="driveHidden" class="form-group">
<div class="row">
<div style="margin-left: 2%" class="col-sm-3">
<button data-toggle="modal" data-target="#backupLogs" ng-hide="driveHidden"
type="button" ng-click="fetchLogs()"
class="btn btn-gray">{% trans "View Logs" %}</button>
<div id="backupLogs" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;
</button>
<h4 class="modal-title">{% trans "Git Logs" %} <img
ng-hide="cyberPanelLoading"
src="{% static 'images/loading.gif' %}">
</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm-9">
</div>
<div class="col-sm-2">
<div class="form-group">
<select ng-model="recordsToShowLogs"
ng-change="fetchLogs()"
class="form-control" id="example-select">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
</div>
</div>
<table style="margin: 0px; padding-bottom: 2%" class="table">
<thead>
<tr>
<th>Type</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="log in logs track by $index">
<td ng-bind="log.type"></td>
<td ng-bind="log.message"></td>
</tr>
</tbody>
</table>
<div style="margin-top: 2%" class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8">
</div>
<div class="col-md-3">
<div class="form-group">
<select ng-model="currentPageLogs"
class="form-control"
ng-change="fetchLogs()">
<option ng-repeat="page in paginationLogs">{$ $index + 1 $}</option>
</select>
</div>
</div>
</div> <!-- end row -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
</div>
<div class="col-sm-2">
<div class="form-group">
<select ng-model="recordsToShow"
ng-change="fetchWebsites()"
class="form-control" id="example-select">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
</div>
</div>
<table style="margin-top: 2%" class="table">
<thead>
<tr>
<th>Sites</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="website in websites track by $index">
<td ng-bind="website.name"></td>
<td>
<button type="button" ng-click="deleteSite(website.name)"
class="btn btn-danger">{% trans "Delete" %}</button>
</td>
</tr>
</tbody>
</table>
<div style="margin-top: 2%" class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-8">
</div>
<div class="col-md-3">
<div class="form-group">
<select ng-model="currentPage" class="form-control"
ng-change="fetchWebsites()">
<option ng-repeat="page in pagination">{$ $index + 1$}</option>
</select>
</div>
</div>
</div> <!-- end row -->
</div>
</div>
</div>
</form>
<!-- Sites Table -->
<table class="gdrive-table">
<thead>
<tr>
<th>{% trans "Website" %}</th>
<th style="text-align: center;">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="website in websites track by $index">
<td>
<i class="fas fa-globe" style="color: #5b5fcf; margin-right: 0.5rem;"></i>
<strong ng-bind="website.name"></strong>
</td>
<td style="text-align: center;">
<button type="button" ng-click="deleteSite(website.name)" class="btn-danger">
<i class="fas fa-times"></i>
{% trans "Remove" %}
</button>
</td>
</tr>
</tbody>
</table>
<!-- Pagination -->
<div style="display: flex; justify-content: center; margin-top: 2rem;">
<select ng-model="currentPage" class="form-control"
ng-change="fetchWebsites()" style="width: auto;">
<option ng-repeat="page in pagination">{$ $index + 1 $}</option>
</select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Setup Account Modal -->
<div id="setupGdrive" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">
<i class="fas fa-user-plus" style="margin-right: 0.5rem;"></i>
{% trans "Set up Google Drive Account" %}
</h4>
<button type="button" class="close" data-dismiss="modal" style="font-size: 1.5rem;">&times;</button>
</div>
<div class="modal-body">
<form name="containerSettingsForm" action="/" method="post">
<div class="form-group">
<label class="form-label">
<i class="fas fa-tag" style="margin-right: 0.5rem;"></i>
{% trans "Account Name" %}
</label>
<input name="accountName" type="text" class="form-control"
ng-model="accountName"
placeholder="{% trans 'e.g., My Google Drive Backup' %}" required>
<small class="form-text text-muted">
{% trans "Choose a descriptive name for this Google Drive account" %}
</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary" data-dismiss="modal">
<i class="fas fa-times"></i>
{% trans "Cancel" %}
</button>
<button type="button" class="btn-primary" ng-click="setupAccount()">
<i class="fas fa-check"></i>
{% trans "Continue Setup" %}
<span ng-hide="cyberPanelLoading" class="loading-spinner" style="width: 16px; height: 16px;"></span>
</button>
</div>
</div>
</div>
</div>
{% endblock %} <!-- Logs Modal -->
<div id="backupLogs" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">
<i class="fas fa-file-alt" style="margin-right: 0.5rem;"></i>
{% trans "Backup Logs" %}
<span ng-hide="cyberPanelLoading" class="loading-spinner" style="width: 16px; height: 16px;"></span>
</h4>
<button type="button" class="close" data-dismiss="modal" style="font-size: 1.5rem;">&times;</button>
</div>
<div class="modal-body">
<div style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
<select ng-model="recordsToShowLogs" ng-change="fetchLogs()"
class="form-control" style="width: auto;">
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</div>
<table class="log-table">
<thead>
<tr>
<th style="width: 100px;">{% trans "Type" %}</th>
<th>{% trans "Message" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="log in logs track by $index">
<td class="log-type" ng-bind="log.type"></td>
<td ng-bind="log.message"></td>
</tr>
</tbody>
</table>
<div style="display: flex; justify-content: center; margin-top: 2rem;">
<select ng-model="currentPageLogs" class="form-control"
ng-change="fetchLogs()" style="width: auto;">
<option ng-repeat="page in paginationLogs">{$ $index + 1 $}</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

0
backup/templates/backup/index.html Executable file → Normal file
View File

1150
backup/templates/backup/oneClickBackups.html Executable file → Normal file

File diff suppressed because it is too large Load Diff

789
backup/templates/backup/remoteBackups.html Executable file → Normal file
View File

@@ -4,149 +4,690 @@
{% block content %} {% block content %}
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container"> <style>
<div id="page-title"> .modern-container {
<h2>{% trans "Remote Backups" %} - <a target="_blank" href="http://go.cyberpanel.net/remote-transfer" style="height: 23px;line-height: 21px;" class="btn btn-border btn-alt border-red btn-link font-red" title=""><span>{% trans "Remote Transfer" %}</span></a></h2> max-width: 1400px;
<p>{% trans "This feature can import website(s) from remote server" %}</p> margin: 0 auto;
</div> padding: 2rem;
}
.page-header {
text-align: center;
margin-bottom: 3rem;
padding: 3rem 0;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-radius: 20px;
animation: fadeInDown 0.5s ease-out;
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at 30% 70%, rgba(91, 95, 207, 0.1) 0%, transparent 50%);
animation: float 20s ease-in-out infinite;
}
.page-title {
font-size: 3rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
position: relative;
z-index: 1;
}
.page-subtitle {
font-size: 1.25rem;
color: #64748b;
margin-bottom: 1.5rem;
position: relative;
z-index: 1;
}
.header-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
position: relative;
z-index: 1;
}
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-primary:hover {
background: #4547a9;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(91, 95, 207, 0.4);
color: white;
}
.btn-primary:disabled {
background: #cbd5e1;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-secondary {
background: #fff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-secondary:hover {
background: #f8f9ff;
border-color: #5b5fcf;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(91, 95, 207, 0.2);
}
.btn-success {
background: #10b981;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.btn-success:hover {
background: #059669;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
}
.btn-danger {
background: #ef4444;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(239, 68, 68, 0.4);
}
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 10px 40px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
overflow: hidden;
margin-bottom: 2rem;
animation: fadeInUp 0.5s ease-out;
}
.card-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
padding: 1.5rem 2rem;
border-bottom: 1px solid #e8e9ff;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-body {
padding: 2rem;
}
.form-section {
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #1e293b;
font-size: 0.875rem;
}
.form-control {
width: 100%;
padding: 0.875rem 1rem;
border: 1px solid #e8e9ff;
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: #fff;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1);
}
.alert {
padding: 1rem 1.5rem;
border-radius: 12px;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
animation: slideInRight 0.3s ease-out;
}
.alert-success {
background: #d1fae5;
color: #065f46;
border: 1px solid #a7f3d0;
}
.alert-danger {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fecaca;
}
.alert p {
margin: 0;
font-size: 0.875rem;
font-weight: 500;
}
.remote-table {
width: 100%;
background: white;
border-radius: 12px;
overflow: hidden;
border: 1px solid #e8e9ff;
margin-top: 1rem;
}
.remote-table thead {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
}
.remote-table th {
padding: 1rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e8e9ff;
}
.remote-table td {
padding: 1rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #f3f4f6;
}
.remote-table tbody tr:hover {
background: #f8f9ff;
}
.remote-table tbody tr:last-child td {
border-bottom: none;
}
.checkbox-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.checkbox-wrapper input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #5b5fcf;
}
.search-box {
position: relative;
margin-bottom: 1.5rem;
}
.search-box i {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: #94a3b8;
}
.search-input {
width: 100%;
padding: 0.875rem 1rem 0.875rem 3rem;
border: 1px solid #e8e9ff;
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: #f8f9ff;
}
.search-input:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1);
background: #fff;
}
.terminal-section {
background: #1e293b;
border-radius: 12px;
overflow: hidden;
margin-top: 2rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.terminal-header {
background: #334155;
padding: 0.75rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.terminal-title {
color: #e2e8f0;
font-size: 0.875rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
}
.terminal-dots {
display: flex;
gap: 0.5rem;
}
.terminal-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.terminal-dot.red {
background: #ef4444;
}
.terminal-dot.yellow {
background: #f59e0b;
}
.terminal-dot.green {
background: #10b981;
}
.terminal-body {
padding: 1.5rem;
height: 350px;
overflow-y: auto;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.875rem;
line-height: 1.6;
color: #e2e8f0;
}
.terminal-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.action-buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin-top: 2rem;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 3px solid #e8e9ff;
border-top-color: #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
margin-left: 1rem;
}
.php-badge {
background: #e0e7ff;
color: #5b5fcf;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
}
.package-badge {
background: #f3f4f6;
color: #1e293b;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes float {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
33% { transform: translate(30px, -30px) rotate(120deg); }
66% { transform: translate(-20px, 20px) rotate(240deg); }
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.terminal-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.action-buttons button {
width: 100%;
}
}
</style>
<div ng-controller="remoteBackupControl" class="panel"> <div class="modern-container" ng-controller="remoteBackupControl">
<div class="panel-body"> <div class="page-header">
<h3 class="title-hero"> <h1 class="page-title">
{% trans "Remote Backups" %} <img ng-hide="backupLoading" src="{% static 'images/loading.gif' %}"> <i class="fas fa-server"></i>
</h3> {% trans "Remote Backups" %}
<div class="example-box-wrapper"> </h1>
<p class="page-subtitle">{% trans "Import websites from remote CyberPanel servers via SSH" %}</p>
<div class="header-actions">
<a href="https://cyberpanel.net/docs/transfer-website-from-another-server-using-remote-transfer/"
target="_blank"
class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Remote Transfer Guide" %}
</a>
</div>
</div>
<p ng-bind="transferStatus"></p> <!-- Connection Card -->
<form action="/" class="form-horizontal bordered-row"> <div class="main-card">
<div class="card-header">
<div class="form-group"> <h2 class="card-title">
<label class="col-sm-3 control-label">{% trans "IP Address" %}</label> <i class="fas fa-network-wired"></i>
<div class="col-sm-6"> {% trans "Remote Server Connection" %}
<input type="text" class="form-control" ng-model="IPAddress" required> <span ng-hide="backupLoading" class="loading-spinner"></span>
</div> </h2>
</div> </div>
<div class="card-body">
<form action="/" method="post">
<div class="form-group"> <div class="row">
<label class="col-sm-3 control-label">{% trans "Password" %}</label> <div class="col-md-6">
<div class="col-sm-6"> <div class="form-group">
<input ng-change="passwordEnter()" type="password" class="form-control" ng-model="password" required> <label class="form-label">
</div> <i class="fas fa-map-marker-alt" style="margin-right: 0.5rem;"></i>
</div> {% trans "Remote Server IP Address" %}
</label>
<div ng-hide="backupButton" class="form-group"> <input type="text" class="form-control" ng-model="IPAddress"
<label class="col-sm-3 control-label"></label> placeholder="192.168.1.100" required>
<div class="col-sm-4">
<button type="button" ng-disabled="fetchAccountsBtn" ng-click="fetchAccountsFromRemoteServer()" class="btn btn-primary btn-lg btn-block">{% trans "Fetch Accounts" %}</button>
</div>
</div>
<div ng-hide="transferBoxBtn" class="form-group">
<label class="col-sm-1 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-disabled="startTransferbtn" ng-click="startTransfer()" class="btn btn-primary btn-lg btn-block">{% trans "Start Transfer" %}</button>
</div>
<div class="col-sm-4">
<button type="button" ng-disabled="stopTransferbtn" ng-click="cancelRemoteBackup()" class="btn btn-primary btn-lg btn-block">{% trans "Cancel" %}</button>
</div>
</div>
<div ng-hide="notificationsBox" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<div ng-hide="errorMessage" class="alert alert-danger">
<p>{$ error_message $}</p>
</div> </div>
<div ng-hide="couldNotConnect" class="alert alert-danger"> </div>
<p>{% trans "Could not connect, please refresh this page." %}</p> <div class="col-md-6">
<div class="form-group">
<label class="form-label">
<i class="fas fa-key" style="margin-right: 0.5rem;"></i>
{% trans "Root Password" %}
</label>
<input ng-change="passwordEnter()" type="password" class="form-control"
ng-model="password" placeholder="••••••••" required>
</div> </div>
<div ng-hide="accountsFetched" class="alert alert-success">
<p>{% trans "Accounts Successfully Fetched from remote server." %}</p>
</div>
<div ng-hide="backupProcessStarted" class="alert alert-success">
<p>{% trans "Backup Process successfully started." %}</p>
</div>
<div ng-hide="backupCancelled" class="alert alert-success">
<p>{% trans "Backup successfully cancelled." %}</p>
</div>
</div> </div>
</div> </div>
<div ng-hide="backupButton" class="action-buttons">
<!------ List of Accounts in remote server ---------------> <button type="button" ng-disabled="fetchAccountsBtn"
ng-click="fetchAccountsFromRemoteServer()" class="btn-primary">
<div ng-hide="accountsInRemoteServerTable" class="form-group"> <i class="fas fa-cloud-download-alt"></i>
{% trans "Fetch Accounts" %}
<div class="col-sm-12"> </button>
<input type="text" ng-model="accountsSearch" placeholder="{% trans 'Search Accounts..' %}" class="form-control autocomplete-input">
</div>
</div>
<div ng-hide="accountsInRemoteServerTable" class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>{% trans "Website" %}</th>
<th>{% trans "PHP" %}</th>
<th>{% trans "Package" %}</th>
<th>{% trans "Email" %}</th>
<th><input ng-model="webSiteStatus" ng-change="allChecked(webSiteStatus)" type="checkbox" value=""></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records | filter:accountsSearch">
<td ng-bind="record.website"></td>
<td ng-bind="record.php"></td>
<td ng-bind="record.package"></td>
<td ng-bind="record.email"></td>
<td ng-click=""><input ng-model="webSiteStatus" ng-change="addRemoveWebsite(record.website,webSiteStatus)" type="checkbox" value=""></td>
</tr>
</tbody>
</table>
</div>
</div> </div>
<!------ List of Accounts in remote server --------------->
</form> </form>
</div>
</div>
<!-- Notifications -->
<div ng-hide="notificationsBox">
<div ng-hide="errorMessage" class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>
<p>{$ error_message $}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<i class="fas fa-times-circle"></i>
<p>{% trans "Could not connect, please refresh this page." %}</p>
</div>
<div ng-hide="accountsFetched" class="alert alert-success">
<i class="fas fa-check-circle"></i>
<p>{% trans "Accounts Successfully Fetched from remote server." %}</p>
</div>
<div ng-hide="backupProcessStarted" class="alert alert-success">
<i class="fas fa-play-circle"></i>
<p>{% trans "Backup Process successfully started." %}</p>
</div>
<div ng-hide="backupCancelled" class="alert alert-success">
<i class="fas fa-stop-circle"></i>
<p>{% trans "Backup successfully cancelled." %}</p>
</div>
</div>
<!-- Accounts Table -->
<div ng-hide="accountsInRemoteServerTable" class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-list"></i>
{% trans "Available Websites on Remote Server" %}
</h2>
</div>
<div class="card-body">
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" ng-model="accountsSearch"
placeholder="{% trans 'Search websites...' %}"
class="search-input">
</div>
<div class="table-responsive">
<table class="remote-table">
<thead>
<tr>
<th>{% trans "Website" %}</th>
<th>{% trans "PHP Version" %}</th>
<th>{% trans "Package" %}</th>
<th>{% trans "Email" %}</th>
<th style="text-align: center;">
<div class="checkbox-wrapper">
<input ng-model="webSiteStatus"
ng-change="allChecked(webSiteStatus)"
type="checkbox" value="">
</div>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records | filter:accountsSearch">
<td>
<i class="fas fa-globe" style="color: #5b5fcf; margin-right: 0.5rem;"></i>
<strong ng-bind="record.website"></strong>
</td>
<td>
<span class="php-badge" ng-bind="record.php"></span>
</td>
<td>
<span class="package-badge" ng-bind="record.package"></span>
</td>
<td>
<i class="fas fa-envelope" style="color: #94a3b8; margin-right: 0.5rem;"></i>
<span ng-bind="record.email"></span>
</td>
<td style="text-align: center;">
<div class="checkbox-wrapper">
<input ng-model="webSiteStatus"
ng-change="addRemoveWebsite(record.website,webSiteStatus)"
type="checkbox" value="">
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div ng-hide="transferBoxBtn" class="action-buttons">
<button type="button" ng-disabled="startTransferbtn"
ng-click="startTransfer()" class="btn-success">
<i class="fas fa-exchange-alt"></i>
{% trans "Start Transfer" %}
</button>
<button type="button" ng-disabled="stopTransferbtn"
ng-click="cancelRemoteBackup()" class="btn-danger">
<i class="fas fa-times"></i>
{% trans "Cancel Transfer" %}
</button>
</div>
</div>
</div>
<div ng-hide="backupStatus" class="form-group"> <!-- Transfer Progress Terminal -->
<div class="col-sm-6"> <div ng-hide="backupStatus" class="terminal-section">
<textarea ng-model="requestData" rows="15" class="form-control"></textarea> <div class="terminal-header">
<div class="terminal-title">
<i class="fas fa-terminal"></i>
{% trans "Transfer Progress" %}
</div>
<div class="terminal-dots">
<div class="terminal-dot red"></div>
<div class="terminal-dot yellow"></div>
<div class="terminal-dot green"></div>
</div>
</div>
<div class="terminal-body">
<div class="terminal-grid">
<div>
<h4 style="color: #94a3b8; margin-bottom: 1rem; font-size: 0.875rem;">
{% trans "Backup Progress" %}
</h4>
<div ng-bind="requestData" style="white-space: pre-wrap;"></div>
</div> </div>
<div class="col-sm-6"> <div>
<textarea ng-model="restoreData" rows="15" class="form-control"></textarea> <h4 style="color: #94a3b8; margin-bottom: 1rem; font-size: 0.875rem;">
{% trans "Restore Progress" %}
</h4>
<div ng-bind="restoreData" style="white-space: pre-wrap;"></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

524
backup/templates/backup/restore.html Executable file → Normal file
View File

@@ -4,113 +4,483 @@
{% block content %} {% block content %}
{% load static %} {% load static %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} --> <!-- Current language: {{ LANGUAGE_CODE }} -->
<style>
.modern-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.page-header {
text-align: center;
margin-bottom: 3rem;
padding: 3rem 0;
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
border-radius: 20px;
animation: fadeInDown 0.5s ease-out;
}
.page-title {
font-size: 3rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
.page-subtitle {
font-size: 1.25rem;
color: #64748b;
margin-bottom: 1.5rem;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.header-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.info-box {
background: #e0e7ff;
border: 1px solid #c7d2fe;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
display: flex;
align-items: center;
gap: 1rem;
}
.info-box i {
font-size: 1.5rem;
color: #5b5fcf;
}
.info-box p {
margin: 0;
color: #3730a3;
font-size: 0.925rem;
}
.btn-primary {
background: #5b5fcf;
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-primary:hover {
background: #4547a9;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(91, 95, 207, 0.4);
color: white;
}
.btn-secondary {
background: #fff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.875rem;
}
.btn-secondary:hover {
background: #f8f9ff;
border-color: #5b5fcf;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(91, 95, 207, 0.2);
}
.main-card {
background: white;
border-radius: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 10px 40px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
overflow: hidden;
margin-bottom: 2rem;
animation: fadeInUp 0.5s ease-out;
}
.card-header {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f1ff 100%);
padding: 1.5rem 2rem;
border-bottom: 1px solid #e8e9ff;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-body {
padding: 2rem;
}
.form-section {
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #1e293b;
font-size: 0.875rem;
}
.form-control {
width: 100%;
padding: 0.875rem 1rem;
border: 1px solid #e8e9ff;
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.3s ease;
background: #fff;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91, 95, 207, 0.1);
}
.restore-progress-card {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.progress-status {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.status-info {
display: flex;
align-items: center;
gap: 1rem;
}
.status-icon {
width: 40px;
height: 40px;
background: #f39c12;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
animation: pulse 2s infinite;
}
.status-details h4 {
margin: 0;
color: #856404;
font-size: 1rem;
}
.status-details p {
margin: 0;
color: #856404;
font-size: 0.875rem;
opacity: 0.8;
}
.backup-select-card {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 12px;
padding: 2rem;
margin-bottom: 2rem;
}
.backup-icon {
font-size: 3rem;
color: #5b5fcf;
text-align: center;
margin-bottom: 1.5rem;
}
.alert {
padding: 1rem 1.5rem;
border-radius: 12px;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
animation: slideInRight 0.3s ease-out;
}
.alert-success {
background: #d1fae5;
color: #065f46;
border: 1px solid #a7f3d0;
}
.alert-danger {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fecaca;
}
.loading-spinner {
width: 20px;
height: 20px;
border: 3px solid #e8e9ff;
border-top-color: #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
display: inline-block;
margin-left: 1rem;
}
.progress-table {
width: 100%;
background: white;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e8e9ff;
}
.progress-table th {
background: #f8f9ff;
padding: 0.75rem;
text-align: left;
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
border-bottom: 1px solid #e8e9ff;
}
.progress-table td {
padding: 0.75rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #f3f4f6;
}
.file-badge {
background: #e0e7ff;
color: #5b5fcf;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
font-family: monospace;
}
.status-text {
font-weight: 600;
color: #f39c12;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
}
</style>
<div class="container"> <div class="modern-container" ng-controller="restoreWebsiteControl">
<div id="page-title"> <div class="page-header">
<h2>{% trans "Restore Website" %} - <a target="_blank" href="http://go.cyberpanel.net/backup" style="height: 23px;line-height: 21px;" class="btn btn-border btn-alt border-red btn-link font-red" title=""><span>{% trans "Backup Docs" %}</span></a></h2> <h1 class="page-title">
<p>{% trans "This page can be used to restore your websites, Backup should be generated from CyberPanel Backup generation tool, it will detect all Backups under <strong>/home/backup</strong>." %}</p> <i class="fas fa-undo-alt"></i>
</div> {% trans "Restore Website" %}
</h1>
<p class="page-subtitle">{% trans "Restore your websites from backups created by CyberPanel" %}</p>
<div class="header-actions">
<a href="http://go.cyberpanel.net/backup" target="_blank" class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Backup Documentation" %}
</a>
</div>
</div>
<div ng-controller="restoreWebsiteControl" class="panel"> <!-- Info Box -->
<div class="panel-body"> <div class="info-box">
<h3 class="title-hero"> <i class="fas fa-info-circle"></i>
{% trans "Restore Website" %} <img ng-hide="restoreLoading" src="{% static 'images/loading.gif' %}"> <p>{% trans "This tool detects all backups under" %} <strong>/home/backup</strong>. {% trans "Backups should be generated from CyberPanel's backup generation tool." %}</p>
</h3> </div>
<div class="example-box-wrapper">
<div class="main-card">
<form action="/" class="form-horizontal bordered-row"> <div class="card-header">
<h2 class="card-title">
<i class="fas fa-folder-open"></i>
<div class="form-group"> {% trans "Select Backup to Restore" %}
<label class="col-sm-3 control-label">{% trans "Select Backup" %}</label> <span ng-hide="restoreLoading" class="loading-spinner"></span>
<div class="col-sm-6"> </h2>
</div>
<div class="card-body">
<form action="/" method="post">
<div class="backup-select-card">
<div class="backup-icon">
<i class="fas fa-archive"></i>
</div>
<div class="form-group">
<label class="form-label">
<i class="fas fa-database" style="margin-right: 0.5rem;"></i>
{% trans "Available Backups" %}
</label>
<select ng-change="fetchDetails()" ng-model="backupFile" class="form-control"> <select ng-change="fetchDetails()" ng-model="backupFile" class="form-control">
<option value="">{% trans "Choose a backup file..." %}</option>
{% for items in backups %} {% for items in backups %}
<option>{{ items }}</option> <option>{{ items }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div ng-hide="restoreButton" style="text-align: center; margin-top: 2rem;">
<button type="button" ng-click="restoreBackup()" id="restoreBackup" class="btn-primary">
<i class="fas fa-sync-alt"></i>
{% trans "Start Restoration" %}
</button>
</div>
</div> </div>
<!-- Restore Progress -->
<div ng-hide="runningRestore" class="restore-progress-card">
<div ng-hide="restoreButton" class="form-group"> <div class="progress-status">
<label class="col-sm-3 control-label"></label> <div class="status-info">
<div class="col-sm-4"> <div class="status-icon">
<button type="button" ng-click="restoreBackup()" id="restoreBackup" class="btn btn-primary btn-lg btn-block">{% trans "Restore" %}</button> <i class="fas fa-sync-alt fa-spin" style="color: white;"></i>
</div>
<div class="status-details">
<h4>{% trans "Restoration in Progress" %}</h4>
<p>{% trans "Please wait while your website is being restored..." %}</p>
</div>
</div>
</div> </div>
</div>
<table class="progress-table" style="margin-top: 1.5rem;">
<thead>
<!---- if restore is running ----->
<div ng-hide="runningRestore" class="form-group">
<div class="col-sm-12">
<table class="table">
<thead>
<tr> <tr>
<th>{% trans "Condition" %}</th> <th>{% trans "Condition" %}</th>
<th>{% trans "File Name" %}</th> <th>{% trans "File Name" %}</th>
<th>{% trans "Status" %} <img ng-hide="restoreFinished" src="{% static 'images/loading.gif' %}"></th> <th>
{% trans "Status" %}
<span ng-hide="restoreFinished" class="loading-spinner" style="width: 16px; height: 16px; margin-left: 0.5rem;"></span>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{$ running $}</td> <td>{$ running $}</td>
<td>{$ fileName $}</td> <td><span class="file-badge">{$ fileName $}</span></td>
<td style="color: red"><strong>{$ status $}</strong></td> <td><span class="status-text">{$ status $}</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
<!-- Alert Messages -->
<!---- if restore is running------> <div ng-hide="backupError" class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>
{% trans "Error message:" %} {$ errorMessage $}
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<div ng-hide="backupError" class="alert alert-danger">
<p>{% trans "Error message:" %} {$ errorMessage $}</p>
</div>
<div ng-hide="siteExists" class="alert alert-danger">
<p>{% trans "Site related to this Backup already exists." %}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
</div>
</div> </div>
<div ng-hide="siteExists" class="alert alert-danger">
<i class="fas fa-exclamation-triangle"></i>
{% trans "Site related to this backup already exists." %}
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<i class="fas fa-times-circle"></i>
{% trans "Could not connect to server. Please refresh this page." %}
</div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

0
backup/templates/backup/restoreOCBackups.html Executable file → Normal file
View File

0
backup/tests.py Executable file → Normal file
View File

0
backup/urls.py Executable file → Normal file
View File

0
backup/views.py Executable file → Normal file
View File

0
baseTemplate/__init__.py Executable file → Normal file
View File

0
baseTemplate/admin.py Executable file → Normal file
View File

0
baseTemplate/apps.py Executable file → Normal file
View File

0
baseTemplate/migrations/__init__.py Executable file → Normal file
View File

0
baseTemplate/models.py Executable file → Normal file
View File

View File

View File

View File

0
baseTemplate/static/baseTemplate/assets/bootstrap/css/bootstrap.css vendored Executable file → Normal file
View File

View File

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

0
baseTemplate/static/baseTemplate/assets/bootstrap/js/bootstrap.js vendored Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

Some files were not shown because too many files have changed in this diff Show More