File: //proc/self/root/usr/local/CyberCP/websiteFunctions/templates/websiteFunctions/website.html
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% load static %}
{% block title %}{{ domain }} - CyberPanel{% endblock %}
{% block header_scripts %}
<!-- Bootstrap CSS and JS for modal functionality (jQuery already loaded in base template) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!-- xterm for terminal -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
<!-- Ensure modal is available globally -->
<script>
// Polyfill for modal if Bootstrap doesn't load properly
document.addEventListener('DOMContentLoaded', function() {
if (typeof $ !== 'undefined' && !$.fn.modal) {
console.warn('Bootstrap modal not loaded, adding basic modal support');
$.fn.modal = function(action) {
return this.each(function() {
var $this = $(this);
if (action === 'show') {
$this.addClass('in').show();
$('body').append('<div class="modal-backdrop fade in"></div>').addClass('modal-open');
} else if (action === 'hide') {
$this.removeClass('in').hide();
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
});
};
}
});
</script>
{% endblock %}
{% block content %}
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<style>
/* Website Page Specific Styles */
.cyberpanel-website-page {
background: transparent;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.cyberpanel-website-page .cyber-card {
background: var(--bg-secondary, white);
border-radius: 12px;
box-shadow: 0 2px 8px var(--shadow-color, rgba(0,0,0,0.08));
border: 1px solid var(--border-color, #e8e9ff);
margin-bottom: 25px;
padding: 25px;
transition: box-shadow 0.2s;
}
.cyberpanel-website-page .cyber-card:hover {
box-shadow: 0 4px 16px var(--shadow-color-hover, rgba(0,0,0,0.12));
}
.cyberpanel-website-page .cyber-section-title {
font-size: 16px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.cyberpanel-website-page .cyber-section-title::before {
content: '';
width: 4px;
height: 24px;
background: var(--accent-color, #5b5fcf);
border-radius: 2px;
}
.cyberpanel-website-page .cyber-btn {
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
color: var(--accent-color, #5b5fcf);
padding: 8px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 6px;
margin-right: 12px;
transition: all 0.2s ease;
}
.cyberpanel-website-page .cyber-btn:hover {
background: var(--accent-color, #5b5fcf);
color: var(--text-on-accent, white);
border-color: var(--accent-color, #5b5fcf);
box-shadow: 0 2px 4px var(--accent-shadow, rgba(91,95,207,0.3));
text-decoration: none;
}
.cyberpanel-website-page .cyber-btn.blue {
background: var(--info-bg, #f0f9ff);
color: var(--info-color, #0ea5e9);
border-color: var(--info-border, #bae6fd);
}
.cyberpanel-website-page .cyber-btn.orange {
background: var(--warning-bg, #fff7ed);
color: var(--warning-color, #ea580c);
border-color: var(--warning-border, #fed7aa);
}
.cyberpanel-website-page .cyber-btn.green {
background: var(--success-bg, #f0fdf4);
color: var(--success-color, #16a34a);
border-color: var(--success-border, #bbf7d0);
}
.cyberpanel-website-page .cyber-btn.purple {
background: var(--purple-bg, #faf5ff);
color: var(--purple-color, #9333ea);
border-color: var(--purple-border, #e9d5ff);
}
.cyberpanel-website-page .cyber-table {
width: 100%;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border-color, #e8e9ff);
}
.cyberpanel-website-page .cyber-table th,
.cyberpanel-website-page .cyber-table td {
padding: 12px 16px;
border-bottom: 1px solid var(--border-light, #f0f0ff);
font-size: 14px;
}
.cyberpanel-website-page .cyber-table th {
color: var(--text-secondary, #64748b);
font-weight: 700;
background: var(--bg-hover, #f8f9ff);
border-top: none;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.8px;
}
.cyberpanel-website-page .cyber-table tr:last-child td {
border-bottom: none;
}
.cyberpanel-website-page .cyber-table tr:hover {
background: var(--bg-hover, #f8f9ff);
}
.cyberpanel-website-page .cyber-progress {
background: var(--border-color, #e8e9ff);
border-radius: 8px;
height: 12px;
width: 100%;
margin-bottom: 15px;
overflow: hidden;
}
.cyberpanel-website-page .cyber-progress-bar {
background: var(--accent-color, #5b5fcf);
height: 100%;
border-radius: 8px;
color: var(--text-on-accent, white);
font-size: 10px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
transition: width 0.4s ease;
}
.cyberpanel-website-page .cyber-ssl-box {
background: var(--info-bg, #f0f9ff);
color: var(--info-text, #0c4a6e);
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
font-size: 13px;
font-weight: 500;
border: 1px solid var(--info-border, #bae6fd);
}
.cyberpanel-website-page .cyber-resource-row {
display: flex;
gap: 25px;
align-items: stretch;
}
.cyberpanel-website-page .cyber-resource-col {
flex: 1;
min-width: 300px;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
/* Legacy panel styles for old sections */
.example-box-wrapper {
background: var(--bg-secondary, white);
border-radius: 12px;
box-shadow: 0 2px 8px var(--shadow-color, rgba(0,0,0,0.08));
border: 1px solid var(--border-color, #e8e9ff);
margin-bottom: 25px;
}
.panel {
background: transparent;
border: none;
box-shadow: none;
}
.panel-body {
padding: 25px;
}
.content-box-header {
font-size: 16px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.content-box-header::before {
content: '';
width: 4px;
height: 24px;
background: var(--accent-color, #5b5fcf);
border-radius: 2px;
}
.btn {
background: var(--accent-color, #5b5fcf);
border: 1px solid var(--accent-color, #5b5fcf);
color: var(--text-on-accent, white);
padding: 8px 16px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
transition: all 0.2s ease;
}
.btn:hover {
background: var(--accent-hover, #4b4fbf);
border-color: var(--accent-hover, #4b4fbf);
color: var(--text-on-accent, white);
box-shadow: 0 2px 4px var(--accent-shadow, rgba(91,95,207,0.3));
}
.btn-success {
background: var(--success-color, #16a34a);
border-color: var(--success-color, #16a34a);
}
.btn-success:hover {
background: var(--success-hover, #15803d);
border-color: var(--success-hover, #15803d);
}
.alert {
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
font-size: 14px;
line-height: 1.5;
}
.alert-success {
background: var(--success-bg, #f0f9ff);
border: 1px solid var(--success-border, #bae6fd);
color: var(--success-text, #0c4a6e);
}
.alert-danger {
background: var(--danger-bg, #fef2f2);
border: 1px solid var(--danger-border, #fecaca);
color: var(--danger-text, #991b1b);
}
.alert-warning {
background: var(--warning-bg, #fffbeb);
border: 1px solid var(--warning-border, #fed7aa);
color: var(--warning-text, #92400e);
}
.form-control {
padding: 8px 12px;
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 6px;
font-size: 14px;
background: var(--bg-secondary, white);
color: var(--text-primary, #2f3640);
transition: all 0.2s ease;
}
.form-control:focus {
outline: none;
border-color: var(--accent-color, #5b5fcf);
box-shadow: 0 0 0 3px var(--accent-focus, rgba(91,95,207,0.1));
}
/* General select Windows fix for ALL selectboxes */
select.form-control {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232f3640' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px;
padding-right: 40px;
line-height: 1.5;
min-height: 38px;
color: #2f3640;
}
/* Windows-specific fixes for ALL selectboxes */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
select.form-control {
color: #2f3640 !important;
background-color: white !important;
}
}
/* Fix for Windows Edge and Chrome for ALL selectboxes */
select.form-control::-ms-expand {
display: none;
}
select.form-control:focus {
color: #2f3640;
}
/* Modal styles */
.modal {
display: none;
}
.modal.in {
display: block !important;
}
.modal-backdrop {
background: rgba(0,0,0,0.5);
}
.modal-dialog {
margin: 30px auto;
}
.modal-content {
background: var(--bg-secondary, white);
border-radius: 12px;
border: 1px solid var(--border-color, #e8e9ff);
box-shadow: 0 4px 20px var(--modal-shadow, rgba(0,0,0,0.15));
}
.modal-header {
padding: 20px 25px 15px;
border-bottom: 1px solid var(--border-color, #e8e9ff);
border-radius: 12px 12px 0 0;
}
.modal-title {
font-size: 18px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin: 0;
}
.modal-body {
padding: 25px;
}
.close {
background: none;
border: none;
font-size: 24px;
color: var(--text-secondary, #8893a7);
cursor: pointer;
float: right;
margin-top: -5px;
}
.close:hover {
color: var(--text-primary, #2f3640);
}
/* Grid layout for items in sections */
.row {
display: flex;
flex-wrap: wrap;
margin: 0 -15px;
}
/* Ensure logs section displays horizontally */
.content-box-wrapper .row {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: stretch;
}
.col-md-3, .col-md-4, .col-md-6 {
padding: 0 15px;
margin-bottom: 20px;
}
.col-md-3 {
flex: 0 0 25%;
max-width: 25%;
}
.col-md-4 {
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-md-6 {
flex: 0 0 50%;
max-width: 50%;
}
.col-md-12 {
flex: 0 0 100%;
max-width: 100%;
padding: 0 15px;
}
/* Section items styling */
.panel-body a {
display: flex;
flex-direction: row;
align-items: center;
text-align: left;
padding: 15px;
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 8px;
text-decoration: none;
color: var(--text-primary, #2f3640);
transition: all 0.2s ease;
min-height: 80px;
gap: 12px;
}
.panel-body a:hover {
background: var(--border-color, #e8e9ff);
border-color: var(--accent-color, #5b5fcf);
text-decoration: none;
color: var(--accent-color, #5b5fcf);
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--accent-shadow-light, rgba(91,95,207,0.15));
}
.panel-body a img {
flex-shrink: 0;
width: 48px;
height: 48px;
object-fit: contain;
}
.panel-body a .h4 {
margin: 0;
font-size: 15px;
font-weight: 600;
flex: 1;
}
/* Special styling for all content sections */
.content-box-wrapper .row {
display: flex !important;
flex-wrap: wrap;
margin: 0 -10px;
}
/* Handle different column sizes */
.content-box-wrapper .row .col-md-3 {
flex: 0 0 25%;
max-width: 25%;
padding: 10px;
}
.content-box-wrapper .row .col-md-4 {
flex: 0 0 33.333%;
max-width: 33.333%;
padding: 10px;
}
.content-box-wrapper .row .col-md-6 {
flex: 0 0 50%;
max-width: 50%;
padding: 10px;
}
/* Style all clickable items consistently */
.content-box-wrapper .row .col-md-3 > a,
.content-box-wrapper .row .col-md-4 > a,
.content-box-wrapper .row .col-md-6 > div {
display: flex;
align-items: center;
gap: 12px;
padding: 15px;
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
min-height: 70px;
text-decoration: none;
color: var(--text-primary, #2f3640);
width: 100%;
}
.content-box-wrapper .row .col-md-3 > a:hover,
.content-box-wrapper .row .col-md-4 > a:hover,
.content-box-wrapper .row .col-md-6 > div:hover {
background: var(--border-color, #e8e9ff);
border-color: var(--accent-color, #5b5fcf);
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--accent-shadow-light, rgba(91,95,207,0.15));
text-decoration: none;
}
/* Style for logs section clickable areas */
.content-box-wrapper .row .col-md-6 .clickable-log {
display: flex;
align-items: center;
gap: 12px;
padding: 15px;
background: var(--bg-hover, #f8f9ff);
border: 1px solid var(--border-color, #e8e9ff);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
min-height: 70px;
width: 100%;
}
.content-box-wrapper .row .col-md-6 .clickable-log:hover {
background: var(--border-color, #e8e9ff);
border-color: var(--accent-color, #5b5fcf);
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--accent-shadow-light, rgba(91,95,207,0.15));
}
/* Icon styling */
.content-box-wrapper .row img {
width: 45px;
height: 45px;
margin: 0 !important;
flex-shrink: 0;
}
/* Text styling */
.content-box-wrapper .row .h4 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: var(--text-primary, #2f3640);
line-height: 1.2;
}
.content-box-wrapper .row a:hover .h4,
.content-box-wrapper .row .clickable-log:hover .h4 {
color: var(--accent-color, #5b5fcf);
}
@media (max-width: 900px) {
.cyberpanel-website-page .cyber-resource-row {
flex-direction: column;
gap: 0;
}
.cyberpanel-website-page .cyber-resource-col {
margin-bottom: 20px;
}
.cyberpanel-website-page {
padding: 15px;
}
.modal-dialog {
margin: 10px;
width: auto;
}
/* Make columns stack on mobile */
.content-box-wrapper .row .col-md-3,
.content-box-wrapper .row .col-md-4,
.content-box-wrapper .row .col-md-6 {
flex: 0 0 100%;
max-width: 100%;
}
.content-box-wrapper .row .col-md-3 > a,
.content-box-wrapper .row .col-md-4 > a,
.content-box-wrapper .row .col-md-6 > div,
.content-box-wrapper .row .col-md-6 .clickable-log {
min-height: 60px;
padding: 12px;
gap: 10px;
}
.content-box-wrapper .row img {
width: 40px;
height: 40px;
}
.content-box-wrapper .row .h4 {
font-size: 13px;
}
}
/* Modal and Form Modal Styling for Rewrite Rules */
.form-horizontal.bordered-row {
background: var(--bg-secondary, white);
padding: 30px;
border-radius: 16px;
box-shadow: 0 10px 40px var(--shadow-color, rgba(0,0,0,0.08));
border: 1px solid var(--border-color, #e8e9ff);
margin: 20px 0;
}
/* Rewrite Rules Specific Styling */
textarea[ng-model="rewriteRules"],
textarea[ng-model="configData"] {
background: var(--bg-code, #f8fafc);
border: 2px solid var(--border-color, #e2e8f0);
border-radius: 12px;
padding: 16px;
font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
color: var(--text-primary, #1e293b);
min-height: 300px;
}
textarea[ng-model="rewriteRules"]:focus,
textarea[ng-model="configData"]:focus {
background: var(--bg-secondary, white);
border-color: var(--accent-color, #5b5fcf);
box-shadow: 0 0 0 4px var(--accent-focus, rgba(91, 95, 207, 0.1));
}
/* Form group spacing in modal */
.form-horizontal.bordered-row .form-group {
margin-bottom: 25px;
}
/* Alert styling in modal */
.form-horizontal.bordered-row .alert {
margin-bottom: 25px;
font-size: 15px;
border-radius: 8px;
}
/* Better button styling for modal forms */
.btn-lg {
padding: 14px 28px;
font-size: 16px;
font-weight: 600;
border-radius: 8px;
box-shadow: 0 4px 15px var(--accent-shadow, rgba(91, 95, 207, 0.2));
}
.btn-primary.btn-lg:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px var(--accent-shadow, rgba(91, 95, 207, 0.3));
}
/* Style for template dropdown */
.form-horizontal.bordered-row select.form-control {
background: var(--bg-code, #f8fafc);
border: 2px solid var(--border-color, #e2e8f0);
border-radius: 8px;
padding: 12px 16px;
font-size: 14px;
color: var(--text-primary, #1e293b);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231e293b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px;
padding-right: 40px;
line-height: 1.5;
min-height: 44px;
}
/* Windows-specific fixes */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.form-horizontal.bordered-row select.form-control {
color: var(--text-primary, #1e293b) !important;
background-color: var(--bg-code, #f8fafc) !important;
}
}
/* Fix for Windows Edge and Chrome */
.form-horizontal.bordered-row select.form-control::-ms-expand {
display: none;
}
.form-horizontal.bordered-row select.form-control:focus {
background: var(--bg-secondary, white);
border-color: var(--accent-color, #5b5fcf);
box-shadow: 0 0 0 4px var(--accent-focus, rgba(91, 95, 207, 0.1));
color: var(--text-primary, #1e293b);
}
/* Style for label */
.form-horizontal.bordered-row .control-label {
font-weight: 600;
color: var(--text-primary, #2f3640);
font-size: 14px;
margin-bottom: 8px;
}
/* Creative Hero Section Design */
.domain-hero {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
border-radius: 20px;
padding: 40px;
margin-bottom: 30px;
position: relative;
overflow: hidden;
color: white;
}
.domain-hero::before {
content: '';
position: absolute;
top: -100px;
right: -100px;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
border-radius: 50%;
}
.domain-hero::after {
content: '';
position: absolute;
bottom: -50px;
left: -50px;
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%);
border-radius: 50%;
}
.domain-info {
position: relative;
z-index: 1;
}
.domain-name {
font-size: 36px;
font-weight: 800;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 20px;
}
.domain-status {
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(255,255,255,0.2);
padding: 6px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
}
.status-dot {
width: 8px;
height: 8px;
background: #10b981;
border-radius: 50%;
animation: pulse 2s infinite;
}
.domain-description {
font-size: 18px;
opacity: 0.9;
margin-bottom: 30px;
}
.hero-actions {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.hero-btn {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: rgba(255,255,255,0.15);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.3);
color: white;
border-radius: 10px;
font-weight: 600;
font-size: 14px;
transition: all 0.3s ease;
text-decoration: none;
}
.hero-btn:hover {
background: rgba(255,255,255,0.25);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
color: white;
text-decoration: none;
}
.hero-btn.primary {
background: white;
color: #1e3c72;
}
.hero-btn.primary:hover {
background: #f0f0f0;
color: #1e3c72;
}
/* Quick Actions Bar */
.quick-actions {
background: var(--bg-secondary, white);
border-radius: 16px;
padding: 20px;
margin-bottom: 30px;
box-shadow: 0 4px 20px var(--shadow-color, rgba(0,0,0,0.08));
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 20px;
}
.action-group {
display: flex;
gap: 12px;
align-items: center;
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: var(--bg-hover, #f8f9ff);
border: 2px solid transparent;
border-radius: 10px;
font-weight: 600;
font-size: 14px;
transition: all 0.3s ease;
text-decoration: none;
color: var(--text-primary, #2f3640);
position: relative;
overflow: hidden;
}
.action-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
transition: left 0.5s;
}
.action-btn:hover::before {
left: 100%;
}
.action-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
text-decoration: none;
}
.action-btn.terminal {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
border: none;
}
.action-btn.git {
border-color: var(--git-color, #ff6b6b);
color: var(--git-color, #ff6b6b);
}
.action-btn.git:hover {
background: var(--git-color, #ff6b6b);
color: var(--text-on-accent, white);
}
.action-btn.staging {
border-color: var(--staging-color, #4ecdc4);
color: var(--staging-color, #4ecdc4);
}
.action-btn.staging:hover {
background: var(--staging-color, #4ecdc4);
color: var(--text-on-accent, white);
}
.action-btn.ssh {
border-color: var(--accent-color, #5b5fcf);
color: var(--accent-color, #5b5fcf);
}
.action-btn.ssh:hover {
background: var(--accent-color, #5b5fcf);
color: var(--text-on-accent, white);
}
.action-btn.stress {
border-color: var(--stress-color, #f39c12);
color: var(--stress-color, #f39c12);
}
.action-btn.stress:hover {
background: var(--stress-color, #f39c12);
color: var(--text-on-accent, white);
}
/* Modern Resource Cards */
.resource-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
@media (min-width: 1200px) {
.resource-grid {
grid-template-columns: repeat(4, 1fr);
}
/* Make SSL card span 2 columns on large screens */
.resource-card.ssl {
grid-column: span 2;
}
}
.resource-card {
background: var(--bg-secondary, white);
border-radius: 16px;
padding: 25px;
box-shadow: 0 4px 20px var(--shadow-color, rgba(0,0,0,0.08));
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.resource-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
}
.resource-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 30px var(--shadow-color-hover, rgba(0,0,0,0.12));
}
.resource-icon {
width: 50px;
height: 50px;
background: var(--bg-hover, #f8f9ff);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
font-size: 24px;
}
.resource-card.disk .resource-icon {
background: var(--info-bg, #e8f5ff);
color: var(--info-color, #2196f3);
}
.resource-card.bandwidth .resource-icon {
background: var(--purple-bg, #f3e5f5);
color: var(--purple-color, #9c27b0);
}
.resource-card.database .resource-icon {
background: var(--success-bg, #e8f5e9);
color: var(--success-color, #4caf50);
}
.resource-card.ftp .resource-icon {
background: var(--warning-bg, #fff3e0);
color: var(--warning-color, #ff9800);
}
.resource-card.ssl .resource-icon {
background: var(--success-bg, #f0fdf4);
color: var(--success-color, #10b981);
}
/* SSL Card Special Styling */
.resource-card.ssl::before {
background: linear-gradient(90deg, #10b981 0%, #059669 100%);
}
/* Self-signed SSL styling */
.resource-card.ssl:has(.fa-exclamation-triangle)::before {
background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
}
.resource-title {
font-size: 14px;
color: var(--text-secondary, #64748b);
margin-bottom: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.resource-value {
font-size: 28px;
font-weight: 800;
color: var(--text-primary, #1e293b);
margin-bottom: 15px;
}
.resource-limit {
font-size: 14px;
color: var(--text-muted, #94a3b8);
}
.resource-progress {
background: var(--border-light, #f1f5f9);
height: 8px;
border-radius: 10px;
overflow: hidden;
margin-top: 15px;
}
.resource-progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
transition: width 0.5s ease;
}
/* Application Installer Creative Redesign */
.app-installer-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
padding: 40px;
margin-bottom: 30px;
position: relative;
overflow: hidden;
}
.app-installer-section::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 50%);
animation: float 20s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
33% { transform: translate(30px, -30px) rotate(120deg); }
66% { transform: translate(-20px, 20px) rotate(240deg); }
}
.app-installer-header {
text-align: center;
margin-bottom: 40px;
position: relative;
z-index: 1;
}
.app-installer-title {
color: white;
font-size: 28px;
font-weight: 700;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.app-installer-subtitle {
color: rgba(255,255,255,0.9);
font-size: 16px;
max-width: 600px;
margin: 0 auto;
}
.app-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
position: relative;
z-index: 1;
}
.app-card {
background: var(--bg-secondary, white);
border-radius: 16px;
padding: 30px;
text-align: center;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
text-decoration: none;
color: inherit;
position: relative;
overflow: hidden;
box-shadow: 0 4px 20px var(--shadow-color, rgba(0,0,0,0.1));
}
.app-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
.app-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 40px var(--shadow-color-hover, rgba(0,0,0,0.15));
}
.app-card:hover::before {
transform: scaleX(1);
}
.app-card:hover .app-icon-wrapper {
transform: scale(1.1) rotate(5deg);
}
.app-card:hover .install-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.app-icon-wrapper {
width: 80px;
height: 80px;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 20px;
transition: all 0.3s ease;
position: relative;
}
.app-card.wordpress .app-icon-wrapper {
background: linear-gradient(135deg, #e8f5ff 0%, #cce7ff 100%);
}
.app-card.git .app-icon-wrapper {
background: linear-gradient(135deg, #fff4e6 0%, #ffe0b2 100%);
}
.app-card.prestashop .app-icon-wrapper {
background: linear-gradient(135deg, #fce4ec 0%, #f8bbd0 100%);
}
.app-card.mautic .app-icon-wrapper {
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
}
.app-icon {
width: 50px;
height: 50px;
object-fit: contain;
}
.app-name {
font-size: 20px;
font-weight: 700;
color: var(--text-primary, #2f3640);
margin-bottom: 8px;
}
.app-description {
font-size: 14px;
color: var(--text-secondary, #64748b);
margin-bottom: 20px;
min-height: 40px;
}
.app-features {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-bottom: 20px;
}
.feature-tag {
background: var(--accent-bg, #f0f4ff);
color: var(--accent-color, #5b5fcf);
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.install-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 24px;
background: var(--bg-hover, #f8f9ff);
color: var(--accent-color, #5b5fcf);
border-radius: 8px;
font-weight: 600;
font-size: 14px;
transition: all 0.3s ease;
border: 1px solid var(--border-color, #e8e9ff);
}
.quick-badge {
position: absolute;
top: 15px;
right: 15px;
background: var(--success-color, #10b981);
color: var(--text-on-accent, white);
padding: 4px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
}
@media (max-width: 768px) {
.app-installer-section {
padding: 30px 20px;
}
.app-grid {
grid-template-columns: 1fr;
}
}
</style>
<div class="cyberpanel-website-page" ng-controller="websitePages">
<!-- Creative Hero Section -->
<div class="domain-hero">
<div class="domain-info">
<div class="domain-name">
<span id="domainNamePage">{{ domain }}</span>
<div class="domain-status">
<span class="status-dot"></span>
{% trans "Active" %}
</div>
</div>
<p class="domain-description">
{% trans "Manage your website with powerful tools and real-time monitoring" %}
</p>
<div class="hero-actions">
<a target="_blank" href="{$ previewUrl $}" class="hero-btn primary">
<i class="fas fa-external-link-alt"></i>
{% trans "Preview Website" %}
</a>
<a href="/filemanager/{{ domain }}" class="hero-btn">
<i class="fas fa-folder"></i>
{% trans "File Manager" %}
</a>
</div>
</div>
</div>
{% if not error %}
{% if accessed_via_ip %}
<div class="alert alert-danger ssh-access-warning">
<strong>Notice:</strong> You are accessing CyberPanel via an <b>IP address</b>.<br>
The Web Terminal will not work when accessed via IP. Please issue a <b>hostname SSL</b> and access the panel using your hostname (with valid SSL) to enable the terminal.<br>
<a href="{{ ssl_issue_link }}" target="_blank" class="btn btn-warning" style="margin-top:10px;">Issue Hostname SSL</a>
</div>
{% endif %}
<!-- Quick Actions Bar -->
<div class="quick-actions">
<div class="action-group">
<button type="button" class="action-btn terminal" data-toggle="modal" data-target="#web-terminal-modal">
<i class="fas fa-terminal"></i>
{% trans "Open Terminal" %}
</button>
<!-- Modal for Web Terminal -->
<div id="web-terminal-modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Web SSH Terminal</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
{% if is_selfsigned_ssl %}
<div class="alert alert-warning" style="margin-bottom:18px;">
<strong>Warning:</strong> Your server is using a <b>self-signed SSL certificate</b> for the web terminal.<br>
For security and browser compatibility, please issue a valid hostname SSL certificate.<br>
<a href="{{ ssl_issue_link }}" target="_blank" class="btn btn-warning" style="margin-top:10px;">Issue SSL Now</a>
</div>
{% endif %}
{% if not has_addons %}
<div style="background: var(--warning-bg, #fff3cd); color: var(--warning-text, #856404); border: 1px solid var(--warning-border, #ffeeba); border-radius: 8px; padding: 18px; margin-bottom: 18px; text-align: center;">
<strong>This feature requires the CyberPanel Add-ons bundle.</strong><br>
<a href="https://cyberpanel.net/cyberpanel-addons" target="_blank" style="color: #2563eb; text-decoration: underline; font-weight: 600;">Learn more & upgrade</a>
</div>
<div style="position: relative; width: 100%; height: 400px;">
<div id="xterm-container" style="width:100%;height:400px;background:var(--terminal-bg, #000);"></div>
<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: var(--overlay-bg, rgba(255,255,255,0.85)); display: flex; align-items: center; justify-content: center; z-index: 10; border-radius: 8px; font-size: 1.2em; color: var(--text-muted, #888); font-weight: 600;">
Web Terminal is disabled. Please upgrade to CyberPanel Add-ons to enable this feature.
</div>
</div>
{% else %}
<div id="xterm-container" style="width:100%;height:400px;background:var(--terminal-bg, #000);"></div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- End Open Terminal Button/Modal -->
<a class="action-btn git" href="/websites/{{ domain }}/manageGIT" title="Manage Git">
<i class="fas fa-code-branch"></i>
{% trans "Manage Git" %}
</a>
<a class="action-btn staging" href="/websites/{{ domain }}/setupStaging" title="">
<i class="fas fa-clone"></i>
{% trans "Clone/Staging" %}
</a>
<a class="action-btn ssh" href="/websites/{{ domain }}/sshAccess" title="">
<i class="fas fa-key"></i>
{% trans "SSH/SFTP Access" %}
</a>
<a class="action-btn stress" href="https://cyberpanel.net/server-load-tester" title="">
<i class="fas fa-tachometer-alt"></i>
{% trans "Stress Test" %}
</a>
</div>
</div>
<!-- Modern Resource Cards Grid -->
<div class="resource-grid">
<!-- Disk Usage Card -->
<div class="resource-card disk">
<div class="resource-icon">
<i class="fas fa-hdd"></i>
</div>
<div class="resource-title">{% trans "Disk Usage" %}</div>
<div class="resource-value">{{ diskInMB }} MB</div>
<div class="resource-limit">{% trans "of" %} {{ diskInMBTotal }} MB</div>
<div class="resource-progress">
<div class="resource-progress-bar" style="width: {{ diskUsage }}%;"></div>
</div>
</div>
<!-- Bandwidth Card -->
<div class="resource-card bandwidth">
<div class="resource-icon">
<i class="fas fa-chart-line"></i>
</div>
<div class="resource-title">{% trans "Bandwidth" %}</div>
<div class="resource-value">{{ bwInMB }} MB</div>
<div class="resource-limit">{% trans "of" %} {{ bwInMBTotal }} MB</div>
<div class="resource-progress">
<div class="resource-progress-bar" style="width: {{ bwUsage }}%;"></div>
</div>
</div>
<!-- Database Card -->
<div class="resource-card database">
<div class="resource-icon">
<i class="fas fa-database"></i>
</div>
<div class="resource-title">{% trans "Databases" %}</div>
<div class="resource-value">{{ databasesUsed }}</div>
<div class="resource-limit">{% trans "of" %} {{ databasesTotal }}</div>
</div>
<!-- FTP Card -->
<div class="resource-card ftp">
<div class="resource-icon">
<i class="fas fa-upload"></i>
</div>
<div class="resource-title">{% trans "FTP Accounts" %}</div>
<div class="resource-value">{{ ftpUsed }}</div>
<div class="resource-limit">{% trans "of" %} {{ ftpTotal }}</div>
</div>
{% if viewSSL == 1 %}
<!-- SSL Certificate Card -->
<div class="resource-card ssl">
{% if "SELF-SIGNED" in authority %}
<div class="resource-icon" style="background: #fef3c7; color: #f59e0b;">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="resource-title">{% trans "SSL CERTIFICATE" %}</div>
<div class="resource-value" style="display: flex; align-items: center; gap: 10px; margin-bottom: 8px;">
<i class="fas fa-exclamation-triangle" style="color: #f59e0b; font-size: 20px;"></i>
<span style="font-size: 20px;">{% trans "Self-Signed" %}</span>
</div>
<div class="resource-limit" style="display: flex; align-items: center; gap: 5px;">
<span style="background: #fef3c7; color: #f59e0b; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600;">
<i class="fas fa-exclamation" style="margin-right: 5px;"></i>{% trans "Not Secure" %}
</span>
<span style="color: #64748b;">• {% trans "Not trusted by browsers" %}</span>
</div>
{% else %}
<div class="resource-icon" style="background: #f0fdf4; color: #10b981;">
<i class="fas fa-shield-alt"></i>
</div>
<div class="resource-title">{% trans "SSL CERTIFICATE" %}</div>
<div class="resource-value" style="display: flex; align-items: center; gap: 10px; margin-bottom: 8px;">
{% if days|add:0 >= 30 %}
<i class="fas fa-check-circle" style="color: #10b981; font-size: 20px;"></i>
{% elif days|add:0 >= 7 %}
<i class="fas fa-exclamation-triangle" style="color: #f59e0b; font-size: 20px;"></i>
{% else %}
<i class="fas fa-exclamation-circle" style="color: #ef4444; font-size: 20px;"></i>
{% endif %}
<span style="font-size: 24px;">{{ authority }}</span>
</div>
<div class="resource-limit" style="display: flex; align-items: center; gap: 5px;">
{% if days|add:0 >= 30 %}
<span style="background: #f0fdf4; color: #10b981; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600;">
<i class="fas fa-lock" style="margin-right: 5px;"></i>{% trans "Secure" %}
</span>
<span style="color: #64748b;">• {% trans "Valid for" %} {{ days }} {% trans "days" %}</span>
{% elif days|add:0 >= 7 %}
<span style="background: #fef3c7; color: #f59e0b; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600;">
<i class="fas fa-clock" style="margin-right: 5px;"></i>{% trans "Expiring Soon" %}
</span>
<span style="color: #64748b;">• {{ days }} {% trans "days left" %}</span>
{% else %}
<span style="background: #fee2e2; color: #ef4444; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600;">
<i class="fas fa-exclamation" style="margin-right: 5px;"></i>{% trans "Critical" %}
</span>
<span style="color: #64748b;">• {{ days }} {% trans "days left" %}</span>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
</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 class="example-box-wrapper my-10">
<div class="panel panel-body">
<h3 class="content-box-header">
{% trans "Logs" %} <img ng-hide="logFileLoading" src="/static/images/loading.gif">
</h3>
<div class="content-box-wrapper">
<div class="row mt-5 mx-10">
<div class="col-md-6">
<div class="clickable-log" ng-click="fetchLogs(1)" title="{% trans 'Load Access Logs' %}">
<img src="{% static 'images/icons/log-file-format.png' %}" width="65">
<span class="h4">{% trans "Access Logs" %}</span>
</div>
</div>
<div class="col-md-6">
<div class="clickable-log" ng-click="fetchErrorLogs(1)" title="{% trans 'Load Error Logs' %}">
<img src="{% static 'images/icons/warning.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Error Logs" %}</span>
</div>
</div>
<div class="col-md-12">
<form ng-hide="hideLogs" class="form-horizontal bordered-row">
<div ng-hide="logsFeteched" class="alert alert-success">
<p>{% trans "Logs Fetched" %}</p>
</div>
<div ng-hide="couldNotFetchLogs" class="alert alert-danger">
<p>{% trans "Could not fetch logs, see the logs file through command line. Error message:" %}
{$ errorMessage $}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
<div ng-hide="fetchedData" class="mx-10">
<div class="col-sm-3">
<input placeholder="Search..." ng-model="logSearch" name="dom" type="text"
class="form-control" ng-model="domainNameCreate" required>
</div>
<div class="col-sm-2">
<input placeholder="Page Number" type="number" class="form-control"
ng-model="pageNumber" required>
</div>
<div class="col-sm-6">
<button ng-click="fetchLogs(3)" type="button"
class="btn ra-50 btn-primary mx-5">{% trans "Next" %}</button>
<button ng-click="fetchLogs(4)" type="button"
class="btn ra-50 btn-primary mx-5">{% trans "Previous" %}</button>
</div>
<div style="margin-bottom: 1%;" class=" col-sm-1">
<a ng-click="hidelogsbtn()" href="">
<!--img src="/static/images/close-32.png"-->
<h3 class="glyph-icon icon-close text-danger mt-5"></h3>
</a>
</div>
<div class="col-sm-12">
<table class="table cyber-table">
<thead>
<tr>
<th>Type</th>
<th>IP Address</th>
<th>Time</th>
<th>Resource</th>
<th>Size</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in records | filter:logSearch">
<td ng-bind="record.domain"></td>
<td ng-bind="record.ipAddress"></td>
<td ng-bind="record.time"></td>
<td ng-bind="record.resource"></td>
<td ng-bind="record.size"></td>
</tr>
</tbody>
</table>
</div>
</div>
<div ng-hide="hideErrorLogs" class="">
<div class="col-sm-2">
<input placeholder="Page Number" type="number" class="form-control"
ng-model="errorPageNumber" required>
</div>
<div class="col-sm-9">
<button ng-click="fetchErrorLogs(3)" type="button"
class="btn btn-sm ra-50 btn-primary mx-5">{% trans "Next" %}</button>
<button ng-click="fetchErrorLogs(4)" type="button"
class="btn btn-sm ra-50 btn-primary mx-5">{% trans "Previous" %}</button>
</div>
<div style="margin-bottom: 1%;" class=" col-sm-1">
<a ng-click="hideErrorLogsbtn()" href="">
<!--img src="/static/images/close-32.png"-->
<h3 class="glyph-icon icon-close text-danger mt-5"></h3>
</a>
</div>
<div class="col-sm-12">
<textarea ng-model="errorLogsData" rows="25"
class="form-control"></textarea>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="example-box-wrapper my-10">
<div class="panel panel-body">
<h3 class="content-box-header">
{% trans "Domains" %} <img ng-hide="domainLoading" src="/static/images/loading.gif">
</h3>
<div class="content-box-wrapper">
<div class="row mx-10">
<div class="col-md-3">
<a href="{% url 'CreateNewDomain' %}" title="{% trans 'Add Domains' %}">
<img src="{% static 'images/icons/domains.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Add Domains" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="#" ng-click="showListDomains(); $event.preventDefault()" title="{% trans 'List Domains' %}">
<img src="{% static 'images/icons/sort.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "List Domains" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="{$ domainAliasURL $}" target="_self" title="{% trans 'Domain Alias' %}">
<img src="{% static 'images/icons/web-domain.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Domain Alias" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="{% url 'listCron' %}?domain={{ domain }}" target="_self" title="{% trans 'Add new Cron Job' %}">
<img src="{% static 'images/icons/repeat.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Cron Jobs" %}</span>
</a>
</div>
<!---------- HTML For creating domains --------------->
<div class="col-md-12 mx-10">
<form id="domainCreationForm" name="websiteCreationForm" action="/"
class="form-horizontal bordered-row">
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Domain Name" %}</label>
<div class="col-sm-6">
<input name="dom" type="text" class="form-control"
ng-model="domainNameCreate" required>
</div>
<div style="margin-bottom: 1%;" class=" col-sm-1">
<a title="{% trans 'Cancel' %}" ng-click="hideDomainCreationForm()" href="">
<h3 class="glyph-icon icon-close text-danger mt-5"></h3>
</a>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Path" %}: /home/{{ domain }}/ </label>
<div class="col-sm-6">
<input placeholder="{% trans 'This path is relative to: ' %}{$ masterDomain $}. {% trans 'Leave empty to set default.' %}"
type="text" class="form-control" ng-model="docRootPath" required>
</div>
<div ng-show="websiteCreationForm.dom.$error.pattern"
class="current-pack">{% trans "Invalid Domain (Note: You don't need to add 'http' or 'https')" %}</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label">{% trans "Select PHP" %}</label>
<div class="col-sm-6">
<select ng-model="phpSelection" class="form-control">
{% for php in phps %}
<option>{{ php }}</option>
{% endfor %}
</select>
</div>
</div>
<div ng-hide="installationDetailsForm" ng-hide="installationDetailsForm"
class="form-group">
<label class="col-sm-3 control-label">{% trans "Additional Features" %}</label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="sslCheck" type="checkbox" value="">
SSL
</label>
</div>
</div>
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="dkimCheck" type="checkbox" value="">
DKIM Support
</label>
</div>
</div>
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<div class="checkbox">
<label>
<input ng-model="openBasedir" type="checkbox" value="">
open_basedir Protection
</label>
</div>
</div>
</div>
<div ng-hide="installationDetailsForm" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="createDomain()"
class="btn btn-primary btn-lg">{% trans "Create Domain" %}</button>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-7">
<div class="alert alert-success text-center">
<h2>{$ currentStatus $}</h2>
</div>
<div class="progress">
<div id="installProgress" class="progress-bar" role="progressbar"
aria-valuenow="70" aria-valuemin="0" aria-valuemax="100"
style="width:0%">
<span class="sr-only">70% Complete</span>
</div>
</div>
<div ng-hide="errorMessageBox" class="alert alert-danger">
<p>{% trans "Error message:" %} {$ errorMessage $}</p>
</div>
<div ng-hide="success" class="alert alert-success">
<p>{% trans "Website succesfully created." %}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
</div>
</div>
<div ng-hide="installationProgress" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-disabled="goBackDisable" ng-click="goBack()"
class="btn btn-primary btn-lg">{% trans "Go Back" %}</button>
</div>
</div>
</form>
</div>
<!---------- HTML For creating domains --------------->
<!---------- HTML For Listing domains --------------->
<div id="listDomains" class="col-md-12 mx-10">
<form ng-hide="" class="form-horizontal bordered-row">
<div ng-hide="phpChanged" class="alert alert-success">
<p>{% trans "PHP Version Changed to:" %} {$ changedPHPVersion $} </p>
</div>
<div ng-hide="domainDeleted" class="alert alert-success">
<p>{% trans "Deleted:" %} {$ deletedDomain $} </p>
</div>
<div ng-hide="sslIssued" class="alert alert-success">
<p>{% trans "SSL Issued:" %} {$ sslDomainIssued $} </p>
</div>
<div ng-hide="childBaseDirChanged" class="alert alert-success">
<p>{% trans "Changes applied successfully." %} </p>
</div>
<div ng-hide="domainError" class="alert alert-danger">
<p>{$ errorMessage $}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
<div ng-hide="" class="form-group">
<div class="col-sm-11">
<input placeholder="Search Domain..." ng-model="logSearch" name="dom"
type="text" class="form-control" ng-model="domainNameCreate"
required>
</div>
<div style="margin-bottom: 1%;" class=" col-sm-1">
<a title="{% trans 'Close' %}" ng-click="hideListDomains()" href="">
<h3 class="glyph-icon icon-close text-danger mt-5"></h3>
</a>
</div>
<div class="col-sm-12">
<table class="table cyber-table">
<thead>
<tr>
<th>Domain</th>
<th>Launch</th>
<th>Path</th>
<th>open_basedir</th>
<th>PHP</th>
<th>SSL</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in childDomains | filter:logSearch">
<td ng-bind="record.childDomain"></td>
<td><a href="{$ record.childLunch $}"><img width="30px" height="30"
class="center-block"
src="{% static 'baseTemplate/assets/image-resources/webPanel.png' %}"></a>
</td>
<td ng-bind="record.path"></td>
<td>
<select ng-change="changeChildBaseDir(record.childDomain,childBaseDir)"
ng-model="childBaseDir" class="form-control">
<option>Enable</option>
<option>Disable</option>
</select>
</td>
<td>
<select ng-change="changePHP(record.childDomain,phpSelection)"
ng-model="phpSelection" class="form-control">
{% for php in phps %}
<option>{{ php }}</option>
{% endfor %}
</select>
</td>
<td>
<button type="button"
ng-click="issueSSL(record.childDomain,record.path)"
class="btn ra-50 btn-primary">{% trans "Issue" %}</button>
</td>
<td>
<button type="button"
ng-click="deleteChildDomain(record.childDomain)"
class="btn ra-50 btn-primary">{% trans "Delete" %}</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
</div>
<!---------- HTML For Listing domains --------------->
</div>
</div>
</div>
</div>
<div class="example-box-wrapper my-10">
<div class="panel panel-body">
<h3 class="content-box-header">
{% trans "Configurations" %} <img ng-hide="configFileLoading" src="/static/images/loading.gif">
</h3>
<div class="content-box-wrapper">
<div class="row mx-10">
<div class="col-md-3">
<a href="{% url 'ApacheManager' domain=domain %}"
title="{% trans 'Apache Manager' %}">
<img src="{% static 'images/icons/file.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Apache Manager" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="#" ng-click="fetchConfigurations(); $event.preventDefault()"
title="{% trans 'Edit vHost Main Configurations' %}">
<img src="{% static 'images/icons/file.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "vHost Conf" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="#" ng-click="fetchRewriteFules(); $event.preventDefault()"
title="{% trans 'Add Rewrite Rules (.htaccess)' %}">
<img src="{% static 'images/icons/pencilcase.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Rewrite Rules" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="#" ng-click="addSSL(); $event.preventDefault()" title="{% trans 'Add Your Own SSL' %}">
<img src="{% static 'images/icons/locked.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Add SSL" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="#" ng-click="changePHPMaster(); $event.preventDefault()" title="{% trans 'Change PHP Version' %}">
<img src="{% static 'images/icons/laptop.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Change PHP" %}</span>
</a>
</div>
<!---- HTML for main ssl file ---->
<div class="col-md-12">
<form ng-hide="hidsslconfigs" class="form-horizontal bordered-row">
<div ng-hide="sslSaved" class="alert alert-success">
<p>{% trans "SSL Saved" %}</p>
</div>
<div ng-hide="couldNotSaveSSL" class="alert alert-danger">
<p>{% trans "Could not save SSL. Error message:" %} {$ errorMessage $}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
<div ng-hide="" class="form-group">
<div style="margin-bottom: 1%;" class="col-sm-offset-11 col-sm-1">
<a ng-click="hidesslbtn()" href=""><img
src="/static/images/close-32.png"></a>
</div>
<div class="col-sm-6">
<textarea placeholder="Paste Your Cert" ng-model="cert" rows="10"
class="form-control"></textarea>
</div>
<div class="col-sm-6">
<textarea placeholder="Paste Your Key" ng-model="key" rows="10"
class="form-control"></textarea>
</div>
</div>
<div ng-hide="" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="saveSSL()"
class="btn btn-primary btn-lg">{% trans "Save" %}</button>
</div>
</div>
</form>
</div>
<!----- HTML For SSL ---->
<!---- HTML for main conf file ---->
<div class="col-md-12">
<form ng-hide="configurationsBox" class="form-horizontal bordered-row">
<div ng-hide="configsFetched" class="alert alert-success">
<p>{% trans "Current configuration in the file fetched." %}</p>
</div>
<div ng-hide="couldNotFetchConfigs" class="alert alert-danger">
<p>{% trans "Could not fetch current configuration. Error message:" %} {$
errorMessage $}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
<div ng-hide="configSaved" class="alert alert-success">
<p>{% trans "Configurations saved." %}</p>
</div>
<div ng-hide="couldNotSaveConfigurations" class="alert alert-danger">
<p>{% trans "Could not fetch current configuration. Error message:" %} {$
errorMessage $}</p>
</div>
<div ng-hide="fetchedConfigsData" class="form-group">
<div style="margin-bottom: 1%;" class="col-sm-offset-11 col-sm-1">
<a ng-click="hideconfigbtn()" href=""><img
src="/static/images/close-32.png"></a>
</div>
<div class="col-sm-12">
<textarea ng-model="configData" rows="20" class="form-control"></textarea>
</div>
</div>
<div ng-hide="saveConfigBtn" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="saveCongiruations()"
class="btn btn-primary btn-lg">{% trans "Save" %}</button>
</div>
</div>
</form>
</div>
<!-- HTML For rewrite rules-->
<div class="col-md-12">
<form ng-hide="configurationsBoxRewrite" class="form-horizontal bordered-row">
<form name="websiteCreationForm" action="/" id="createPackages"
class="form-horizontal bordered-row panel-body">
<div ng-hide="fetchedRewriteRules" class="form-group">
<label class="col-sm-3 control-label">{% trans "Select Template" %}</label>
<div class="col-sm-6">
<select ng-change="applyRewriteTemplate()" ng-model="rewriteTemplate"
class="form-control">
<option>Force HTTP -> HTTPS</option>
<option>Force WWW -> NON-WWW</option>
<option>Force NON-WWW -> WWW</option>
<option>Disable Wordpress XMLRPC & Trackback</option>
</select>
</div>
</div>
</form>
<div ng-hide="couldNotFetchRewriteRules" class="alert alert-danger">
<p>{% trans "Could not fetch current rewrite rules. Error message:" %} {$
errorMessage $}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
<div ng-hide="rewriteRulesSaved" class="alert alert-success">
<p>{% trans "Configurations saved." %}</p>
</div>
<div ng-hide="couldNotSaveRewriteRules" class="alert alert-danger">
<p>{% trans "Could not save rewrite rules. Error message:" %} {$ errorMessage
$}</p>
</div>
<div ng-hide="fetchedRewriteRules" class="form-group">
<div style="margin-bottom: 1%;" class="col-sm-offset-11 col-sm-1">
<a ng-click="hideRewriteRulesbtn()" href=""><img
src="/static/images/close-32.png"></a>
</div>
<div class="col-sm-12">
<textarea ng-model="rewriteRules" rows="10" class="form-control"></textarea>
</div>
</div>
<div ng-hide="saveRewriteRulesBTN" class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button style="margin-top: 5%" type="button" ng-click="saveRewriteRules()"
class="btn btn-primary btn-lg">{% trans "Save Rewrite Rules" %}</button>
</div>
</div>
</form>
</div>
<!--- HTML To change PHP --->
<div class="col-md-12">
<form ng-hide="changePHPView" name="" action="/" class="form-horizontal bordered-row">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Select PHP" %}</label>
<div class="col-sm-6">
<select ng-model="phpSelectionMaster" class="form-control">
{% for php in phps %}
<option>{{ php }}</option>
{% endfor %}
</select>
</div>
<div style="margin-bottom: 1%;" class=" col-sm-1">
<a title="{% trans 'Cancel' %}" ng-click="hideChangePHPMaster()"
href=""><h3 class="glyph-icon icon-close text-danger mt-5"></h3></a>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="changePHPVersionMaster()"
class="btn btn-primary btn-lg">{% trans "Change PHP" %}</button>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<div ng-hide="failedToChangePHPMaster" class="alert alert-danger">
<p>{% trans "Failed to change PHP version. Error message:" %} {$
errorMessage $}</p>
</div>
<div ng-hide="phpChangedMaster" class="alert alert-success">
<p>{% trans "PHP successfully changed for: " %} <strong>{$ websiteDomain
$}</strong></p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="example-box-wrapper my-10">
<div class="panel panel-body">
<h3 class="content-box-header">
{% trans "Files" %}
</h3>
<div class="content-box-wrapper">
<div class="row mx-10">
<div class="col-md-3">
<a href="{$ fileManagerURL $}" target="_self" title="{% trans 'File Manager' %}">
<img src="{% static 'images/icons/office-material.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "File Manager" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="#" ng-click="openBaseDirView(); $event.preventDefault()" title="{% trans 'open_basedir Protection' %}">
<img src="{% static 'images/icons/open_basedir.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "open_basedir" %}</span>
</a>
</div>
{% if ftp %}
<div class="col-md-3">
<a href="{% url 'createFTPAccount' %}" title="{% trans 'Create FTP Account' %}">
<img src="{% static 'images/icons/ftp-upload.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Create FTP Acct" %}</span>
</a>
</div>
<div class="col-md-3">
<a href="{% url 'deleteFTPAccount' %}" title="{% trans 'Delete FTP Account' %}">
<img src="{% static 'images/icons/delete-ftp.png' %}" width="65" class="mr-10">
<span class="h4">{% trans "Delete FTP Acct" %}</span>
</a>
</div>
{% endif %}
<!--- HTML To change open_basedir --->
<div ng-hide="openBaseDirBox" class="col-md-12">
<form action="/" class="form-horizontal bordered-row">
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "open_basedir Protection" %}</label>
<div class="col-sm-6">
<select ng-model="openBasedirValue" class="form-control">
<option>Enable</option>
<option>Disable</option>
</select>
</div>
<div ng-hide="baseDirLoading" style="margin-bottom: 1%;" class=" col-sm-1">
<img src="{% static 'images/loading.gif' %}">
</div>
<div style="margin-bottom: 2%;" class="col-sm-1">
<a title="{% trans 'Cancel' %}" ng-click="hideOpenBasedir()" href=""><img
src="/static/images/close-32.png"></a>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<button type="button" ng-click="applyOpenBasedirChanges()"
class="btn btn-primary btn-lg">{% trans "Apply Changes" %}</button>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-4">
<div ng-hide="operationFailed" class="alert alert-danger">
<p>{% trans "Error message:" %} {$ errorMessage $} </p>
</div>
<div ng-hide="operationSuccessfull" class="alert alert-success">
<p>{% trans "Changes successfully saved." %}</p>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<p>{% trans "Could not connect to server. Please refresh this page." %}</p>
</div>
</div>
</div>
</form>
</div>
<!--- HTML To change open_basedir --->
</div>
</div>
</div>
</div>
{% if email %}
{% if marketingStatus %}
<div class="example-box-wrapper my-10">
<div class="panel panel-body">
<h3 class="content-box-header">
{% trans "Email Marketing" %}
</h3>
<div class="content-box-wrapper">
<div class="row mx-10">
<div class="col-md-3">
<a id="emailLists" href="#" title="{% trans 'Create Lists' %}">
<img src="{% static 'emailMarketing/mailing.png' %}" width="65"
class="mr-10">
<span class="h4">{% trans "Create Lists" %}</span>
</a>
</div>
<div class="col-md-3">
<a id="manageLists" href="#" title="{% trans 'Manage Lists' %}">
<img src="{% static 'emailMarketing/checklist.png' %}" width="65"
class="mr-10">
<span class="h4">{% trans "Manage Lists" %}</span>
</a>
</div>
<div class="col-md-3">
<a id="manageSMTPHosts" href="#" title="{% trans 'SMTP Hosts' %}">
<img src="{% static 'emailMarketing/post-office.png' %}" width="65"
class="mr-10">
<span class="h4">{% trans "SMTP Hosts" %}</span>
</a>
</div>
<div class="col-md-3">
<a id="composeEmails" href="#" title="{% trans 'Compose Message' %}">
<img src="{% static 'emailMarketing/compose.png' %}" width="65"
class="mr-10">
<span class="h4">{% trans "Compose" %}</span>
</a>
</div>
<div class="col-md-3">
<a id="sendEmailsPage" href="#" title="{% trans 'Send Emails' %}">
<img src="{% static 'emailMarketing/paper-plane.png' %}" width="65"
class="mr-10">
<span class="h4">{% trans "Send Emails" %}</span>
</a>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endif %}
<!-- Creative Application Installer Section -->
<div class="app-installer-section">
<div class="app-installer-header">
<h2 class="app-installer-title">
<i class="fas fa-magic" style="margin-right: 15px;"></i>
{% trans "Quick App Installer" %}
</h2>
<p class="app-installer-subtitle">
{% trans "Deploy popular applications with just one click. Pre-configured for optimal performance." %}
</p>
</div>
<div class="app-grid">
<!-- WordPress Card -->
<a href="{$ wordPressInstallURL $}" class="app-card wordpress">
<div class="quick-badge">1-Click</div>
<div class="app-icon-wrapper">
<img src="{% static 'images/icons/wordpress.png' %}" alt="WordPress" class="app-icon">
</div>
<h3 class="app-name">WordPress</h3>
<p class="app-description">{% trans "World's most popular CMS with built-in LSCache" %}</p>
<div class="app-features">
<span class="feature-tag">LSCache</span>
<span class="feature-tag">SEO Ready</span>
<span class="feature-tag">Blog</span>
</div>
<div class="install-btn">
<i class="fas fa-download"></i>
{% trans "Install Now" %}
</div>
</a>
<!-- Git Card -->
<a href="/websites/{{ domain }}/manageGIT" class="app-card git">
<div class="app-icon-wrapper">
<img src="{% static 'images/icons/git-logo.png' %}" alt="Git" class="app-icon">
</div>
<h3 class="app-name">Git Integration</h3>
<p class="app-description">{% trans "Version control and deployment automation" %}</p>
<div class="app-features">
<span class="feature-tag">Deploy</span>
<span class="feature-tag">CI/CD</span>
<span class="feature-tag">Backup</span>
</div>
<div class="install-btn">
<i class="fas fa-code-branch"></i>
{% trans "Setup Git" %}
</div>
</a>
<!-- PrestaShop Card -->
<a href="{$ installPrestaURL $}" class="app-card prestashop">
<div class="quick-badge">E-Commerce</div>
<div class="app-icon-wrapper">
<img src="{% static 'images/icons/prestashop.png' %}" alt="PrestaShop" class="app-icon">
</div>
<h3 class="app-name">PrestaShop</h3>
<p class="app-description">{% trans "Professional e-commerce platform" %}</p>
<div class="app-features">
<span class="feature-tag">Shop</span>
<span class="feature-tag">Payment</span>
<span class="feature-tag">Multi-Lang</span>
</div>
<div class="install-btn">
<i class="fas fa-shopping-cart"></i>
{% trans "Install Now" %}
</div>
</a>
<!-- Mautic Card -->
<a href="{$ installMauticURL $}" class="app-card mautic">
<div class="app-icon-wrapper">
<img src="{% static 'baseTemplate/images/icons/mautic.png' %}" alt="Mautic" class="app-icon">
</div>
<h3 class="app-name">Mautic</h3>
<p class="app-description">{% trans "Open source marketing automation" %}</p>
<div class="app-features">
<span class="feature-tag">Marketing</span>
<span class="feature-tag">Email</span>
<span class="feature-tag">Analytics</span>
</div>
<div class="install-btn">
<i class="fas fa-chart-line"></i>
{% trans "Install Now" %}
</div>
</a>
</div>
</div>
{% else %}
<div class="alert alert-danger">
<p>{{ domain }}</p>
</div>
{% endif %}
</div>
{% 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>
<!-- Fix modal functionality -->
<script>
// Wait for both jQuery and Bootstrap to be loaded
window.addEventListener('load', function() {
// Double-check that jQuery and Bootstrap modal are available
if (typeof $ !== 'undefined' && $.fn.modal) {
// Ensure modal is properly initialized and hidden
$('#web-terminal-modal').modal({
show: false,
backdrop: true,
keyboard: true
});
// Fix modal backdrop issues
$('#web-terminal-modal').on('hidden.bs.modal', function () {
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
});
// Fix modal z-index issues
$('#web-terminal-modal').on('show.bs.modal', function () {
var zIndex = 1040 + (10 * $('.modal:visible').length);
$(this).css('z-index', zIndex);
setTimeout(function() {
$('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack');
}, 0);
});
// Initialize terminal when modal is shown
var terminal = null;
var socket = null;
$('#web-terminal-modal').on('shown.bs.modal', function () {
// Check if terminal is already initialized
if (terminal) return;
// Initialize xterm terminal
terminal = new Terminal({
cursorBlink: true,
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
theme: {
background: '#000000',
foreground: '#d4d4d4'
}
});
// Open terminal in container
terminal.open(document.getElementById('xterm-container'));
terminal.write('Fetching authentication token...\r\n');
terminal.focus();
// Get domain name
var domain = document.getElementById('domainNamePage').textContent.trim();
// Get CSRF token
var csrftoken = getCookie('csrftoken');
// Helper function to get cookie
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Fetch JWT token from backend
$.ajax({
url: '/websites/getTerminalJWT',
type: 'POST',
data: JSON.stringify({ domain: domain }),
contentType: 'application/json',
headers: { 'X-CSRFToken': csrftoken },
success: function(response) {
if (response.status === 1 && response.token) {
var token = response.token;
var ssh_user = response.ssh_user;
var wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
var wsUrl = wsProto + '://' + window.location.hostname + ':8888/ws?token=' + encodeURIComponent(token) + '&ssh_user=' + encodeURIComponent(ssh_user);
terminal.write('\r\nConnecting to terminal...\r\n');
socket = new WebSocket(wsUrl);
socket.binaryType = 'arraybuffer';
socket.onopen = function() {
terminal.write('\x1b[32mConnected.\x1b[0m\r\n');
};
socket.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) {
var text = new Uint8Array(event.data);
terminal.write(new TextDecoder().decode(text));
} else if (typeof event.data === 'string') {
terminal.write(event.data);
}
};
socket.onerror = function(error) {
terminal.write('\r\n\x1b[31mWebSocket error. Please check your connection.\x1b[0m\r\n');
console.error('WebSocket error:', error);
};
socket.onclose = function() {
terminal.write('\r\n\x1b[31mConnection closed.\x1b[0m\r\n');
};
// Send terminal input to server
terminal.onData(function(data) {
if (socket && socket.readyState === WebSocket.OPEN) {
var encoder = new TextEncoder();
socket.send(encoder.encode(data));
}
});
// Handle terminal resize
terminal.onResize(function(size) {
if (socket && socket.readyState === WebSocket.OPEN) {
var msg = JSON.stringify({resize: {cols: size.cols, rows: size.rows}});
socket.send(msg);
}
});
} else {
terminal.write('\x1b[31mFailed to get terminal token.\x1b[0m\r\n');
if (response.error) {
terminal.write('\x1b[31m' + response.error + '\x1b[0m\r\n');
}
}
},
error: function(xhr, status, error) {
terminal.write('\x1b[31mFailed to contact backend: ' + error + '\x1b[0m\r\n');
console.error('Failed to get terminal token:', error);
}
});
});
// Clean up when modal is hidden
$('#web-terminal-modal').on('hidden.bs.modal', function () {
if (socket) {
socket.close();
socket = null;
}
if (terminal) {
terminal.dispose();
terminal = null;
}
// Clear the container
document.getElementById('xterm-container').innerHTML = '';
});
} else {
console.error('Bootstrap modal not loaded properly');
}
});
</script>
{% endblock %}