Files
openpanel/templates/admini/manager/nodejs.html
2024-10-25 01:44:26 +02:00

866 lines
34 KiB
HTML

<!-- manager/nodejs.html -->
{% extends 'base.html' %}
{% block content %}
<div class="modal fade" id="installPIPModal" tabindex="-1" role="dialog" aria-labelledby="installPIPModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="installPIPModalLabel">{{ _('Application Logs') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ _('Close') }}"></button>
</div>
<div class="modal-body bg-dark">
<div id="pipinstalllogprogress" class="mb-0"></div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="viewModal" tabindex="-1" role="dialog" aria-labelledby="viewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="viewModalLabel">{{ _('Application Logs') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ _('Close') }}"></button>
</div>
<div class="modal-body bg-dark">
<div id="fileContent" class="mb-0"></div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/punycode/2.1.1/punycode.min.js"></script>
<style>
.st1 {
fill: #192030;
}
/* Style for dark mode svg icons */
[data-skin="dark"] .st1 {
fill: #506fd9;
}
</style>
<style>
.nije-link {
text-decoration: none;
color: black;
border-bottom: 1px dashed black;
}
.ikona:hover {
background: aliceblue;
}
#action_description p {
display: none;
margin-bottom: 0px;
margin-top: 1rem;
}
</style>
{% if current_domain %}
<div class="card">
<div class="row">
<div class="col-md-auto">
{% include 'partials/screenshot.html' %}
</div>
<div class="col">
<a href="#" onclick="updateScreenshot()" id="refresh_site_screenshot">
<i class="bi bi-arrow-clockwise" style="left: 15px;top: 15px;position: absolute; padding: 6px 10px;" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Refresh website screenshot"></i>
</a>
<div class="row">
<div class="col">
<div class="card-body">
<small class="card-text">Status</small>
<p class="card-text {% if pm2_data[8] == 'stopped' %}text-danger{% elif pm2_data[8] == 'online' %}text-success{% else %}text-warning{% endif %}">● {{ pm2_data[8] }}</p>
</div>
</div>
<div class="col">
<div class="card-body">
<small class="card-text">{{ _('Type') }}</small>
<p class="card-text"><i class=""><img src="/static/images/icons/nodejs.png" style="height: 1em;"></i> {{ container.type }}</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card-body">
<small class="card-text">{{ _('Files') }} <span id="filesSize">({{ _('Calculating size...') }})</span></small>
<p class="card-text"><a class="nije-link" href="/files/{{ current_domain }}">{{ domain_directory }}</a></p>
</div>
</div>
<div class="col">
<div class="card-body">
<small class="card-text">{{ _('Domain:') }}</small>
<p class="card-text">
<span id="favicon"></span> <a class="punycode nije-link" href="/domains">
{{ current_domain }}
</a>
</p>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card-body">
<small class="card-text">
<a class="nije-link" data-bs-toggle="collapse" href="#collapseAppInfo" aria-expanded="false" aria-controls="collapseAppInfo">{{ _('Status Information') }}</a>
</small>
<p class="card-text">
<div class="collapse" id="collapseAppInfo">
<ul class="list-group">
<li class="list-group-item"><small>{{ _('Uptime:') }}</small> <span><b>{{ pm2_data[6] }}</b></span></li>
<li class="list-group-item"><small>{{ _('Restarts:') }}</small> <span><b>{{ pm2_data[7] }}</b></span></li>
<li class="list-group-item"><small>{{ _('Watching:') }}</small> <span><b>{{ pm2_data[12] }}</b></span></li>
</ul>
</div>
</div>
</div>
<div class="col">
<div class="card-body">
<small class="card-text">{{ _('Created') }} <a data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="{{ container.created_date }}"><i class="bi bi-info-circle"></i></a></small>
</button>
<p class="card-text">
<span id="created_date">{{ container.created_date }}</span>
</p>
<script>
function GetFIlesSize() {
const filesSizeSpan = document.getElementById("filesSize");
const currentDomain = "{{current_domain}}";
// Fetch files size
fetch(`/get-files-size?selected_domain=${encodeURIComponent(currentDomain)}`)
.then(response => response.json())
.then(data => {
filesSizeSpan.textContent = "(" + data.size + ")";
})
.catch(error => {
console.error("An error occurred:", error);
});
}
GetFIlesSize()
</script>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card-body">
<small class="card-text">{{ _('NodeJS version:') }}</small>
<h4 class="card-text"><span id="nodejs-version"></span></h4>
</div>
</div><div class="col">
<div class="card-body">
<small class="card-text">{{ _('CPU usage:') }}</small>
<h4 class="card-text"><i class="dashboard_icon bi bi-cpu"></i> {{ pm2_data[9] }}</h4>
</div>
</div>
<div class="col">
<div class="card-body">
<small class="card-text">{{ _('Memory usage:') }}</small>
<h4 class="card-text"><i class="dashboard_icon bi bi-memory"></i> {{ pm2_data[10] }}</span></h4>
</div>
</div>
</div>
</div>
</div>
</div>
<!--p>domain id: {{ container.domain_id }}</p-->
<div class="row my-4">
<div role="group" aria-label="WP Actions" style="width: 100%; margin-top: 0px;" class="btn-group">
{% if 'temporary_links' in enabled_modules %}
<button type="button" class="btn btn-outline-dark btn-lg" data-bs-description="preview" data-selected-domain="{{ current_domain }}" data-bs-toggle="modal" data-bs-target="#previewModal" onclick="sendDataToPreview(event)"><span class="desktop-only">{{ _('Preview') }}</span><span class="mobile-only"><i class="bi bi-box-arrow-up-right"></i></span></button>
<script>
function sendDataToPreview(event) {
const button = event.currentTarget;
const domain = button.getAttribute('data-selected-domain');
fetch(`/domains/temporary-link?domain=${encodeURIComponent(domain)}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.link) {
window.open(data.link, '_blank');
} else {
console.error('No link found in the response.');
}
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
{% endif %}
<button type="button" class="btn btn-outline-primary view-button btn-lg" data-file="{{ pm2_data[1] }}" data-type="logs" data-bs-description="logs">
<span class="desktop-only">{{ _('View Error Log') }}</span><span class="mobile-only"><i class="bi bi-bug"></i></span>
</button>
{% if pm2_data[8] == 'stopped' %}
<button type="button" class="btn btn-outline-success btn-lg" data-bs-description="start" onclick="startApp(this);">
<i class="bi bi-play-fill"></i><span class="desktop-only"> {{ _('Start') }}</span>
</button>
<script>
function startApp(button) {
// Get the PM2 process ID
const pm2Id = "{{ pm2_data[1] }}";
// Create a new XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', `/pm2/start/${pm2Id}`, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Optionally handle the response
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
// Handle success (e.g., show a success message or refresh the page)
console.log('Start successful:', xhr.responseText);
// Optionally refresh the application list or update the UI
} else {
// Handle error (e.g., show an error message)
console.error('Start failed:', xhr.statusText);
}
};
// Send the request
xhr.send();
}
</script>
{% else %}
<button type="button" class="btn btn-outline-danger btn-lg" data-bs-description="stop" onclick="stopApp(this);">
<i class="bi bi-stop-fill"></i><span class="desktop-only"> {{ _('Stop') }}</span>
</button>
<script>
function stopApp(button) {
// Get the PM2 process ID
const pm2Id = "{{ pm2_data[1] }}";
// Create a new XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', `/pm2/stop/${pm2Id}`, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Optionally handle the response
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
// Handle success (e.g., show a success message or refresh the page)
console.log('Stop successful:', xhr.responseText);
// Optionally refresh the application list or update the UI
} else {
// Handle error (e.g., show an error message)
console.error('Stop failed:', xhr.statusText);
}
};
// Send the request
xhr.send();
}
</script>
{% endif %}
<button type="button" data-bs-description="restart" id="restartButton" class="btn btn-primary btn-lg"></i><i class="bi bi-arrow-clockwise"></i><span class="desktop-only"> {{ _('Restart') }}</span></button>
<script>
document.getElementById('restartButton').addEventListener('click', function(event) {
event.preventDefault(); // Prevent the default button action
// Get the PM2 process ID
const pm2Id = "{{ pm2_data[1] }}";
// Create a new XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', `/pm2/restart/${pm2Id}`, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Optionally handle the response
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
// Handle success (e.g., show a success message)
console.log('Restart successful:', xhr.responseText);
} else {
// Handle error (e.g., show an error message)
console.error('Restart failed:', xhr.statusText);
}
};
// Send the request
xhr.send();
});
</script>
<button type="button" class="btn btn-danger btn-lg" data-bs-description="uninstall" onclick="confirmAppDelete(this);"><i class="bi bi-trash3"></i><span class="desktop-only"> {{ _('Uninstall') }}</span></button>
</div>
<div id="action_description" class="">
<p id="blank-id">&nbsp;</p>
<p id="preview-description">{{ _("Preview website with a temporary domain,") }} <b>{{ _("valid for 15 minutes only!") }}</b> {{ _("Helpful if your domain hasn't been pointed to the server's IP address yet and lacks an SSL certificate.") }}</p>
<p id="logs-description">{{ _("View application error log file.") }}</p>
<p id="start-description">{{ _("Start the application process and make app available again on the domain.") }}</p>
<p id="stop-description">{{ _('Stop the application process and make app temporary unavailable.') }}</p>
<p id="restart-description">{{ _('Restart the nodejs application (executes pm2 stop and pm2 start commands).') }}</p>
<p id="uninstall-description">{{ _('Completely remove the nodejs application, including the domain proxy, and records from this manager.') }}</p>
</div>
</div>
<script>
function confirmAppDelete(button) {
var countdown = 5;
var countdownActive = true; // Variable to track countdown status
// Change the button style and text
$(button).removeClass('btn-danger').addClass('btn-dark').html('<i class="bi bi-trash3-fill"></i> Confirm <span class="btn-indicator btn-indicator-mini bg-danger">' + countdown + '</span>');
// Interval to update countdown
var intervalId = setInterval(function () {
countdown--;
// Update the countdown value in the button text
$(button).find('.btn-indicator-mini').text(countdown);
// Remove the onclick event to prevent further changes on subsequent clicks
$(button).removeAttr('onclick');
// If countdown reaches 0, revert the button, clear the interval, and set countdownActive to false
if (countdown === 0) {
clearInterval(intervalId);
revertButton(button);
countdownActive = false;
}
}, 1000);
// Add a click event to the confirm button
$(button).on('click', function () {
// Check if countdown is active before allowing form submission
if (countdownActive) {
// Submit the parent form when the button is clicked during the countdown
const pm2Id = "{{ pm2_data[1] }}";
// Create a new XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', `/pm2/delete/${pm2Id}`, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// Optionally handle the response
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
// Handle success (e.g., show a success message or refresh the page)
console.log('Deletion successful:', xhr.responseText);
// Optionally remove the deleted item from the DOM or refresh the list
} else {
// Handle error (e.g., show an error message)
console.error('Deletion failed:', xhr.statusText);
}
};
// Send the request
xhr.send();
}
});
}
// Function to revert the button to its initial state
function revertButton(button) {
$(button).removeClass('btn-dark').addClass('btn-danger').html('<i class="bi bi-trash3"></i> Delete');
$(button).attr('onclick', 'confirmAppDelete(this);');
}
</script>
<script>
$('html').on('click', '.view-button', function() {
const button = $(this);
const domain = button.data('file');
fetch(`/pm2/logs/${encodeURIComponent(domain)}`)
.then(response => response.text())
.then(data => {
const modalTitle = $('#viewModalLabel');
const modalBody = $('#viewModal').find('.modal-body');
modalTitle.text("Error log for application: " + domain);
modalBody.empty();
const textContent = document.createElement('pre');
textContent.textContent = data;
modalBody.append(textContent);
$('#viewModal').modal('show');
})
.catch(error => {
console.error('{{ _("Error fetching pm2 logs for applicaiton:") }}', error);
});
});
</script>
<script>
$('html').on('click', '.npm-install-link', function() {
const button = $(this);
var domain = new URLSearchParams(window.location.search).get('domain');
fetch(`/pm2/pip/${encodeURIComponent(domain)}`)
.then(response => response.text())
.then(data => {
const modalTitle = $('#installPIPModalLabel');
const modalBody = $('#installPIPModal').find('.modal-body');
modalTitle.text("Running NPM install for application: " + domain);
modalBody.empty();
const textContent = document.createElement('pre');
textContent.textContent = data;
modalBody.append(textContent);
$('#installPIPModal').modal('show');
})
.catch(error => {
console.error('{{ _("Error running npm install for application:") }}', error);
});
});
</script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
// Get the value of the "domain" parameter from the URL
var domainParam = new URLSearchParams(window.location.search).get('domain');
const pipinstallLink = document.getElementById('npm-install-link');
const requirementsstatusElement = document.getElementById('requirements_status');
// Use setTimeout to send the AJAX request 100ms after the page loads
setTimeout(function() {
$.ajax({
type: "GET",
url: "/website/node_info/" + domainParam,
dataType: "json",
success: function(data) {
var nodejsVersion = data.nodejs_version;
var sslStatus = data.ssl_status;
var requirementsFile = data.requirements_file;
var currentDomain = "{{ current_domain }}";
// Check if currentDomain is in punycode format
if (punycode.toASCII(currentDomain) !== currentDomain) {
currentDomain = punycode.toUnicode(currentDomain);
}
if (requirementsFile.includes("exists")) {
requirementsstatusElement.textContent = 'package.json detected';
requirementsstatusElement.classList.remove('bg-dark', 'bg-warning');
requirementsstatusElement.classList.add('bg-success');
pipinstallLink.setAttribute('data-bs-title', `Click to run NPM install from package.json file`);
pipinstallLink.href = `/pm2/npm/${currentDomain}`;
pipinstallLink.target = '_blank';
} else {
requirementsstatusElement.textContent = 'package.json not found';
requirementsstatusElement.classList.remove('bg-dark', 'bg-success');
requirementsstatusElement.classList.add('bg-secondary');
pipinstallLink.setAttribute('data-bs-title', `Add package.json file first to run npm install`);
pipinstallLink.href = `#`;
}
const tooltip = new bootstrap.Tooltip(pipinstallLink);
tooltip.enable();
// Update SSL status display based on the response
var sslButton;
if (sslStatus.includes("VALID")) {
var expiryDate = new Date(sslStatus.split(": ")[1]);
var day = expiryDate.getDate();
var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var monthName = monthNames[expiryDate.getMonth()];
var year = expiryDate.getFullYear();
var expiryStatus = day + ' ' + monthName + ' ' + year;
$("#site_link_https").html(`<a href="https://${currentDomain}" target="_blank" class="btn btn-primary d-flex align-items-center gap-2">https://${currentDomain} <i class="bi bi-box-arrow-up-right"></i></a>`);
$("#https_link").html(`<a href="https://${currentDomain}" target="_blank"><img id="screenshot-image" style="width: 700px;" src="/screenshot/{{ current_domain }}" alt="Screenshot of {{ current_domain }}" class="img-fluid"></a>`);
sslButton = `<a href="/ssl" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ _('Expires on:') }} ${expiryStatus}"><span style="color:green;"> {{ _('Valid SSL') }}</span></a>`;
} else {
sslButton = '<a href="/ssl"><span style="color:red;"> {{ _("SSL not detected") }}</span></a>';
}
// Set the HTML content including SSL status and tooltip
$("#site-ssl-status").html(sslButton);
$("#nodejs-version").text(nodejsVersion);
},
error: function(error) {
// Handle any errors here
console.error(error);
}
});
}, 1); // Delay in milliseconds
$('#action_description p').hide();
// Show corresponding description on hover
$('.btn').hover(function() {
var descriptionId = $(this).data('bs-description') + '-description';
//var blankId = '#blank-id';
$('#' + descriptionId).show();
//$(blankId).hide();
}, function() {
var descriptionId = $(this).data('bs-description') + '-description';
//var blankId = '#blank-id';
$('#' + descriptionId).hide();
//$(blankId).show();
});
});
</script>
<hr>
<div class="row mt-3">
{% include 'partials/pagespeed.html' %}
<div class="col-md-9">
<div class="row">
<div class="col-md-2">
<a style="text-decoration: none;" href="/files/{{ current_domain }}" target="_blank"><div class="card" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="{{ _('Open application folder in FileManager') }}"">
<div class="card-body ikona text-center">
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="filemanager" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="width:50px; padding-bottom:1em; enable-background:new 0 0 58 58;" xml:space="preserve">
<style type="text/css">
.st0{fill:#BFCCE0;}
.st1{fill:#192030;}
</style>
<path class="st0" d="M52,14H6c-3.3,0-6,2.7000008-6,6v26c0,6.5999985,5.4000001,12,12,12h34c6.5999985,0,12-5.4000015,12-12V20
C58,16.7000008,55.2999992,14,52,14z"/>
<path class="st1" d="M36.0000038,42H21.9999981C20.8999996,42,20,41.1000023,20,40.0000038v-0.0000076
C20,38.8999977,20.8999996,38,21.9999981,38h14.0000057C37.1000023,38,38,38.8999977,38,39.9999962v0.0000076
C38,41.1000023,37.1000023,42,36.0000038,42z"/>
<path class="st1" d="M36.0000038,34H21.9999981C20.8999996,34,20,33.1000023,20,32.0000038v-0.0000057
C20,30.8999996,20.8999996,30,21.9999981,30h14.0000057C37.1000023,30,38,30.8999996,38,31.9999981v0.0000057
C38,33.1000023,37.1000023,34,36.0000038,34z"/>
<path class="st1" d="M6,14h46c0.3412476,0,0.6739502,0.0354614,1,0.0908813V14v-1V6c0-3.2999878-2.7000122-6-6-6H35
c-3.2999878,0-6,2.7000122-6,6v1H11c-3.2999878,0-6,2.7000122-6,6v1.0908813C5.3260498,14.0354614,5.6587524,14,6,14z"/>
</svg>
<h6 class="card-title">{{ _('File Manager') }}</h6>
</div>
</div></a>
</div>
<div class="col-md-2">
<a style="text-decoration: none;" id="pm2link" href="/pm2">
<div class="card" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="{{ _('Go to Applications Manager') }}">
<div class="card-body ikona text-center">
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="DB" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="width:50px; padding-bottom:1em; enable-background:new 0 0 58 58;" xml:space="preserve">
<style type="text/css">
.st0{fill:#BFCCE0;}
.st1{fill:#192030;}
</style>
<path class="st0" d="M52,16H6c-3.3,0-6-2.6999998-6-6V6c0-3.3,2.7-6,6-6h46c3.2999992,0,6,2.7,6,6v4
C58,13.3000002,55.2999992,16,52,16z"/>
<path class="st1" d="M29.0000019,10H16.9999981C15.8999987,10,15,9.1000013,15,8.0000019V7.9999981
C15,6.8999991,15.8999987,6,16.9999981,6h12.0000038C30.1000004,6,31,6.8999991,31,7.9999981v0.0000038
C31,9.1000013,30.1000004,10,29.0000019,10z"/>
<circle class="st1" cx="8" cy="8" r="2"/>
<path class="st0" d="M52,37H6c-3.3,0-6-2.7000008-6-6v-4c0-3.2999992,2.7-6,6-6h46c3.2999992,0,6,2.7000008,6,6v4
C58,34.2999992,55.2999992,37,52,37z"/>
<path class="st1" d="M29.0000019,31H16.9999981C15.8999987,31,15,30.1000004,15,29.0000019v-0.0000038
C15,27.8999996,15.8999987,27,16.9999981,27h12.0000038C30.1000004,27,31,27.8999996,31,28.9999981v0.0000038
C31,30.1000004,30.1000004,31,29.0000019,31z"/>
<circle class="st1" cx="8" cy="29" r="2"/>
<path class="st0" d="M52,58H6c-3.3,0-6-2.7000008-6-6v-4c0-3.2999992,2.7-6,6-6h46c3.2999992,0,6,2.7000008,6,6v4
C58,55.2999992,55.2999992,58,52,58z"/>
<path class="st1" d="M29.0000019,52H16.9999981C15.8999987,52,15,51.1000023,15,50.0000038v-0.0000076
C15,48.8999977,15.8999987,48,16.9999981,48h12.0000038C30.1000004,48,31,48.8999977,31,49.9999962v0.0000076
C31,51.1000023,30.1000004,52,29.0000019,52z"/>
<circle class="st1" cx="8" cy="50" r="2"/>
</svg>
<h6 class="card-title">{{ _('PM2') }}</h6>
</div>
</div></a>
</div>
<div class="col-md-2">
<a style="text-decoration: none;" id="npm-install-link" href="/npm-install" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="{{ _('Install dependencies from package.json file') }}">
<div class="card">
<div class="card-body ikona text-center">
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="pip" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 58 58" style="width:50px; padding-bottom:1em; enable-background:new 0 0 58 58;" xml:space="preserve">
<style type="text/css">
.st0{fill:#BFCCE0;}
.st1{fill:#192030;}
</style><path class="st0" d="M0,18v22c0,9.9,8.1,18,18,18h22c9.9,0,18-8.1,18-18V18c0-2.9-0.7-5.6-1.9-8H1.9C0.7,12.4,0,15.1,0,18z"/> <path class="st1" d="M40,0H18C11,0,4.9,4.1,1.9,10h54.2C53.1,4.1,47,0,40,0z"/> <g> <path class="st1" d="M27,45c-0.1,0-0.3,0-0.4,0c-1.1-0.2-1.8-1.3-1.6-2.3l4-21c0.2-1.1,1.3-1.8,2.3-1.6c1.1,0.2,1.8,1.3,1.6,2.3 l-4,21C28.8,44.3,27.9,45,27,45z"/> </g> <g> <path class="st1" d="M18,41c-0.6,0-1.1-0.2-1.5-0.7l-6-7c-0.7-0.8-0.6-2.1,0.2-2.8c0.8-0.7,2.1-0.6,2.8,0.2l6,7 c0.7,0.8,0.6,2.1-0.2,2.8C18.9,40.8,18.5,41,18,41z"/> </g> <g> <path class="st1" d="M12,34c-0.5,0-0.9-0.2-1.3-0.5c-0.8-0.7-0.9-2-0.2-2.8l6-7c0.7-0.8,2-0.9,2.8-0.2c0.8,0.7,0.9,2,0.2,2.8l-6,7 C13.1,33.8,12.6,34,12,34z"/> </g> <g> <path class="st1" d="M40,41c-0.5,0-0.9-0.2-1.3-0.5c-0.8-0.7-0.9-2-0.2-2.8l6-7c0.7-0.8,2-0.9,2.8-0.2c0.8,0.7,0.9,2,0.2,2.8l-6,7 C41.1,40.8,40.6,41,40,41z"/> </g> <g> <path class="st1" d="M46,34c-0.6,0-1.1-0.2-1.5-0.7l-6-7c-0.7-0.8-0.6-2.1,0.2-2.8c0.8-0.7,2.1-0.6,2.8,0.2l6,7 c0.7,0.8,0.6,2.1-0.2,2.8C46.9,33.8,46.5,34,46,34z"/> </g> </svg>
<h6 class="card-title" id="manage_phpmyadmin_public_title">{{ _('Run NPM Install') }}<br><span id="requirements_status" class="badge bg-dark">{{ _('Checking...') }}</span></h6>
</div>
</div></a>
</div>
<div class="col-md-2">
<div class="card" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="{{ _('View SSL Certificates') }}">
<a style="text-decoration: none;" href="/ssl" target="_blank">
<div class="card-body ikona text-center">
<svg version="1.1" id="katanac" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 58 58" style="width:50px; padding-bottom:1em; enable-background:new 0 0 58 58;" xml:space="preserve">
<style type="text/css">
.st0{fill:#BFCCE0;}
.st1{fill:#192030;}
</style>
<path class="st0" d="M38,58H12C5.4,58,0,52.6,0,46V32c0-6.6,5.4-12,12-12h26c6.6,0,12,5.4,12,12v14C50,52.6,44.6,58,38,58z"/>
<path class="st1" d="M25,46L25,46c-1.1,0-2-0.9-2-2V34c0-1.1,0.9-2,2-2h0c1.1,0,2,0.9,2,2v10C27,45.1,26.1,46,25,46z"/>
<path class="st1" d="M12,20h1v-6c0-5.5,4.5-10,10-10h4c5.5,0,10,4.5,10,10v6h1c1,0,2,0.1,3,0.4V14c0-7.7-6.3-14-14-14h-4
C15.3,0,9,6.3,9,14v6.4C10,20.1,11,20,12,20z"/>
</svg>
<h6 class="card-title">
<span id="site-ssl-status">
<div class="spinner-border spinner-border-sm" role="status"></div>{{ _('Checking SSL status..') }}</span>
</h6>
</div></a>
</div>
</div>
<div class="col-md-2">
<a style="text-decoration: none;" href="/cronjobs" target="_blank"> <div class="card" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="{{ _('Manage cronjobs') }}">
<div class="card-body ikona text-center">
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="cronovi" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 54 58" style="width:50px; padding-bottom:1em; enable-background:new 0 0 54 58;" xml:space="preserve">
<style type="text/css">
.st0{fill:#BFCCE0;}
.st1{fill:#192030;}
</style>
<path class="st0" d="M42,58H12C5.4000001,58,0,52.5999985,0,46V21h54v25C54,52.5999985,48.5999985,58,42,58z"/>
<path class="st1" d="M54,21H0v-3C0,11.3999996,5.4000001,6,12,6h30c6.5999985,0,12,5.3999996,12,12V21z"/>
<path class="st0" d="M13.0000019,12h-0.0000038C11.8999987,12,11,11.1000013,11,10.0000019V1.999998
C11,0.8999991,11.8999987,0,12.9999981,0h0.0000038C14.1000013,0,15,0.8999991,15,1.999998v8.0000038
C15,11.1000013,14.1000013,12,13.0000019,12z"/>
<path class="st0" d="M41.0000038,12h-0.0000076C39.8999977,12,39,11.1000013,39,10.0000019V1.999998
C39,0.8999991,39.8999977,0,40.9999962,0h0.0000076C42.1000023,0,43,0.8999991,43,1.999998v8.0000038
C43,11.1000013,42.1000023,12,41.0000038,12z"/>
<path class="st1" d="M28.0000019,35h-2.0000038C24.8999996,35,24,34.1000023,24,33.0000038v-0.0000076
C24,31.8999996,24.8999996,31,25.9999981,31h2.0000038C29.1000004,31,30,31.8999996,30,32.9999962v0.0000076
C30,34.1000023,29.1000004,35,28.0000019,35z"/>
<path class="st1" d="M15.0000019,35h-2.0000038C11.8999987,35,11,34.1000023,11,33.0000038v-0.0000076
C11,31.8999996,11.8999987,31,12.9999981,31h2.0000038C16.1000004,31,17,31.8999996,17,32.9999962v0.0000076
C17,34.1000023,16.1000004,35,15.0000019,35z"/>
<path class="st1" d="M41.0000038,35h-2.0000076C37.8999977,35,37,34.1000023,37,33.0000038v-0.0000076
C37,31.8999996,37.8999977,31,38.9999962,31h2.0000076C42.1000023,31,43,31.8999996,43,32.9999962v0.0000076
C43,34.1000023,42.1000023,35,41.0000038,35z"/>
<path class="st1" d="M28.0000019,47h-2.0000038C24.8999996,47,24,46.1000023,24,45.0000038v-0.0000076
C24,43.8999977,24.8999996,43,25.9999981,43h2.0000038C29.1000004,43,30,43.8999977,30,44.9999962v0.0000076
C30,46.1000023,29.1000004,47,28.0000019,47z"/>
<path class="st1" d="M15.0000019,47h-2.0000038C11.8999987,47,11,46.1000023,11,45.0000038v-0.0000076
C11,43.8999977,11.8999987,43,12.9999981,43h2.0000038C16.1000004,43,17,43.8999977,17,44.9999962v0.0000076
C17,46.1000023,16.1000004,47,15.0000019,47z"/>
<path class="st1" d="M41.0000038,47h-2.0000076C37.8999977,47,37,46.1000023,37,45.0000038v-0.0000076
C37,43.8999977,37.8999977,43,38.9999962,43h2.0000076C42.1000023,43,43,43.8999977,43,44.9999962v0.0000076
C43,46.1000023,42.1000023,47,41.0000038,47z"/>
</svg>
<h6 class="card-title">{{ _('Cronjobs') }}</h6>
</div>
</div></a>
</div>
{% if 'malware_scan' in enabled_modules %}
<div class="col-md-2">
<div class="card">
<a style="text-decoration: none;" href="/malware-scanner?path=/home/{{current_username}}/{{ current_domain }}" target="_blank" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Scan website files with ClamAV">
<div class="card-body ikona text-center">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="scanner" x="0px" y="0px" viewBox="0 0 58 58" style="width:50px; padding-bottom:1em; enable-background:new 0 0 58 58;" xml:space="preserve"> <style type="text/css"> .st0{fill:#414954;} .st1{fill:#EBF3FF;} </style> <g> <path class="st0" d="M12,37v1c0,5.5,4.5,10,10,10h14c5.5,0,10-4.5,10-10v-1H12z"/> <path class="st0" d="M46,27v-7c0-5.5-4.5-10-10-10H22c-5.5,0-10,4.5-10,10v7H46z"/> </g> <g> <path class="st1" d="M2,14c-1.1,0-2-0.9-2-2C0,5.4,5.4,0,12,0c1.1,0,2,0.9,2,2s-0.9,2-2,2c-4.4,0-8,3.6-8,8C4,13.1,3.1,14,2,14z"/> </g> <g> <path class="st1" d="M12,58C5.4,58,0,52.6,0,46c0-1.1,0.9-2,2-2s2,0.9,2,2c0,4.4,3.6,8,8,8c1.1,0,2,0.9,2,2S13.1,58,12,58z"/> </g> <g> <path class="st1" d="M46,58c-1.1,0-2-0.9-2-2s0.9-2,2-2c4.4,0,8-3.6,8-8c0-1.1,0.9-2,2-2s2,0.9,2,2C58,52.6,52.6,58,46,58z"/> </g> <g> <path class="st1" d="M56,14c-1.1,0-2-0.9-2-2c0-4.4-3.6-8-8-8c-1.1,0-2-0.9-2-2s0.9-2,2-2c6.6,0,12,5.4,12,12 C58,13.1,57.1,14,56,14z"/> </g> <path class="st1" d="M56,34H2c-1.1,0-2-0.9-2-2v0c0-1.1,0.9-2,2-2h54c1.1,0,2,0.9,2,2v0C58,33.1,57.1,34,56,34z"/> </svg>
<h6 class="card-title">{{ _('Scanning') }}</h6>
</div>
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var punycodeElements = document.querySelectorAll(".punycode");
punycodeElements.forEach(function(element) {
element.textContent = punycode.toUnicode(element.textContent);
});
});
document.addEventListener('DOMContentLoaded', function() {
const domainElement = document.querySelector('.card-text .punycode.nije-link');
const faviconElement = document.getElementById('favicon');
const currentDomain = '{{ current_domain }}';
// Create the URL to the favicon
const faviconUrl = `https://www.google.com/s2/favicons?domain=${currentDomain}`;
// Create an img element and set its src to the favicon URL
const img = document.createElement('img');
img.src = faviconUrl;
img.alt = 'Favicon';
img.style.width = '16px'; // Optional: set the size of the favicon
img.style.height = '16px'; // Optional: set the size of the favicon
// Append the img element to the favicon span
faviconElement.appendChild(img);
});
</script>
{% endif %}
{% endblock %}