add important links section

This commit is contained in:
Tomáš Mládek 2020-06-20 19:32:10 +02:00
parent eba01c013a
commit cb623ca83b
6 changed files with 158 additions and 41 deletions

View file

@ -1,11 +1,16 @@
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 from sdbs_infra.dashboard.models import Service, Link
class LinkAdmin(OrderedModelAdmin):
list_display = ('short_name', 'url', 'move_up_down_links')
class ServiceAdmin(OrderedModelAdmin): class ServiceAdmin(OrderedModelAdmin):
list_display = ('short_name', 'url', 'move_up_down_links') list_display = ('short_name', 'url', 'move_up_down_links')
admin.site.register(Link, LinkAdmin)
admin.site.register(Service, ServiceAdmin) admin.site.register(Service, ServiceAdmin)

View file

@ -0,0 +1,28 @@
# Generated by Django 3.0.7 on 2020-06-20 17:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dashboard', '0006_service_port'),
]
operations = [
migrations.CreateModel(
name='Link',
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='links')),
('description', models.TextField(blank=True, null=True)),
('url', models.URLField()),
],
options={
'verbose_name': 'Important Link',
'verbose_name_plural': 'Important Links',
},
),
]

View file

@ -1,7 +1,5 @@
import socket
from enum import Enum from enum import Enum
from bs4 import BeautifulSoup
from django.db import models from django.db import models
from ordered_model.models import OrderedModel from ordered_model.models import OrderedModel
@ -21,3 +19,17 @@ class Service(OrderedModel):
def __str__(self): def __str__(self):
return f"{self.short_name} ({self.url})" return f"{self.short_name} ({self.url})"
class Link(OrderedModel):
short_name = models.CharField(null=False, max_length=64)
image = models.ImageField(null=True, blank=True, upload_to='links')
description = models.TextField(null=True, blank=True)
url = models.URLField()
def __str__(self):
return f"{self.short_name} ({self.url})"
class Meta:
verbose_name = "Important Link"
verbose_name_plural = "Important Links"

View file

