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 django.contrib import admin
from ordered_model.admin import OrderedModelAdmin 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): class LinkAdmin(OrderedModelAdmin):
@ -12,5 +12,10 @@ class ServiceAdmin(OrderedModelAdmin):
list_display = ('short_name', 'url', 'move_up_down_links') 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(Link, LinkAdmin)
admin.site.register(Service, ServiceAdmin) 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 from ordered_model.models import OrderedModel
class ServiceStatus(Enum): class Status(Enum):
OK = 'ok' OK = 'ok'
DOWN = 'down' DOWN = 'down'
UNKNOWN = 'unknown' UNKNOWN = 'unknown'
@ -33,3 +33,14 @@ class Link(OrderedModel):
class Meta(OrderedModel.Meta): class Meta(OrderedModel.Meta):
verbose_name = "Important Link" verbose_name = "Important Link"
verbose_name_plural = "Important Links" 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; padding: 2rem;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: flex-end;
} }
.link .box-content { .link .box-content {
@ -78,18 +80,13 @@ h2 {
justify-content: space-evenly; justify-content: space-evenly;
} }
.service .box-content {
flex-direction: column;
justify-content: flex-end;
}
.box-content img { .box-content img {
filter: grayscale(100%); filter: grayscale(100%);
image-rendering: crisp-edges; image-rendering: crisp-edges;
text-align: center; text-align: center;
} }
.service img { .machine img, .service img {
min-width: 50%; min-width: 50%;
max-width: 100%; max-width: 100%;
} }
@ -99,7 +96,7 @@ h2 {
max-height: 4rem; max-height: 4rem;
} }
.service .label h3 { .label h3 {
margin: 1rem 0; margin: 1rem 0;
text-align: center; text-align: center;
} }
@ -118,7 +115,7 @@ h2 {
align-self: flex-start; align-self: flex-start;
} }
.service-status { .status {
border: 1px solid white; border: 1px solid white;
border-top: 0; border-top: 0;
text-transform: uppercase; text-transform: uppercase;
@ -126,15 +123,15 @@ h2 {
padding: .25em .5em; padding: .25em .5em;
} }
.status-ok .service-status { .status-ok .status {
color: gray; color: gray;
} }
.status-down .service-status { .status-down .status {
color: darkred; color: darkred;
} }
.status-unknown .service-status { .status-unknown .status {
color: darkorange; color: darkorange;
} }

View file

@ -44,12 +44,33 @@
{% endif %} {% endif %}
</div> </div>
</section> </section>
<section class="service-status"> <section class="status">
STATUS: {{ service.status }} STATUS: {{ service.status }}
</section> </section>
</a> </a>
{% endfor %} {% endfor %}
</section> </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"> <section class="stats">
VPS STATS &mdash; {{ vps_stats|safe }} VPS STATS &mdash; {{ vps_stats|safe }}
</section> </section>

View file

@ -2,6 +2,7 @@ import asyncio
import socket import socket
import time import time
from collections import namedtuple from collections import namedtuple
from datetime import datetime
from urllib.parse import urlparse from urllib.parse import urlparse
import aiohttp import aiohttp
@ -11,7 +12,8 @@ from bs4 import BeautifulSoup
from django.views.generic import TemplateView from django.views.generic import TemplateView
from humanize import naturalsize 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): class IndexView(TemplateView):
@ -22,6 +24,7 @@ class IndexView(TemplateView):
return { return {
'links': asyncio.run(self.process_links(list(Link.objects.all()))), 'links': asyncio.run(self.process_links(list(Link.objects.all()))),
'services': asyncio.run(self.process_services(list(Service.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() 'vps_stats': self.vps_stats()
} }
@ -68,13 +71,13 @@ class IndexView(TemplateView):
location = ("localhost", service.port) location = ("localhost", service.port)
result_of_check = a_socket.connect_ex(location) result_of_check = a_socket.connect_ex(location)
if result_of_check == 0: if result_of_check == 0:
status = ServiceStatus.OK status = Status.OK
else: else:
status = ServiceStatus.DOWN status = Status.DOWN
elif index_status: elif index_status:
status = ServiceStatus.OK if index_status == 200 else ServiceStatus.DOWN status = Status.OK if index_status == 200 else Status.DOWN
else: else:
status = ServiceStatus.UNKNOWN status = Status.UNKNOWN
image = service.image.url if service.image else self.extract_favicon(service.url, index_text) 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() await session.close()
return result 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 @staticmethod
def extract_favicon(url, index_text): def extract_favicon(url, index_text):
if not index_text: if not index_text:

View file

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