mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-04-07 19:03:38 +00:00
Compare commits
1 Commits
2.20.1
...
dcb8724ed2
| Author | SHA1 | Date | |
|---|---|---|---|
| dcb8724ed2 |
@@ -125,7 +125,7 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
def __hash__(self) -> int: ... # basically any magic (or look-alike) method
|
def __hash__(self) -> int: ... # basically any magic (or look-alike) method
|
||||||
```
|
```
|
||||||
|
|
||||||
Methods inside one group should be ordered alphabetically, the only exceptions are `__init__` (`__post_init__` for dataclasses), `__new__` and `__del__` methods which should be defined first. For test methods it is recommended to follow the order in which functions are defined. Same idea applies to frontend classes.
|
Methods inside one group should be ordered alphabetically, the only exceptions are `__init__` (`__post_init__` for dataclasses), `__new__` and `__del__` methods which should be defined first. For test methods it is recommended to follow the order in which functions are defined.
|
||||||
|
|
||||||
Though, we would like to highlight abstract methods (i.e. ones which raise `NotImplementedError`), we still keep in global order at the moment.
|
Though, we would like to highlight abstract methods (i.e. ones which raise `NotImplementedError`), we still keep in global order at the moment.
|
||||||
|
|
||||||
@@ -172,9 +172,8 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
* Imports goes in alphabetical order, no relative imports allowed. Same rule applies to frontend classes.
|
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines.
|
||||||
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines. Same rule applies to frontend classes.
|
* It is possible to create file which contains some functions (e.g. `ahriman.core.util`), but in this case you would need to define `__all__` attribute.
|
||||||
* It is possible to create file which contains some functions (e.g. `ahriman.core.utils`), but in this case you would need to define `__all__` attribute.
|
|
||||||
* The file size mentioned above must be applicable in general. In case of big classes consider splitting them into traits. Note, however, that `pylint` includes comments and docstrings into counter, thus you need to check file size by other tools.
|
* The file size mentioned above must be applicable in general. In case of big classes consider splitting them into traits. Note, however, that `pylint` includes comments and docstrings into counter, thus you need to check file size by other tools.
|
||||||
* No global variable is allowed outside of `ahriman` module. `ahriman.core.context` is also special case.
|
* No global variable is allowed outside of `ahriman` module. `ahriman.core.context` is also special case.
|
||||||
* Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent.
|
* Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent.
|
||||||
@@ -227,8 +226,6 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
|
|
||||||
The projects also uses typing checks (provided by `mypy`) and some linter checks provided by `pylint` and `bandit`. Those checks must be passed successfully for any open pull requests.
|
The projects also uses typing checks (provided by `mypy`) and some linter checks provided by `pylint` and `bandit`. Those checks must be passed successfully for any open pull requests.
|
||||||
|
|
||||||
Frontend checks normally are performed by `eslint` (e.g. `npx run eslint`).
|
|
||||||
|
|
||||||
## Developers how to
|
## Developers how to
|
||||||
|
|
||||||
### Run automated checks
|
### Run automated checks
|
||||||
|
|||||||
120
docs/_static/architecture.dot
vendored
120
docs/_static/architecture.dot
vendored
@@ -138,12 +138,11 @@ digraph G {
|
|||||||
ahriman_core_http [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nhttp",shape="box"];
|
ahriman_core_http [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nhttp",shape="box"];
|
||||||
ahriman_core_http_sync_ahriman_client [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nhttp\.\nsync_ahriman_client",shape="box"];
|
ahriman_core_http_sync_ahriman_client [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nhttp\.\nsync_ahriman_client",shape="box"];
|
||||||
ahriman_core_http_sync_http_client [fillcolor="#933f24",fontcolor="#ffffff",label="ahriman\.\ncore\.\nhttp\.\nsync_http_client"];
|
ahriman_core_http_sync_http_client [fillcolor="#933f24",fontcolor="#ffffff",label="ahriman\.\ncore\.\nhttp\.\nsync_http_client"];
|
||||||
ahriman_core_log [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nlog",shape="box"];
|
ahriman_core_log [fillcolor="#e53b05",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog"];
|
||||||
ahriman_core_log_http_log_handler [fillcolor="#914730",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nhttp_log_handler"];
|
ahriman_core_log_http_log_handler [fillcolor="#914730",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nhttp_log_handler"];
|
||||||
ahriman_core_log_journal_handler [fillcolor="#ac6149",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\njournal_handler"];
|
ahriman_core_log_journal_handler [fillcolor="#ac6149",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\njournal_handler"];
|
||||||
ahriman_core_log_lazy_logging [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nlog\.\nlazy_logging",shape="box"];
|
ahriman_core_log_lazy_logging [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlazy_logging"];
|
||||||
ahriman_core_log_log_context [fillcolor="#db582f",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlog_context"];
|
ahriman_core_log_log_loader [fillcolor="#82402b",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlog_loader"];
|
||||||
ahriman_core_log_log_loader [fillcolor="#7a3c28",fontcolor="#ffffff",label="ahriman\.\ncore\.\nlog\.\nlog_loader"];
|
|
||||||
ahriman_core_module_loader [fillcolor="#ce5e3b",fontcolor="#ffffff",label="ahriman\.\ncore\.\nmodule_loader"];
|
ahriman_core_module_loader [fillcolor="#ce5e3b",fontcolor="#ffffff",label="ahriman\.\ncore\.\nmodule_loader"];
|
||||||
ahriman_core_report [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nreport",shape="box"];
|
ahriman_core_report [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nreport",shape="box"];
|
||||||
ahriman_core_report_console [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nreport\.\nconsole",shape="box"];
|
ahriman_core_report_console [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nreport\.\nconsole",shape="box"];
|
||||||
@@ -243,18 +242,15 @@ digraph G {
|
|||||||
ahriman_web_apispec_info [fillcolor="#a14f35",fontcolor="#ffffff",label="ahriman\.\nweb\.\napispec\.\ninfo"];
|
ahriman_web_apispec_info [fillcolor="#a14f35",fontcolor="#ffffff",label="ahriman\.\nweb\.\napispec\.\ninfo"];
|
||||||
ahriman_web_cors [fillcolor="#b0573a",fontcolor="#ffffff",label="ahriman\.\nweb\.\ncors"];
|
ahriman_web_cors [fillcolor="#b0573a",fontcolor="#ffffff",label="ahriman\.\nweb\.\ncors"];
|
||||||
ahriman_web_keys [fillcolor="#823017",fontcolor="#ffffff",label="ahriman\.\nweb\.\nkeys"];
|
ahriman_web_keys [fillcolor="#823017",fontcolor="#ffffff",label="ahriman\.\nweb\.\nkeys"];
|
||||||
ahriman_web_middlewares [fillcolor="#ef3e06",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares"];
|
ahriman_web_middlewares [fillcolor="#e9410c",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares"];
|
||||||
ahriman_web_middlewares_auth_handler [fillcolor="#733826",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nauth_handler"];
|
ahriman_web_middlewares_auth_handler [fillcolor="#733826",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nauth_handler"];
|
||||||
ahriman_web_middlewares_exception_handler [fillcolor="#994b33",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nexception_handler"];
|
ahriman_web_middlewares_exception_handler [fillcolor="#994b33",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nexception_handler"];
|
||||||
ahriman_web_middlewares_metrics_handler [fillcolor="#a34628",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nmetrics_handler"];
|
ahriman_web_middlewares_metrics_handler [fillcolor="#a34628",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nmetrics_handler"];
|
||||||
ahriman_web_middlewares_request_id_handler [fillcolor="#8a442e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nmiddlewares\.\nrequest_id_handler"];
|
|
||||||
ahriman_web_routes [fillcolor="#8a442e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nroutes"];
|
ahriman_web_routes [fillcolor="#8a442e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nroutes"];
|
||||||
ahriman_web_schemas [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas",shape="box"];
|
ahriman_web_schemas [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas",shape="box"];
|
||||||
ahriman_web_schemas_any_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nany_schema"];
|
ahriman_web_schemas_any_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nany_schema"];
|
||||||
ahriman_web_schemas_aur_package_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\naur_package_schema"];
|
ahriman_web_schemas_aur_package_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\naur_package_schema"];
|
||||||
ahriman_web_schemas_auth_info_schema [fillcolor="#c45431",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nauth_info_schema"];
|
|
||||||
ahriman_web_schemas_auth_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nauth_schema"];
|
ahriman_web_schemas_auth_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nauth_schema"];
|
||||||
ahriman_web_schemas_auto_refresh_interval_schema [fillcolor="#c45431",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nauto_refresh_interval_schema"];
|
|
||||||
ahriman_web_schemas_build_options_schema [fillcolor="#d04e24",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nbuild_options_schema"];
|
ahriman_web_schemas_build_options_schema [fillcolor="#d04e24",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nbuild_options_schema"];
|
||||||
ahriman_web_schemas_changes_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nchanges_schema"];
|
ahriman_web_schemas_changes_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nchanges_schema"];
|
||||||
ahriman_web_schemas_configuration_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nconfiguration_schema"];
|
ahriman_web_schemas_configuration_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nconfiguration_schema"];
|
||||||
@@ -265,7 +261,6 @@ digraph G {
|
|||||||
ahriman_web_schemas_event_search_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\nevent_search_schema",shape="box"];
|
ahriman_web_schemas_event_search_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\nevent_search_schema",shape="box"];
|
||||||
ahriman_web_schemas_file_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nfile_schema"];
|
ahriman_web_schemas_file_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nfile_schema"];
|
||||||
ahriman_web_schemas_info_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\ninfo_schema",shape="box"];
|
ahriman_web_schemas_info_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\ninfo_schema",shape="box"];
|
||||||
ahriman_web_schemas_info_v2_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\ninfo_v2_schema",shape="box"];
|
|
||||||
ahriman_web_schemas_internal_status_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\ninternal_status_schema",shape="box"];
|
ahriman_web_schemas_internal_status_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\ninternal_status_schema",shape="box"];
|
||||||
ahriman_web_schemas_log_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nlog_schema"];
|
ahriman_web_schemas_log_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nlog_schema"];
|
||||||
ahriman_web_schemas_login_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nlogin_schema"];
|
ahriman_web_schemas_login_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nlogin_schema"];
|
||||||
@@ -294,12 +289,11 @@ digraph G {
|
|||||||
ahriman_web_schemas_status_schema [fillcolor="#ca4116",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nstatus_schema"];
|
ahriman_web_schemas_status_schema [fillcolor="#ca4116",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nstatus_schema"];
|
||||||
ahriman_web_schemas_update_flags_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\nupdate_flags_schema",shape="box"];
|
ahriman_web_schemas_update_flags_schema [fillcolor="blue",fontcolor="white",label="ahriman\.\nweb\.\nschemas\.\nupdate_flags_schema",shape="box"];
|
||||||
ahriman_web_schemas_worker_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nworker_schema"];
|
ahriman_web_schemas_worker_schema [fillcolor="#b85a3d",fontcolor="#ffffff",label="ahriman\.\nweb\.\nschemas\.\nworker_schema"];
|
||||||
ahriman_web_server_info [fillcolor="#93371a",fontcolor="#ffffff",label="ahriman\.\nweb\.\nserver_info"];
|
|
||||||
ahriman_web_views [fillcolor="#f94810",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews"];
|
ahriman_web_views [fillcolor="#f94810",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews"];
|
||||||
ahriman_web_views_api_docs [fillcolor="#794434",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\napi\.\ndocs"];
|
ahriman_web_views_api_docs [fillcolor="#794434",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\napi\.\ndocs"];
|
||||||
ahriman_web_views_api_swagger [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\napi\.\nswagger"];
|
ahriman_web_views_api_swagger [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\napi\.\nswagger"];
|
||||||
ahriman_web_views_base [fillcolor="#952603",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nbase"];
|
ahriman_web_views_base [fillcolor="#952603",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nbase"];
|
||||||
ahriman_web_views_index [fillcolor="#794434",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nindex"];
|
ahriman_web_views_index [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nindex"];
|
||||||
ahriman_web_views_static [fillcolor="#884d3a",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nstatic"];
|
ahriman_web_views_static [fillcolor="#884d3a",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nstatic"];
|
||||||
ahriman_web_views_status_view_guard [fillcolor="#ef3e06",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nstatus_view_guard"];
|
ahriman_web_views_status_view_guard [fillcolor="#ef3e06",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nstatus_view_guard"];
|
||||||
ahriman_web_views_v1_auditlog_events [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nauditlog\.\nevents"];
|
ahriman_web_views_v1_auditlog_events [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nauditlog\.\nevents"];
|
||||||
@@ -322,14 +316,13 @@ digraph G {
|
|||||||
ahriman_web_views_v1_service_search [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nservice\.\nsearch"];
|
ahriman_web_views_v1_service_search [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nservice\.\nsearch"];
|
||||||
ahriman_web_views_v1_service_update [fillcolor="#724031",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nservice\.\nupdate"];
|
ahriman_web_views_v1_service_update [fillcolor="#724031",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nservice\.\nupdate"];
|
||||||
ahriman_web_views_v1_service_upload [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nservice\.\nupload"];
|
ahriman_web_views_v1_service_upload [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nservice\.\nupload"];
|
||||||
ahriman_web_views_v1_status_info [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\ninfo"];
|
ahriman_web_views_v1_status_info [fillcolor="#724031",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\ninfo"];
|
||||||
ahriman_web_views_v1_status_metrics [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\nmetrics"];
|
ahriman_web_views_v1_status_metrics [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\nmetrics"];
|
||||||
ahriman_web_views_v1_status_repositories [fillcolor="#724031",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\nrepositories"];
|
ahriman_web_views_v1_status_repositories [fillcolor="#724031",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\nrepositories"];
|
||||||
ahriman_web_views_v1_status_status [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\nstatus"];
|
ahriman_web_views_v1_status_status [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nstatus\.\nstatus"];
|
||||||
ahriman_web_views_v1_user_login [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nuser\.\nlogin"];
|
ahriman_web_views_v1_user_login [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nuser\.\nlogin"];
|
||||||
ahriman_web_views_v1_user_logout [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nuser\.\nlogout"];
|
ahriman_web_views_v1_user_logout [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv1\.\nuser\.\nlogout"];
|
||||||
ahriman_web_views_v2_packages_logs [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv2\.\npackages\.\nlogs"];
|
ahriman_web_views_v2_packages_logs [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv2\.\npackages\.\nlogs"];
|
||||||
ahriman_web_views_v2_status_info [fillcolor="#6b3c2e",fontcolor="#ffffff",label="ahriman\.\nweb\.\nviews\.\nv2\.\nstatus\.\ninfo"];
|
|
||||||
ahriman_web_web [fillcolor="#733826",fontcolor="#ffffff",label="ahriman\.\nweb\.\nweb"];
|
ahriman_web_web [fillcolor="#733826",fontcolor="#ffffff",label="ahriman\.\nweb\.\nweb"];
|
||||||
aioauth_client [fillcolor="#c07d40",shape="folder"];
|
aioauth_client [fillcolor="#c07d40",shape="folder"];
|
||||||
aiohttp [fillcolor="#f9b506",shape="folder"];
|
aiohttp [fillcolor="#f9b506",shape="folder"];
|
||||||
@@ -495,12 +488,11 @@ digraph G {
|
|||||||
ahriman_core -> ahriman_models_worker [fillcolor="#ef3e06",minlen="2"];
|
ahriman_core -> ahriman_models_worker [fillcolor="#ef3e06",minlen="2"];
|
||||||
ahriman_core -> ahriman_web_keys [fillcolor="#ef3e06",minlen="2"];
|
ahriman_core -> ahriman_web_keys [fillcolor="#ef3e06",minlen="2"];
|
||||||
ahriman_core -> ahriman_web_middlewares_auth_handler [fillcolor="#ef3e06",minlen="3"];
|
ahriman_core -> ahriman_web_middlewares_auth_handler [fillcolor="#ef3e06",minlen="3"];
|
||||||
ahriman_core -> ahriman_web_middlewares_request_id_handler [fillcolor="#ef3e06",minlen="3"];
|
|
||||||
ahriman_core -> ahriman_web_routes [fillcolor="#ef3e06",minlen="2"];
|
ahriman_core -> ahriman_web_routes [fillcolor="#ef3e06",minlen="2"];
|
||||||
ahriman_core -> ahriman_web_server_info [fillcolor="#ef3e06",minlen="2"];
|
|
||||||
ahriman_core -> ahriman_web_views_api_docs [fillcolor="#ef3e06",minlen="3"];
|
ahriman_core -> ahriman_web_views_api_docs [fillcolor="#ef3e06",minlen="3"];
|
||||||
ahriman_core -> ahriman_web_views_api_swagger [fillcolor="#ef3e06",minlen="3"];
|
ahriman_core -> ahriman_web_views_api_swagger [fillcolor="#ef3e06",minlen="3"];
|
||||||
ahriman_core -> ahriman_web_views_base [fillcolor="#ef3e06",minlen="3"];
|
ahriman_core -> ahriman_web_views_base [fillcolor="#ef3e06",minlen="3"];
|
||||||
|
ahriman_core -> ahriman_web_views_index [fillcolor="#ef3e06",minlen="3"];
|
||||||
ahriman_core -> ahriman_web_views_status_view_guard [fillcolor="#ef3e06",minlen="3"];
|
ahriman_core -> ahriman_web_views_status_view_guard [fillcolor="#ef3e06",minlen="3"];
|
||||||
ahriman_core -> ahriman_web_views_v1_distributed_workers [fillcolor="#ef3e06",minlen="3"];
|
ahriman_core -> ahriman_web_views_v1_distributed_workers [fillcolor="#ef3e06",minlen="3"];
|
||||||
ahriman_core -> ahriman_web_views_v1_packages_logs [fillcolor="#ef3e06",minlen="3"];
|
ahriman_core -> ahriman_web_views_v1_packages_logs [fillcolor="#ef3e06",minlen="3"];
|
||||||
@@ -550,13 +542,13 @@ digraph G {
|
|||||||
ahriman_core_archive_archive_trigger -> ahriman_core_archive [fillcolor="blue",weight="3"];
|
ahriman_core_archive_archive_trigger -> ahriman_core_archive [fillcolor="blue",weight="3"];
|
||||||
ahriman_core_auth -> ahriman_web_keys [fillcolor="blue",minlen="2"];
|
ahriman_core_auth -> ahriman_web_keys [fillcolor="blue",minlen="2"];
|
||||||
ahriman_core_auth -> ahriman_web_middlewares_auth_handler [fillcolor="blue",minlen="3"];
|
ahriman_core_auth -> ahriman_web_middlewares_auth_handler [fillcolor="blue",minlen="3"];
|
||||||
ahriman_core_auth -> ahriman_web_server_info [fillcolor="blue",minlen="2"];
|
|
||||||
ahriman_core_auth -> ahriman_web_views_base [fillcolor="blue",minlen="3"];
|
ahriman_core_auth -> ahriman_web_views_base [fillcolor="blue",minlen="3"];
|
||||||
|
ahriman_core_auth -> ahriman_web_views_index [fillcolor="blue",minlen="3"];
|
||||||
ahriman_core_auth -> ahriman_web_views_v1_user_login [fillcolor="blue",minlen="3"];
|
ahriman_core_auth -> ahriman_web_views_v1_user_login [fillcolor="blue",minlen="3"];
|
||||||
ahriman_core_auth -> ahriman_web_views_v1_user_logout [fillcolor="blue",minlen="3"];
|
ahriman_core_auth -> ahriman_web_views_v1_user_logout [fillcolor="blue",minlen="3"];
|
||||||
ahriman_core_auth -> ahriman_web_web [fillcolor="blue",minlen="2"];
|
ahriman_core_auth -> ahriman_web_web [fillcolor="blue",minlen="2"];
|
||||||
ahriman_core_auth_auth -> ahriman_core_auth [fillcolor="blue",weight="3"];
|
ahriman_core_auth_auth -> ahriman_core_auth [fillcolor="blue",weight="3"];
|
||||||
ahriman_core_auth_helpers -> ahriman_web_server_info [fillcolor="#d04e24",minlen="3"];
|
ahriman_core_auth_helpers -> ahriman_web_views_index [fillcolor="#d04e24",minlen="3"];
|
||||||
ahriman_core_auth_helpers -> ahriman_web_views_v1_user_login [fillcolor="#d04e24",minlen="3"];
|
ahriman_core_auth_helpers -> ahriman_web_views_v1_user_login [fillcolor="#d04e24",minlen="3"];
|
||||||
ahriman_core_auth_helpers -> ahriman_web_views_v1_user_logout [fillcolor="#d04e24",minlen="3"];
|
ahriman_core_auth_helpers -> ahriman_web_views_v1_user_logout [fillcolor="#d04e24",minlen="3"];
|
||||||
ahriman_core_auth_mapping -> ahriman_core_auth_auth [fillcolor="blue",weight="3"];
|
ahriman_core_auth_mapping -> ahriman_core_auth_auth [fillcolor="blue",weight="3"];
|
||||||
@@ -851,39 +843,35 @@ digraph G {
|
|||||||
ahriman_core_http_sync_ahriman_client -> ahriman_core_http [fillcolor="blue",weight="3"];
|
ahriman_core_http_sync_ahriman_client -> ahriman_core_http [fillcolor="blue",weight="3"];
|
||||||
ahriman_core_http_sync_http_client -> ahriman_core_http [fillcolor="#933f24",weight="3"];
|
ahriman_core_http_sync_http_client -> ahriman_core_http [fillcolor="#933f24",weight="3"];
|
||||||
ahriman_core_http_sync_http_client -> ahriman_core_http_sync_ahriman_client [fillcolor="#933f24",weight="3"];
|
ahriman_core_http_sync_http_client -> ahriman_core_http_sync_ahriman_client [fillcolor="#933f24",weight="3"];
|
||||||
ahriman_core_log -> ahriman_application_application_application_properties [fillcolor="blue",minlen="3"];
|
ahriman_core_log -> ahriman_application_application_application_properties [fillcolor="#e53b05",minlen="3"];
|
||||||
ahriman_core_log -> ahriman_application_application_workers_updater [fillcolor="blue",minlen="3"];
|
ahriman_core_log -> ahriman_application_application_workers_updater [fillcolor="#e53b05",minlen="3"];
|
||||||
ahriman_core_log -> ahriman_application_handlers_handler [fillcolor="blue",minlen="3"];
|
ahriman_core_log -> ahriman_application_handlers_handler [fillcolor="#e53b05",minlen="3"];
|
||||||
ahriman_core_log -> ahriman_application_lock [fillcolor="blue",minlen="2"];
|
ahriman_core_log -> ahriman_application_lock [fillcolor="#e53b05",minlen="2"];
|
||||||
ahriman_core_log -> ahriman_core_alpm_pacman [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_alpm_pacman [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_alpm_repo [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_alpm_repo [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_archive_archive_tree [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_archive_archive_tree [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_auth_auth [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_auth_auth [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_build_tools_package_version [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_build_tools_package_version [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_build_tools_sources [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_build_tools_sources [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_build_tools_task [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_build_tools_task [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_database_migrations [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_database_migrations [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_database_operations_operations [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_database_operations_operations [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_distributed_workers_cache [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_distributed_workers_cache [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_gitremote_remote_pull [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_gitremote_remote_pull [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_gitremote_remote_push [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_gitremote_remote_push [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_http_sync_http_client [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_http_sync_http_client [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_report_report [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_report_report [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_repository_repository_properties [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_repository_repository_properties [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_spawn [fillcolor="blue",weight="2"];
|
ahriman_core_log -> ahriman_core_spawn [fillcolor="#e53b05",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_status_watcher [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_status_watcher [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_triggers_trigger [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_triggers_trigger [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_triggers_trigger_loader [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_triggers_trigger_loader [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_core_upload_upload [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_core_log -> ahriman_core_upload_upload [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_core_log -> ahriman_models_package [fillcolor="blue",minlen="2"];
|
ahriman_core_log -> ahriman_models_package [fillcolor="#e53b05",minlen="2"];
|
||||||
ahriman_core_log -> ahriman_models_repository_paths [fillcolor="blue",minlen="2"];
|
ahriman_core_log -> ahriman_models_repository_paths [fillcolor="#e53b05",minlen="2"];
|
||||||
ahriman_core_log -> ahriman_web_middlewares_request_id_handler [fillcolor="blue",minlen="3"];
|
|
||||||
ahriman_core_log_http_log_handler -> ahriman_core_log_log_loader [fillcolor="#914730",weight="3"];
|
ahriman_core_log_http_log_handler -> ahriman_core_log_log_loader [fillcolor="#914730",weight="3"];
|
||||||
ahriman_core_log_lazy_logging -> ahriman_core_log [fillcolor="blue",weight="3"];
|
ahriman_core_log_lazy_logging -> ahriman_core_log [fillcolor="#b85a3d",weight="3"];
|
||||||
ahriman_core_log_log_context -> ahriman_core_log_lazy_logging [fillcolor="#db582f",weight="3"];
|
ahriman_core_log_log_loader -> ahriman_application_handlers_handler [fillcolor="#82402b",minlen="3"];
|
||||||
ahriman_core_log_log_context -> ahriman_core_log_log_loader [fillcolor="#db582f",weight="3"];
|
|
||||||
ahriman_core_log_log_context -> ahriman_web_middlewares_request_id_handler [fillcolor="#db582f",minlen="3"];
|
|
||||||
ahriman_core_log_log_loader -> ahriman_application_handlers_handler [fillcolor="#7a3c28",minlen="3"];
|
|
||||||
ahriman_core_module_loader -> ahriman_application_ahriman [fillcolor="#ce5e3b",minlen="2"];
|
ahriman_core_module_loader -> ahriman_application_ahriman [fillcolor="#ce5e3b",minlen="2"];
|
||||||
ahriman_core_module_loader -> ahriman_web_routes [fillcolor="#ce5e3b",minlen="2"];
|
ahriman_core_module_loader -> ahriman_web_routes [fillcolor="#ce5e3b",minlen="2"];
|
||||||
ahriman_core_report_console -> ahriman_core_report_report [fillcolor="blue",weight="3"];
|
ahriman_core_report_console -> ahriman_core_report_report [fillcolor="blue",weight="3"];
|
||||||
@@ -1003,7 +991,6 @@ digraph G {
|
|||||||
ahriman_core_types -> ahriman_core_report_jinja_template [fillcolor="#f94810",minlen="2",weight="2"];
|
ahriman_core_types -> ahriman_core_report_jinja_template [fillcolor="#f94810",minlen="2",weight="2"];
|
||||||
ahriman_core_types -> ahriman_core_report_rss [fillcolor="#f94810",minlen="2",weight="2"];
|
ahriman_core_types -> ahriman_core_report_rss [fillcolor="#f94810",minlen="2",weight="2"];
|
||||||
ahriman_core_types -> ahriman_core_utils [fillcolor="#f94810",weight="2"];
|
ahriman_core_types -> ahriman_core_utils [fillcolor="#f94810",weight="2"];
|
||||||
ahriman_core_types -> ahriman_web_server_info [fillcolor="#f94810",minlen="2"];
|
|
||||||
ahriman_core_types -> ahriman_web_views_v1_distributed_workers [fillcolor="#f94810",minlen="3"];
|
ahriman_core_types -> ahriman_web_views_v1_distributed_workers [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_core_types -> ahriman_web_views_v1_packages_packages [fillcolor="#f94810",minlen="3"];
|
ahriman_core_types -> ahriman_web_views_v1_packages_packages [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_core_types -> ahriman_web_views_v1_service_search [fillcolor="#f94810",minlen="3"];
|
ahriman_core_types -> ahriman_web_views_v1_service_search [fillcolor="#f94810",minlen="3"];
|
||||||
@@ -1073,9 +1060,8 @@ digraph G {
|
|||||||
ahriman_core_utils -> ahriman_models_repository_paths [fillcolor="#db3805",minlen="2"];
|
ahriman_core_utils -> ahriman_models_repository_paths [fillcolor="#db3805",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_repository_stats [fillcolor="#db3805",minlen="2"];
|
ahriman_core_utils -> ahriman_models_repository_stats [fillcolor="#db3805",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_models_worker [fillcolor="#db3805",minlen="2"];
|
ahriman_core_utils -> ahriman_models_worker [fillcolor="#db3805",minlen="2"];
|
||||||
ahriman_core_utils -> ahriman_web_server_info [fillcolor="#db3805",minlen="2"];
|
|
||||||
ahriman_core_utils -> ahriman_web_views_api_swagger [fillcolor="#db3805",minlen="3"];
|
ahriman_core_utils -> ahriman_web_views_api_swagger [fillcolor="#db3805",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_web_views_base [fillcolor="#db3805",minlen="3"];
|
ahriman_core_utils -> ahriman_web_views_index [fillcolor="#db3805",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_web_views_v1_packages_logs [fillcolor="#db3805",minlen="3"];
|
ahriman_core_utils -> ahriman_web_views_v1_packages_logs [fillcolor="#db3805",minlen="3"];
|
||||||
ahriman_core_utils -> ahriman_web_views_v1_service_upload [fillcolor="#db3805",minlen="3"];
|
ahriman_core_utils -> ahriman_web_views_v1_service_upload [fillcolor="#db3805",minlen="3"];
|
||||||
ahriman_models -> ahriman_application_ahriman [fillcolor="#f94810",minlen="2"];
|
ahriman_models -> ahriman_application_ahriman [fillcolor="#f94810",minlen="2"];
|
||||||
@@ -1259,7 +1245,6 @@ digraph G {
|
|||||||
ahriman_models -> ahriman_web_views_v1_user_login [fillcolor="#f94810",minlen="3"];
|
ahriman_models -> ahriman_web_views_v1_user_login [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_models -> ahriman_web_views_v1_user_logout [fillcolor="#f94810",minlen="3"];
|
ahriman_models -> ahriman_web_views_v1_user_logout [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_models -> ahriman_web_views_v2_packages_logs [fillcolor="#f94810",minlen="3"];
|
ahriman_models -> ahriman_web_views_v2_packages_logs [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_models -> ahriman_web_views_v2_status_info [fillcolor="#f94810",minlen="3"];
|
|
||||||
ahriman_models -> ahriman_web_web [fillcolor="#f94810",minlen="2"];
|
ahriman_models -> ahriman_web_web [fillcolor="#f94810",minlen="2"];
|
||||||
ahriman_models_action -> ahriman_application_handlers_change [fillcolor="#e75222",minlen="3"];
|
ahriman_models_action -> ahriman_application_handlers_change [fillcolor="#e75222",minlen="3"];
|
||||||
ahriman_models_action -> ahriman_application_handlers_patch [fillcolor="#e75222",minlen="3"];
|
ahriman_models_action -> ahriman_application_handlers_patch [fillcolor="#e75222",minlen="3"];
|
||||||
@@ -1668,7 +1653,6 @@ digraph G {
|
|||||||
ahriman_models_user_access -> ahriman_web_views_v1_user_login [fillcolor="#f94810",minlen="3"];
|
ahriman_models_user_access -> ahriman_web_views_v1_user_login [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_models_user_access -> ahriman_web_views_v1_user_logout [fillcolor="#f94810",minlen="3"];
|
ahriman_models_user_access -> ahriman_web_views_v1_user_logout [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_models_user_access -> ahriman_web_views_v2_packages_logs [fillcolor="#f94810",minlen="3"];
|
ahriman_models_user_access -> ahriman_web_views_v2_packages_logs [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_models_user_access -> ahriman_web_views_v2_status_info [fillcolor="#f94810",minlen="3"];
|
|
||||||
ahriman_models_waiter -> ahriman_application_lock [fillcolor="#c45431",minlen="2"];
|
ahriman_models_waiter -> ahriman_application_lock [fillcolor="#c45431",minlen="2"];
|
||||||
ahriman_models_waiter -> ahriman_core_report_remote_call [fillcolor="#c45431",minlen="3"];
|
ahriman_models_waiter -> ahriman_core_report_remote_call [fillcolor="#c45431",minlen="3"];
|
||||||
ahriman_models_worker -> ahriman_application_application_workers_remote_updater [fillcolor="#e9410c",minlen="3"];
|
ahriman_models_worker -> ahriman_application_application_workers_remote_updater [fillcolor="#e9410c",minlen="3"];
|
||||||
@@ -1679,9 +1663,7 @@ digraph G {
|
|||||||
ahriman_web -> ahriman_application_handlers_web [fillcolor="#f94810",minlen="3"];
|
ahriman_web -> ahriman_application_handlers_web [fillcolor="#f94810",minlen="3"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_any_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_any_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_aur_package_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_aur_package_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_auth_info_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_auth_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_auth_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_auto_refresh_interval_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_build_options_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_build_options_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_changes_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_changes_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_configuration_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_configuration_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
@@ -1692,7 +1674,6 @@ digraph G {
|
|||||||
ahriman_web_apispec -> ahriman_web_schemas_event_search_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_event_search_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_file_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_file_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_info_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_info_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_info_v2_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_internal_status_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_internal_status_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_log_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_log_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_login_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_login_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
@@ -1721,9 +1702,9 @@ digraph G {
|
|||||||
ahriman_web_apispec -> ahriman_web_schemas_status_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_status_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_update_flags_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_update_flags_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_schemas_worker_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_schemas_worker_schema [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_server_info [fillcolor="#e53b05",weight="2"];
|
|
||||||
ahriman_web_apispec -> ahriman_web_views_api_docs [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_api_docs [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_views_api_swagger [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_api_swagger [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
|
ahriman_web_apispec -> ahriman_web_views_index [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_views_v1_auditlog_events [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_v1_auditlog_events [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_views_v1_distributed_workers [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_v1_distributed_workers [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_views_v1_packages_changes [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_v1_packages_changes [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
@@ -1751,7 +1732,6 @@ digraph G {
|
|||||||
ahriman_web_apispec -> ahriman_web_views_v1_user_login [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_v1_user_login [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_views_v1_user_logout [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_v1_user_logout [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_views_v2_packages_logs [fillcolor="#e53b05",minlen="2",weight="2"];
|
ahriman_web_apispec -> ahriman_web_views_v2_packages_logs [fillcolor="#e53b05",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec -> ahriman_web_views_v2_status_info [fillcolor="#e53b05",minlen="2",weight="2"];
|
|
||||||
ahriman_web_apispec -> ahriman_web_web [fillcolor="#e53b05",weight="2"];
|
ahriman_web_apispec -> ahriman_web_web [fillcolor="#e53b05",weight="2"];
|
||||||
ahriman_web_apispec_decorators -> ahriman_web_views_v1_auditlog_events [fillcolor="#bd3104",minlen="2",weight="2"];
|
ahriman_web_apispec_decorators -> ahriman_web_views_v1_auditlog_events [fillcolor="#bd3104",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec_decorators -> ahriman_web_views_v1_distributed_workers [fillcolor="#bd3104",minlen="2",weight="2"];
|
ahriman_web_apispec_decorators -> ahriman_web_views_v1_distributed_workers [fillcolor="#bd3104",minlen="2",weight="2"];
|
||||||
@@ -1780,19 +1760,17 @@ digraph G {
|
|||||||
ahriman_web_apispec_decorators -> ahriman_web_views_v1_user_login [fillcolor="#bd3104",minlen="2",weight="2"];
|
ahriman_web_apispec_decorators -> ahriman_web_views_v1_user_login [fillcolor="#bd3104",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec_decorators -> ahriman_web_views_v1_user_logout [fillcolor="#bd3104",minlen="2",weight="2"];
|
ahriman_web_apispec_decorators -> ahriman_web_views_v1_user_logout [fillcolor="#bd3104",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec_decorators -> ahriman_web_views_v2_packages_logs [fillcolor="#bd3104",minlen="2",weight="2"];
|
ahriman_web_apispec_decorators -> ahriman_web_views_v2_packages_logs [fillcolor="#bd3104",minlen="2",weight="2"];
|
||||||
ahriman_web_apispec_decorators -> ahriman_web_views_v2_status_info [fillcolor="#bd3104",minlen="2",weight="2"];
|
|
||||||
ahriman_web_apispec_info -> ahriman_web_web [fillcolor="#a14f35",minlen="2",weight="2"];
|
ahriman_web_apispec_info -> ahriman_web_web [fillcolor="#a14f35",minlen="2",weight="2"];
|
||||||
ahriman_web_cors -> ahriman_web_web [fillcolor="#b0573a",weight="2"];
|
ahriman_web_cors -> ahriman_web_web [fillcolor="#b0573a",weight="2"];
|
||||||
ahriman_web_keys -> ahriman_web_apispec_info [fillcolor="#823017",minlen="2",weight="2"];
|
ahriman_web_keys -> ahriman_web_apispec_info [fillcolor="#823017",minlen="2",weight="2"];
|
||||||
ahriman_web_keys -> ahriman_web_views_base [fillcolor="#823017",minlen="2",weight="2"];
|
ahriman_web_keys -> ahriman_web_views_base [fillcolor="#823017",minlen="2",weight="2"];
|
||||||
ahriman_web_keys -> ahriman_web_web [fillcolor="#823017",weight="2"];
|
ahriman_web_keys -> ahriman_web_web [fillcolor="#823017",weight="2"];
|
||||||
ahriman_web_middlewares -> ahriman_web_views_v1_status_metrics [fillcolor="#ef3e06",minlen="2",weight="2"];
|
ahriman_web_middlewares -> ahriman_web_views_v1_status_metrics [fillcolor="#e9410c",minlen="2",weight="2"];
|
||||||
ahriman_web_middlewares -> ahriman_web_web [fillcolor="#ef3e06",weight="2"];
|
ahriman_web_middlewares -> ahriman_web_web [fillcolor="#e9410c",weight="2"];
|
||||||
ahriman_web_middlewares_auth_handler -> ahriman_web_web [fillcolor="#733826",minlen="2",weight="2"];
|
ahriman_web_middlewares_auth_handler -> ahriman_web_web [fillcolor="#733826",minlen="2",weight="2"];
|
||||||
ahriman_web_middlewares_exception_handler -> ahriman_web_web [fillcolor="#994b33",minlen="2",weight="2"];
|
ahriman_web_middlewares_exception_handler -> ahriman_web_web [fillcolor="#994b33",minlen="2",weight="2"];
|
||||||
ahriman_web_middlewares_metrics_handler -> ahriman_web_views_v1_status_metrics [fillcolor="#a34628",minlen="2",weight="2"];
|
ahriman_web_middlewares_metrics_handler -> ahriman_web_views_v1_status_metrics [fillcolor="#a34628",minlen="2",weight="2"];
|
||||||
ahriman_web_middlewares_metrics_handler -> ahriman_web_web [fillcolor="#a34628",minlen="2",weight="2"];
|
ahriman_web_middlewares_metrics_handler -> ahriman_web_web [fillcolor="#a34628",minlen="2",weight="2"];
|
||||||
ahriman_web_middlewares_request_id_handler -> ahriman_web_web [fillcolor="#8a442e",minlen="2",weight="2"];
|
|
||||||
ahriman_web_routes -> ahriman_web_web [fillcolor="#8a442e",weight="2"];
|
ahriman_web_routes -> ahriman_web_web [fillcolor="#8a442e",weight="2"];
|
||||||
ahriman_web_schemas -> ahriman_web_apispec_decorators [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_web_schemas -> ahriman_web_apispec_decorators [fillcolor="blue",minlen="2",weight="2"];
|
||||||
ahriman_web_schemas -> ahriman_web_views_v1_auditlog_events [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_web_schemas -> ahriman_web_views_v1_auditlog_events [fillcolor="blue",minlen="2",weight="2"];
|
||||||
@@ -1821,14 +1799,9 @@ digraph G {
|
|||||||
ahriman_web_schemas -> ahriman_web_views_v1_status_status [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_web_schemas -> ahriman_web_views_v1_status_status [fillcolor="blue",minlen="2",weight="2"];
|
||||||
ahriman_web_schemas -> ahriman_web_views_v1_user_login [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_web_schemas -> ahriman_web_views_v1_user_login [fillcolor="blue",minlen="2",weight="2"];
|
||||||
ahriman_web_schemas -> ahriman_web_views_v2_packages_logs [fillcolor="blue",minlen="2",weight="2"];
|
ahriman_web_schemas -> ahriman_web_views_v2_packages_logs [fillcolor="blue",minlen="2",weight="2"];
|
||||||
ahriman_web_schemas -> ahriman_web_views_v2_status_info [fillcolor="blue",minlen="2",weight="2"];
|
|
||||||
ahriman_web_schemas_any_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
ahriman_web_schemas_any_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
||||||
ahriman_web_schemas_aur_package_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
ahriman_web_schemas_aur_package_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
||||||
ahriman_web_schemas_auth_info_schema -> ahriman_web_schemas [fillcolor="#c45431",weight="3"];
|
|
||||||
ahriman_web_schemas_auth_info_schema -> ahriman_web_schemas_info_v2_schema [fillcolor="#c45431",weight="3"];
|
|
||||||
ahriman_web_schemas_auth_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
ahriman_web_schemas_auth_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
||||||
ahriman_web_schemas_auto_refresh_interval_schema -> ahriman_web_schemas [fillcolor="#c45431",weight="3"];
|
|
||||||
ahriman_web_schemas_auto_refresh_interval_schema -> ahriman_web_schemas_info_v2_schema [fillcolor="#c45431",weight="3"];
|
|
||||||
ahriman_web_schemas_build_options_schema -> ahriman_web_schemas [fillcolor="#d04e24",weight="3"];
|
ahriman_web_schemas_build_options_schema -> ahriman_web_schemas [fillcolor="#d04e24",weight="3"];
|
||||||
ahriman_web_schemas_build_options_schema -> ahriman_web_schemas_package_names_schema [fillcolor="#d04e24",weight="3"];
|
ahriman_web_schemas_build_options_schema -> ahriman_web_schemas_package_names_schema [fillcolor="#d04e24",weight="3"];
|
||||||
ahriman_web_schemas_build_options_schema -> ahriman_web_schemas_update_flags_schema [fillcolor="#d04e24",weight="3"];
|
ahriman_web_schemas_build_options_schema -> ahriman_web_schemas_update_flags_schema [fillcolor="#d04e24",weight="3"];
|
||||||
@@ -1842,7 +1815,6 @@ digraph G {
|
|||||||
ahriman_web_schemas_event_search_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
ahriman_web_schemas_event_search_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
||||||
ahriman_web_schemas_file_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
ahriman_web_schemas_file_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
||||||
ahriman_web_schemas_info_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
ahriman_web_schemas_info_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
||||||
ahriman_web_schemas_info_v2_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
|
||||||
ahriman_web_schemas_internal_status_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
ahriman_web_schemas_internal_status_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
||||||
ahriman_web_schemas_log_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
ahriman_web_schemas_log_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
||||||
ahriman_web_schemas_login_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
ahriman_web_schemas_login_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
||||||
@@ -1875,7 +1847,6 @@ digraph G {
|
|||||||
ahriman_web_schemas_remote_schema -> ahriman_web_schemas_package_schema [fillcolor="#b44d2d",weight="3"];
|
ahriman_web_schemas_remote_schema -> ahriman_web_schemas_package_schema [fillcolor="#b44d2d",weight="3"];
|
||||||
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas [fillcolor="#ef3e06",weight="3"];
|
||||||
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_info_schema [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_info_schema [fillcolor="#ef3e06",weight="3"];
|
||||||
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_info_v2_schema [fillcolor="#ef3e06",weight="3"];
|
|
||||||
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_internal_status_schema [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_internal_status_schema [fillcolor="#ef3e06",weight="3"];
|
||||||
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_package_status_schema [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_package_status_schema [fillcolor="#ef3e06",weight="3"];
|
||||||
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_package_version_schema [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_schemas_repository_id_schema -> ahriman_web_schemas_package_version_schema [fillcolor="#ef3e06",weight="3"];
|
||||||
@@ -1889,13 +1860,8 @@ digraph G {
|
|||||||
ahriman_web_schemas_status_schema -> ahriman_web_schemas_package_status_schema [fillcolor="#ca4116",weight="3"];
|
ahriman_web_schemas_status_schema -> ahriman_web_schemas_package_status_schema [fillcolor="#ca4116",weight="3"];
|
||||||
ahriman_web_schemas_update_flags_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
ahriman_web_schemas_update_flags_schema -> ahriman_web_schemas [fillcolor="blue",weight="3"];
|
||||||
ahriman_web_schemas_worker_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
ahriman_web_schemas_worker_schema -> ahriman_web_schemas [fillcolor="#b85a3d",weight="3"];
|
||||||
ahriman_web_server_info -> ahriman_web_views_index [fillcolor="#93371a",minlen="2",weight="2"];
|
|
||||||
ahriman_web_server_info -> ahriman_web_views_v1_status_info [fillcolor="#93371a",minlen="2",weight="2"];
|
|
||||||
ahriman_web_server_info -> ahriman_web_views_v2_status_info [fillcolor="#93371a",minlen="2",weight="2"];
|
|
||||||
ahriman_web_views -> ahriman_web_routes [fillcolor="#f94810",weight="2"];
|
ahriman_web_views -> ahriman_web_routes [fillcolor="#f94810",weight="2"];
|
||||||
ahriman_web_views -> ahriman_web_server_info [fillcolor="#f94810",weight="2"];
|
|
||||||
ahriman_web_views_base -> ahriman_web_routes [fillcolor="#952603",minlen="2",weight="2"];
|
ahriman_web_views_base -> ahriman_web_routes [fillcolor="#952603",minlen="2",weight="2"];
|
||||||
ahriman_web_views_base -> ahriman_web_server_info [fillcolor="#952603",minlen="2",weight="2"];
|
|
||||||
ahriman_web_views_base -> ahriman_web_views_api_docs [fillcolor="#952603",weight="3"];
|
ahriman_web_views_base -> ahriman_web_views_api_docs [fillcolor="#952603",weight="3"];
|
||||||
ahriman_web_views_base -> ahriman_web_views_api_swagger [fillcolor="#952603",weight="3"];
|
ahriman_web_views_base -> ahriman_web_views_api_swagger [fillcolor="#952603",weight="3"];
|
||||||
ahriman_web_views_base -> ahriman_web_views_index [fillcolor="#952603",weight="3"];
|
ahriman_web_views_base -> ahriman_web_views_index [fillcolor="#952603",weight="3"];
|
||||||
@@ -1927,7 +1893,6 @@ digraph G {
|
|||||||
ahriman_web_views_base -> ahriman_web_views_v1_user_login [fillcolor="#952603",weight="3"];
|
ahriman_web_views_base -> ahriman_web_views_v1_user_login [fillcolor="#952603",weight="3"];
|
||||||
ahriman_web_views_base -> ahriman_web_views_v1_user_logout [fillcolor="#952603",weight="3"];
|
ahriman_web_views_base -> ahriman_web_views_v1_user_logout [fillcolor="#952603",weight="3"];
|
||||||
ahriman_web_views_base -> ahriman_web_views_v2_packages_logs [fillcolor="#952603",weight="3"];
|
ahriman_web_views_base -> ahriman_web_views_v2_packages_logs [fillcolor="#952603",weight="3"];
|
||||||
ahriman_web_views_base -> ahriman_web_views_v2_status_info [fillcolor="#952603",weight="3"];
|
|
||||||
ahriman_web_views_status_view_guard -> ahriman_web_views_v1_packages_changes [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_views_status_view_guard -> ahriman_web_views_v1_packages_changes [fillcolor="#ef3e06",weight="3"];
|
||||||
ahriman_web_views_status_view_guard -> ahriman_web_views_v1_packages_dependencies [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_views_status_view_guard -> ahriman_web_views_v1_packages_dependencies [fillcolor="#ef3e06",weight="3"];
|
||||||
ahriman_web_views_status_view_guard -> ahriman_web_views_v1_packages_logs [fillcolor="#ef3e06",weight="3"];
|
ahriman_web_views_status_view_guard -> ahriman_web_views_v1_packages_logs [fillcolor="#ef3e06",weight="3"];
|
||||||
@@ -1947,11 +1912,9 @@ digraph G {
|
|||||||
aiohttp -> ahriman_web_middlewares_auth_handler [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_middlewares_auth_handler [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_middlewares_exception_handler [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_middlewares_exception_handler [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_middlewares_metrics_handler [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_middlewares_metrics_handler [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_middlewares_request_id_handler [fillcolor="#f9b506",minlen="3"];
|
|
||||||
aiohttp -> ahriman_web_routes [fillcolor="#f9b506",minlen="2"];
|
aiohttp -> ahriman_web_routes [fillcolor="#f9b506",minlen="2"];
|
||||||
aiohttp -> ahriman_web_views_api_swagger [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_api_swagger [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_views_base [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_base [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_views_index [fillcolor="#f9b506",minlen="3"];
|
|
||||||
aiohttp -> ahriman_web_views_static [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_static [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_views_v1_auditlog_events [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_v1_auditlog_events [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_views_v1_distributed_workers [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_v1_distributed_workers [fillcolor="#f9b506",minlen="3"];
|
||||||
@@ -1980,7 +1943,6 @@ digraph G {
|
|||||||
aiohttp -> ahriman_web_views_v1_user_login [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_v1_user_login [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_views_v1_user_logout [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_v1_user_logout [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_views_v2_packages_logs [fillcolor="#f9b506",minlen="3"];
|
aiohttp -> ahriman_web_views_v2_packages_logs [fillcolor="#f9b506",minlen="3"];
|
||||||
aiohttp -> ahriman_web_views_v2_status_info [fillcolor="#f9b506",minlen="3"];
|
|
||||||
aiohttp -> ahriman_web_web [fillcolor="#f9b506",minlen="2"];
|
aiohttp -> ahriman_web_web [fillcolor="#f9b506",minlen="2"];
|
||||||
aiohttp -> aiohttp_cors [fillcolor="#f9b506",minlen="2"];
|
aiohttp -> aiohttp_cors [fillcolor="#f9b506",minlen="2"];
|
||||||
aiohttp -> aiohttp_jinja2 [fillcolor="#f9b506",minlen="2"];
|
aiohttp -> aiohttp_jinja2 [fillcolor="#f9b506",minlen="2"];
|
||||||
|
|||||||
@@ -28,14 +28,6 @@ ahriman.core.log.lazy\_logging module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.core.log.log\_context module
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.log.log_context
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.core.log.log\_loader module
|
ahriman.core.log.log\_loader module
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -28,14 +28,6 @@ ahriman.web.middlewares.metrics\_handler module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.middlewares.request\_id\_handler module
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.middlewares.request_id_handler
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ Packages have strict rules of importing:
|
|||||||
* ``ahriman.application`` package must not be used outside of this package.
|
* ``ahriman.application`` package must not be used outside of this package.
|
||||||
* ``ahriman.core`` and ``ahriman.models`` packages don't have any import restriction. Actually we would like to totally restrict importing of ``core`` package from ``models``, but it is impossible at the moment.
|
* ``ahriman.core`` and ``ahriman.models`` packages don't have any import restriction. Actually we would like to totally restrict importing of ``core`` package from ``models``, but it is impossible at the moment.
|
||||||
* ``ahriman.web`` package is allowed to be imported from ``ahriman.application`` (web handler only, only ``ahriman.web.web`` methods).
|
* ``ahriman.web`` package is allowed to be imported from ``ahriman.application`` (web handler only, only ``ahriman.web.web`` methods).
|
||||||
|
* The idea remains the same for all imports, if an package requires some specific dependencies, it must be imported locally to keep dependencies optional.
|
||||||
The idea remains the same for all imports, if a package requires some specific dependencies, it must be imported locally to keep dependencies optional.
|
|
||||||
|
|
||||||
Full dependency diagram:
|
Full dependency diagram:
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ This package contains everything required for the most of application actions an
|
|||||||
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
||||||
* ``ahriman.core.housekeeping`` package provides few triggers for removing old data.
|
* ``ahriman.core.housekeeping`` package provides few triggers for removing old data.
|
||||||
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
||||||
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger, log context for injecting context variables into log records and some wrappers.
|
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
||||||
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
||||||
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.Repository`` class) implementation.
|
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.Repository`` class) implementation.
|
||||||
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
|
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
|
||||||
@@ -86,7 +85,6 @@ Application run
|
|||||||
#. Call ``Handler.execute`` method.
|
#. Call ``Handler.execute`` method.
|
||||||
#. Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or continue in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether the application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Config``, which utilizes stdout to print messages).
|
#. Define list of architectures to run. In case if there is more than one architecture specified run several subprocesses or continue in current process otherwise. Class attribute ``ALLOW_MULTI_ARCHITECTURE_RUN`` controls whether the application can be run in multiple processes or not - this feature is required for some handlers (e.g. ``Config``, which utilizes stdout to print messages).
|
||||||
#. In each child process call lock functions.
|
#. In each child process call lock functions.
|
||||||
#. Load configuration and install logging.
|
|
||||||
#. After success checks pass control to ``Handler.run`` method defined by specific handler class.
|
#. After success checks pass control to ``Handler.run`` method defined by specific handler class.
|
||||||
#. Return result (success or failure) of each subprocess and exit from application.
|
#. Return result (success or failure) of each subprocess and exit from application.
|
||||||
#. Some handlers may override their status and throw ``ExitCode`` exception. This exception is just silently suppressed and changes application exit code to ``1``.
|
#. Some handlers may override their status and throw ``ExitCode`` exception. This exception is just silently suppressed and changes application exit code to ``1``.
|
||||||
@@ -161,12 +159,12 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
|||||||
├── aur.files -> aur.files.tar.gz
|
├── aur.files -> aur.files.tar.gz
|
||||||
└── aur.files.tar.gz
|
└── aur.files.tar.gz
|
||||||
|
|
||||||
There are multiple subdirectories, some of them are common for any repository, but some of them are not.
|
There are multiple subdirectories, some of them are commons for any repository, but some of them are not.
|
||||||
|
|
||||||
* ``archive`` is the package archive directory. It is common for all repositories and architectures and contains two subdirectories:
|
* ``archive`` is the package archive directory. It is common for all repositories and architectures and contains two subdirectories:
|
||||||
|
|
||||||
* ``archive/packages/{first_letter}/{package_base}`` stores the actual built package files and their signatures.
|
* ``archive/packages/{first_letter}/{package_base}`` stores the actual built package files and their signatures.
|
||||||
* ``archive/repos/{YYYY}/{MM}/{DD}/{repository}/{architecture}`` contains daily repository snapshots. Each snapshot is a repository database with symlinks pointing to the corresponding packages in the ``archive/packages`` tree. These directories only appear if ``ahriman.core.archive.ArchiveTrigger`` is enabled.
|
* ``archive/repos/{YYYY}/{MM}/{DD}/{repository}/{architecture}`` contains daily repository snapshots. Each snapshot is a repository database with symlinks pointing to the corresponding packages in the ``archive/packages`` tree.
|
||||||
|
|
||||||
The archive also allows the build process to skip rebuilding a package if a matching version already exists.
|
The archive also allows the build process to skip rebuilding a package if a matching version already exists.
|
||||||
|
|
||||||
@@ -241,9 +239,9 @@ Check outdated packages
|
|||||||
There are few ways for packages to be marked as out-of-date and hence requiring rebuild. Those are following:
|
There are few ways for packages to be marked as out-of-date and hence requiring rebuild. Those are following:
|
||||||
|
|
||||||
#. User requested update of the package. It can be caused by calling ``package-add`` subcommand (or ``package-update`` with arguments).
|
#. User requested update of the package. It can be caused by calling ``package-add`` subcommand (or ``package-update`` with arguments).
|
||||||
#. The most common way for packages to be marked as out-of-date is that the version in AUR (or the official repositories) is newer than in the repository.
|
#. The most common way for packages to be marked as out-of-dated is that the version in AUR (or the official repositories) is newer than in the repository.
|
||||||
#. In addition to the above, if package is named as VCS (e.g. has suffix ``-git``) and the last update was more than specified threshold ago, the service will also try to fetch sources and check if the revision is newer than the built one.
|
#. In addition to the above, if package is named as VCS (e.g. has suffix ``-git``) and the last update was more than specified threshold ago, the service will also try to fetch sources and check if the revision is newer than the built one.
|
||||||
#. In addition, there is ability to check if the dependencies of the package have been updated (e.g. if linked library has been renamed or the modules directory - e.g. in case of python and ruby packages - has been changed). And if so, the package will be marked as out-of-date as well.
|
#. In addition, there is ability to check if the dependencies of the package have been updated (e.g. if linked library has been renamed or the modules directory - e.g. in case of python and ruby packages - has been changed). And if so, the package will be marked as out-of-dated as well.
|
||||||
|
|
||||||
Update packages
|
Update packages
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
@@ -257,7 +255,6 @@ This feature is divided into the following stages: check AUR for updates and run
|
|||||||
|
|
||||||
#. Download package data from AUR.
|
#. Download package data from AUR.
|
||||||
#. Bump ``pkgrel`` if there is duplicate version in the local repository (see explanation below).
|
#. Bump ``pkgrel`` if there is duplicate version in the local repository (see explanation below).
|
||||||
#. Check if there is already built package of the same version in archive (cross-repository support). If so, then just copy built archives and skip steps below.
|
|
||||||
#. Build every package in clean chroot.
|
#. Build every package in clean chroot.
|
||||||
#. Sign packages if required.
|
#. Sign packages if required.
|
||||||
#. Add packages to database and sign database if required.
|
#. Add packages to database and sign database if required.
|
||||||
@@ -316,7 +313,7 @@ Having the initial dependencies tree, the application is looking for packages wh
|
|||||||
|
|
||||||
Those paths are also filtered by regular expressions set in the configuration.
|
Those paths are also filtered by regular expressions set in the configuration.
|
||||||
|
|
||||||
All those implicit dependencies are stored in the database and extracted on each check. In case if any of the repository packages doesn't contain any entry anymore (e.g. so version has been changed or modules directory has been changed), the dependent package will be marked as out-of-date.
|
All those implicit dependencies are stored in the database and extracted on each check. In case if any of the repository packages doesn't contain any entry anymore (e.g. so version has been changed or modules directory has been changed), the dependent package will be marked as out-of-dated.
|
||||||
|
|
||||||
Core functions reference
|
Core functions reference
|
||||||
------------------------
|
------------------------
|
||||||
@@ -347,8 +344,6 @@ The ``_Context`` class itself mimics default collection interface (as is ``Mappi
|
|||||||
|
|
||||||
In order to provide statically typed interface, the ``ahriman.models.context_key.ContextKey`` class is used for both ``_Content.get`` and ``_Content.set`` methods; the context instance itself, however, does not store information about types.
|
In order to provide statically typed interface, the ``ahriman.models.context_key.ContextKey`` class is used for both ``_Content.get`` and ``_Content.set`` methods; the context instance itself, however, does not store information about types.
|
||||||
|
|
||||||
Logging module has its own context variables, which are required to be registered in advance to avoid possible race conditions.
|
|
||||||
|
|
||||||
Submodules
|
Submodules
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
@@ -375,7 +370,7 @@ Passwords must be stored in database as ``hash(password + salt)``, where ``passw
|
|||||||
|
|
||||||
means that there is user ``username`` with ``read`` access and password ``password`` hashed by ``sha512`` with salt ``salt``.
|
means that there is user ``username`` with ``read`` access and password ``password`` hashed by ``sha512`` with salt ``salt``.
|
||||||
|
|
||||||
OAuth provider uses library definitions (``aioauth-client``) in order to *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
|
OAuth provider uses library definitions (``aioauth-client``) in order *authenticate* users. It still requires user permission to be set in database, thus it inherits mapping provider without any changes. Whereas we could override ``check_credentials`` (authentication method) by something custom, OAuth flow is a bit more complex than just forward request, thus we have to implement the flow in login form.
|
||||||
|
|
||||||
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature can be used by service reporting (aka robots).
|
OAuth's implementation also allows authenticating users via username + password (in the same way as mapping does) though it is not recommended for end-users and password must be left blank. In particular this feature can be used by service reporting (aka robots).
|
||||||
|
|
||||||
@@ -388,7 +383,7 @@ Triggers
|
|||||||
|
|
||||||
Triggers are extensions which can be used in order to perform any actions on application start, after the update process and, finally, before the application exit.
|
Triggers are extensions which can be used in order to perform any actions on application start, after the update process and, finally, before the application exit.
|
||||||
|
|
||||||
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join the remaining part by ``.`` and interpret as module path, import module and extract attribute from it.
|
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it.
|
||||||
|
|
||||||
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``list[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
|
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``list[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
|
||||||
|
|
||||||
@@ -412,7 +407,7 @@ PKGBUILD parsing
|
|||||||
|
|
||||||
The application provides a house-made shell parser ``ahriman.core.alpm.pkgbuild_parser.PkgbuildParser`` to process PKGBUILDs and extract package data from them. It relies on the ``shlex.shlex`` parser with some configuration tweaks and adds some token post-processing.
|
The application provides a house-made shell parser ``ahriman.core.alpm.pkgbuild_parser.PkgbuildParser`` to process PKGBUILDs and extract package data from them. It relies on the ``shlex.shlex`` parser with some configuration tweaks and adds some token post-processing.
|
||||||
|
|
||||||
#. During the parser process, firstly, it extracts the next token from the source file (basically, the word) and tries to match it to the variable assignment. If so, then just processes accordingly.
|
#. During the parser process, firstly, it extract next token from the source file (basically, the word) and tries to match it to the variable assignment. If so, then just processes accordingly.
|
||||||
#. If it is not an assignment, the parser checks if the token was quoted.
|
#. If it is not an assignment, the parser checks if the token was quoted.
|
||||||
#. If it wasn't quoted then the parser tries to match the array starts (two consecutive tokens like ``array=`` and ``(``) or it is function (``function``, ``()`` and ``{``).
|
#. If it wasn't quoted then the parser tries to match the array starts (two consecutive tokens like ``array=`` and ``(``) or it is function (``function``, ``()`` and ``{``).
|
||||||
#. The arrays are processed until the next closing bracket ``)``. After extraction, the parser tries to expand an array according to bash rules (``prefix{first,second}suffix`` constructions).
|
#. The arrays are processed until the next closing bracket ``)``. After extraction, the parser tries to expand an array according to bash rules (``prefix{first,second}suffix`` constructions).
|
||||||
@@ -425,15 +420,6 @@ The PKGBUILD class also provides some additional functions on top of that:
|
|||||||
* Ability to extract fields defined inside ``package*()`` functions, which are in particular used for the multi-packages.
|
* Ability to extract fields defined inside ``package*()`` functions, which are in particular used for the multi-packages.
|
||||||
* Shell substitution, which supports constructions ``$var`` (including ``${var}``), ``${var#(#)pattern}``, ``${var%(%)pattern}`` and ``${var/(/)pattern/replacement}`` (including ``#pattern`` and ``%pattern``).
|
* Shell substitution, which supports constructions ``$var`` (including ``${var}``), ``${var#(#)pattern}``, ``${var%(%)pattern}`` and ``${var/(/)pattern/replacement}`` (including ``#pattern`` and ``%pattern``).
|
||||||
|
|
||||||
HTTP client
|
|
||||||
^^^^^^^^^^^
|
|
||||||
|
|
||||||
The ``ahriman.core.http`` package provides a HTTP client built on top of the ``requests`` library.
|
|
||||||
|
|
||||||
The base class ``ahriman.core.http.SyncHttpClient`` wraps ``requests.Session`` and provides common features for all HTTP interactions: configurable timeouts, retry policies with exponential backoff (using ``urllib3.util.retry.Retry``), basic authentication, custom User-Agent header, error processing, and ``make_request`` method. The session is lazily created (via ``cached_property``).
|
|
||||||
|
|
||||||
On top of that, ``ahriman.core.http.SyncAhrimanClient`` extends the base client for communication with the ahriman web service specifically. It adds automatic login on session creation (using configured credentials), ``X-Request-ID`` header injection and Unix socket transport support (via ``requests-unixsocket2``) if required.
|
|
||||||
|
|
||||||
Additional features
|
Additional features
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@@ -459,7 +445,7 @@ Web application requires the following python packages to be installed:
|
|||||||
Middlewares
|
Middlewares
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
|
|
||||||
Service provides some custom middlewares, e.g. logging every exception (except for user ones), user authorization and request tracing via ``X-Request-ID`` header.
|
Service provides some custom middlewares, e.g. logging every exception (except for user ones) and user authorization.
|
||||||
|
|
||||||
HEAD and OPTIONS requests
|
HEAD and OPTIONS requests
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -471,7 +457,7 @@ On the other side, ``OPTIONS`` method is implemented in the ``ahriman.web.middle
|
|||||||
Web views
|
Web views
|
||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
|
||||||
All web views are defined in a separate package and derived from ``ahriman.web.views.base.Base`` class which provides typed interfaces for web application.
|
All web views are defined in separated package and derived from ``ahriman.web.views.base.Base`` class which provides typed interfaces for web application.
|
||||||
|
|
||||||
REST API supports only JSON data.
|
REST API supports only JSON data.
|
||||||
|
|
||||||
@@ -490,7 +476,7 @@ The views are also divided by supporting API versions (e.g. ``v1``, ``v2``).
|
|||||||
Templating
|
Templating
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
Package provides base jinja templates which can be overridden by settings. The default web interface is a React application. The classic bootstrap-based template is still available as ``build-status-classic.jinja2`` and can be enabled via the ``web.template`` configuration option.
|
Package provides base jinja templates which can be overridden by settings. Vanilla templates actively use bootstrap library.
|
||||||
|
|
||||||
Requests and scopes
|
Requests and scopes
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|||||||
@@ -27,26 +27,21 @@ export default tseslint.config(
|
|||||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||||
|
|
||||||
// imports
|
// imports
|
||||||
"simple-import-sort/exports": "error",
|
|
||||||
"simple-import-sort/imports": "error",
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
|
||||||
// core
|
// brackets
|
||||||
"curly": "error",
|
"curly": "error",
|
||||||
"eqeqeq": "error",
|
"@stylistic/brace-style": ["error", "1tbs"],
|
||||||
"no-console": "error",
|
|
||||||
"no-eval": "error",
|
|
||||||
|
|
||||||
// stylistic
|
// stylistic
|
||||||
"@stylistic/array-bracket-spacing": ["error", "never"],
|
"@stylistic/array-bracket-spacing": ["error", "never"],
|
||||||
"@stylistic/arrow-parens": ["error", "as-needed"],
|
"@stylistic/arrow-parens": ["error", "as-needed"],
|
||||||
"@stylistic/brace-style": ["error", "1tbs"],
|
|
||||||
"@stylistic/comma-dangle": ["error", "always-multiline"],
|
"@stylistic/comma-dangle": ["error", "always-multiline"],
|
||||||
"@stylistic/comma-spacing": ["error", { before: false, after: true }],
|
"@stylistic/comma-spacing": ["error", { before: false, after: true }],
|
||||||
"@stylistic/eol-last": ["error", "always"],
|
"@stylistic/eol-last": ["error", "always"],
|
||||||
"@stylistic/indent": ["error", 4],
|
"@stylistic/indent": ["error", 4],
|
||||||
"@stylistic/jsx-curly-brace-presence": ["error", { props: "never", children: "never" }],
|
|
||||||
"@stylistic/jsx-quotes": ["error", "prefer-double"],
|
"@stylistic/jsx-quotes": ["error", "prefer-double"],
|
||||||
"@stylistic/jsx-self-closing-comp": ["error", { component: true, html: true }],
|
|
||||||
"@stylistic/max-len": ["error", {
|
"@stylistic/max-len": ["error", {
|
||||||
code: 120,
|
code: 120,
|
||||||
ignoreComments: true,
|
ignoreComments: true,
|
||||||
@@ -54,7 +49,6 @@ export default tseslint.config(
|
|||||||
ignoreTemplateLiterals: true,
|
ignoreTemplateLiterals: true,
|
||||||
ignoreUrls: true,
|
ignoreUrls: true,
|
||||||
}],
|
}],
|
||||||
"@stylistic/member-delimiter-style": ["error", { multiline: { delimiter: "semi" }, singleline: { delimiter: "semi" } }],
|
|
||||||
"@stylistic/no-extra-parens": ["error", "all"],
|
"@stylistic/no-extra-parens": ["error", "all"],
|
||||||
"@stylistic/no-multi-spaces": "error",
|
"@stylistic/no-multi-spaces": "error",
|
||||||
"@stylistic/no-multiple-empty-lines": ["error", { max: 1 }],
|
"@stylistic/no-multiple-empty-lines": ["error", { max: 1 }],
|
||||||
@@ -64,14 +58,10 @@ export default tseslint.config(
|
|||||||
"@stylistic/semi": ["error", "always"],
|
"@stylistic/semi": ["error", "always"],
|
||||||
|
|
||||||
// typescript
|
// typescript
|
||||||
"@typescript-eslint/consistent-type-definitions": ["error", "interface"],
|
|
||||||
"@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
|
"@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", { allowExpressions: true }],
|
"@typescript-eslint/explicit-function-return-type": ["error", { allowExpressions: true }],
|
||||||
"@typescript-eslint/no-deprecated": "error",
|
"@typescript-eslint/no-deprecated": "error",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
"@typescript-eslint/prefer-nullish-coalescing": "error",
|
|
||||||
"@typescript-eslint/prefer-optional-chain": "error",
|
|
||||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "ahriman-frontend",
|
"name": "ahriman-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.20.1",
|
"version": "2.20.0-rc4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -11,30 +11,29 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.11.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^7.3.9",
|
"@mui/icons-material": "^7.3.8",
|
||||||
"@mui/material": "^7.3.9",
|
"@mui/material": "^7.3.8",
|
||||||
"@mui/x-data-grid": "^8.27.4",
|
"@mui/x-data-grid": "^8.27.3",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.0",
|
||||||
|
"highlight.js": "^11.11.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-chartjs-2": "^5.3.1",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4"
|
||||||
"react-syntax-highlighter": "^16.1.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.3",
|
"@eslint/js": "^9.39.3",
|
||||||
"@stylistic/eslint-plugin": "^5.10.0",
|
"@stylistic/eslint-plugin": "^5.9.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
"@vitejs/plugin-react": "^5.1.4",
|
|
||||||
"eslint": "^9.39.3",
|
"eslint": "^9.39.3",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.3.0",
|
||||||
"typescript-eslint": "^8.56.1",
|
"typescript-eslint": "^8.56.1",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-tsconfig-paths": "^6.1.1"
|
"vite-tsconfig-paths": "^6.1.1"
|
||||||
|
|||||||
@@ -17,14 +17,16 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import CssBaseline from "@mui/material/CssBaseline";
|
||||||
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import AppLayout from "components/layout/AppLayout";
|
import AppLayout from "components/layout/AppLayout";
|
||||||
import { AuthProvider } from "contexts/AuthProvider";
|
import { AuthProvider } from "contexts/AuthProvider";
|
||||||
import { ClientProvider } from "contexts/ClientProvider";
|
import { ClientProvider } from "contexts/ClientProvider";
|
||||||
import { NotificationProvider } from "contexts/NotificationProvider";
|
import { NotificationProvider } from "contexts/NotificationProvider";
|
||||||
import { RepositoryProvider } from "contexts/RepositoryProvider";
|
import { RepositoryProvider } from "contexts/RepositoryProvider";
|
||||||
import { ThemeProvider } from "contexts/ThemeProvider";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
|
import Theme from "theme/Theme";
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@@ -36,17 +38,20 @@ const queryClient = new QueryClient({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default function App(): React.JSX.Element {
|
export default function App(): React.JSX.Element {
|
||||||
return <QueryClientProvider client={queryClient}>
|
return (
|
||||||
<ThemeProvider>
|
<QueryClientProvider client={queryClient}>
|
||||||
<NotificationProvider>
|
<ThemeProvider theme={Theme}>
|
||||||
|
<CssBaseline />
|
||||||
<ClientProvider>
|
<ClientProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<RepositoryProvider>
|
<RepositoryProvider>
|
||||||
<AppLayout />
|
<NotificationProvider>
|
||||||
|
<AppLayout />
|
||||||
|
</NotificationProvider>
|
||||||
</RepositoryProvider>
|
</RepositoryProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</ClientProvider>
|
</ClientProvider>
|
||||||
</NotificationProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</QueryClientProvider>
|
||||||
</QueryClientProvider>;
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,14 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { Client } from "api/client/Client";
|
import { BaseClient } from "api/client/BaseClient";
|
||||||
import { FetchClient } from "api/client/FetchClient";
|
import { FetchMixin } from "api/client/FetchMixin";
|
||||||
import { ServiceClient } from "api/client/ServiceClient";
|
import { ServiceMixin } from "api/client/ServiceMixin";
|
||||||
import type { LoginRequest } from "models/LoginRequest";
|
import type { LoginRequest } from "models/LoginRequest";
|
||||||
|
import { applyMixins } from "utils";
|
||||||
|
|
||||||
export class AhrimanClient extends Client {
|
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
|
||||||
|
export class AhrimanClient extends BaseClient {
|
||||||
readonly fetch = new FetchClient(this);
|
|
||||||
readonly service = new ServiceClient(this);
|
|
||||||
|
|
||||||
async login(data: LoginRequest): Promise<void> {
|
async login(data: LoginRequest): Promise<void> {
|
||||||
return this.request("/api/v1/login", { method: "POST", json: data });
|
return this.request("/api/v1/login", { method: "POST", json: data });
|
||||||
@@ -35,3 +34,7 @@ export class AhrimanClient extends Client {
|
|||||||
return this.request("/api/v1/logout", { method: "POST" });
|
return this.request("/api/v1/logout", { method: "POST" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AhrimanClient extends FetchMixin, ServiceMixin {}
|
||||||
|
/* eslint-enable @typescript-eslint/no-unsafe-declaration-merging */
|
||||||
|
applyMixins(AhrimanClient, [FetchMixin, ServiceMixin]);
|
||||||
|
|||||||
@@ -20,12 +20,10 @@
|
|||||||
import { ApiError } from "api/client/ApiError";
|
import { ApiError } from "api/client/ApiError";
|
||||||
import type { RequestOptions } from "api/client/RequestOptions";
|
import type { RequestOptions } from "api/client/RequestOptions";
|
||||||
|
|
||||||
export class Client {
|
export class BaseClient {
|
||||||
|
|
||||||
private static readonly DEFAULT_TIMEOUT = 30_000;
|
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
|
||||||
|
const { method, query, json } = options;
|
||||||
async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
|
|
||||||
const { method, query, json, timeout = Client.DEFAULT_TIMEOUT } = options;
|
|
||||||
|
|
||||||
let fullUrl = url;
|
let fullUrl = url;
|
||||||
if (query) {
|
if (query) {
|
||||||
@@ -40,41 +38,35 @@ export class Client {
|
|||||||
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"X-Request-ID": crypto.randomUUID?.() ?? Date.now().toString(),
|
|
||||||
};
|
};
|
||||||
if (json !== undefined) {
|
if (json !== undefined) {
|
||||||
headers["Content-Type"] = "application/json";
|
headers["Content-Type"] = "application/json";
|
||||||
}
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
const requestInit: RequestInit = {
|
const requestInit: RequestInit = {
|
||||||
method: method ?? (json ? "POST" : "GET"),
|
method: method || (json ? "POST" : "GET"),
|
||||||
headers,
|
headers,
|
||||||
signal: controller.signal,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (json !== undefined) {
|
if (json !== undefined) {
|
||||||
requestInit.body = JSON.stringify(json);
|
requestInit.body = JSON.stringify(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: Response;
|
const response = await fetch(fullUrl, requestInit);
|
||||||
try {
|
|
||||||
response = await fetch(fullUrl, requestInit);
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const body = await response.text();
|
const body = await response.text();
|
||||||
throw new ApiError(response.status, response.statusText, body);
|
throw new ApiError(response.status, response.statusText, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = response.headers.get("Content-Type") ?? "";
|
if (response.redirected) {
|
||||||
if (!contentType.includes("application/json")) {
|
|
||||||
return undefined as T;
|
return undefined as T;
|
||||||
}
|
}
|
||||||
return await response.json() as T;
|
|
||||||
|
const contentType = response.headers.get("Content-Type") ?? "";
|
||||||
|
if (contentType.includes("application/json")) {
|
||||||
|
return await response.json() as T;
|
||||||
|
}
|
||||||
|
return await response.text() as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import type { Client } from "api/client/Client";
|
import { BaseClient } from "api/client/BaseClient";
|
||||||
import type { Changes } from "models/Changes";
|
import type { Changes } from "models/Changes";
|
||||||
import type { Dependencies } from "models/Dependencies";
|
import type { Dependencies } from "models/Dependencies";
|
||||||
import type { Event } from "models/Event";
|
import type { Event } from "models/Event";
|
||||||
@@ -28,28 +28,22 @@ import type { PackageStatus } from "models/PackageStatus";
|
|||||||
import type { Patch } from "models/Patch";
|
import type { Patch } from "models/Patch";
|
||||||
import { RepositoryId } from "models/RepositoryId";
|
import { RepositoryId } from "models/RepositoryId";
|
||||||
|
|
||||||
export class FetchClient {
|
export class FetchMixin extends BaseClient {
|
||||||
|
|
||||||
protected client: Client;
|
|
||||||
|
|
||||||
constructor(client: Client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPackage(packageBase: string, repository: RepositoryId): Promise<PackageStatus[]> {
|
async fetchPackage(packageBase: string, repository: RepositoryId): Promise<PackageStatus[]> {
|
||||||
return this.client.request<PackageStatus[]>(`/api/v1/packages/${encodeURIComponent(packageBase)}`, {
|
return this.request<PackageStatus[]>(`/api/v1/packages/${encodeURIComponent(packageBase)}`, {
|
||||||
query: repository.toQuery(),
|
query: repository.toQuery(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchPackageChanges(packageBase: string, repository: RepositoryId): Promise<Changes> {
|
async fetchPackageChanges(packageBase: string, repository: RepositoryId): Promise<Changes> {
|
||||||
return this.client.request<Changes>(`/api/v1/packages/${encodeURIComponent(packageBase)}/changes`, {
|
return this.request<Changes>(`/api/v1/packages/${encodeURIComponent(packageBase)}/changes`, {
|
||||||
query: repository.toQuery(),
|
query: repository.toQuery(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchPackageDependencies(packageBase: string, repository: RepositoryId): Promise<Dependencies> {
|
async fetchPackageDependencies(packageBase: string, repository: RepositoryId): Promise<Dependencies> {
|
||||||
return this.client.request<Dependencies>(`/api/v1/packages/${encodeURIComponent(packageBase)}/dependencies`, {
|
return this.request<Dependencies>(`/api/v1/packages/${encodeURIComponent(packageBase)}/dependencies`, {
|
||||||
query: repository.toQuery(),
|
query: repository.toQuery(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -62,7 +56,7 @@ export class FetchClient {
|
|||||||
if (limit) {
|
if (limit) {
|
||||||
query.limit = limit;
|
query.limit = limit;
|
||||||
}
|
}
|
||||||
return this.client.request<Event[]>("/api/v1/events", { query });
|
return this.request<Event[]>("/api/v1/events", { query });
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchPackageLogs(
|
async fetchPackageLogs(
|
||||||
@@ -82,28 +76,26 @@ export class FetchClient {
|
|||||||
if (head) {
|
if (head) {
|
||||||
query.head = true;
|
query.head = true;
|
||||||
}
|
}
|
||||||
return this.client.request<LogRecord[]>(`/api/v2/packages/${encodeURIComponent(packageBase)}/logs`, { query });
|
return this.request<LogRecord[]>(`/api/v2/packages/${encodeURIComponent(packageBase)}/logs`, { query });
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchPackagePatches(packageBase: string): Promise<Patch[]> {
|
async fetchPackagePatches(packageBase: string): Promise<Patch[]> {
|
||||||
return this.client.request<Patch[]>(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches`);
|
return this.request<Patch[]>(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchPackages(repository: RepositoryId): Promise<PackageStatus[]> {
|
async fetchPackages(repository: RepositoryId): Promise<PackageStatus[]> {
|
||||||
return this.client.request<PackageStatus[]>("/api/v1/packages", { query: repository.toQuery() });
|
return this.request<PackageStatus[]>("/api/v1/packages", { query: repository.toQuery() });
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchServerInfo(): Promise<InfoResponse> {
|
async fetchServerInfo(): Promise<InfoResponse> {
|
||||||
const info = await this.client.request<InfoResponse>("/api/v2/info");
|
const info = await this.request<InfoResponse>("/api/v2/info");
|
||||||
return {
|
info.repositories = info.repositories.map(repositories =>
|
||||||
...info,
|
new RepositoryId(repositories.architecture, repositories.repository),
|
||||||
repositories: info.repositories.map(repo =>
|
);
|
||||||
new RepositoryId(repo.architecture, repo.repository),
|
return info;
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchServerStatus(repository: RepositoryId): Promise<InternalStatus> {
|
async fetchServerStatus(repository: RepositoryId): Promise<InternalStatus> {
|
||||||
return this.client.request<InternalStatus>("/api/v1/status", { query: repository.toQuery() });
|
return this.request<InternalStatus>("/api/v1/status", { query: repository.toQuery() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,5 +21,4 @@ export interface RequestOptions {
|
|||||||
method?: string;
|
method?: string;
|
||||||
query?: Record<string, string | number | boolean>;
|
query?: Record<string, string | number | boolean>;
|
||||||
json?: unknown;
|
json?: unknown;
|
||||||
timeout?: number;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,33 +17,27 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import type { Client } from "api/client/Client";
|
import { BaseClient } from "api/client/BaseClient";
|
||||||
import type { AURPackage } from "models/AURPackage";
|
import type { AURPackage } from "models/AURPackage";
|
||||||
import type { PackageActionRequest } from "models/PackageActionRequest";
|
import type { PackageActionRequest } from "models/PackageActionRequest";
|
||||||
import type { PGPKey } from "models/PGPKey";
|
import type { PGPKey } from "models/PGPKey";
|
||||||
import type { PGPKeyRequest } from "models/PGPKeyRequest";
|
import type { PGPKeyRequest } from "models/PGPKeyRequest";
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
import type { RepositoryId } from "models/RepositoryId";
|
||||||
|
|
||||||
export class ServiceClient {
|
export class ServiceMixin extends BaseClient {
|
||||||
|
|
||||||
protected client: Client;
|
|
||||||
|
|
||||||
constructor(client: Client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePackageAdd(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
async servicePackageAdd(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
||||||
return this.client.request("/api/v1/service/add", { method: "POST", query: repository.toQuery(), json: data });
|
return this.request("/api/v1/service/add", { method: "POST", query: repository.toQuery(), json: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
async servicePackagePatchRemove(packageBase: string, key: string): Promise<void> {
|
async servicePackagePatchRemove(packageBase: string, key: string): Promise<void> {
|
||||||
return this.client.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches/${encodeURIComponent(key)}`, {
|
return this.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches/${encodeURIComponent(key)}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async servicePackageRemove(repository: RepositoryId, packages: string[]): Promise<void> {
|
async servicePackageRemove(repository: RepositoryId, packages: string[]): Promise<void> {
|
||||||
return this.client.request("/api/v1/service/remove", {
|
return this.request("/api/v1/service/remove", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
query: repository.toQuery(),
|
query: repository.toQuery(),
|
||||||
json: { packages },
|
json: { packages },
|
||||||
@@ -51,7 +45,7 @@ export class ServiceClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async servicePackageRequest(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
async servicePackageRequest(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
||||||
return this.client.request("/api/v1/service/request", {
|
return this.request("/api/v1/service/request", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
query: repository.toQuery(),
|
query: repository.toQuery(),
|
||||||
json: data,
|
json: data,
|
||||||
@@ -59,11 +53,11 @@ export class ServiceClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async servicePackageSearch(query: string): Promise<AURPackage[]> {
|
async servicePackageSearch(query: string): Promise<AURPackage[]> {
|
||||||
return this.client.request<AURPackage[]>("/api/v1/service/search", { query: { for: query } });
|
return this.request<AURPackage[]>("/api/v1/service/search", { query: { for: query } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async servicePackageUpdate(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
async servicePackageUpdate(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
||||||
return this.client.request("/api/v1/service/update", {
|
return this.request("/api/v1/service/update", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
query: repository.toQuery(),
|
query: repository.toQuery(),
|
||||||
json: data,
|
json: data,
|
||||||
@@ -71,15 +65,15 @@ export class ServiceClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async servicePGPFetch(key: string, server: string): Promise<PGPKey> {
|
async servicePGPFetch(key: string, server: string): Promise<PGPKey> {
|
||||||
return this.client.request<PGPKey>("/api/v1/service/pgp", { query: { key, server } });
|
return this.request<PGPKey>("/api/v1/service/pgp", { query: { key, server } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async servicePGPImport(data: PGPKeyRequest): Promise<void> {
|
async servicePGPImport(data: PGPKeyRequest): Promise<void> {
|
||||||
return this.client.request("/api/v1/service/pgp", { method: "POST", json: data });
|
return this.request("/api/v1/service/pgp", { method: "POST", json: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
async serviceRebuild(repository: RepositoryId, packages: string[]): Promise<void> {
|
async serviceRebuild(repository: RepositoryId, packages: string[]): Promise<void> {
|
||||||
return this.client.request("/api/v1/service/rebuild", {
|
return this.request("/api/v1/service/rebuild", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
query: repository.toQuery(),
|
query: repository.toQuery(),
|
||||||
json: { packages },
|
json: { packages },
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { blue } from "@mui/material/colors";
|
|
||||||
import type { Event } from "models/Event";
|
import type { Event } from "models/Event";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { Line } from "react-chartjs-2";
|
import { Line } from "react-chartjs-2";
|
||||||
@@ -34,8 +33,6 @@ export default function EventDurationLineChart({ events }: EventDurationLineChar
|
|||||||
{
|
{
|
||||||
label: "update duration, s",
|
label: "update duration, s",
|
||||||
data: updateEvents.map(event => event.data?.took ?? 0),
|
data: updateEvents.map(event => event.data?.took ?? 0),
|
||||||
borderColor: blue[500],
|
|
||||||
backgroundColor: blue[200],
|
|
||||||
cubicInterpolationMode: "monotone" as const,
|
cubicInterpolationMode: "monotone" as const,
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ export default function PackageCountBarChart({ stats }: PackageCountBarChartProp
|
|||||||
data={{
|
data={{
|
||||||
labels: ["packages"],
|
labels: ["packages"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
|
||||||
label: "bases",
|
|
||||||
data: [stats.bases ?? 0],
|
|
||||||
backgroundColor: indigo[300],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "archives",
|
label: "archives",
|
||||||
data: [stats.packages ?? 0],
|
data: [stats.packages ?? 0],
|
||||||
backgroundColor: blue[500],
|
backgroundColor: blue[500],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "bases",
|
||||||
|
data: [stats.bases ?? 0],
|
||||||
|
backgroundColor: indigo[300],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
options={{
|
options={{
|
||||||
@@ -48,7 +48,7 @@ export default function PackageCountBarChart({ stats }: PackageCountBarChartProp
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
x: { stacked: true },
|
x: { stacked: true },
|
||||||
y: { stacked: false },
|
y: { stacked: true },
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import type { BuildStatus } from "models/BuildStatus";
|
import type { BuildStatus } from "models/BuildStatus.ts";
|
||||||
import type { Counters } from "models/Counters";
|
import type { Counters } from "models/Counters";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { Pie } from "react-chartjs-2";
|
import { Pie } from "react-chartjs-2";
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export default function AutoRefreshControl({
|
|||||||
<Tooltip title="Auto-refresh">
|
<Tooltip title="Auto-refresh">
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="Auto-refresh"
|
|
||||||
onClick={event => setAnchorEl(event.currentTarget)}
|
onClick={event => setAnchorEl(event.currentTarget)}
|
||||||
color={enabled ? "primary" : "default"}
|
color={enabled ? "primary" : "default"}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ import CopyButton from "components/common/CopyButton";
|
|||||||
import React, { type RefObject } from "react";
|
import React, { type RefObject } from "react";
|
||||||
|
|
||||||
interface CodeBlockProps {
|
interface CodeBlockProps {
|
||||||
|
codeRef?: RefObject<HTMLElement | null>;
|
||||||
preRef?: RefObject<HTMLElement | null>;
|
preRef?: RefObject<HTMLElement | null>;
|
||||||
|
className?: string;
|
||||||
getText: () => string;
|
getText: () => string;
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
onScroll?: () => void;
|
onScroll?: () => void;
|
||||||
@@ -30,7 +32,9 @@ interface CodeBlockProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CodeBlock({
|
export default function CodeBlock({
|
||||||
|
codeRef,
|
||||||
preRef,
|
preRef,
|
||||||
|
className,
|
||||||
getText,
|
getText,
|
||||||
height,
|
height,
|
||||||
onScroll,
|
onScroll,
|
||||||
@@ -42,7 +46,7 @@ export default function CodeBlock({
|
|||||||
component="pre"
|
component="pre"
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: "action.hover",
|
backgroundColor: "grey.100",
|
||||||
p: 2,
|
p: 2,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
@@ -52,8 +56,8 @@ export default function CodeBlock({
|
|||||||
...wordBreak ? { whiteSpace: "pre-wrap", wordBreak: "break-all" } : {},
|
...wordBreak ? { whiteSpace: "pre-wrap", wordBreak: "break-all" } : {},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<code>
|
<code ref={codeRef} className={className}>
|
||||||
{getText()}
|
{!codeRef && getText()}
|
||||||
</code>
|
</code>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
|
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
|
||||||
|
|||||||
@@ -17,11 +17,12 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { createContext } from "react";
|
import React from "react";
|
||||||
|
|
||||||
export interface ThemeContextValue {
|
interface StringListProps {
|
||||||
mode: "light" | "dark";
|
items: string[];
|
||||||
toggleTheme: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemeContext = createContext<ThemeContextValue | null>(null);
|
export default function StringList({ items }: StringListProps): React.JSX.Element {
|
||||||
|
return <>{items.join("\n")}</>;
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ export default function DashboardDialog({ open, onClose }: DashboardDialogProps)
|
|||||||
|
|
||||||
const { data: status } = useQuery<InternalStatus>({
|
const { data: status } = useQuery<InternalStatus>({
|
||||||
queryKey: current ? QueryKeys.status(current) : ["status"],
|
queryKey: current ? QueryKeys.status(current) : ["status"],
|
||||||
queryFn: current ? () => client.fetch.fetchServerStatus(current) : skipToken,
|
queryFn: current ? () => client.fetchServerStatus(current) : skipToken,
|
||||||
enabled: open,
|
enabled: open,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,12 +86,12 @@ export default function DashboardDialog({ open, onClose }: DashboardDialogProps)
|
|||||||
|
|
||||||
<Grid container spacing={2} sx={{ mt: 2 }}>
|
<Grid container spacing={2} sx={{ mt: 2 }}>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Box sx={{ height: 300 }}>
|
<Box sx={{ maxHeight: 300 }}>
|
||||||
<PackageCountBarChart stats={status.stats} />
|
<PackageCountBarChart stats={status.stats} />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Box sx={{ height: 300, display: "flex", justifyContent: "center", alignItems: "center" }}>
|
<Box sx={{ maxHeight: 300, display: "flex", justifyContent: "center", alignItems: "center" }}>
|
||||||
<StatusPieChart counters={status.packages} />
|
<StatusPieChart counters={status.packages} />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -47,10 +47,14 @@ export default function KeyImportDialog({ open, onClose }: KeyImportDialogProps)
|
|||||||
const [server, setServer] = useState("keyserver.ubuntu.com");
|
const [server, setServer] = useState("keyserver.ubuntu.com");
|
||||||
const [keyBody, setKeyBody] = useState("");
|
const [keyBody, setKeyBody] = useState("");
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const resetFields = (): void => {
|
||||||
setFingerprint("");
|
setFingerprint("");
|
||||||
setServer("keyserver.ubuntu.com");
|
setServer("keyserver.ubuntu.com");
|
||||||
setKeyBody("");
|
setKeyBody("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (): void => {
|
||||||
|
resetFields();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +63,7 @@ export default function KeyImportDialog({ open, onClose }: KeyImportDialogProps)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await client.service.servicePGPFetch(fingerprint, server);
|
const result = await client.servicePGPFetch(fingerprint, server);
|
||||||
setKeyBody(result.key);
|
setKeyBody(result.key);
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const detail = ApiError.errorDetail(exception);
|
const detail = ApiError.errorDetail(exception);
|
||||||
@@ -72,7 +76,7 @@ export default function KeyImportDialog({ open, onClose }: KeyImportDialogProps)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await client.service.servicePGPImport({ key: fingerprint, server });
|
await client.servicePGPImport({ key: fingerprint, server });
|
||||||
handleClose();
|
handleClose();
|
||||||
showSuccess("Success", `Key ${fingerprint} has been imported`);
|
showSuccess("Success", `Key ${fingerprint} has been imported`);
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import { useDebounce } from "hooks/useDebounce";
|
|||||||
import { useNotification } from "hooks/useNotification";
|
import { useNotification } from "hooks/useNotification";
|
||||||
import { useSelectedRepository } from "hooks/useSelectedRepository";
|
import { useSelectedRepository } from "hooks/useSelectedRepository";
|
||||||
import type { AURPackage } from "models/AURPackage";
|
import type { AURPackage } from "models/AURPackage";
|
||||||
import type { PackageActionRequest } from "models/PackageActionRequest";
|
|
||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
|
|
||||||
interface EnvironmentVariable {
|
interface EnvironmentVariable {
|
||||||
@@ -78,11 +77,11 @@ export default function PackageAddDialog({ open, onClose }: PackageAddDialogProp
|
|||||||
|
|
||||||
const { data: searchResults = [] } = useQuery<AURPackage[]>({
|
const { data: searchResults = [] } = useQuery<AURPackage[]>({
|
||||||
queryKey: QueryKeys.search(debouncedSearch),
|
queryKey: QueryKeys.search(debouncedSearch),
|
||||||
queryFn: () => client.service.servicePackageSearch(debouncedSearch),
|
queryFn: () => client.servicePackageSearch(debouncedSearch),
|
||||||
enabled: debouncedSearch.length >= 3,
|
enabled: debouncedSearch.length >= 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async (action: "add" | "request"): Promise<void> => {
|
const handleAdd: () => Promise<void> = async () => {
|
||||||
if (!packageName) {
|
if (!packageName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -92,18 +91,38 @@ export default function PackageAddDialog({ open, onClose }: PackageAddDialogProp
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const patches = environmentVariables.filter(variable => variable.key);
|
const patches = environmentVariables.filter(variable => variable.key);
|
||||||
const request: PackageActionRequest = { packages: [packageName], patches };
|
await client.servicePackageAdd(repository, {
|
||||||
if (action === "add") {
|
packages: [packageName],
|
||||||
request.refresh = refreshDatabase;
|
patches,
|
||||||
await client.service.servicePackageAdd(repository, request);
|
refresh: refreshDatabase,
|
||||||
} else {
|
});
|
||||||
await client.service.servicePackageRequest(repository, request);
|
|
||||||
}
|
|
||||||
handleClose();
|
handleClose();
|
||||||
showSuccess("Success", `Packages ${packageName} have been ${action === "add" ? "added" : "requested"}`);
|
showSuccess("Success", `Packages ${packageName} have been added`);
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
const detail = ApiError.errorDetail(exception);
|
const detail = ApiError.errorDetail(exception);
|
||||||
showError("Action failed", `Package ${action} failed: ${detail}`);
|
showError("Action failed", `Package addition failed: ${detail}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRequest: () => Promise<void> = async () => {
|
||||||
|
if (!packageName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const repository = repositorySelect.selectedRepository;
|
||||||
|
if (!repository) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const patches = environmentVariables.filter(variable => variable.key);
|
||||||
|
await client.servicePackageRequest(repository, {
|
||||||
|
packages: [packageName],
|
||||||
|
patches,
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
showSuccess("Success", `Packages ${packageName} have been requested`);
|
||||||
|
} catch (exception) {
|
||||||
|
const detail = ApiError.errorDetail(exception);
|
||||||
|
showError("Action failed", `Package request failed: ${detail}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -186,8 +205,8 @@ export default function PackageAddDialog({ open, onClose }: PackageAddDialogProp
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => void handleSubmit("add")} variant="contained" startIcon={<PlayArrowIcon />}>add</Button>
|
<Button onClick={() => void handleAdd()} variant="contained" startIcon={<PlayArrowIcon />}>add</Button>
|
||||||
<Button onClick={() => void handleSubmit("request")} variant="contained" color="success" startIcon={<AddIcon />}>request</Button>
|
<Button onClick={() => void handleRequest()} variant="contained" color="success" startIcon={<AddIcon />}>request</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>;
|
</Dialog>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,6 @@ export default function PackageInfoDialog({
|
|||||||
const { showSuccess, showError } = useNotification();
|
const { showSuccess, showError } = useNotification();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [localPackageBase, setLocalPackageBase] = useState(packageBase);
|
|
||||||
if (packageBase !== null && packageBase !== localPackageBase) {
|
|
||||||
setLocalPackageBase(packageBase);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
const [refreshDatabase, setRefreshDatabase] = useState(true);
|
const [refreshDatabase, setRefreshDatabase] = useState(true);
|
||||||
|
|
||||||
@@ -77,22 +72,21 @@ export default function PackageInfoDialog({
|
|||||||
const autoRefresh = useAutoRefresh("package-info-autoreload-button", defaultInterval(autoRefreshIntervals));
|
const autoRefresh = useAutoRefresh("package-info-autoreload-button", defaultInterval(autoRefreshIntervals));
|
||||||
|
|
||||||
const { data: packageData } = useQuery<PackageStatus[]>({
|
const { data: packageData } = useQuery<PackageStatus[]>({
|
||||||
queryKey: localPackageBase && current ? QueryKeys.package(localPackageBase, current) : ["packages"],
|
queryKey: packageBase && current ? QueryKeys.package(packageBase, current) : ["packages"],
|
||||||
queryFn: localPackageBase && current ? () => client.fetch.fetchPackage(localPackageBase, current) : skipToken,
|
queryFn: packageBase && current ? () => client.fetchPackage(packageBase, current) : skipToken,
|
||||||
enabled: open,
|
enabled: open,
|
||||||
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: dependencies } = useQuery<Dependencies>({
|
const { data: dependencies } = useQuery<Dependencies>({
|
||||||
queryKey: localPackageBase && current ? QueryKeys.dependencies(localPackageBase, current) : ["dependencies"],
|
queryKey: packageBase && current ? QueryKeys.dependencies(packageBase, current) : ["dependencies"],
|
||||||
queryFn: localPackageBase && current
|
queryFn: packageBase && current ? () => client.fetchPackageDependencies(packageBase, current) : skipToken,
|
||||||
? () => client.fetch.fetchPackageDependencies(localPackageBase, current) : skipToken,
|
|
||||||
enabled: open,
|
enabled: open,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: patches = [] } = useQuery<Patch[]>({
|
const { data: patches = [] } = useQuery<Patch[]>({
|
||||||
queryKey: localPackageBase ? QueryKeys.patches(localPackageBase) : ["patches"],
|
queryKey: packageBase ? QueryKeys.patches(packageBase) : ["patches"],
|
||||||
queryFn: localPackageBase ? () => client.fetch.fetchPackagePatches(localPackageBase) : skipToken,
|
queryFn: packageBase ? () => client.fetchPackagePatches(packageBase) : skipToken,
|
||||||
enabled: open,
|
enabled: open,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,24 +96,24 @@ export default function PackageInfoDialog({
|
|||||||
const headerStyle = status ? StatusHeaderStyles[status.status] : {};
|
const headerStyle = status ? StatusHeaderStyles[status.status] : {};
|
||||||
|
|
||||||
const handleUpdate: () => Promise<void> = async () => {
|
const handleUpdate: () => Promise<void> = async () => {
|
||||||
if (!localPackageBase || !current) {
|
if (!packageBase || !current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await client.service.servicePackageAdd(current, { packages: [localPackageBase], refresh: refreshDatabase });
|
await client.servicePackageAdd(current, { packages: [packageBase], refresh: refreshDatabase });
|
||||||
showSuccess("Success", `Run update for packages ${localPackageBase}`);
|
showSuccess("Success", `Run update for packages ${packageBase}`);
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
showError("Action failed", `Package update failed: ${ApiError.errorDetail(exception)}`);
|
showError("Action failed", `Package update failed: ${ApiError.errorDetail(exception)}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove: () => Promise<void> = async () => {
|
const handleRemove: () => Promise<void> = async () => {
|
||||||
if (!localPackageBase || !current) {
|
if (!packageBase || !current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await client.service.servicePackageRemove(current, [localPackageBase]);
|
await client.servicePackageRemove(current, [packageBase]);
|
||||||
showSuccess("Success", `Packages ${localPackageBase} have been removed`);
|
showSuccess("Success", `Packages ${packageBase} have been removed`);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
showError("Action failed", `Could not remove package: ${ApiError.errorDetail(exception)}`);
|
showError("Action failed", `Could not remove package: ${ApiError.errorDetail(exception)}`);
|
||||||
@@ -127,12 +121,12 @@ export default function PackageInfoDialog({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeletePatch: (key: string) => Promise<void> = async key => {
|
const handleDeletePatch: (key: string) => Promise<void> = async key => {
|
||||||
if (!localPackageBase) {
|
if (!packageBase) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await client.service.servicePackagePatchRemove(localPackageBase, key);
|
await client.servicePackagePatchRemove(packageBase, key);
|
||||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.patches(localPackageBase) });
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.patches(packageBase) });
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
showError("Action failed", `Could not delete variable: ${ApiError.errorDetail(exception)}`);
|
showError("Action failed", `Could not delete variable: ${ApiError.errorDetail(exception)}`);
|
||||||
}
|
}
|
||||||
@@ -142,7 +136,7 @@ export default function PackageInfoDialog({
|
|||||||
<DialogHeader onClose={handleClose} sx={headerStyle}>
|
<DialogHeader onClose={handleClose} sx={headerStyle}>
|
||||||
{pkg && status
|
{pkg && status
|
||||||
? `${pkg.base} ${status.status} at ${new Date(status.timestamp * 1000).toISOStringShort()}`
|
? `${pkg.base} ${status.status} at ${new Date(status.timestamp * 1000).toISOStringShort()}`
|
||||||
: localPackageBase ?? ""}
|
: packageBase ?? ""}
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -163,18 +157,18 @@ export default function PackageInfoDialog({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{tabIndex === 0 && localPackageBase && current &&
|
{tabIndex === 0 && packageBase && current &&
|
||||||
<BuildLogsTab
|
<BuildLogsTab
|
||||||
packageBase={localPackageBase}
|
packageBase={packageBase}
|
||||||
repository={current}
|
repository={current}
|
||||||
refreshInterval={autoRefresh.interval}
|
refreshInterval={autoRefresh.interval}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{tabIndex === 1 && localPackageBase && current &&
|
{tabIndex === 1 && packageBase && current &&
|
||||||
<ChangesTab packageBase={localPackageBase} repository={current} />
|
<ChangesTab packageBase={packageBase} repository={current} />
|
||||||
}
|
}
|
||||||
{tabIndex === 2 && localPackageBase && current &&
|
{tabIndex === 2 && packageBase && current &&
|
||||||
<EventsTab packageBase={localPackageBase} repository={current} />
|
<EventsTab packageBase={packageBase} repository={current} />
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default function PackageRebuildDialog({ open, onClose }: PackageRebuildDi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await client.service.serviceRebuild(repository, [dependency]);
|
await client.serviceRebuild(repository, [dependency]);
|
||||||
handleClose();
|
handleClose();
|
||||||
showSuccess("Success", `Repository rebuild has been run for packages which depend on ${dependency}`);
|
showSuccess("Success", `Repository rebuild has been run for packages which depend on ${dependency}`);
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import Brightness4Icon from "@mui/icons-material/Brightness4";
|
import { Box, Container } from "@mui/material";
|
||||||
import Brightness7Icon from "@mui/icons-material/Brightness7";
|
|
||||||
import { Box, Container, IconButton, Tooltip } from "@mui/material";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import LoginDialog from "components/dialogs/LoginDialog";
|
import LoginDialog from "components/dialogs/LoginDialog";
|
||||||
import Footer from "components/layout/Footer";
|
import Footer from "components/layout/Footer";
|
||||||
@@ -29,7 +27,6 @@ import { QueryKeys } from "hooks/QueryKeys";
|
|||||||
import { useAuth } from "hooks/useAuth";
|
import { useAuth } from "hooks/useAuth";
|
||||||
import { useClient } from "hooks/useClient";
|
import { useClient } from "hooks/useClient";
|
||||||
import { useRepository } from "hooks/useRepository";
|
import { useRepository } from "hooks/useRepository";
|
||||||
import { useThemeMode } from "hooks/useThemeMode";
|
|
||||||
import type { InfoResponse } from "models/InfoResponse";
|
import type { InfoResponse } from "models/InfoResponse";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
@@ -37,12 +34,11 @@ export default function AppLayout(): React.JSX.Element {
|
|||||||
const client = useClient();
|
const client = useClient();
|
||||||
const { setAuthState } = useAuth();
|
const { setAuthState } = useAuth();
|
||||||
const { setRepositories } = useRepository();
|
const { setRepositories } = useRepository();
|
||||||
const { mode, toggleTheme } = useThemeMode();
|
|
||||||
const [loginOpen, setLoginOpen] = useState(false);
|
const [loginOpen, setLoginOpen] = useState(false);
|
||||||
|
|
||||||
const { data: info } = useQuery<InfoResponse>({
|
const { data: info } = useQuery<InfoResponse>({
|
||||||
queryKey: QueryKeys.info,
|
queryKey: QueryKeys.info,
|
||||||
queryFn: () => client.fetch.fetchServerInfo(),
|
queryFn: () => client.fetchServerInfo(),
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,17 +52,12 @@ export default function AppLayout(): React.JSX.Element {
|
|||||||
|
|
||||||
return <Container maxWidth="xl">
|
return <Container maxWidth="xl">
|
||||||
<Box sx={{ display: "flex", alignItems: "center", py: 1, gap: 1 }}>
|
<Box sx={{ display: "flex", alignItems: "center", py: 1, gap: 1 }}>
|
||||||
<a href="https://ahriman.readthedocs.io/" title="logo">
|
<a href="https://github.com/arcan1s/ahriman" title="logo">
|
||||||
<img src="/static/logo.svg" width={30} height={30} alt="" />
|
<img src="/static/logo.svg" width={30} height={30} alt="" />
|
||||||
</a>
|
</a>
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
</Box>
|
</Box>
|
||||||
<Tooltip title="Toggle theme">
|
|
||||||
<IconButton aria-label="Toggle theme" onClick={toggleTheme}>
|
|
||||||
{mode === "dark" ? <Brightness7Icon /> : <Brightness4Icon />}
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<PackageTable
|
<PackageTable
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default function BuildLogsTab({
|
|||||||
|
|
||||||
const { data: allLogs } = useQuery<LogRecord[]>({
|
const { data: allLogs } = useQuery<LogRecord[]>({
|
||||||
queryKey: QueryKeys.logs(packageBase, repository),
|
queryKey: QueryKeys.logs(packageBase, repository),
|
||||||
queryFn: () => client.fetch.fetchPackageLogs(packageBase, repository),
|
queryFn: () => client.fetchPackageLogs(packageBase, repository),
|
||||||
enabled: !!packageBase,
|
enabled: !!packageBase,
|
||||||
refetchInterval: refreshInterval > 0 ? refreshInterval : false,
|
refetchInterval: refreshInterval > 0 ? refreshInterval : false,
|
||||||
});
|
});
|
||||||
@@ -112,9 +112,7 @@ export default function BuildLogsTab({
|
|||||||
const { data: versionLogs } = useQuery<LogRecord[]>({
|
const { data: versionLogs } = useQuery<LogRecord[]>({
|
||||||
queryKey: QueryKeys.logsVersion(packageBase, repository, activeVersion?.version ?? "", activeVersion?.processId ?? ""),
|
queryKey: QueryKeys.logsVersion(packageBase, repository, activeVersion?.version ?? "", activeVersion?.processId ?? ""),
|
||||||
queryFn: activeVersion
|
queryFn: activeVersion
|
||||||
? () => client.fetch.fetchPackageLogs(
|
? () => client.fetchPackageLogs(packageBase, repository, activeVersion.version, activeVersion.processId)
|
||||||
packageBase, repository, activeVersion.version, activeVersion.processId,
|
|
||||||
)
|
|
||||||
: skipToken,
|
: skipToken,
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
refetchInterval: refreshInterval > 0 ? refreshInterval : false,
|
refetchInterval: refreshInterval > 0 ? refreshInterval : false,
|
||||||
|
|||||||
@@ -17,20 +17,20 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
import "highlight.js/styles/github.css";
|
||||||
|
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import CopyButton from "components/common/CopyButton";
|
import CodeBlock from "components/common/CodeBlock";
|
||||||
|
import hljs from "highlight.js/lib/core";
|
||||||
|
import diff from "highlight.js/lib/languages/diff";
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
import { QueryKeys } from "hooks/QueryKeys";
|
||||||
import { useClient } from "hooks/useClient";
|
import { useClient } from "hooks/useClient";
|
||||||
import { useThemeMode } from "hooks/useThemeMode";
|
|
||||||
import type { Changes } from "models/Changes";
|
import type { Changes } from "models/Changes";
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
import type { RepositoryId } from "models/RepositoryId";
|
||||||
import React from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
||||||
import diff from "react-syntax-highlighter/dist/esm/languages/hljs/diff";
|
|
||||||
import { githubGist, vs2015 } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
|
||||||
|
|
||||||
SyntaxHighlighter.registerLanguage("diff", diff);
|
hljs.registerLanguage("diff", diff);
|
||||||
|
|
||||||
interface ChangesTabProps {
|
interface ChangesTabProps {
|
||||||
packageBase: string;
|
packageBase: string;
|
||||||
@@ -39,34 +39,23 @@ interface ChangesTabProps {
|
|||||||
|
|
||||||
export default function ChangesTab({ packageBase, repository }: ChangesTabProps): React.JSX.Element {
|
export default function ChangesTab({ packageBase, repository }: ChangesTabProps): React.JSX.Element {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const { mode } = useThemeMode();
|
const codeRef = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
const { data } = useQuery<Changes>({
|
const { data } = useQuery<Changes>({
|
||||||
queryKey: QueryKeys.changes(packageBase, repository),
|
queryKey: QueryKeys.changes(packageBase, repository),
|
||||||
queryFn: () => client.fetch.fetchPackageChanges(packageBase, repository),
|
queryFn: () => client.fetchPackageChanges(packageBase, repository),
|
||||||
enabled: !!packageBase,
|
enabled: !!packageBase,
|
||||||
});
|
});
|
||||||
|
|
||||||
const changesText = data?.changes ?? "";
|
const changesText = data?.changes ?? "";
|
||||||
|
|
||||||
return <Box sx={{ position: "relative", mt: 1 }}>
|
useEffect(() => {
|
||||||
<SyntaxHighlighter
|
if (codeRef.current) {
|
||||||
language="diff"
|
codeRef.current.innerHTML = hljs.highlight(changesText, { language: "diff" }).value;
|
||||||
style={mode === "dark" ? vs2015 : githubGist}
|
}
|
||||||
customStyle={{
|
}, [changesText]);
|
||||||
padding: "16px",
|
|
||||||
borderRadius: "4px",
|
return <Box sx={{ mt: 1 }}>
|
||||||
overflow: "auto",
|
<CodeBlock codeRef={codeRef} className="language-diff" getText={() => changesText} height={400} />
|
||||||
height: 400,
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
fontFamily: "monospace",
|
|
||||||
margin: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{changesText}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
|
|
||||||
<CopyButton getText={() => changesText} />
|
|
||||||
</Box>
|
|
||||||
</Box>;
|
</Box>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import { useClient } from "hooks/useClient";
|
|||||||
import type { Event } from "models/Event";
|
import type { Event } from "models/Event";
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
import type { RepositoryId } from "models/RepositoryId";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
interface EventsTabProps {
|
interface EventsTabProps {
|
||||||
packageBase: string;
|
packageBase: string;
|
||||||
@@ -51,16 +50,16 @@ export default function EventsTab({ packageBase, repository }: EventsTabProps):
|
|||||||
|
|
||||||
const { data: events = [] } = useQuery<Event[]>({
|
const { data: events = [] } = useQuery<Event[]>({
|
||||||
queryKey: QueryKeys.events(repository, packageBase),
|
queryKey: QueryKeys.events(repository, packageBase),
|
||||||
queryFn: () => client.fetch.fetchPackageEvents(repository, packageBase, 30),
|
queryFn: () => client.fetchPackageEvents(repository, packageBase, 30),
|
||||||
enabled: !!packageBase,
|
enabled: !!packageBase,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rows = useMemo<EventRow[]>(() => events.map((event, index) => ({
|
const rows: EventRow[] = events.map((event, index) => ({
|
||||||
id: index,
|
id: index,
|
||||||
timestamp: new Date(event.created * 1000).toISOStringShort(),
|
timestamp: new Date(event.created * 1000).toISOStringShort(),
|
||||||
event: event.event,
|
event: event.event,
|
||||||
message: event.message ?? "",
|
message: event.message ?? "",
|
||||||
})), [events]);
|
}));
|
||||||
|
|
||||||
return <Box sx={{ mt: 1 }}>
|
return <Box sx={{ mt: 1 }}>
|
||||||
<EventDurationLineChart events={events} />
|
<EventDurationLineChart events={events} />
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { Grid, Link, Typography } from "@mui/material";
|
import { Grid, Link, Typography } from "@mui/material";
|
||||||
|
import StringList from "components/common/StringList";
|
||||||
import type { Dependencies } from "models/Dependencies";
|
import type { Dependencies } from "models/Dependencies";
|
||||||
import type { Package } from "models/Package";
|
import type { Package } from "models/Package";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@@ -66,7 +67,7 @@ export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetails
|
|||||||
return <>
|
return <>
|
||||||
<Grid container spacing={1} sx={{ mt: 1 }}>
|
<Grid container spacing={1} sx={{ mt: 1 }}>
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">packages</Typography></Grid>
|
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">packages</Typography></Grid>
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}>{packagesList.unique().join("\n")}</Typography></Grid>
|
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={packagesList.unique()} /></Typography></Grid>
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">version</Typography></Grid>
|
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">version</Typography></Grid>
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2">{pkg.version}</Typography></Grid>
|
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2">{pkg.version}</Typography></Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -80,16 +81,16 @@ export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetails
|
|||||||
|
|
||||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">groups</Typography></Grid>
|
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">groups</Typography></Grid>
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}>{groups.unique().join("\n")}</Typography></Grid>
|
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={groups.unique()} /></Typography></Grid>
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">licenses</Typography></Grid>
|
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">licenses</Typography></Grid>
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}>{licenses.unique().join("\n")}</Typography></Grid>
|
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={licenses.unique()} /></Typography></Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">upstream</Typography></Grid>
|
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">upstream</Typography></Grid>
|
||||||
<Grid size={{ xs: 8, md: 5 }}>
|
<Grid size={{ xs: 8, md: 5 }}>
|
||||||
{upstreamUrls.map(url =>
|
{upstreamUrls.map(url =>
|
||||||
<Link key={url} href={url} target="_blank" rel="noopener noreferrer" underline="hover" display="block" variant="body2">
|
<Link key={url} href={url} target="_blank" rel="noopener" underline="hover" display="block" variant="body2">
|
||||||
{url}
|
{url}
|
||||||
</Link>,
|
</Link>,
|
||||||
)}
|
)}
|
||||||
@@ -98,7 +99,7 @@ export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetails
|
|||||||
<Grid size={{ xs: 8, md: 5 }}>
|
<Grid size={{ xs: 8, md: 5 }}>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
{aurUrl &&
|
{aurUrl &&
|
||||||
<Link href={aurUrl} target="_blank" rel="noopener noreferrer" underline="hover">AUR link</Link>
|
<Link href={aurUrl} target="_blank" rel="noopener" underline="hover">AUR link</Link>
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -106,9 +107,9 @@ export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetails
|
|||||||
|
|
||||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">depends</Typography></Grid>
|
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">depends</Typography></Grid>
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}>{allDepends.join("\n")}</Typography></Grid>
|
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={allDepends} /></Typography></Grid>
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">implicitly depends</Typography></Grid>
|
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">implicitly depends</Typography></Grid>
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}>{implicitDepends.unique().join("\n")}</Typography></Grid>
|
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={implicitDepends.unique()} /></Typography></Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function PackagePatchesList({
|
|||||||
sx={{ flex: 1 }}
|
sx={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
{editable &&
|
{editable &&
|
||||||
<IconButton size="small" color="error" aria-label="Remove patch" onClick={() => onDelete(patch.key)}>
|
<IconButton size="small" color="error" onClick={() => onDelete(patch.key)}>
|
||||||
<DeleteIcon fontSize="small" />
|
<DeleteIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
type GridRowId,
|
type GridRowId,
|
||||||
useGridApiRef,
|
useGridApiRef,
|
||||||
} from "@mui/x-data-grid";
|
} from "@mui/x-data-grid";
|
||||||
|
import StringList from "components/common/StringList";
|
||||||
import DashboardDialog from "components/dialogs/DashboardDialog";
|
import DashboardDialog from "components/dialogs/DashboardDialog";
|
||||||
import KeyImportDialog from "components/dialogs/KeyImportDialog";
|
import KeyImportDialog from "components/dialogs/KeyImportDialog";
|
||||||
import PackageAddDialog from "components/dialogs/PackageAddDialog";
|
import PackageAddDialog from "components/dialogs/PackageAddDialog";
|
||||||
@@ -57,7 +58,7 @@ function createListColumn(
|
|||||||
...options,
|
...options,
|
||||||
valueGetter: (value: string[]) => (value ?? []).join(" "),
|
valueGetter: (value: string[]) => (value ?? []).join(" "),
|
||||||
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
||||||
<Box sx={{ whiteSpace: "pre-line" }}>{((params.row[field] as string[]) ?? []).join("\n")}</Box>,
|
<Box sx={{ whiteSpace: "pre-line" }}><StringList items={(params.row[field] as string[]) ?? []} /></Box>,
|
||||||
sortComparator: (left: string, right: string) => left.localeCompare(right),
|
sortComparator: (left: string, right: string) => left.localeCompare(right),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -84,7 +85,7 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
|||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
||||||
params.row.webUrl ?
|
params.row.webUrl ?
|
||||||
<Link href={params.row.webUrl} target="_blank" rel="noopener noreferrer" underline="hover">
|
<Link href={params.row.webUrl} target="_blank" rel="noopener" underline="hover">
|
||||||
{params.value as string}
|
{params.value as string}
|
||||||
</Link>
|
</Link>
|
||||||
: params.value as string,
|
: params.value as string,
|
||||||
@@ -113,7 +114,7 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
return <Box>
|
||||||
<PackageTableToolbar
|
<PackageTableToolbar
|
||||||
hasSelection={table.selectionModel.length > 0}
|
hasSelection={table.selectionModel.length > 0}
|
||||||
isAuthorized={table.isAuthorized}
|
isAuthorized={table.isAuthorized}
|
||||||
@@ -129,7 +130,7 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
|||||||
onDashboardClick: () => table.setDialogOpen("dashboard"),
|
onDashboardClick: () => table.setDialogOpen("dashboard"),
|
||||||
onAddClick: () => table.setDialogOpen("add"),
|
onAddClick: () => table.setDialogOpen("add"),
|
||||||
onUpdateClick: () => void table.handleUpdate(),
|
onUpdateClick: () => void table.handleUpdate(),
|
||||||
onRefreshDatabaseClick: () => void table.handleRefreshDatabase(),
|
onRefreshDbClick: () => void table.handleRefreshDb(),
|
||||||
onRebuildClick: () => table.setDialogOpen("rebuild"),
|
onRebuildClick: () => table.setDialogOpen("rebuild"),
|
||||||
onRemoveClick: () => void table.handleRemove(),
|
onRemoveClick: () => void table.handleRemove(),
|
||||||
onKeyImportClick: () => table.setDialogOpen("keyImport"),
|
onKeyImportClick: () => table.setDialogOpen("keyImport"),
|
||||||
@@ -176,7 +177,7 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
|||||||
table.setSelectedPackage(String(params.id));
|
table.setSelectedPackage(String(params.id));
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
flex: 1,
|
height: 500,
|
||||||
"& .MuiDataGrid-row": { cursor: "pointer" },
|
"& .MuiDataGrid-row": { cursor: "pointer" },
|
||||||
}}
|
}}
|
||||||
density="compact"
|
density="compact"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export interface ToolbarActions {
|
|||||||
onDashboardClick: () => void;
|
onDashboardClick: () => void;
|
||||||
onAddClick: () => void;
|
onAddClick: () => void;
|
||||||
onUpdateClick: () => void;
|
onUpdateClick: () => void;
|
||||||
onRefreshDatabaseClick: () => void;
|
onRefreshDbClick: () => void;
|
||||||
onRebuildClick: () => void;
|
onRebuildClick: () => void;
|
||||||
onRemoveClick: () => void;
|
onRemoveClick: () => void;
|
||||||
onKeyImportClick: () => void;
|
onKeyImportClick: () => void;
|
||||||
@@ -116,7 +116,7 @@ export default function PackageTableToolbar({
|
|||||||
<PlayArrowIcon fontSize="small" sx={{ mr: 1 }} /> update
|
<PlayArrowIcon fontSize="small" sx={{ mr: 1 }} /> update
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => {
|
<MenuItem onClick={() => {
|
||||||
setPackagesAnchorEl(null); actions.onRefreshDatabaseClick();
|
setPackagesAnchorEl(null); actions.onRefreshDbClick();
|
||||||
}}>
|
}}>
|
||||||
<DownloadIcon fontSize="small" sx={{ mr: 1 }} /> update pacman databases
|
<DownloadIcon fontSize="small" sx={{ mr: 1 }} /> update pacman databases
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@@ -17,41 +17,33 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import { AuthContext } from "contexts/AuthContext";
|
import { AuthContext } from "contexts/AuthContext";
|
||||||
import { useClient } from "hooks/useClient";
|
import { useClient } from "hooks/useClient";
|
||||||
import { useNotification } from "hooks/useNotification";
|
|
||||||
import React, { type ReactNode, useCallback, useMemo, useState } from "react";
|
import React, { type ReactNode, useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
export function AuthProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const [authState, setAuthState] = useState({ enabled: true, username: null as string | null });
|
const [state, setState] = useState({ enabled: true, username: null as string | null });
|
||||||
const { showError } = useNotification();
|
|
||||||
|
|
||||||
const login = useCallback(async (username: string, password: string) => {
|
const login = useCallback(async (username: string, password: string) => {
|
||||||
await client.login({ username, password });
|
await client.login({ username, password });
|
||||||
setAuthState(prev => ({ ...prev, username }));
|
setState(prev => ({ ...prev, username }));
|
||||||
}, [client]);
|
}, [client]);
|
||||||
|
|
||||||
const logout = useCallback(async () => {
|
const doLogout = useCallback(async () => {
|
||||||
try {
|
await client.logout();
|
||||||
await client.logout();
|
setState(prev => ({ ...prev, username: null }));
|
||||||
setAuthState(prev => ({ ...prev, username: null }));
|
}, [client]);
|
||||||
} catch (exception) {
|
|
||||||
const detail = ApiError.errorDetail(exception);
|
|
||||||
showError("Login error", `Could not log out: ${detail}`);
|
|
||||||
}
|
|
||||||
}, [client, showError]);
|
|
||||||
|
|
||||||
const isAuthorized = useMemo(() =>
|
const isAuthorized = useMemo(() => !state.enabled || state.username !== null, [state.enabled, state.username]);
|
||||||
!authState.enabled || authState.username !== null, [authState.enabled, authState.username],
|
|
||||||
);
|
|
||||||
|
|
||||||
const value = useMemo(() => ({
|
const value = useMemo(() => ({
|
||||||
...authState, isAuthorized, setAuthState, login, logout,
|
...state, isAuthorized, setAuthState: setState, login, logout: doLogout,
|
||||||
}), [authState, isAuthorized, login, logout]);
|
}), [state, isAuthorized, login, doLogout]);
|
||||||
|
|
||||||
return <AuthContext.Provider value={value}>
|
return (
|
||||||
{children}
|
<AuthContext.Provider value={value}>
|
||||||
</AuthContext.Provider>;
|
{children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import type { AlertColor } from "@mui/material";
|
import type { AlertColor } from "@mui/material";
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
id: string;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
message: string;
|
message: string;
|
||||||
severity: AlertColor;
|
severity: AlertColor;
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { Alert, Slide } from "@mui/material";
|
import { Alert, Slide } from "@mui/material";
|
||||||
import type { Notification } from "models/Notification";
|
import type { Notification } from "contexts/Notification";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface NotificationItemProps {
|
interface NotificationItemProps {
|
||||||
notification: Notification;
|
notification: Notification;
|
||||||
onClose: (id: string) => void;
|
onClose: (id: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NotificationItem({ notification, onClose }: NotificationItemProps): React.JSX.Element {
|
export default function NotificationItem({ notification, onClose }: NotificationItemProps): React.JSX.Element {
|
||||||
@@ -18,26 +18,22 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { type AlertColor, Box } from "@mui/material";
|
import { type AlertColor, Box } from "@mui/material";
|
||||||
import NotificationItem from "components/common/NotificationItem";
|
import type { Notification } from "contexts/Notification";
|
||||||
import { NotificationContext } from "contexts/NotificationContext";
|
import { NotificationContext } from "contexts/NotificationContext";
|
||||||
import type { Notification } from "models/Notification";
|
import NotificationItem from "contexts/NotificationItem";
|
||||||
import React, { type ReactNode, useCallback, useMemo, useState } from "react";
|
import React, { type ReactNode, useCallback, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
export function NotificationProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
export function NotificationProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
||||||
|
const nextId = useRef(0);
|
||||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||||
|
|
||||||
const addNotification = useCallback((title: string, message: string, severity: AlertColor) => {
|
const addNotification = useCallback((title: string, message: string, severity: AlertColor) => {
|
||||||
const id = `${severity}:${title}:${message}`;
|
const id = nextId.current++;
|
||||||
setNotifications(prev => {
|
setNotifications(prev => [...prev, { id, title, message, severity }]);
|
||||||
if (prev.some(notification => notification.id === id)) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
return [...prev, { id, title, message, severity }];
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const removeNotification = useCallback((key: string) => {
|
const removeNotification = useCallback((id: number) => {
|
||||||
setNotifications(prev => prev.filter(notification => notification.id !== key));
|
setNotifications(prev => prev.filter(notification => notification.id !== id));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const showSuccess = useCallback(
|
const showSuccess = useCallback(
|
||||||
@@ -51,26 +47,28 @@ export function NotificationProvider({ children }: { children: ReactNode }): Rea
|
|||||||
|
|
||||||
const value = useMemo(() => ({ showSuccess, showError }), [showSuccess, showError]);
|
const value = useMemo(() => ({ showSuccess, showError }), [showSuccess, showError]);
|
||||||
|
|
||||||
return <NotificationContext.Provider value={value}>
|
return (
|
||||||
{children}
|
<NotificationContext.Provider value={value}>
|
||||||
<Box
|
{children}
|
||||||
sx={{
|
<Box
|
||||||
position: "fixed",
|
sx={{
|
||||||
top: 16,
|
position: "fixed",
|
||||||
left: "50%",
|
top: 16,
|
||||||
transform: "translateX(-50%)",
|
left: "50%",
|
||||||
zIndex: theme => theme.zIndex.snackbar,
|
transform: "translateX(-50%)",
|
||||||
display: "flex",
|
zIndex: theme => theme.zIndex.snackbar,
|
||||||
flexDirection: "column",
|
display: "flex",
|
||||||
gap: 1,
|
flexDirection: "column",
|
||||||
maxWidth: 500,
|
gap: 1,
|
||||||
width: "100%",
|
maxWidth: 500,
|
||||||
pointerEvents: "none",
|
width: "100%",
|
||||||
}}
|
pointerEvents: "none",
|
||||||
>
|
}}
|
||||||
{notifications.map(notification =>
|
>
|
||||||
<NotificationItem key={notification.id} notification={notification} onClose={removeNotification} />,
|
{notifications.map(notification =>
|
||||||
)}
|
<NotificationItem key={notification.id} notification={notification} onClose={removeNotification} />,
|
||||||
</Box>
|
)}
|
||||||
</NotificationContext.Provider>;
|
</Box>
|
||||||
|
</NotificationContext.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
import CssBaseline from "@mui/material/CssBaseline";
|
|
||||||
import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
|
|
||||||
import { defaults as chartDefaults } from "chart.js";
|
|
||||||
import { ThemeContext } from "contexts/ThemeContext";
|
|
||||||
import { useLocalStorage } from "hooks/useLocalStorage";
|
|
||||||
import React, { useCallback, useEffect, useMemo } from "react";
|
|
||||||
import { createAppTheme } from "theme/Theme";
|
|
||||||
|
|
||||||
function systemPreference(): "light" | "dark" {
|
|
||||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ThemeProvider({ children }: { children: React.ReactNode }): React.JSX.Element {
|
|
||||||
const [mode, setMode] = useLocalStorage<"light" | "dark">("theme-mode", systemPreference());
|
|
||||||
|
|
||||||
const toggleTheme = useCallback(() => {
|
|
||||||
setMode(prev => prev === "light" ? "dark" : "light");
|
|
||||||
}, [setMode]);
|
|
||||||
|
|
||||||
const theme = useMemo(() => createAppTheme(mode), [mode]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const textColor = mode === "dark" ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.7)";
|
|
||||||
const gridColor = mode === "dark" ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
||||||
chartDefaults.color = textColor;
|
|
||||||
chartDefaults.borderColor = gridColor;
|
|
||||||
}, [mode]);
|
|
||||||
|
|
||||||
const value = useMemo(() => ({ mode, toggleTheme }), [mode, toggleTheme]);
|
|
||||||
|
|
||||||
return <ThemeContext.Provider value={value}>
|
|
||||||
<MuiThemeProvider theme={theme}>
|
|
||||||
<CssBaseline />
|
|
||||||
{children}
|
|
||||||
</MuiThemeProvider>
|
|
||||||
</ThemeContext.Provider>;
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { type RefObject, useCallback, useRef } from "react";
|
import { type RefObject, useRef } from "react";
|
||||||
|
|
||||||
interface UseAutoScrollResult {
|
interface UseAutoScrollResult {
|
||||||
preRef: RefObject<HTMLElement | null>;
|
preRef: RefObject<HTMLElement | null>;
|
||||||
@@ -31,19 +31,19 @@ export function useAutoScroll(): UseAutoScrollResult {
|
|||||||
const initialScrollDone = useRef(false);
|
const initialScrollDone = useRef(false);
|
||||||
const wasAtBottom = useRef(true);
|
const wasAtBottom = useRef(true);
|
||||||
|
|
||||||
const handleScroll = useCallback((): void => {
|
const handleScroll: () => void = () => {
|
||||||
if (preRef.current) {
|
if (preRef.current) {
|
||||||
const element = preRef.current;
|
const element = preRef.current;
|
||||||
wasAtBottom.current = element.scrollTop + element.clientHeight >= element.scrollHeight - 50;
|
wasAtBottom.current = element.scrollTop + element.clientHeight >= element.scrollHeight - 50;
|
||||||
}
|
}
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const resetScroll = useCallback((): void => {
|
const resetScroll: () => void = () => {
|
||||||
initialScrollDone.current = false;
|
initialScrollDone.current = false;
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
// scroll to bottom on initial load, then only if already near bottom and no active text selection
|
// scroll to bottom on initial load, then only if already near bottom and no active text selection
|
||||||
const scrollToBottom = useCallback((): void => {
|
const scrollToBottom: () => void = () => {
|
||||||
if (!preRef.current) {
|
if (!preRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ export function useAutoScroll(): UseAutoScrollResult {
|
|||||||
element.scrollTop = element.scrollHeight;
|
element.scrollTop = element.scrollHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
return { preRef, handleScroll, scrollToBottom, resetScroll };
|
return { preRef, handleScroll, scrollToBottom, resetScroll };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { type Context, useContext } from "react";
|
|||||||
|
|
||||||
export function useContextNotNull<T>(context: Context<T | null>): T {
|
export function useContextNotNull<T>(context: Context<T | null>): T {
|
||||||
const ctx = useContext(context);
|
const ctx = useContext(context);
|
||||||
if (ctx === null) {
|
if (!ctx) {
|
||||||
throw new Error("must be used within a Provider");
|
throw new Error("must be used within a Provider");
|
||||||
}
|
}
|
||||||
return ctx;
|
return ctx;
|
||||||
|
|||||||
@@ -23,12 +23,11 @@ import { QueryKeys } from "hooks/QueryKeys";
|
|||||||
import { useClient } from "hooks/useClient";
|
import { useClient } from "hooks/useClient";
|
||||||
import { useNotification } from "hooks/useNotification";
|
import { useNotification } from "hooks/useNotification";
|
||||||
import { useRepository } from "hooks/useRepository";
|
import { useRepository } from "hooks/useRepository";
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
|
|
||||||
export interface UsePackageActionsResult {
|
export interface UsePackageActionsResult {
|
||||||
handleReload: () => void;
|
handleReload: () => void;
|
||||||
handleUpdate: () => Promise<void>;
|
handleUpdate: () => Promise<void>;
|
||||||
handleRefreshDatabase: () => Promise<void>;
|
handleRefreshDb: () => Promise<void>;
|
||||||
handleRemove: () => Promise<void>;
|
handleRemove: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,68 +40,80 @@ export function usePackageActions(
|
|||||||
const { showSuccess, showError } = useNotification();
|
const { showSuccess, showError } = useNotification();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const invalidate = (repository: RepositoryId): void => {
|
const handleReload: () => void = () => {
|
||||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.packages(repository) });
|
if (!current) {
|
||||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.status(repository) });
|
return;
|
||||||
|
}
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.packages(current) });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.status(current) });
|
||||||
};
|
};
|
||||||
|
|
||||||
const performAction = async (
|
const handleUpdate: () => Promise<void> = async () => {
|
||||||
action: (repository: RepositoryId) => Promise<string>,
|
|
||||||
errorMessage: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const successMessage = await action(current);
|
if (selectionModel.length === 0) {
|
||||||
showSuccess("Success", successMessage);
|
await client.servicePackageUpdate(current, { packages: [] });
|
||||||
invalidate(current);
|
showSuccess("Success", "Repository update has been run");
|
||||||
|
} else {
|
||||||
|
await client.servicePackageAdd(current, { packages: selectionModel });
|
||||||
|
showSuccess("Success", `Run update for packages ${selectionModel.join(", ")}`);
|
||||||
|
}
|
||||||
setSelectionModel([]);
|
setSelectionModel([]);
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.packages(current) });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.status(current) });
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
showError("Action failed", `${errorMessage}: ${ApiError.errorDetail(exception)}`);
|
const detail = ApiError.errorDetail(exception);
|
||||||
|
showError("Action failed", `Packages update failed: ${detail}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReload: () => void = () => {
|
const handleRefreshDb: () => Promise<void> = async () => {
|
||||||
if (current !== null) {
|
if (!current) {
|
||||||
invalidate(current);
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await client.servicePackageUpdate(current, {
|
||||||
|
packages: [],
|
||||||
|
refresh: true,
|
||||||
|
aur: false,
|
||||||
|
local: false,
|
||||||
|
manual: false,
|
||||||
|
});
|
||||||
|
showSuccess("Success", "Pacman database update has been requested");
|
||||||
|
setSelectionModel([]);
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.packages(current) });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.status(current) });
|
||||||
|
} catch (exception) {
|
||||||
|
const detail = ApiError.errorDetail(exception);
|
||||||
|
showError("Action failed", `Could not update pacman databases: ${detail}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = (): Promise<void> => performAction(async (repository): Promise<string> => {
|
const handleRemove: () => Promise<void> = async () => {
|
||||||
if (selectionModel.length === 0) {
|
if (!current) {
|
||||||
await client.service.servicePackageUpdate(repository, { packages: [] });
|
return;
|
||||||
return "Repository update has been run";
|
|
||||||
}
|
}
|
||||||
await client.service.servicePackageAdd(repository, { packages: selectionModel });
|
|
||||||
return `Run update for packages ${selectionModel.join(", ")}`;
|
|
||||||
}, "Packages update failed");
|
|
||||||
|
|
||||||
const handleRefreshDatabase = (): Promise<void> => performAction(async (repository): Promise<string> => {
|
|
||||||
await client.service.servicePackageUpdate(repository, {
|
|
||||||
packages: [],
|
|
||||||
refresh: true,
|
|
||||||
aur: false,
|
|
||||||
local: false,
|
|
||||||
manual: false,
|
|
||||||
});
|
|
||||||
return "Pacman database update has been requested";
|
|
||||||
}, "Could not update pacman databases");
|
|
||||||
|
|
||||||
const handleRemove = (): Promise<void> => {
|
|
||||||
if (selectionModel.length === 0) {
|
if (selectionModel.length === 0) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await client.servicePackageRemove(current, selectionModel);
|
||||||
|
showSuccess("Success", `Packages ${selectionModel.join(", ")} have been removed`);
|
||||||
|
setSelectionModel([]);
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.packages(current) });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: QueryKeys.status(current) });
|
||||||
|
} catch (exception) {
|
||||||
|
const detail = ApiError.errorDetail(exception);
|
||||||
|
showError("Action failed", `Could not remove packages: ${detail}`);
|
||||||
}
|
}
|
||||||
return performAction(async (repository): Promise<string> => {
|
|
||||||
await client.service.servicePackageRemove(repository, selectionModel);
|
|
||||||
return `Packages ${selectionModel.join(", ")} have been removed`;
|
|
||||||
}, "Could not remove packages");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleReload,
|
handleReload,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleRefreshDatabase,
|
handleRefreshDb,
|
||||||
handleRemove,
|
handleRemove,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ export function usePackageData(autoRefreshIntervals: AutoRefreshInterval[]): Use
|
|||||||
|
|
||||||
const { data: packages = [], isLoading } = useQuery({
|
const { data: packages = [], isLoading } = useQuery({
|
||||||
queryKey: current ? QueryKeys.packages(current) : ["packages"],
|
queryKey: current ? QueryKeys.packages(current) : ["packages"],
|
||||||
queryFn: current ? () => client.fetch.fetchPackages(current) : skipToken,
|
queryFn: current ? () => client.fetchPackages(current) : skipToken,
|
||||||
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: status } = useQuery({
|
const { data: status } = useQuery({
|
||||||
queryKey: current ? QueryKeys.status(current) : ["status"],
|
queryKey: current ? QueryKeys.status(current) : ["status"],
|
||||||
queryFn: current ? () => client.fetch.fetchServerStatus(current) : skipToken,
|
queryFn: current ? () => client.fetchServerStatus(current) : skipToken,
|
||||||
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export interface UsePackageTableResult {
|
|||||||
|
|
||||||
handleReload: () => void;
|
handleReload: () => void;
|
||||||
handleUpdate: () => Promise<void>;
|
handleUpdate: () => Promise<void>;
|
||||||
handleRefreshDatabase: () => Promise<void>;
|
handleRefreshDb: () => Promise<void>;
|
||||||
handleRemove: () => Promise<void>;
|
handleRemove: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
import { ThemeContext, type ThemeContextValue } from "contexts/ThemeContext";
|
|
||||||
import { useContextNotNull } from "hooks/useContextNotNull";
|
|
||||||
|
|
||||||
export function useThemeMode(): ThemeContextValue {
|
|
||||||
return useContextNotNull(ThemeContext);
|
|
||||||
}
|
|
||||||
@@ -21,15 +21,15 @@ import { amber, green, grey, orange, red } from "@mui/material/colors";
|
|||||||
import type { BuildStatus } from "models/BuildStatus";
|
import type { BuildStatus } from "models/BuildStatus";
|
||||||
|
|
||||||
const base: Record<BuildStatus, string> = {
|
const base: Record<BuildStatus, string> = {
|
||||||
unknown: grey[600],
|
unknown: grey[800],
|
||||||
pending: amber[700],
|
pending: amber[900],
|
||||||
building: orange[800],
|
building: orange[900],
|
||||||
failed: red[700],
|
failed: red[900],
|
||||||
success: green[700],
|
success: green[800],
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerBase: Record<BuildStatus, string> = {
|
const headerBase: Record<BuildStatus, string> = {
|
||||||
unknown: grey[600],
|
unknown: grey[800],
|
||||||
pending: amber[700],
|
pending: amber[700],
|
||||||
building: orange[600],
|
building: orange[600],
|
||||||
failed: red[500],
|
failed: red[500],
|
||||||
|
|||||||
@@ -17,20 +17,17 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { createTheme, type Theme } from "@mui/material/styles";
|
import { createTheme } from "@mui/material/styles";
|
||||||
|
|
||||||
export function createAppTheme(mode: "light" | "dark"): Theme {
|
const Theme = createTheme({
|
||||||
return createTheme({
|
components: {
|
||||||
palette: {
|
MuiDialog: {
|
||||||
mode,
|
defaultProps: {
|
||||||
},
|
maxWidth: "lg",
|
||||||
components: {
|
fullWidth: true,
|
||||||
MuiDialog: {
|
|
||||||
defaultProps: {
|
|
||||||
maxWidth: "lg",
|
|
||||||
fullWidth: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default Theme;
|
||||||
|
|||||||
@@ -19,6 +19,21 @@
|
|||||||
*/
|
*/
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
||||||
|
|
||||||
|
// https://www.typescriptlang.org/docs/handbook/mixins.html#alternative-pattern
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
|
||||||
|
export function applyMixins(clazz: any, classes: any[]): void {
|
||||||
|
classes.forEach(baseClass => {
|
||||||
|
Object.getOwnPropertyNames(baseClass.prototype).forEach(name => {
|
||||||
|
Object.defineProperty(
|
||||||
|
clazz.prototype,
|
||||||
|
name,
|
||||||
|
Object.getOwnPropertyDescriptor(baseClass.prototype, name) || Object.create(null),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
|
||||||
|
|
||||||
export function defaultInterval(intervals: AutoRefreshInterval[]): number {
|
export function defaultInterval(intervals: AutoRefreshInterval[]): number {
|
||||||
return intervals.find(interval => interval.is_active)?.interval ?? 0;
|
return intervals.find(interval => interval.is_active)?.interval ?? 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import react from "@vitejs/plugin-react";
|
|
||||||
import path from "path";
|
|
||||||
import { defineConfig, type Plugin } from "vite";
|
import { defineConfig, type Plugin } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
function rename(oldName: string, newName: string): Plugin {
|
function renameHtml(newName: string): Plugin {
|
||||||
return {
|
return {
|
||||||
name: "rename",
|
name: "rename-html",
|
||||||
enforce: "post",
|
enforce: "post",
|
||||||
generateBundle(_, bundle) {
|
generateBundle(_, bundle) {
|
||||||
if (bundle[oldName]) {
|
if (bundle["index.html"]) {
|
||||||
bundle[oldName].fileName = newName;
|
bundle["index.html"].fileName = newName;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(), tsconfigPaths(), rename("index.html", "build-status.jinja2")],
|
plugins: [react(), tsconfigPaths(), renameHtml("build-status.jinja2")],
|
||||||
base: "/",
|
base: "/",
|
||||||
build: {
|
build: {
|
||||||
chunkSizeWarningLimit: 10000,
|
chunkSizeWarningLimit: 10000,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
pkgbase='ahriman'
|
pkgbase='ahriman'
|
||||||
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
||||||
pkgver=2.20.1
|
pkgver=2.20.0rc4
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
@@ -17,10 +17,11 @@ source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$
|
|||||||
build() {
|
build() {
|
||||||
cd "$pkgbase-$pkgver"
|
cd "$pkgbase-$pkgver"
|
||||||
|
|
||||||
npm --prefix "frontend" install --cache "$srcdir/npm-cache"
|
|
||||||
npm --prefix "frontend" run build
|
|
||||||
|
|
||||||
python -m build --wheel --no-isolation
|
python -m build --wheel --no-isolation
|
||||||
|
|
||||||
|
cd "frontend"
|
||||||
|
npm install --cache "$srcdir/npm-cache"
|
||||||
|
npm run build
|
||||||
}
|
}
|
||||||
|
|
||||||
package_ahriman() {
|
package_ahriman() {
|
||||||
|
|||||||
@@ -26,12 +26,10 @@ formatter = syslog_format
|
|||||||
args = ("/dev/log",)
|
args = ("/dev/log",)
|
||||||
|
|
||||||
[formatter_generic_format]
|
[formatter_generic_format]
|
||||||
format = [{levelname} {asctime}] [{name}]: {message}
|
format = [%(levelname)s %(asctime)s] [%(name)s]: %(message)s
|
||||||
style = {
|
|
||||||
|
|
||||||
[formatter_syslog_format]
|
[formatter_syslog_format]
|
||||||
format = [{levelname}] [{name}]: {message}
|
format = [%(levelname)s] [%(name)s]: %(message)s
|
||||||
style = {
|
|
||||||
|
|
||||||
[logger_root]
|
[logger_root]
|
||||||
level = DEBUG
|
level = DEBUG
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<link rel="icon" href="/static/favicon.ico" />
|
<link rel="icon" href="/static/favicon.ico" />
|
||||||
<script type="module" crossorigin src="/static/index.js"></script>
|
<script type="module" crossorigin src="/static/index.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/static/index.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.TH AHRIMAN "1" "2026\-03\-13" "ahriman 2.20.1" "ArcH linux ReposItory MANager"
|
.TH AHRIMAN "1" "2026\-02\-21" "ahriman 2.20.0rc4" "ArcH linux ReposItory MANager"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ahriman \- ArcH linux ReposItory MANager
|
ahriman \- ArcH linux ReposItory MANager
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ _shtab_ahriman_init_options=(
|
|||||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
||||||
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
||||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
||||||
"--packager[packager name and email]:packager:"
|
"--packager[packager name and email (default\: None)]:packager:"
|
||||||
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
||||||
"--sign-key[sign key id (default\: None)]:sign_key:"
|
"--sign-key[sign key id (default\: None)]:sign_key:"
|
||||||
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
||||||
@@ -526,7 +526,7 @@ _shtab_ahriman_repo_init_options=(
|
|||||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
||||||
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
||||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
||||||
"--packager[packager name and email]:packager:"
|
"--packager[packager name and email (default\: None)]:packager:"
|
||||||
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
||||||
"--sign-key[sign key id (default\: None)]:sign_key:"
|
"--sign-key[sign key id (default\: None)]:sign_key:"
|
||||||
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
||||||
@@ -583,7 +583,7 @@ _shtab_ahriman_repo_setup_options=(
|
|||||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
||||||
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
||||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
||||||
"--packager[packager name and email]:packager:"
|
"--packager[packager name and email (default\: None)]:packager:"
|
||||||
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
||||||
"--sign-key[sign key id (default\: None)]:sign_key:"
|
"--sign-key[sign key id (default\: None)]:sign_key:"
|
||||||
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
||||||
@@ -757,7 +757,7 @@ _shtab_ahriman_service_setup_options=(
|
|||||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
||||||
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
||||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
||||||
"--packager[packager name and email]:packager:"
|
"--packager[packager name and email (default\: None)]:packager:"
|
||||||
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
||||||
"--sign-key[sign key id (default\: None)]:sign_key:"
|
"--sign-key[sign key id (default\: None)]:sign_key:"
|
||||||
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
||||||
@@ -792,7 +792,7 @@ _shtab_ahriman_setup_options=(
|
|||||||
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
|
||||||
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
|
||||||
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
|
||||||
"--packager[packager name and email]:packager:"
|
"--packager[packager name and email (default\: None)]:packager:"
|
||||||
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
|
||||||
"--sign-key[sign key id (default\: None)]:sign_key:"
|
"--sign-key[sign key id (default\: None)]:sign_key:"
|
||||||
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
|
||||||
|
|||||||
@@ -17,4 +17,4 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2.20.1"
|
__version__ = "2.20.0rc4"
|
||||||
|
|||||||
@@ -138,14 +138,8 @@ class PacmanDatabase(SyncHttpClient):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
force(bool): force database synchronization (same as ``pacman -Syy``)
|
force(bool): force database synchronization (same as ``pacman -Syy``)
|
||||||
|
|
||||||
Raises:
|
|
||||||
PacmanError: on operation error (invalid scheme or incomplete configuration)
|
|
||||||
"""
|
"""
|
||||||
try:
|
server = next(iter(self.database.servers))
|
||||||
server = next(iter(self.database.servers))
|
|
||||||
except StopIteration:
|
|
||||||
raise PacmanError("No configured servers available for database") from None
|
|
||||||
filename = f"{self.database.name}.files.tar.gz"
|
filename = f"{self.database.name}.files.tar.gz"
|
||||||
url = f"{server}/{filename}"
|
url = f"{server}/{filename}"
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class Auth(LazyLogging):
|
|||||||
Returns:
|
Returns:
|
||||||
str: login control as html code to insert
|
str: login control as html code to insert
|
||||||
"""
|
"""
|
||||||
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>"
|
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
|
@property
|
||||||
def is_external(self) -> bool:
|
def is_external(self) -> bool:
|
||||||
|
|||||||
@@ -116,19 +116,6 @@ class GitRemoteError(RuntimeError):
|
|||||||
RuntimeError.__init__(self, "Git remote failed")
|
RuntimeError.__init__(self, "Git remote failed")
|
||||||
|
|
||||||
|
|
||||||
class GPGError(RuntimeError):
|
|
||||||
"""
|
|
||||||
PGP/GPG related exception
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, details: str) -> None:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
details(str): details of the exception
|
|
||||||
"""
|
|
||||||
RuntimeError.__init__(self, f"GPG operation failed: {details}")
|
|
||||||
|
|
||||||
|
|
||||||
class InitializeError(RuntimeError):
|
class InitializeError(RuntimeError):
|
||||||
"""
|
"""
|
||||||
base service initialization exception
|
base service initialization exception
|
||||||
|
|||||||
@@ -86,11 +86,6 @@ class ArchiveRotationTrigger(Trigger):
|
|||||||
package(Package): package which has been updated to check for older versions
|
package(Package): package which has been updated to check for older versions
|
||||||
pacman(Pacman): alpm wrapper instance
|
pacman(Pacman): alpm wrapper instance
|
||||||
"""
|
"""
|
||||||
# explicit guard to skip process in case if rotation is disabled
|
|
||||||
# this guard is supposed to speedup process
|
|
||||||
if self.keep_built_packages == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
packages: dict[tuple[str, str], Package] = {}
|
packages: dict[tuple[str, str], Package] = {}
|
||||||
# we can't use here load_archives, because it ignores versions
|
# we can't use here load_archives, because it ignores versions
|
||||||
for full_path in filter(package_like, self.paths.archive_for(package.base).iterdir()):
|
for full_path in filter(package_like, self.paths.archive_for(package.base).iterdir()):
|
||||||
@@ -99,7 +94,7 @@ class ArchiveRotationTrigger(Trigger):
|
|||||||
|
|
||||||
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
||||||
to_remove = sorted(packages.values(), key=cmp_to_key(comparator))
|
to_remove = sorted(packages.values(), key=cmp_to_key(comparator))
|
||||||
|
# 0 will implicitly be translated into [:0], meaning we keep all packages
|
||||||
for single in to_remove[:-self.keep_built_packages]:
|
for single in to_remove[:-self.keep_built_packages]:
|
||||||
self.logger.info("removing version %s of package %s", single.version, single.base)
|
self.logger.info("removing version %s of package %s", single.version, single.base)
|
||||||
for archive in single.packages.values():
|
for archive in single.packages.values():
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
#
|
#
|
||||||
import contextlib
|
import contextlib
|
||||||
import requests
|
import requests
|
||||||
import uuid
|
|
||||||
|
|
||||||
from requests.adapters import BaseAdapter
|
from requests.adapters import BaseAdapter
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
@@ -61,15 +60,6 @@ class SyncAhrimanClient(SyncHttpClient):
|
|||||||
|
|
||||||
return adapters
|
return adapters
|
||||||
|
|
||||||
def headers(self) -> dict[str, str]:
|
|
||||||
"""
|
|
||||||
additional request headers
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict[str, str]: additional request headers defined by class
|
|
||||||
"""
|
|
||||||
return SyncHttpClient.headers(self) | {"X-Request-ID": str(uuid.uuid4())}
|
|
||||||
|
|
||||||
def on_session_creation(self, session: requests.Session) -> None:
|
def on_session_creation(self, session: requests.Session) -> None:
|
||||||
"""
|
"""
|
||||||
method which will be called on session creation
|
method which will be called on session creation
|
||||||
|
|||||||
@@ -144,15 +144,6 @@ class SyncHttpClient(LazyLogging):
|
|||||||
"https://": HTTPAdapter(max_retries=self.retry),
|
"https://": HTTPAdapter(max_retries=self.retry),
|
||||||
}
|
}
|
||||||
|
|
||||||
def headers(self) -> dict[str, str]:
|
|
||||||
"""
|
|
||||||
additional request headers
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict[str, str]: additional request headers defined by class
|
|
||||||
"""
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def make_request(self, method: Literal["DELETE", "GET", "HEAD", "POST", "PUT"], url: str, *,
|
def make_request(self, method: Literal["DELETE", "GET", "HEAD", "POST", "PUT"], url: str, *,
|
||||||
headers: dict[str, str] | None = None,
|
headers: dict[str, str] | None = None,
|
||||||
params: list[tuple[str, str]] | None = None,
|
params: list[tuple[str, str]] | None = None,
|
||||||
@@ -187,9 +178,6 @@ class SyncHttpClient(LazyLogging):
|
|||||||
if session is None:
|
if session is None:
|
||||||
session = self.session
|
session = self.session
|
||||||
|
|
||||||
if additional_headers := self.headers():
|
|
||||||
headers = additional_headers | (headers or {})
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = session.request(method, url, params=params, data=data, headers=headers, files=files, json=json,
|
response = session.request(method, url, params=params, data=data, headers=headers, files=files, json=json,
|
||||||
stream=stream, auth=self.auth, timeout=self.timeout)
|
stream=stream, auth=self.auth, timeout=self.timeout)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ from collections.abc import Iterator
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ahriman.core.log.log_context import LogContext
|
|
||||||
from ahriman.models.log_record_id import LogRecordId
|
from ahriman.models.log_record_id import LogRecordId
|
||||||
|
|
||||||
|
|
||||||
@@ -55,20 +54,30 @@ class LazyLogging:
|
|||||||
prefix = "" if clazz.__module__ is None else f"{clazz.__module__}."
|
prefix = "" if clazz.__module__ is None else f"{clazz.__module__}."
|
||||||
return f"{prefix}{clazz.__qualname__}"
|
return f"{prefix}{clazz.__qualname__}"
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@staticmethod
|
||||||
def in_context(self, name: str, value: Any) -> Iterator[None]:
|
def _package_logger_reset() -> None:
|
||||||
"""
|
"""
|
||||||
execute function while setting log context. The context will be reset after the execution
|
reset package logger to empty one
|
||||||
|
"""
|
||||||
|
logging.setLogRecordFactory(logging.LogRecord)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _package_logger_set(package_base: str, version: str | None) -> None:
|
||||||
|
"""
|
||||||
|
set package base as extra info to the logger
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name(str): attribute name to set on log records
|
package_base(str): package base
|
||||||
value(Any): current value of the context variable
|
version(str | None): package version if available
|
||||||
"""
|
"""
|
||||||
token = LogContext.set(name, value)
|
current_factory = logging.getLogRecordFactory()
|
||||||
try:
|
|
||||||
yield
|
def package_record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
|
||||||
finally:
|
record = current_factory(*args, **kwargs)
|
||||||
LogContext.reset(name, token)
|
record.package_id = LogRecordId(package_base, version or "<unknown>")
|
||||||
|
return record
|
||||||
|
|
||||||
|
logging.setLogRecordFactory(package_record_factory)
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def in_package_context(self, package_base: str, version: str | None) -> Iterator[None]:
|
def in_package_context(self, package_base: str, version: str | None) -> Iterator[None]:
|
||||||
@@ -85,5 +94,8 @@ class LazyLogging:
|
|||||||
>>> with self.in_package_context(package.base, package.version):
|
>>> with self.in_package_context(package.base, package.version):
|
||||||
>>> build_package(package)
|
>>> build_package(package)
|
||||||
"""
|
"""
|
||||||
with self.in_context("package_id", LogRecordId(package_base, version or "<unknown>")):
|
try:
|
||||||
|
self._package_logger_set(package_base, version)
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
|
self._package_logger_reset()
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
import contextvars
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from typing import Any, ClassVar, TypeVar, cast
|
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
class LogContext:
|
|
||||||
"""
|
|
||||||
logging context manager which provides context variables injection into log records
|
|
||||||
"""
|
|
||||||
|
|
||||||
_context: ClassVar[dict[str, contextvars.ContextVar[Any]]] = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, name: str) -> T | None:
|
|
||||||
"""
|
|
||||||
get context variable if available
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name(str): name of the context variable
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
T | None: context variable if available and ``None`` otherwise
|
|
||||||
"""
|
|
||||||
if (variable := cls._context.get(name)) is not None:
|
|
||||||
return cast(T | None, variable.get())
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def log_record_factory(cls, *args: Any, **kwargs: Any) -> logging.LogRecord:
|
|
||||||
"""
|
|
||||||
log record factory which injects all registered context variables into log records
|
|
||||||
|
|
||||||
Args:
|
|
||||||
*args(Any): positional arguments for the log factory
|
|
||||||
**kwargs(Any): keyword arguments for the log factory
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
logging.LogRecord: log record with context variables set as attributes
|
|
||||||
"""
|
|
||||||
record = logging.LogRecord(*args, **kwargs)
|
|
||||||
|
|
||||||
for name, variable in cls._context.items():
|
|
||||||
if (value := variable.get()) is not None:
|
|
||||||
setattr(record, name, value)
|
|
||||||
|
|
||||||
return record
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register(cls, name: str) -> contextvars.ContextVar[T]:
|
|
||||||
"""
|
|
||||||
(re)create context variable for log records
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name(str): name of the context variable
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
contextvars.ContextVar[T]: created context variable
|
|
||||||
"""
|
|
||||||
variable = cls._context[name] = contextvars.ContextVar(name, default=None)
|
|
||||||
return variable
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def reset(cls, name: str, token: contextvars.Token[T]) -> None:
|
|
||||||
"""
|
|
||||||
reset context variable to its previous value
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name(str): attribute name to reset on log records
|
|
||||||
token(contextvars.Token[T]): previously registered token
|
|
||||||
"""
|
|
||||||
cls._context[name].reset(token)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set(cls, name: str, value: T) -> contextvars.Token[T]:
|
|
||||||
"""
|
|
||||||
set context variable for log records. This value will be automatically emitted with each log record
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name(str): attribute name to set on log records
|
|
||||||
value(T): current value of the context variable
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
contextvars.Token[T]: token created with this value
|
|
||||||
"""
|
|
||||||
return cls._context[name].set(value)
|
|
||||||
@@ -21,11 +21,10 @@ import logging
|
|||||||
|
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import ClassVar, Literal
|
from typing import ClassVar
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.log.http_log_handler import HttpLogHandler
|
from ahriman.core.log.http_log_handler import HttpLogHandler
|
||||||
from ahriman.core.log.log_context import LogContext
|
|
||||||
from ahriman.models.log_handler import LogHandler
|
from ahriman.models.log_handler import LogHandler
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
|
||||||
@@ -37,13 +36,11 @@ class LogLoader:
|
|||||||
Attributes:
|
Attributes:
|
||||||
DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback)
|
DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback)
|
||||||
DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
|
DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
|
||||||
DEFAULT_LOG_STYLE(str): (class attribute) default log style (in case of fallback)
|
|
||||||
DEFAULT_SYSLOG_DEVICE(Path): (class attribute) default path to syslog device
|
DEFAULT_SYSLOG_DEVICE(Path): (class attribute) default path to syslog device
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_LOG_FORMAT: ClassVar[str] = "[{levelname} {asctime}] [{name}]: {message}"
|
DEFAULT_LOG_FORMAT: ClassVar[str] = "[%(levelname)s %(asctime)s] [%(name)s]: %(message)s"
|
||||||
DEFAULT_LOG_LEVEL: ClassVar[int] = logging.DEBUG
|
DEFAULT_LOG_LEVEL: ClassVar[int] = logging.DEBUG
|
||||||
DEFAULT_LOG_STYLE: ClassVar[Literal["%", "{", "$"]] = "{"
|
|
||||||
DEFAULT_SYSLOG_DEVICE: ClassVar[Path] = Path("/") / "dev" / "log"
|
DEFAULT_SYSLOG_DEVICE: ClassVar[Path] = Path("/") / "dev" / "log"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -103,22 +100,10 @@ class LogLoader:
|
|||||||
fileConfig(log_configuration, disable_existing_loggers=True)
|
fileConfig(log_configuration, disable_existing_loggers=True)
|
||||||
logging.debug("using %s logger", default_handler)
|
logging.debug("using %s logger", default_handler)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.basicConfig(filename=None, format=LogLoader.DEFAULT_LOG_FORMAT,
|
logging.basicConfig(filename=None, format=LogLoader.DEFAULT_LOG_FORMAT, level=LogLoader.DEFAULT_LOG_LEVEL)
|
||||||
style=LogLoader.DEFAULT_LOG_STYLE, level=LogLoader.DEFAULT_LOG_LEVEL)
|
|
||||||
logging.exception("could not load logging from configuration, fallback to stderr")
|
logging.exception("could not load logging from configuration, fallback to stderr")
|
||||||
|
|
||||||
HttpLogHandler.load(repository_id, configuration, report=report)
|
HttpLogHandler.load(repository_id, configuration, report=report)
|
||||||
LogLoader.register_context()
|
|
||||||
|
|
||||||
if quiet:
|
if quiet:
|
||||||
logging.disable(logging.WARNING) # only print errors here
|
logging.disable(logging.WARNING) # only print errors here
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def register_context() -> None:
|
|
||||||
"""
|
|
||||||
register logging context
|
|
||||||
"""
|
|
||||||
# predefined context variables
|
|
||||||
for variable in ("package_id", "request_id"):
|
|
||||||
LogContext.register(variable)
|
|
||||||
logging.setLogRecordFactory(LogContext.log_record_factory)
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import BuildError, GPGError
|
from ahriman.core.exceptions import BuildError
|
||||||
from ahriman.core.http import SyncHttpClient
|
from ahriman.core.http import SyncHttpClient
|
||||||
from ahriman.core.utils import check_output
|
from ahriman.core.utils import check_output
|
||||||
from ahriman.models.sign_settings import SignSettings
|
from ahriman.models.sign_settings import SignSettings
|
||||||
@@ -147,19 +147,12 @@ class GPG(SyncHttpClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: full PGP key fingerprint
|
str: full PGP key fingerprint
|
||||||
|
|
||||||
Raises:
|
|
||||||
GPGError: if key is in wrong format
|
|
||||||
"""
|
"""
|
||||||
|
metadata = check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger)
|
||||||
# fingerprint line will be like
|
# fingerprint line will be like
|
||||||
# fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:
|
# fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:
|
||||||
metadata = check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger)
|
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines()))
|
||||||
|
return fingerprint.split(":")[-2]
|
||||||
try:
|
|
||||||
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines()))
|
|
||||||
return fingerprint.split(":")[-2]
|
|
||||||
except (IndexError, StopIteration):
|
|
||||||
raise GPGError(f"key {key} has invalid metadata") from None
|
|
||||||
|
|
||||||
def key_import(self, server: str, key: str) -> None:
|
def key_import(self, server: str, key: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -88,12 +88,8 @@ class User:
|
|||||||
"""
|
"""
|
||||||
if not self.password:
|
if not self.password:
|
||||||
return None
|
return None
|
||||||
|
algo = next(segment for segment in self.password.split("$") if segment)
|
||||||
try:
|
return f"${algo}$"
|
||||||
algo = next(segment for segment in self.password.split("$") if segment)
|
|
||||||
return f"${algo}$"
|
|
||||||
except StopIteration:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_password(length: int) -> str:
|
def generate_password(length: int) -> str:
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from aiohttp.typedefs import Middleware
|
|
||||||
from aiohttp.web import Request, StreamResponse, middleware
|
|
||||||
|
|
||||||
from ahriman.core.log.log_context import LogContext
|
|
||||||
from ahriman.web.middlewares import HandlerType
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["request_id_handler"]
|
|
||||||
|
|
||||||
|
|
||||||
def request_id_handler() -> Middleware:
|
|
||||||
"""
|
|
||||||
middleware to trace request id header
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Middleware: request id processing middleware
|
|
||||||
"""
|
|
||||||
@middleware
|
|
||||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
|
||||||
request_id = request.headers.getone("X-Request-ID", str(uuid.uuid4()))
|
|
||||||
|
|
||||||
token = LogContext.set("request_id", request_id)
|
|
||||||
try:
|
|
||||||
response = await handler(request)
|
|
||||||
response.headers["X-Request-ID"] = request_id
|
|
||||||
return response
|
|
||||||
finally:
|
|
||||||
LogContext.reset("request_id", token)
|
|
||||||
|
|
||||||
return handle
|
|
||||||
@@ -25,11 +25,9 @@ class AuthInfoSchema(Schema):
|
|||||||
authorization information schema
|
authorization information schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
control = fields.String(
|
control = fields.String(required=True, metadata={
|
||||||
metadata={
|
"description": "HTML control for login interface",
|
||||||
"description": "HTML control for login interface",
|
})
|
||||||
"example": "<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>",
|
|
||||||
})
|
|
||||||
enabled = fields.Boolean(required=True, metadata={
|
enabled = fields.Boolean(required=True, metadata={
|
||||||
"description": "Whether authentication is enabled or not",
|
"description": "Whether authentication is enabled or not",
|
||||||
})
|
})
|
||||||
@@ -37,5 +35,5 @@ class AuthInfoSchema(Schema):
|
|||||||
"description": "Whether authorization provider is external (e.g. OAuth)",
|
"description": "Whether authorization provider is external (e.g. OAuth)",
|
||||||
})
|
})
|
||||||
username = fields.String(metadata={
|
username = fields.String(metadata={
|
||||||
"description": "Currently authenticated username if available",
|
"description": "Currently authenticated username if any",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,12 +27,10 @@ class AutoRefreshIntervalSchema(Schema):
|
|||||||
|
|
||||||
interval = fields.Integer(required=True, metadata={
|
interval = fields.Integer(required=True, metadata={
|
||||||
"description": "Auto refresh interval in milliseconds",
|
"description": "Auto refresh interval in milliseconds",
|
||||||
"example": "60000",
|
|
||||||
})
|
})
|
||||||
is_active = fields.Boolean(required=True, metadata={
|
is_active = fields.Boolean(required=True, metadata={
|
||||||
"description": "Whether this interval is the default active one",
|
"description": "Whether this interval is the default active one",
|
||||||
})
|
})
|
||||||
text = fields.String(required=True, metadata={
|
text = fields.String(required=True, metadata={
|
||||||
"description": "Human readable interval description",
|
"description": "Human readable interval description",
|
||||||
"example": "1 minute",
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class InfoV2Schema(Schema):
|
|||||||
})
|
})
|
||||||
index_url = fields.String(metadata={
|
index_url = fields.String(metadata={
|
||||||
"description": "URL to the repository index page",
|
"description": "URL to the repository index page",
|
||||||
"example": "https://ahriman.readthedocs.io/",
|
|
||||||
})
|
})
|
||||||
repositories = fields.Nested(RepositoryIdSchema(many=True), required=True, metadata={
|
repositories = fields.Nested(RepositoryIdSchema(many=True), required=True, metadata={
|
||||||
"description": "List of loaded repositories",
|
"description": "List of loaded repositories",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class RepositoryIdSchema(Schema):
|
|||||||
})
|
})
|
||||||
id = fields.String(metadata={
|
id = fields.String(metadata={
|
||||||
"description": "Unique repository identifier",
|
"description": "Unique repository identifier",
|
||||||
"example": "x86_64-aur",
|
"example": "aur-x86_64",
|
||||||
})
|
})
|
||||||
repository = fields.String(metadata={
|
repository = fields.String(metadata={
|
||||||
"description": "Repository name",
|
"description": "Repository name",
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ from ahriman.web.cors import setup_cors
|
|||||||
from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey
|
from ahriman.web.keys import AuthKey, ConfigurationKey, SpawnKey, WatcherKey, WorkersKey
|
||||||
from ahriman.web.middlewares.exception_handler import exception_handler
|
from ahriman.web.middlewares.exception_handler import exception_handler
|
||||||
from ahriman.web.middlewares.metrics_handler import metrics_handler
|
from ahriman.web.middlewares.metrics_handler import metrics_handler
|
||||||
from ahriman.web.middlewares.request_id_handler import request_id_handler
|
|
||||||
from ahriman.web.routes import setup_routes
|
from ahriman.web.routes import setup_routes
|
||||||
|
|
||||||
|
|
||||||
@@ -147,7 +146,6 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
|||||||
application.on_startup.append(_on_startup)
|
application.on_startup.append(_on_startup)
|
||||||
|
|
||||||
application.middlewares.append(normalize_path_middleware(append_slash=False, remove_slash=True))
|
application.middlewares.append(normalize_path_middleware(append_slash=False, remove_slash=True))
|
||||||
application.middlewares.append(request_id_handler())
|
|
||||||
application.middlewares.append(exception_handler(application.logger))
|
application.middlewares.append(exception_handler(application.logger))
|
||||||
application.middlewares.append(metrics_handler())
|
application.middlewares.append(metrics_handler())
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ SUBPACKAGES = {
|
|||||||
prefix / "lib" / "systemd" / "system" / "ahriman-web.service",
|
prefix / "lib" / "systemd" / "system" / "ahriman-web.service",
|
||||||
prefix / "lib" / "systemd" / "system" / "ahriman-web@.service",
|
prefix / "lib" / "systemd" / "system" / "ahriman-web@.service",
|
||||||
prefix / "share" / "ahriman" / "settings" / "ahriman.ini.d" / "00-web.ini",
|
prefix / "share" / "ahriman" / "settings" / "ahriman.ini.d" / "00-web.ini",
|
||||||
prefix / "share" / "ahriman" / "templates" / "api.jinja2",
|
|
||||||
prefix / "share" / "ahriman" / "templates" / "build-status",
|
prefix / "share" / "ahriman" / "templates" / "build-status",
|
||||||
prefix / "share" / "ahriman" / "templates" / "build-status-classic.jinja2",
|
|
||||||
prefix / "share" / "ahriman" / "templates" / "build-status.jinja2",
|
prefix / "share" / "ahriman" / "templates" / "build-status.jinja2",
|
||||||
|
prefix / "share" / "ahriman" / "templates" / "build-status-legacy.jinja2",
|
||||||
|
prefix / "share" / "ahriman" / "templates" / "api.jinja2",
|
||||||
prefix / "share" / "ahriman" / "templates" / "error.jinja2",
|
prefix / "share" / "ahriman" / "templates" / "error.jinja2",
|
||||||
prefix / "share" / "ahriman" / "templates" / "static",
|
prefix / "share" / "ahriman" / "templates" / "static",
|
||||||
site_packages / "ahriman" / "application" / "handlers" / "web.py",
|
site_packages / "ahriman" / "application" / "handlers" / "web.py",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from ahriman.core.auth import Auth
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.core.database.migrations import Migrations
|
from ahriman.core.database.migrations import Migrations
|
||||||
from ahriman.core.log.log_loader import LogLoader
|
|
||||||
from ahriman.core.repository import Repository
|
from ahriman.core.repository import Repository
|
||||||
from ahriman.core.spawn import Spawn
|
from ahriman.core.spawn import Spawn
|
||||||
from ahriman.core.status import Client
|
from ahriman.core.status import Client
|
||||||
@@ -125,14 +124,6 @@ def import_error(package: str, components: list[str], mocker: MockerFixture) ->
|
|||||||
|
|
||||||
|
|
||||||
# generic fixtures
|
# generic fixtures
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def _register_log_context() -> None:
|
|
||||||
"""
|
|
||||||
register log context variables and factory
|
|
||||||
"""
|
|
||||||
LogLoader.register_context()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def aur_package_ahriman() -> AURPackage:
|
def aur_package_ahriman() -> AURPackage:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -183,15 +183,6 @@ def test_sync_files_local(pacman_database: PacmanDatabase, mocker: MockerFixture
|
|||||||
copy_mock.assert_called_once_with(Path("/var/core.files.tar.gz"), pytest.helpers.anyvar(int))
|
copy_mock.assert_called_once_with(Path("/var/core.files.tar.gz"), pytest.helpers.anyvar(int))
|
||||||
|
|
||||||
|
|
||||||
def test_sync_files_no_servers(pacman_database: PacmanDatabase) -> None:
|
|
||||||
"""
|
|
||||||
must raise PacmanError if no servers are configured
|
|
||||||
"""
|
|
||||||
pacman_database.database.servers = []
|
|
||||||
with pytest.raises(PacmanError):
|
|
||||||
pacman_database.sync_files(force=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_sync_files_unknown_source(pacman_database: PacmanDatabase) -> None:
|
def test_sync_files_unknown_source(pacman_database: PacmanDatabase) -> None:
|
||||||
"""
|
"""
|
||||||
must raise an exception in case if server scheme is unsupported
|
must raise an exception in case if server scheme is unsupported
|
||||||
|
|||||||
@@ -30,13 +30,6 @@ def test_login_url(ahriman_client: SyncAhrimanClient) -> None:
|
|||||||
assert ahriman_client._login_url().endswith("/api/v1/login")
|
assert ahriman_client._login_url().endswith("/api/v1/login")
|
||||||
|
|
||||||
|
|
||||||
def test_headers(ahriman_client: SyncAhrimanClient) -> None:
|
|
||||||
"""
|
|
||||||
must inject request id header
|
|
||||||
"""
|
|
||||||
assert "X-Request-ID" in ahriman_client.headers()
|
|
||||||
|
|
||||||
|
|
||||||
def test_on_session_creation(ahriman_client: SyncAhrimanClient, user: User, mocker: MockerFixture) -> None:
|
def test_on_session_creation(ahriman_client: SyncAhrimanClient, user: User, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must log in user on start
|
must log in user on start
|
||||||
|
|||||||
@@ -94,13 +94,6 @@ def test_adapters() -> None:
|
|||||||
assert all(adapter.max_retries == client.retry for adapter in adapters.values())
|
assert all(adapter.max_retries == client.retry for adapter in adapters.values())
|
||||||
|
|
||||||
|
|
||||||
def test_headers() -> None:
|
|
||||||
"""
|
|
||||||
must return empty additional headers
|
|
||||||
"""
|
|
||||||
assert SyncHttpClient().headers() == {}
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_request(mocker: MockerFixture) -> None:
|
def test_make_request(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must make HTTP request
|
must make HTTP request
|
||||||
@@ -201,20 +194,6 @@ def test_make_request_session() -> None:
|
|||||||
stream=None, auth=None, timeout=client.timeout)
|
stream=None, auth=None, timeout=client.timeout)
|
||||||
|
|
||||||
|
|
||||||
def test_make_request_with_additional_headers(mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must merge additional headers into request
|
|
||||||
"""
|
|
||||||
request_mock = mocker.patch("requests.Session.request")
|
|
||||||
mocker.patch("ahriman.core.http.sync_http_client.SyncHttpClient.headers", return_value={"X-Custom": "value"})
|
|
||||||
client = SyncHttpClient()
|
|
||||||
|
|
||||||
client.make_request("GET", "url")
|
|
||||||
request_mock.assert_called_once_with(
|
|
||||||
"GET", "url", params=None, data=None, headers={"X-Custom": "value"}, files=None, json=None,
|
|
||||||
stream=None, auth=None, timeout=client.timeout)
|
|
||||||
|
|
||||||
|
|
||||||
def test_on_session_creation() -> None:
|
def test_on_session_creation() -> None:
|
||||||
"""
|
"""
|
||||||
must do nothing on start
|
must do nothing on start
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.core.alpm.repo import Repo
|
from ahriman.core.alpm.repo import Repo
|
||||||
from ahriman.core.build_tools.task import Task
|
from ahriman.core.build_tools.task import Task
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
@@ -28,46 +30,59 @@ def test_logger_name(database: SQLite, repo: Repo, task_ahriman: Task) -> None:
|
|||||||
assert task_ahriman.logger_name == "ahriman.core.build_tools.task.Task"
|
assert task_ahriman.logger_name == "ahriman.core.build_tools.task.Task"
|
||||||
|
|
||||||
|
|
||||||
def test_in_context(database: SQLite) -> None:
|
def test_package_logger_set_reset(database: SQLite) -> None:
|
||||||
"""
|
"""
|
||||||
must set and reset generic log context
|
must set and reset package base attribute
|
||||||
"""
|
"""
|
||||||
with database.in_context("package_id", "42"):
|
log_record_id = LogRecordId("base", "version")
|
||||||
record = logging.makeLogRecord({})
|
|
||||||
assert record.package_id == "42"
|
|
||||||
|
|
||||||
|
database._package_logger_set(log_record_id.package_base, log_record_id.version)
|
||||||
record = logging.makeLogRecord({})
|
record = logging.makeLogRecord({})
|
||||||
assert not hasattr(record, "package_id")
|
assert record.package_id == log_record_id
|
||||||
|
|
||||||
|
|
||||||
def test_in_context_failed(database: SQLite) -> None:
|
|
||||||
"""
|
|
||||||
must reset context even if exception occurs
|
|
||||||
"""
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
with database.in_context("package_id", "42"):
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
|
database._package_logger_reset()
|
||||||
record = logging.makeLogRecord({})
|
record = logging.makeLogRecord({})
|
||||||
assert not hasattr(record, "package_id")
|
with pytest.raises(AttributeError):
|
||||||
|
assert record.package_id
|
||||||
|
|
||||||
|
|
||||||
def test_in_package_context(database: SQLite, package_ahriman: Package) -> None:
|
def test_in_package_context(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must set package log context
|
must set package log context
|
||||||
"""
|
"""
|
||||||
|
set_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_set")
|
||||||
|
reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset")
|
||||||
|
|
||||||
with database.in_package_context(package_ahriman.base, package_ahriman.version):
|
with database.in_package_context(package_ahriman.base, package_ahriman.version):
|
||||||
record = logging.makeLogRecord({})
|
pass
|
||||||
assert record.package_id == LogRecordId(package_ahriman.base, package_ahriman.version)
|
|
||||||
|
|
||||||
record = logging.makeLogRecord({})
|
set_mock.assert_called_once_with(package_ahriman.base, package_ahriman.version)
|
||||||
assert not hasattr(record, "package_id")
|
reset_mock.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
def test_in_package_context_empty_version(database: SQLite, package_ahriman: Package) -> None:
|
def test_in_package_context_empty_version(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must set package log context with empty version
|
must set package log context with empty version
|
||||||
"""
|
"""
|
||||||
|
set_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_set")
|
||||||
|
reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset")
|
||||||
|
|
||||||
with database.in_package_context(package_ahriman.base, None):
|
with database.in_package_context(package_ahriman.base, None):
|
||||||
record = logging.makeLogRecord({})
|
pass
|
||||||
assert record.package_id == LogRecordId(package_ahriman.base, "<unknown>")
|
|
||||||
|
set_mock.assert_called_once_with(package_ahriman.base, None)
|
||||||
|
reset_mock.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_in_package_context_failed(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must reset package context even if exception occurs
|
||||||
|
"""
|
||||||
|
mocker.patch("ahriman.core.log.LazyLogging._package_logger_set")
|
||||||
|
reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
with database.in_package_context(package_ahriman.base, ""):
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
reset_mock.assert_called_once_with()
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from ahriman.core.log.log_context import LogContext
|
|
||||||
|
|
||||||
|
|
||||||
def test_get() -> None:
|
|
||||||
"""
|
|
||||||
must get context variable value
|
|
||||||
"""
|
|
||||||
token = LogContext.set("package_id", "value")
|
|
||||||
assert LogContext.get("package_id") == "value"
|
|
||||||
LogContext.reset("package_id", token)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_empty() -> None:
|
|
||||||
"""
|
|
||||||
must return None when context variable is unknown or not set
|
|
||||||
"""
|
|
||||||
assert LogContext.get("package_id") is None
|
|
||||||
assert LogContext.get("random") is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_log_record_factory() -> None:
|
|
||||||
"""
|
|
||||||
must inject all registered context variables into log records
|
|
||||||
"""
|
|
||||||
package_token = LogContext.set("package_id", "package")
|
|
||||||
|
|
||||||
record = logging.makeLogRecord({})
|
|
||||||
assert record.package_id == "package"
|
|
||||||
|
|
||||||
LogContext.reset("package_id", package_token)
|
|
||||||
|
|
||||||
|
|
||||||
def test_log_record_factory_empty() -> None:
|
|
||||||
"""
|
|
||||||
must not inject context variable when value is None
|
|
||||||
"""
|
|
||||||
record = logging.makeLogRecord({})
|
|
||||||
assert not hasattr(record, "package_id")
|
|
||||||
|
|
||||||
|
|
||||||
def test_register() -> None:
|
|
||||||
"""
|
|
||||||
must register a context variable
|
|
||||||
"""
|
|
||||||
variable = LogContext.register("random")
|
|
||||||
|
|
||||||
assert "random" in LogContext._context
|
|
||||||
assert LogContext._context["random"] is variable
|
|
||||||
|
|
||||||
del LogContext._context["random"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_reset() -> None:
|
|
||||||
"""
|
|
||||||
must reset context variable so it is no longer injected
|
|
||||||
"""
|
|
||||||
token = LogContext.set("package_id", "value")
|
|
||||||
LogContext.reset("package_id", token)
|
|
||||||
|
|
||||||
record = logging.makeLogRecord({})
|
|
||||||
assert not hasattr(record, "package_id")
|
|
||||||
|
|
||||||
|
|
||||||
def test_set() -> None:
|
|
||||||
"""
|
|
||||||
must set context variable and inject it into log records
|
|
||||||
"""
|
|
||||||
token = LogContext.set("package_id", "value")
|
|
||||||
|
|
||||||
record = logging.makeLogRecord({})
|
|
||||||
assert record.package_id == "value"
|
|
||||||
|
|
||||||
LogContext.reset("package_id", token)
|
|
||||||
@@ -7,7 +7,6 @@ from pytest_mock import MockerFixture
|
|||||||
from systemd.journal import JournalHandler
|
from systemd.journal import JournalHandler
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.log.log_context import LogContext
|
|
||||||
from ahriman.core.log.log_loader import LogLoader
|
from ahriman.core.log.log_loader import LogLoader
|
||||||
from ahriman.models.log_handler import LogHandler
|
from ahriman.models.log_handler import LogHandler
|
||||||
|
|
||||||
@@ -76,13 +75,3 @@ def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None
|
|||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
LogLoader.load(repository_id, configuration, LogHandler.Journald, quiet=True, report=False)
|
LogLoader.load(repository_id, configuration, LogHandler.Journald, quiet=True, report=False)
|
||||||
disable_mock.assert_called_once_with(logging.WARNING)
|
disable_mock.assert_called_once_with(logging.WARNING)
|
||||||
|
|
||||||
|
|
||||||
def test_register_context() -> None:
|
|
||||||
"""
|
|
||||||
must register predefined context variables and install log record factory
|
|
||||||
"""
|
|
||||||
LogLoader.register_context()
|
|
||||||
assert "package_id" in LogContext._context
|
|
||||||
assert "request_id" in LogContext._context
|
|
||||||
assert logging.getLogRecordFactory().__func__ is LogContext.log_record_factory.__func__
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from pathlib import Path
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import GPGError
|
|
||||||
from ahriman.core.sign.gpg import GPG
|
from ahriman.core.sign.gpg import GPG
|
||||||
from ahriman.models.sign_settings import SignSettings
|
from ahriman.models.sign_settings import SignSettings
|
||||||
|
|
||||||
@@ -114,15 +113,6 @@ fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:""")
|
|||||||
check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key, logger=gpg.logger)
|
check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key, logger=gpg.logger)
|
||||||
|
|
||||||
|
|
||||||
def test_key_fingerprint_invalid(gpg: GPG, mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must raise GPGError if no fingerprint found in output
|
|
||||||
"""
|
|
||||||
mocker.patch("ahriman.core.sign.gpg.check_output", return_value="no fingerprint here")
|
|
||||||
with pytest.raises(GPGError):
|
|
||||||
gpg.key_fingerprint("0xCE3C45C2")
|
|
||||||
|
|
||||||
|
|
||||||
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
|
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must import PGP key from the server
|
must import PGP key from the server
|
||||||
|
|||||||
@@ -291,28 +291,6 @@ def test_filter_json_empty_value(package_ahriman: Package) -> None:
|
|||||||
assert "base" not in filter_json(probe, probe.keys())
|
assert "base" not in filter_json(probe, probe.keys())
|
||||||
|
|
||||||
|
|
||||||
def test_filter_json_nested() -> None:
|
|
||||||
"""
|
|
||||||
must recursively filter None values from nested dicts
|
|
||||||
"""
|
|
||||||
assert filter_json({"a": {"b": None, "c": 1}}) == {"a": {"c": 1}}
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_json_list() -> None:
|
|
||||||
"""
|
|
||||||
must recursively filter None values inside lists
|
|
||||||
"""
|
|
||||||
assert filter_json([{"a": 1, "b": None}]) == [{"a": 1}]
|
|
||||||
|
|
||||||
|
|
||||||
def test_filter_json_scalar() -> None:
|
|
||||||
"""
|
|
||||||
must pass through scalar values unchanged
|
|
||||||
"""
|
|
||||||
assert filter_json("scalar") == "scalar"
|
|
||||||
assert filter_json(42) == 42
|
|
||||||
|
|
||||||
|
|
||||||
def test_full_version() -> None:
|
def test_full_version() -> None:
|
||||||
"""
|
"""
|
||||||
must construct full version
|
must construct full version
|
||||||
@@ -605,7 +583,7 @@ def test_walk(resource_path_root: Path) -> None:
|
|||||||
resource_path_root / "web" / "templates" / "utils" / "style.jinja2",
|
resource_path_root / "web" / "templates" / "utils" / "style.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "api.jinja2",
|
resource_path_root / "web" / "templates" / "api.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "build-status.jinja2",
|
resource_path_root / "web" / "templates" / "build-status.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "build-status-classic.jinja2",
|
resource_path_root / "web" / "templates" / "build-status-legacy.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "email-index.jinja2",
|
resource_path_root / "web" / "templates" / "email-index.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "error.jinja2",
|
resource_path_root / "web" / "templates" / "error.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "repo-index.jinja2",
|
resource_path_root / "web" / "templates" / "repo-index.jinja2",
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ def test_algo() -> None:
|
|||||||
"""
|
"""
|
||||||
assert User(username="user", password=None, access=UserAccess.Read).algo is None
|
assert User(username="user", password=None, access=UserAccess.Read).algo is None
|
||||||
assert User(username="user", password="", access=UserAccess.Read).algo is None
|
assert User(username="user", password="", access=UserAccess.Read).algo is None
|
||||||
assert User(username="user", password="$$$", access=UserAccess.Read).algo is None
|
|
||||||
|
|
||||||
assert User(
|
assert User(
|
||||||
username="user",
|
username="user",
|
||||||
password="$6$rounds=656000$mWBiecMPrHAL1VgX$oU4Y5HH8HzlvMaxwkNEJjK13ozElyU1wAHBoO/WW5dAaE4YEfnB0X3FxbynKMl4FBdC3Ovap0jINz4LPkNADg0",
|
password="$6$rounds=656000$mWBiecMPrHAL1VgX$oU4Y5HH8HzlvMaxwkNEJjK13ozElyU1wAHBoO/WW5dAaE4YEfnB0X3FxbynKMl4FBdC3Ovap0jINz4LPkNADg0",
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import logging
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from ahriman.web.middlewares.request_id_handler import request_id_handler
|
|
||||||
|
|
||||||
|
|
||||||
async def test_request_id_handler() -> None:
|
|
||||||
"""
|
|
||||||
must use request id from request if available
|
|
||||||
"""
|
|
||||||
request = pytest.helpers.request("", "", "")
|
|
||||||
request.headers = MagicMock()
|
|
||||||
request.headers.getone.return_value = "request_id"
|
|
||||||
|
|
||||||
response = MagicMock()
|
|
||||||
response.headers = {}
|
|
||||||
|
|
||||||
async def check_handler(_: Any) -> MagicMock:
|
|
||||||
record = logging.makeLogRecord({})
|
|
||||||
assert record.request_id == "request_id"
|
|
||||||
return response
|
|
||||||
|
|
||||||
handler = request_id_handler()
|
|
||||||
await handler(request, check_handler)
|
|
||||||
assert response.headers["X-Request-ID"] == "request_id"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_request_id_handler_generate() -> None:
|
|
||||||
"""
|
|
||||||
must generate request id and set it in response header
|
|
||||||
"""
|
|
||||||
request = pytest.helpers.request("", "", "")
|
|
||||||
|
|
||||||
response = MagicMock()
|
|
||||||
response.headers = {}
|
|
||||||
request_handler = AsyncMock(return_value=response)
|
|
||||||
|
|
||||||
handler = request_id_handler()
|
|
||||||
await handler(request, request_handler)
|
|
||||||
assert "X-Request-ID" in response.headers
|
|
||||||
5
tox.toml
5
tox.toml
@@ -268,13 +268,12 @@ commands = [
|
|||||||
[
|
[
|
||||||
"git",
|
"git",
|
||||||
"add",
|
"add",
|
||||||
"docs/_static/architecture.dot",
|
|
||||||
"frontend/package.json",
|
|
||||||
"package/archlinux/PKGBUILD",
|
"package/archlinux/PKGBUILD",
|
||||||
|
"src/ahriman/__init__.py",
|
||||||
|
"docs/_static/architecture.dot",
|
||||||
"package/share/man/man1/ahriman.1",
|
"package/share/man/man1/ahriman.1",
|
||||||
"package/share/bash-completion/completions/_ahriman",
|
"package/share/bash-completion/completions/_ahriman",
|
||||||
"package/share/zsh/site-functions/_ahriman",
|
"package/share/zsh/site-functions/_ahriman",
|
||||||
"src/ahriman/__init__.py",
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"git",
|
"git",
|
||||||
|
|||||||
Reference in New Issue
Block a user