mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-07-07 21:43:41 +02:00
Initial commit for v2.4.3
This commit is contained in:
BIN
databases/.DS_Store
vendored
Normal file
BIN
databases/.DS_Store
vendored
Normal file
Binary file not shown.
0
databases/__init__.py
Normal file
0
databases/__init__.py
Normal file
9
databases/admin.py
Normal file
9
databases/admin.py
Normal 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
8
databases/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DatabasesConfig(AppConfig):
|
||||
name = 'databases'
|
||||
383
databases/databaseManager.py
Normal file
383
databases/databaseManager.py
Normal 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()
|
||||
0
databases/migrations/__init__.py
Normal file
0
databases/migrations/__init__.py
Normal file
25
databases/models.py
Normal file
25
databases/models.py
Normal 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
121
databases/mysqlOptimizer.py
Normal 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)
|
||||
36
databases/pluginManager.py
Normal file
36
databases/pluginManager.py
Normal 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
28
databases/signals.py
Normal 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()
|
||||
1048
databases/static/databases/databases.js
Normal file
1048
databases/static/databases/databases.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
databases/static/databases/hourglass.png
Normal file
BIN
databases/static/databases/hourglass.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
databases/static/databases/link.png
Normal file
BIN
databases/static/databases/link.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
databases/static/databases/snail.png
Normal file
BIN
databases/static/databases/snail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
44
databases/templates/databases/AutoLogin.html
Normal file
44
databases/templates/databases/AutoLogin.html
Normal 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>
|
||||
528
databases/templates/databases/OptimizeMySQL.html
Normal file
528
databases/templates/databases/OptimizeMySQL.html
Normal 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 %}
|
||||
99
databases/templates/databases/Updatemysql.html
Normal file
99
databases/templates/databases/Updatemysql.html
Normal 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 %}
|
||||
553
databases/templates/databases/createDatabase.html
Normal file
553
databases/templates/databases/createDatabase.html
Normal 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 %}
|
||||
584
databases/templates/databases/deleteDatabase.html
Normal file
584
databases/templates/databases/deleteDatabase.html
Normal 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 %}
|
||||
80
databases/templates/databases/index.html
Normal file
80
databases/templates/databases/index.html
Normal 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 %}
|
||||
664
databases/templates/databases/listDataBases.html
Normal file
664
databases/templates/databases/listDataBases.html
Normal 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">×</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 %}
|
||||
627
databases/templates/databases/mysqlmanager.html
Normal file
627
databases/templates/databases/mysqlmanager.html
Normal 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 %}
|
||||
38
databases/templates/databases/phpMyAdmin.html
Normal file
38
databases/templates/databases/phpMyAdmin.html
Normal 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
74
databases/tests.py
Normal 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
27
databases/urls.py
Normal 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
552
databases/views.py
Normal 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)
|
||||
Reference in New Issue
Block a user