@ -25,10 +25,17 @@ a {
h1, h2 { h1, h2 {
text-align: center; text-align: center;
margin: 1rem 0;
} }
main { h1 {
margin: 1rem 0 0 0;
}
h2 {
margin: 2rem 0 0 0;
}
.boxes {
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -36,7 +43,7 @@ main {
margin-bottom: -1rem; margin-bottom: -1rem;
} }
.service { .box {
flex-basis: 20%; flex-basis: 20%;
margin: 1rem; margin: 1rem;
display: flex; display: flex;
@ -45,44 +52,63 @@ main {
@media screen and (max-width: 1000px) { @media screen and (max-width: 1000px) {
.service { .box {
flex-basis: 40%; flex-basis: 40%;
} }
} }
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.service { .box {
flex-basis: 100%; flex-basis: 100%;
} }
} }
.service-content { .box-content {
flex-grow: 1; flex-grow: 1;
border: 1px solid white; border: 1px solid white;
padding: 2rem; padding: 2rem;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center;
} }
.service-content img { .link .box-content {
flex-grow: 1; flex-direction: row;
min-width: 50%; justify-content: space-evenly;
max-width: 100%; }
.service .box-content {
flex-direction: column;
justify-content: flex-end;
}
.box-content img {
filter: grayscale(100%); filter: grayscale(100%);
image-rendering: crisp-edges; image-rendering: crisp-edges;
text-align: center; text-align: center;
} }
.service .label h3 { .service img {
min-width: 50%;
max-width: 100%;
}
.link img {
min-height: 1rem;
max-height: 4rem;
}
.box .label h3 {
margin: 1rem 0; margin: 1rem 0;
text-align: center; text-align: center;
} }
.service .label .description { .link .label h3 {
margin: 0 0 1rem;
}
.box .label .description {
font-size: 10pt; font-size: 10pt;
margin: 0; margin: 0;
align-self: flex-start; align-self: flex-start;

View file

@ -10,12 +10,30 @@
<body> <body>
<header> <header>
<h1>/-\ infrastructure</h1> <h1>/-\ infrastructure</h1>
<h2>status&nbsp;page (internal&nbsp;services)</h2>
</header> </header>
<main> <h2>important links</h2>
<section class="links boxes">
{% for link in links %}
<a class="link box" href="{{ link.url }}">
<section class="box-content">
{% if link.image_url %}
<img src="{{ link.image_url }}" alt="image for {{ link.short_name }}"/>
{% endif %}
<div class="label">
<h3>{{ link.short_name }}</h3>
{% if link.description %}
<p class="description">{{ link.description }}</p>
{% endif %}
</div>
</section>
</a>
{% endfor %}
</section>
<h2>internal&nbsp;services</h2>
<section class="services boxes">
{% for service in services %} {% for service in services %}
<a class="service status-{{ service.status }}" href="{{ service.url }}"> <a class="service box status-{{ service.status }}" href="{{ service.url }}">
<section class="service-content"> <section class="box-content">
{% if service.image_url %} {% if service.image_url %}
<img src="{{ service.image_url }}" alt="image for {{ service.short_name }}"/> <img src="{{ service.image_url }}" alt="image for {{ service.short_name }}"/>
{% endif %} {% endif %}
@ -31,7 +49,7 @@
</section> </section>
</a> </a>
{% endfor %} {% endfor %}
</main> </section>
<section class="stats"> <section class="stats">
VPS STATS &mdash; {{ vps_stats|safe }} VPS STATS &mdash; {{ vps_stats|safe }}
</section> </section>

View file

@ -9,20 +9,45 @@ 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 from sdbs_infra.dashboard.models import Service, ServiceStatus, Link
class IndexView(TemplateView): class IndexView(TemplateView):
template_name = "index.html" template_name = "index.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
return { return {
'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()))),
'vps_stats': self.vps_stats() 'vps_stats': self.vps_stats()
} }
@staticmethod async def process_links(self, links):
async def process_services(services): result = []
session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5, sock_connect=1))
for link in links:
index_text = None, None
if not link.image:
try:
async with session.get(link.url) as response:
index_status, index_text = response.status, await response.text()
except asyncio.TimeoutError:
pass
image = link.image.url if link.image else self.extract_favicon(link.url, index_text)
result.append({
'image_url': image,
**vars(link)
})
await session.close()
return result
async def process_services(self, services):
result = [] result = []
session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5, sock_connect=1)) session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5, sock_connect=1))
@ -49,20 +74,7 @@ class IndexView(TemplateView):
else: else:
status = ServiceStatus.UNKNOWN status = ServiceStatus.UNKNOWN
image = None image = service.image.url if service.image else self.extract_favicon(service.url, index_text)
if service.image:
image = service.image.url
elif index_text:
parsed_html = BeautifulSoup(index_text, features="html.parser")
link_tags = parsed_html.find_all('link')
for rel in ['apple-touch-icon', 'shortcut', 'icon']:
for link_tag in link_tags:
if rel in link_tag.attrs['rel']:
link = link_tag.attrs['href']
if service.url not in link:
image = service.url + (link if link.startswith("/") else f"/{link}")
else:
image = link
result.append({ result.append({
'status': status.value, 'status': status.value,
@ -71,9 +83,25 @@ class IndexView(TemplateView):
}) })
await session.close() await session.close()
return result return result
@staticmethod
def extract_favicon(url, index_text):
if not index_text:
return None
parsed_html = BeautifulSoup(index_text, features="html.parser")
link_tags = parsed_html.find_all('link')
for rel in ['apple-touch-icon', 'shortcut', 'icon']:
for link_tag in link_tags:
if rel in link_tag.attrs['rel']:
href = link_tag.attrs['href']
if url not in href:
image = url + (href if href.startswith("/") else f"/{href}")
else:
image = href
return image
# noinspection PyListCreation # noinspection PyListCreation
@staticmethod @staticmethod
def vps_stats(): def vps_stats():