387 lines
18 KiB
HTML
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"> 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"> 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 %} |