add machines
This commit is contained in:
parent
f19e04c962
commit
6310c2f878
7 changed files with 121 additions and 19 deletions
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -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}"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 — {{ vps_stats|safe }}
|
VPS STATS — {{ vps_stats|safe }}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue