mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
866 lines
34 KiB
HTML
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"> </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 %}
|
|
|