better templating

This commit is contained in:
Evgenii Alekseev 2021-03-13 05:12:53 +03:00
parent 3405105dce
commit 356cd35c5f
14 changed files with 193 additions and 152 deletions

View File

@ -23,7 +23,7 @@ optdepends=('aws-cli: sync to s3'
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz" source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz"
'ahriman.sysusers' 'ahriman.sysusers'
'ahriman.tmpfiles') 'ahriman.tmpfiles')
sha512sums=('ef3c5bf73f95a87f226bc7ca90cee990abf8e7d7dacf56ce5ab346e689019a414a1ffe186a38c75bbf2c9fcc7e04822455e704c67fe2e125c5883dee8066f4bc' sha512sums=('70b2fd83859bdab15037137bf79a599116e52ddf901a401f0a7650f0009770f060e11936f8dd8e57800cd14f1a734ecfd31cde56456506896f7093fd7309034f'
'13718afec2c6786a18f0b223ef8e58dccf0688bca4cdbe203f14071f5031ed20120eb0ce38b52c76cfd6e8b6581a9c9eaa2743eb11abbaca637451a84c33f075' '13718afec2c6786a18f0b223ef8e58dccf0688bca4cdbe203f14071f5031ed20120eb0ce38b52c76cfd6e8b6581a9c9eaa2743eb11abbaca637451a84c33f075'
'55b20f6da3d66e7bbf2add5d95a3b60632df121717d25a993e56e737d14f51fe063eb6f1b38bd81cc32e05db01c0c1d80aaa720c45cde87f238d8b46cdb8cbc4') '55b20f6da3d66e7bbf2add5d95a3b60632df121717d25a993e56e737d14f51fe063eb6f1b38bd81cc32e05db01c0c1d80aaa720c45cde87f238d8b46cdb8cbc4')
backup=('etc/ahriman.ini' backup=('etc/ahriman.ini'

View File

@ -3,118 +3,20 @@
<head> <head>
<title>{{ repository|e }}</title> <title>{{ repository|e }}</title>
<style> {% include "style.jinja2" %}
:root {
--color-building: 250, 255, 146;
--color-failed: 255, 94, 94;
--color-pending: 250, 255, 146;
--color-success: 121, 255, 94;
--color-unknown: 197, 197, 197;
}
@keyframes blink-building {
0% { background-color: rgba(var(--color-building), 1.0); }
10% { background-color: rgba(var(--color-building), 0.9); }
20% { background-color: rgba(var(--color-building), 0.8); }
30% { background-color: rgba(var(--color-building), 0.7); }
40% { background-color: rgba(var(--color-building), 0.6); }
50% { background-color: rgba(var(--color-building), 0.5); }
60% { background-color: rgba(var(--color-building), 0.4); }
70% { background-color: rgba(var(--color-building), 0.3); }
80% { background-color: rgba(var(--color-building), 0.2); }
90% { background-color: rgba(var(--color-building), 0.1); }
100% { background-color: rgba(var(--color-building), 0.0); }
}
div.root {
width: 70%;
padding: 15px 15% 0;
}
section.element {
width: 100%;
}
input, table {
width: inherit;
padding: 10px 0;
}
th, td {
padding: 5px;
}
tr.package:nth-child(odd) {
background-color: rgba(255, 255, 255, 1);
}
tr.package:nth-child(even) {
background-color: rgba(235, 235, 255, 1);
}
tr.header, tr.package:hover {
background-color: rgba(200, 200, 255, 1);
}
td.package-unknown {
background-color: rgba(var(--color-unknown), 1.0);
}
td.package-pending {
background-color: rgba(var(--color-pending), 1.0);
}
td.package-building {
background-color: rgba(var(--color-building), 1.0);
animation-name: blink-building;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
td.package-failed {
background-color: rgba(var(--color-failed), 1.0);
}
td.package-success {
background-color: rgba(var(--color-success), 1.0);
}
</style>
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
<script type="text/javascript">
function searchInTable() {
const input = document.getElementById("search");
const filter = input.value.toLowerCase();
const table = document.getElementById("builds");
const tr = table.getElementsByTagName("tr");
// from 1 coz of header
for (var i = 1; i < tr.length; i++) {
let td = tr[i].getElementsByClassName("include-search");
let display = "none";
for (var j = 0; j < td.length; j++) {
if (td[j].tagName.toLowerCase() === "td") {
if (td[j].innerHTML.toLowerCase().indexOf(filter) > -1) {
display = "";
break;
}
}
}
tr[i].style.display = display;
}
}
</script>
{% include "sorttable.jinja2" %}
{% include "search.jinja2" %}
</head> </head>
<body> <body>
<div class="root"> <div class="root">
<h1>ahriman {{ version|e }}</h1> <h1>ahriman {{ version|e }}</h1>
<section class="element"> {% include "search-line.jinja2" %}
<input type="search" id="search" onkeyup="searchInTable()" placeholder="search for package" title="search for package"/>
</section>
<section class="element"> <section class="element">
<table class="sortable" id="builds"> <table class="sortable search-table">
<tr class="header"> <tr class="header">
<th>package base</th> <th>package base</th>
<th>packages</th> <th>packages</th>
@ -126,7 +28,7 @@
{% for package in packages %} {% for package in packages %}
<tr class="package"> <tr class="package">
<td class="package include-search"><a href="{{ package.web_url|e }}" title="{{ package.base|e }}">{{ package.base|e }}</a></td> <td class="include-search"><a href="{{ package.web_url|e }}" title="{{ package.base|e }}">{{ package.base|e }}</a></td>
<td class="include-search">{{ package.packages|join("<br>"|safe) }}</td> <td class="include-search">{{ package.packages|join("<br>"|safe) }}</td>
<td>{{ package.version|e }}</td> <td>{{ package.version|e }}</td>
<td>{{ architecture|e }}</td> <td>{{ architecture|e }}</td>

View File

@ -2,31 +2,53 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>{{ repository|e }}</title> <title>{{ repository|e }}</title>
{% include "style.jinja2" %}
{% include "sorttable.jinja2" %}
{% include "search.jinja2" %}
</head> </head>
<body> <body>
<h1>Archlinux custom repository</h1> <div class="root">
<h1>Archlinux user repository</h1>
{% if pgp_key is not none %} <section class="element">
<p>This repository is signed with <a href="http://keys.gnupg.net/pks/lookup?search=0x{{ pgp_key|e }}" title="key search">{{ pgp_key|e }}</a>.</p> {% if pgp_key is not none %}
{% endif %} <p>This repository is signed with <a href="http://keys.gnupg.net/pks/lookup?search=0x{{ pgp_key|e }}&fingerprint=on&op=index" title="key search">{{ pgp_key|e }}</a> by default.</p>
{% endif %}
<code> <code>
$ cat /etc/pacman.conf<br> $ cat /etc/pacman.conf<br>
[{{ repository|e }}]<br> [{{ repository|e }}]<br>
Server = {{ link_path|e }}<br> Server = {{ link_path|e }}<br>
SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Package{% if has_package_signed %}Required{% else %}Never{% endif %} TrustedOnly SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Package{% if has_package_signed %}Required{% else %}Never{% endif %} TrustedOnly
</code> </code>
</section>
<p>Packages:</p> {% include "search-line.jinja2" %}
<ul>
{% for package, package_url in packages.items() %}
<li><a href="{{ package_url|e }}" title="{{ package|e }}">{{ package|e }}</a></li>
{% endfor %}
</ul>
{% if homepage is not none %} <section class="element">
<footer><a href="{{ homepage|e }}" title="homepage">Homepage</a></footer> <table class="sortable search-table">
{% endif %} <tr class="header">
<th>package</th>
<th>version</th>
<th>architecture</th>
</tr>
{% for package in packages %}
<tr class="package">
<td class="include-search"><a href="{{ link_path|e }}/{{ package.filename|e }}" title="{{ package.name|e }}">{{ package.name|e }}</a></td>
<td>{{ package.version|e }}</td>
<td>{{ architecture|e }}</td>
</tr>
{% endfor %}
</table>
</section>
{% if homepage is not none %}
<footer><a href="{{ homepage|e }}" title="homepage">Homepage</a></footer>
{% endif %}
</div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,3 @@
<section class="element">
<input type="search" id="search" onkeyup="searchInTable()" placeholder="search for package" title="search for package"/>
</section>

View File

@ -0,0 +1,25 @@
<script type="text/javascript">
function searchInTable() {
const input = document.getElementById("search");
const filter = input.value.toLowerCase();
const tables = document.getElementsByClassName("search-table");
for (let i = 0; i < tables.length; i++) {
const tr = tables[i].getElementsByTagName("tr");
// from 1 coz of header
for (let i = 1; i < tr.length; i++) {
let td = tr[i].getElementsByClassName("include-search");
let display = "none";
for (let j = 0; j < td.length; j++) {
if (td[j].tagName.toLowerCase() === "td") {
if (td[j].innerHTML.toLowerCase().indexOf(filter) > -1) {
display = "";
break;
}
}
}
tr[i].style.display = display;
}
}
}
</script>

View File

@ -0,0 +1 @@
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>

View File

@ -0,0 +1,74 @@
<style>
:root {
--color-building: 250, 255, 146;
--color-failed: 255, 94, 94;
--color-pending: 250, 255, 146;
--color-success: 121, 255, 94;
--color-unknown: 197, 197, 197;
}
@keyframes blink-building {
0% { background-color: rgba(var(--color-building), 1.0); }
10% { background-color: rgba(var(--color-building), 0.9); }
20% { background-color: rgba(var(--color-building), 0.8); }
30% { background-color: rgba(var(--color-building), 0.7); }
40% { background-color: rgba(var(--color-building), 0.6); }
50% { background-color: rgba(var(--color-building), 0.5); }
60% { background-color: rgba(var(--color-building), 0.4); }
70% { background-color: rgba(var(--color-building), 0.3); }
80% { background-color: rgba(var(--color-building), 0.2); }
90% { background-color: rgba(var(--color-building), 0.1); }
100% { background-color: rgba(var(--color-building), 0.0); }
}
div.root {
width: 70%;
padding: 15px 15% 0;
}
section.element {
width: 100%;
padding: 10px 0;
}
code, input, table {
width: inherit;
}
th, td {
padding: 5px;
}
tr.package:nth-child(odd) {
background-color: rgba(255, 255, 255, 1);
}
tr.package:nth-child(even) {
background-color: rgba(235, 235, 255, 1);
}
tr.header, tr.package:hover {
background-color: rgba(200, 200, 255, 1);
}
td.package-unknown {
background-color: rgba(var(--color-unknown), 1.0);
}
td.package-pending {
background-color: rgba(var(--color-pending), 1.0);
}
td.package-building {
background-color: rgba(var(--color-building), 1.0);
animation-name: blink-building;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
td.package-failed {
background-color: rgba(var(--color-failed), 1.0);
}
td.package-success {
background-color: rgba(var(--color-success), 1.0);
}
</style>

View File

@ -57,6 +57,10 @@ setup(
('share/ahriman', [ ('share/ahriman', [
'package/share/ahriman/build-status.jinja2', 'package/share/ahriman/build-status.jinja2',
'package/share/ahriman/repo-index.jinja2', 'package/share/ahriman/repo-index.jinja2',
'package/share/ahriman/search.jinja2',
'package/share/ahriman/search-line.jinja2',
'package/share/ahriman/sorttable.jinja2',
'package/share/ahriman/style.jinja2',
]), ]),
], ],

View File

@ -47,10 +47,10 @@ class Application:
return cls(args.architecture, config) return cls(args.architecture, config)
def _known_packages(self) -> Set[str]: def _known_packages(self) -> Set[str]:
known_packages = set() known_packages: Set[str] = set()
# local set # local set
for package in self.repository.packages(): for package in self.repository.packages():
known_packages.update(package.packages) known_packages.update(package.packages.keys())
known_packages.update(self.repository.pacman.all_packages()) known_packages.update(self.repository.pacman.all_packages())
return known_packages return known_packages

View File

@ -20,11 +20,12 @@
import jinja2 import jinja2
import os import os
from typing import Dict from typing import Dict, Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report.report import Report from ahriman.core.report.report import Report
from ahriman.core.util import package_like from ahriman.core.util import package_like
from ahriman.models.package import Package
from ahriman.models.sign_settings import SignSettings from ahriman.models.sign_settings import SignSettings
@ -32,37 +33,42 @@ class HTML(Report):
def __init__(self, architecture: str, config: Configuration) -> None: def __init__(self, architecture: str, config: Configuration) -> None:
Report.__init__(self, architecture, config) Report.__init__(self, architecture, config)
self.architecture = architecture
section = config.get_section_name('html', architecture) section = config.get_section_name('html', architecture)
self.report_path = config.get(section, 'path') self.report_path = config.get(section, 'path')
self.link_path = config.get(section, 'link_path') self.link_path = config.get(section, 'link_path')
self.template_path = config.get(section, 'template_path') self.template_path = config.get(section, 'template_path')
# base template vars # base template vars
self.sign_targets = [SignSettings.from_option(opt) for opt in config.getlist('sign', 'target')]
self.pgp_key = config.get('sign', 'key', fallback=None)
self.homepage = config.get(section, 'homepage', fallback=None) self.homepage = config.get(section, 'homepage', fallback=None)
self.repository = config.get('repository', 'name') self.repository = config.get('repository', 'name')
def generate(self, path: str) -> None: sign_section = config.get_section_name('sign', architecture)
self.sign_targets = [SignSettings.from_option(opt) for opt in config.getlist(sign_section, 'target')]
self.pgp_key = config.get(sign_section, 'key') if self.sign_targets else None
def generate(self, packages: Iterable[Package]) -> None:
# idea comes from https://stackoverflow.com/a/38642558 # idea comes from https://stackoverflow.com/a/38642558
templates_dir, template_name = os.path.split(self.template_path) templates_dir, template_name = os.path.split(self.template_path)
loader = jinja2.FileSystemLoader(searchpath=templates_dir) loader = jinja2.FileSystemLoader(searchpath=templates_dir)
environment = jinja2.Environment(loader=loader) environment = jinja2.Environment(loader=loader)
template = environment.get_template(template_name) template = environment.get_template(template_name)
packages: Dict[str, str] = {} content = [
for fn in sorted(os.listdir(path)): {
if not package_like(fn): 'filename': filename,
continue 'name': package,
packages[fn] = f'{self.link_path}/{fn}' 'version': base.version
} for base in packages for package, filename in base.packages.items()
]
html = template.render( html = template.render(
architecture=self.architecture,
homepage=self.homepage, homepage=self.homepage,
link_path=self.link_path, link_path=self.link_path,
has_package_signed=SignSettings.SignPackages in self.sign_targets, has_package_signed=SignSettings.SignPackages in self.sign_targets,
has_repo_signed=SignSettings.SignRepository in self.sign_targets, has_repo_signed=SignSettings.SignRepository in self.sign_targets,
packages=packages, packages=content,
pgp_key=self.pgp_key, pgp_key=self.pgp_key,
repository=self.repository) repository=self.repository)

View File

@ -19,8 +19,11 @@
# #
import logging import logging
from typing import Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ReportFailed from ahriman.core.exceptions import ReportFailed
from ahriman.models.package import Package
from ahriman.models.report_settings import ReportSettings from ahriman.models.report_settings import ReportSettings
@ -32,7 +35,7 @@ class Report:
self.config = config self.config = config
@staticmethod @staticmethod
def run(architecture: str, config: Configuration, target: str, path: str) -> None: def run(architecture: str, config: Configuration, target: str, packages: Iterable[Package]) -> None:
provider = ReportSettings.from_option(target) provider = ReportSettings.from_option(target)
if provider == ReportSettings.HTML: if provider == ReportSettings.HTML:
from ahriman.core.report.html import HTML from ahriman.core.report.html import HTML
@ -41,10 +44,10 @@ class Report:
report = Report(architecture, config) report = Report(architecture, config)
try: try:
report.generate(path) report.generate(packages)
except Exception: except Exception:
report.logger.exception('report generation failed', exc_info=True) report.logger.exception('report generation failed', exc_info=True)
raise ReportFailed() raise ReportFailed()
def generate(self, path: str) -> None: def generate(self, packages: Iterable[Package]) -> None:
pass pass

View File

@ -114,11 +114,12 @@ class Repository:
except Exception: except Exception:
self.logger.exception(f'could not remove {package}', exc_info=True) self.logger.exception(f'could not remove {package}', exc_info=True)
requested = set(packages)
for local in self.packages(): for local in self.packages():
if local.base in packages: if local.base in packages:
to_remove = local.packages to_remove = set(local.packages.keys())
elif local.packages.intersection(packages): elif requested.intersection(local.packages.keys()):
to_remove = local.packages.intersection(packages) to_remove = requested.intersection(local.packages.keys())
else: else:
to_remove = set() to_remove = set()
self.web.remove(local.base, to_remove) self.web.remove(local.base, to_remove)
@ -131,7 +132,7 @@ class Repository:
if targets is None: if targets is None:
targets = self.config.getlist('report', 'target') targets = self.config.getlist('report', 'target')
for target in targets: for target in targets:
Report.run(self.architecture, self.config, target, self.paths.repository) Report.run(self.architecture, self.config, target, self.packages())
def process_sync(self, targets: Optional[Iterable[str]]) -> None: def process_sync(self, targets: Optional[Iterable[str]]) -> None:
if targets is None: if targets is None:

View File

@ -61,8 +61,8 @@ class Leaf:
:param packages: :param packages:
:return: true if any of packages is dependency of the leaf, false otherwise :return: true if any of packages is dependency of the leaf, false otherwise
''' '''
for package in packages: for leaf in packages:
if package.package.packages.intersection(self.dependencies): if self.dependencies.intersection(leaf.package.packages.keys()):
return False return False
return True return True

View File

@ -24,9 +24,9 @@ import os
import shutil import shutil
import tempfile import tempfile
from dataclasses import dataclass, field from dataclasses import dataclass
from srcinfo.parse import parse_srcinfo from srcinfo.parse import parse_srcinfo
from typing import List, Optional, Set, Type from typing import Dict, List, Optional, Set, Type
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
@ -38,7 +38,7 @@ class Package:
base: str base: str
version: str version: str
aur_url: str aur_url: str
packages: Set[str] = field(default_factory=set) packages: Dict[str, str] # map of package name to archive name
@property @property
def git_url(self) -> str: def git_url(self) -> str:
@ -82,12 +82,12 @@ class Package:
@classmethod @classmethod
def from_archive(cls: Type[Package], path: str, pacman: Pacman, aur_url: str) -> Package: def from_archive(cls: Type[Package], path: str, pacman: Pacman, aur_url: str) -> Package:
package = pacman.handle.load_pkg(path) package = pacman.handle.load_pkg(path)
return cls(package.base, package.version, aur_url, {package.name}) return cls(package.base, package.version, aur_url, {package.name: os.path.basename(path)})
@classmethod @classmethod
def from_aur(cls: Type[Package], name: str, aur_url: str)-> Package: def from_aur(cls: Type[Package], name: str, aur_url: str)-> Package:
package = aur.info(name) package = aur.info(name)
return cls(package.package_base, package.version, aur_url, {package.name}) return cls(package.package_base, package.version, aur_url, {package.name: ''})
@classmethod @classmethod
def from_build(cls: Type[Package], path: str, aur_url: str) -> Package: def from_build(cls: Type[Package], path: str, aur_url: str) -> Package:
@ -95,7 +95,7 @@ class Package:
src_info, errors = parse_srcinfo(fn.read()) src_info, errors = parse_srcinfo(fn.read())
if errors: if errors:
raise InvalidPackageInfo(errors) raise InvalidPackageInfo(errors)
packages = set(src_info['packages'].keys()) packages = {key: '' for key in src_info['packages'].keys()}
version = cls.full_version(src_info.get('epoch'), src_info['pkgver'], src_info['pkgrel']) version = cls.full_version(src_info.get('epoch'), src_info['pkgver'], src_info['pkgrel'])
return cls(src_info['pkgbase'], version, aur_url, packages) return cls(src_info['pkgbase'], version, aur_url, packages)