add machines

This commit is contained in:
Tomáš Mládek 2020-07-15 03:56:01 +02:00
parent f19e04c962
commit 6310c2f878
7 changed files with 121 additions and 19 deletions

View file

@ -1,7 +1,7 @@
from django.contrib import admin
from ordered_model.admin import OrderedModelAdmin
from sdbs_infra.dashboard.models import Service, Link
from sdbs_infra.dashboard.models import Service, Link, Machine
class LinkAdmin(OrderedModelAdmin):
@ -12,5 +12,10 @@ class ServiceAdmin(OrderedModelAdmin):
list_display = ('short_name', 'url', 'move_up_down_links')
class MachineAdmin(OrderedModelAdmin):
list_display = ('short_name', 'move_up_down_links')
admin.site.register(Link, LinkAdmin)
admin.site.register(Service, ServiceAdmin)
admin.site.register(Machine, MachineAdmin)

View file

@ -0,0 +1,31 @@
# Generated by Django 3.0.7 on 2020-07-15 01:51
from django.db import migrations, models
class Migration(migrations.Migration):
replaces = [('dashboard', '0009_machine'), ('dashboard', '0010_auto_20200715_0344'), ('dashboard', '0011_machine_url'), ('dashboard', '0012_auto_20200715_0350')]
dependencies = [
('dashboard', '0008_auto_20200620_1952'),
]
operations = [
migrations.CreateModel(
name='Machine',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
('short_name', models.CharField(max_length=64)),
('image', models.ImageField(blank=True, null=True, upload_to='machines')),
('description', models.TextField(blank=True, null=True)),
('healthcheck_id', models.CharField(blank=True, max_length=36, null=True)),
('url', models.URLField(blank=True, null=True)),
],
options={
'ordering': ('order',),
'abstract': False,
},
),
]

View file

@ -4,7 +4,7 @@ from django.db import models
from ordered_model.models import OrderedModel
class ServiceStatus(Enum):
class Status(Enum):
OK = 'ok'
DOWN = 'down'
UNKNOWN = 'unknown'
@ -33,3 +33,14 @@ class Link(OrderedModel):
class Meta(OrderedModel.Meta):
verbose_name = "Important Link"
verbose_name_plural = "Important Links"
class Machine(OrderedModel):
short_name = models.CharField(null=False, max_length=64)
image = models.ImageField(null=True, blank=True, upload_to='machines')
description = models.TextField(null=True, blank=True)
url = models.URLField(null=True, blank=True)
healthcheck_id = models.CharField(null=True, blank=True, max_length=36)
def __str__(self):
return f"{self.short_name}"

View file

@ -70,7 +70,9 @@ h2 {
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}
.link .box-content {
@ -78,18 +80,13 @@ h2 {
justify-content: space-evenly;
}
.service .box-content {
flex-direction: column;
justify-content: flex-end;
}
.box-content img {
filter: grayscale(100%);
image-rendering: crisp-edges;
text-align: center;
}
.service img {
.machine img, .service img {
min-width: 50%;
max-width: 100%;
}
@ -99,7 +96,7 @@ h2 {
max-height: 4rem;
}
.service .label h3 {
.label h3 {
margin: 1rem 0;
text-align: center;
}
@ -118,7 +115,7 @@ h2 {
align-self: flex-start;
}
.service-status {
.status {
border: 1px solid white;
border-top: 0;
text-transform: uppercase;
@ -126,15 +123,15 @@ h2 {
padding: .25em .5em;
}
.status-ok .service-status {
.status-ok .status {
color: gray;
}
.status-down .service-status {
.status-down .status {
color: darkred;
}
.status-unknown .service-status {
.status-unknown .status {
color: darkorange;
}

View file

@ -44,12 +44,33 @@
{% endif %}
</div>
</section>
<section class="service-status">
<section class="status">
STATUS: {{ service.status }}
</section>
</a>
{% endfor %}
</section>
<h2>machines</h2>
<section class="machines boxes">
{% for machine in machines %}
<a class="machine box status-{{ machine.status }}" href="{{ machine.url|default:"#" }}">
<section class="box-content">
{% if machine.image_url %}
<img src="{{ machine.image_url }}" alt="image for {{ machine.short_name }}"/>
{% endif %}
<div class="label">
<h3>{{ machine.short_name }}</h3>
{% if machine.description %}
<p class="description">{{ machine.description }}</p>
{% endif %}
</div>
</section>
<section class="status" title="Last ping at: {{ machine.last_ping|default:"UNKNOWN" }}">
STATUS: {{ machine.status }}
</section>
</a>
{% endfor %}
</section>
<section class="stats">
VPS STATS &mdash; {{ vps_stats|safe }}
</section>

View file

@ -2,6 +2,7 @@ import asyncio
import socket
import time
from collections import namedtuple
from datetime import datetime
from urllib.parse import urlparse
import aiohttp
@ -11,7 +12,8 @@ from bs4 import BeautifulSoup
from django.views.generic import TemplateView
from humanize import naturalsize
from sdbs_infra.dashboard.models import Service, ServiceStatus, Link
from sdbs_infra import settings
from sdbs_infra.dashboard.models import Service, Status, Link, Machine
class IndexView(TemplateView):
@ -22,6 +24,7 @@ class IndexView(TemplateView):
return {
'links': asyncio.run(self.process_links(list(Link.objects.all()))),
'services': asyncio.run(self.process_services(list(Service.objects.all()))),
'machines': asyncio.run(self.process_machines(list(Machine.objects.all()))),
'vps_stats': self.vps_stats()
}
@ -68,13 +71,13 @@ class IndexView(TemplateView):
location = ("localhost", service.port)
result_of_check = a_socket.connect_ex(location)
if result_of_check == 0:
status = ServiceStatus.OK
status = Status.OK
else:
status = ServiceStatus.DOWN
status = Status.DOWN
elif index_status:
status = ServiceStatus.OK if index_status == 200 else ServiceStatus.DOWN
status = Status.OK if index_status == 200 else Status.DOWN
else:
status = ServiceStatus.UNKNOWN
status = Status.UNKNOWN
image = service.image.url if service.image else self.extract_favicon(service.url, index_text)
@ -87,6 +90,38 @@ class IndexView(TemplateView):
await session.close()
return result
async def process_machines(self, machines):
result = []
session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5, sock_connect=1), headers={
'X-Api-Key': settings.HEALTCHECKS_API_KEY
})
for machine in machines:
status = Status.UNKNOWN
last_ping = None
if settings.HEALTCHECKS_API_KEY and machine.healthcheck_id:
try:
async with session.get(
f"https://healthchecks.io/api/v1/checks/{machine.healthcheck_id}") as response:
check = await response.json()
status = {
'up': Status.OK,
'down': Status.DOWN
}.get(check.get('status'), Status.UNKNOWN)
last_ping = datetime.fromisoformat(check.get('last_ping'))
except (asyncio.TimeoutError, ClientConnectorError):
pass
result.append({
'status': status.value,
'last_ping': last_ping,
**vars(machine)
})
return result
@staticmethod
def extract_favicon(url, index_text):
if not index_text:

View file

@ -128,3 +128,5 @@ STATIC_ROOT = os.path.join(BASE_DIR, "static")
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
HEALTCHECKS_API_KEY = os.getenv("HEALTHCHECKS_API_KEY")