mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
complete web tests
This commit is contained in:
parent
2c90efc339
commit
839b241ec1
2
setup.py
2
setup.py
@ -36,7 +36,7 @@ setup(
|
||||
],
|
||||
tests_require=[
|
||||
"pytest",
|
||||
"pytest-asyncio",
|
||||
"pytest-aiohttp",
|
||||
"pytest-cov",
|
||||
"pytest-helpers-namespace",
|
||||
"pytest-mock",
|
||||
|
@ -35,8 +35,8 @@ class Pacman:
|
||||
:param config: configuration instance
|
||||
"""
|
||||
root = config.get("alpm", "root")
|
||||
pacman_root = config.get("alpm", "database")
|
||||
self.handle = Handle(root, pacman_root)
|
||||
pacman_root = config.getpath("alpm", "database")
|
||||
self.handle = Handle(root, str(pacman_root))
|
||||
for repository in config.getlist("alpm", "repositories"):
|
||||
self.handle.register_syncdb(repository, 0) # 0 is pgp_level
|
||||
|
||||
|
@ -55,8 +55,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
"""
|
||||
:return: path to directory with configuration includes
|
||||
"""
|
||||
value = Path(self.get("settings", "include"))
|
||||
return self.absolute_path_for(value)
|
||||
return self.getpath("settings", "include")
|
||||
|
||||
@classmethod
|
||||
def from_path(cls: Type[Configuration], path: Path, logfile: bool) -> Configuration:
|
||||
@ -71,16 +70,6 @@ class Configuration(configparser.RawConfigParser):
|
||||
config.load_logging(logfile)
|
||||
return config
|
||||
|
||||
def absolute_path_for(self, path_part: Path) -> Path:
|
||||
"""
|
||||
helper to generate absolute configuration path for relative settings value
|
||||
:param path_part: path to generate
|
||||
:return: absolute path according to current path configuration
|
||||
"""
|
||||
if self.path is None or path_part.is_absolute():
|
||||
return path_part
|
||||
return self.path.parent / path_part
|
||||
|
||||
def dump(self, architecture: str) -> Dict[str, Dict[str, str]]:
|
||||
"""
|
||||
dump configuration to dictionary
|
||||
@ -112,6 +101,18 @@ class Configuration(configparser.RawConfigParser):
|
||||
return []
|
||||
return raw.split()
|
||||
|
||||
def getpath(self, section: str, key: str) -> Path:
|
||||
"""
|
||||
helper to generate absolute configuration path for relative settings value
|
||||
:param section: section name
|
||||
:param key: key name
|
||||
:return: absolute path according to current path configuration
|
||||
"""
|
||||
value = Path(self.get(section, key))
|
||||
if self.path is None or value.is_absolute():
|
||||
return value
|
||||
return self.path.parent / value
|
||||
|
||||
def get_section_name(self, prefix: str, suffix: str) -> str:
|
||||
"""
|
||||
check if there is `prefix`_`suffix` section and return it on success. Return `prefix` otherwise
|
||||
@ -148,8 +149,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
"""
|
||||
def file_logger() -> None:
|
||||
try:
|
||||
value = Path(self.get("settings", "logging"))
|
||||
config_path = self.absolute_path_for(value)
|
||||
config_path = self.getpath("settings", "logging")
|
||||
fileConfig(config_path)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
console_logger()
|
||||
|
@ -19,7 +19,6 @@
|
||||
#
|
||||
import jinja2
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Callable, Dict, Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -60,9 +59,9 @@ class HTML(Report):
|
||||
"""
|
||||
Report.__init__(self, architecture, config)
|
||||
section = config.get_section_name("html", architecture)
|
||||
self.report_path = Path(config.get(section, "path"))
|
||||
self.report_path = config.getpath(section, "path")
|
||||
self.link_path = config.get(section, "link_path")
|
||||
self.template_path = Path(config.get(section, "template_path"))
|
||||
self.template_path = config.getpath(section, "template_path")
|
||||
|
||||
# base template vars
|
||||
self.homepage = config.get(section, "homepage", fallback=None)
|
||||
|
@ -19,8 +19,6 @@
|
||||
#
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -52,7 +50,7 @@ class Properties:
|
||||
self.aur_url = config.get("alpm", "aur_url")
|
||||
self.name = config.get("repository", "name")
|
||||
|
||||
self.paths = RepositoryPaths(Path(config.get("repository", "root")), architecture)
|
||||
self.paths = RepositoryPaths(config.getpath("repository", "root"), architecture)
|
||||
self.paths.create_tree()
|
||||
|
||||
self.pacman = Pacman(config)
|
||||
|
@ -82,8 +82,9 @@ def setup_service(architecture: str, config: Configuration) -> web.Application:
|
||||
|
||||
application.logger.info("setup routes")
|
||||
setup_routes(application)
|
||||
|
||||
application.logger.info("setup templates")
|
||||
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(config.get("web", "templates")))
|
||||
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(config.getpath("web", "templates")))
|
||||
|
||||
application.logger.info("setup configuration")
|
||||
application["config"] = config
|
||||
|
@ -83,10 +83,10 @@ def package_description_python2_schedule() -> PackageDescription:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repository_paths() -> RepositoryPaths:
|
||||
def repository_paths(configuration: Configuration) -> RepositoryPaths:
|
||||
return RepositoryPaths(
|
||||
architecture="x86_64",
|
||||
root=Path("/var/lib/ahriman"))
|
||||
root=configuration.getpath("repository", "root"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -26,7 +26,8 @@ def test_absolute_path_for_absolute(configuration: Configuration) -> None:
|
||||
must not change path for absolute path in settings
|
||||
"""
|
||||
path = Path("/a/b/c")
|
||||
assert configuration.absolute_path_for(path) == path
|
||||
configuration.set("build", "path", str(path))
|
||||
assert configuration.getpath("build", "path") == path
|
||||
|
||||
|
||||
def test_absolute_path_for_relative(configuration: Configuration) -> None:
|
||||
@ -34,7 +35,8 @@ def test_absolute_path_for_relative(configuration: Configuration) -> None:
|
||||
must prepend root path to relative path
|
||||
"""
|
||||
path = Path("a")
|
||||
result = configuration.absolute_path_for(path)
|
||||
configuration.set("build", "path", str(path))
|
||||
result = configuration.getpath("build", "path")
|
||||
assert result.is_absolute()
|
||||
assert result.parent == configuration.path.parent
|
||||
assert result.name == path.name
|
||||
|
@ -8,7 +8,6 @@ from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.web.web import on_startup, run_server
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_on_startup(application: web.Application, watcher: Watcher, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call load method
|
||||
@ -20,7 +19,6 @@ async def test_on_startup(application: web.Application, watcher: Watcher, mocker
|
||||
load_mock.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_on_startup_exception(application: web.Application, watcher: Watcher, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must throw exception on load error
|
||||
|
14
tests/ahriman/web/views/conftest.py
Normal file
14
tests/ahriman/web/views/conftest.py
Normal file
@ -0,0 +1,14 @@
|
||||
import pytest
|
||||
|
||||
from aiohttp import web
|
||||
from asyncio import BaseEventLoop
|
||||
from pytest_aiohttp import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(application: web.Application, loop: BaseEventLoop,
|
||||
aiohttp_client: Any, mocker: MockerFixture) -> TestClient:
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[])
|
||||
return loop.run_until_complete(aiohttp_client(application))
|
37
tests/ahriman/web/views/test_view_ahriman.py
Normal file
37
tests/ahriman/web/views/test_view_ahriman.py
Normal file
@ -0,0 +1,37 @@
|
||||
from aiohttp.test_utils import TestClient
|
||||
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
|
||||
|
||||
async def test_get(client: TestClient) -> None:
|
||||
"""
|
||||
must return valid service status
|
||||
"""
|
||||
response = await client.get("/api/v1/ahriman")
|
||||
status = BuildStatus.from_json(await response.json())
|
||||
|
||||
assert response.status == 200
|
||||
assert status.status == BuildStatusEnum.Unknown
|
||||
|
||||
|
||||
async def test_post(client: TestClient) -> None:
|
||||
"""
|
||||
must update service status correctly
|
||||
"""
|
||||
payload = {"status": BuildStatusEnum.Success.value}
|
||||
post_response = await client.post("/api/v1/ahriman", json=payload)
|
||||
assert post_response.status == 204
|
||||
|
||||
response = await client.get("/api/v1/ahriman")
|
||||
status = BuildStatus.from_json(await response.json())
|
||||
|
||||
assert response.status == 200
|
||||
assert status.status == BuildStatusEnum.Success
|
||||
|
||||
|
||||
async def test_post_exception(client: TestClient) -> None:
|
||||
"""
|
||||
must raise exception on invalid payload
|
||||
"""
|
||||
post_response = await client.post("/api/v1/ahriman", json={})
|
||||
assert post_response.status == 400
|
19
tests/ahriman/web/views/test_view_index.py
Normal file
19
tests/ahriman/web/views/test_view_index.py
Normal file
@ -0,0 +1,19 @@
|
||||
from pytest_aiohttp import TestClient
|
||||
|
||||
|
||||
async def test_get(client: TestClient) -> None:
|
||||
"""
|
||||
must generate status page correctly (/)
|
||||
"""
|
||||
response = await client.get("/")
|
||||
assert response.status == 200
|
||||
assert await response.text()
|
||||
|
||||
|
||||
async def test_get_index(client: TestClient) -> None:
|
||||
"""
|
||||
must generate status page correctly (/index.html)
|
||||
"""
|
||||
response = await client.get("/index.html")
|
||||
assert response.status == 200
|
||||
assert await response.text()
|
117
tests/ahriman/web/views/test_view_package.py
Normal file
117
tests/ahriman/web/views/test_view_package.py
Normal file
@ -0,0 +1,117 @@
|
||||
from pytest_aiohttp import TestClient
|
||||
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must return status for specific package
|
||||
"""
|
||||
await client.post(f"/api/v1/packages/{package_ahriman.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
|
||||
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 200
|
||||
|
||||
packages = [Package.from_json(item["package"]) for item in await response.json()]
|
||||
assert packages
|
||||
assert {package.base for package in packages} == {package_ahriman.base}
|
||||
|
||||
|
||||
async def test_get_not_found(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return Not Found for unknown package
|
||||
"""
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 404
|
||||
|
||||
|
||||
async def test_delete(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must delete single base
|
||||
"""
|
||||
await client.post(f"/api/v1/packages/{package_ahriman.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
|
||||
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
|
||||
|
||||
response = await client.delete(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 204
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 404
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_python_schedule.base}")
|
||||
assert response.status == 200
|
||||
|
||||
|
||||
async def test_delete_unknown(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must suppress errors on unknown package deletion
|
||||
"""
|
||||
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
|
||||
|
||||
response = await client.delete(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 204
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 404
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_python_schedule.base}")
|
||||
assert response.status == 200
|
||||
|
||||
|
||||
async def test_post(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must update package status
|
||||
"""
|
||||
post_response = await client.post(
|
||||
f"/api/v1/packages/{package_ahriman.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
|
||||
assert post_response.status == 204
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 200
|
||||
|
||||
|
||||
async def test_post_exception(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise exception on invalid payload
|
||||
"""
|
||||
post_response = await client.post(f"/api/v1/packages/{package_ahriman.base}", json={})
|
||||
assert post_response.status == 400
|
||||
|
||||
|
||||
async def test_post_light(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must update package status only
|
||||
"""
|
||||
post_response = await client.post(
|
||||
f"/api/v1/packages/{package_ahriman.base}",
|
||||
json={"status": BuildStatusEnum.Unknown.value, "package": package_ahriman.view()})
|
||||
assert post_response.status == 204
|
||||
|
||||
post_response = await client.post(
|
||||
f"/api/v1/packages/{package_ahriman.base}", json={"status": BuildStatusEnum.Success.value})
|
||||
assert post_response.status == 204
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
|
||||
assert response.status == 200
|
||||
statuses = {
|
||||
Package.from_json(item["package"]).base: BuildStatus.from_json(item["status"])
|
||||
for item in await response.json()
|
||||
}
|
||||
assert statuses[package_ahriman.base].status == BuildStatusEnum.Success
|
||||
|
||||
|
||||
async def test_post_not_found(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise exception on status update for unknown package
|
||||
"""
|
||||
post_response = await client.post(
|
||||
f"/api/v1/packages/{package_ahriman.base}", json={"status": BuildStatusEnum.Success.value})
|
||||
assert post_response.status == 400
|
32
tests/ahriman/web/views/test_view_packages.py
Normal file
32
tests/ahriman/web/views/test_view_packages.py
Normal file
@ -0,0 +1,32 @@
|
||||
from pytest_aiohttp import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must return status for all packages
|
||||
"""
|
||||
await client.post(f"/api/v1/packages/{package_ahriman.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
|
||||
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
|
||||
|
||||
response = await client.get("/api/v1/packages")
|
||||
assert response.status == 200
|
||||
|
||||
packages = [Package.from_json(item["package"]) for item in await response.json()]
|
||||
assert packages
|
||||
assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base}
|
||||
|
||||
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must be able to reload packages
|
||||
"""
|
||||
load_mock = mocker.patch("ahriman.core.status.watcher.Watcher.load")
|
||||
response = await client.post("/api/v1/packages")
|
||||
assert response.status == 204
|
||||
load_mock.assert_called_once()
|
@ -29,7 +29,7 @@ target =
|
||||
path =
|
||||
homepage =
|
||||
link_path =
|
||||
template_path = /usr/share/ahriman/repo-index.jinja2
|
||||
template_path = ../web/templates/repo-index.jinja2
|
||||
|
||||
[upload]
|
||||
target =
|
||||
@ -41,4 +41,4 @@ remote =
|
||||
bucket =
|
||||
|
||||
[web]
|
||||
templates = /usr/share/ahriman
|
||||
templates = ../web/templates
|
54
tests/testresources/web/templates/build-status.jinja2
Normal file
54
tests/testresources/web/templates/build-status.jinja2
Normal file
@ -0,0 +1,54 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ repository|e }}</title>
|
||||
|
||||
{% include "style.jinja2" %}
|
||||
|
||||
{% include "sorttable.jinja2" %}
|
||||
{% include "search.jinja2" %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="root">
|
||||
<h1>ahriman
|
||||
<img src="https://img.shields.io/badge/version-{{ version|e }}-informational" alt="{{ version|e }}">
|
||||
<img src="https://img.shields.io/badge/architecture-{{ architecture|e }}-informational" alt="{{ architecture|e }}">
|
||||
<img src="https://img.shields.io/badge/service%20status-{{ service.status|e }}-{{ service.status_color|e }}" alt="{{ service.status|e }}" title="{{ service.timestamp|e }}">
|
||||
</h1>
|
||||
|
||||
{% include "search-line.jinja2" %}
|
||||
|
||||
<section class="element">
|
||||
<table class="sortable search-table">
|
||||
<tr class="header">
|
||||
<th>package base</th>
|
||||
<th>packages</th>
|
||||
<th>version</th>
|
||||
<th>last update</th>
|
||||
<th>status</th>
|
||||
</tr>
|
||||
|
||||
{% for package in packages %}
|
||||
<tr class="package">
|
||||
<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>{{ package.version|e }}</td>
|
||||
<td>{{ package.timestamp|e }}</td>
|
||||
<td class="status package-{{ package.status|e }}">{{ package.status|e }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<ul class="navigation">
|
||||
<li><a href="https://github.com/arcan1s/ahriman" title="sources">ahriman</a></li>
|
||||
<li><a href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li>
|
||||
<li><a href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
62
tests/testresources/web/templates/repo-index.jinja2
Normal file
62
tests/testresources/web/templates/repo-index.jinja2
Normal file
@ -0,0 +1,62 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ repository|e }}</title>
|
||||
|
||||
{% include "style.jinja2" %}
|
||||
|
||||
{% include "sorttable.jinja2" %}
|
||||
{% include "search.jinja2" %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="root">
|
||||
<h1>Archlinux user repository</h1>
|
||||
|
||||
<section class="element">
|
||||
{% if pgp_key is not none %}
|
||||
<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>
|
||||
$ cat /etc/pacman.conf<br>
|
||||
[{{ repository|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
|
||||
</code>
|
||||
</section>
|
||||
|
||||
{% include "search-line.jinja2" %}
|
||||
|
||||
<section class="element">
|
||||
<table class="sortable search-table">
|
||||
<tr class="header">
|
||||
<th>package</th>
|
||||
<th>version</th>
|
||||
<th>archive size</th>
|
||||
<th>installed size</th>
|
||||
<th>build date</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>{{ package.archive_size|e }}</td>
|
||||
<td>{{ package.installed_size|e }}</td>
|
||||
<td>{{ package.build_date|e }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<ul class="navigation">
|
||||
{% if homepage is not none %}
|
||||
<li><a href="{{ homepage|e }}" title="homepage">Homepage</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
3
tests/testresources/web/templates/search-line.jinja2
Normal file
3
tests/testresources/web/templates/search-line.jinja2
Normal file
@ -0,0 +1,3 @@
|
||||
<section class="element">
|
||||
<input type="search" id="search" onkeyup="searchInTable()" placeholder="search for package" title="search for package"/>
|
||||
</section>
|
25
tests/testresources/web/templates/search.jinja2
Normal file
25
tests/testresources/web/templates/search.jinja2
Normal 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>
|
1
tests/testresources/web/templates/sorttable.jinja2
Normal file
1
tests/testresources/web/templates/sorttable.jinja2
Normal file
@ -0,0 +1 @@
|
||||
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
136
tests/testresources/web/templates/style.jinja2
Normal file
136
tests/testresources/web/templates/style.jinja2
Normal file
@ -0,0 +1,136 @@
|
||||
<style>
|
||||
:root {
|
||||
--color-building: 255, 255, 146;
|
||||
--color-failed: 255, 94, 94;
|
||||
--color-pending: 255, 255, 146;
|
||||
--color-success: 94, 255, 94;
|
||||
--color-unknown: 225, 225, 225;
|
||||
|
||||
--color-header: 200, 200, 255;
|
||||
--color-hover: 255, 255, 225;
|
||||
--color-line-blue: 235, 235, 255;
|
||||
--color-line-white: 255, 255, 255;
|
||||
}
|
||||
|
||||
@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, footer {
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
code, input, table {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
tr.package:nth-child(odd) {
|
||||
background-color: rgba(var(--color-line-white), 1.0);
|
||||
}
|
||||
|
||||
tr.package:nth-child(even) {
|
||||
background-color: rgba(var(--color-line-blue), 1.0);
|
||||
}
|
||||
|
||||
tr.package:hover {
|
||||
background-color: rgba(var(--color-hover), 1.0);
|
||||
}
|
||||
|
||||
tr.header{
|
||||
background-color: rgba(var(--color-header), 1.0);
|
||||
}
|
||||
|
||||
td.status {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
li.service-unknown {
|
||||
background-color: rgba(var(--color-unknown), 1.0);
|
||||
}
|
||||
li.service-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;
|
||||
}
|
||||
li.service-failed {
|
||||
background-color: rgba(var(--color-failed), 1.0);
|
||||
}
|
||||
li.service-success {
|
||||
background-color: rgba(var(--color-success), 1.0);
|
||||
}
|
||||
|
||||
ul.navigation {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: rgba(var(--color-header), 1.0);
|
||||
}
|
||||
|
||||
ul.navigation li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul.navigation li.status {
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
ul.navigation li a {
|
||||
display: block;
|
||||
color: black;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
ul.navigation li a:hover {
|
||||
background-color: rgba(var(--color-hover), 1.0);
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user