mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-11-15 17:56:12 +01:00
bug fix
This commit is contained in:
@@ -1,60 +1,32 @@
|
|||||||
:root {
|
#logo {
|
||||||
/* Modern color palette */
|
width: 200px;
|
||||||
--primary-color: #4f46e5;
|
height: auto;
|
||||||
--primary-hover: #4338ca;
|
|
||||||
--secondary-color: #6b7280;
|
|
||||||
--background-light: #f9fafb;
|
|
||||||
--background-lighter: #ffffff;
|
|
||||||
--text-primary: #111827;
|
|
||||||
--text-secondary: #4b5563;
|
|
||||||
--border-color: #e5e7eb;
|
|
||||||
--success-color: #10b981;
|
|
||||||
--danger-color: #ef4444;
|
|
||||||
--warning-color: #f59e0b;
|
|
||||||
--hover-bg: #f3f4f6;
|
|
||||||
--logo-bg: #f8fafc;
|
|
||||||
|
|
||||||
/* Enhanced shadows */
|
|
||||||
--card-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
||||||
--card-shadow-hover: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
||||||
--button-shadow: 0 2px 4px 0 rgb(0 0 0 / 0.05);
|
|
||||||
--button-shadow-hover: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
||||||
|
|
||||||
/* Modern transitions */
|
|
||||||
--transition-base: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
--transition-bounce: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
/*#navBar{
|
||||||
color: var(--text-primary);
|
background: -moz-linear-gradient(#a4dbf5, #8cc5e0);
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
background: -webkit-linear-gradient(#a4dbf5, #8cc5e0);
|
||||||
background-color: var(--background-light);
|
background: -o-linear-gradient(#a4dbf5, #8cc5e0);
|
||||||
line-height: 1.6;
|
}*/
|
||||||
}
|
|
||||||
|
|
||||||
/* Enhanced Navbar Styling */
|
|
||||||
#navBar {
|
#navBar {
|
||||||
background: var(--background-lighter);
|
background: linear-gradient(135deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%);
|
||||||
box-shadow: var(--card-shadow);
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
padding: 1.25rem;
|
padding: 0.5rem 0;
|
||||||
border-bottom: 1px solid var(--border-color);
|
}
|
||||||
position: sticky;
|
.navbar-brand {
|
||||||
top: 0;
|
margin: 0 1rem;
|
||||||
z-index: 100;
|
color: #fff !important;
|
||||||
backdrop-filter: blur(12px);
|
font-weight: 500;
|
||||||
-webkit-backdrop-filter: blur(12px);
|
}
|
||||||
|
#mainRow {
|
||||||
|
margin: 2rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modern Header Logo */
|
#tableHead {
|
||||||
.header-logo {
|
background: #f8f9fa;
|
||||||
display: flex;
|
color: #2c3e50;
|
||||||
align-items: center;
|
font-weight: 500;
|
||||||
gap: 1rem;
|
border-radius: 8px 8px 0 0;
|
||||||
padding: 0.875rem 1.25rem;
|
|
||||||
background: var(--logo-bg);
|
|
||||||
border-radius: 1.25rem;
|
|
||||||
transition: var(--transition-bounce);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-logo:hover {
|
.header-logo:hover {
|
||||||
@@ -68,11 +40,24 @@ body {
|
|||||||
filter: brightness(0.8) contrast(1.2);
|
filter: brightness(0.8) contrast(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-logo a {
|
.my-drop-zone {
|
||||||
display: flex;
|
border: 2px dashed #cbd5e0;
|
||||||
align-items: center;
|
border-radius: 8px;
|
||||||
gap: 0.75rem;
|
padding: 2rem;
|
||||||
text-decoration: none;
|
background: #f7fafc;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-drop-zone:hover {
|
||||||
|
border-color: #4158D0;
|
||||||
|
background: #f0f5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queueProg {
|
||||||
|
margin-bottom: 2%;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-logo span {
|
.header-logo span {
|
||||||
@@ -291,20 +276,14 @@ body {
|
|||||||
border-radius: 1rem 1rem 0 0;
|
border-radius: 1rem 1rem 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.path-nav-item {
|
a.nav-link {
|
||||||
display: inline-flex;
|
color: rgba(255,255,255,0.9) !important;
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
transition: var(--transition-bounce);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
a.nav-link:hover {
|
||||||
.path-nav-item:hover {
|
color: #ffffff !important;
|
||||||
color: var(--primary-color);
|
|
||||||
background: var(--hover-bg);
|
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,267 +323,142 @@ i.fa.fa-folder {
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
transition: var(--transition-smooth);
|
transition: var(--transition-smooth);
|
||||||
}
|
}
|
||||||
|
.form-control {
|
||||||
i.fa.fa-file {
|
|
||||||
color: var(--secondary-color) !important;
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
transition: var(--transition-smooth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modern Permissions Display */
|
|
||||||
.permissions {
|
|
||||||
font-family: 'SF Mono', 'Roboto Mono', monospace;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
background: var(--background-light);
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enhanced Navigation Actions */
|
|
||||||
.nav-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
background: var(--background-lighter);
|
|
||||||
border-bottom: 1.5px solid var(--border-color);
|
|
||||||
border-radius: 1.25rem 1.25rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
transition: var(--transition-bounce);
|
|
||||||
border: 1.5px solid var(--border-color);
|
|
||||||
background: var(--background-lighter);
|
|
||||||
min-width: 130px;
|
|
||||||
font-weight: 600;
|
|
||||||
box-shadow: var(--button-shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action:hover {
|
|
||||||
color: var(--primary-color);
|
|
||||||
background: var(--background-lighter);
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: var(--button-shadow-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action i {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action.home-action {
|
|
||||||
color: var(--primary-color);
|
|
||||||
background: var(--background-lighter);
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action.home-action:hover {
|
|
||||||
background: rgba(37, 99, 235, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action.back-action i,
|
|
||||||
.nav-action.refresh-action i {
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action.select-action,
|
|
||||||
.nav-action.unselect-action {
|
|
||||||
background: var(--background-light);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action.select-action:hover,
|
|
||||||
.nav-action.unselect-action:hover {
|
|
||||||
background: var(--hover-bg);
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File Table Header */
|
|
||||||
.file-table-header {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(200px, 2fr) minmax(100px, 1fr) minmax(150px, 1fr) minmax(100px, 1fr);
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: var(--background-light);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-table-header th {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File Table Row */
|
|
||||||
.file-table-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(200px, 2fr) minmax(100px, 1fr) minmax(150px, 1fr) minmax(100px, 1fr);
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
transition: background-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-table-row:hover {
|
|
||||||
background-color: var(--hover-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-name-cell {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-icon {
|
|
||||||
color: var(--warning-color);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-size,
|
|
||||||
.file-date,
|
|
||||||
.file-permissions {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-permissions {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Enhancements */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.nav-item .nav-link {
|
|
||||||
padding: 0.625rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#treeView {
|
|
||||||
height: auto;
|
|
||||||
max-height: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table td, .table th {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-actions {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-action {
|
|
||||||
min-width: auto;
|
|
||||||
padding: 0.625rem 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File Manager Navigation */
|
|
||||||
.file-manager-nav {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: var(--background-lighter);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-manager-nav a {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #4158D0;
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.15);
|
||||||
|
}
|
||||||
|
.form-control[readonly] {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #4158D0;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #C850C0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 20px rgba(0,0,0,0.05);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.table td, .table th {
|
||||||
|
padding: .75rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-top: 1px solid #edf2f7;
|
||||||
|
}
|
||||||
|
.table thead th {
|
||||||
|
border-bottom: 2px solid #edf2f7;
|
||||||
|
font-weight: 600;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--text-secondary);
|
text-transform: uppercase;
|
||||||
text-decoration: none;
|
letter-spacing: 0.5px;
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
background: var(--background-lighter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-manager-nav a:hover {
|
.table td {
|
||||||
color: var(--primary-color);
|
font-size: 0.9rem;
|
||||||
border-color: var(--primary-color);
|
color: #4a5568;
|
||||||
background: var(--hover-bg);
|
}
|
||||||
|
.list-group-item {
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
border: 1px solid #edf2f7;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
background-color: white;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.list-group-item:hover {
|
||||||
|
background-color: #f7fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-manager-nav a i {
|
i.fa.fa-file,
|
||||||
margin-right: 0.5rem;
|
i.fa.fa-minus,
|
||||||
|
i.fa.fa-plus {
|
||||||
|
color: #4158D0 !important;
|
||||||
|
}
|
||||||
|
.bg-lightgray {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-manager-nav a.home-link {
|
.progress-bar {
|
||||||
color: var(--primary-color);
|
background: linear-gradient(135deg, #4158D0 0%, #C850C0 100%);
|
||||||
border-color: var(--primary-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-manager-nav a.home-link:hover {
|
/* Card styles */
|
||||||
background: rgba(37, 99, 235, 0.1);
|
.card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 20px rgba(0,0,0,0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table Header */
|
.card-header {
|
||||||
.table-header {
|
background: #f8f9fa;
|
||||||
display: grid;
|
border-bottom: 1px solid #edf2f7;
|
||||||
grid-template-columns: minmax(300px, 2fr) minmax(100px, 1fr) minmax(150px, 1fr) minmax(120px, 1fr);
|
padding: 1rem 1.25rem;
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: var(--background-light);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--text-secondary);
|
border-radius: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-header div {
|
/* Navigation link styles */
|
||||||
font-size: 0.875rem;
|
a.nav-link {
|
||||||
text-transform: uppercase;
|
color: rgba(255,255,255,0.9) !important;
|
||||||
letter-spacing: 0.05em;
|
font-weight: 500;
|
||||||
}
|
|
||||||
|
|
||||||
.col-sm-9 .nav {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.75rem;
|
|
||||||
background: var(--background-lighter);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-sm-9 .nav .nav-item a {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
color: var(--text-secondary);
|
transition: all 0.2s ease;
|
||||||
text-decoration: none;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
background: var(--background-lighter);
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-sm-9 .nav .nav-item a:hover {
|
a.nav-link:hover {
|
||||||
color: var(--primary-color);
|
color: #ffffff !important;
|
||||||
border-color: var(--primary-color);
|
transform: translateY(-1px);
|
||||||
background: var(--hover-bg);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-sm-9 .nav .nav-item a i {
|
/* Utility classes */
|
||||||
margin-right: 0.5rem;
|
.bg-lightgray {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #c8c8c8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #4158D0 0%, #C850C0 100%);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(65, 88, 208, 0.15);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2329,8 +2329,26 @@ milter_default_action = accept
|
|||||||
command = "chmod +x /usr/local/CyberCP/cli/cyberPanel.py"
|
command = "chmod +x /usr/local/CyberCP/cli/cyberPanel.py"
|
||||||
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||||
|
|
||||||
|
def setupPHPSymlink(self):
|
||||||
|
try:
|
||||||
|
# Remove existing PHP symlink if it exists
|
||||||
|
if os.path.exists('/usr/bin/php'):
|
||||||
|
os.remove('/usr/bin/php')
|
||||||
|
|
||||||
|
# Create symlink to PHP 8.0
|
||||||
|
command = 'ln -s /usr/local/lsws/lsphp80/bin/php /usr/bin/php'
|
||||||
|
preFlightsChecks.call(command, self.distro, command, command, 1, 0, os.EX_OSERR)
|
||||||
|
|
||||||
|
logging.InstallLog.writeToFile("[setupPHPSymlink] PHP symlink created successfully.")
|
||||||
|
|
||||||
|
except OSError as msg:
|
||||||
|
logging.InstallLog.writeToFile('[ERROR] ' + str(msg) + " [setupPHPSymlink]")
|
||||||
|
return 0
|
||||||
|
|
||||||
def setupPHPAndComposer(self):
|
def setupPHPAndComposer(self):
|
||||||
try:
|
try:
|
||||||
|
# First setup the PHP symlink
|
||||||
|
self.setupPHPSymlink()
|
||||||
|
|
||||||
if self.distro == ubuntu:
|
if self.distro == ubuntu:
|
||||||
if not os.access('/usr/local/lsws/lsphp70/bin/php', os.R_OK):
|
if not os.access('/usr/local/lsws/lsphp70/bin/php', os.R_OK):
|
||||||
|
|||||||
@@ -12,21 +12,54 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
|||||||
$scope.canNotIssue = true;
|
$scope.canNotIssue = true;
|
||||||
$scope.sslIssued = true;
|
$scope.sslIssued = true;
|
||||||
$scope.couldNotConnect = true;
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.sslDetails = null;
|
||||||
|
|
||||||
$scope.showbtn = function () {
|
$scope.showbtn = function () {
|
||||||
$scope.issueSSLBtn = false;
|
$scope.issueSSLBtn = false;
|
||||||
|
$scope.fetchSSLDetails();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.fetchSSLDetails = function() {
|
||||||
|
if (!$scope.virtualHost) return;
|
||||||
|
|
||||||
|
var url = "/manageSSL/getSSLDetails";
|
||||||
|
var data = {
|
||||||
|
virtualHost: $scope.virtualHost
|
||||||
|
};
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(function(response) {
|
||||||
|
if (response.data.status === 1) {
|
||||||
|
$scope.sslDetails = response.data;
|
||||||
|
} else {
|
||||||
|
$scope.sslDetails = null;
|
||||||
|
new PNotify({
|
||||||
|
title: 'Error',
|
||||||
|
text: response.data.error_message,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function(response) {
|
||||||
|
$scope.sslDetails = null;
|
||||||
|
new PNotify({
|
||||||
|
title: 'Error',
|
||||||
|
text: 'Could not fetch SSL details',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.issueSSL = function () {
|
$scope.issueSSL = function () {
|
||||||
$scope.manageSSLLoading = false;
|
$scope.manageSSLLoading = false;
|
||||||
|
|
||||||
var url = "/manageSSL/issueSSL";
|
var url = "/manageSSL/issueSSL";
|
||||||
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
virtualHost: $scope.virtualHost,
|
virtualHost: $scope.virtualHost,
|
||||||
};
|
};
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': getCookie('csrftoken')
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
@@ -35,22 +68,16 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
|||||||
|
|
||||||
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
function ListInitialDatas(response) {
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
|
||||||
if (response.data.SSL == 1) {
|
if (response.data.SSL == 1) {
|
||||||
|
|
||||||
$scope.sslIssueCtrl = true;
|
$scope.sslIssueCtrl = true;
|
||||||
$scope.manageSSLLoading = true;
|
$scope.manageSSLLoading = true;
|
||||||
$scope.issueSSLBtn = false;
|
$scope.issueSSLBtn = false;
|
||||||
$scope.canNotIssue = true;
|
$scope.canNotIssue = true;
|
||||||
$scope.sslIssued = false;
|
$scope.sslIssued = false;
|
||||||
$scope.couldNotConnect = true;
|
$scope.couldNotConnect = true;
|
||||||
|
|
||||||
$scope.sslDomain = $scope.virtualHost;
|
$scope.sslDomain = $scope.virtualHost;
|
||||||
|
$scope.fetchSSLDetails(); // Refresh SSL details after issuing
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$scope.sslIssueCtrl = true;
|
$scope.sslIssueCtrl = true;
|
||||||
$scope.manageSSLLoading = true;
|
$scope.manageSSLLoading = true;
|
||||||
@@ -59,10 +86,7 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
|||||||
$scope.sslIssued = true;
|
$scope.sslIssued = true;
|
||||||
$scope.couldNotConnect = true;
|
$scope.couldNotConnect = true;
|
||||||
$scope.errorMessage = response.data.error_message;
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cantLoadInitialDatas(response) {
|
function cantLoadInitialDatas(response) {
|
||||||
@@ -72,10 +96,7 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
|||||||
$scope.canNotIssue = true;
|
$scope.canNotIssue = true;
|
||||||
$scope.sslIssued = true;
|
$scope.sslIssued = true;
|
||||||
$scope.couldNotConnect = false;
|
$scope.couldNotConnect = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -85,21 +106,54 @@ app.controller('sslIssueCtrl', function ($scope, $http) {
|
|||||||
app.controller('sslIssueCtrlV2', function ($scope, $http) {
|
app.controller('sslIssueCtrlV2', function ($scope, $http) {
|
||||||
|
|
||||||
$scope.manageSSLLoading = true;
|
$scope.manageSSLLoading = true;
|
||||||
|
$scope.sslDetails = null;
|
||||||
|
|
||||||
$scope.showbtn = function () {
|
$scope.showbtn = function () {
|
||||||
$scope.issueSSLBtn = false;
|
$scope.issueSSLBtn = false;
|
||||||
|
$scope.fetchSSLDetails();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.fetchSSLDetails = function() {
|
||||||
|
if (!$scope.virtualHost) return;
|
||||||
|
|
||||||
|
var url = "/manageSSL/getSSLDetails";
|
||||||
|
var data = {
|
||||||
|
virtualHost: $scope.virtualHost
|
||||||
|
};
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(function(response) {
|
||||||
|
if (response.data.status === 1) {
|
||||||
|
$scope.sslDetails = response.data;
|
||||||
|
} else {
|
||||||
|
$scope.sslDetails = null;
|
||||||
|
new PNotify({
|
||||||
|
title: 'Error',
|
||||||
|
text: response.data.error_message,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function(response) {
|
||||||
|
$scope.sslDetails = null;
|
||||||
|
new PNotify({
|
||||||
|
title: 'Error',
|
||||||
|
text: 'Could not fetch SSL details',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.issueSSL = function () {
|
$scope.issueSSL = function () {
|
||||||
$scope.manageSSLLoading = false;
|
$scope.manageSSLLoading = false;
|
||||||
|
|
||||||
var url = "/manageSSL/v2IssueSSL";
|
var url = "/manageSSL/v2IssueSSL";
|
||||||
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
virtualHost: $scope.virtualHost,
|
virtualHost: $scope.virtualHost,
|
||||||
};
|
};
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': getCookie('csrftoken')
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
@@ -108,24 +162,16 @@ app.controller('sslIssueCtrlV2', function ($scope, $http) {
|
|||||||
|
|
||||||
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
function ListInitialDatas(response) {
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
$scope.manageSSLLoading = true;
|
$scope.manageSSLLoading = true;
|
||||||
|
|
||||||
|
|
||||||
if (response.data.SSL === 1) {
|
if (response.data.SSL === 1) {
|
||||||
|
|
||||||
$scope.sslStatus = 'Issued.';
|
$scope.sslStatus = 'Issued.';
|
||||||
$scope.sslLogs = response.data.sslLogs;
|
$scope.sslLogs = response.data.sslLogs;
|
||||||
|
$scope.fetchSSLDetails(); // Refresh SSL details after issuing
|
||||||
} else {
|
} else {
|
||||||
$scope.sslStatus = 'Failed.';
|
$scope.sslStatus = 'Failed.';
|
||||||
$scope.sslLogs = response.data.sslLogs;
|
$scope.sslLogs = response.data.sslLogs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cantLoadInitialDatas(response) {
|
function cantLoadInitialDatas(response) {
|
||||||
@@ -135,12 +181,8 @@ app.controller('sslIssueCtrlV2', function ($scope, $http) {
|
|||||||
$scope.canNotIssue = true;
|
$scope.canNotIssue = true;
|
||||||
$scope.sslIssued = true;
|
$scope.sslIssued = true;
|
||||||
$scope.couldNotConnect = false;
|
$scope.couldNotConnect = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
/* Java script code to issue SSL V2 ends here */
|
/* Java script code to issue SSL V2 ends here */
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
style="height: 23px;line-height: 21px;"
|
style="height: 23px;line-height: 21px;"
|
||||||
class="btn btn-border btn-alt border-red btn-link font-red"
|
class="btn btn-border btn-alt border-red btn-link font-red"
|
||||||
title=""><span>{% trans "SSL Docs" %}</span></a></h2>
|
title=""><span>{% trans "SSL Docs" %}</span></a></h2>
|
||||||
<p>{% trans "This page can be used to issue Let’s Encrypt SSL for existing websites on server." %}</p>
|
<p>{% trans "This page can be used to issue Let's Encrypt SSL for existing websites on server." %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-controller="sslIssueCtrl" class="panel">
|
<div ng-controller="sslIssueCtrl" class="panel">
|
||||||
@@ -39,6 +39,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" ng-if="sslDetails">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="panel panel-primary">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">SSL Details</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div ng-if="sslDetails.hasSSL">
|
||||||
|
<p><strong>Status:</strong> Active</p>
|
||||||
|
<p><strong>Issued By:</strong> {$ sslDetails.authority $}</p>
|
||||||
|
<p><strong>Expiry Date:</strong> {$ sslDetails.expiryDate $}</p>
|
||||||
|
<p><strong>Days Remaining:</strong> {$ sslDetails.days $}</p>
|
||||||
|
</div>
|
||||||
|
<div ng-if="!sslDetails.hasSSL">
|
||||||
|
<p><strong>Status:</strong> No SSL Certificate</p>
|
||||||
|
<p ng-if="sslDetails.error_message"><strong>Error:</strong> {$ sslDetails.error_message $}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div ng-hide="issueSSLBtn" class="form-group">
|
<div ng-hide="issueSSLBtn" class="form-group">
|
||||||
<label class="col-sm-3 control-label"></label>
|
<label class="col-sm-3 control-label"></label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<label class="col-sm-2 control-label">{% trans "Select Domain" %} </label>
|
<label class="col-sm-2 control-label">{% trans "Select Domain" %} </label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<select ng-model="virtualHost"
|
<select ng-model="virtualHost"
|
||||||
class="form-control">
|
class="form-control" ng-change="showbtn()">
|
||||||
{% for items in websiteList %}
|
{% for items in websiteList %}
|
||||||
<option>{{ items }}</option>
|
<option>{{ items }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -83,8 +83,29 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group" ng-if="sslDetails">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="panel panel-primary">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">SSL Details</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div ng-if="sslDetails.hasSSL">
|
||||||
|
<p><strong>Status:</strong> Active</p>
|
||||||
|
<p><strong>Issued By:</strong> {$ sslDetails.authority $}</p>
|
||||||
|
<p><strong>Expiry Date:</strong> {$ sslDetails.expiryDate $}</p>
|
||||||
|
<p><strong>Days Remaining:</strong> {$ sslDetails.days $}</p>
|
||||||
|
</div>
|
||||||
|
<div ng-if="!sslDetails.hasSSL">
|
||||||
|
<p><strong>Status:</strong> No SSL Certificate</p>
|
||||||
|
<p ng-if="sslDetails.error_message"><strong>Error:</strong> {$ sslDetails.error_message $}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h3 style="margin: 2%">SSL Status: {$ sslStatus $}</h3>
|
<h3 style="margin: 2%">SSL Status: {$ sslStatus $}</h3>
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
path('manageSSL', views.manageSSL, name='manageSSL'),
|
path('manageSSL', views.manageSSL, name='manageSSL'),
|
||||||
path('issueSSL', views.issueSSL, name='issueSSL'),
|
path('issueSSL', views.issueSSL, name='issueSSL'),
|
||||||
|
path('getSSLDetails', views.getSSLDetails, name='getSSLDetails'),
|
||||||
|
|
||||||
path('sslForHostName', views.sslForHostName, name='sslForHostName'),
|
path('sslForHostName', views.sslForHostName, name='sslForHostName'),
|
||||||
path('obtainHostNameSSL', views.obtainHostNameSSL, name='obtainHostNameSSL'),
|
path('obtainHostNameSSL', views.obtainHostNameSSL, name='obtainHostNameSSL'),
|
||||||
|
|||||||
@@ -333,3 +333,73 @@ def obtainMailServerSSL(request):
|
|||||||
'error_message': str(msg)}
|
'error_message': str(msg)}
|
||||||
json_data = json.dumps(data_ret)
|
json_data = json.dumps(data_ret)
|
||||||
return HttpResponse(json_data)
|
return HttpResponse(json_data)
|
||||||
|
|
||||||
|
def getSSLDetails(request):
|
||||||
|
try:
|
||||||
|
userID = request.session['userID']
|
||||||
|
admin = Administrator.objects.get(pk=userID)
|
||||||
|
try:
|
||||||
|
if request.method == 'POST':
|
||||||
|
currentACL = ACLManager.loadedACL(userID)
|
||||||
|
|
||||||
|
if currentACL['admin'] == 1:
|
||||||
|
pass
|
||||||
|
elif currentACL['manageSSL'] == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ACLManager.loadErrorJson('SSL', 0)
|
||||||
|
|
||||||
|
data = json.loads(request.body)
|
||||||
|
virtualHost = data['virtualHost']
|
||||||
|
|
||||||
|
if ACLManager.checkOwnership(virtualHost, admin, currentACL) == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ACLManager.loadErrorJson()
|
||||||
|
|
||||||
|
try:
|
||||||
|
website = ChildDomains.objects.get(domain=virtualHost)
|
||||||
|
except:
|
||||||
|
website = Websites.objects.get(domain=virtualHost)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import OpenSSL
|
||||||
|
from datetime import datetime
|
||||||
|
filePath = '/etc/letsencrypt/live/%s/fullchain.pem' % (virtualHost)
|
||||||
|
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||||
|
open(filePath, 'r').read())
|
||||||
|
expireData = x509.get_notAfter().decode('ascii')
|
||||||
|
finalDate = datetime.strptime(expireData, '%Y%m%d%H%M%SZ')
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
diff = finalDate - now
|
||||||
|
|
||||||
|
data_ret = {
|
||||||
|
'status': 1,
|
||||||
|
'hasSSL': True,
|
||||||
|
'days': str(diff.days),
|
||||||
|
'authority': x509.get_issuer().get_components()[1][1].decode('utf-8'),
|
||||||
|
'expiryDate': finalDate.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
}
|
||||||
|
|
||||||
|
if data_ret['authority'] == 'Denial':
|
||||||
|
data_ret['authority'] = 'SELF-SIGNED SSL'
|
||||||
|
|
||||||
|
except BaseException as msg:
|
||||||
|
data_ret = {
|
||||||
|
'status': 1,
|
||||||
|
'hasSSL': False,
|
||||||
|
'error_message': str(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
except KeyError:
|
||||||
|
data_ret = {'status': 0, 'error_message': 'Not logged in'}
|
||||||
|
json_data = json.dumps(data_ret)
|
||||||
|
return HttpResponse(json_data)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ django.setup()
|
|||||||
from loginSystem.models import Administrator, ACL
|
from loginSystem.models import Administrator, ACL
|
||||||
from django.shortcuts import HttpResponse
|
from django.shortcuts import HttpResponse
|
||||||
from packages.models import Package
|
from packages.models import Package
|
||||||
from websiteFunctions.models import Websites, ChildDomains, aliasDomains, DockerSites
|
from websiteFunctions.models import Websites, ChildDomains, aliasDomains, DockerSites, WPSites
|
||||||
import json
|
import json
|
||||||
from subprocess import call, CalledProcessError
|
from subprocess import call, CalledProcessError
|
||||||
from shlex import split
|
from shlex import split
|
||||||
@@ -582,25 +582,45 @@ class ACLManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def searchWebsiteObjects(currentACL, userID, searchTerm):
|
def searchWebsiteObjects(currentACL, userID, searchTerm):
|
||||||
|
|
||||||
if currentACL['admin'] == 1:
|
if currentACL['admin'] == 1:
|
||||||
return Websites.objects.filter(domain__istartswith=searchTerm)
|
# Get websites that match the search term
|
||||||
|
websites = Websites.objects.filter(domain__istartswith=searchTerm)
|
||||||
|
# Get WordPress sites that match the search term
|
||||||
|
wp_sites = WPSites.objects.filter(title__icontains=searchTerm)
|
||||||
|
# Add WordPress sites' parent websites to the results
|
||||||
|
for wp in wp_sites:
|
||||||
|
if wp.owner not in websites:
|
||||||
|
websites = websites | Websites.objects.filter(pk=wp.owner.pk)
|
||||||
|
return websites
|
||||||
else:
|
else:
|
||||||
websiteList = []
|
websiteList = []
|
||||||
admin = Administrator.objects.get(pk=userID)
|
admin = Administrator.objects.get(pk=userID)
|
||||||
|
|
||||||
|
# Get websites that match the search term
|
||||||
websites = admin.websites_set.filter(domain__istartswith=searchTerm)
|
websites = admin.websites_set.filter(domain__istartswith=searchTerm)
|
||||||
|
|
||||||
for items in websites:
|
for items in websites:
|
||||||
websiteList.append(items)
|
websiteList.append(items)
|
||||||
|
|
||||||
admins = Administrator.objects.filter(owner=admin.pk)
|
# Get WordPress sites that match the search term
|
||||||
|
wp_sites = WPSites.objects.filter(title__icontains=searchTerm)
|
||||||
|
for wp in wp_sites:
|
||||||
|
if wp.owner.admin == admin and wp.owner not in websiteList:
|
||||||
|
websiteList.append(wp.owner)
|
||||||
|
|
||||||
|
admins = Administrator.objects.filter(owner=admin.pk)
|
||||||
for items in admins:
|
for items in admins:
|
||||||
|
# Get websites that match the search term
|
||||||
webs = items.websites_set.filter(domain__istartswith=searchTerm)
|
webs = items.websites_set.filter(domain__istartswith=searchTerm)
|
||||||
for web in webs:
|
for web in webs:
|
||||||
|
if web not in websiteList:
|
||||||
websiteList.append(web)
|
websiteList.append(web)
|
||||||
|
|
||||||
|
# Get WordPress sites that match the search term
|
||||||
|
wp_sites = WPSites.objects.filter(title__icontains=searchTerm)
|
||||||
|
for wp in wp_sites:
|
||||||
|
if wp.owner.admin == items and wp.owner not in websiteList:
|
||||||
|
websiteList.append(wp.owner)
|
||||||
|
|
||||||
return websiteList
|
return websiteList
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
934
plogical/customACME.py
Normal file
934
plogical/customACME.py
Normal file
@@ -0,0 +1,934 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
import OpenSSL
|
||||||
|
from plogical import CyberCPLogFileWriter as logging
|
||||||
|
from plogical.processUtilities import ProcessUtilities
|
||||||
|
import socket
|
||||||
|
|
||||||
|
class CustomACME:
|
||||||
|
def __init__(self, domain, admin_email, staging=False, provider='letsencrypt'):
|
||||||
|
"""Initialize CustomACME"""
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Initializing CustomACME for domain: {domain}, email: {admin_email}, staging: {staging}, provider: {provider}')
|
||||||
|
self.domain = domain
|
||||||
|
self.admin_email = admin_email
|
||||||
|
self.staging = staging
|
||||||
|
self.provider = provider
|
||||||
|
|
||||||
|
# Set the ACME directory URL based on provider and staging flag
|
||||||
|
if provider == 'zerossl':
|
||||||
|
if staging:
|
||||||
|
self.acme_directory = "https://acme-staging.zerossl.com/v2/DV90"
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using ZeroSSL staging ACME directory')
|
||||||
|
else:
|
||||||
|
self.acme_directory = "https://acme.zerossl.com/v2/DV90"
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using ZeroSSL production ACME directory')
|
||||||
|
else: # letsencrypt
|
||||||
|
if staging:
|
||||||
|
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using Let\'s Encrypt staging ACME directory')
|
||||||
|
else:
|
||||||
|
self.acme_directory = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using Let\'s Encrypt production ACME directory')
|
||||||
|
|
||||||
|
self.account_key = None
|
||||||
|
self.account_url = None
|
||||||
|
self.directory = None
|
||||||
|
self.nonce = None
|
||||||
|
self.order_url = None
|
||||||
|
self.authorizations = []
|
||||||
|
self.finalize_url = None
|
||||||
|
self.certificate_url = None
|
||||||
|
|
||||||
|
# Initialize paths
|
||||||
|
self.cert_path = f'/etc/letsencrypt/live/{domain}'
|
||||||
|
self.challenge_path = '/usr/local/lsws/Example/html/.well-known/acme-challenge'
|
||||||
|
self.account_key_path = f'/etc/letsencrypt/accounts/{domain}.key'
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Certificate path: {self.cert_path}, Challenge path: {self.challenge_path}')
|
||||||
|
|
||||||
|
# Create accounts directory if it doesn't exist
|
||||||
|
os.makedirs('/etc/letsencrypt/accounts', exist_ok=True)
|
||||||
|
|
||||||
|
def _generate_account_key(self):
|
||||||
|
"""Generate RSA account key"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Generating RSA account key...')
|
||||||
|
key = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=2048,
|
||||||
|
backend=default_backend()
|
||||||
|
)
|
||||||
|
self.account_key = key
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Successfully generated RSA account key')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error generating account key: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_directory(self):
|
||||||
|
"""Get ACME directory"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Fetching ACME directory from {self.acme_directory}')
|
||||||
|
response = requests.get(self.acme_directory)
|
||||||
|
self.directory = response.json()
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully fetched ACME directory: {json.dumps(self.directory)}')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error getting directory: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_nonce(self):
|
||||||
|
"""Get new nonce from ACME server"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Getting new nonce...')
|
||||||
|
response = requests.head(self.directory['newNonce'])
|
||||||
|
self.nonce = response.headers['Replay-Nonce']
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully got nonce: {self.nonce}')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error getting nonce: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _create_jws(self, payload, url):
|
||||||
|
"""Create JWS (JSON Web Signature)"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Creating JWS for URL: {url}')
|
||||||
|
if payload is not None:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Payload: {json.dumps(payload)}')
|
||||||
|
|
||||||
|
# Get a fresh nonce for this request
|
||||||
|
if not self._get_nonce():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get fresh nonce')
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get the private key numbers
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Getting private key numbers...')
|
||||||
|
private_numbers = self.account_key.private_numbers()
|
||||||
|
public_numbers = private_numbers.public_numbers
|
||||||
|
|
||||||
|
# Convert numbers to bytes
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Converting RSA numbers to bytes...')
|
||||||
|
n_bytes = public_numbers.n.to_bytes((public_numbers.n.bit_length() + 7) // 8, 'big')
|
||||||
|
e_bytes = public_numbers.e.to_bytes((public_numbers.e.bit_length() + 7) // 8, 'big')
|
||||||
|
|
||||||
|
# Create JWK
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Creating JWK...')
|
||||||
|
jwk_key = {
|
||||||
|
"kty": "RSA",
|
||||||
|
"n": base64.urlsafe_b64encode(n_bytes).decode('utf-8').rstrip('='),
|
||||||
|
"e": base64.urlsafe_b64encode(e_bytes).decode('utf-8').rstrip('='),
|
||||||
|
"alg": "RS256"
|
||||||
|
}
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Created JWK: {json.dumps(jwk_key)}')
|
||||||
|
|
||||||
|
# Create protected header
|
||||||
|
protected = {
|
||||||
|
"alg": "RS256",
|
||||||
|
"url": url,
|
||||||
|
"nonce": self.nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add either JWK or Key ID based on whether we have an account URL
|
||||||
|
if self.account_url and url != self.directory['newAccount']:
|
||||||
|
protected["kid"] = self.account_url
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Using Key ID: {self.account_url}')
|
||||||
|
else:
|
||||||
|
protected["jwk"] = jwk_key
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using JWK for new account')
|
||||||
|
|
||||||
|
# Encode protected header
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Encoding protected header...')
|
||||||
|
protected_b64 = base64.urlsafe_b64encode(
|
||||||
|
json.dumps(protected).encode('utf-8')
|
||||||
|
).decode('utf-8').rstrip('=')
|
||||||
|
|
||||||
|
# For POST-as-GET requests, payload_b64 should be empty string
|
||||||
|
if payload is None:
|
||||||
|
payload_b64 = ""
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using empty payload for POST-as-GET request')
|
||||||
|
else:
|
||||||
|
# Encode payload
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Encoding payload...')
|
||||||
|
payload_b64 = base64.urlsafe_b64encode(
|
||||||
|
json.dumps(payload).encode('utf-8')
|
||||||
|
).decode('utf-8').rstrip('=')
|
||||||
|
|
||||||
|
# Create signature input
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Creating signature input...')
|
||||||
|
signature_input = f"{protected_b64}.{payload_b64}".encode('utf-8')
|
||||||
|
|
||||||
|
# Sign the input
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Signing input...')
|
||||||
|
signature = self.account_key.sign(
|
||||||
|
signature_input,
|
||||||
|
padding.PKCS1v15(),
|
||||||
|
hashes.SHA256()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Encode signature
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Encoding signature...')
|
||||||
|
signature_b64 = base64.urlsafe_b64encode(signature).decode('utf-8').rstrip('=')
|
||||||
|
|
||||||
|
# Create final JWS
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Creating final JWS...')
|
||||||
|
jws = {
|
||||||
|
"protected": protected_b64,
|
||||||
|
"signature": signature_b64
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only add payload if it exists
|
||||||
|
if payload is not None:
|
||||||
|
jws["payload"] = payload_b64
|
||||||
|
|
||||||
|
# Ensure the JWS is properly formatted
|
||||||
|
jws_str = json.dumps(jws, separators=(',', ':'))
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Final JWS: {jws_str}')
|
||||||
|
|
||||||
|
return jws_str
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error creating JWS: {str(e)}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _load_account_key(self):
|
||||||
|
"""Load existing account key if available"""
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.account_key_path):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Loading existing account key...')
|
||||||
|
with open(self.account_key_path, 'rb') as f:
|
||||||
|
key_data = f.read()
|
||||||
|
self.account_key = serialization.load_pem_private_key(
|
||||||
|
key_data,
|
||||||
|
password=None,
|
||||||
|
backend=default_backend()
|
||||||
|
)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Successfully loaded existing account key')
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error loading account key: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _save_account_key(self):
|
||||||
|
"""Save account key for future use"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Saving account key...')
|
||||||
|
key_data = self.account_key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.PKCS8,
|
||||||
|
encryption_algorithm=serialization.NoEncryption()
|
||||||
|
)
|
||||||
|
with open(self.account_key_path, 'wb') as f:
|
||||||
|
f.write(key_data)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Successfully saved account key')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error saving account key: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _create_account(self):
|
||||||
|
"""Create new ACME account"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Creating new ACME account...')
|
||||||
|
payload = {
|
||||||
|
"termsOfServiceAgreed": True,
|
||||||
|
"contact": [f"mailto:{self.admin_email}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
jws = self._create_jws(payload, self.directory['newAccount'])
|
||||||
|
if not jws:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to create JWS for account creation')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Sending account creation request...')
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/jose+json'
|
||||||
|
}
|
||||||
|
response = requests.post(self.directory['newAccount'], data=jws, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Account creation response status: {response.status_code}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Account creation response: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
self.account_url = response.headers['Location']
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully created account. Account URL: {self.account_url}')
|
||||||
|
# Save the account key for future use
|
||||||
|
self._save_account_key()
|
||||||
|
return True
|
||||||
|
elif response.status_code == 429:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Rate limit hit for account creation. Using staging environment...')
|
||||||
|
self.staging = True
|
||||||
|
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
# Get new directory and nonce for staging
|
||||||
|
if not self._get_directory():
|
||||||
|
return False
|
||||||
|
if not self._get_nonce():
|
||||||
|
return False
|
||||||
|
# Try one more time with staging
|
||||||
|
return self._create_account()
|
||||||
|
elif response.status_code == 400 and "badNonce" in response.text:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Bad nonce, getting new nonce and retrying...')
|
||||||
|
if not self._get_nonce():
|
||||||
|
return False
|
||||||
|
return self._create_account()
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error creating account: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _create_order(self, domains):
|
||||||
|
"""Create new order for domains"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Creating new order for domains: {domains}')
|
||||||
|
identifiers = [{"type": "dns", "value": domain} for domain in domains]
|
||||||
|
payload = {
|
||||||
|
"identifiers": identifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
jws = self._create_jws(payload, self.directory['newOrder'])
|
||||||
|
if not jws:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to create JWS for order creation')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Sending order creation request...')
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/jose+json'
|
||||||
|
}
|
||||||
|
response = requests.post(self.directory['newOrder'], data=jws, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order creation response status: {response.status_code}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order creation response: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
self.order_url = response.headers['Location']
|
||||||
|
self.authorizations = response.json()['authorizations']
|
||||||
|
self.finalize_url = response.json()['finalize']
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully created order. Order URL: {self.order_url}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Authorizations: {self.authorizations}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Finalize URL: {self.finalize_url}')
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error creating order: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_http_challenge(self, challenge):
|
||||||
|
"""Handle HTTP-01 challenge"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Handling HTTP challenge: {json.dumps(challenge)}')
|
||||||
|
|
||||||
|
# Get key authorization
|
||||||
|
key_auth = self._get_key_authorization(challenge)
|
||||||
|
if not key_auth:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get key authorization')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Create challenge directory if it doesn't exist
|
||||||
|
if not os.path.exists(self.challenge_path):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Creating challenge directory: {self.challenge_path}')
|
||||||
|
os.makedirs(self.challenge_path)
|
||||||
|
|
||||||
|
# Write challenge file
|
||||||
|
challenge_file = os.path.join(self.challenge_path, challenge['token'])
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Writing challenge file: {challenge_file}')
|
||||||
|
|
||||||
|
# Write only the key authorization to the file
|
||||||
|
with open(challenge_file, 'w') as f:
|
||||||
|
f.write(key_auth)
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Successfully handled HTTP challenge')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error handling HTTP challenge: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_dns_challenge(self, challenge):
|
||||||
|
"""Handle DNS-01 challenge (Cloudflare)"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Handling DNS challenge: {json.dumps(challenge)}')
|
||||||
|
# This is a placeholder - implement Cloudflare API integration
|
||||||
|
# You'll need to add your Cloudflare API credentials and implementation
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error handling DNS challenge: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_key_authorization(self, challenge):
|
||||||
|
"""Get key authorization for challenge"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Getting key authorization...')
|
||||||
|
|
||||||
|
# Get the private key numbers
|
||||||
|
private_numbers = self.account_key.private_numbers()
|
||||||
|
public_numbers = private_numbers.public_numbers
|
||||||
|
|
||||||
|
# Convert numbers to bytes
|
||||||
|
n_bytes = public_numbers.n.to_bytes((public_numbers.n.bit_length() + 7) // 8, 'big')
|
||||||
|
e_bytes = public_numbers.e.to_bytes((public_numbers.e.bit_length() + 7) // 8, 'big')
|
||||||
|
|
||||||
|
# Create JWK without alg field
|
||||||
|
jwk_key = {
|
||||||
|
"kty": "RSA",
|
||||||
|
"n": base64.urlsafe_b64encode(n_bytes).decode('utf-8').rstrip('='),
|
||||||
|
"e": base64.urlsafe_b64encode(e_bytes).decode('utf-8').rstrip('=')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate the JWK thumbprint according to RFC 7638
|
||||||
|
# The thumbprint is a hash of the JWK (JSON Web Key) in a specific format
|
||||||
|
# First, we create a dictionary with the required JWK parameters
|
||||||
|
jwk = {
|
||||||
|
"e": base64.urlsafe_b64encode(public_numbers.e.to_bytes(3, 'big')).decode('utf-8').rstrip('='),
|
||||||
|
"kty": "RSA", # Key type
|
||||||
|
"n": base64.urlsafe_b64encode(public_numbers.n.to_bytes(256, 'big')).decode('utf-8').rstrip('=')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sort the JWK parameters alphabetically by key name
|
||||||
|
# This ensures consistent thumbprint calculation regardless of parameter order
|
||||||
|
sorted_jwk = json.dumps(jwk, sort_keys=True, separators=(',', ':'))
|
||||||
|
|
||||||
|
# Calculate the SHA-256 hash of the sorted JWK
|
||||||
|
# Example of what sorted_jwk might look like:
|
||||||
|
# {"e":"AQAB","kty":"RSA","n":"tVKUtcx_n9rt5afY_2WFNVAu9fjD4xqX4Xm3dJz3XYb"}
|
||||||
|
# The thumbprint will be a 32-byte SHA-256 hash of this string
|
||||||
|
# For example, it might look like: b'x\x9c\x1d\x8f\x8b\x1b\x1e\x8b\x1b\x1e\x8b\x1b\x1e\x8b\x1b\x1e'
|
||||||
|
thumbprint = hashlib.sha256(sorted_jwk.encode('utf-8')).digest()
|
||||||
|
|
||||||
|
# Encode the thumbprint in base64url format (RFC 4648)
|
||||||
|
# This removes padding characters (=) and replaces + and / with - and _
|
||||||
|
# Example final thumbprint: "xJ0dj8sbHosbHosbHosbHos"
|
||||||
|
thumbprint = base64.urlsafe_b64encode(thumbprint).decode('utf-8').rstrip('=')
|
||||||
|
|
||||||
|
# Combine token and key authorization
|
||||||
|
key_auth = f"{challenge['token']}.{thumbprint}"
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Key authorization: {key_auth}')
|
||||||
|
return key_auth
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error getting key authorization: {str(e)}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _verify_challenge(self, challenge_url):
|
||||||
|
"""Verify challenge completion with the ACME server
|
||||||
|
|
||||||
|
This function sends a POST request to the ACME server to verify that the challenge
|
||||||
|
has been completed successfully. The challenge URL is provided by the ACME server
|
||||||
|
when the challenge is created.
|
||||||
|
|
||||||
|
Example challenge_url:
|
||||||
|
"https://acme-v02.api.letsencrypt.org/acme/challenge/example.com/123456"
|
||||||
|
|
||||||
|
The verification process:
|
||||||
|
1. Creates an empty payload (POST-as-GET request)
|
||||||
|
2. Creates a JWS (JSON Web Signature) with the payload
|
||||||
|
3. Sends the request to the ACME server
|
||||||
|
4. Checks the response status
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if challenge is verified successfully, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Verifying challenge at URL: {challenge_url}')
|
||||||
|
|
||||||
|
# Create empty payload for POST-as-GET request
|
||||||
|
# This is a special type of request where we want to GET a resource
|
||||||
|
# but need to include a signature, so we use POST with an empty payload
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
# Create JWS (JSON Web Signature) for the request
|
||||||
|
# Example JWS might look like:
|
||||||
|
# {
|
||||||
|
# "protected": "eyJhbGciOiJSUzI1NiIsIm5vbmNlIjoiMTIzNDU2Nzg5MCIsInVybCI6Imh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2NoYWxsZW5nZS9leGFtcGxlLmNvbS8xMjM0NTYifQ",
|
||||||
|
# "signature": "c2lnbmF0dXJlX2hlcmU",
|
||||||
|
# "payload": ""
|
||||||
|
# }
|
||||||
|
jws = self._create_jws(payload, challenge_url)
|
||||||
|
if not jws:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to create JWS for challenge verification')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Sending challenge verification request...')
|
||||||
|
|
||||||
|
# Set headers for the request
|
||||||
|
# Content-Type: application/jose+json indicates we're sending a JWS
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/jose+json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send the verification request to the ACME server
|
||||||
|
# Example response might look like:
|
||||||
|
# {
|
||||||
|
# "type": "http-01",
|
||||||
|
# "status": "valid",
|
||||||
|
# "validated": "2024-03-20T12:00:00Z",
|
||||||
|
# "url": "https://acme-v02.api.letsencrypt.org/acme/challenge/example.com/123456"
|
||||||
|
# }
|
||||||
|
response = requests.post(challenge_url, data=jws, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Challenge verification response status: {response.status_code}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Challenge verification response: {response.text}')
|
||||||
|
|
||||||
|
# Check if the challenge was verified successfully
|
||||||
|
# Status code 200 indicates success
|
||||||
|
# The response will contain the challenge status and validation time
|
||||||
|
if response.status_code == 200:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Successfully verified challenge')
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error verifying challenge: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _finalize_order(self, csr):
|
||||||
|
"""Finalize order and get certificate"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Finalizing order...')
|
||||||
|
payload = {
|
||||||
|
"csr": base64.urlsafe_b64encode(csr).decode('utf-8').rstrip('=')
|
||||||
|
}
|
||||||
|
|
||||||
|
jws = self._create_jws(payload, self.finalize_url)
|
||||||
|
if not jws:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to create JWS for order finalization')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Sending order finalization request...')
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/jose+json'
|
||||||
|
}
|
||||||
|
response = requests.post(self.finalize_url, data=jws, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order finalization response status: {response.status_code}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order finalization response: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
# Wait for order to be processed
|
||||||
|
max_attempts = 30
|
||||||
|
delay = 2
|
||||||
|
for attempt in range(max_attempts):
|
||||||
|
if not self._get_nonce():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get nonce for order status check')
|
||||||
|
return False
|
||||||
|
|
||||||
|
response = requests.get(self.order_url, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order status check response: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
order_status = response.json().get('status')
|
||||||
|
if order_status == 'valid':
|
||||||
|
self.certificate_url = response.json().get('certificate')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully finalized order. Certificate URL: {self.certificate_url}')
|
||||||
|
return True
|
||||||
|
elif order_status == 'invalid':
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
|
||||||
|
return False
|
||||||
|
elif order_status == 'processing':
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order still processing, attempt {attempt + 1}/{max_attempts}')
|
||||||
|
time.sleep(delay)
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
|
||||||
|
time.sleep(delay)
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error finalizing order: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _download_certificate(self):
|
||||||
|
"""Download certificate from ACME server"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Downloading certificate...')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Certificate URL: {self.certificate_url}')
|
||||||
|
|
||||||
|
# For certificate downloads, we can use a simple GET request
|
||||||
|
response = requests.get(self.certificate_url)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response status: {response.status_code}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response headers: {response.headers}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Certificate download response content: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Successfully downloaded certificate')
|
||||||
|
return response.content
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error downloading certificate: {str(e)}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _wait_for_challenge_validation(self, challenge_url, max_attempts=30, delay=2):
|
||||||
|
"""Wait for challenge to be validated by the ACME server"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Waiting for challenge validation at URL: {challenge_url}')
|
||||||
|
for attempt in range(max_attempts):
|
||||||
|
if not self._get_nonce():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get nonce for challenge status check')
|
||||||
|
return False
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/jose+json'
|
||||||
|
}
|
||||||
|
response = requests.get(challenge_url, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Challenge status check response: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
challenge_status = response.json().get('status')
|
||||||
|
if challenge_status == 'valid':
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Challenge validated successfully')
|
||||||
|
return True
|
||||||
|
elif challenge_status == 'invalid':
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Challenge validation failed')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Challenge still pending, attempt {attempt + 1}/{max_attempts}')
|
||||||
|
time.sleep(delay)
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Challenge validation timed out')
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error waiting for challenge validation: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _check_dns_record(self, domain):
|
||||||
|
"""Check if a domain has valid DNS records
|
||||||
|
|
||||||
|
This function performs multiple DNS checks to ensure the domain has valid DNS records.
|
||||||
|
It includes:
|
||||||
|
1. A record (IPv4) check
|
||||||
|
2. AAAA record (IPv6) check
|
||||||
|
3. DNS caching prevention
|
||||||
|
4. Multiple DNS server checks
|
||||||
|
|
||||||
|
Args:
|
||||||
|
domain (str): The domain to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid DNS records are found, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Checking DNS records for domain: {domain}')
|
||||||
|
|
||||||
|
# List of public DNS servers to check against
|
||||||
|
dns_servers = [
|
||||||
|
'8.8.8.8', # Google DNS
|
||||||
|
'1.1.1.1', # Cloudflare DNS
|
||||||
|
'208.67.222.222' # OpenDNS
|
||||||
|
]
|
||||||
|
|
||||||
|
# Function to check DNS record with specific DNS server
|
||||||
|
def check_with_dns_server(server, record_type='A'):
|
||||||
|
try:
|
||||||
|
# Create a new socket for each check
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.settimeout(5) # 5 second timeout
|
||||||
|
|
||||||
|
# Set the DNS server
|
||||||
|
sock.connect((server, 53))
|
||||||
|
|
||||||
|
# Create DNS query
|
||||||
|
query = bytearray()
|
||||||
|
# DNS header
|
||||||
|
query += b'\x00\x01' # Transaction ID
|
||||||
|
query += b'\x01\x00' # Flags: Standard query
|
||||||
|
query += b'\x00\x01' # Questions: 1
|
||||||
|
query += b'\x00\x00' # Answer RRs: 0
|
||||||
|
query += b'\x00\x00' # Authority RRs: 0
|
||||||
|
query += b'\x00\x00' # Additional RRs: 0
|
||||||
|
|
||||||
|
# Domain name
|
||||||
|
for part in domain.split('.'):
|
||||||
|
query.append(len(part))
|
||||||
|
query.extend(part.encode())
|
||||||
|
query += b'\x00' # End of domain name
|
||||||
|
|
||||||
|
# Query type and class
|
||||||
|
if record_type == 'A':
|
||||||
|
query += b'\x00\x01' # Type: A
|
||||||
|
else: # AAAA
|
||||||
|
query += b'\x00\x1c' # Type: AAAA
|
||||||
|
query += b'\x00\x01' # Class: IN
|
||||||
|
|
||||||
|
# Send query
|
||||||
|
sock.send(query)
|
||||||
|
|
||||||
|
# Receive response
|
||||||
|
response = sock.recv(1024)
|
||||||
|
|
||||||
|
# Check if we got a valid response
|
||||||
|
if len(response) > 12: # Minimum DNS response size
|
||||||
|
# Check if there are answers in the response
|
||||||
|
answer_count = int.from_bytes(response[6:8], 'big')
|
||||||
|
if answer_count > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error checking DNS with server {server}: {str(e)}')
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
# Check A records (IPv4) with multiple DNS servers
|
||||||
|
a_record_found = False
|
||||||
|
for server in dns_servers:
|
||||||
|
if check_with_dns_server(server, 'A'):
|
||||||
|
a_record_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check AAAA records (IPv6) with multiple DNS servers
|
||||||
|
aaaa_record_found = False
|
||||||
|
for server in dns_servers:
|
||||||
|
if check_with_dns_server(server, 'AAAA'):
|
||||||
|
aaaa_record_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# Also check with system's DNS resolver as a fallback
|
||||||
|
try:
|
||||||
|
# Try to resolve A record (IPv4)
|
||||||
|
socket.gethostbyname(domain)
|
||||||
|
a_record_found = True
|
||||||
|
except socket.gaierror:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to resolve AAAA record (IPv6)
|
||||||
|
socket.getaddrinfo(domain, None, socket.AF_INET6)
|
||||||
|
aaaa_record_found = True
|
||||||
|
except socket.gaierror:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Log the results
|
||||||
|
if a_record_found:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'IPv4 DNS record found for domain: {domain}')
|
||||||
|
if aaaa_record_found:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'IPv6 DNS record found for domain: {domain}')
|
||||||
|
|
||||||
|
# Return True if either A or AAAA record is found
|
||||||
|
return a_record_found or aaaa_record_found
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error checking DNS records: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _wait_for_order_processing(self, max_attempts=30, delay=2):
|
||||||
|
"""Wait for order to be processed"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Waiting for order processing...')
|
||||||
|
for attempt in range(max_attempts):
|
||||||
|
if not self._get_nonce():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get nonce for order status check')
|
||||||
|
return False
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/jose+json'
|
||||||
|
}
|
||||||
|
response = requests.get(self.order_url, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order status check response: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
order_status = response.json().get('status')
|
||||||
|
if order_status == 'valid':
|
||||||
|
self.certificate_url = response.json().get('certificate')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Order validated successfully')
|
||||||
|
return True
|
||||||
|
elif order_status == 'invalid':
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
|
||||||
|
return False
|
||||||
|
elif order_status == 'processing':
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order still processing, attempt {attempt + 1}/{max_attempts}')
|
||||||
|
time.sleep(delay)
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
|
||||||
|
time.sleep(delay)
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error waiting for order processing: {str(e)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def issue_certificate(self, domains, use_dns=False):
|
||||||
|
"""Main method to issue certificate"""
|
||||||
|
try:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Starting certificate issuance for domains: {domains}, use_dns: {use_dns}')
|
||||||
|
|
||||||
|
# Try to load existing account key first
|
||||||
|
if self._load_account_key():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using existing account key')
|
||||||
|
else:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('No existing account key found, will create new one')
|
||||||
|
|
||||||
|
# Filter domains to only include those with valid DNS records
|
||||||
|
valid_domains = []
|
||||||
|
for domain in domains:
|
||||||
|
if self._check_dns_record(domain):
|
||||||
|
valid_domains.append(domain)
|
||||||
|
else:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Skipping domain {domain} due to missing DNS records')
|
||||||
|
|
||||||
|
if not valid_domains:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('No valid domains found with DNS records')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Initialize ACME
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 1: Generating account key')
|
||||||
|
if not self._generate_account_key():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to generate account key')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 2: Getting ACME directory')
|
||||||
|
if not self._get_directory():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get ACME directory')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 3: Getting nonce')
|
||||||
|
if not self._get_nonce():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get nonce')
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 4: Creating account')
|
||||||
|
if not self._create_account():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to create account')
|
||||||
|
# If we failed to create account and we're not in staging, try staging
|
||||||
|
if not self.staging:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Switching to staging environment...')
|
||||||
|
self.staging = True
|
||||||
|
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
if not self._get_directory():
|
||||||
|
return False
|
||||||
|
if not self._get_nonce():
|
||||||
|
return False
|
||||||
|
if not self._create_account():
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Create order with only valid domains
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 5: Creating order')
|
||||||
|
if not self._create_order(valid_domains):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to create order')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Handle challenges
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 6: Handling challenges')
|
||||||
|
for auth_url in self.authorizations:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Processing authorization URL: {auth_url}')
|
||||||
|
if not self._get_nonce():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get nonce for authorization')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get authorization details with GET request
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/jose+json'
|
||||||
|
}
|
||||||
|
response = requests.get(auth_url, headers=headers)
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Authorization response status: {response.status_code}')
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Authorization response: {response.text}')
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to get authorization')
|
||||||
|
return False
|
||||||
|
|
||||||
|
challenges = response.json()['challenges']
|
||||||
|
for challenge in challenges:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Processing challenge: {json.dumps(challenge)}')
|
||||||
|
|
||||||
|
# Only handle the challenge type we're using
|
||||||
|
if use_dns and challenge['type'] == 'dns-01':
|
||||||
|
if not self._handle_dns_challenge(challenge):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to handle DNS challenge')
|
||||||
|
return False
|
||||||
|
if not self._verify_challenge(challenge['url']):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to verify DNS challenge')
|
||||||
|
return False
|
||||||
|
if not self._wait_for_challenge_validation(challenge['url']):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('DNS challenge validation failed')
|
||||||
|
return False
|
||||||
|
elif not use_dns and challenge['type'] == 'http-01':
|
||||||
|
if not self._handle_http_challenge(challenge):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to handle HTTP challenge')
|
||||||
|
return False
|
||||||
|
if not self._verify_challenge(challenge['url']):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to verify HTTP challenge')
|
||||||
|
return False
|
||||||
|
if not self._wait_for_challenge_validation(challenge['url']):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('HTTP challenge validation failed')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Skipping {challenge["type"]} challenge')
|
||||||
|
|
||||||
|
# Generate CSR
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 7: Generating CSR')
|
||||||
|
key = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=2048,
|
||||||
|
backend=default_backend()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the domain from the order response
|
||||||
|
order_response = requests.get(self.order_url, headers=headers).json()
|
||||||
|
order_domains = [identifier['value'] for identifier in order_response['identifiers']]
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Order domains: {order_domains}')
|
||||||
|
|
||||||
|
# Create CSR with exactly the domains from the order
|
||||||
|
csr = x509.CertificateSigningRequestBuilder().subject_name(
|
||||||
|
x509.Name([
|
||||||
|
x509.NameAttribute(x509.NameOID.COMMON_NAME, order_domains[0])
|
||||||
|
])
|
||||||
|
).add_extension(
|
||||||
|
x509.SubjectAlternativeName([
|
||||||
|
x509.DNSName(domain) for domain in order_domains
|
||||||
|
]),
|
||||||
|
critical=False
|
||||||
|
).sign(key, hashes.SHA256(), default_backend())
|
||||||
|
|
||||||
|
# Finalize order
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 8: Finalizing order')
|
||||||
|
if not self._finalize_order(csr.public_bytes(serialization.Encoding.DER)):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to finalize order')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Wait for order processing
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 9: Waiting for order processing')
|
||||||
|
if not self._wait_for_order_processing():
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to process order')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Download certificate
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 10: Downloading certificate')
|
||||||
|
certificate = self._download_certificate()
|
||||||
|
if not certificate:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Failed to download certificate')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Save certificate
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Step 11: Saving certificate')
|
||||||
|
if not os.path.exists(self.cert_path):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Creating certificate directory: {self.cert_path}')
|
||||||
|
os.makedirs(self.cert_path)
|
||||||
|
|
||||||
|
cert_file = os.path.join(self.cert_path, 'fullchain.pem')
|
||||||
|
key_file = os.path.join(self.cert_path, 'privkey.pem')
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Saving certificate to: {cert_file}')
|
||||||
|
with open(cert_file, 'wb') as f:
|
||||||
|
f.write(certificate)
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Saving private key to: {key_file}')
|
||||||
|
with open(key_file, 'wb') as f:
|
||||||
|
f.write(key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.PKCS8,
|
||||||
|
encryption_algorithm=serialization.NoEncryption()
|
||||||
|
))
|
||||||
|
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Successfully completed certificate issuance')
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error issuing certificate: {str(e)}')
|
||||||
|
return False
|
||||||
@@ -16,23 +16,30 @@ from plogical import CyberCPLogFileWriter as logging
|
|||||||
from plogical.processUtilities import ProcessUtilities
|
from plogical.processUtilities import ProcessUtilities
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
|
||||||
class CustomACME:
|
class CustomACME:
|
||||||
def __init__(self, domain, admin_email, staging=False):
|
def __init__(self, domain, admin_email, staging=False, provider='letsencrypt'):
|
||||||
"""Initialize CustomACME"""
|
"""Initialize CustomACME"""
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Initializing CustomACME for domain: {domain}, email: {admin_email}, staging: {staging}, provider: {provider}')
|
||||||
f'Initializing CustomACME for domain: {domain}, email: {admin_email}, staging: {staging}')
|
|
||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.admin_email = admin_email
|
self.admin_email = admin_email
|
||||||
self.staging = staging
|
self.staging = staging
|
||||||
|
self.provider = provider
|
||||||
|
|
||||||
# Set the ACME directory URL based on staging flag
|
# Set the ACME directory URL based on provider and staging flag
|
||||||
|
if provider == 'zerossl':
|
||||||
|
if staging:
|
||||||
|
self.acme_directory = "https://acme-staging.zerossl.com/v2/DV90"
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using ZeroSSL staging ACME directory')
|
||||||
|
else:
|
||||||
|
self.acme_directory = "https://acme.zerossl.com/v2/DV90"
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile('Using ZeroSSL production ACME directory')
|
||||||
|
else: # letsencrypt
|
||||||
if staging:
|
if staging:
|
||||||
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
logging.CyberCPLogFileWriter.writeToFile('Using staging ACME directory')
|
logging.CyberCPLogFileWriter.writeToFile('Using Let\'s Encrypt staging ACME directory')
|
||||||
else:
|
else:
|
||||||
self.acme_directory = "https://acme-v02.api.letsencrypt.org/directory"
|
self.acme_directory = "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
logging.CyberCPLogFileWriter.writeToFile('Using production ACME directory')
|
logging.CyberCPLogFileWriter.writeToFile('Using Let\'s Encrypt production ACME directory')
|
||||||
|
|
||||||
self.account_key = None
|
self.account_key = None
|
||||||
self.account_url = None
|
self.account_url = None
|
||||||
@@ -47,8 +54,7 @@ class CustomACME:
|
|||||||
self.cert_path = f'/etc/letsencrypt/live/{domain}'
|
self.cert_path = f'/etc/letsencrypt/live/{domain}'
|
||||||
self.challenge_path = '/usr/local/lsws/Example/html/.well-known/acme-challenge'
|
self.challenge_path = '/usr/local/lsws/Example/html/.well-known/acme-challenge'
|
||||||
self.account_key_path = f'/etc/letsencrypt/accounts/{domain}.key'
|
self.account_key_path = f'/etc/letsencrypt/accounts/{domain}.key'
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Certificate path: {self.cert_path}, Challenge path: {self.challenge_path}')
|
||||||
f'Certificate path: {self.cert_path}, Challenge path: {self.challenge_path}')
|
|
||||||
|
|
||||||
# Create accounts directory if it doesn't exist
|
# Create accounts directory if it doesn't exist
|
||||||
os.makedirs('/etc/letsencrypt/accounts', exist_ok=True)
|
os.makedirs('/etc/letsencrypt/accounts', exist_ok=True)
|
||||||
@@ -75,8 +81,7 @@ class CustomACME:
|
|||||||
logging.CyberCPLogFileWriter.writeToFile(f'Fetching ACME directory from {self.acme_directory}')
|
logging.CyberCPLogFileWriter.writeToFile(f'Fetching ACME directory from {self.acme_directory}')
|
||||||
response = requests.get(self.acme_directory)
|
response = requests.get(self.acme_directory)
|
||||||
self.directory = response.json()
|
self.directory = response.json()
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully fetched ACME directory: {json.dumps(self.directory)}')
|
||||||
f'Successfully fetched ACME directory: {json.dumps(self.directory)}')
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.CyberCPLogFileWriter.writeToFile(f'Error getting directory: {str(e)}')
|
logging.CyberCPLogFileWriter.writeToFile(f'Error getting directory: {str(e)}')
|
||||||
@@ -254,14 +259,12 @@ class CustomACME:
|
|||||||
|
|
||||||
if response.status_code == 201:
|
if response.status_code == 201:
|
||||||
self.account_url = response.headers['Location']
|
self.account_url = response.headers['Location']
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully created account. Account URL: {self.account_url}')
|
||||||
f'Successfully created account. Account URL: {self.account_url}')
|
|
||||||
# Save the account key for future use
|
# Save the account key for future use
|
||||||
self._save_account_key()
|
self._save_account_key()
|
||||||
return True
|
return True
|
||||||
elif response.status_code == 429:
|
elif response.status_code == 429:
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile('Rate limit hit for account creation. Using staging environment...')
|
||||||
'Rate limit hit for account creation. Using staging environment...')
|
|
||||||
self.staging = True
|
self.staging = True
|
||||||
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
self.acme_directory = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
# Get new directory and nonce for staging
|
# Get new directory and nonce for staging
|
||||||
@@ -517,20 +520,17 @@ class CustomACME:
|
|||||||
order_status = response.json().get('status')
|
order_status = response.json().get('status')
|
||||||
if order_status == 'valid':
|
if order_status == 'valid':
|
||||||
self.certificate_url = response.json().get('certificate')
|
self.certificate_url = response.json().get('certificate')
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully finalized order. Certificate URL: {self.certificate_url}')
|
||||||
f'Successfully finalized order. Certificate URL: {self.certificate_url}')
|
|
||||||
return True
|
return True
|
||||||
elif order_status == 'invalid':
|
elif order_status == 'invalid':
|
||||||
logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
|
logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
|
||||||
return False
|
return False
|
||||||
elif order_status == 'processing':
|
elif order_status == 'processing':
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Order still processing, attempt {attempt + 1}/{max_attempts}')
|
||||||
f'Order still processing, attempt {attempt + 1}/{max_attempts}')
|
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
|
||||||
f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
|
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
|
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
|
||||||
@@ -584,8 +584,7 @@ class CustomACME:
|
|||||||
logging.CyberCPLogFileWriter.writeToFile('Challenge validation failed')
|
logging.CyberCPLogFileWriter.writeToFile('Challenge validation failed')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Challenge still pending, attempt {attempt + 1}/{max_attempts}')
|
||||||
f'Challenge still pending, attempt {attempt + 1}/{max_attempts}')
|
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
logging.CyberCPLogFileWriter.writeToFile('Challenge validation timed out')
|
logging.CyberCPLogFileWriter.writeToFile('Challenge validation timed out')
|
||||||
@@ -740,13 +739,11 @@ class CustomACME:
|
|||||||
logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
|
logging.CyberCPLogFileWriter.writeToFile('Order validation failed')
|
||||||
return False
|
return False
|
||||||
elif order_status == 'processing':
|
elif order_status == 'processing':
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Order still processing, attempt {attempt + 1}/{max_attempts}')
|
||||||
f'Order still processing, attempt {attempt + 1}/{max_attempts}')
|
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
|
||||||
f'Order status check failed, attempt {attempt + 1}/{max_attempts}')
|
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
|
logging.CyberCPLogFileWriter.writeToFile('Order processing timed out')
|
||||||
@@ -758,8 +755,7 @@ class CustomACME:
|
|||||||
def issue_certificate(self, domains, use_dns=False):
|
def issue_certificate(self, domains, use_dns=False):
|
||||||
"""Main method to issue certificate"""
|
"""Main method to issue certificate"""
|
||||||
try:
|
try:
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Starting certificate issuance for domains: {domains}, use_dns: {use_dns}')
|
||||||
f'Starting certificate issuance for domains: {domains}, use_dns: {use_dns}')
|
|
||||||
|
|
||||||
# Try to load existing account key first
|
# Try to load existing account key first
|
||||||
if self._load_account_key():
|
if self._load_account_key():
|
||||||
|
|||||||
@@ -169,27 +169,31 @@ class ProcessUtilities(multi.Thread):
|
|||||||
distroPath = '/etc/lsb-release'
|
distroPath = '/etc/lsb-release'
|
||||||
distroPathAlma = '/etc/redhat-release'
|
distroPathAlma = '/etc/redhat-release'
|
||||||
|
|
||||||
if os.path.exists(distroPath):
|
# First check if we're on Ubuntu
|
||||||
|
if os.path.exists('/etc/os-release'):
|
||||||
## this is check only
|
with open('/etc/os-release', 'r') as f:
|
||||||
if open(distroPath, 'r').read().find('22.04') > -1:
|
content = f.read()
|
||||||
|
if 'Ubuntu' in content:
|
||||||
|
if '22.04' in content:
|
||||||
ProcessUtilities.ubuntu22Check = 1
|
ProcessUtilities.ubuntu22Check = 1
|
||||||
|
return ProcessUtilities.ubuntu20
|
||||||
if open(distroPath, 'r').read().find('20.04') > -1 or open(distroPath, 'r').read().find('22.04'):
|
elif '20.04' in content:
|
||||||
return ProcessUtilities.ubuntu20
|
return ProcessUtilities.ubuntu20
|
||||||
return ProcessUtilities.ubuntu
|
return ProcessUtilities.ubuntu
|
||||||
else:
|
|
||||||
if open('/etc/redhat-release', 'r').read().find('CentOS Linux release 8') > -1 or open('/etc/redhat-release', 'r').read().find('AlmaLinux release 8') > -1 \
|
# Check for RedHat-based distributions
|
||||||
or open('/etc/redhat-release', 'r').read().find('Rocky Linux release 8') > -1 \
|
if os.path.exists(distroPathAlma):
|
||||||
or open('/etc/redhat-release', 'r').read().find('Rocky Linux release 9') > -1 or open('/etc/redhat-release', 'r').read().find('AlmaLinux release 9') > -1 or \
|
with open(distroPathAlma, 'r') as f:
|
||||||
open('/etc/redhat-release', 'r').read().find('CloudLinux release 9') > -1 or open('/etc/redhat-release', 'r').read().find('CloudLinux release 8') > -1:
|
content = f.read()
|
||||||
## this is check only
|
if any(x in content for x in ['CentOS Linux release 8', 'AlmaLinux release 8', 'Rocky Linux release 8',
|
||||||
if open(distroPathAlma, 'r').read().find('AlmaLinux release 9') > -1 or open(distroPathAlma, 'r').read().find('Rocky Linux release 9') > -1:
|
'Rocky Linux release 9', 'AlmaLinux release 9', 'CloudLinux release 9',
|
||||||
|
'CloudLinux release 8']):
|
||||||
|
if any(x in content for x in ['AlmaLinux release 9', 'Rocky Linux release 9']):
|
||||||
ProcessUtilities.alma9check = 1
|
ProcessUtilities.alma9check = 1
|
||||||
|
|
||||||
return ProcessUtilities.cent8
|
return ProcessUtilities.cent8
|
||||||
return ProcessUtilities.centos
|
|
||||||
|
|
||||||
|
# Default to Ubuntu if no other distribution is detected
|
||||||
|
return ProcessUtilities.ubuntu
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def containerCheck():
|
def containerCheck():
|
||||||
|
|||||||
@@ -3,150 +3,113 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import django
|
import django
|
||||||
|
from typing import Union, Optional
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import time
|
||||||
|
|
||||||
sys.path.append('/usr/local/CyberCP')
|
sys.path.append('/usr/local/CyberCP')
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
|
||||||
django.setup()
|
django.setup()
|
||||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||||
from websiteFunctions.models import Websites, ChildDomains
|
from websiteFunctions.models import Websites, ChildDomains
|
||||||
from os import path
|
|
||||||
from datetime import datetime
|
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
from plogical.virtualHostUtilities import virtualHostUtilities
|
from plogical.virtualHostUtilities import virtualHostUtilities
|
||||||
|
from plogical.processUtilities import ProcessUtilities
|
||||||
|
|
||||||
class Renew:
|
class Renew:
|
||||||
|
def _check_and_renew_ssl(self, domain: str, path: str, admin_email: str, is_child: bool = False) -> None:
|
||||||
|
"""Helper method to check and renew SSL for a domain."""
|
||||||
|
try:
|
||||||
|
logging.writeToFile(f'Checking SSL for {domain}.', 0)
|
||||||
|
file_path = f'/etc/letsencrypt/live/{domain}/fullchain.pem'
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
logging.writeToFile(f'SSL does not exist for {domain}. Obtaining now..', 0)
|
||||||
|
virtualHostUtilities.issueSSL(domain, path, admin_email)
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.writeToFile(f'SSL exists for {domain}. Checking if SSL will expire in 15 days..', 0)
|
||||||
|
|
||||||
|
with open(file_path, 'r') as cert_file:
|
||||||
|
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_file.read())
|
||||||
|
|
||||||
|
expire_data = x509.get_notAfter().decode('ascii')
|
||||||
|
final_date = datetime.strptime(expire_data, '%Y%m%d%H%M%SZ')
|
||||||
|
now = datetime.now()
|
||||||
|
diff = final_date - now
|
||||||
|
|
||||||
|
ssl_provider = x509.get_issuer().get_components()[1][1].decode('utf-8')
|
||||||
|
logging.writeToFile(f'Provider: {ssl_provider}, Days until expiration: {diff.days}', 0)
|
||||||
|
|
||||||
|
if diff.days >= 15 and ssl_provider != 'Denial':
|
||||||
|
logging.writeToFile(f'SSL exists for {domain} and is not ready to renew, skipping..', 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
if ssl_provider == 'Denial' or ssl_provider == "Let's Encrypt":
|
||||||
|
logging.writeToFile(f'SSL exists for {domain} and ready to renew..', 0)
|
||||||
|
logging.writeToFile(f'Renewing SSL for {domain}..', 0)
|
||||||
|
virtualHostUtilities.issueSSL(domain, path, admin_email)
|
||||||
|
elif ssl_provider != "Let's Encrypt":
|
||||||
|
logging.writeToFile(f'Custom SSL exists for {domain} and ready to renew..', 1)
|
||||||
|
|
||||||
|
except OpenSSL.crypto.Error as e:
|
||||||
|
logging.writeToFile(f'OpenSSL error for {domain}: {str(e)}', 1)
|
||||||
|
except Exception as e:
|
||||||
|
logging.writeToFile(f'Error processing SSL for {domain}: {str(e)}', 1)
|
||||||
|
|
||||||
|
def _restart_services(self) -> None:
|
||||||
|
"""Helper method to restart required services."""
|
||||||
|
try:
|
||||||
|
logging.writeToFile('Restarting mail services for them to see new SSL.', 0)
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
'postmap -F hash:/etc/postfix/vmail_ssl.map',
|
||||||
|
'systemctl restart postfix',
|
||||||
|
'systemctl restart dovecot',
|
||||||
|
'systemctl restart lscpd'
|
||||||
|
]
|
||||||
|
|
||||||
|
for cmd in commands:
|
||||||
|
ProcessUtilities.normalExecutioner(cmd)
|
||||||
|
# Add a small delay between restarts
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.writeToFile(f'Error restarting services: {str(e)}', 1)
|
||||||
|
|
||||||
def SSLObtainer(self):
|
def SSLObtainer(self):
|
||||||
try:
|
try:
|
||||||
logging.writeToFile('Running SSL Renew Utility')
|
logging.writeToFile('Running SSL Renew Utility')
|
||||||
|
|
||||||
## For Non-suspended websites only
|
# Process main domains
|
||||||
|
|
||||||
for website in Websites.objects.filter(state=1):
|
for website in Websites.objects.filter(state=1):
|
||||||
logging.writeToFile('Checking SSL for %s.' % (website.domain), 0)
|
self._check_and_renew_ssl(
|
||||||
filePath = '/etc/letsencrypt/live/%s/fullchain.pem' % (website.domain)
|
website.domain,
|
||||||
|
f'/home/{website.domain}/public_html',
|
||||||
|
website.adminEmail
|
||||||
|
)
|
||||||
|
|
||||||
if path.exists(filePath):
|
# Process child domains
|
||||||
logging.writeToFile('SSL exists for %s. Checking if SSL will expire in 15 days..' % (website.domain), 0)
|
for child in ChildDomains.objects.all():
|
||||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
self._check_and_renew_ssl(
|
||||||
open(filePath, 'r').read())
|
child.domain,
|
||||||
expireData = x509.get_notAfter().decode('ascii')
|
child.path,
|
||||||
finalDate = datetime.strptime(expireData, '%Y%m%d%H%M%SZ')
|
child.master.adminEmail,
|
||||||
now = datetime.now()
|
is_child=True
|
||||||
diff = finalDate - now
|
)
|
||||||
|
|
||||||
SSLProvider = x509.get_issuer().get_components()[1][1].decode('utf-8')
|
self._restart_services()
|
||||||
|
|
||||||
print(f"Provider: {x509.get_issuer().get_components()[1][1].decode('utf-8')}, Days : {diff.days}")
|
except Exception as e:
|
||||||
|
logging.writeToFile(f'Error in SSLObtainer: {str(e)}', 1)
|
||||||
if int(diff.days) >= 15 and SSLProvider!='Denial':
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL exists for %s and is not ready to renew, skipping..' % (website.domain), 0)
|
|
||||||
print(
|
|
||||||
f'SSL exists for %s and is not ready to renew, skipping..' % (website.domain))
|
|
||||||
elif SSLProvider == 'Denial':
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL exists for %s and ready to renew..' % (website.domain), 0)
|
|
||||||
logging.writeToFile(
|
|
||||||
'Renewing SSL for %s..' % (website.domain), 0)
|
|
||||||
|
|
||||||
print(
|
|
||||||
f'SSL exists for %s and ready to renew..' % (website.domain))
|
|
||||||
|
|
||||||
virtualHostUtilities.issueSSL(website.domain, '/home/%s/public_html' % (website.domain),
|
|
||||||
website.adminEmail)
|
|
||||||
elif SSLProvider != "Let's Encrypt":
|
|
||||||
logging.writeToFile(
|
|
||||||
'Custom SSL exists for %s and ready to renew..' % (website.domain), 1)
|
|
||||||
print(
|
|
||||||
'Custom SSL exists for %s and ready to renew..' % (website.domain))
|
|
||||||
else:
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL exists for %s and ready to renew..' % (website.domain), 0)
|
|
||||||
logging.writeToFile(
|
|
||||||
'Renewing SSL for %s..' % (website.domain), 0)
|
|
||||||
|
|
||||||
print(
|
|
||||||
'SSL exists for %s and ready to renew..' % (website.domain))
|
|
||||||
|
|
||||||
|
|
||||||
virtualHostUtilities.issueSSL(website.domain, '/home/%s/public_html' % (website.domain), website.adminEmail)
|
|
||||||
else:
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL does not exist for %s. Obtaining now..' % (website.domain), 0)
|
|
||||||
virtualHostUtilities.issueSSL(website.domain, '/home/%s/public_html' % (website.domain),
|
|
||||||
website.adminEmail)
|
|
||||||
|
|
||||||
## For child-domains
|
|
||||||
|
|
||||||
for website in ChildDomains.objects.all():
|
|
||||||
logging.writeToFile('Checking SSL for %s.' % (website.domain), 0)
|
|
||||||
filePath = '/etc/letsencrypt/live/%s/fullchain.pem' % (website.domain)
|
|
||||||
|
|
||||||
if path.exists(filePath):
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL exists for %s. Checking if SSL will expire in 15 days..' % (website.domain), 0)
|
|
||||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
|
||||||
open(filePath, 'r').read())
|
|
||||||
expireData = x509.get_notAfter().decode('ascii')
|
|
||||||
finalDate = datetime.strptime(expireData, '%Y%m%d%H%M%SZ')
|
|
||||||
now = datetime.now()
|
|
||||||
diff = finalDate - now
|
|
||||||
|
|
||||||
SSLProvider = x509.get_issuer().get_components()[1][1]
|
|
||||||
|
|
||||||
print(f"Provider: {x509.get_issuer().get_components()[1][1].decode('utf-8')}, Days : {diff.days}")
|
|
||||||
|
|
||||||
if int(diff.days) >= 15 and SSLProvider != 'Denial':
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL exists for %s and is not ready to renew, skipping..' % (website.domain), 0)
|
|
||||||
elif SSLProvider == 'Denial':
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL exists for %s and ready to renew..' % (website.domain), 0)
|
|
||||||
logging.writeToFile(
|
|
||||||
'Renewing SSL for %s..' % (website.domain), 0)
|
|
||||||
|
|
||||||
virtualHostUtilities.issueSSL(website.domain, website.path,
|
|
||||||
website.master.adminEmail)
|
|
||||||
elif SSLProvider != "Let's Encrypt":
|
|
||||||
logging.writeToFile(
|
|
||||||
'Custom SSL exists for %s and ready to renew..' % (website.domain), 1)
|
|
||||||
else:
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL exists for %s and ready to renew..' % (website.domain), 0)
|
|
||||||
logging.writeToFile(
|
|
||||||
'Renewing SSL for %s..' % (website.domain), 0)
|
|
||||||
|
|
||||||
virtualHostUtilities.issueSSL(website.domain, website.path,
|
|
||||||
website.master.adminEmail)
|
|
||||||
else:
|
|
||||||
logging.writeToFile(
|
|
||||||
'SSL does not exist for %s. Obtaining now..' % (website.domain), 0)
|
|
||||||
virtualHostUtilities.issueSSL(website.domain, website.path,
|
|
||||||
website.master.adminEmail)
|
|
||||||
|
|
||||||
self.file = logging.writeToFile('Restarting mail services for them to see new SSL.', 0)
|
|
||||||
|
|
||||||
from plogical.processUtilities import ProcessUtilities
|
|
||||||
command = 'postmap -F hash:/etc/postfix/vmail_ssl.map'
|
|
||||||
ProcessUtilities.normalExecutioner(command)
|
|
||||||
|
|
||||||
command = 'systemctl restart postfix'
|
|
||||||
ProcessUtilities.normalExecutioner(command)
|
|
||||||
|
|
||||||
command = 'systemctl restart dovecot'
|
|
||||||
ProcessUtilities.normalExecutioner(command)
|
|
||||||
|
|
||||||
command = 'systemctl restart lscpd'
|
|
||||||
ProcessUtilities.normalExecutioner(command)
|
|
||||||
|
|
||||||
except BaseException as msg:
|
|
||||||
logging.writeToFile(str(msg) + '. Renew.SSLObtainer')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def FixMailSSL():
|
def FixMailSSL():
|
||||||
|
try:
|
||||||
for website in Websites.objects.all():
|
for website in Websites.objects.all():
|
||||||
virtualHostUtilities.setupAutoDiscover(1, '/home/cyberpanel/templogs', website.domain, website.admin)
|
virtualHostUtilities.setupAutoDiscover(1, '/home/cyberpanel/templogs', website.domain, website.admin)
|
||||||
|
except Exception as e:
|
||||||
|
logging.writeToFile(f'Error in FixMailSSL: {str(e)}', 1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sslOB = Renew()
|
sslOB = Renew()
|
||||||
|
|||||||
@@ -233,8 +233,7 @@ class sslUtilities:
|
|||||||
# If conflicts found, log them and return
|
# If conflicts found, log them and return
|
||||||
if conflicts:
|
if conflicts:
|
||||||
conflict_message = 'Configuration conflicts found: ' + '; '.join(conflicts)
|
conflict_message = 'Configuration conflicts found: ' + '; '.join(conflicts)
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Configuration conflicts for {virtualHostName}: {conflict_message}')
|
||||||
f'Configuration conflicts for {virtualHostName}: {conflict_message}')
|
|
||||||
return 0, conflict_message
|
return 0, conflict_message
|
||||||
|
|
||||||
# Create challenge directory if it doesn't exist
|
# Create challenge directory if it doesn't exist
|
||||||
@@ -281,8 +280,7 @@ context /.well-known/acme-challenge {
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
f.write(line)
|
f.write(line)
|
||||||
if line.find('DocumentRoot /home/') > -1 and check == 0:
|
if line.find('DocumentRoot /home/') > -1 and check == 0:
|
||||||
f.write(
|
f.write(' Alias /.well-known/acme-challenge /usr/local/lsws/Example/html/.well-known/acme-challenge\n')
|
||||||
' Alias /.well-known/acme-challenge /usr/local/lsws/Example/html/.well-known/acme-challenge\n')
|
|
||||||
check = 1
|
check = 1
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logging.CyberCPLogFileWriter.writeToFile(f'Error writing Apache configuration: {str(e)}')
|
logging.CyberCPLogFileWriter.writeToFile(f'Error writing Apache configuration: {str(e)}')
|
||||||
@@ -292,8 +290,7 @@ context /.well-known/acme-challenge {
|
|||||||
try:
|
try:
|
||||||
from plogical import installUtilities
|
from plogical import installUtilities
|
||||||
installUtilities.installUtilities.reStartLiteSpeed()
|
installUtilities.installUtilities.reStartLiteSpeed()
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(f'Successfully configured ACME challenge for {virtualHostName}')
|
||||||
f'Successfully configured ACME challenge for {virtualHostName}')
|
|
||||||
return 1, 'Successfully configured ACME challenge'
|
return 1, 'Successfully configured ACME challenge'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.CyberCPLogFileWriter.writeToFile(f'Error restarting LiteSpeed: {str(e)}')
|
logging.CyberCPLogFileWriter.writeToFile(f'Error restarting LiteSpeed: {str(e)}')
|
||||||
@@ -578,7 +575,7 @@ context /.well-known/acme-challenge {
|
|||||||
command = f'chmod -R 755 /usr/local/lsws/Example/html'
|
command = f'chmod -R 755 /usr/local/lsws/Example/html'
|
||||||
ProcessUtilities.executioner(command)
|
ProcessUtilities.executioner(command)
|
||||||
|
|
||||||
# Try custom ACME implementation first
|
# Try Let's Encrypt first
|
||||||
try:
|
try:
|
||||||
domains = [virtualHostName, f'www.{virtualHostName}']
|
domains = [virtualHostName, f'www.{virtualHostName}']
|
||||||
if aliasDomain:
|
if aliasDomain:
|
||||||
@@ -593,16 +590,31 @@ context /.well-known/acme-challenge {
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
acme = CustomACME(virtualHostName, adminEmail, staging=False) # Force production environment
|
acme = CustomACME(virtualHostName, adminEmail, staging=False, provider='letsencrypt')
|
||||||
if acme.issue_certificate(domains, use_dns=use_dns):
|
if acme.issue_certificate(domains, use_dns=use_dns):
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(
|
||||||
f"Successfully obtained SSL using custom ACME implementation for: {virtualHostName}")
|
f"Successfully obtained SSL using Let's Encrypt for: {virtualHostName}")
|
||||||
return 1
|
return 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(
|
||||||
f"Custom ACME implementation failed: {str(e)}. Falling back to acme.sh")
|
f"Let's Encrypt failed: {str(e)}. Trying ZeroSSL...")
|
||||||
|
|
||||||
# Fallback to acme.sh if custom implementation fails
|
# Try ZeroSSL if Let's Encrypt fails
|
||||||
|
try:
|
||||||
|
domains = [virtualHostName, f'www.{virtualHostName}']
|
||||||
|
if aliasDomain:
|
||||||
|
domains.extend([aliasDomain, f'www.{aliasDomain}'])
|
||||||
|
|
||||||
|
acme = CustomACME(virtualHostName, adminEmail, staging=False, provider='zerossl')
|
||||||
|
if acme.issue_certificate(domains, use_dns=use_dns):
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(
|
||||||
|
f"Successfully obtained SSL using ZeroSSL for: {virtualHostName}")
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(
|
||||||
|
f"ZeroSSL failed: {str(e)}. Falling back to acme.sh")
|
||||||
|
|
||||||
|
# Fallback to acme.sh if both ACME providers fail
|
||||||
try:
|
try:
|
||||||
acmePath = '/root/.acme.sh/acme.sh'
|
acmePath = '/root/.acme.sh/acme.sh'
|
||||||
command = '%s --register-account -m %s' % (acmePath, adminEmail)
|
command = '%s --register-account -m %s' % (acmePath, adminEmail)
|
||||||
@@ -625,8 +637,7 @@ context /.well-known/acme-challenge {
|
|||||||
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
|
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
|
||||||
result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True)
|
result = subprocess.run(command, capture_output=True, universal_newlines=True, shell=True)
|
||||||
else:
|
else:
|
||||||
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True)
|
||||||
universal_newlines=True, shell=True)
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
command = acmePath + " --issue -d " + virtualHostName + " -d www." + virtualHostName \
|
command = acmePath + " --issue -d " + virtualHostName + " -d www." + virtualHostName \
|
||||||
|
|||||||
@@ -2207,6 +2207,10 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
|
|||||||
if not Upgrade.executioner(command, command, 1):
|
if not Upgrade.executioner(command, command, 1):
|
||||||
return 0, 'Failed to execute %s' % (command)
|
return 0, 'Failed to execute %s' % (command)
|
||||||
|
|
||||||
|
command = 'git clean -f'
|
||||||
|
if not Upgrade.executioner(command, command, 1):
|
||||||
|
return 0, 'Failed to execute %s' % (command)
|
||||||
|
|
||||||
command = 'git pull'
|
command = 'git pull'
|
||||||
if not Upgrade.executioner(command, command, 1):
|
if not Upgrade.executioner(command, command, 1):
|
||||||
return 0, 'Failed to execute %s' % (command)
|
return 0, 'Failed to execute %s' % (command)
|
||||||
@@ -3401,6 +3405,25 @@ pm.max_spare_servers = 3
|
|||||||
WriteToFile.write(content)
|
WriteToFile.write(content)
|
||||||
WriteToFile.close()
|
WriteToFile.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def setupPHPSymlink():
|
||||||
|
try:
|
||||||
|
# Remove existing PHP symlink if it exists
|
||||||
|
if os.path.exists('/usr/bin/php'):
|
||||||
|
os.remove('/usr/bin/php')
|
||||||
|
|
||||||
|
# Create symlink to PHP 8.0
|
||||||
|
command = 'ln -s /usr/local/lsws/lsphp80/bin/php /usr/bin/php'
|
||||||
|
Upgrade.executioner(command, 'Setup PHP Symlink', 0)
|
||||||
|
|
||||||
|
Upgrade.stdOut("PHP symlink created successfully.")
|
||||||
|
|
||||||
|
except BaseException as msg:
|
||||||
|
Upgrade.stdOut('[ERROR] ' + str(msg) + " [setupPHPSymlink]")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def upgrade(branch):
|
def upgrade(branch):
|
||||||
|
|
||||||
@@ -3464,6 +3487,7 @@ pm.max_spare_servers = 3
|
|||||||
Upgrade.executioner(command, 'tmp adjustment', 0)
|
Upgrade.executioner(command, 'tmp adjustment', 0)
|
||||||
|
|
||||||
Upgrade.dockerUsers()
|
Upgrade.dockerUsers()
|
||||||
|
Upgrade.setupPHPSymlink()
|
||||||
Upgrade.setupComposer()
|
Upgrade.setupComposer()
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
143
static/websiteFunctions/js/resource-monitoring.js
Normal file
143
static/websiteFunctions/js/resource-monitoring.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// Resource Monitoring
|
||||||
|
let cpuChart, memoryChart, diskChart;
|
||||||
|
let cpuData = [], memoryData = [], diskData = [];
|
||||||
|
const maxDataPoints = 30;
|
||||||
|
|
||||||
|
function initializeCharts() {
|
||||||
|
const chartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
duration: 750
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// CPU Chart
|
||||||
|
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
||||||
|
cpuChart = new Chart(cpuCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'CPU Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#2563eb',
|
||||||
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
// Memory Chart
|
||||||
|
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
||||||
|
memoryChart = new Chart(memoryCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Memory Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#00b894',
|
||||||
|
backgroundColor: 'rgba(0, 184, 148, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disk Chart
|
||||||
|
const diskCtx = document.getElementById('diskChart').getContext('2d');
|
||||||
|
diskChart = new Chart(diskCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Disk Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#ff9800',
|
||||||
|
backgroundColor: 'rgba(255, 152, 0, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCharts(data) {
|
||||||
|
const now = new Date();
|
||||||
|
const timeLabel = now.toLocaleTimeString();
|
||||||
|
|
||||||
|
// Update CPU Chart
|
||||||
|
cpuData.push(data.cpu_usage);
|
||||||
|
if (cpuData.length > maxDataPoints) cpuData.shift();
|
||||||
|
cpuChart.data.labels.push(timeLabel);
|
||||||
|
if (cpuChart.data.labels.length > maxDataPoints) cpuChart.data.labels.shift();
|
||||||
|
cpuChart.data.datasets[0].data = cpuData;
|
||||||
|
cpuChart.update('none'); // Use 'none' mode for better performance
|
||||||
|
|
||||||
|
// Update Memory Chart
|
||||||
|
memoryData.push(data.memory_usage);
|
||||||
|
if (memoryData.length > maxDataPoints) memoryData.shift();
|
||||||
|
memoryChart.data.labels.push(timeLabel);
|
||||||
|
if (memoryChart.data.labels.length > maxDataPoints) memoryChart.data.labels.shift();
|
||||||
|
memoryChart.data.datasets[0].data = memoryData;
|
||||||
|
memoryChart.update('none');
|
||||||
|
|
||||||
|
// Update Disk Chart
|
||||||
|
diskData.push(data.disk_percent);
|
||||||
|
if (diskData.length > maxDataPoints) diskData.shift();
|
||||||
|
diskChart.data.labels.push(timeLabel);
|
||||||
|
if (diskChart.data.labels.length > maxDataPoints) diskChart.data.labels.shift();
|
||||||
|
diskChart.data.datasets[0].data = diskData;
|
||||||
|
diskChart.update('none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchResourceUsage() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/websites/get_website_resources/',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
'domain': $('#domainNamePage').text().trim()
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.status === 1) {
|
||||||
|
updateCharts(data);
|
||||||
|
} else {
|
||||||
|
console.error('Error fetching resource data:', data.error_message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('Failed to fetch resource usage:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize charts when the page loads
|
||||||
|
$(document).ready(function() {
|
||||||
|
if (document.getElementById('cpuChart')) {
|
||||||
|
initializeCharts();
|
||||||
|
// Fetch resource usage every 5 seconds
|
||||||
|
setInterval(fetchResourceUsage, 5000);
|
||||||
|
// Initial fetch
|
||||||
|
fetchResourceUsage();
|
||||||
|
}
|
||||||
|
});
|
||||||
52
websiteFunctions/resource_monitoring.py
Normal file
52
websiteFunctions/resource_monitoring.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import psutil
|
||||||
|
import os
|
||||||
|
from plogical.processUtilities import ProcessUtilities
|
||||||
|
from plogical.acl import ACLManager
|
||||||
|
import plogical.CyberCPLogFileWriter as logging
|
||||||
|
|
||||||
|
def get_website_resource_usage(externalApp):
|
||||||
|
try:
|
||||||
|
user = externalApp
|
||||||
|
if not user:
|
||||||
|
return {'status': 0, 'error_message': 'User not found'}
|
||||||
|
|
||||||
|
# Get CPU and Memory usage using ps command
|
||||||
|
command = f"ps -u {user} -o pcpu,pmem | grep -v CPU | awk '{{cpu += $1; mem += $2}} END {{print cpu, mem}}'"
|
||||||
|
result = ProcessUtilities.outputExecutioner(command)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cpu_percent, memory_percent = map(float, result.split())
|
||||||
|
except:
|
||||||
|
cpu_percent = 0
|
||||||
|
memory_percent = 0
|
||||||
|
|
||||||
|
# Get disk usage using du command
|
||||||
|
website_path = f"/home/{user}/public_html"
|
||||||
|
if os.path.exists(website_path):
|
||||||
|
# Get disk usage in MB
|
||||||
|
command = f"du -sm {website_path} | cut -f1"
|
||||||
|
disk_used = float(ProcessUtilities.outputExecutioner(command))
|
||||||
|
|
||||||
|
# Get total disk space
|
||||||
|
command = f"df -m {website_path} | tail -1 | awk '{{print $2}}'"
|
||||||
|
disk_total = float(ProcessUtilities.outputExecutioner(command))
|
||||||
|
|
||||||
|
# Calculate percentage
|
||||||
|
disk_percent = (disk_used / disk_total) * 100 if disk_total > 0 else 0
|
||||||
|
else:
|
||||||
|
disk_used = 0
|
||||||
|
disk_total = 0
|
||||||
|
disk_percent = 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
'status': 1,
|
||||||
|
'cpu_usage': round(cpu_percent, 2),
|
||||||
|
'memory_usage': round(memory_percent, 2),
|
||||||
|
'disk_used': round(disk_used, 2),
|
||||||
|
'disk_total': round(disk_total, 2),
|
||||||
|
'disk_percent': round(disk_percent, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
except BaseException as msg:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error in get_website_resource_usage: {str(msg)}')
|
||||||
|
return {'status': 0, 'error_message': str(msg)}
|
||||||
143
websiteFunctions/static/js/resource-monitoring.js
Normal file
143
websiteFunctions/static/js/resource-monitoring.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// Resource Monitoring
|
||||||
|
let cpuChart, memoryChart, diskChart;
|
||||||
|
let cpuData = [], memoryData = [], diskData = [];
|
||||||
|
const maxDataPoints = 30;
|
||||||
|
|
||||||
|
function initializeCharts() {
|
||||||
|
const chartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
duration: 750
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// CPU Chart
|
||||||
|
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
||||||
|
cpuChart = new Chart(cpuCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'CPU Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#2563eb',
|
||||||
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
// Memory Chart
|
||||||
|
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
||||||
|
memoryChart = new Chart(memoryCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Memory Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#00b894',
|
||||||
|
backgroundColor: 'rgba(0, 184, 148, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disk Chart
|
||||||
|
const diskCtx = document.getElementById('diskChart').getContext('2d');
|
||||||
|
diskChart = new Chart(diskCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Disk Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#ff9800',
|
||||||
|
backgroundColor: 'rgba(255, 152, 0, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCharts(data) {
|
||||||
|
const now = new Date();
|
||||||
|
const timeLabel = now.toLocaleTimeString();
|
||||||
|
|
||||||
|
// Update CPU Chart
|
||||||
|
cpuData.push(data.cpu_usage);
|
||||||
|
if (cpuData.length > maxDataPoints) cpuData.shift();
|
||||||
|
cpuChart.data.labels.push(timeLabel);
|
||||||
|
if (cpuChart.data.labels.length > maxDataPoints) cpuChart.data.labels.shift();
|
||||||
|
cpuChart.data.datasets[0].data = cpuData;
|
||||||
|
cpuChart.update('none'); // Use 'none' mode for better performance
|
||||||
|
|
||||||
|
// Update Memory Chart
|
||||||
|
memoryData.push(data.memory_usage);
|
||||||
|
if (memoryData.length > maxDataPoints) memoryData.shift();
|
||||||
|
memoryChart.data.labels.push(timeLabel);
|
||||||
|
if (memoryChart.data.labels.length > maxDataPoints) memoryChart.data.labels.shift();
|
||||||
|
memoryChart.data.datasets[0].data = memoryData;
|
||||||
|
memoryChart.update('none');
|
||||||
|
|
||||||
|
// Update Disk Chart
|
||||||
|
diskData.push(data.disk_percent);
|
||||||
|
if (diskData.length > maxDataPoints) diskData.shift();
|
||||||
|
diskChart.data.labels.push(timeLabel);
|
||||||
|
if (diskChart.data.labels.length > maxDataPoints) diskChart.data.labels.shift();
|
||||||
|
diskChart.data.datasets[0].data = diskData;
|
||||||
|
diskChart.update('none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchResourceUsage() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/websites/get_website_resources/',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
'domain': $('#domainNamePage').text().trim()
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.status === 1) {
|
||||||
|
updateCharts(data);
|
||||||
|
} else {
|
||||||
|
console.error('Error fetching resource data:', data.error_message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('Failed to fetch resource usage:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize charts when the page loads
|
||||||
|
$(document).ready(function() {
|
||||||
|
if (document.getElementById('cpuChart')) {
|
||||||
|
initializeCharts();
|
||||||
|
// Fetch resource usage every 5 seconds
|
||||||
|
setInterval(fetchResourceUsage, 5000);
|
||||||
|
// Initial fetch
|
||||||
|
fetchResourceUsage();
|
||||||
|
}
|
||||||
|
});
|
||||||
162
websiteFunctions/static/js/websiteFunctions.js
Normal file
162
websiteFunctions/static/js/websiteFunctions.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// Resource Monitoring
|
||||||
|
let cpuChart, memoryChart, diskChart;
|
||||||
|
let cpuData = [], memoryData = [], diskData = [];
|
||||||
|
const maxDataPoints = 30;
|
||||||
|
|
||||||
|
function initializeCharts() {
|
||||||
|
// CPU Chart
|
||||||
|
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
||||||
|
cpuChart = new Chart(cpuCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'CPU Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#2563eb',
|
||||||
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Memory Chart
|
||||||
|
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
||||||
|
memoryChart = new Chart(memoryCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Memory Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#00b894',
|
||||||
|
backgroundColor: 'rgba(0, 184, 148, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disk Chart
|
||||||
|
const diskCtx = document.getElementById('diskChart').getContext('2d');
|
||||||
|
diskChart = new Chart(diskCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Disk Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#ff9800',
|
||||||
|
backgroundColor: 'rgba(255, 152, 0, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCharts(data) {
|
||||||
|
const now = new Date();
|
||||||
|
const timeLabel = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
|
||||||
|
|
||||||
|
// Update CPU Chart
|
||||||
|
cpuData.push(data.cpu_usage);
|
||||||
|
if (cpuData.length > maxDataPoints) cpuData.shift();
|
||||||
|
cpuChart.data.labels.push(timeLabel);
|
||||||
|
if (cpuChart.data.labels.length > maxDataPoints) cpuChart.data.labels.shift();
|
||||||
|
cpuChart.data.datasets[0].data = cpuData;
|
||||||
|
cpuChart.update();
|
||||||
|
|
||||||
|
// Update Memory Chart
|
||||||
|
memoryData.push(data.memory_usage);
|
||||||
|
if (memoryData.length > maxDataPoints) memoryData.shift();
|
||||||
|
memoryChart.data.labels.push(timeLabel);
|
||||||
|
if (memoryChart.data.labels.length > maxDataPoints) memoryChart.data.labels.shift();
|
||||||
|
memoryChart.data.datasets[0].data = memoryData;
|
||||||
|
memoryChart.update();
|
||||||
|
|
||||||
|
// Update Disk Chart
|
||||||
|
diskData.push(data.disk_percent);
|
||||||
|
if (diskData.length > maxDataPoints) diskData.shift();
|
||||||
|
diskChart.data.labels.push(timeLabel);
|
||||||
|
if (diskChart.data.labels.length > maxDataPoints) diskChart.data.labels.shift();
|
||||||
|
diskChart.data.datasets[0].data = diskData;
|
||||||
|
diskChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchResourceUsage() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/website/get_website_resources/',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
'domain': $('#domainNamePage').text()
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.status === 1) {
|
||||||
|
updateCharts(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
console.error('Error fetching resource usage data');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize charts when the page loads
|
||||||
|
$(document).ready(function() {
|
||||||
|
initializeCharts();
|
||||||
|
// Fetch resource usage every 5 seconds
|
||||||
|
setInterval(fetchResourceUsage, 5000);
|
||||||
|
// Initial fetch
|
||||||
|
fetchResourceUsage();
|
||||||
|
});
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// Resource Monitoring
|
||||||
|
let cpuChart, memoryChart, diskChart;
|
||||||
|
let cpuData = [], memoryData = [], diskData = [];
|
||||||
|
const maxDataPoints = 30;
|
||||||
|
|
||||||
|
function initializeCharts() {
|
||||||
|
const chartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
duration: 750
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// CPU Chart
|
||||||
|
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
||||||
|
cpuChart = new Chart(cpuCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'CPU Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#2563eb',
|
||||||
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
// Memory Chart
|
||||||
|
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
||||||
|
memoryChart = new Chart(memoryCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Memory Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#00b894',
|
||||||
|
backgroundColor: 'rgba(0, 184, 148, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disk Chart
|
||||||
|
const diskCtx = document.getElementById('diskChart').getContext('2d');
|
||||||
|
diskChart = new Chart(diskCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Disk Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#ff9800',
|
||||||
|
backgroundColor: 'rgba(255, 152, 0, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: chartOptions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCharts(data) {
|
||||||
|
const now = new Date();
|
||||||
|
const timeLabel = now.toLocaleTimeString();
|
||||||
|
|
||||||
|
// Update CPU Chart
|
||||||
|
cpuData.push(data.cpu_usage);
|
||||||
|
if (cpuData.length > maxDataPoints) cpuData.shift();
|
||||||
|
cpuChart.data.labels.push(timeLabel);
|
||||||
|
if (cpuChart.data.labels.length > maxDataPoints) cpuChart.data.labels.shift();
|
||||||
|
cpuChart.data.datasets[0].data = cpuData;
|
||||||
|
cpuChart.update('none'); // Use 'none' mode for better performance
|
||||||
|
|
||||||
|
// Update Memory Chart
|
||||||
|
memoryData.push(data.memory_usage);
|
||||||
|
if (memoryData.length > maxDataPoints) memoryData.shift();
|
||||||
|
memoryChart.data.labels.push(timeLabel);
|
||||||
|
if (memoryChart.data.labels.length > maxDataPoints) memoryChart.data.labels.shift();
|
||||||
|
memoryChart.data.datasets[0].data = memoryData;
|
||||||
|
memoryChart.update('none');
|
||||||
|
|
||||||
|
// Update Disk Chart
|
||||||
|
diskData.push(data.disk_percent);
|
||||||
|
if (diskData.length > maxDataPoints) diskData.shift();
|
||||||
|
diskChart.data.labels.push(timeLabel);
|
||||||
|
if (diskChart.data.labels.length > maxDataPoints) diskChart.data.labels.shift();
|
||||||
|
diskChart.data.datasets[0].data = diskData;
|
||||||
|
diskChart.update('none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchResourceUsage() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/websites/get_website_resources/',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
'domain': $('#domainNamePage').text().trim()
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.status === 1) {
|
||||||
|
updateCharts(data);
|
||||||
|
} else {
|
||||||
|
console.error('Error fetching resource data:', data.error_message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('Failed to fetch resource usage:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize charts when the page loads
|
||||||
|
$(document).ready(function() {
|
||||||
|
if (document.getElementById('cpuChart')) {
|
||||||
|
initializeCharts();
|
||||||
|
// Fetch resource usage every 5 seconds
|
||||||
|
setInterval(fetchResourceUsage, 5000);
|
||||||
|
// Initial fetch
|
||||||
|
fetchResourceUsage();
|
||||||
|
}
|
||||||
|
});
|
||||||
162
websiteFunctions/static/websiteFunctions/js/websiteFunctions.js
Normal file
162
websiteFunctions/static/websiteFunctions/js/websiteFunctions.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// Resource Monitoring
|
||||||
|
let cpuChart, memoryChart, diskChart;
|
||||||
|
let cpuData = [], memoryData = [], diskData = [];
|
||||||
|
const maxDataPoints = 30;
|
||||||
|
|
||||||
|
function initializeCharts() {
|
||||||
|
// CPU Chart
|
||||||
|
const cpuCtx = document.getElementById('cpuChart').getContext('2d');
|
||||||
|
cpuChart = new Chart(cpuCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'CPU Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#2563eb',
|
||||||
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Memory Chart
|
||||||
|
const memoryCtx = document.getElementById('memoryChart').getContext('2d');
|
||||||
|
memoryChart = new Chart(memoryCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Memory Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#00b894',
|
||||||
|
backgroundColor: 'rgba(0, 184, 148, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disk Chart
|
||||||
|
const diskCtx = document.getElementById('diskChart').getContext('2d');
|
||||||
|
diskChart = new Chart(diskCtx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Disk Usage (%)',
|
||||||
|
data: [],
|
||||||
|
borderColor: '#ff9800',
|
||||||
|
backgroundColor: 'rgba(255, 152, 0, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 100,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return value + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCharts(data) {
|
||||||
|
const now = new Date();
|
||||||
|
const timeLabel = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
|
||||||
|
|
||||||
|
// Update CPU Chart
|
||||||
|
cpuData.push(data.cpu_usage);
|
||||||
|
if (cpuData.length > maxDataPoints) cpuData.shift();
|
||||||
|
cpuChart.data.labels.push(timeLabel);
|
||||||
|
if (cpuChart.data.labels.length > maxDataPoints) cpuChart.data.labels.shift();
|
||||||
|
cpuChart.data.datasets[0].data = cpuData;
|
||||||
|
cpuChart.update();
|
||||||
|
|
||||||
|
// Update Memory Chart
|
||||||
|
memoryData.push(data.memory_usage);
|
||||||
|
if (memoryData.length > maxDataPoints) memoryData.shift();
|
||||||
|
memoryChart.data.labels.push(timeLabel);
|
||||||
|
if (memoryChart.data.labels.length > maxDataPoints) memoryChart.data.labels.shift();
|
||||||
|
memoryChart.data.datasets[0].data = memoryData;
|
||||||
|
memoryChart.update();
|
||||||
|
|
||||||
|
// Update Disk Chart
|
||||||
|
diskData.push(data.disk_percent);
|
||||||
|
if (diskData.length > maxDataPoints) diskData.shift();
|
||||||
|
diskChart.data.labels.push(timeLabel);
|
||||||
|
if (diskChart.data.labels.length > maxDataPoints) diskChart.data.labels.shift();
|
||||||
|
diskChart.data.datasets[0].data = diskData;
|
||||||
|
diskChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchResourceUsage() {
|
||||||
|
$.ajax({
|
||||||
|
url: '/website/get_website_resources/',
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
'domain': $('#domainNamePage').text()
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.status === 1) {
|
||||||
|
updateCharts(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
console.error('Error fetching resource usage data');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize charts when the page loads
|
||||||
|
$(document).ready(function() {
|
||||||
|
initializeCharts();
|
||||||
|
// Fetch resource usage every 5 seconds
|
||||||
|
setInterval(fetchResourceUsage, 5000);
|
||||||
|
// Initial fetch
|
||||||
|
fetchResourceUsage();
|
||||||
|
});
|
||||||
@@ -16905,3 +16905,936 @@ app.controller('BuyAddons', function ($scope, $http) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.controller('launchChild', function ($scope, $http) {
|
||||||
|
|
||||||
|
$scope.logFileLoading = true;
|
||||||
|
$scope.logsFeteched = true;
|
||||||
|
$scope.couldNotFetchLogs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedData = true;
|
||||||
|
$scope.hideLogs = true;
|
||||||
|
$scope.hideErrorLogs = true;
|
||||||
|
|
||||||
|
$scope.hidelogsbtn = function () {
|
||||||
|
$scope.hideLogs = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.hideErrorLogsbtn = function () {
|
||||||
|
$scope.hideLogs = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.fileManagerURL = "/filemanager/" + $("#domainNamePage").text();
|
||||||
|
$scope.previewUrl = "/preview/" + $("#childDomain").text() + "/";
|
||||||
|
$scope.wordPressInstallURL = "/websites/" + $("#childDomain").text() + "/wordpressInstall";
|
||||||
|
$scope.joomlaInstallURL = "/websites/" + $("#childDomain").text() + "/joomlaInstall";
|
||||||
|
$scope.setupGit = "/websites/" + $("#childDomain").text() + "/setupGit";
|
||||||
|
$scope.installPrestaURL = "/websites/" + $("#childDomain").text() + "/installPrestaShop";
|
||||||
|
$scope.installMagentoURL = "/websites/" + $("#childDomain").text() + "/installMagento";
|
||||||
|
|
||||||
|
var logType = 0;
|
||||||
|
$scope.pageNumber = 1;
|
||||||
|
|
||||||
|
$scope.fetchLogs = function (type) {
|
||||||
|
|
||||||
|
var pageNumber = $scope.pageNumber;
|
||||||
|
|
||||||
|
|
||||||
|
if (type == 3) {
|
||||||
|
pageNumber = $scope.pageNumber + 1;
|
||||||
|
$scope.pageNumber = pageNumber;
|
||||||
|
} else if (type == 4) {
|
||||||
|
pageNumber = $scope.pageNumber - 1;
|
||||||
|
$scope.pageNumber = pageNumber;
|
||||||
|
} else {
|
||||||
|
logType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$scope.logFileLoading = false;
|
||||||
|
$scope.logsFeteched = true;
|
||||||
|
$scope.couldNotFetchLogs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedData = false;
|
||||||
|
$scope.hideErrorLogs = true;
|
||||||
|
|
||||||
|
|
||||||
|
url = "/websites/getDataFromLogFile";
|
||||||
|
|
||||||
|
var domainNamePage = $("#domainNamePage").text();
|
||||||
|
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
logType: logType,
|
||||||
|
virtualHost: domainNamePage,
|
||||||
|
page: pageNumber,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
if (response.data.logstatus === 1) {
|
||||||
|
|
||||||
|
|
||||||
|
$scope.logFileLoading = true;
|
||||||
|
$scope.logsFeteched = false;
|
||||||
|
$scope.couldNotFetchLogs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedData = false;
|
||||||
|
$scope.hideLogs = false;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.records = JSON.parse(response.data.data);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$scope.logFileLoading = true;
|
||||||
|
$scope.logsFeteched = true;
|
||||||
|
$scope.couldNotFetchLogs = false;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedData = true;
|
||||||
|
$scope.hideLogs = false;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
console.log(domainNamePage)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
$scope.logFileLoading = true;
|
||||||
|
$scope.logsFeteched = true;
|
||||||
|
$scope.couldNotFetchLogs = true;
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
$scope.fetchedData = true;
|
||||||
|
$scope.hideLogs = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.errorPageNumber = 1;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.fetchErrorLogs = function (type) {
|
||||||
|
|
||||||
|
var errorPageNumber = $scope.errorPageNumber;
|
||||||
|
|
||||||
|
|
||||||
|
if (type === 3) {
|
||||||
|
errorPageNumber = $scope.errorPageNumber + 1;
|
||||||
|
$scope.errorPageNumber = errorPageNumber;
|
||||||
|
} else if (type === 4) {
|
||||||
|
errorPageNumber = $scope.errorPageNumber - 1;
|
||||||
|
$scope.errorPageNumber = errorPageNumber;
|
||||||
|
} else {
|
||||||
|
logType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
|
||||||
|
$scope.logFileLoading = false;
|
||||||
|
$scope.logsFeteched = true;
|
||||||
|
$scope.couldNotFetchLogs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedData = true;
|
||||||
|
$scope.hideErrorLogs = true;
|
||||||
|
$scope.hideLogs = false;
|
||||||
|
|
||||||
|
|
||||||
|
url = "/websites/fetchErrorLogs";
|
||||||
|
|
||||||
|
var domainNamePage = $("#domainNamePage").text();
|
||||||
|
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
virtualHost: domainNamePage,
|
||||||
|
page: errorPageNumber,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
if (response.data.logstatus === 1) {
|
||||||
|
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
|
||||||
|
$scope.logFileLoading = true;
|
||||||
|
$scope.logsFeteched = false;
|
||||||
|
$scope.couldNotFetchLogs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedData = true;
|
||||||
|
$scope.hideLogs = false;
|
||||||
|
$scope.hideErrorLogs = false;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.errorLogsData = response.data.data;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
|
||||||
|
$scope.logFileLoading = true;
|
||||||
|
$scope.logsFeteched = true;
|
||||||
|
$scope.couldNotFetchLogs = false;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedData = true;
|
||||||
|
$scope.hideLogs = true;
|
||||||
|
$scope.hideErrorLogs = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
|
||||||
|
$scope.logFileLoading = true;
|
||||||
|
$scope.logsFeteched = true;
|
||||||
|
$scope.couldNotFetchLogs = true;
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
$scope.fetchedData = true;
|
||||||
|
$scope.hideLogs = true;
|
||||||
|
$scope.hideErrorLogs = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
///////// Configurations Part
|
||||||
|
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
|
||||||
|
$scope.hideconfigbtn = function () {
|
||||||
|
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.fetchConfigurations = function () {
|
||||||
|
|
||||||
|
|
||||||
|
$scope.hidsslconfigs = true;
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.changePHPView = true;
|
||||||
|
|
||||||
|
|
||||||
|
//Rewrite rules
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
$scope.configFileLoading = false;
|
||||||
|
|
||||||
|
|
||||||
|
url = "/websites/getDataFromConfigFile";
|
||||||
|
|
||||||
|
var virtualHost = $("#childDomain").text();
|
||||||
|
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
virtualHost: virtualHost,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
if (response.data.configstatus === 1) {
|
||||||
|
|
||||||
|
//Rewrite rules
|
||||||
|
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
$scope.configurationsBox = false;
|
||||||
|
$scope.configsFetched = false;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedConfigsData = false;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
$scope.saveConfigBtn = false;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.configData = response.data.configData;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//Rewrite rules
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
|
||||||
|
///
|
||||||
|
$scope.configurationsBox = false;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = false;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
//Rewrite rules
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
///
|
||||||
|
|
||||||
|
$scope.configurationsBox = false;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.saveCongiruations = function () {
|
||||||
|
|
||||||
|
$scope.configFileLoading = false;
|
||||||
|
|
||||||
|
|
||||||
|
url = "/websites/saveConfigsToFile";
|
||||||
|
|
||||||
|
var virtualHost = $("#childDomain").text();
|
||||||
|
var configData = $scope.configData;
|
||||||
|
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
virtualHost: virtualHost,
|
||||||
|
configData: configData,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
if (response.data.configstatus == 1) {
|
||||||
|
|
||||||
|
$scope.configurationsBox = false;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = false;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
$scope.saveConfigBtn = true;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$scope.configurationsBox = false;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedConfigsData = false;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = false;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
$scope.configurationsBox = false;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
///////// Rewrite Rules
|
||||||
|
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
|
||||||
|
$scope.hideRewriteRulesbtn = function () {
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$scope.fetchRewriteFules = function () {
|
||||||
|
|
||||||
|
$scope.hidsslconfigs = true;
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.changePHPView = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
$scope.saveConfigBtn = true;
|
||||||
|
|
||||||
|
$scope.configFileLoading = false;
|
||||||
|
|
||||||
|
|
||||||
|
url = "/websites/getRewriteRules";
|
||||||
|
|
||||||
|
var virtualHost = $("#childDomain").text();
|
||||||
|
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
virtualHost: virtualHost,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
if (response.data.rewriteStatus == 1) {
|
||||||
|
|
||||||
|
|
||||||
|
// from main
|
||||||
|
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
$scope.saveConfigBtn = true;
|
||||||
|
|
||||||
|
// main ends
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
$scope.configurationsBoxRewrite = false;
|
||||||
|
$scope.rewriteRulesFetched = false;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = false;
|
||||||
|
$scope.saveRewriteRulesBTN = false;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.rewriteRules = response.data.rewriteRules;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// from main
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
$scope.saveConfigBtn = true;
|
||||||
|
// from main
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = false;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
// from main
|
||||||
|
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.configsFetched = true;
|
||||||
|
$scope.couldNotFetchConfigs = true;
|
||||||
|
$scope.fetchedConfigsData = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.configSaved = true;
|
||||||
|
$scope.couldNotSaveConfigurations = true;
|
||||||
|
$scope.saveConfigBtn = true;
|
||||||
|
|
||||||
|
// from main
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.saveRewriteRules = function () {
|
||||||
|
|
||||||
|
$scope.configFileLoading = false;
|
||||||
|
|
||||||
|
|
||||||
|
url = "/websites/saveRewriteRules";
|
||||||
|
|
||||||
|
var virtualHost = $("#childDomain").text();
|
||||||
|
var rewriteRules = $scope.rewriteRules;
|
||||||
|
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
virtualHost: virtualHost,
|
||||||
|
rewriteRules: rewriteRules,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
if (response.data.rewriteStatus == 1) {
|
||||||
|
|
||||||
|
$scope.configurationsBoxRewrite = false;
|
||||||
|
$scope.rewriteRulesFetched = true;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = false;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$scope.configurationsBoxRewrite = false;
|
||||||
|
$scope.rewriteRulesFetched = false;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = false;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = false;
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
$scope.configurationsBoxRewrite = false;
|
||||||
|
$scope.rewriteRulesFetched = false;
|
||||||
|
$scope.couldNotFetchRewriteRules = true;
|
||||||
|
$scope.rewriteRulesSaved = true;
|
||||||
|
$scope.couldNotSaveRewriteRules = true;
|
||||||
|
$scope.fetchedRewriteRules = true;
|
||||||
|
$scope.saveRewriteRulesBTN = false;
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//////// SSL Part
|
||||||
|
|
||||||
|
$scope.sslSaved = true;
|
||||||
|
$scope.couldNotSaveSSL = true;
|
||||||
|
$scope.hidsslconfigs = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.hidesslbtn = function () {
|
||||||
|
$scope.hidsslconfigs = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.addSSL = function () {
|
||||||
|
$scope.hidsslconfigs = false;
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.changePHPView = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$scope.saveSSL = function () {
|
||||||
|
|
||||||
|
|
||||||
|
$scope.configFileLoading = false;
|
||||||
|
|
||||||
|
url = "/websites/saveSSL";
|
||||||
|
|
||||||
|
var virtualHost = $("#childDomain").text();
|
||||||
|
var cert = $scope.cert;
|
||||||
|
var key = $scope.key;
|
||||||
|
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
virtualHost: virtualHost,
|
||||||
|
cert: cert,
|
||||||
|
key: key,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
if (response.data.sslStatus === 1) {
|
||||||
|
|
||||||
|
$scope.sslSaved = false;
|
||||||
|
$scope.couldNotSaveSSL = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$scope.sslSaved = true;
|
||||||
|
$scope.couldNotSaveSSL = false;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
$scope.sslSaved = true;
|
||||||
|
$scope.couldNotSaveSSL = true;
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//// Change PHP Master
|
||||||
|
|
||||||
|
$scope.failedToChangePHPMaster = true;
|
||||||
|
$scope.phpChangedMaster = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
|
||||||
|
$scope.changePHPView = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.hideChangePHPMaster = function () {
|
||||||
|
$scope.changePHPView = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.changePHPMaster = function () {
|
||||||
|
$scope.hidsslconfigs = true;
|
||||||
|
$scope.configurationsBox = true;
|
||||||
|
$scope.configurationsBoxRewrite = true;
|
||||||
|
$scope.changePHPView = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$scope.changePHPVersionMaster = function (childDomain, phpSelection) {
|
||||||
|
|
||||||
|
// notifcations
|
||||||
|
|
||||||
|
$scope.configFileLoading = false;
|
||||||
|
|
||||||
|
var url = "/websites/changePHP";
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
childDomain: $("#childDomain").text(),
|
||||||
|
phpSelection: $scope.phpSelectionMaster,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
|
||||||
|
if (response.data.changePHP === 1) {
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.websiteDomain = $("#childDomain").text();
|
||||||
|
|
||||||
|
|
||||||
|
// notifcations
|
||||||
|
|
||||||
|
$scope.failedToChangePHPMaster = true;
|
||||||
|
$scope.phpChangedMaster = false;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
// notifcations
|
||||||
|
|
||||||
|
$scope.failedToChangePHPMaster = false;
|
||||||
|
$scope.phpChangedMaster = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
$scope.configFileLoading = true;
|
||||||
|
|
||||||
|
// notifcations
|
||||||
|
|
||||||
|
$scope.failedToChangePHPMaster = true;
|
||||||
|
$scope.phpChangedMaster = true;
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Open_basedir protection
|
||||||
|
|
||||||
|
$scope.baseDirLoading = true;
|
||||||
|
$scope.operationFailed = true;
|
||||||
|
$scope.operationSuccessfull = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.openBaseDirBox = true;
|
||||||
|
|
||||||
|
|
||||||
|
$scope.openBaseDirView = function () {
|
||||||
|
$scope.openBaseDirBox = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.hideOpenBasedir = function () {
|
||||||
|
$scope.openBaseDirBox = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.applyOpenBasedirChanges = function (childDomain, phpSelection) {
|
||||||
|
|
||||||
|
// notifcations
|
||||||
|
|
||||||
|
$scope.baseDirLoading = false;
|
||||||
|
$scope.operationFailed = true;
|
||||||
|
$scope.operationSuccessfull = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.openBaseDirBox = false;
|
||||||
|
|
||||||
|
|
||||||
|
var url = "/websites/changeOpenBasedir";
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
domainName: $("#childDomain").text(),
|
||||||
|
openBasedirValue: $scope.openBasedirValue
|
||||||
|
};
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||||
|
|
||||||
|
|
||||||
|
function ListInitialDatas(response) {
|
||||||
|
|
||||||
|
|
||||||
|
if (response.data.changeOpenBasedir === 1) {
|
||||||
|
|
||||||
|
$scope.baseDirLoading = true;
|
||||||
|
$scope.operationFailed = true;
|
||||||
|
$scope.operationSuccessfull = false;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.openBaseDirBox = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$scope.baseDirLoading = true;
|
||||||
|
$scope.operationFailed = false;
|
||||||
|
$scope.operationSuccessfull = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.openBaseDirBox = false;
|
||||||
|
|
||||||
|
$scope.errorMessage = response.data.error_message;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cantLoadInitialDatas(response) {
|
||||||
|
|
||||||
|
$scope.baseDirLoading = true;
|
||||||
|
$scope.operationFailed = true;
|
||||||
|
$scope.operationSuccessfull = true;
|
||||||
|
$scope.couldNotConnect = false;
|
||||||
|
$scope.openBaseDirBox = false;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,42 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.ScanWordpressSite = function () {
|
||||||
|
$('#cyberPanelLoading').show();
|
||||||
|
var url = "{% url 'ScanWordpressSite' %}";
|
||||||
|
var data = {};
|
||||||
|
var config = {
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': getCookie('csrftoken')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.post(url, data, config).then(function(response) {
|
||||||
|
$('#cyberPanelLoading').hide();
|
||||||
|
if (response.data.status === 1) {
|
||||||
|
new PNotify({
|
||||||
|
title: 'Success!',
|
||||||
|
text: 'WordPress sites scanned successfully!',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
new PNotify({
|
||||||
|
title: 'Operation Failed!',
|
||||||
|
text: response.data.error_message,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function(response) {
|
||||||
|
$('#cyberPanelLoading').hide();
|
||||||
|
new PNotify({
|
||||||
|
title: 'Operation Failed!',
|
||||||
|
text: response.data.error_message,
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
$scope.updateSetting = function(site, setting) {
|
$scope.updateSetting = function(site, setting) {
|
||||||
var settingMap = {
|
var settingMap = {
|
||||||
'search-indexing': 'searchIndex',
|
'search-indexing': 'searchIndex',
|
||||||
@@ -355,6 +391,7 @@
|
|||||||
<h3 class="panel-title">{% trans "WordPress Sites" %}</h3>
|
<h3 class="panel-title">{% trans "WordPress Sites" %}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 text-right">
|
<div class="col-sm-6 text-right">
|
||||||
|
<button ng-click="ScanWordpressSite()" class="btn btn-info btn-sm" style="margin-right: 10px;">Scan WordPress Sites</button>
|
||||||
<a href="{% url 'createWordpress' %}" class="btn btn-success btn-sm">Install WordPress</a>
|
<a href="{% url 'createWordpress' %}" class="btn btn-success btn-sm">Install WordPress</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -603,5 +640,3 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-10" style="padding: 0px; box-shadow: 0px 0px 1px 0px #888888; margin-bottom: 2%">
|
<div class="col-sm-10" style="padding: 0px; box-shadow: 0px 0px 1px 0px #888888; margin-bottom: 2%">
|
||||||
<input ng-change="searchWebsites()" placeholder="Search..." ng-model="patternAdded" name="dom" type="text"
|
<input ng-keypress="$event.keyCode === 13 && searchWebsites()" placeholder="Search... (Press Enter to search)" ng-model="patternAdded" name="dom" type="text"
|
||||||
class="form-control" required>
|
class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +1,173 @@
|
|||||||
{% extends "baseTemplate/index.html" %}
|
{% extends "baseTemplate/index.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
{% block title %}{{ domain }} - CyberPanel{% endblock %}
|
{% block title %}{{ domain }} - CyberPanel{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
<!-- Current language: {{ LANGUAGE_CODE }} -->
|
<!-- Current language: {{ LANGUAGE_CODE }} -->
|
||||||
|
|
||||||
<div ng-controller="websitePages" class="container">
|
<style>
|
||||||
|
.cyberpanel-website-page {
|
||||||
|
/* background: #f7fafd; */
|
||||||
|
padding: 32px 0;
|
||||||
|
font-family: 'Inter', 'Segoe UI', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(40,60,90,0.06);
|
||||||
|
margin-bottom: 32px;
|
||||||
|
padding: 32px 28px 28px 28px;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-card:hover {
|
||||||
|
box-shadow: 0 4px 24px 0 rgba(40,60,90,0.10);
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-section-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: #222b38;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1.5px solid #e0e6ed;
|
||||||
|
background: #fff;
|
||||||
|
color: #2d3a4a;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 7px 22px;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 16px;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-btn:last-child { margin-right: 0; }
|
||||||
|
.cyberpanel-website-page .cyber-btn.blue { background: #f6faff; color: #2563eb; border-color: #b6d4fe; }
|
||||||
|
.cyberpanel-website-page .cyber-btn.orange { background: #fff8f1; color: #ff9800; border-color: #ffd59e; }
|
||||||
|
.cyberpanel-website-page .cyber-btn.green { background: #f3fdf7; color: #00b894; border-color: #a2f5d3; }
|
||||||
|
.cyberpanel-website-page .cyber-btn.purple { background: #faf6ff; color: #8e44ad; border-color: #d1b3ff; }
|
||||||
|
.cyberpanel-website-page .cyber-btn:hover { background: #f0f4f8; color: #1a2233; }
|
||||||
|
.cyberpanel-website-page .cyber-table th, .cyberpanel-website-page .cyber-table td {
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
font-size: 1.07rem;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-table th {
|
||||||
|
color: #6b7c93;
|
||||||
|
font-weight: 700;
|
||||||
|
background: #f6faff;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-table tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-progress {
|
||||||
|
background: #eaf0fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 18px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-progress-bar {
|
||||||
|
background: linear-gradient(90deg, #2563eb 0%, #60a5fa 100%);
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-right: 10px;
|
||||||
|
transition: width 0.4s;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-resource-table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-ssl-box {
|
||||||
|
background: #eaffea;
|
||||||
|
color: #2d3a4a;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 18px 18px 10px 18px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid #c6f7d0;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-resource-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-resource-col {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 320px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.cyberpanel-website-page .cyber-resource-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.cyberpanel-website-page .cyber-resource-row { flex-direction: column; gap: 0; }
|
||||||
|
.cyberpanel-website-page .cyber-resource-col { margin-bottom: 24px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div id="page-title">
|
<div class="cyberpanel-website-page" ng-controller="websitePages">
|
||||||
<h2><span id="domainNamePage">{{ domain }}</span> - <a target="_blank" href="{$ previewUrl $}"
|
|
||||||
style="height: 23px;line-height: 21px;"
|
<div id="page-title" style="margin-bottom: 32px;">
|
||||||
class="btn btn-border btn-alt border-red btn-link font-red"
|
<h2 style="font-size:2.1rem;font-weight:700;color:#222b38;display:flex;align-items:center;gap:18px;">
|
||||||
title=""><span>{% trans "Preview" %}</span></a></h2>
|
<span id="domainNamePage">{{ domain }}</span>
|
||||||
<p>{% trans "All functions related to a particular site." %}</p>
|
<a target="_blank" href="{$ previewUrl $}"
|
||||||
|
class="cyber-btn blue"
|
||||||
|
title=""><span>{% trans "PREVIEW" %}</span></a>
|
||||||
|
</h2>
|
||||||
|
<p style="color:#6b7c93;font-size:1.13rem;margin-top:2px;">{% trans "All functions related to a particular site." %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not error %}
|
{% if not error %}
|
||||||
|
|
||||||
|
<div style="display:flex;justify-content:flex-end;gap:18px;margin-bottom:18px;">
|
||||||
|
<a class="cyber-btn orange" href="/websites/{{ domain }}/manageGIT" title="Manage Git">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="vertical-align:middle;margin-right:7px;"><circle cx="10" cy="10" r="9" stroke="#ff9800" stroke-width="2"/><path d="M7 13L13 7M13 13V7H7" stroke="#ff9800" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
{% trans "Manage Git" %}
|
||||||
|
</a>
|
||||||
|
<a class="cyber-btn blue" href="/websites/{{ domain }}/setupStaging" title="">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="vertical-align:middle;margin-right:7px;"><rect x="3" y="3" width="14" height="14" rx="3" stroke="#2563eb" stroke-width="2"/><path d="M7 7H13V13" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
{% trans "Clone/Staging" %}
|
||||||
|
</a>
|
||||||
|
<a class="cyber-btn green" href="/websites/{{ domain }}/sshAccess" title="">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="vertical-align:middle;margin-right:7px;"><rect x="3" y="7" width="14" height="10" rx="2" stroke="#00b894" stroke-width="2"/><path d="M7 11V9C7 7.89543 7.89543 7 9 7H11C12.1046 7 13 7.89543 13 9V11" stroke="#00b894" stroke-width="2"/></svg>
|
||||||
|
{% trans "Set up SSH/SFTP Access" %}
|
||||||
|
</a>
|
||||||
|
<a class="cyber-btn purple" href="https://go.cyberpanel.net/StessTest" title="">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="vertical-align:middle;margin-right:7px;"><circle cx="10" cy="10" r="8" stroke="#8e44ad" stroke-width="2"/><path d="M10 10V5" stroke="#8e44ad" stroke-width="2" stroke-linecap="round"/><path d="M10 10L13 13" stroke="#8e44ad" stroke-width="2" stroke-linecap="round"/></svg>
|
||||||
|
{% trans "Stress Test" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="example-box-wrapper">
|
<div class="cyber-card">
|
||||||
|
<div class="cyber-section-title" style="margin-bottom:28px;">
|
||||||
<div class="panel panel-body">
|
<span style="display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border:1.5px solid #dbeafe;border-radius:8px;margin-right:12px;background:#f6faff;">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><rect x="3" y="6" width="20" height="14" rx="3" stroke="#222b38" stroke-width="2"/><rect x="7" y="10" width="12" height="2" rx="1" fill="#b6d4fe"/><rect x="7" y="14" width="7" height="2" rx="1" fill="#b6d4fe"/></svg>
|
||||||
<h3 class="content-box-header">
|
</span>
|
||||||
{% trans "Resource Usage" %} <img ng-hide="domainLoading" src="/static/images/loading.gif">
|
<span>{% trans "Resource Usage" %}</span>
|
||||||
|
</div>
|
||||||
<a style="float: right; margin-left: 2%" class="btn btn-border btn-alt border-orange btn-link font-orange" href="/websites/{{ domain }}/manageGIT" title="Manage Git"><span>{% trans "Manage Git" %}</span></a>
|
<div class="cyber-resource-row">
|
||||||
|
<div class="cyber-resource-col">
|
||||||
<a style="float: right; margin-left: 2%"
|
<table class="cyber-table cyber-resource-table" style="width:100%;border-radius:10px;overflow:hidden;">
|
||||||
class="btn btn-border btn-alt border-azure btn-link font-azure"
|
|
||||||
href="/websites/{{ domain }}/setupStaging" title=""><span>{% trans "Clone/Staging" %}</span></a>
|
|
||||||
<a style="float: right; margin-left: 2%" class="btn btn-border btn-alt border-blue-alt btn-link font-blue-alt"
|
|
||||||
href="/websites/{{ domain }}/sshAccess" title=""><span>{% trans "Set up SSH/SFTP Access" %}</span></a>
|
|
||||||
<a style="float: right" class="btn btn-border btn-alt border-black btn-link font-black"
|
|
||||||
href="https://go.cyberpanel.net/StessTest" title=""><span>{% trans "Stress Test" %}</span></a>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="content-box-wrapper">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Resource" %}</th>
|
<th>{% trans "Resource" %}</th>
|
||||||
@@ -52,66 +177,71 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="row-title">{% trans "FTP" %}</td>
|
<td>FTP</td>
|
||||||
<td><span class="text-success h4">{{ ftpUsed }}</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ ftpUsed }}</span></td>
|
||||||
<td><span class="text-success h4">{{ ftpTotal }}</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ ftpTotal }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="row-title">{% trans "Databases" %}</td>
|
<td>Databases</td>
|
||||||
<td><span class="text-success h4">{{ databasesUsed }}</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ databasesUsed }}</span></td>
|
||||||
<td><span class="text-success h4">{{ databasesTotal }}</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ databasesTotal }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="row-title">{% trans "Disk Usage" %}</td>
|
<td>Disk Usage</td>
|
||||||
<td><span class="text-success h4">{{ diskInMB }} (MB)</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ diskInMB }} (MB)</span></td>
|
||||||
<td><span class="text-success h4">{{ diskInMBTotal }} (MB)</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ diskInMBTotal }} (MB)</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="row-title">{% trans "Bandwidth Usage" %}</td>
|
<td>Bandwidth Usage</td>
|
||||||
<td><span class="text-success h4">{{ bwInMB }} (MB)</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ bwInMB }} (MB)</span></td>
|
||||||
<td><span class="text-success h4">{{ bwInMBTotal }} (MB)</span></td>
|
<td><span style="color:#00b894;font-weight:600;">{{ bwInMBTotal }} (MB)</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cyber-resource-col" style="max-width:520px;">
|
||||||
<div class="col-md-6">
|
|
||||||
<div style="margin-top: 4%" class="content-box mt-5 mx-10">
|
|
||||||
<div class="panel-body">
|
|
||||||
{% if viewSSL == 1 %}
|
{% if viewSSL == 1 %}
|
||||||
<div style="margin-top: 2%" class="alert alert-success">
|
<div class="cyber-ssl-box">
|
||||||
<div class="alert-content">
|
<span style="font-weight:700;text-transform:uppercase;">{{ authority }}</span><br>
|
||||||
<h4 class="alert-title">{{ authority }}</h4>
|
Your SSL will expire in {{ days }} days.
|
||||||
<p>Your SSL will expire in {{ days }} days.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="example-box-wrapper">
|
<div style="margin-bottom:10px;">
|
||||||
<h3 class="title-hero">
|
<span style="color:#2563eb;font-weight:600;">Disk Usage</span>
|
||||||
{% trans "Disk Usage" %}
|
<div class="cyber-progress">
|
||||||
</h3>
|
<div class="cyber-progress-bar" style="width: {{ diskUsage }}%;min-width:40px;">{{ diskUsage }}%</div>
|
||||||
<div class="progressbar" data-value="{{ diskUsage }}">
|
|
||||||
<div class="progressbar-value bg-primary">
|
|
||||||
<div class="progress-overlay"></div>
|
|
||||||
<div class="progress-label">{{ diskUsage }}%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3 class="title-hero">
|
|
||||||
{% trans "Bandwidth Usage" %}
|
|
||||||
</h3>
|
|
||||||
<div class="progressbar" data-value="{{ bwUsage }}">
|
|
||||||
<div class="progressbar-value bg-primary">
|
|
||||||
<div class="progress-overlay"></div>
|
|
||||||
<div class="progress-label">{{ bwUsage }}%</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span style="color:#2563eb;font-weight:600;">Bandwidth Usage</span>
|
||||||
|
<div class="cyber-progress">
|
||||||
|
<div class="cyber-progress-bar" style="width: {{ bwUsage }}%;min-width:40px;">{{ bwUsage }}%</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Resource Usage Graphs -->
|
||||||
|
<div class="cyber-card">
|
||||||
|
<div class="cyber-section-title" style="margin-bottom:28px;">
|
||||||
|
<span style="display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border:1.5px solid #dbeafe;border-radius:8px;margin-right:12px;background:#f6faff;">
|
||||||
|
<svg width="26" height="26" viewBox="0 0 26 26" fill="none"><path d="M3 3V21H23" stroke="#222b38" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 16L9 10L13 14L23 4" stroke="#222b38" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</span>
|
||||||
|
<span>{% trans "Real-time Resource Usage" %}</span>
|
||||||
|
</div>
|
||||||
|
<div class="cyber-resource-row">
|
||||||
|
<div class="cyber-resource-col">
|
||||||
|
<canvas id="cpuChart" style="width:100%;height:200px;"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="cyber-resource-col">
|
||||||
|
<canvas id="memoryChart" style="width:100%;height:200px;"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cyber-resource-row" style="margin-top:20px;">
|
||||||
|
<div class="cyber-resource-col">
|
||||||
|
<canvas id="diskChart" style="width:100%;height:200px;"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -520,7 +650,6 @@
|
|||||||
|
|
||||||
<!---------- HTML For Listing domains --------------->
|
<!---------- HTML For Listing domains --------------->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1090,3 +1219,10 @@
|
|||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer_scripts %}
|
||||||
|
{{ block.super }}
|
||||||
|
<!-- Add Chart.js and resource monitoring script -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="{% static 'websiteFunctions/js/resource-monitoring.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -200,4 +200,6 @@ urlpatterns = [
|
|||||||
# Catch all for domains
|
# Catch all for domains
|
||||||
path('<domain>/<childDomain>', views.launchChild, name='launchChild'),
|
path('<domain>/<childDomain>', views.launchChild, name='launchChild'),
|
||||||
path('<domain>', views.domain, name='domain'),
|
path('<domain>', views.domain, name='domain'),
|
||||||
|
|
||||||
|
path('get_website_resources/', views.get_website_resources, name='get_website_resources'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
|
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
from loginSystem.models import Administrator
|
from loginSystem.models import Administrator
|
||||||
from loginSystem.views import loadLoginPage
|
from loginSystem.views import loadLoginPage
|
||||||
import json
|
import json
|
||||||
import plogical.CyberCPLogFileWriter as logging
|
import plogical.CyberCPLogFileWriter as logging
|
||||||
|
from plogical.acl import ACLManager
|
||||||
|
|
||||||
|
|
||||||
from plogical.httpProc import httpProc
|
from plogical.httpProc import httpProc
|
||||||
@@ -17,6 +18,7 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from .dockerviews import startContainer as docker_startContainer
|
from .dockerviews import startContainer as docker_startContainer
|
||||||
from .dockerviews import stopContainer as docker_stopContainer
|
from .dockerviews import stopContainer as docker_stopContainer
|
||||||
from .dockerviews import restartContainer as docker_restartContainer
|
from .dockerviews import restartContainer as docker_restartContainer
|
||||||
|
from .resource_monitoring import get_website_resource_usage
|
||||||
|
|
||||||
def loadWebsitesHome(request):
|
def loadWebsitesHome(request):
|
||||||
val = request.session['userID']
|
val = request.session['userID']
|
||||||
@@ -1883,3 +1885,41 @@ def restartContainer(request):
|
|||||||
return HttpResponse('Not allowed')
|
return HttpResponse('Not allowed')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return redirect(loadLoginPage)
|
return redirect(loadLoginPage)
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def get_website_resources(request):
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
domain = data['domain']
|
||||||
|
|
||||||
|
# Get userID from session
|
||||||
|
try:
|
||||||
|
userID = request.session['userID']
|
||||||
|
admin = Administrator.objects.get(pk=userID)
|
||||||
|
except:
|
||||||
|
return JsonResponse({'status': 0, 'error_message': 'Unauthorized access'})
|
||||||
|
|
||||||
|
# Verify domain ownership
|
||||||
|
currentACL = ACLManager.loadedACL(userID)
|
||||||
|
|
||||||
|
from websiteFunctions.models import Websites
|
||||||
|
try:
|
||||||
|
website = Websites.objects.get(domain=domain)
|
||||||
|
except Websites.DoesNotExist:
|
||||||
|
return JsonResponse({'status': 0, 'error_message': 'Website not found'})
|
||||||
|
|
||||||
|
if ACLManager.checkOwnership(domain, admin, currentACL) == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return ACLManager.loadError()
|
||||||
|
|
||||||
|
# Get resource usage data using externalApp
|
||||||
|
resource_data = get_website_resource_usage(website.externalApp)
|
||||||
|
if resource_data['status'] == 0:
|
||||||
|
return JsonResponse(resource_data)
|
||||||
|
|
||||||
|
return JsonResponse(resource_data)
|
||||||
|
|
||||||
|
except BaseException as msg:
|
||||||
|
logging.CyberCPLogFileWriter.writeToFile(f'Error in get_website_resources: {str(msg)}')
|
||||||
|
return JsonResponse({'status': 0, 'error_message': str(msg)})
|
||||||
@@ -1955,14 +1955,38 @@ class WebsiteManager:
|
|||||||
return HttpResponse(json_data)
|
return HttpResponse(json_data)
|
||||||
|
|
||||||
def UpdateWPSettings(self, userID=None, data=None):
|
def UpdateWPSettings(self, userID=None, data=None):
|
||||||
|
# Map old setting names to new ones
|
||||||
|
setting_map = {
|
||||||
|
'PasswordProtection': 'password-protection',
|
||||||
|
'searchIndex': 'search-indexing',
|
||||||
|
'debugging': 'debugging',
|
||||||
|
'maintenanceMode': 'maintenance-mode',
|
||||||
|
'lscache': 'lscache',
|
||||||
|
'Wpcron': 'wpcron',
|
||||||
|
# Add more mappings as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
siteId = data.get('siteId') or data.get('WPid')
|
||||||
|
if not siteId:
|
||||||
|
resp = {'status': 0, 'error_message': 'Missing siteId or WPid'}
|
||||||
|
return JsonResponse(resp)
|
||||||
|
|
||||||
|
# Accept both new and old setting names
|
||||||
|
setting = data.get('setting')
|
||||||
|
if not setting:
|
||||||
|
for old_key in setting_map:
|
||||||
|
if old_key in data:
|
||||||
|
setting = old_key
|
||||||
|
data['settingValue'] = data[old_key]
|
||||||
|
break
|
||||||
|
|
||||||
|
# Map to new setting name if needed
|
||||||
|
setting = setting_map.get(setting, setting)
|
||||||
|
value = data.get('value') or data.get('settingValue')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
currentACL = ACLManager.loadedACL(userID)
|
currentACL = ACLManager.loadedACL(userID)
|
||||||
admin = Administrator.objects.get(pk=userID)
|
admin = Administrator.objects.get(pk=userID)
|
||||||
|
|
||||||
siteId = data['siteId']
|
|
||||||
setting = data['setting']
|
|
||||||
value = data['value']
|
|
||||||
|
|
||||||
wpsite = WPSites.objects.get(pk=siteId)
|
wpsite = WPSites.objects.get(pk=siteId)
|
||||||
|
|
||||||
if ACLManager.checkOwnership(wpsite.owner.domain, admin, currentACL) != 1:
|
if ACLManager.checkOwnership(wpsite.owner.domain, admin, currentACL) != 1:
|
||||||
@@ -1977,10 +2001,8 @@ class WebsiteManager:
|
|||||||
|
|
||||||
# Update the appropriate setting based on the setting type
|
# Update the appropriate setting based on the setting type
|
||||||
if setting == 'search-indexing':
|
if setting == 'search-indexing':
|
||||||
# Update search engine indexing
|
|
||||||
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp option update blog_public {value} --skip-plugins --skip-themes --path={wpsite.path}'
|
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp option update blog_public {value} --skip-plugins --skip-themes --path={wpsite.path}'
|
||||||
elif setting == 'debugging':
|
elif setting == 'debugging':
|
||||||
# Update debugging in wp-config.php
|
|
||||||
if value:
|
if value:
|
||||||
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp config set WP_DEBUG true --raw --skip-plugins --skip-themes --path={wpsite.path}'
|
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp config set WP_DEBUG true --raw --skip-plugins --skip-themes --path={wpsite.path}'
|
||||||
else:
|
else:
|
||||||
@@ -1990,76 +2012,84 @@ class WebsiteManager:
|
|||||||
vhostPassDir = f'/home/{vhostName}'
|
vhostPassDir = f'/home/{vhostName}'
|
||||||
path = f'{vhostPassDir}/{siteId}'
|
path = f'{vhostPassDir}/{siteId}'
|
||||||
if value:
|
if value:
|
||||||
# Enable password protection
|
|
||||||
tempPath = f'/home/cyberpanel/{str(randint(1000, 9999))}'
|
tempPath = f'/home/cyberpanel/{str(randint(1000, 9999))}'
|
||||||
os.makedirs(tempPath)
|
os.makedirs(tempPath)
|
||||||
|
|
||||||
# Create temporary .htpasswd file
|
|
||||||
htpasswd = f'{tempPath}/.htpasswd'
|
htpasswd = f'{tempPath}/.htpasswd'
|
||||||
htaccess = f'{tempPath}/.htaccess'
|
htaccess = f'{tempPath}/.htaccess'
|
||||||
password = randomPassword.generate_pass(12)
|
password = randomPassword.generate_pass(12)
|
||||||
|
|
||||||
# Create .htpasswd file
|
|
||||||
command = f"htpasswd -cb {htpasswd} admin {password}"
|
command = f"htpasswd -cb {htpasswd} admin {password}"
|
||||||
ProcessUtilities.executioner(command)
|
ProcessUtilities.executioner(command)
|
||||||
|
|
||||||
# Create .htaccess file content
|
|
||||||
htaccess_content = f"""
|
htaccess_content = f"""
|
||||||
AuthType Basic
|
AuthType Basic
|
||||||
AuthName "Restricted Access"
|
AuthName "Restricted Access"
|
||||||
AuthUserFile {path}/.htpasswd
|
AuthUserFile {path}/.htpasswd
|
||||||
Require valid-user
|
Require valid-user
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with open(htaccess, 'w') as f:
|
with open(htaccess, 'w') as f:
|
||||||
f.write(htaccess_content)
|
f.write(htaccess_content)
|
||||||
|
|
||||||
# Create final directory and move files
|
|
||||||
command = f"mkdir -p {path}"
|
command = f"mkdir -p {path}"
|
||||||
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
||||||
|
|
||||||
# Move files to final location
|
|
||||||
command = f"mv {htpasswd} {path}/.htpasswd"
|
command = f"mv {htpasswd} {path}/.htpasswd"
|
||||||
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
||||||
|
|
||||||
command = f"mv {htaccess} {wpsite.path}/.htaccess"
|
command = f"mv {htaccess} {wpsite.path}/.htaccess"
|
||||||
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
||||||
|
|
||||||
# Cleanup temp directory
|
|
||||||
command = f"rm -rf {tempPath}"
|
command = f"rm -rf {tempPath}"
|
||||||
ProcessUtilities.executioner(command)
|
ProcessUtilities.executioner(command)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Disable password protection
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
command = f"rm -rf {path}"
|
command = f"rm -rf {path}"
|
||||||
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
||||||
|
|
||||||
htaccess = f'{wpsite.path}/.htaccess'
|
htaccess = f'{wpsite.path}/.htaccess'
|
||||||
if os.path.exists(htaccess):
|
if os.path.exists(htaccess):
|
||||||
command = f"rm -f {htaccess}"
|
command = f"rm -f {htaccess}"
|
||||||
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
ProcessUtilities.executioner(command, wpsite.owner.externalApp)
|
||||||
|
resp = {'status': 1, 'error_message': 'None'}
|
||||||
return JsonResponse({'status': 1, 'error_message': 'None'})
|
if data.get('legacy_response'):
|
||||||
|
import json
|
||||||
|
return HttpResponse(json.dumps(resp))
|
||||||
|
else:
|
||||||
|
return JsonResponse(resp)
|
||||||
elif setting == 'maintenance-mode':
|
elif setting == 'maintenance-mode':
|
||||||
if value:
|
if value:
|
||||||
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp maintenance-mode activate --skip-plugins --skip-themes --path={wpsite.path}'
|
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp maintenance-mode activate --skip-plugins --skip-themes --path={wpsite.path}'
|
||||||
else:
|
else:
|
||||||
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp maintenance-mode deactivate --skip-plugins --skip-themes --path={wpsite.path}'
|
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp maintenance-mode deactivate --skip-plugins --skip-themes --path={wpsite.path}'
|
||||||
|
elif setting == 'lscache':
|
||||||
|
if value:
|
||||||
|
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp plugin activate litespeed-cache --skip-plugins --skip-themes --path={wpsite.path}'
|
||||||
else:
|
else:
|
||||||
return JsonResponse({'status': 0, 'error_message': 'Invalid setting type'})
|
command = f'sudo -u {Vhuser} {FinalPHPPath} -d error_reporting=0 /usr/bin/wp plugin deactivate litespeed-cache --skip-plugins --skip-themes --path={wpsite.path}'
|
||||||
|
else:
|
||||||
|
resp = {'status': 0, 'error_message': 'Invalid setting type'}
|
||||||
|
if data.get('legacy_response'):
|
||||||
|
import json
|
||||||
|
return HttpResponse(json.dumps(resp))
|
||||||
|
else:
|
||||||
|
return JsonResponse(resp)
|
||||||
|
|
||||||
result = ProcessUtilities.outputExecutioner(command)
|
result = ProcessUtilities.outputExecutioner(command)
|
||||||
if result.find('Error:') > -1:
|
if result.find('Error:') > -1:
|
||||||
return JsonResponse({'status': 0, 'error_message': result})
|
resp = {'status': 0, 'error_message': result}
|
||||||
|
if data.get('legacy_response'):
|
||||||
|
import json
|
||||||
|
return HttpResponse(json.dumps(resp))
|
||||||
|
else:
|
||||||
|
return JsonResponse(resp)
|
||||||
|
|
||||||
return JsonResponse({'status': 1, 'error_message': 'None'})
|
resp = {'status': 1, 'error_message': 'None'}
|
||||||
|
if data.get('legacy_response'):
|
||||||
|
import json
|
||||||
|
return HttpResponse(json.dumps(resp))
|
||||||
|
else:
|
||||||
|
return JsonResponse(resp)
|
||||||
|
|
||||||
except BaseException as msg:
|
except BaseException as msg:
|
||||||
return JsonResponse({'status': 0, 'error_message': str(msg)})
|
resp = {'status': 0, 'error_message': str(msg)}
|
||||||
|
if data and data.get('legacy_response'):
|
||||||
|
import json
|
||||||
|
return HttpResponse(json.dumps(resp))
|
||||||
|
else:
|
||||||
|
return JsonResponse(resp)
|
||||||
|
|
||||||
def submitWorpressCreation(self, userID=None, data=None):
|
def submitWorpressCreation(self, userID=None, data=None):
|
||||||
try:
|
try:
|
||||||
@@ -4572,8 +4602,7 @@ StrictHostKeyChecking no
|
|||||||
|
|
||||||
websites = ACLManager.searchWebsiteObjects(currentlACL, userID, searchTerm)
|
websites = ACLManager.searchWebsiteObjects(currentlACL, userID, searchTerm)
|
||||||
|
|
||||||
json_data = "["
|
json_data = []
|
||||||
checker = 0
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ipFile = "/etc/cyberpanel/machineIP"
|
ipFile = "/etc/cyberpanel/machineIP"
|
||||||
@@ -4604,19 +4633,34 @@ StrictHostKeyChecking no
|
|||||||
PHPVersionActual = 'PHP 8.1'
|
PHPVersionActual = 'PHP 8.1'
|
||||||
|
|
||||||
diskUsed = "%sMB" % str(DiskUsage)
|
diskUsed = "%sMB" % str(DiskUsage)
|
||||||
dic = {'domain': items.domain, 'adminEmail': items.adminEmail, 'ipAddress': ipAddress,
|
|
||||||
'admin': items.admin.userName, 'package': items.package.packageName, 'state': state,
|
|
||||||
'diskUsed': diskUsed, 'phpVersion': PHPVersionActual}
|
|
||||||
|
|
||||||
if checker == 0:
|
# Get WordPress sites for this website
|
||||||
json_data = json_data + json.dumps(dic)
|
wp_sites = []
|
||||||
checker = 1
|
try:
|
||||||
else:
|
wp_sites = WPSites.objects.filter(owner=items)
|
||||||
json_data = json_data + ',' + json.dumps(dic)
|
wp_sites = [{
|
||||||
|
'id': wp.id,
|
||||||
|
'title': wp.title,
|
||||||
|
'url': wp.FinalURL,
|
||||||
|
'version': wp.version if hasattr(wp, 'version') else 'Unknown',
|
||||||
|
'phpVersion': wp.phpVersion if hasattr(wp, 'phpVersion') else 'Unknown'
|
||||||
|
} for wp in wp_sites]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
json_data = json_data + ']'
|
json_data.append({
|
||||||
|
'domain': items.domain,
|
||||||
|
'adminEmail': items.adminEmail,
|
||||||
|
'ipAddress': ipAddress,
|
||||||
|
'admin': items.admin.userName,
|
||||||
|
'package': items.package.packageName,
|
||||||
|
'state': state,
|
||||||
|
'diskUsed': diskUsed,
|
||||||
|
'phpVersion': PHPVersionActual,
|
||||||
|
'wp_sites': wp_sites
|
||||||
|
})
|
||||||
|
|
||||||
return json_data
|
return json.dumps(json_data)
|
||||||
|
|
||||||
def findWebsitesJson(self, currentACL, userID, pageNumber):
|
def findWebsitesJson(self, currentACL, userID, pageNumber):
|
||||||
finalPageNumber = ((pageNumber * 10)) - 10
|
finalPageNumber = ((pageNumber * 10)) - 10
|
||||||
@@ -4779,7 +4823,6 @@ StrictHostKeyChecking no
|
|||||||
return ACLManager.loadErrorJson()
|
return ACLManager.loadErrorJson()
|
||||||
|
|
||||||
tempStatusPath = "/home/cyberpanel/" + str(randint(1000, 9999))
|
tempStatusPath = "/home/cyberpanel/" + str(randint(1000, 9999))
|
||||||
|
|
||||||
execPath = "/usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/virtualHostUtilities.py"
|
execPath = "/usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/virtualHostUtilities.py"
|
||||||
execPath = execPath + " switchServer --phpVersion '" + phpVersion + "' --server " + str(
|
execPath = execPath + " switchServer --phpVersion '" + phpVersion + "' --server " + str(
|
||||||
server) + " --virtualHostName " + domainName + " --tempStatusPath " + tempStatusPath
|
server) + " --virtualHostName " + domainName + " --tempStatusPath " + tempStatusPath
|
||||||
@@ -7222,4 +7265,3 @@ StrictHostKeyChecking no
|
|||||||
data_ret = {'status': 0, 'fetchStatus': 0, 'error_message': str(msg)}
|
data_ret = {'status': 0, 'fetchStatus': 0, 'error_message': str(msg)}
|
||||||
json_data = json.dumps(data_ret)
|
json_data = json.dumps(data_ret)
|
||||||
return HttpResponse(json_data)
|
return HttpResponse(json_data)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user