Files
Django-Proxmox-Mikrotik/frontend/templates/frontend/dashboard.html
Holger Sielaff 90c0ff61ed initial
2025-08-27 09:55:55 +02:00

387 lines
18 KiB
HTML

{% extends 'frontend/base.html' %}
{% block title %} - Dashboard{% endblock %}
{% block content %}
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h3>
<i class="bi bi-pc-display-horizontal"></i> Dev Container
</h3>
{% if request.user.is_staff %}
<div role="group">
<a href="{% url 'frontend:create_container' %}"
class="btn btn-primary"
><i class="bi bi-plus-circle"></i> Create Dev Container</a>
{% if default_template %}
<a href="{% url 'frontend:create_container' %}?clone_template={{ default_template.pk }}"
class="btn btn-secondary"
><i class="bi bi-plus-circle"></i> Create from default Template</a>
{% endif %}
</div>
{% endif %}
</div>
{% url 'frontend:dashboard' as search_action_url %}
{% include 'frontend/includes/pagination_snippet.html' %}
<div id="cardView" class="row">
{% for container in page_obj %}
<div class="col-12 col-md-6 col-lg-4 mb-3 container-item" data-crow="{{ container.internal_id }}">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<!-- LXC Status -->
<div class="me-2" title="LXC Status: {{ container.lxc.status }}">
<span class="status-circle status-lxc {{ container.lxc.status }} lxc-status-{{ container.internal_id }}"></span>
</div>
<div class="me-3" title="Network Status: {{ container.lease.status }}">
<span class="status-circle status-lease {{ container.lxc.status }} lease_status_{{ container.internal_id }}"></span>
</div>
<h5 class="mb-0">{{ container.vmid }} {{ container.name }}</h5>
</div>
</div>
<div class="card-body">
<div class="mb-2"><strong
style="width:25%;display: inline-block">Hostname:</strong> {{ container.hostname }}
</div>
<div class="mb-2"><strong
style="width:25%;display: inline-block">IP-Address:</strong> {{ container.address }}
</div>
<div class="mb-2"><strong style="width: 25%;display: inline-block;">Root
Disk:</strong> {{ container.lxc.disksize }} GB
</div>
<div class="mb-2"><strong
style="width: 25%;display: inline-block;">Memory:</strong> {{ container.lxc.memory }}
MB
</div>
<div class="mb-2"><strong
style="width: 25%;display: inline-block;">Cores:</strong> {{ container.lxc.cores }}
</div>
</div>
<div class="card-footer">
<div class="btn-group w-100" role="group">
<button onclick="openProxmoxConsole('{{ container.lxc.proxmox_console_url }}', '{{ container.lxc.vmid }}')"
class="btn btn-secondary terminal terminal-{{ container.pk }}"
title="Open Proxmox Console">
<i class="bi bi-terminal"></i>
</button>
<a href="{% url 'frontend:container_detail' container.pk %}"
class="btn btn-info" title="Details">
<i class="bi bi-info-circle"></i>
</a>
<a href="{% url 'frontend:edit_container' container.pk %}"
class="btn btn-warning" title="Edit">
<i class="bi bi-gear"></i>
</a>
<a href="{% url 'frontend:create_container' %}?clone_lxc={{ container.pk }}"
class="btn btn-secondary btn-sm" title="Clone">
<i class="bi bi-copy"></i>
</a>
<a href="{% url 'frontend:delete_container' container.pk %}"
class="btn btn-danger" title="Delete">
<i class="bi bi-trash"></i>
</a>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="alert alert-info text-center">
{% if request.GET.search %}
No Container for "{{ request.GET.search }}".
<div class="mt-3 text-center">
<a href="{% url 'frontend:dashboard' %}" class="btn btn-sm btn-secondary mt-2"><i
class="bi bi-trash">&nbsp;Clear Search</i></a>
</div>
{% else %}
No Containers found
{% endif %}
</div>
</div>
{% endfor %}
</div>
<div id="listView" class="row d-none">
<div class="col-12">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Stati</th>
<th>Name</th>
<th>Hostname</th>
<th>IP-Address</th>
<th>Root Disk</th>
<th>Memory</th>
<th>Cores</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for container in page_obj %}
<tr class="container-item" data-crow="{{ container.internal_id }}">
<td>
<div class="d-flex align-items-center">
<!-- LXC Status -->
<div class="me-2" title="LXC Status: {{ container.lxc.status }}">
<span class="status-circle {{ container.lxc.status }} lxc_status_{{ container.internal_id }}"></span>
</div>
<!-- Network Status -->
<div title="Network Status: {{ container.status }}">
<span class="status-circle {{ container.lease.status }} lease_status_{{ container.internal_id }}"></span>
</div>
</div>
</td>
<td>{{ container.vmid }} {{ container.name }}</td>
<td>{{ container.hostname }}</td>
<td>{{ container.address }}</td>
<td>{{ container.lxc.disksize }} GB</td>
<td>{{ container.lxc.memory }} MB</td>
<td>{{ container.lxc.cores }}</td>
<td>
<div class="btn-group" role="group">
<button onclick="openProxmoxConsole('{{ container.lxc.proxmox_console_url }}', '{{ container.lxc.vmid }}')"
class="btn btn-sm btn-secondary terminal terminal-{{ container.pk }}"
title="Open Proxmox Console">
<i class="bi bi-terminal"></i>
</button>
<a href="{% url 'frontend:container_detail' container.pk %}"
class="btn btn-info btn-sm" title="Details">
<i class="bi bi-info-circle"></i>
</a>
<a href="{% url 'frontend:edit_container' container.pk %}"
class="btn btn-warning btn-sm" title="Edit">
<i class="bi bi-gear"></i>
</a>
<a href="{% url 'frontend:create_container' %}?clone_lxc={{ container.lxc.pk }}"
class="btn btn-secondary btn-sm" title="Clone">
<i class="bi bi-copy"></i>
</a>
<a href="{% url 'frontend:delete_container' container.pk %}"
class="btn btn-danger btn-sm" title="Delete">
<i class="bi bi-trash"></i>
</a>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center">
{% if request.GET.search %}
No Container for "{{ request.GET.search }}".
<div class="mt-3 text-center">
<a href="{% url 'frontend:dashboard' %}"
class="btn btn-sm btn-secondary mt-2"><i class="bi bi-trash">&nbsp;Clear
Search</i></a>
</div>
{% else %}
No Containers found
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% include 'frontend/includes/pagination_snippet.html' %}
<style>
.status-circle {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: darkred;
}
.status-low,
.status-low td,
.status-low div {
background-color: #f9aea8 !important;
}
.status-circle.active,
.status-circle.running,
.status-circle.bound {
background-color: darkgreen;
}
.status-circle.pending {
background-color: orange;
animation: pulse 1.5s infinite;
}
.status-circle.error {
background-color: red;
animation: blink 1s infinite;
}
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.3;
}
100% {
opacity: 1;
}
}
.btn.terminal {
background-color: #444444;
border-color: #444444;
color: white;
}
.terminal-none {
display: none;
}
.search-form {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
padding-bottom: 0;
margin-bottom: 20px;
}
</style>
<script>
var respd = false;
document.addEventListener('DOMContentLoaded', function () {
const cardViewBtn = document.getElementById('cardViewBtn');
const listViewBtn = document.getElementById('listViewBtn');
const cardView = document.getElementById('cardView');
const listView = document.getElementById('listView');
let viewMode = localStorage.getItem('containerViewMode') || '{{ user_profile.settings.dashboard_view }}';
'{{page_ids }}'.split(',').forEach(function (id) {
if (!id) {
console.warn('Current Container called but id is empty - {{ page_ids }}');
return;
}
$.get("{% url 'frontend:current_container_details' %}", {'ids': id}, function (container) {
container = container[0]
if (container.is_low) {
$('[data-crow="' + id + '"]').addClass(
'status-low'
).attr(
'title',
'Low Warning: Disk: ' + container.disk_percent + '%, Memory: ' + container.mem_percent + '%'
);
}
let lxc_status = $('.lxc_status_' + id);
let lease_status = $('.lease_status_' + id);
lxc_status.removeClass('stopped,running)').addClass(container.lxc_status).css('cursor', 'pointer');
/*
if(container.lxc_status === 'running'){
$('.terminal-' + id).removeClass('terminal-none')
}else{
$('.terminal-' + id).addClass('terminal-none')
}
*/
lxc_status.attr('title', 'LXC Status: ' + container.lxc_status);
let stopurl = "{% url 'frontend:stop_lxc' 0 %}";
let starturl = "{% url 'frontend:start_lxc' 0 %}";
lxc_status.click(function () {
if (container.lxc_status === 'stopped') {
performContainerAction(id, 'start', starturl, lxc_status);
} else {
performContainerAction(id, 'stop', stopurl, lxc_status);
}
});
lease_status.removeClass('bound,waiting)').addClass(container.lease_status);
lease_status.attr('title', 'Network Status: ' + container.lease_status);
});
});
function checkScreenSizeAndAdjustView() {
const isMobileOrTablet = window.innerWidth < 768;
if (isMobileOrTablet && viewMode === 'list') {
cardView.classList.remove('d-none');
listView.classList.add('d-none');
cardViewBtn.classList.add('active');
listViewBtn.classList.remove('active');
} else {
if (viewMode === 'list') {
cardView.classList.add('d-none');
listView.classList.remove('d-none');
cardViewBtn.classList.remove('active');
listViewBtn.classList.add('active');
} else {
cardView.classList.remove('d-none');
listView.classList.add('d-none');
cardViewBtn.classList.add('active');
listViewBtn.classList.remove('active');
}
}
}
checkScreenSizeAndAdjustView();
window.addEventListener('resize', checkScreenSizeAndAdjustView);
function chView(vm) {
viewMode = vm;
localStorage.setItem('containerViewMode', viewMode);
checkScreenSizeAndAdjustView();
/*
$.post('{ % url 'manager:set_view_mode' % }', {'mode': viewMode}, function (data) {
console.log(data);
});
*/
}
cardViewBtn.addEventListener('click', function () {
chView('card');
});
listViewBtn.addEventListener('click', function () {
chView('list');
});
});
function openProxmoxConsole(url, vmid) {
if (!localStorage.getItem('pm-warning-confirmed')) {
if (!confirm('You have to be logged in in the Proxmox to open a Console here.\n\n')) {
return;
}
localStorage.setItem('pm-warning-confirmed', 'true');
}
window.open(
url,
'proxmox-console-' + vmid,
'width=800,height=600,scrollbars=yes,resizable=yes,status=yes,toolbar=yes,menubar=no,location=yes'
);
}
function performContainerAction(containerId, action, url, statusElement) {
// Simply redirect to the URL (synchronous operation with form-blocking)
window.location.href = url + '?id=' + containerId;
}
</script>
{% endblock %}