feat: brand-new interface (#158)

This was initally generated by ai, but later has been heavily edited.
The reason why it has been implemented is that there are plans to
implement more features to ui, but it becomes hard to add new features
to plain js, so I decided to rewrite it in typescript.

Yet because it is still ai slop, it is still possible to enable old
interface via configuration, even though new interface is turned on by
default to get feedback
This commit is contained in:
2026-03-06 00:59:10 +02:00
committed by GitHub
parent db46147f0d
commit a05eab9042
158 changed files with 5965 additions and 306 deletions

View File

@@ -60,6 +60,16 @@ class Auth(LazyLogging):
"""
return """<button type="button" class="btn btn-link" data-bs-toggle="modal" data-bs-target="#login-modal" style="text-decoration: none"><i class="bi bi-box-arrow-in-right"></i> login</button>"""
@property
def is_external(self) -> bool:
"""
check if the provider is external (e.g. OAuth)
Returns:
bool: ``True`` in case if external provider is used and ``False`` otherwise
"""
return False
@staticmethod
def load(configuration: Configuration, database: SQLite) -> Auth:
"""

View File

@@ -36,7 +36,6 @@ class OAuth(Mapping):
Attributes:
client_id(str): application client id
client_secret(str): application client secret key
icon(str): icon to be used in login control
provider(aioauth_client.OAuth2Client): provider class, should be one of aiohttp-client provided classes
redirect_uri(str): redirect URI registered in provider
scopes(str): list of scopes required by the application
@@ -59,7 +58,6 @@ class OAuth(Mapping):
self.provider = self.get_provider(configuration.get("auth", "oauth_provider"))
# it is list, but we will have to convert to string it anyway
self.scopes = configuration.get("auth", "oauth_scopes")
self.icon = configuration.get("auth", "oauth_icon", fallback="google")
@property
def auth_control(self) -> str:
@@ -69,8 +67,17 @@ class OAuth(Mapping):
Returns:
str: login control as html code to insert
"""
return f"<a class=\"nav-link\" href=\"/api/v1/login\" title=\"login via OAuth2\"><i class=\"bi bi-{
self.icon}\"></i> login</a>"
return "<a class=\"nav-link\" href=\"/api/v1/login\" title=\"login via OAuth2\"><i class=\"bi bi-box-arrow-in-right\"></i> login</a>"
@property
def is_external(self) -> bool:
"""
check if the provider is external (e.g. OAuth)
Returns:
bool: ``True`` in case if external provider is used and ``False`` otherwise
"""
return True
@staticmethod
def get_provider(name: str) -> type[aioauth_client.OAuth2Client]:

View File

@@ -161,10 +161,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"coerce": "integer",
"min": 0,
},
"oauth_icon": {
"type": "string",
"empty": False,
},
"oauth_provider": {
"type": "string",
"empty": False,
@@ -398,6 +394,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"path_exists": True,
"path_type": "dir",
},
"template": {
"type": "string",
"empty": False,
},
"templates": {
"type": "list",
"coerce": "list",

View File

@@ -35,7 +35,7 @@ from enum import Enum
from filelock import FileLock
from pathlib import Path
from pwd import getpwuid
from typing import Any, IO, TypeVar
from typing import Any, IO, TypeVar, cast
from ahriman.core.exceptions import CalledProcessError, OptionError, UnsafeRunError
from ahriman.core.types import Comparable
@@ -285,16 +285,17 @@ def filelock(path: Path) -> Iterator[FileLock]:
lock_path.unlink(missing_ok=True)
def filter_json(source: dict[str, Any], known_fields: Iterable[str]) -> dict[str, Any]:
def filter_json(source: T, known_fields: Iterable[str] | None = None) -> T:
"""
filter json object by fields used for json-to-object conversion
recursively filter json object removing ``None`` values and optionally filtering by known fields
Args:
source(dict[str, Any]): raw json object
known_fields(Iterable[str]): list of fields which have to be known for the target object
source(T): raw json object (dict, list, or scalar)
known_fields(Iterable[str] | None, optional): list of fields which have to be known for the target object
(Default value = None)
Returns:
dict[str, Any]: json object without unknown and empty fields
T: json without ``None`` values
Examples:
This wrapper is mainly used for the dataclasses, thus the flow must be something like this::
@@ -306,7 +307,15 @@ def filter_json(source: dict[str, Any], known_fields: Iterable[str]) -> dict[str
>>> properties = filter_json(dump, known_fields)
>>> package = Package(**properties)
"""
return {key: value for key, value in source.items() if key in known_fields and value is not None}
if isinstance(source, dict):
return cast(T, {
key: filter_json(value)
for key, value in source.items()
if value is not None and (known_fields is None or key in known_fields)
})
if isinstance(source, list):
return cast(T, [filter_json(value) for value in source])
return source
def full_version(epoch: str | int | None, pkgver: str, pkgrel: str) -> str:

View File

@@ -19,7 +19,9 @@
#
from ahriman.web.schemas.any_schema import AnySchema
from ahriman.web.schemas.aur_package_schema import AURPackageSchema
from ahriman.web.schemas.auth_info_schema import AuthInfoSchema
from ahriman.web.schemas.auth_schema import AuthSchema
from ahriman.web.schemas.auto_refresh_interval_schema import AutoRefreshIntervalSchema
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema
from ahriman.web.schemas.changes_schema import ChangesSchema
from ahriman.web.schemas.configuration_schema import ConfigurationSchema
@@ -30,6 +32,7 @@ from ahriman.web.schemas.event_schema import EventSchema
from ahriman.web.schemas.event_search_schema import EventSearchSchema
from ahriman.web.schemas.file_schema import FileSchema
from ahriman.web.schemas.info_schema import InfoSchema
from ahriman.web.schemas.info_v2_schema import InfoV2Schema
from ahriman.web.schemas.internal_status_schema import InternalStatusSchema
from ahriman.web.schemas.log_schema import LogSchema
from ahriman.web.schemas.login_schema import LoginSchema

View File

@@ -0,0 +1,39 @@
#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.web.apispec import Schema, fields
class AuthInfoSchema(Schema):
"""
authorization information schema
"""
control = fields.String(required=True, metadata={
"description": "HTML control for login interface",
})
enabled = fields.Boolean(required=True, metadata={
"description": "Whether authentication is enabled or not",
})
external = fields.Boolean(required=True, metadata={
"description": "Whether authorization provider is external (e.g. OAuth)",
})
username = fields.String(metadata={
"description": "Currently authenticated username if any",
})

View File

@@ -0,0 +1,36 @@
#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.web.apispec import Schema, fields
class AutoRefreshIntervalSchema(Schema):
"""
auto refresh interval schema
"""
interval = fields.Integer(required=True, metadata={
"description": "Auto refresh interval in milliseconds",
})
is_active = fields.Boolean(required=True, metadata={
"description": "Whether this interval is the default active one",
})
text = fields.String(required=True, metadata={
"description": "Human readable interval description",
})

View File

@@ -27,7 +27,7 @@ class InfoSchema(Schema):
response service information schema
"""
auth = fields.Boolean(dump_default=False, required=True, metadata={
auth = fields.Boolean(required=True, metadata={
"description": "Whether authentication is enabled or not",
})
repositories = fields.Nested(RepositoryIdSchema(many=True), required=True, metadata={

View File

@@ -0,0 +1,50 @@
#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman import __version__
from ahriman.web.apispec import Schema, fields
from ahriman.web.schemas.auth_info_schema import AuthInfoSchema
from ahriman.web.schemas.auto_refresh_interval_schema import AutoRefreshIntervalSchema
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
class InfoV2Schema(Schema):
"""
response service information schema
"""
auth = fields.Nested(AuthInfoSchema(), required=True, metadata={
"description": "Authorization descriptor",
})
autorefresh_intervals = fields.Nested(AutoRefreshIntervalSchema(many=True), metadata={
"description": "Available auto refresh intervals",
})
docs_enabled = fields.Boolean(metadata={
"description": "Whether API documentation is enabled",
})
index_url = fields.String(metadata={
"description": "URL to the repository index page",
})
repositories = fields.Nested(RepositoryIdSchema(many=True), required=True, metadata={
"description": "List of loaded repositories",
})
version = fields.String(required=True, metadata={
"description": "Service version",
"example": __version__,
})

View File

@@ -29,6 +29,10 @@ class RepositoryIdSchema(Schema):
"description": "Repository architecture",
"example": "x86_64",
})
id = fields.String(metadata={
"description": "Unique repository identifier",
"example": "aur-x86_64",
})
repository = fields.String(metadata={
"description": "Repository name",
"example": "aur",

View File

@@ -0,0 +1,73 @@
#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from collections.abc import Callable
from typing import Any
from ahriman import __version__
from ahriman.core.auth.helpers import authorized_userid
from ahriman.core.types import Comparable
from ahriman.core.utils import pretty_interval
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.views.base import BaseView
__all__ = ["server_info"]
async def server_info(view: BaseView) -> dict[str, Any]:
"""
generate server info which can be used in responses directly
Args:
view(BaseView): view of the request
Returns:
dict[str, Any]: server info as a json response
"""
autorefresh_intervals = [
{
"interval": interval * 1000, # milliseconds
"is_active": index == 0, # first element is always default
"text": pretty_interval(interval),
}
for index, interval in enumerate(view.configuration.getintlist("web", "autorefresh_intervals", fallback=[]))
if interval > 0 # special case if 0 exists and first, refresh will not be turned on by default
]
comparator: Callable[[dict[str, Any]], Comparable] = lambda interval: interval["interval"]
return {
"auth": {
"control": view.validator.auth_control,
"enabled": view.validator.enabled,
"external": view.validator.is_external,
"username": await authorized_userid(view.request),
},
"autorefresh_intervals": sorted(autorefresh_intervals, key=comparator),
"docs_enabled": aiohttp_apispec is not None,
"index_url": view.configuration.get("web", "index_url", fallback=None),
"repositories": [
{
"id": repository_id.id,
**repository_id.view(),
}
for repository_id in sorted(view.services)
],
"version": __version__,
}

View File

@@ -17,10 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Request, StreamResponse, View
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Request, Response, StreamResponse, View, json_response
from aiohttp_cors import CorsViewMixin
from collections.abc import Awaitable, Callable
from typing import ClassVar, TypeVar
from typing import Any, ClassVar, TypeVar
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
@@ -29,6 +29,7 @@ from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.sign.gpg import GPG
from ahriman.core.spawn import Spawn
from ahriman.core.status.watcher import Watcher
from ahriman.core.utils import filter_json
from ahriman.models.repository_id import RepositoryId
from ahriman.models.user_access import UserAccess
from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey
@@ -162,6 +163,20 @@ class BaseView(View, CorsViewMixin):
raise KeyError(f"Key {key} is missing or empty") from None
return value
@staticmethod
def json_response(data: dict[str, Any] | list[Any], **kwargs: Any) -> Response:
"""
filter and convert data and return :class:`aiohttp.web.Response` object
Args:
data(dict[str, Any] | list[Any]): response in json format
**kwargs(Any): keyword arguments for :func:`aiohttp.web.json_response` function
Returns:
Response: generated response object
"""
return json_response(filter_json(data), **kwargs)
# pylint: disable=not-callable,protected-access
async def head(self) -> StreamResponse:
"""

View File

@@ -19,12 +19,11 @@
#
import aiohttp_jinja2
from typing import Any, ClassVar
from aiohttp.web import Response
from typing import ClassVar
from ahriman.core.auth.helpers import authorized_userid
from ahriman.core.utils import pretty_interval
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec import aiohttp_apispec
from ahriman.web.server_info import server_info
from ahriman.web.views.base import BaseView
@@ -48,6 +47,7 @@ class IndexView(BaseView):
* id - unique repository identifier, string, required
* repository - repository name, string, required
* architecture - repository architecture, string, required
* version - service version, string, required
Attributes:
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
@@ -56,41 +56,14 @@ class IndexView(BaseView):
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/", "/index.html"]
@aiohttp_jinja2.template("build-status.jinja2")
async def get(self) -> dict[str, Any]:
async def get(self) -> Response:
"""
process get request. No parameters supported here
Returns:
dict[str, Any]: parameters for jinja template
Response: 200 with rendered index page
"""
auth_username = await authorized_userid(self.request)
auth = {
"control": self.validator.auth_control,
"enabled": self.validator.enabled,
"username": auth_username,
}
context = await server_info(self)
autorefresh_intervals = [
{
"interval": interval * 1000, # milliseconds
"is_active": index == 0, # first element is always default
"text": pretty_interval(interval),
}
for index, interval in enumerate(self.configuration.getintlist("web", "autorefresh_intervals", fallback=[]))
if interval > 0 # special case if 0 exists and first, refresh will not be turned on by default
]
return {
"auth": auth,
"autorefresh_intervals": sorted(autorefresh_intervals, key=lambda interval: interval["interval"]),
"docs_enabled": aiohttp_apispec is not None,
"index_url": self.configuration.get("web", "index_url", fallback=None),
"repositories": [
{
"id": repository.id,
**repository.view(),
}
for repository in sorted(self.services)
]
}
template = self.configuration.get("web", "template", fallback="build-status.jinja2")
return aiohttp_jinja2.render_template(template, self.request, context)

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response
from typing import ClassVar
from ahriman.models.event import Event
@@ -70,7 +70,7 @@ class EventsView(BaseView):
events = self.service().event_get(event, object_id, from_date, to_date, limit, offset)
response = [event.view() for event in events]
return json_response(response)
return self.json_response(response)
@apidocs(
tags=["Audit log"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response
from collections.abc import Callable
from typing import ClassVar
@@ -78,7 +78,7 @@ class WorkersView(BaseView):
comparator: Callable[[Worker], Comparable] = lambda item: item.identifier
response = [worker.view() for worker in sorted(workers, key=comparator)]
return json_response(response)
return self.json_response(response)
@apidocs(
tags=["Distributed"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response
from typing import ClassVar
from ahriman.models.changes import Changes
@@ -65,7 +65,7 @@ class ChangesView(StatusViewGuard, BaseView):
changes = self.service(package_base=package_base).package_changes_get(package_base)
return json_response(changes.view())
return self.json_response(changes.view())
@apidocs(
tags=["Packages"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response
from typing import ClassVar
from ahriman.models.dependencies import Dependencies
@@ -65,7 +65,7 @@ class DependenciesView(StatusViewGuard, BaseView):
dependencies = self.service(package_base=package_base).package_dependencies_get(package_base)
return json_response(dependencies.view())
return self.json_response(dependencies.view())
@apidocs(
tags=["Packages"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response
from typing import ClassVar
from ahriman.core.exceptions import UnknownPackageError
@@ -99,7 +99,7 @@ class LogsView(StatusViewGuard, BaseView):
"status": status.view(),
"logs": "\n".join(f"[{pretty_datetime(log_record.created)}] {log_record.message}" for log_record in logs)
}
return json_response(response)
return self.json_response(response)
@apidocs(
tags=["Packages"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response
from typing import ClassVar
from ahriman.core.exceptions import UnknownPackageError
@@ -105,7 +105,7 @@ class PackageView(StatusViewGuard, BaseView):
"repository": repository_id.view(),
}
]
return json_response(response)
return self.json_response(response)
@apidocs(
tags=["Packages"],

View File

@@ -19,7 +19,7 @@
#
import itertools
from aiohttp.web import HTTPNoContent, Response, json_response
from aiohttp.web import HTTPNoContent, Response
from collections.abc import Callable
from typing import ClassVar
@@ -78,7 +78,7 @@ class PackagesView(StatusViewGuard, BaseView):
} for package, status in itertools.islice(sorted(packages, key=comparator), offset, stop)
]
return json_response(response)
return self.json_response(response)
@apidocs(
tags=["Packages"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
from aiohttp.web import HTTPNoContent, HTTPNotFound, Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
@@ -89,4 +89,4 @@ class PatchView(StatusViewGuard, BaseView):
if selected is None:
raise HTTPNotFound(reason=f"Patch {variable} is unknown")
return json_response(selected.view())
return self.json_response(selected.view())

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response
from typing import ClassVar
from ahriman.models.pkgbuild_patch import PkgbuildPatch
@@ -60,7 +60,7 @@ class PatchesView(StatusViewGuard, BaseView):
patches = self.service().package_patches_get(package_base, None)
response = [patch.view() for patch in patches]
return json_response(response)
return self.json_response(response)
@apidocs(
tags=["Packages"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from aiohttp.web import HTTPBadRequest, Response
from typing import ClassVar
from ahriman.models.pkgbuild_patch import PkgbuildPatch
@@ -78,4 +78,4 @@ class AddView(BaseView):
refresh=data.get("refresh", False),
)
return json_response({"process_id": process_id})
return self.json_response({"process_id": process_id})

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPNoContent, Response, json_response
from aiohttp.web import HTTPNoContent, Response
from typing import ClassVar
from ahriman.core.formatters import ConfigurationPrinter
@@ -64,7 +64,7 @@ class ConfigView(BaseView):
for key, value in values.items()
if key not in ConfigurationPrinter.HIDE_KEYS
]
return json_response(response)
return self.json_response(response)
@apidocs(
tags=["Actions"],

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
@@ -71,7 +71,7 @@ class PGPView(BaseView):
except Exception:
raise HTTPNotFound(reason=f"Key {key} is unknown")
return json_response({"key": key})
return self.json_response({"key": key})
@apidocs(
tags=["Actions"],
@@ -100,4 +100,4 @@ class PGPView(BaseView):
process_id = self.spawner.key_import(key, data.get("server"))
return json_response({"process_id": process_id})
return self.json_response({"process_id": process_id})

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPNotFound, Response, json_response
from aiohttp.web import HTTPNotFound, Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
@@ -66,4 +66,4 @@ class ProcessView(BaseView):
"is_alive": is_alive,
}
return json_response(response)
return self.json_response(response)

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from aiohttp.web import HTTPBadRequest, Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
@@ -74,4 +74,4 @@ class RebuildView(BaseView):
increment=data.get("increment", True),
)
return json_response({"process_id": process_id})
return self.json_response({"process_id": process_id})

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from aiohttp.web import HTTPBadRequest, Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
@@ -67,4 +67,4 @@ class RemoveView(BaseView):
repository_id = self.repository_id()
process_id = self.spawner.packages_remove(repository_id, packages)
return json_response({"process_id": process_id})
return self.json_response({"process_id": process_id})

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from aiohttp.web import HTTPBadRequest, Response
from typing import ClassVar
from ahriman.models.pkgbuild_patch import PkgbuildPatch
@@ -78,4 +78,4 @@ class RequestView(BaseView):
refresh=False, # refresh doesn't work here
)
return json_response({"process_id": process_id})
return self.json_response({"process_id": process_id})

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response
from collections.abc import Callable
from typing import ClassVar
@@ -83,4 +83,4 @@ class SearchView(BaseView):
"description": package.description,
} for package in sorted(packages, key=comparator)
]
return json_response(response)
return self.json_response(response)

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, Response, json_response
from aiohttp.web import HTTPBadRequest, Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
@@ -75,4 +75,4 @@ class UpdateView(BaseView):
refresh=data.get("refresh", False),
)
return json_response({"process_id": process_id})
return self.json_response({"process_id": process_id})

View File

@@ -17,13 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import Response, json_response
from aiohttp.web import Response
from typing import ClassVar
from ahriman import __version__
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import InfoSchema
from ahriman.web.server_info import server_info
from ahriman.web.views.base import BaseView
@@ -52,13 +52,11 @@ class InfoView(BaseView):
Returns:
Response: 200 with service information object
"""
info = await server_info(self)
response = {
"auth": self.validator.enabled,
"repositories": [
repository_id.view()
for repository_id in sorted(self.services)
],
"version": __version__,
"auth": info["auth"]["enabled"],
"repositories": info["repositories"],
"version": info["version"],
}
return json_response(response)
return self.json_response(response)

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import Response, json_response
from aiohttp.web import Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
@@ -56,4 +56,4 @@ class RepositoriesView(BaseView):
for repository_id in sorted(self.services)
]
return json_response(repositories)
return self.json_response(repositories)

View File

@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response
from typing import ClassVar
from ahriman import __version__
@@ -75,7 +75,7 @@ class StatusView(StatusViewGuard, BaseView):
version=__version__,
)
return json_response(status.view())
return self.json_response(status.view())
@apidocs(
tags=["Status"],

View File

@@ -19,7 +19,7 @@
#
import itertools
from aiohttp.web import Response, json_response
from aiohttp.web import Response
from dataclasses import replace
from typing import ClassVar
@@ -31,8 +31,7 @@ from ahriman.web.views.status_view_guard import StatusViewGuard
class LogsView(StatusViewGuard, BaseView):
""" else:
"""
package logs web view
Attributes:
@@ -80,4 +79,4 @@ class LogsView(StatusViewGuard, BaseView):
]
response = [log_record.view() for log_record in logs]
return json_response(response)
return self.json_response(response)

View File

@@ -0,0 +1,19 @@
#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

View File

@@ -0,0 +1,56 @@
#
# Copyright (c) 2021-2026 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from aiohttp.web import Response
from typing import ClassVar
from ahriman.models.user_access import UserAccess
from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import InfoV2Schema
from ahriman.web.server_info import server_info
from ahriman.web.views.base import BaseView
class InfoView(BaseView):
"""
web service information view
Attributes:
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
"""
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Unauthorized
ROUTES = ["/api/v2/info"]
@apidocs(
tags=["Status"],
summary="Service information",
description="Perform basic service health check and returns its information",
permission=GET_PERMISSION,
schema=InfoV2Schema,
)
async def get(self) -> Response:
"""
get service information
Returns:
Response: 200 with service information object
"""
response = await server_info(self)
return self.json_response(response)