mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
985 lines
33 KiB
HTML
985 lines
33 KiB
HTML
<!-- wordpress.html -->
|
|
{% extends 'base.html' %}
|
|
|
|
{% block content %}
|
|
|
|
{% if domains %}
|
|
<style>
|
|
.toast-header {
|
|
background-color: #192030;
|
|
padding: 15px;
|
|
font-weight: 600;
|
|
font-size: 15px;
|
|
margin-bottom: 0;
|
|
line-height: 1.4;
|
|
color: rgb(255 255 255 / 84%)!important;
|
|
|
|
}
|
|
|
|
.no_link {
|
|
color: black;
|
|
text-decoration: none;
|
|
}
|
|
.nije-link {
|
|
text-decoration: none;
|
|
color: black;
|
|
border-bottom: 1px dashed black;
|
|
}
|
|
</style>
|
|
|
|
<!-- Flash messages -->
|
|
{% with messages = get_flashed_messages() %}
|
|
{% if messages %}
|
|
{% for message in messages %}
|
|
{% if "Error" in message %}
|
|
<script type="module">
|
|
const toastMessage = '{{ message }}';
|
|
const toast = toaster({
|
|
body: toastMessage,
|
|
header: `<div class="d-flex align-items-center" style="color: #495057;"><l-i class="bi bi-x-lg" class="me-2" style="color:red;"></l-i> {{ _("WordPress Installation failed.") }}</div>`,
|
|
});
|
|
</script>
|
|
{% else %}
|
|
<script type="module">
|
|
const toastMessage = '{{ message }}';
|
|
const toast = toaster({
|
|
body: toastMessage,
|
|
header: `<div class="d-flex align-items-center" style="color: #495057;"><l-i class="bi bi-check-lg" class="me-2" style="color:green;"></l-i> {{ _("WordPress successfully installed.") }}</div>`,
|
|
});
|
|
</script>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
<!-- END Flash messages -->
|
|
|
|
<div class="row">
|
|
|
|
<script>
|
|
// Function to attach event listener to the scanButton
|
|
|
|
function attachScanButtonListener() {
|
|
const scanButton = document.getElementById("scanButton");
|
|
const refButton = document.getElementById("refreshData");
|
|
if (scanButton) {
|
|
scanButton.addEventListener("click", async (ev) => {
|
|
ev.preventDefault();
|
|
|
|
// Update the action to send a scan request
|
|
const action = 'scan';
|
|
|
|
// Display the scanning started toast
|
|
const scanStartedToast = toaster({
|
|
body: '{{ _("Scanning started...") }}',
|
|
className: 'border-0 text-white bg-info',
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(`/wordpress/scan?action=${action}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
});
|
|
|
|
// Wait for the scan to finish
|
|
const resultText = await response.text();
|
|
|
|
// Check if installations were found or not
|
|
if (resultText.trim() === "Scan completed. Found installations:") {
|
|
// Display the message when no installations were found
|
|
toaster({
|
|
body: '{{ _("No installations found.") }}',
|
|
className: 'border-0 text-white bg-success',
|
|
});
|
|
} else if (resultText.startsWith("Scan completed. Found installations:")) {
|
|
// Display the installations if some were found
|
|
toaster({
|
|
body: resultText,
|
|
className: 'border-0 text-white bg-success',
|
|
});
|
|
} else if (resultText.trim() === "Scan skipped. WordPress installation is currently running.") {
|
|
// Display the skipped message as danger
|
|
toaster({
|
|
body: '{{ _("Scan skipped. WordPress installation is currently running.") }}',
|
|
className: 'border-0 text-white bg-danger',
|
|
});
|
|
} else {
|
|
// Display the entire response if it doesn't match the expected format
|
|
toaster({
|
|
body: resultText,
|
|
className: 'border-0 text-white bg-success',
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
|
|
// Display error message in a new toast
|
|
toaster({
|
|
body: 'Error occurred during scan.',
|
|
className: 'border-0 text-white bg-danger',
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if (refButton) {
|
|
refButton.addEventListener("click", async (ev) => {
|
|
ev.preventDefault();
|
|
|
|
|
|
// Display the scanning started toast
|
|
const refStartedToast = toaster({
|
|
body: '{{ _("Data refresh in progress...") }}',
|
|
className: 'border-0 text-white bg-info',
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(`/wordpress/reload_data`, {
|
|
method: 'GET',
|
|
});
|
|
const resultText = await response.text();
|
|
if (resultText.startsWith("Scan completed. Found installations:")) {
|
|
// Display the installations if some were found
|
|
toaster({
|
|
body: "{{ _('Refresh completed.') }}",
|
|
className: 'border-0 text-white bg-success',
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
|
|
// Display error message in a new toast
|
|
toaster({
|
|
body: 'Error occurred during refresh.',
|
|
className: 'border-0 text-white bg-danger',
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
// Attach event listener to the scanButton initially
|
|
attachScanButtonListener();
|
|
|
|
// Add a click event listener to scanButton2
|
|
var scanButton2 = document.getElementById('scanButton2');
|
|
if (scanButton2) {
|
|
scanButton2.addEventListener('click', async () => {
|
|
const scanButton = document.getElementById("scanButton");
|
|
if (scanButton) {
|
|
scanButton.click();
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
@keyframes rotate {
|
|
to {
|
|
--angle: 360deg
|
|
}
|
|
}
|
|
|
|
@property --angle {
|
|
syntax: "<angle>";
|
|
initial-value: 0deg;
|
|
inherits: false
|
|
}
|
|
|
|
.CrawlerStatusCard.active {
|
|
animation: rotate 2s linear infinite;
|
|
background: hsla(0,0%,100%,.5);
|
|
border: 2px solid transparent;
|
|
border-image: conic-gradient(from var(--angle),transparent 0deg 90deg,transparent 90deg 180deg,transparent 180deg 270deg,#0077bc 270deg 1turn) 1 stretch;
|
|
}
|
|
</style>
|
|
|
|
|
|
<div class="collapse mb-2" id="collapseExample">
|
|
<div id="toAddActive" class="card CrawlerStatusCard card-body">
|
|
<!-- Form for adding new containers -->
|
|
<div class="container">
|
|
<a href="#" class="nije-link" id="cancelLink" style="display: none;"><i class="bi bi-x-lg" style="right: 15px;top: 15px;position: absolute;color: black;padding: 6px 10px;border-radius: 50px;"></i></a>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 offset-md-3">
|
|
<h2 class="mb-3"><i class="bi bi-wordpress"></i> {{ _("Install WordPress") }}</h2>
|
|
<p>{{ _("Install WordPress on an existing domain.") }}</p>
|
|
<form method="post" id="installForm" action="/wordpress/install">
|
|
<div class="form-group row">
|
|
<div class="col-6">
|
|
<label for="website_name" class="form-label">{{ _("Website Name:") }}</label>
|
|
<input type="text" class="form-control" name="website_name" id="website_name" value="My Blog" required>
|
|
</div>
|
|
<div class="col-6">
|
|
<label for="site_description" class="form-label">{{ _("Site Description:") }}</label>
|
|
<input type="text" class="form-control" name="site_description" id="site_description" value="My WordPress Blog" required>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="domain_id" class="form-label">{{ _("Domain:") }}</label>
|
|
<div class="input-group">
|
|
</select>
|
|
<select class="form-select" name="domain_id" id="domain_id">
|
|
<option selected disabled value="">{{ _("Select a domain") }}</option>
|
|
{% for domain in domains %}
|
|
<option class="punycode" value="{{ domain.domain_id }}">{{ domain.domain_url }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="input-group-append" style="width:30%;">
|
|
<input type="text" class="form-control" name="subdirectory" id="subdirectory" placeholder="subfolder">
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="admin_email">{{ _("Admin Email:") }}</label>
|
|
<input type="email" class="form-control" name="admin_email" id="admin_email" required>
|
|
</div>
|
|
|
|
|
|
|
|
<script type="module">
|
|
$(document).ready(function() {
|
|
// Check if the URL contains the parameter "install"
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const installParam = urlParams.get('install');
|
|
|
|
if (installParam || window.location.hash === '#install') {
|
|
// Show the Bootstrap collapsible element
|
|
$("#collapseExample").collapse('show');
|
|
}
|
|
|
|
|
|
// Add event listener to the dropdown
|
|
$("#domain_id").change(function() {
|
|
// Get the selected domain URL
|
|
var selectedDomain = $("#domain_id option:selected").text();
|
|
|
|
// Update the admin email input value
|
|
var adminEmailInput = $("#admin_email");
|
|
var currentAdminEmail = adminEmailInput.val();
|
|
var atIndex = currentAdminEmail.indexOf('@');
|
|
if (atIndex !== -1) {
|
|
// If '@' exists in the email, replace the part after it with the selected domain
|
|
adminEmailInput.val(currentAdminEmail.substring(0, atIndex + 1) + selectedDomain);
|
|
} else {
|
|
// If '@' doesn't exist in the email, add the selected domain after 'admin@'
|
|
adminEmailInput.val('admin@' + selectedDomain);
|
|
}
|
|
});
|
|
// Add event listener to the "Install WordPress" button to toggle form and jumbotron
|
|
$("#collapseExample").on('shown.bs.collapse', function () {
|
|
$("#jumbotronSection").hide();
|
|
$("#cancelLink").show();
|
|
});
|
|
|
|
// Add event listener to the "Cancel" link to toggle form and jumbotron
|
|
$("#cancelLink").click(function() {
|
|
$("#collapseExample").collapse('hide');
|
|
$("#jumbotronSection").show();
|
|
$("#cancelLink").hide();
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
var selectedDomain = $("#domain_id option:selected").text();
|
|
|
|
// Check if the selected domain is the first option (placeholder)
|
|
if ($("#domain_id option:selected").index() !== 0) {
|
|
// Update the admin email input value
|
|
var adminEmailInput = $("#admin_email");
|
|
var currentAdminEmail = adminEmailInput.val();
|
|
var atIndex = currentAdminEmail.indexOf('@');
|
|
|
|
if (atIndex !== -1) {
|
|
// If '@' exists in the email, replace the part after it with the selected domain
|
|
adminEmailInput.val(currentAdminEmail.substring(0, atIndex + 1) + selectedDomain);
|
|
} else {
|
|
// If '@' doesn't exist in the email, add the selected domain after 'admin@'
|
|
adminEmailInput.val('admin@' + selectedDomain);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-md-6">
|
|
<label for="admin_username" class="form-label">{{ _("Admin Username:") }}</label>
|
|
<input type="text" class="form-control" name="admin_username" id="admin_username" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="admin_password" class="form-label">{{ _("Admin Password:") }}</label>
|
|
<div class="input-group">
|
|
<input type="password" class="form-control" name="admin_password" id="admin_password" required>
|
|
<div class="input-group-append">
|
|
<button class="btn btn-outline-success" type="button" id="generatePassword">
|
|
{{ _("Generate") }}
|
|
</button>
|
|
<button class="btn btn-outline-secondary" type="button" id="togglePassword">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script type="module">
|
|
function generateRandomUsername(length) {
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
let result = "";
|
|
for (let i = 0; i < length; i++) {
|
|
const randomIndex = Math.floor(Math.random() * charset.length);
|
|
result += charset.charAt(randomIndex);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function generateRandomStrongPassword(length) {
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+";
|
|
let result = "";
|
|
for (let i = 0; i < length; i++) {
|
|
const randomIndex = Math.floor(Math.random() * charset.length);
|
|
result += charset.charAt(randomIndex);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function generateInitiallyUsernameAndPassword() {
|
|
const generatedUsername = generateRandomUsername(8);
|
|
const generatedPassword = generateRandomStrongPassword(16);
|
|
|
|
document.getElementById("admin_username").value = generatedUsername;
|
|
document.getElementById("admin_password").value = generatedPassword;
|
|
};
|
|
|
|
generateInitiallyUsernameAndPassword();
|
|
|
|
document.getElementById("generatePassword").addEventListener("click", function() {
|
|
const generatedPassword = generateRandomStrongPassword(16);
|
|
document.getElementById("admin_password").value = generatedPassword;
|
|
|
|
const passwordField = document.getElementById("admin_password");
|
|
if (passwordField.type === "password") {
|
|
passwordField.type = "text";
|
|
}
|
|
|
|
});
|
|
|
|
document.getElementById("togglePassword").addEventListener("click", function() {
|
|
const passwordField = document.getElementById("admin_password");
|
|
if (passwordField.type === "password") {
|
|
passwordField.type = "text";
|
|
} else {
|
|
passwordField.type = "password";
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div class="form-group">
|
|
<label for="wordpress_version" class="form-label">WordPress Version:</label>
|
|
<div class="form-field">
|
|
<select class="form-select" name="wordpress_version" id="wordpress_version">
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<br>
|
|
<button type="submit" class="btn btn-primary" id="installButton">{{ _("Start Installation") }}</button>
|
|
</form>
|
|
<div id="statusMessage"></div>
|
|
<script type="module">
|
|
document.getElementById("installForm").addEventListener("submit", function(event) {
|
|
event.preventDefault();
|
|
|
|
var selectedDomain = $("#domain_id option:selected").text();
|
|
// Check if the first option (placeholder) is selected
|
|
if ($("#domain_id option:selected").index() === 0) {
|
|
event.preventDefault(); // Prevent form submission
|
|
toaster({
|
|
body: `{{ _("Please select a domain name.") }}`,
|
|
className: 'border-0 text-white bg-danger',
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// Hide the installation form
|
|
const installForm = document.getElementById("installForm");
|
|
installForm.style.display = "none"; // Hide the form
|
|
|
|
// Show the active state
|
|
document.getElementById("toAddActive").classList.add("active");
|
|
|
|
// Disable the button to prevent multiple clicks
|
|
const button = document.getElementById("installButton");
|
|
button.disabled = true;
|
|
button.innerText = "Installing..."; // Initial message
|
|
|
|
// Clear previous status messages
|
|
const statusMessageDiv = document.getElementById("statusMessage");
|
|
statusMessageDiv.innerText = "";
|
|
|
|
// Start fetching for updates
|
|
const formData = new FormData(this); // Get the form data
|
|
const responseStream = fetch('/wordpress/install', {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
|
|
responseStream
|
|
.then(response => {
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder('utf-8');
|
|
|
|
function read() {
|
|
reader.read().then(({ done, value }) => {
|
|
if (done) {
|
|
showCompletion();
|
|
return;
|
|
}
|
|
const message = decoder.decode(value, { stream: true });
|
|
const jsonMessages = message.split('\n').filter(Boolean); // Split lines and remove empty
|
|
|
|
jsonMessages.forEach(jsonMessage => {
|
|
try {
|
|
const data = JSON.parse(jsonMessage);
|
|
if (data.status) {
|
|
statusMessageDiv.innerText += data.status + "\n"; // append
|
|
} else if (data.error) {
|
|
toaster({
|
|
body: data.error,
|
|
className: 'border-0 text-white bg-danger',
|
|
});
|
|
showCompletion(); // redirect
|
|
}
|
|
} catch (e) {
|
|
//console.error("Error parsing JSON:", e);
|
|
}
|
|
});
|
|
read(); // Continue
|
|
});
|
|
}
|
|
|
|
read(); // Start
|
|
})
|
|
.catch(error => {
|
|
statusMessageDiv.innerText += "Error occurred while processing.\n";
|
|
|
|
console.error("Fetch error:", error);
|
|
toaster({
|
|
body: error,
|
|
className: 'border-0 text-white bg-danger',
|
|
});
|
|
showCompletion(); // redirect
|
|
|
|
});
|
|
|
|
function showCompletion() {
|
|
statusMessageDiv.style.display = "block";
|
|
window.location.href = '/wordpress';
|
|
}
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
<script type="module">
|
|
|
|
|
|
// Fetch the WordPress versions from the API
|
|
fetch('https://api.wordpress.org/core/stable-check/1.0/')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Filter out the versions with "latest" and "outdated" status
|
|
const filteredVersions = Object.entries(data)
|
|
.filter(([version, status]) => status !== 'insecure')
|
|
.map(([version, _]) => version)
|
|
.sort((a, b) => compareVersions(b, a)) // Sort in descending order
|
|
|
|
// Take the latest 10 versions
|
|
const latestVersions = filteredVersions.slice(0, 10);
|
|
|
|
// Populate the select dropdown with options
|
|
const selectElement = document.getElementById('wordpress_version');
|
|
latestVersions.forEach(version => {
|
|
const option = document.createElement('option');
|
|
option.value = version;
|
|
option.textContent = `${version}`;
|
|
selectElement.appendChild(option);
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching WordPress versions:', error);
|
|
|
|
// In case of an error, you might want to display a default option
|
|
const selectElement = document.getElementById('wordpress_version');
|
|
const option = document.createElement('option');
|
|
option.value = ''; // Set a value for the default option if needed
|
|
option.textContent = '{{ _("Error fetching versions") }}';
|
|
selectElement.appendChild(option);
|
|
});
|
|
|
|
// Function to compare two version strings
|
|
function compareVersions(a, b) {
|
|
const versionA = a.split('.').map(Number);
|
|
const versionB = b.split('.').map(Number);
|
|
|
|
for (let i = 0; i < Math.max(versionA.length, versionB.length); i++) {
|
|
const partA = versionA[i] || 0;
|
|
const partB = versionB[i] || 0;
|
|
|
|
if (partA < partB) return -1;
|
|
if (partA > partB) return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
</script>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
{% if data %}
|
|
{% if view_mode == 'table' %}
|
|
<table class="table table-bordered table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ _("Domain") }}</th>
|
|
<th>{{ _("WordPress Version") }}</th>
|
|
<th>{{ _("Admin Email") }}</th>
|
|
<th>{{ _("Created on") }}</th>
|
|
<th>{{ _("Actions") }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in data %}
|
|
<div class="modal fade" id="removeModal{{ row[1] }}" tabindex="-1" role="dialog" aria-labelledby="removeModalLabel{{ row[1] }}" aria-hidden="true">
|
|
<div class="modal-dialog" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="removeModalLabel{{ row[0] }}">{{ row[0] }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body row">
|
|
<div class="col-md-6">
|
|
<h4>{{ _("Delete WordPress website") }}</h4>
|
|
<p>{{ _("This will irreversibly delete the website, permanently deleting all files and database.") }}</p>
|
|
<button type="button" class="btn btn-danger" onclick="confirmRemove('{{ row[1] }}')">{{ _("Uninstall") }}</button>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<h4>{{ _("Remove from WP Manager") }}</h4>
|
|
<p>{{ _("This will just remove the installation from WP Manager but keep the files and database.") }}</p>
|
|
<button type="button" class="btn btn-warning" onclick="confirmDetach('{{ row[1] }}')">{{ _("Detach") }}</button>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
<div class="modal-footer">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{% set domain_url = row[0] %}
|
|
<tr>
|
|
<td><a class="punycode no_link domain_link" href="http://{{ row[0] }}" target="_blank"><img src="https://www.google.com/s2/favicons?domain={{ row[0] }}" alt="{{ row[0] }} Favicon" style="width:16px; height:16px; margin-right:5px;">
|
|
{{ row[0] }} <i class="bi bi-box-arrow-up-right"></i></a></td>
|
|
<td>{{ row[3] }}</td>
|
|
<td>{{ row[2] }}</td>
|
|
<td>{{ row[4] }}</td>
|
|
<td>
|
|
<a href="#" class="btn btn-primary" onclick="handleLoginClick('{{ row[0] }}')"> {{ _("Login as Admin") }} <i class="bi bi-box-arrow-in-right"></i></a>
|
|
<a class="btn btn-secondary mx-2" href="/website?domain={{ row[0] }}">{{ _("Manage") }}</a>
|
|
<button type="button" class="btn btn-danger" style="border: 0px;" data-bs-toggle="modal" data-bs-target="#removeModal{{ row[1] }}"><i class="bi bi-trash3"></i> {{ _("Remove") }}</button>
|
|
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
|
|
</table>
|
|
{% endif %}
|
|
{% if view_mode == 'cards' %}
|
|
<style>
|
|
/* Style for the container */
|
|
.image-container {
|
|
position: relative;
|
|
display: inline-block;
|
|
}
|
|
|
|
/* Style for the button */
|
|
.center-button {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
display: none; /* Hide the button by default */
|
|
z-index: 1; /* Ensure the button appears above the image */
|
|
}
|
|
|
|
/* Show the button when hovering over the image container */
|
|
.image-container:hover .center-button {
|
|
display: block;
|
|
}
|
|
|
|
.card {
|
|
border: none;
|
|
border-radius: 10px
|
|
}
|
|
|
|
.c-details span {
|
|
font-weight: 300;
|
|
font-size: 13px
|
|
}
|
|
|
|
.icon {
|
|
width: 50px;
|
|
height: 50px;
|
|
background-color: #eee;
|
|
border-radius: 15px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 39px
|
|
}
|
|
|
|
.badge span {
|
|
background-color: black;
|
|
width: 80px;
|
|
height: 20px;
|
|
padding-bottom: 3px;
|
|
border-radius: 5px;
|
|
display: flex;
|
|
color: white;
|
|
justify-content: center;
|
|
align-items: center
|
|
}
|
|
|
|
.text1 {
|
|
font-size: 14px;
|
|
font-weight: 600
|
|
}
|
|
|
|
.text2 {
|
|
color: #a5aec0
|
|
}
|
|
|
|
a.close_button {
|
|
right: 5px;top: 10px;position: absolute;color: white;padding: 0px 4px;background: indianred;border-radius: 50px; z-index:1;
|
|
|
|
}
|
|
a.close_button:hover {
|
|
background: red;
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
<div class="container mb-3">
|
|
<div class="row">
|
|
{% for row in data %}
|
|
<div class="modal fade" id="removeModal{{ row[6] }}" tabindex="-1" role="dialog" aria-labelledby="removeModalLabel{{ row[6] }}" aria-hidden="true">
|
|
<div class="modal-dialog" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="removeModalLabel{{ row[6] }}">{{ row[0] }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body row">
|
|
<div class="col-md-6">
|
|
<h4>{{ _("Delete WordPress website") }}</h4>
|
|
<p>{{ _("This will irreversibly delete the website, permanently deleting all files and database.") }}</p>
|
|
<button type="button" class="btn btn-danger" onclick="confirmRemove('{{ row[6] }}')">{{ _("Uninstall") }}</button>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<h4>{{ _("Remove from WP Manager") }}</h4>
|
|
<p>{{ _("This will just remove the installation from WP Manager but keep the files and database.") }}</p>
|
|
<button type="button" class="btn btn-warning" onclick="confirmDetach('{{ row[6] }}')">{{ _("Detach") }}</button>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
<div class="modal-footer">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="card bg-light mb-2">
|
|
<a href="#" class="close_button" data-bs-toggle="modal" data-bs-target="#removeModal{{ row[6] }}"><i class="bi bi-x-lg" style=""></i></a>
|
|
<div class="mt-0">
|
|
|
|
<div class="image-container">
|
|
<a href="/website?domain={{ row[0] }}">
|
|
<img
|
|
id="screenshot-image-{{ row[0] }}"
|
|
src="/static/images/placeholder.svg"
|
|
alt="Screenshot of {{ row[0] }}"
|
|
class="img-fluid"
|
|
/>
|
|
</a>
|
|
<a href="/website?domain={{ row[0] }}" class="center-button btn btn-dark">
|
|
<i class="bi bi-sliders2-vertical"></i> {{ _("Manage") }}
|
|
</a>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
<div class="d-flex p-2 justify-content-between">
|
|
<div class="d-flex flex-row align-items-center">
|
|
<div class="icon"> <i class="bi bi-wordpress"></i> </div>
|
|
<div class="ms-2 c-details">
|
|
<h6 class="punycode mb-0"><a href="http://{{ row[0] }}" target="_blank" class="nije-link">{{ row[0] }}</a></h6> <span>{{ _("WordPress") }} {{ row[3] }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="badge" style="display: -webkit-box;"><button type="button" style="width:100%;" class="btn btn-sm btn-outline-primary" onclick="handleLoginClick('{{ row[0] }}')"> {{ _("Login as Admin") }} <i class="bi bi-box-arrow-in-right"></i></button> <!--div class="dropdown" style="vertical-align: middle; display: inline;">
|
|
<a href="" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<i class="bi bi-three-dots-vertical" style="font-size: 1.3125rem;"></i>
|
|
</a>
|
|
<ul class="dropdown-menu" aria-labelledby="more-actions">
|
|
<li><a class="dropdown-item" href="#">PHP Version</a></li>
|
|
<li><a class="dropdown-item" href="#">SSL Security</a></li>
|
|
<li><a class="dropdown-item" href="#">SSL Security</a></li>
|
|
<hr>
|
|
<li><a class="dropdown-item" href="#">Refresh Preview</a></li>
|
|
</ul>
|
|
</div--></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
{% endfor %}
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
// Function to update a screenshot image
|
|
function updateScreenshot(screenshotImage, screenshotURL) {
|
|
var imageLoader = new Image();
|
|
imageLoader.src = screenshotURL;
|
|
imageLoader.onload = function() {
|
|
screenshotImage.src = screenshotURL;
|
|
};
|
|
}
|
|
|
|
// Function to update all screenshot images
|
|
function updateAllScreenshots() {
|
|
{% for row in data %}
|
|
var screenshotImage = document.getElementById("screenshot-image-{{ row[0] }}");
|
|
var screenshotURL = "/screenshot/{{ row[0] }}";
|
|
updateScreenshot(screenshotImage, screenshotURL);
|
|
{% endfor %}
|
|
}
|
|
|
|
updateAllScreenshots();
|
|
</script>
|
|
|
|
|
|
{% endif %}
|
|
|
|
<script type="module">
|
|
// Function to confirm removal
|
|
function confirmRemove(id) {
|
|
// Send an AJAX request to the server to remove WordPress
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '/wordpress/remove', true);
|
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState === 4) {
|
|
if (xhr.status === 200) {
|
|
// Reload the page to update the table
|
|
location.reload();
|
|
} else {
|
|
alert('{{ _("An error occurred while removing WordPress.") }}');
|
|
}
|
|
}
|
|
};
|
|
xhr.send('id=' + id);
|
|
}
|
|
|
|
// Function to confirm detachment
|
|
function confirmDetach(id) {
|
|
// Send an AJAX request to the server to remove WordPress
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '/wordpress/detach', true);
|
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState === 4) {
|
|
if (xhr.status === 200) {
|
|
// Redirect to wp manager page
|
|
window.location.href = '/wordpress';
|
|
} else {
|
|
alert('{{ _("An error occurred while detaching WordPress.") }}');
|
|
}
|
|
}
|
|
};
|
|
xhr.send('id=' + id);
|
|
}
|
|
</script>
|
|
|
|
|
|
<script>
|
|
// Function to handle the button click
|
|
function handleLoginClick(domain) {
|
|
// Send an AJAX request to the server to get the login link
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', '/get_login_link?domain=' + domain, true);
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|
var response = JSON.parse(xhr.responseText);
|
|
if (response && response.login_link) {
|
|
// Open the login link in a new tab
|
|
window.open(response.login_link, '_blank');
|
|
}
|
|
}
|
|
};
|
|
xhr.send();
|
|
}
|
|
</script>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<style>
|
|
.nije-link {
|
|
text-decoration: none;
|
|
color: black;
|
|
border-bottom: 1px dashed black;
|
|
}
|
|
|
|
|
|
</style>
|
|
{% else %}
|
|
|
|
<!-- Display jumbotron for no wp installations -->
|
|
<div class="jumbotron text-center mt-5" id="jumbotronSection" style="min-height: 70vh;display:block;">
|
|
<h1>{{ _("No WordPress Installations") }}</h1>
|
|
<p>{{ _("You don't have WordPress sites connected yet. Install a new WordPress site or scan to find existing ones.") }}</p>
|
|
<button class="btn btn-lg btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
|
|
<i class="bi bi-wordpress"></i> {{ _("Install WordPress") }}
|
|
</button>
|
|
<p class="mb-0">{{ _("or") }}</p>
|
|
<button id="scanButton2" class="btn btn-outline">
|
|
<i class="bi bi-search"></i> {{ _("Scan for existing Installations") }}
|
|
</button>
|
|
|
|
</div>
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{% else %}
|
|
<!-- Display jumbotron for no domains -->
|
|
<div class="jumbotron text-center mt-5" id="jumbotronSection">
|
|
<h1>{{ _("No Domains") }}</h1>
|
|
<p>{{ _("Add a domain name first in order to install WordPress.") }}</p>
|
|
<a href="/domains#add-new" class="btn btn-lg btn-primary">
|
|
<i class="bi bi-plus-lg"></i> {{ _("Add a Domain Name") }}
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/punycode/2.1.1/punycode.min.js"></script>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
var punycodeElements = document.querySelectorAll(".punycode");
|
|
punycodeElements.forEach(function(element) {
|
|
element.textContent = punycode.toUnicode(element.textContent);
|
|
});
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
<footer class="main-footer btn-toolbar" role="toolbar">
|
|
|
|
<div class="btn-group" role="group" aria-label="Status">
|
|
</div>
|
|
|
|
<div class="ms-auto" role="group" aria-label="Actions">
|
|
|
|
<button class="btn btn-transparent" type="button" aria-expanded="false" id="refreshData">
|
|
<i class="bi bi-arrow-counterclockwise"></i> {{ _('Refresh') }}<span class="desktop-only"> {{ _('Data') }}</span>
|
|
</button>
|
|
|
|
<button class="btn btn-primary mx-2" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
|
|
<i class="bi bi-wordpress"></i> {{ _('Install') }}<span class="desktop-only"> {{ _('WordPress') }}</span>
|
|
</button>
|
|
|
|
<button id="scanButton" class="btn btn-dark" type="button">
|
|
<i class="bi bi-search"></i> {{ _('Scan') }}
|
|
</button>
|
|
|
|
<span class="desktop-only">{% if view_mode == 'cards' %}
|
|
<a href="{{ url_for('list_wordpress', view='table') }}" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ _('Switch to List View - perfect for those with many Websites.') }}" class="btn btn-outline" style="padding-left: 5px; padding-right: 5px;"><i class="bi bi-table"></i></a>
|
|
{% endif %}
|
|
{% if view_mode == 'table' %}
|
|
<a href="{{ url_for('list_wordpress', view='cards') }}" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ _('Switch to Grid View - a visual representation of your Websites and their content.') }}" class="btn btn-outline" style="padding-left: 5px; padding-right: 5px;"><i class="bi bi-view-list"></i></a>
|
|
{% endif %}</span>
|
|
|
|
<script>
|
|
// Add an event listener to the links to hide tooltip as its buggy!
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
var links = document.querySelectorAll('.desktop-only a');
|
|
|
|
links.forEach(function (link) {
|
|
link.addEventListener('click', function () {
|
|
// Hide the tooltip when the link is clicked
|
|
var tooltip = new bootstrap.Tooltip(link);
|
|
tooltip.hide();
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
</div>
|
|
</footer>
|
|
|
|
|
|
|
|
{% endblock %}
|