Initial commit for v2.4.3

This commit is contained in:
usmannasir
2025-08-01 14:56:30 +05:00
commit 6dd7114f6d
4521 changed files with 1795978 additions and 0 deletions

BIN
databases/.DS_Store vendored Normal file

Binary file not shown.

0
databases/__init__.py Normal file
View File

9
databases/admin.py Normal file
View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from django.contrib import admin
from . import models
# Register your models here.
admin.site.register(models.Databases)

8
databases/apps.py Normal file
View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from django.apps import AppConfig
class DatabasesConfig(AppConfig):
name = 'databases'

View File

@@ -0,0 +1,383 @@
#!/usr/local/CyberCP/bin/python
import os.path
import sys
import django
from django.shortcuts import redirect
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
from django.http import HttpResponse
import json
from plogical.acl import ACLManager
import plogical.CyberCPLogFileWriter as logging
from plogical.mysqlUtilities import mysqlUtilities
from websiteFunctions.models import Websites
from databases.models import Databases, DBMeta
import argparse
from loginSystem.models import Administrator
import plogical.randomPassword as randomPassword
from plogical.httpProc import httpProc
from backup.models import DBUsers
class DatabaseManager:
REMOTE_ACCESS = 'remote_access'
def loadDatabaseHome(self, request = None, userID = None):
template = 'databases/index.html'
proc = httpProc(request, template, None, 'createDatabase')
return proc.render()
def phpMyAdmin(self, request = None, userID = None):
template = 'databases/phpMyAdmin.html'
proc = httpProc(request, template, None, 'createDatabase')
return proc.render()
def createDatabase(self, request = None, userID = None):
currentACL = ACLManager.loadedACL(userID)
websitesName = ACLManager.findAllSites(currentACL, userID)
template = 'databases/createDatabase.html'
proc = httpProc(request, template, {'websitesList': websitesName}, 'createDatabase')
return proc.render()
def submitDBCreation(self, userID = None, data = None, rAPI = None):
try:
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
if ACLManager.currentContextPermission(currentACL, 'createDatabase') == 0:
return ACLManager.loadErrorJson('createDBStatus', 0)
databaseWebsite = data['databaseWebsite']
dbName = data['dbName']
dbUsername = data['dbUsername']
dbPassword = data['dbPassword']
webUsername = data['webUserName']
if ACLManager.checkOwnership(databaseWebsite, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
if rAPI == None:
dbName = webUsername + "_" + dbName
dbUsername = webUsername + "_" + dbUsername
result = mysqlUtilities.submitDBCreation(dbName, dbUsername, dbPassword, databaseWebsite)
if result[0] == 1:
data_ret = {'status': 1, 'createDBStatus': 1, 'error_message': "None",
'dbName': dbName, 'dbUsername': dbUsername}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
else:
data_ret = {'status': 0, 'createDBStatus': 0, 'error_message': result[1]}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'createDBStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def deleteDatabase(self, request = None, userID = None):
currentACL = ACLManager.loadedACL(userID)
websitesName = ACLManager.findAllSites(currentACL, userID)
template = 'databases/deleteDatabase.html'
proc = httpProc(request, template, {'websitesList': websitesName}, 'deleteDatabase')
return proc.render()
def MySQLManager(self, request = None, userID = None):
try:
from plogical.processUtilities import ProcessUtilities
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
url = "https://platform.cyberpersons.com/CyberpanelAdOns/Adonpermission"
data = {
"name": "Filemanager",
"IP": ACLManager.fetchIP()
}
import requests
response = requests.post(url, data=json.dumps(data))
Status = response.json()['status']
if (Status == 1):
template = 'baseTemplate/FileManager.html'
else:
return redirect("https://cyberpanel.net/cyberpanel-addons")
else:
template = 'databases/mysqlmanager.html'
except BaseException as msg:
template = 'databases/mysqlmanager.html'
template = 'databases/mysqlmanager.html'
proc = httpProc(request, template, None, 'admin')
return proc.render()
def OptimizeMySQL(self, request = None, userID = None):
from cloudAPI.cloudManager import CloudManager
cm = CloudManager()
result = cm.fetchRam(request)
data1 = json.loads(result.content)
data = {}
data['ramInGB'] = data1.get('ramInGB')
data['conf'] = data1.get('conf')
template = 'databases/OptimizeMySQL.html'
proc = httpProc(request, template, data, 'admin')
return proc.render()
def Upgardemysql(self, request = None, userID = None):
data={}
data['mysqlversions']=['10.6','10.11']
template = 'databases/Updatemysql.html'
proc = httpProc(request, template, data, 'admin')
return proc.render()
def fetchDatabases(self, userID = None, data = None):
try:
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'deleteDatabase') == 0:
return ACLManager.loadErrorJson('fetchStatus', 0)
databaseWebsite = data['databaseWebsite']
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(databaseWebsite, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
website = Websites.objects.get(domain=databaseWebsite)
databases = Databases.objects.filter(website=website)
json_data = "["
checker = 0
for items in databases:
dic = {'id': items.pk,
'dbName': items.dbName,
'dbUser': items.dbUser, }
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
json_data = json_data + ']'
final_json = json.dumps({'status': 1, 'fetchStatus': 1, 'error_message': "None", "data": json_data})
return HttpResponse(final_json)
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg))
final_json = json.dumps({'status': 0, 'fetchStatus': 0, 'error_message': str(msg)})
return HttpResponse(final_json)
def submitDatabaseDeletion(self, userID = None, data = None):
try:
currentACL = ACLManager.loadedACL(userID)
admin = Administrator.objects.get(pk=userID)
if ACLManager.currentContextPermission(currentACL, 'deleteDatabase') == 0:
return ACLManager.loadErrorJson('deleteStatus', 0)
dbName = data['dbName']
db = Databases.objects.get(dbName=dbName)
if ACLManager.checkOwnership(db.website.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
result = mysqlUtilities.submitDBDeletion(dbName)
if result[0] == 1:
data_ret = {'status': 1, 'deleteStatus': 1, 'error_message': "None"}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
else:
data_ret = {'status': 0, 'deleteStatus': 0, 'error_message': result[1]}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'deleteStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def listDBs(self, request = None, userID = None):
currentACL = ACLManager.loadedACL(userID)
AllWebsites = ACLManager.findAllSites(currentACL, userID)
template = 'databases/listDataBases.html'
proc = httpProc(request, template, {'AllWebsites': AllWebsites}, 'listDatabases')
return proc.render()
def changePassword(self, userID = None, data = None):
try:
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'listDatabases') == 0:
return ACLManager.loadErrorJson('changePasswordStatus', 0)
userName = data['dbUserName']
dbPassword = data['dbPassword']
db = Databases.objects.filter(dbUser=userName)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(db[0].website.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
try:
meta = DBMeta.objects.get(database=db[0], key=DatabaseManager.REMOTE_ACCESS)
host = json.loads(meta.value)['remoteIP']
except:
host = None
res = mysqlUtilities.changePassword(userName, dbPassword, None, host)
if res == 0:
data_ret = {'status': 0, 'changePasswordStatus': 0,'error_message': "Please see CyberPanel main log file."}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
data_ret = {'status': 1, 'changePasswordStatus': 1, 'error_message': "None"}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0, 'changePasswordStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def remoteAccess(self, userID = None, data = None):
try:
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'listDatabases') == 0:
return ACLManager.loadErrorJson('changePasswordStatus', 0)
userName = data['dbUserName']
db = Databases.objects.filter(dbUser=userName)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(db[0].website.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
try:
meta = DBMeta.objects.get(database=db[0], key=DatabaseManager.REMOTE_ACCESS)
data_ret = {'status': 1, 'dbHost': json.loads(meta.value)['remoteIP']}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 1, 'dbHost': 'localhost'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0,'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def allowRemoteIP(self, userID = None, data = None):
try:
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'listDatabases') == 0:
return ACLManager.loadErrorJson('changePasswordStatus', 0)
userName = data['dbUserName']
remoteIP = data['remoteIP']
db = Databases.objects.filter(dbUser=userName)
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(db[0].website.domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
mysqlUtilities.allowRemoteAccess(db[0].dbName, userName, remoteIP)
mysqlUtilities.createDatabase(db[0].dbName, userName, 'cyberpanel', 0, remoteIP)
dbUserInMysql = DBUsers.objects.get(user=userName, host='localhost')
mysqlUtilities.changePassword(userName, dbUserInMysql.password, 1, remoteIP)
metaData = {'remoteIP': remoteIP}
try:
meta = DBMeta.objects.get(database=db[0], key=DatabaseManager.REMOTE_ACCESS)
meta.value = json.dumps(metaData)
meta.save()
except:
DBMeta(database=db[0], value = json.dumps(metaData), key=DatabaseManager.REMOTE_ACCESS).save()
data_ret = {'status': 1}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
data_ret = {'status': 0,'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@staticmethod
def generatePHPMYAdminData(userID):
try:
admin = Administrator.objects.get(id=userID)
path = '/etc/cyberpanel/' + admin.userName
currentACL = ACLManager.loadedACL(userID)
websiteOBJs = ACLManager.findWebsiteObjects(currentACL, userID)
finalUserPassword = randomPassword.generate_pass()
writeToFile = open(path, 'w')
writeToFile.write(finalUserPassword)
writeToFile.close()
mysqlUtilities.createDBUser(admin.userName, finalUserPassword)
mysqlUtilities.changePassword(admin.userName, finalUserPassword)
for webs in websiteOBJs:
for db in webs.databases_set.all():
mysqlUtilities.allowGlobalUserAccess(admin.userName, db.dbName)
print("1," + finalUserPassword)
except BaseException as msg:
print("0," + str(msg))
def main():
parser = argparse.ArgumentParser(description='CyberPanel Installer')
parser.add_argument('function', help='Specific a function to call!')
parser.add_argument('--userID', help='Logged in user ID')
args = parser.parse_args()
if args.function == "generatePHPMYAdminData":
DatabaseManager.generatePHPMYAdminData(int(args.userID))
if __name__ == "__main__":
main()

View File

25
databases/models.py Normal file
View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from django.db import models
from websiteFunctions.models import Websites
# Create your models here.
class Databases(models.Model):
website = models.ForeignKey(Websites, on_delete=models.CASCADE)
dbName = models.CharField(max_length=50,unique=True)
dbUser = models.CharField(max_length=50)
class DatabasesUsers(models.Model):
owner = models.ForeignKey(Databases, on_delete=models.CASCADE)
username = models.CharField(max_length=50,unique=True)
class DBMeta(models.Model):
database = models.ForeignKey(Databases, on_delete=models.CASCADE)
key = models.CharField(max_length=200)
value = models.TextField()
class GlobalUserDB(models.Model):
username = models.CharField(max_length=200)
password = models.CharField(max_length=500)
token = models.CharField(max_length=20)

121
databases/mysqlOptimizer.py Normal file
View File

@@ -0,0 +1,121 @@
import math
import random
from textwrap import dedent
class MySQLOptimizer:
defaults = {
'mysql_dir': "/var/lib/mysql",
'log_error': "/var/lib/mysql/mysqld.log",
'slow_query_log_file': "/var/lib/mysql/mysqld-slow.log",
'pid_file': "/var/lib/mysql/mysqld.pid",
'mysql_ram_gb': 1,
'query_cache_type': 0,
'query_cache_size': 0,
'long_query_time': 2,
'max_connections': 500,
'server_id': random.randint(100000, 999999)
}
@staticmethod
def mycnf_innodb_log_file_size_MB(innodb_buffer_pool_size_GB):
if innodb_buffer_pool_size_GB > 64:
return '768M'
if innodb_buffer_pool_size_GB > 24:
return '512M'
if innodb_buffer_pool_size_GB > 8:
return '256M'
if innodb_buffer_pool_size_GB > 2:
return '128M'
return '64M'
@staticmethod
def output_memory_gb(gb):
if math.fabs(math.ceil(gb) - gb) < 0.01:
return str(int(gb)) + 'G'
return str(int(gb * 1024)) + 'M'
@staticmethod
def mycnf_make(m):
m['innodb_buffer_pool_size'] = MySQLOptimizer.output_memory_gb(float(m['mysql_ram_gb']) * 0.15)
m['innodb_log_file_size'] = MySQLOptimizer.mycnf_innodb_log_file_size_MB(m['mysql_ram_gb'])
return m
@staticmethod
def output_my_cnf(_metaconf):
return dedent("""
[mysqld]
# GENERAL #
user = mysql
default-storage-engine = InnoDB
#socket = {mysql_dir}/mysql.sock
#pid-file = {pid_file}
# MyISAM #
# key-buffer-size = 32M
# myisam-recover = FORCE,BACKUP
# SAFETY #
max-allowed-packet = 16M
max-connect-errors = 1000000
sql-mode = NO_ENGINE_SUBSTITUTION,NO_AUTO_CREATE_USER
sysdate-is-now = 1
innodb-strict-mode = 1
# DATA STORAGE #
datadir = {mysql_dir}
# SERVER ID #
server-id = {server_id}
# CACHES AND LIMITS #
max-connections = {max_connections}
tmp-table-size = 32M
max-heap-table-size = 32M
query-cache-type = {query_cache_type}
query-cache-size = {query_cache_size}
thread-cache-size = 50
open-files-limit = 65535
table-definition-cache = 1024
table-open-cache = 2048
# INNODB #
innodb-flush-method = O_DIRECT
innodb-log-files-in-group = 2
innodb-log-file-size = {innodb_log_file_size}
innodb-flush-log-at-trx-commit = 1
innodb-file-per-table = 1
innodb-buffer-pool-size = {innodb_buffer_pool_size}
# LOGGING #
#log-error = {log_error}
slow-query-log = 1
#slow-query-log-file = {slow_query_log_file}
log-queries-not-using-indexes = OFF
long_query_time = 30
[mysqldump]
max-allowed-packet = 16M
!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mariadb.conf.d/
""".format(**MySQLOptimizer.mycnf_make(_metaconf)))
@staticmethod
def generateRecommendations(detectedRam):
MySQLOptimizer.defaults['mysql_ram_gb'] = int(float(detectedRam))
return MySQLOptimizer.output_my_cnf(MySQLOptimizer.defaults)

View File

@@ -0,0 +1,36 @@
from .signals import *
from plogical.pluginManagerGlobal import pluginManagerGlobal
class pluginManager:
@staticmethod
def preCreateDatabase(request):
return pluginManagerGlobal.globalPlug(request, preCreateDatabase)
@staticmethod
def postCreateDatabase(request, response):
return pluginManagerGlobal.globalPlug(request, postCreateDatabase, response)
@staticmethod
def preSubmitDBCreation(request):
return pluginManagerGlobal.globalPlug(request, preSubmitDBCreation)
@staticmethod
def postSubmitDBCreation(request, response):
return pluginManagerGlobal.globalPlug(request, postSubmitDBCreation, response)
@staticmethod
def preSubmitDatabaseDeletion(request):
return pluginManagerGlobal.globalPlug(request, preSubmitDatabaseDeletion)
@staticmethod
def postSubmitDatabaseDeletion(request, response):
return pluginManagerGlobal.globalPlug(request, postSubmitDatabaseDeletion, response)
@staticmethod
def preChangePassword(request):
return pluginManagerGlobal.globalPlug(request, preChangePassword)
@staticmethod
def postChangePassword(request, response):
return pluginManagerGlobal.globalPlug(request, postChangePassword, response)

28
databases/signals.py Normal file
View File

@@ -0,0 +1,28 @@
# The world is a prison for the believer.
from django.dispatch import Signal
## This event is fired before CyberPanel core load the create database template, this special event is used
## to create a beautiful names official plugin. Actual FTP account creation happens with event named preSubmitDBCreation and postSubmitDBCreation.
preCreateDatabase = Signal()
## See preCreateDatabase
postCreateDatabase = Signal()
## This event is fired before CyberPanel core start creation of a database.
preSubmitDBCreation = Signal()
## This event is fired after CyberPanel core finished creation of a database.
postSubmitDBCreation = Signal()
## This event is fired before CyberPanel core start deletion of a database
preSubmitDatabaseDeletion = Signal()
## This event is fired after CyberPanel core finished deletion of a database.
postSubmitDatabaseDeletion = Signal()
## This event is fired before CyberPanel core start to change a database password.
preChangePassword = Signal()
## This event is fired after CyberPanel core finished changing database password.
postChangePassword = Signal()

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Auto login for {{ url }}</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<span style="display: none" id="userName">{{ userName }}</span>
<span style="display: none" id="password">{{ password }}</span>
<form style="display: none" name="loginform" id="loginform" action="/phpmyadmin/phpmyadminsignin.php" method="post">
{% csrf_token %}
<p>
<label for="user_login">Username or Email Address</label>
<input type="text" name="username" id="user_login" class="input" value="" size="20" autocapitalize="off"/>
</p>
<div class="user-pass-wrap">
<label for="user_pass">Password</label>
<div class="wp-pwd">
<input type="password" name="password" id="user_pass" class="input password-input" value="" size="20"/>
<button type="button" class="button button-secondary wp-hide-pw hide-if-no-js" data-toggle="0"
aria-label="Show password">
<span class="dashicons dashicons-visibility" aria-hidden="true"></span>
</button>
</div>
</div>
<p class="forgetmenot"><input name="rememberme" type="checkbox" id="rememberme" value="forever"/> <label
for="rememberme">Remember Me</label></p>
<p class="submit">
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large"
value="Log In"/>
<input type="hidden" name="redirect_to" value="{{ url }}/wp-admin"/>
{# <input type="hidden" name="testcookie" value="1"/>#}
</p>
</form>
<script>
document.getElementById("user_login").value = $("#userName").text();
document.getElementById("user_pass").value = $("#password").text();
document.forms["loginform"].submit();
</script>
</body>
</html>

View File

@@ -0,0 +1,528 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Optimize MySQL - CyberPanel" %}{% endblock %}
{% block content %}
{% 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%;
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;
}
.mysql-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-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);
}
.btn-info {
background: #3b82f6;
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-info:hover {
background: #2563eb;
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4);
}
.ram-info-card {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border: 1px solid #fcd34d;
border-radius: 12px;
padding: 1.25rem 2rem;
margin-bottom: 2rem;
display: inline-flex;
align-items: center;
gap: 1rem;
box-shadow: 0 2px 8px rgba(252, 211, 77, 0.3);
animation: fadeInUp 0.5s ease-out;
}
.ram-icon {
width: 48px;
height: 48px;
background: white;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.ram-label {
font-size: 0.875rem;
color: #92400e;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0;
}
.ram-value {
font-size: 1.75rem;
font-weight: 700;
color: #78350f;
margin: 0;
line-height: 1;
}
.action-buttons {
display: flex;
gap: 1rem;
flex-wrap: wrap;
margin-bottom: 2rem;
justify-content: center;
}
.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;
}
.config-editor {
background: #1e293b;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
animation: fadeInUp 0.5s ease-out;
}
.editor-header {
background: #334155;
padding: 1rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.editor-title {
color: #e2e8f0;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.editor-status {
display: flex;
gap: 0.5rem;
}
.editor-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.editor-dot.red {
background: #ef4444;
}
.editor-dot.yellow {
background: #f59e0b;
}
.editor-dot.green {
background: #10b981;
}
.editor-textarea {
width: 100%;
min-height: 500px;
background: #1e293b;
color: #e2e8f0;
border: none;
padding: 1.5rem;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.875rem;
line-height: 1.6;
resize: vertical;
}
.editor-textarea:focus {
outline: none;
background: #1a202c;
}
.editor-textarea.readonly {
background: #1a202c;
color: #94a3b8;
}
.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;
}
.recommendation-info {
background: #f0f4f8;
border: 1px solid #cbd5e1;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
display: flex;
align-items: flex-start;
gap: 1rem;
animation: fadeInUp 0.5s ease-out;
}
.recommendation-info i {
color: #3b82f6;
font-size: 1.25rem;
margin-top: 0.125rem;
}
.recommendation-info p {
margin: 0;
color: #475569;
font-size: 0.875rem;
line-height: 1.6;
}
@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 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;
}
.action-buttons {
flex-direction: column;
}
.action-buttons button {
width: 100%;
}
.ram-info-card {
flex-direction: column;
text-align: center;
}
.editor-textarea {
min-height: 300px;
}
}
</style>
<div class="modern-container" ng-controller="OptimizeMysql">
<div class="page-header">
<h1 class="page-title">
<div class="mysql-icon">
<i class="fas fa-database" style="color: #5b5fcf; font-size: 1.75rem;"></i>
</div>
{% trans "Optimize MySQL" %}
</h1>
<p class="page-subtitle">{% trans "Generate optimized MySQL configurations based on your server resources" %}</p>
<div class="header-actions">
<div class="ram-info-card">
<div class="ram-icon">
<i class="fas fa-memory" style="color: #f59e0b; font-size: 1.5rem;"></i>
</div>
<div>
<p class="ram-label">{% trans "Detected RAM" %}</p>
<p class="ram-value" id="detectedRam">{{ ramInGB }}</p>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<button ng-click="generateRecommendations()" type="button" class="btn-success">
<i class="fas fa-magic"></i>
{% trans "Generate Recommendations" %}
</button>
<button ng-click="applyMySQLChanges()" type="button" class="btn-danger">
<i class="fas fa-save"></i>
{% trans "Apply Changes" %}
</button>
<button ng-click="restartMySQL()" type="button" class="btn-info">
<i class="fas fa-sync-alt"></i>
{% trans "Restart MySQL" %}
</button>
</div>
<!-- Recommendation Info -->
<div class="recommendation-info">
<i class="fas fa-info-circle"></i>
<p>{% trans "Generate recommendations based on your server's RAM to optimize MySQL performance. Review the suggested configuration carefully before applying changes. Always backup your current configuration before making modifications." %}</p>
</div>
<!-- Configuration Editor -->
<div class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-file-code"></i>
{% trans "Configuration Editor" %}
<span ng-hide="cyberPanelLoading" class="loading-spinner"></span>
</h2>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="config-editor">
<div class="editor-header">
<h3 class="editor-title">
<i class="fas fa-file-alt"></i>
{% trans "Current my.cnf" %}
</h3>
<div class="editor-status">
<div class="editor-dot red"></div>
<div class="editor-dot yellow"></div>
<div class="editor-dot green"></div>
</div>
</div>
<textarea class="editor-textarea readonly" readonly>{{ conf }}</textarea>
</div>
</div>
<div class="col-md-6">
<div class="config-editor">
<div class="editor-header">
<h3 class="editor-title">
<i class="fas fa-lightbulb"></i>
{% trans "Suggested Configuration" %}
</h3>
<div class="editor-status">
<div class="editor-dot red"></div>
<div class="editor-dot yellow"></div>
<div class="editor-dot green"></div>
</div>
</div>
<textarea class="editor-textarea"
ng-model="suggestedContent"
placeholder="{% trans 'Click Generate Recommendations to generate optimized configuration...' %}"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,99 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "List Databases - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Upgrade Mysql" %}
</h2>
<p>{% trans "On this page you can update mysql." %}</p>
</div>
<div ng-controller="mysqlupdate" class="panel">
<div class="panel-body">
<h3 class="content-box-header">
{% trans "Update Mysql" %} <img ng-hide="dbLoading"
src="{% static 'images/loading.gif' %}">
</h3>
<div class="example-box-wrapper">
<form name="websiteCreationForm" action="/"
class="form-horizontal bordered-row panel-body">
<div ng-hide="installform" class="form-group">
<label class="col-sm-3 control-label">{% trans "Mysql version" %} </label>
<div class="col-sm-6">
<select ng-model="version" class="form-control" required>
{% for version in mysqlversions %}
<option>{{ version }}</option>
{% endfor %}
</select>
</div>
</div>
<div ng-hide="installform" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="Upgardemysql()"
class="btn btn-primary btn-lg">{% trans "Update Mysql" %}</button>
</div>
</div>
<div ng-hide="modSecNotifyBox" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-6">
<div ng-hide="failedToStartInallation" class="alert alert-danger">
<p>{% trans "Failed to start installation, Error message: " %} {$ errorMessage
$}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect. Please refresh this page." %} </p>
</div>
<div ng-hide="installationFailed" class="alert alert-danger">
<p>{% trans "Installation failed." %} {$ errorMessage $}</p>
</div>
<div ng-hide="modSecSuccessfullyInstalled" class="alert alert-success">
<p>{% trans "Upgrading Done successfully." %}</p>
</div>
</div>
</div>
<div ng-hide="modeSecInstallBox" class="col-md-12">
<form action="/" id="" class="form-horizontal bordered-row">
<div class="form-group">
<div class="col-sm-12 text-center">
<h3><img
src="{% static 'firewall/icons/firewall.png' %}"> {% trans "In winter we must protect each other.." %}
<img ng-hide="modsecLoading" src="/static/images/loading.gif"></h3>
</div>
<div style="margin-top: 2%;" class="col-sm-12">
<textarea ng-model="requestData" rows="15"
class="form-control">{{ requestData }}</textarea>
</div>
</div>
</form>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,553 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Create New Database - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
/* Modern page styles matching new design */
.page-wrapper {
background: transparent;
padding: 20px;
}
.page-container {
max-width: 900px;
margin: 0 auto;
}
.page-header {
margin-bottom: 30px;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: #2f3640;
margin-bottom: 8px;
}
.page-subtitle {
font-size: 14px;
color: #8893a7;
}
/* Card styles */
.content-card {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
margin-bottom: 25px;
}
.card-title {
font-size: 18px;
font-weight: 700;
color: #2f3640;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 10px;
}
.card-title::before {
content: '';
width: 4px;
height: 24px;
background: #5b5fcf;
border-radius: 2px;
}
/* Form styles */
.form-group {
margin-bottom: 24px;
}
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: #64748b;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-control {
width: 100%;
padding: 10px 14px;
border: 1px solid #e8e9ff;
border-radius: 8px;
font-size: 14px;
color: #2f3640;
background: white;
transition: all 0.2s ease;
}
.form-control:hover {
border-color: #5b5fcf;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91,95,207,0.1);
}
/* Windows selectbox fixes */
select.form-control {
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232f3640' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px;
padding-right: 40px;
line-height: 1.5;
min-height: 44px;
}
/* Windows-specific fixes */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
select.form-control {
color: #2f3640 !important;
background-color: white !important;
}
}
/* Fix for Windows Edge and Chrome */
select.form-control::-ms-expand {
display: none;
}
select.form-control:focus {
color: #2f3640;
background-color: white;
}
/* Website selector card */
.website-selector-card {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
}
/* Database details section */
.dbDetails {
display: none;
}
/* Name preview */
.name-preview {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 6px;
padding: 8px 12px;
font-size: 13px;
color: #5b5fcf;
font-weight: 600;
margin-top: 8px;
display: inline-block;
font-family: 'Courier New', monospace;
}
/* Password input group */
.password-group {
display: flex;
gap: 10px;
align-items: start;
}
.password-input-wrapper {
flex: 1;
}
.password-input {
display: flex;
gap: 0;
align-items: center;
}
.password-input .form-control {
border-radius: 8px 0 0 8px;
}
.password-toggle {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-left: none;
padding: 10px 14px;
border-radius: 0 8px 8px 0;
cursor: pointer;
transition: all 0.2s ease;
}
.password-toggle:hover {
background: #eef0ff;
border-color: #5b5fcf;
}
.password-toggle i {
color: #64748b;
font-size: 16px;
}
/* Generated password section */
.generated-password-box {
background: #e8f5e9;
border: 1px solid #c8e6c9;
border-radius: 8px;
padding: 16px;
margin-top: 16px;
display: none;
}
.generated-password-label {
font-size: 12px;
font-weight: 600;
color: #2e7d32;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.generated-password-value {
font-family: 'Courier New', monospace;
font-size: 14px;
color: #1b5e20;
font-weight: 600;
word-break: break-all;
margin-bottom: 12px;
}
/* Button styles */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-primary {
background: #5b5fcf;
color: white;
}
.btn-primary:hover {
background: #4a4fc4;
box-shadow: 0 4px 12px rgba(91,95,207,0.3);
transform: translateY(-1px);
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-secondary {
background: #f8f9ff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
}
.btn-secondary:hover {
background: #eef0ff;
border-color: #5b5fcf;
}
.btn-success {
background: #66bb6a;
color: white;
}
.btn-success:hover {
background: #4caf50;
box-shadow: 0 4px 12px rgba(102,187,106,0.3);
transform: translateY(-1px);
}
.btn-lg {
padding: 12px 24px;
font-size: 16px;
}
/* Alert styles */
.alert {
padding: 16px 20px;
border-radius: 10px;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 12px;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.alert-danger {
background: #ffebee;
border: 1px solid #ffcdd2;
color: #c62828;
}
.alert-success {
background: #e8f5e9;
border: 1px solid #c8e6c9;
color: #2e7d32;
}
/* Loading spinner */
.loading-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Button container */
.button-container {
margin-top: 30px;
padding-top: 30px;
border-top: 1px solid #e8e9ff;
}
/* Info box */
.info-box {
background: #e3f2fd;
border: 1px solid #bbdefb;
border-radius: 8px;
padding: 16px;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 12px;
}
.info-box i {
color: #1976d2;
font-size: 18px;
}
.info-box-text {
font-size: 13px;
color: #1565c0;
line-height: 1.5;
}
/* Help text */
.help-text {
font-size: 12px;
color: #8893a7;
margin-top: 6px;
}
/* Hide by default */
.generatedPasswordDetails {
display: none;
}
@media (max-width: 768px) {
.content-card {
padding: 20px;
}
.password-group {
flex-direction: column;
}
}
</style>
{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<div class="page-wrapper" ng-controller="createDatabase">
<div class="page-container">
<div class="page-header">
<h1 class="page-title">{% trans "Create Database" %}</h1>
<p class="page-subtitle">{% trans "Create a new MySQL database for your websites" %}</p>
</div>
<div class="info-box">
<i class="fas fa-info-circle"></i>
<div class="info-box-text">
{% trans "Each database will be prefixed with your domain name for security and organization." %}
</div>
</div>
<div class="content-card">
<h2 class="card-title">
{% trans "Database Configuration" %}
<span ng-hide="createDatabaseLoading" class="loading-spinner"></span>
</h2>
<form name="createDatabaseForm">
<div class="website-selector-card">
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select id="create-database-select" ng-model="databaseWebsite" ng-change="websiteChanged()" class="form-control">
<option value="">-- {% trans "Select a website" %} --</option>
{% for items in websitesList %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="dbDetails">
<div class="form-group">
<label class="form-label">{% trans "Database Name" %}</label>
<input name="dbName" type="text" class="form-control" ng-model="dbName"
placeholder="{% trans 'Enter database name' %}" required>
<div class="name-preview" ng-show="dbName">
<span id="domainDatabase"></span>_{$ dbName $}
</div>
<div class="help-text">{% trans "Database name will be prefixed with your domain" %}</div>
</div>
<div class="form-group">
<label class="form-label">{% trans "Database User" %}</label>
<input type="text" name="dbUsername" class="form-control" ng-model="dbUsername"
placeholder="{% trans 'Enter username' %}" required>
<div class="name-preview" ng-show="dbUsername">
<span id="domainUsername"></span>_{$ dbUsername $}
</div>
<div class="help-text">{% trans "Username will be prefixed with your domain" %}</div>
</div>
<div class="form-group">
<label class="form-label">{% trans "Password" %}</label>
<div class="password-group">
<div class="password-input-wrapper">
<div class="password-input">
<input type="password" id="dbPassword" name="dbPassword" class="form-control"
ng-model="dbPassword" placeholder="{% trans 'Enter password' %}" required>
<div class="password-toggle" onclick="togglePasswordVisibility()">
<i class="fas fa-eye" id="passwordToggleIcon"></i>
</div>
</div>
</div>
<button type="button" ng-click="generatePassword()" class="btn btn-secondary">
<i class="fas fa-sync"></i>
{% trans "Generate" %}
</button>
</div>
<div class="help-text">{% trans "Use a strong password for security" %}</div>
</div>
<div class="generated-password-box generatedPasswordDetails">
<div class="generated-password-label">{% trans "Generated Password" %}</div>
<div class="generated-password-value">{$ generatedPassword $}</div>
<button type="button" ng-click="usePassword()" class="btn btn-success btn-sm">
<i class="fas fa-check"></i>
{% trans "Use This Password" %}
</button>
</div>
<div class="button-container">
<button type="button" ng-click="createDatabase()" class="btn btn-primary btn-lg"
ng-disabled="!databaseWebsite || !dbName || !dbUsername || !dbPassword">
<i class="fas fa-database"></i>
{% trans "Create Database" %}
</button>
</div>
</div>
</form>
</div>
<!-- Success/Error messages will be handled by the controller -->
</div>
</div>
{% endblock %}
{% block footer_scripts %}
<script>
// Password visibility toggle
function togglePasswordVisibility() {
var passwordInput = document.getElementById('dbPassword');
var toggleIcon = document.getElementById('passwordToggleIcon');
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
toggleIcon.className = 'fas fa-eye-slash';
} else {
passwordInput.type = 'password';
toggleIcon.className = 'fas fa-eye';
}
}
// Enhance the controller
$(document).ready(function() {
var scope = angular.element($('[ng-controller="createDatabase"]')).scope();
if (scope) {
// Add website change handler
scope.websiteChanged = function() {
if (scope.databaseWebsite) {
$('.dbDetails').fadeIn();
// Update the domain prefixes
var domainParts = scope.databaseWebsite.split('.');
var prefix = domainParts[0];
$('#domainDatabase').text(prefix);
$('#domainUsername').text(prefix);
} else {
$('.dbDetails').fadeOut();
}
};
// Store generated password
scope.generatePasswordOriginal = scope.generatePassword;
scope.generatePassword = function() {
if (scope.generatePasswordOriginal) {
scope.generatePasswordOriginal();
}
// Show generated password box
$('.generatedPasswordDetails').fadeIn();
// Generate a random password (example)
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
var password = '';
for (var i = 0; i < 16; i++) {
password += chars.charAt(Math.floor(Math.random() * chars.length));
}
scope.generatedPassword = password;
scope.$apply();
};
// Use generated password
scope.usePassword = function() {
scope.dbPassword = scope.generatedPassword;
$('.generatedPasswordDetails').fadeOut();
// Update password field to show it's filled
$('#dbPassword').val(scope.generatedPassword);
};
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,584 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Delete Database - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
/* Modern page styles matching new design */
.page-wrapper {
background: transparent;
padding: 20px;
}
.page-container {
max-width: 900px;
margin: 0 auto;
}
.page-header {
margin-bottom: 30px;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: #2f3640;
margin-bottom: 8px;
}
.page-subtitle {
font-size: 14px;
color: #8893a7;
}
/* Card styles */
.content-card {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
margin-bottom: 25px;
}
.card-title {
font-size: 18px;
font-weight: 700;
color: #2f3640;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 10px;
}
.card-title::before {
content: '';
width: 4px;
height: 24px;
background: #dc3545;
border-radius: 2px;
}
/* Form styles */
.form-group {
margin-bottom: 24px;
}
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: #64748b;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-control {
width: 100%;
padding: 10px 14px;
border: 1px solid #e8e9ff;
border-radius: 8px;
font-size: 14px;
color: #2f3640;
background: white;
transition: all 0.2s ease;
}
.form-control:hover {
border-color: #dc3545;
}
.form-control:focus {
outline: none;
border-color: #dc3545;
box-shadow: 0 0 0 3px rgba(220,53,69,0.1);
}
/* Windows selectbox fixes */
select.form-control {
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232f3640' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px;
padding-right: 40px;
line-height: 1.5;
min-height: 44px;
}
/* Windows-specific fixes */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
select.form-control {
color: #2f3640 !important;
background-color: white !important;
}
}
/* Fix for Windows Edge and Chrome */
select.form-control::-ms-expand {
display: none;
}
select.form-control:focus {
color: #2f3640;
background-color: white;
}
/* Website selector card */
.website-selector-card {
background: #fff5f5;
border: 1px solid #ffdddd;
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
}
/* Database selector section */
.database-selector-section {
display: none;
}
/* Database info card */
.database-info-card {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
display: none;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e8e9ff;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-size: 13px;
font-weight: 600;
color: #64748b;
}
.info-value {
font-size: 14px;
color: #2f3640;
font-weight: 600;
font-family: 'Courier New', monospace;
}
/* Warning box */
.warning-box {
background: #fff3e0;
border: 1px solid #ffe0b2;
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
display: flex;
align-items: start;
gap: 15px;
}
.warning-icon {
color: #ff9800;
font-size: 24px;
flex-shrink: 0;
}
.warning-content {
flex: 1;
}
.warning-title {
font-size: 16px;
font-weight: 700;
color: #2f3640;
margin-bottom: 8px;
}
.warning-text {
font-size: 14px;
color: #64748b;
line-height: 1.6;
}
.warning-list {
margin-top: 12px;
padding-left: 20px;
}
.warning-list li {
font-size: 13px;
color: #64748b;
margin-bottom: 6px;
}
/* Button styles */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
box-shadow: 0 4px 12px rgba(220,53,69,0.3);
transform: translateY(-1px);
}
.btn-danger:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-secondary {
background: #f8f9ff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
}
.btn-secondary:hover {
background: #eef0ff;
border-color: #5b5fcf;
}
/* Confirmation section */
.confirmation-section {
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 10px;
padding: 25px;
margin-top: 25px;
text-align: center;
display: none;
}
.confirmation-icon {
font-size: 48px;
color: #dc3545;
margin-bottom: 15px;
}
.confirmation-title {
font-size: 20px;
font-weight: 700;
color: #2f3640;
margin-bottom: 10px;
}
.confirmation-text {
font-size: 14px;
color: #64748b;
margin-bottom: 20px;
}
.confirmation-database {
font-size: 18px;
font-weight: 700;
color: #dc3545;
margin-bottom: 25px;
font-family: 'Courier New', monospace;
}
.confirmation-buttons {
display: flex;
gap: 12px;
justify-content: center;
}
/* Alert styles */
.alert {
padding: 16px 20px;
border-radius: 10px;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 12px;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.alert-danger {
background: #ffebee;
border: 1px solid #ffcdd2;
color: #c62828;
}
.alert-success {
background: #e8f5e9;
border: 1px solid #c8e6c9;
color: #2e7d32;
}
/* Loading spinner */
.loading-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #dc3545;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Button container */
.button-container {
margin-top: 30px;
padding-top: 30px;
border-top: 1px solid #e8e9ff;
}
/* Empty state */
.empty-state {
text-align: center;
padding: 40px;
color: #8893a7;
}
.empty-state i {
font-size: 48px;
color: #e8e9ff;
margin-bottom: 16px;
}
.empty-state-text {
font-size: 14px;
color: #8893a7;
}
/* Hide elements by default */
.hidden {
display: none !important;
}
@media (max-width: 768px) {
.content-card {
padding: 20px;
}
.confirmation-buttons {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
}
</style>
{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<div class="page-wrapper" ng-controller="deleteDatabase">
<div class="page-container">
<div class="page-header">
<h1 class="page-title">{% trans "Delete Database" %}</h1>
<p class="page-subtitle">{% trans "Permanently remove a database from your server" %}</p>
</div>
<div class="content-card">
<h2 class="card-title">
{% trans "Database Selection" %}
<span ng-hide="deleteDatabaseLoading" class="loading-spinner"></span>
</h2>
<div class="website-selector-card">
<div class="form-group">
<label class="form-label">{% trans "Select Website" %}</label>
<select ng-change="fetchDatabases()" ng-model="databaseWebsite" class="form-control">
<option value="">-- {% trans "Select a website" %} --</option>
{% for items in websitesList %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="database-selector-section" ng-hide="fetchedDatabases">
<div class="form-group">
<label class="form-label">{% trans "Select Database to Delete" %}</label>
<select ng-model="selectedDB" ng-change="databaseSelected()" class="form-control">
<option value="">-- {% trans "Select a database" %} --</option>
<option ng-repeat="db in dbnames track by $index">{$ db.dbName $}</option>
</select>
</div>
<div ng-show="dbnames.length === 0" class="empty-state">
<i class="fas fa-database"></i>
<div class="empty-state-text">{% trans "No databases found for this website" %}</div>
</div>
</div>
<div class="database-info-card" ng-show="selectedDB && databaseInfo">
<div class="info-row">
<span class="info-label">{% trans "Database Name" %}</span>
<span class="info-value">{$ selectedDB $}</span>
</div>
<div class="info-row" ng-show="databaseInfo.size">
<span class="info-label">{% trans "Database Size" %}</span>
<span class="info-value">{$ databaseInfo.size $}</span>
</div>
<div class="info-row" ng-show="databaseInfo.tables">
<span class="info-label">{% trans "Tables" %}</span>
<span class="info-value">{$ databaseInfo.tables $}</span>
</div>
</div>
<div class="warning-box">
<i class="fas fa-exclamation-triangle warning-icon"></i>
<div class="warning-content">
<div class="warning-title">{% trans "Warning: This action cannot be undone!" %}</div>
<div class="warning-text">
{% trans "Deleting a database will permanently remove:" %}
</div>
<ul class="warning-list">
<li>{% trans "All tables and their data" %}</li>
<li>{% trans "All stored procedures and functions" %}</li>
<li>{% trans "All user permissions for this database" %}</li>
<li>{% trans "All triggers and events" %}</li>
</ul>
</div>
</div>
<div class="button-container" ng-hide="fetchedDatabases">
<button type="button" ng-click="confirmDelete()" class="btn btn-danger"
ng-disabled="!selectedDB">
<i class="fas fa-trash"></i>
{% trans "Delete Database" %}
</button>
</div>
</div>
<!-- Confirmation Section -->
<div class="content-card confirmation-section" id="deleteConfirmation">
<i class="fas fa-exclamation-circle confirmation-icon"></i>
<h3 class="confirmation-title">{% trans "Are you absolutely sure?" %}</h3>
<p class="confirmation-text">
{% trans "This action will permanently delete the database and all its data." %}
</p>
<div class="confirmation-database">{$ selectedDB $}</div>
<div class="confirmation-buttons">
<button type="button" ng-click="cancelDelete()" class="btn btn-secondary">
<i class="fas fa-times"></i>
{% trans "Cancel" %}
</button>
<button type="button" ng-click="deleteDatabase()" class="btn btn-danger">
<i class="fas fa-trash"></i>
{% trans "Yes, Delete Database" %}
</button>
</div>
</div>
<!-- Alert Messages -->
<div ng-hide="databaseDeletionFailed" class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>
<span>{% trans "Error message:" %} {$ errorMessage $}</span>
</div>
<div ng-hide="databaseDeleted" class="alert alert-success">
<i class="fas fa-check-circle"></i>
<span>{% trans "Database deleted successfully." %}</span>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<i class="fas fa-network-wired"></i>
<span>{% trans "Could not connect to server. Please refresh this page." %}</span>
</div>
</div>
</div>
{% endblock %}
{% block footer_scripts %}
<script>
// Enhance the controller
$(document).ready(function() {
var scope = angular.element($('[ng-controller="deleteDatabase"]')).scope();
if (scope) {
// Add confirmation dialog
scope.confirmDelete = function() {
if (scope.selectedDB) {
$('#deleteConfirmation').fadeIn();
}
};
scope.cancelDelete = function() {
$('#deleteConfirmation').fadeOut();
};
// Database selected handler
scope.databaseSelected = function() {
if (scope.selectedDB) {
// Mock database info - in real implementation, fetch from server
scope.databaseInfo = {
size: '25.3 MB',
tables: '12'
};
$('.database-info-card').fadeIn();
} else {
$('.database-info-card').fadeOut();
}
};
// Override fetchDatabases to show selector
var originalFetchDatabases = scope.fetchDatabases;
scope.fetchDatabases = function() {
if (originalFetchDatabases) {
originalFetchDatabases();
}
if (scope.databaseWebsite) {
$('.database-selector-section').fadeIn();
} else {
$('.database-selector-section').fadeOut();
}
};
// Initialize hidden elements
scope.deleteDatabaseLoading = true;
scope.fetchedDatabases = true;
scope.databaseDeletionFailed = true;
scope.databaseDeleted = true;
scope.couldNotConnect = true;
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,80 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Database Functions - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="container">
<div id="page-title">
<h2>{% trans "Database Functions" %}</h2>
<p>{% trans "Create, edit and delete databases on this page." %}</p>
</div>
<div class="panel col-md-11">
<div class="panel-body">
<h3 class="content-box-header">
{% trans "Available Functions" %}
</h3>
<div class="example-box-wrapper">
<div class="row">
<div class="col-md-3 btn-min-width">
<a href="{% url 'createDatabase' %}" title="{% trans 'Create Database' %}" class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "Create Database" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-plus-square"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="{% url 'deleteDatabase' %}" title="{% trans 'Delete Database' %}" class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "Delete Database" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-edit"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a href="{% url 'listDBs' %}" title="{% trans 'List Databases' %}" class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "List Databases" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-list-ul"></i>
</div>
</a>
</div>
<div class="col-md-3 btn-min-width">
<a target="_blank" href="{% url 'phpMyAdmin' %}" title="{% trans 'PHPMYAdmin' %}" class="tile-box tile-box-shortcut btn-primary">
<div class="tile-header">
{% trans "PHPMYAdmin" %}
</div>
<div class="tile-content-wrapper">
<i class="fa fa-code"></i>
</div>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,664 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "List Databases - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
/* Modern page styles matching new design */
.page-wrapper {
background: transparent;
padding: 20px;
}
.page-container {
max-width: 1400px;
margin: 0 auto;
}
.page-header {
margin-bottom: 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.page-header-content {
flex: 1;
}
.page-title {
font-size: 28px;
font-weight: 700;
color: #2f3640;
margin-bottom: 8px;
}
.page-subtitle {
font-size: 14px;
color: #8893a7;
}
/* Card styles */
.content-card {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
margin-bottom: 25px;
}
.card-title {
font-size: 18px;
font-weight: 700;
color: #2f3640;
margin-bottom: 25px;
display: flex;
align-items: center;
gap: 10px;
}
.card-title::before {
content: '';
width: 4px;
height: 24px;
background: #5b5fcf;
border-radius: 2px;
}
/* Form styles */
.form-group {
margin-bottom: 24px;
}
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: #64748b;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.form-control {
width: 100%;
padding: 10px 14px;
border: 1px solid #e8e9ff;
border-radius: 8px;
font-size: 14px;
color: #2f3640;
background: white;
transition: all 0.2s ease;
}
.form-control:hover {
border-color: #5b5fcf;
}
.form-control:focus {
outline: none;
border-color: #5b5fcf;
box-shadow: 0 0 0 3px rgba(91,95,207,0.1);
}
/* Windows selectbox fixes */
select.form-control {
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232f3640' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px;
padding-right: 40px;
line-height: 1.5;
min-height: 44px;
}
/* Windows-specific fixes */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
select.form-control {
color: #2f3640 !important;
background-color: white !important;
}
}
/* Fix for Windows Edge and Chrome */
select.form-control::-ms-expand {
display: none;
}
select.form-control:focus {
color: #2f3640;
background-color: white;
}
/* Website selector card */
.website-selector-card {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 10px;
padding: 20px;
margin-bottom: 25px;
}
/* Table styles */
.modern-table {
width: 100%;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid #e8e9ff;
}
.modern-table thead {
background: #f8f9ff;
}
.modern-table th {
padding: 16px 20px;
font-size: 13px;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid #e8e9ff;
text-align: left;
}
.modern-table td {
padding: 16px 20px;
font-size: 14px;
color: #2f3640;
border-bottom: 1px solid #f0f0f0;
}
.modern-table tbody tr:last-child td {
border-bottom: none;
}
.modern-table tbody tr:hover {
background: #fafbff;
}
/* Database name styles */
.db-name {
font-family: 'Courier New', monospace;
font-weight: 600;
color: #5b5fcf;
}
/* Button styles */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-primary {
background: #5b5fcf;
color: white;
}
.btn-primary:hover {
background: #4a4fc4;
box-shadow: 0 4px 12px rgba(91,95,207,0.3);
transform: translateY(-1px);
}
.btn-secondary {
background: #f8f9ff;
color: #5b5fcf;
border: 1px solid #e8e9ff;
}
.btn-secondary:hover {
background: #eef0ff;
border-color: #5b5fcf;
}
.btn-sm {
padding: 6px 14px;
font-size: 13px;
}
.btn-lg {
padding: 12px 24px;
font-size: 16px;
}
/* Action buttons */
.action-buttons {
display: flex;
gap: 8px;
}
.btn-action {
padding: 6px 14px;
font-size: 13px;
}
/* Password change section */
.password-change-section {
background: #f8f9ff;
border: 1px solid #e8e9ff;
border-radius: 10px;
padding: 25px;
margin-bottom: 25px;
/* Removed display: none - let Angular control visibility */
}
.password-change-title {
font-size: 16px;
font-weight: 700;
color: #2f3640;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.password-change-title i {
color: #5b5fcf;
}
/* Password input group */
.password-group {
display: flex;
gap: 10px;
align-items: start;
margin-bottom: 20px;
}
.password-input-wrapper {
flex: 1;
}
/* Generated password box */
.generated-password-box {
background: #e8f5e9;
border: 1px solid #c8e6c9;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
/* Removed display: none - let Angular control visibility */
}
.generated-password-label {
font-size: 12px;
font-weight: 600;
color: #2e7d32;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.generated-password-value {
font-family: 'Courier New', monospace;
font-size: 14px;
color: #1b5e20;
font-weight: 600;
word-break: break-all;
}
/* Modal styles */
.modal-content {
border-radius: 12px;
border: none;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
}
.modal-header {
background: #f8f9ff;
border-bottom: 1px solid #e8e9ff;
border-radius: 12px 12px 0 0;
padding: 20px 25px;
}
.modal-title {
font-size: 18px;
font-weight: 700;
color: #2f3640;
display: flex;
align-items: center;
gap: 10px;
}
.modal-body {
padding: 25px;
}
.modal-footer {
background: #f8f9ff;
border-top: 1px solid #e8e9ff;
border-radius: 0 0 12px 12px;
padding: 15px 25px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* IP Access table in modal */
.ip-access-table {
margin-top: 20px;
}
/* Alert styles */
.alert {
padding: 16px 20px;
border-radius: 10px;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 12px;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.alert-danger {
background: #ffebee;
border: 1px solid #ffcdd2;
color: #c62828;
}
.alert-success {
background: #e8f5e9;
border: 1px solid #c8e6c9;
color: #2e7d32;
}
/* Loading spinner */
.loading-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #5b5fcf;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Hide elements by default */
#notificationsBox {
display: none;
}
/* Removed these rules - they were hiding elements incorrectly
#changePasswordBox {
display: none;
}
#dbAccounts {
display: none;
}
*/
#generatedPasswordView {
display: none;
}
@media (max-width: 768px) {
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.modern-table {
display: block;
overflow-x: auto;
white-space: nowrap;
}
.action-buttons {
flex-direction: column;
}
.password-group {
flex-direction: column;
}
}
</style>
{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<div class="page-wrapper" ng-controller="listDBs">
<div class="page-container">
<div class="page-header">
<div class="page-header-content">
<h1 class="page-title">{% trans "List Databases" %}</h1>
<p class="page-subtitle">{% trans "Manage databases and their access credentials" %}</p>
</div>
<a href="{% url 'createDatabase' %}" class="btn btn-primary">
<i class="fas fa-plus"></i>
{% trans "Create Database" %}
</a>
</div>
<div class="content-card">
<h2 class="card-title">
{% trans "Select Website" %}
<span ng-hide="dbLoading" class="loading-spinner"></span>
</h2>
<div class="website-selector-card">
<div class="form-group">
<label class="form-label">{% trans "Select Domain" %}</label>
<select ng-change="fetchDBs()" ng-model="selectedDomain" class="form-control">
<option value="">-- {% trans "Select a domain" %} --</option>
{% for items in AllWebsites %}
<option>{{ items }}</option>
{% endfor %}
</select>
</div>
</div>
<!-- Notifications -->
<div ng-show="!notificationsBox">
<div ng-hide="recordsFetched" class="alert alert-success">
<i class="fas fa-check-circle"></i>
<span>{% trans "Records successfully fetched for" %} <strong>{$ domainFeteched $}</strong></span>
</div>
<div ng-hide="passwordChanged" class="alert alert-success">
<i class="fas fa-check-circle"></i>
<span>{% trans "Password changed for:" %} <strong>{$ dbUsername $}</strong></span>
</div>
<div ng-hide="canNotChangePassword" class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>
<span>{% trans "Cannot change password for" %} <strong>{$ dbUsername $}</strong>, {% trans "Error message:" %} {$ errorMessage $}</span>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<i class="fas fa-network-wired"></i>
<span>{% trans "Could not connect to server. Please refresh this page" %}</span>
</div>
</div>
</div>
<!-- Password Change Section -->
<div class="password-change-section" ng-show="!changePasswordBox">
<div class="password-change-title">
<i class="fas fa-key"></i>
{% trans "Change Password for" %} {$ dbUsername $}
</div>
<div class="password-group">
<div class="password-input-wrapper">
<label class="form-label">{% trans "New Password" %}</label>
<input type="password" class="form-control" ng-model="dbPassword"
placeholder="{% trans 'Enter new password' %}" required>
</div>
<div style="margin-top: 28px;">
<button type="button" ng-click="generatePassword()" class="btn btn-secondary">
<i class="fas fa-sync"></i>
{% trans "Generate" %}
</button>
</div>
</div>
<div class="generated-password-box" ng-show="!generatedPasswordView">
<div class="generated-password-label">{% trans "Generated Password" %}</div>
<div class="generated-password-value">{$ dbPassword $}</div>
<button type="button" ng-click="usePassword()" class="btn btn-success btn-sm" style="margin-top: 10px;">
<i class="fas fa-check"></i>
{% trans "Use This Password" %}
</button>
</div>
<button type="button" ng-click="changePasswordBtn()" class="btn btn-primary btn-lg">
<i class="fas fa-save"></i>
{% trans "Change Password" %}
</button>
</div>
<!-- Database List -->
<div class="content-card" ng-show="!dbAccounts">
<h2 class="card-title">{% trans "Database Accounts" %}</h2>
<div class="table-responsive">
<table class="modern-table">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "Database Name" %}</th>
<th>{% trans "Database User" %}</th>
<th>{% trans "Password" %}</th>
<th>{% trans "Remote Access" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records track by $index">
<td ng-bind="record.id"></td>
<td class="db-name" ng-bind="record.dbName"></td>
<td class="db-name" ng-bind="record.dbUser"></td>
<td>
<button type="button" ng-click="changePassword(record.dbUser)"
class="btn btn-secondary btn-action">
<i class="fas fa-key"></i>
{% trans "Change" %}
</button>
</td>
<td>
<button ng-click="remoteAccess(record.dbUser)"
data-toggle="modal"
data-target="#remoteAccess{$ $index $}"
type="button"
class="btn btn-primary btn-action">
<i class="fas fa-network-wired"></i>
{% trans "Manage" %}
</button>
<!-- Remote Access Modal - Unique ID for each row -->
<div id="remoteAccess{$ $index $}" class="modal fade" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">
{% trans "Remote Access" %}
<span ng-hide="dbLoading" class="loading-spinner"></span>
</h4>
</div>
<div class="modal-body">
<form name="remoteAccessForm" class="form-horizontal">
<div class="form-group">
<label class="form-label">{% trans "Allow Remote Access from IP" %}</label>
<input placeholder="{% trans 'Enter IP Address' %}"
name="remoteIP"
type="text"
class="form-control"
ng-model="$parent.remoteIP"
required>
<div class="help-text" style="margin-top: 6px;">
{% trans "Use % for wildcard (e.g., 192.168.1.% for subnet)" %}
</div>
</div>
<button type="button" ng-click="allowRemoteIP()" class="btn btn-primary">
<i class="fas fa-save"></i>
{% trans "Save Changes" %}
</button>
</form>
<hr style="margin: 25px 0;">
<h5 style="font-weight: 600; margin-bottom: 15px;">{% trans "Current Access" %}</h5>
<table class="modern-table ip-access-table">
<thead>
<tr>
<th>{% trans "Username" %}</th>
<th>{% trans "Host" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td class="db-name">{$ dbUsername $}</td>
<td>{$ dbHost $}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block footer_scripts %}
<script>
// Hide password change section when change password is successful
$(document).ready(function() {
var scope = angular.element($('[ng-controller="listDBs"]')).scope();
if (scope) {
// Override changePasswordBtn to hide the section after success
var originalChangePasswordBtn = scope.changePasswordBtn;
scope.changePasswordBtn = function() {
if (originalChangePasswordBtn) {
originalChangePasswordBtn();
}
// Hide the password change section after a delay
setTimeout(function() {
if (!scope.changePasswordBox) {
scope.changePasswordBox = true;
scope.$apply();
}
}, 2000);
};
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,627 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "MySQL Manager - CyberPanel" %}{% endblock %}
{% block content %}
{% 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: radial-gradient(circle at 70% 30%, rgba(91, 95, 207, 0.15) 0%, transparent 50%);
animation: rotate 30s linear 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;
}
.mysql-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);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-bottom: 3rem;
}
.stat-card {
background: white;
border-radius: 16px;
padding: 2rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 10px 40px rgba(0,0,0,0.08);
border: 1px solid #e8e9ff;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
animation: fadeInUp 0.5s ease-out;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 20px 60px rgba(0,0,0,0.12);
}
.stat-card.uptime {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-color: #bae6fd;
}
.stat-card.connections {
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border-color: #bbf7d0;
}
.stat-card.slow-queries {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border-color: #fcd34d;
}
.stat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.stat-title {
font-size: 1rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.stat-icon {
width: 48px;
height: 48px;
background: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
color: #1e293b;
margin: 0;
line-height: 1;
}
.stat-label {
font-size: 0.875rem;
color: #64748b;
margin-top: 0.5rem;
}
.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;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.card-body {
padding: 0;
}
.processes-table {
width: 100%;
background: white;
}
.processes-table thead {
background: #f8f9ff;
}
.processes-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: 2px solid #e8e9ff;
white-space: nowrap;
}
.processes-table td {
padding: 1rem;
color: #64748b;
font-size: 0.875rem;
border-bottom: 1px solid #f3f4f6;
}
.processes-table tbody tr {
transition: all 0.2s ease;
}
.processes-table tbody tr:hover {
background: #f8f9ff;
}
.processes-table tbody tr:last-child td {
border-bottom: none;
}
.process-id {
font-weight: 600;
color: #5b5fcf;
font-family: monospace;
}
.process-user {
display: flex;
align-items: center;
gap: 0.5rem;
}
.user-icon {
width: 24px;
height: 24px;
background: #e0e7ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
color: #5b5fcf;
}
.command-badge {
background: #f3f4f6;
color: #1e293b;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
font-family: monospace;
}
.time-badge {
background: #fef3c7;
color: #92400e;
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
}
.state-badge {
padding: 0.25rem 0.75rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
}
.state-active {
background: #d1fae5;
color: #065f46;
}
.state-idle {
background: #e0e7ff;
color: #5b5fcf;
}
.info-text {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: monospace;
font-size: 0.825rem;
}
.progress-bar {
width: 100px;
height: 6px;
background: #e8e9ff;
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #5b5fcf;
transition: width 0.3s ease;
}
.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;
}
.refresh-btn {
background: #fff;
color: #64748b;
border: 1px solid #e8e9ff;
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;
}
.refresh-btn:hover {
color: #5b5fcf;
border-color: #5b5fcf;
transform: rotate(180deg);
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
color: #94a3b8;
}
.empty-state i {
font-size: 3rem;
color: #cbd5e1;
margin-bottom: 1rem;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes rotate {
from { transform: rotate(0deg); }
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 pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Angular cloak */
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.stat-value {
font-size: 2rem;
}
.processes-table {
font-size: 0.75rem;
}
.processes-table th,
.processes-table td {
padding: 0.5rem;
}
.info-text {
max-width: 150px;
}
}
</style>
<div class="modern-container" ng-controller="Mysqlmanager" ng-init="cyberPanelLoading=false" ng-cloak>
<div class="page-header">
<h1 class="page-title">
<div class="mysql-icon">
<i class="fas fa-database" style="color: #5b5fcf; font-size: 1.75rem;"></i>
</div>
{% trans "MySQL Manager" %}
</h1>
<p class="page-subtitle">{% trans "Monitor and manage your MySQL database performance" %}</p>
<div class="header-actions">
<a href="{% url 'OptimizeMySQL' %}" class="btn-primary">
<i class="fas fa-tachometer-alt"></i>
{% trans "Optimize MySQL" %}
</a>
<a href="{% url 'phpMyAdmin' %}" class="btn-secondary">
<i class="fas fa-external-link-alt"></i>
{% trans "phpMyAdmin" %}
</a>
</div>
</div>
<!-- Status Cards -->
<div class="stats-grid">
<div class="stat-card uptime">
<div class="stat-header">
<h3 class="stat-title">{% trans "MySQL Uptime" %}</h3>
<div class="stat-icon" style="color: #0ea5e9;">
<i class="fas fa-clock"></i>
</div>
</div>
<p class="stat-value">{$ uptime $}</p>
<p class="stat-label">{% trans "Server running time" %}</p>
</div>
<div class="stat-card connections">
<div class="stat-header">
<h3 class="stat-title">{% trans "Active Connections" %}</h3>
<div class="stat-icon" style="color: #10b981;">
<i class="fas fa-link"></i>
</div>
</div>
<p class="stat-value">{$ connections $}</p>
<p class="stat-label">{% trans "Current connections" %}</p>
</div>
<div class="stat-card slow-queries">
<div class="stat-header">
<h3 class="stat-title">{% trans "Slow Queries" %}</h3>
<div class="stat-icon" style="color: #f59e0b;">
<i class="fas fa-exclamation-triangle"></i>
</div>
</div>
<p class="stat-value">{$ Slow_queries $}</p>
<p class="stat-label">{% trans "Queries exceeding time limit" %}</p>
</div>
</div>
<!-- Processes Table -->
<div class="main-card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-list-ul"></i>
{% trans "Active MySQL Processes" %}
<span ng-show="cyberPanelLoading" class="loading-spinner"></span>
</h2>
<button class="refresh-btn" ng-click="refreshProcesses()">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="processes-table">
<thead>
<tr>
<th>{% trans "ID" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Database" %}</th>
<th>{% trans "Command" %}</th>
<th>{% trans "Time" %}</th>
<th>{% trans "State" %}</th>
<th>{% trans "Query Info" %}</th>
<th>{% trans "Progress" %}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="process in processes">
<td>
<span class="process-id" ng-bind="process.id"></span>
</td>
<td>
<div class="process-user">
<div class="user-icon">
<i class="fas fa-user"></i>
</div>
<span ng-bind="process.user"></span>
</div>
</td>
<td>
<i class="fas fa-database" style="color: #94a3b8; margin-right: 0.5rem;"></i>
<span ng-bind="process.database || 'None'"></span>
</td>
<td>
<span class="command-badge" ng-bind="process.command"></span>
</td>
<td>
<span class="time-badge" ng-bind="process.time + 's'"></span>
</td>
<td>
<span class="state-badge"
ng-class="{'state-active': process.state === 'executing', 'state-idle': process.state !== 'executing'}"
ng-bind="process.state || 'Idle'"></span>
</td>
<td>
<span class="info-text"
ng-bind="process.info || 'No query'"
ng-attr-title="{$ process.info $}"></span>
</td>
<td>
<div class="progress-bar">
<div class="progress-fill"
ng-style="{'width': (process.progress || 0) + '%'}"></div>
</div>
</td>
</tr>
<tr ng-if="processes.length === 0">
<td colspan="8" class="empty-state">
<i class="fas fa-check-circle"></i>
<p>{% trans "No active processes found" %}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
// Add refresh functionality if not already present
if (typeof app !== 'undefined') {
app.controller('Mysqlmanager', function($scope, $http, $interval) {
// Existing controller code...
$scope.refreshProcesses = function() {
// Trigger a refresh of the processes
var icon = document.querySelector('.refresh-btn i');
icon.style.animation = 'spin 1s linear';
setTimeout(function() {
icon.style.animation = '';
}, 1000);
// The actual refresh logic should be implemented in the controller
};
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,38 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "PHPMYAdmin - CyberPanel" %}{% endblock %}
{% block content %}
{% load static %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div ng-controller="phpMyAdmin" class="container">
<div id="page-title">
<h2>{% trans "PHPMYAdmin" %}</h2>
<p>{% trans "Access your databases via PHPMYAdmin" %}</p>
</div>
<div class="panel">
<div class="panel-body">
<h3 class="title-hero">
{% trans "PHPMYAdmin" %}
</h3>
<div class="example-box-wrapper">
<p>{% trans "Auto-login for PHPMYAdmin is now supported. Click the button below to generate auto-access for PHPMYAdmin" %}</p>
<br>
<a ng-click="generateAccess()" href="#">
<button id="phpMyAdminlogin" class="btn btn-primary">Access Now <img ng-hide="cyberPanelLoading" src="{% static 'images/loading.gif' %}"></button>
</a>
</div>
</div>
</div>
<!-- Here we are making the phpmyadmin page autoload in current tab -->
<script type="text/javascript">
$(document).ready(function(){$(function(){$('#phpMyAdminlogin').click();});});
</script>
</div>
{% endblock %}

74
databases/tests.py Normal file
View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
from django.test import TestCase
import json
import requests
import urllib3
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
urllib3.disable_warnings()
# Create your tests here.
class TestDatabases(TestCase):
httpClient = requests.Session()
def MakeRequest(self, endPoint, data):
json_data = json.dumps(data)
path = 'https://cyberpanel.xyz:8090/%s' % (endPoint)
result = TestDatabases.httpClient.post(path, data=json_data, verify=False)
return json.loads(result.text)
def MakeRequestRaw(self, path):
result = requests.get(path)
return str(result.text)
def setupConnection(self):
try:
import MySQLdb as mysql
import MySQLdb.cursors as cursors
conn = mysql.connect(user='admin_hello', passwd='helloworld', cursorclass=cursors.SSCursor)
cursor = conn.cursor()
return conn, cursor
except:
return 0, 0
def setUp(self):
## Verify login
data_ret = {'username': 'admin', 'password': '1234567'}
response = self.MakeRequest('verifyLogin', data_ret)
self.assertEqual(response['loginStatus'], 1)
def test_submitDBCreation(self):
## Create DB
data_ret = {'databaseWebsite': 'cyberpanel.xyz', 'dbName': 'hello', 'dbUsername': 'hello',
'dbPassword': 'helloworld', 'webUserName': 'admin'}
response = self.MakeRequest('dataBases/submitDBCreation', data_ret)
self.assertEqual(response['status'], 1)
## Check connection to database
connection, cursor = self.setupConnection()
self.assertNotEqual(connection, 0)
## Delete db
data_ret = {'dbName': 'admin_hello'}
response = self.MakeRequest('dataBases/submitDatabaseDeletion', data_ret)
self.assertEqual(response['status'], 1)
## Check connection to database
connection, cursor = self.setupConnection()
self.assertEqual(connection, 0)

27
databases/urls.py Normal file
View File

@@ -0,0 +1,27 @@
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^$', views.loadDatabaseHome, name='loadDatabaseHome'),
re_path(r'^createDatabase$', views.createDatabase, name='createDatabase'),
re_path(r'^submitDBCreation$', views.submitDBCreation, name='submitDBCreation'),
re_path(r'^deleteDatabase$', views.deleteDatabase, name='deleteDatabase'),
re_path(r'^fetchDatabases$', views.fetchDatabases, name='fetchDatabases'),
re_path(r'^MysqlManager$', views.MySQLManager, name='MysqlManager'),
re_path(r'^OptimizeMySQL$', views.OptimizeMySQL, name='OptimizeMySQL'),
re_path(r'^upgrademysqlnow$', views.upgrademysqlnow, name='upgrademysqlnow'),
re_path(r'^UpgradeMySQL$', views.UpgradeMySQL, name='UpgradeMySQL'),
re_path(r'^upgrademysqlstatus$', views.upgrademysqlstatus, name='upgrademysqlstatus'),
re_path(r'^getMysqlstatus$', views.getMysqlstatus, name='getMysqlstatus'),
re_path(r'^restartMySQL$', views.restartMySQL, name='restartMySQL'),
re_path(r'^generateRecommendations$', views.generateRecommendations, name='generateRecommendations'),
re_path(r'^applyMySQLChanges$', views.applyMySQLChanges, name='applyMySQLChanges'),
re_path(r'^submitDatabaseDeletion$', views.submitDatabaseDeletion, name='submitDatabaseDeletion'),
re_path(r'^listDBs$', views.listDBs, name='listDBs'),
re_path(r'^changePassword$', views.changePassword, name='changePassword'),
re_path(r'^remoteAccess$', views.remoteAccess, name='remoteAccess'),
re_path(r'^allowRemoteIP$', views.allowRemoteIP, name='allowRemoteIP'),
re_path(r'^phpMyAdmin$', views.phpMyAdmin, name='phpMyAdmin'),
re_path(r'^generateAccess$', views.generateAccess, name='generateAccess'),
re_path(r'^fetchDetailsPHPMYAdmin$', views.fetchDetailsPHPMYAdmin, name='fetchDetailsPHPMYAdmin'),
]

552
databases/views.py Normal file
View File

@@ -0,0 +1,552 @@
# -*- coding: utf-8 -*-
import time
from random import randint
from django.shortcuts import redirect, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from cloudAPI.cloudManager import CloudManager
from loginSystem.views import loadLoginPage
from .databaseManager import DatabaseManager
from .mysqlOptimizer import MySQLOptimizer
from .pluginManager import pluginManager
import json
from plogical.processUtilities import ProcessUtilities
from loginSystem.models import Administrator
from plogical.acl import ACLManager
from databases.models import GlobalUserDB
from plogical import randomPassword
from cryptography.fernet import Fernet
from plogical.mysqlUtilities import mysqlUtilities
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
# Create your views here.
def loadDatabaseHome(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.loadDatabaseHome(request, userID)
except KeyError:
return redirect(loadLoginPage)
def createDatabase(request):
try:
result = pluginManager.preCreateDatabase(request)
if result != 200:
return result
userID = request.session['userID']
dm = DatabaseManager()
coreResult = dm.createDatabase(request, userID)
result = pluginManager.postCreateDatabase(request, coreResult)
if result != 200:
return result
return coreResult
except KeyError:
return redirect(loadLoginPage)
def submitDBCreation(request):
try:
userID = request.session['userID']
result = pluginManager.preSubmitDBCreation(request)
if result != 200:
return result
dm = DatabaseManager()
coreResult = dm.submitDBCreation(userID, json.loads(request.body))
result = pluginManager.postSubmitDBCreation(request, coreResult)
if result != 200:
return result
return coreResult
except KeyError:
return redirect(loadLoginPage)
def deleteDatabase(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.deleteDatabase(request, userID)
except KeyError:
return redirect(loadLoginPage)
def fetchDatabases(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.fetchDatabases(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def submitDatabaseDeletion(request):
try:
userID = request.session['userID']
result = pluginManager.preSubmitDatabaseDeletion(request)
if result != 200:
return result
dm = DatabaseManager()
coreResult = dm.submitDatabaseDeletion(userID, json.loads(request.body))
result = pluginManager.postSubmitDatabaseDeletion(request, coreResult)
if result != 200:
return result
return coreResult
except KeyError:
return redirect(loadLoginPage)
def listDBs(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.listDBs(request, userID)
except KeyError:
return redirect(loadLoginPage)
def changePassword(request):
try:
userID = request.session['userID']
result = pluginManager.preChangePassword(request)
if result != 200:
return result
dm = DatabaseManager()
coreResult = dm.changePassword(userID, json.loads(request.body))
result = pluginManager.postChangePassword(request, coreResult)
if result != 200:
return result
return coreResult
except KeyError:
return redirect(loadLoginPage)
def remoteAccess(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
coreResult = dm.remoteAccess(userID, json.loads(request.body))
return coreResult
except KeyError:
return redirect(loadLoginPage)
def allowRemoteIP(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
coreResult = dm.allowRemoteIP(userID, json.loads(request.body))
return coreResult
except KeyError:
return redirect(loadLoginPage)
def phpMyAdmin(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.phpMyAdmin(request, userID)
except KeyError:
return redirect(loadLoginPage)
def generateAccess(request):
try:
userID = request.session['userID']
admin = Administrator.objects.get(id=userID)
currentACL = ACLManager.loadedACL(userID)
## if user ACL is admin login as root
command = 'chmod 640 /usr/local/lscp/cyberpanel/logs/access.log'
ProcessUtilities.executioner(command)
if currentACL['admin'] == 1:
try:
GlobalUserDB.objects.get(username=admin.userName).delete()
except:
try:
gbobs = GlobalUserDB.objects.filter(username=admin.userName)
for gbobs in gbobs:
gbobs.delete()
except:
pass
password = randomPassword.generate_pass()
token = randomPassword.generate_pass()
GlobalUserDB(username=admin.userName, password=password, token=token).save()
data_ret = {'status': 1, 'token': token, 'username': admin.userName}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
keySavePath = '/home/cyberpanel/phpmyadmin_%s' % (admin.userName)
try:
GlobalUserDB.objects.get(username=admin.userName).delete()
except:
pass
command = 'rm -f %s' % (keySavePath)
ProcessUtilities.executioner(command)
## Create and save new key
key = Fernet.generate_key()
writeToFile = open(keySavePath, 'w')
writeToFile.write(key.decode())
writeToFile.close()
command = 'chown root:root %s' % (keySavePath)
ProcessUtilities.executioner(command)
command = 'chmod 600 %s' % (keySavePath)
ProcessUtilities.executioner(command)
##
password = randomPassword.generate_pass()
token = randomPassword.generate_pass()
f = Fernet(key)
GlobalUserDB(username=admin.userName, password=f.encrypt(password.encode('utf-8')).decode(),
token=token).save()
sites = ACLManager.findWebsiteObjects(currentACL, userID)
mysqlUtilities.addUserToDB(None, admin.userName, password, 1)
for site in sites:
for db in site.databases_set.all():
mysqlUtilities.addUserToDB(db.dbName, admin.userName, password, 0)
data_ret = {'status': 1, 'token': token, 'username': admin.userName}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
logging.writeToFile(str(msg))
data_ret = {'status': 0, 'createDBStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
@csrf_exempt
def fetchDetailsPHPMYAdmin(request):
try:
userID = request.session['userID']
admin = Administrator.objects.get(id=userID)
currentACL = ACLManager.loadedACL(userID)
token = request.POST.get('token')
username = request.POST.get('username')
from plogical.httpProc import httpProc
proc = httpProc(request, None,
)
# return proc.ajax(0, str(request.POST.get('token')))
if username != admin.userName:
return redirect(loadLoginPage)
## Key generation
gdb = GlobalUserDB.objects.get(username=admin.userName)
if gdb.token == token:
if currentACL['admin'] == 1:
passFile = "/etc/cyberpanel/mysqlPassword"
try:
jsonData = json.loads(open(passFile, 'r').read())
mysqluser = jsonData['mysqluser']
password = jsonData['mysqlpassword']
# returnURL = '/phpmyadmin/phpmyadminsignin.php?username=%s&password=%s' % (
# mysqluser, password)
# return redirect(returnURL)
data = {}
data['userName'] = mysqluser
data['password'] = password
proc = httpProc(request, 'databases/AutoLogin.html',
data, 'admin')
return proc.render()
except BaseException as msg:
f = open(passFile)
data = f.read()
password = data.split('\n', 1)[0]
password = password.strip('\n').strip('\r')
data = {}
data['userName'] = 'root'
data['password'] = password
# return redirect(returnURL)
proc = httpProc(request, 'databases/AutoLogin.html',
data, 'admin')
return proc.render()
# returnURL = '/phpmyadmin/phpmyadminsignin.php?username=%s&password=%s' % (
# 'root', password)
# return redirect(returnURL)
keySavePath = '/home/cyberpanel/phpmyadmin_%s' % (admin.userName)
key = ProcessUtilities.outputExecutioner('cat %s' % (keySavePath)).strip('\n').encode()
f = Fernet(key)
password = f.decrypt(gdb.password.encode('utf-8'))
sites = ACLManager.findWebsiteObjects(currentACL, userID)
for site in sites:
for db in site.databases_set.all():
mysqlUtilities.addUserToDB(db.dbName, admin.userName, password.decode(), 0)
data = {}
data['userName'] = admin.userName
data['password'] = password.decode()
# return redirect(returnURL)
proc = httpProc(request, 'databases/AutoLogin.html',
data, 'listDatabases')
return proc.render()
# returnURL = '/phpmyadmin/phpmyadminsignin.php?username=%s&password=%s' % (admin.userName, password.decode())
# return redirect(returnURL)
else:
return redirect(loadLoginPage)
except BaseException as msg:
data_ret = {'status': 0, 'createDBStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def MySQLManager(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.MySQLManager(request, userID)
except KeyError:
return redirect(loadLoginPage)
def OptimizeMySQL(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.OptimizeMySQL(request, userID)
except KeyError:
return redirect(loadLoginPage)
def UpgradeMySQL(request):
try:
userID = request.session['userID']
dm = DatabaseManager()
return dm.Upgardemysql(request, userID)
except KeyError:
return redirect(loadLoginPage)
def getMysqlstatus(request):
try:
userID = request.session['userID']
finalData = mysqlUtilities.showStatus()
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
finalData = json.dumps(finalData)
return HttpResponse(finalData)
except KeyError:
return redirect(loadLoginPage)
def restartMySQL(request):
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
data = {}
finalData = mysqlUtilities.restartMySQL()
data['status'] = finalData[0]
data['error_message'] = finalData[1]
json_data = json.dumps(data)
return HttpResponse(json_data)
except KeyError:
return redirect(loadLoginPage)
def generateRecommendations(request):
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
data = json.loads(request.body)
detectedRam = data['detectedRam']
data = {}
data['status'] = 1
data['generatedConf'] = MySQLOptimizer.generateRecommendations(detectedRam)
final_json = json.dumps(data)
return HttpResponse(final_json)
except KeyError:
return redirect(loadLoginPage)
def applyMySQLChanges(request):
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
data = json.loads(request.body)
finalData = mysqlUtilities.applyMySQLChanges(data)
data = {}
data['status'] = finalData[0]
data['error_message'] = finalData[1]
final_json = json.dumps(data)
return HttpResponse(final_json)
except KeyError:
return redirect(loadLoginPage)
def upgrademysqlnow(request):
try:
from plogical.virtualHostUtilities import virtualHostUtilities
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
data = json.loads(request.body)
version =data['mysqlversion']
tempStatusPath = "/home/cyberpanel/" + str(randint(1000, 9999))
execPath = f"/usr/local/CyberCP/bin/python /usr/local/CyberCP/plogical/mysqlUtilities.py UpgradeMariaDB --version {version} --tempStatusPath {tempStatusPath}"
ProcessUtilities.popenExecutioner(execPath)
time.sleep(2)
data_ret = {'status': 1, 'error_message': "None",
'tempStatusPath': tempStatusPath}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except KeyError:
return redirect(loadLoginPage)
def upgrademysqlstatus(request):
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
data = json.loads(request.body)
statusfile = data['statusfile']
installStatus = ProcessUtilities.outputExecutioner("sudo cat " + statusfile)
if installStatus.find("[200]") > -1:
command = 'sudo rm -f ' + statusfile
ProcessUtilities.executioner(command)
final_json = json.dumps({
'error_message': "None",
'requestStatus': installStatus,
'abort': 1,
'installed': 1,
})
return HttpResponse(final_json)
elif installStatus.find("[404]") > -1:
command = 'sudo rm -f ' + statusfile
ProcessUtilities.executioner(command)
final_json = json.dumps({
'abort': 1,
'installed': 0,
'error_message': "None",
'requestStatus': installStatus,
})
return HttpResponse(final_json)
else:
final_json = json.dumps({
'abort': 0,
'error_message': "None",
'requestStatus': installStatus,
})
return HttpResponse(final_json)
except KeyError:
return redirect(loadLoginPage)