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 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):
list_display = ('short_name', 'url', 'move_up_down_links')
admin.site.register(Link, LinkAdmin)
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 bs4 import BeautifulSoup
from django.db import models
from ordered_model.models import OrderedModel
@ -20,4 +18,18 @@ class Service(OrderedModel):
url = models.URLField()
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 {
text-align: center;
margin: 1rem 0;
}
main {
h1 {
margin: 1rem 0 0 0;
}
h2 {
margin: 2rem 0 0 0;
}
.boxes {
width: 100%;
display: flex;
flex-wrap: wrap;
@ -36,7 +43,7 @@ main {
margin-bottom: -1rem;
}
.service {
.box {
flex-basis: 20%;
margin: 1rem;
display: flex;
@ -45,44 +52,63 @@ main {
@media screen and (max-width: 1000px) {
.service {
.box {
flex-basis: 40%;
}
}
@media screen and (max-width: 600px) {
.service {
.box {
flex-basis: 100%;
}
}
.service-content {
.box-content {
flex-grow: 1;
border: 1px solid white;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.service-content img {
flex-grow: 1;
min-width: 50%;
max-width: 100%;
.link .box-content {
flex-direction: row;
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 .label h3 {
.service img {
min-width: 50%;
max-width: 100%;
}
.link img {
min-height: 1rem;
max-height: 4rem;
}
.box .label h3 {
margin: 1rem 0;
text-align: center;
}
.service .label .description {
.link .label h3 {
margin: 0 0 1rem;
}
.box .label .description {
font-size: 10pt;
margin: 0;
align-self: flex-start;

View file

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

View file

@ -9,20 +9,45 @@ from bs4 import BeautifulSoup
from django.views.generic import TemplateView
from humanize import naturalsize
from sdbs_infra.dashboard.models import Service, ServiceStatus
from sdbs_infra.dashboard.models import Service, ServiceStatus, Link
class IndexView(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
return {
'links': asyncio.run(self.process_links(list(Link.objects.all()))),
'services': asyncio.run(self.process_services(list(Service.objects.all()))),
'vps_stats': self.vps_stats()
}
@staticmethod
async def process_services(services):
async def process_links(self, links):
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 = []
session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=5, sock_connect=1))
@ -49,20 +74,7 @@ class IndexView(TemplateView):
else:
status = ServiceStatus.UNKNOWN
image = None
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
image = service.image.url if service.image else self.extract_favicon(service.url, index_text)
result.append({
'status': status.value,
@ -71,9 +83,25 @@ class IndexView(TemplateView):
})
await session.close()
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
@staticmethod
def vps_stats():