mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-04-07 11:03:37 +00:00
Compare commits
1 Commits
2.20.0rc5
...
cd6fbaae31
| Author | SHA1 | Date | |
|---|---|---|---|
| cd6fbaae31 |
@@ -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
|
||||||
|
|||||||
43
docs/_static/architecture.dot
vendored
43
docs/_static/architecture.dot
vendored
@@ -250,9 +250,7 @@ digraph G {
|
|||||||
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"];
|
||||||
@@ -263,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"];
|
||||||
@@ -292,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"];
|
||||||
@@ -320,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"];
|
||||||
@@ -494,10 +489,10 @@ digraph G {
|
|||||||
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_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"];
|
||||||
@@ -547,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"];
|
||||||
@@ -996,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"];
|
||||||
@@ -1066,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"];
|
||||||
@@ -1252,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"];
|
||||||
@@ -1661,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"];
|
||||||
@@ -1672,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"];
|
||||||
@@ -1685,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"];
|
||||||
@@ -1714,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"];
|
||||||
@@ -1744,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"];
|
||||||
@@ -1773,7 +1760,6 @@ 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"];
|
||||||
@@ -1813,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"];
|
||||||
@@ -1834,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"];
|
||||||
@@ -1867,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"];
|
||||||
@@ -1881,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"];
|
||||||
@@ -1919,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"];
|
||||||
@@ -1942,7 +1915,6 @@ digraph G {
|
|||||||
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"];
|
||||||
@@ -1971,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"];
|
||||||
|
|||||||
@@ -18,17 +18,16 @@
|
|||||||
"@mui/x-data-grid": "^8.27.3",
|
"@mui/x-data-grid": "^8.27.3",
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"chart.js": "^4.5.0",
|
"chart.js": "^4.5.0",
|
||||||
|
"highlight.js": "^11.11.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-chartjs-2": "^5.2.0",
|
"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.9.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.0.0",
|
||||||
"eslint": "^9.39.3",
|
"eslint": "^9.39.3",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
|||||||
@@ -38,18 +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 theme={Theme}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<CssBaseline />
|
<ThemeProvider theme={Theme}>
|
||||||
<NotificationProvider>
|
<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) {
|
||||||
@@ -45,35 +43,30 @@ export class Client {
|
|||||||
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,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";
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -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 }}>
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ 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,
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerStyle = status ? StatusHeaderStyles[status.status.status] : {};
|
const headerStyle = status ? StatusHeaderStyles[status.status.status] : {};
|
||||||
|
|
||||||
return <Dialog open={open} onClose={onClose} maxWidth="lg" fullWidth>
|
return <Dialog open={open} onClose={onClose} keepMounted maxWidth="lg" fullWidth>
|
||||||
<DialogHeader onClose={onClose} sx={headerStyle}>
|
<DialogHeader onClose={onClose} sx={headerStyle}>
|
||||||
System health
|
System health
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -81,7 +85,7 @@ export default function KeyImportDialog({ open, onClose }: KeyImportDialogProps)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="lg" fullWidth>
|
return <Dialog open={open} onClose={handleClose} keepMounted maxWidth="lg" fullWidth>
|
||||||
<DialogHeader onClose={handleClose}>
|
<DialogHeader onClose={handleClose}>
|
||||||
Import key from PGP server
|
Import key from PGP server
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function LoginDialog({ open, onClose }: LoginDialogProps): React.
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
|
return <Dialog open={open} onClose={handleClose} keepMounted maxWidth="xs" fullWidth>
|
||||||
<DialogHeader onClose={handleClose}>
|
<DialogHeader onClose={handleClose}>
|
||||||
Login
|
Login
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -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,22 +91,42 @@ 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}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
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}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Dialog open={open} onClose={handleClose} keepMounted maxWidth="md" fullWidth>
|
||||||
<DialogHeader onClose={handleClose}>
|
<DialogHeader onClose={handleClose}>
|
||||||
Add new packages
|
Add new packages
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -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,22 +121,22 @@ 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)}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="lg" fullWidth>
|
return <Dialog open={open} onClose={handleClose} keepMounted maxWidth="lg" fullWidth>
|
||||||
<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) {
|
||||||
@@ -63,7 +63,7 @@ export default function PackageRebuildDialog({ open, onClose }: PackageRebuildDi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
return <Dialog open={open} onClose={handleClose} keepMounted maxWidth="md" fullWidth>
|
||||||
<DialogHeader onClose={handleClose}>
|
<DialogHeader onClose={handleClose}>
|
||||||
Rebuild depending packages
|
Rebuild depending packages
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function AppLayout(): React.JSX.Element {
|
|||||||
|
|
||||||
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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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,19 +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 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 } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
|
||||||
|
|
||||||
SyntaxHighlighter.registerLanguage("diff", diff);
|
hljs.registerLanguage("diff", diff);
|
||||||
|
|
||||||
interface ChangesTabProps {
|
interface ChangesTabProps {
|
||||||
packageBase: string;
|
packageBase: string;
|
||||||
@@ -38,33 +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 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={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} />
|
||||||
@@ -72,7 +71,8 @@ export default function EventsTab({ packageBase, repository }: EventsTabProps):
|
|||||||
sorting: { sortModel: [{ field: "timestamp", sort: "desc" }] },
|
sorting: { sortModel: [{ field: "timestamp", sort: "desc" }] },
|
||||||
}}
|
}}
|
||||||
pageSizeOptions={[10, 25]}
|
pageSizeOptions={[10, 25]}
|
||||||
sx={{ height: 400, mt: 1 }}
|
autoHeight
|
||||||
|
sx={{ mt: 1 }}
|
||||||
disableRowSelectionOnClick
|
disableRowSelectionOnClick
|
||||||
/>
|
/>
|
||||||
</Box>;
|
</Box>;
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ interface PackageDetailsGridProps {
|
|||||||
dependencies?: Dependencies;
|
dependencies?: Dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listToString(items: string[]): React.ReactNode {
|
||||||
|
const unique = [...new Set(items)].sort();
|
||||||
|
return unique.map((item, index) =>
|
||||||
|
<React.Fragment key={item}>
|
||||||
|
{item}
|
||||||
|
{index < unique.length - 1 && <br />}
|
||||||
|
</React.Fragment>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetailsGridProps): React.JSX.Element {
|
export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetailsGridProps): React.JSX.Element {
|
||||||
const packagesList = Object.entries(pkg.packages)
|
const packagesList = Object.entries(pkg.packages)
|
||||||
.map(([name, properties]) => `${name}${properties.description ? ` (${properties.description})` : ""}`);
|
.map(([name, properties]) => `${name}${properties.description ? ` (${properties.description})` : ""}`);
|
||||||
@@ -37,27 +47,21 @@ export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetails
|
|||||||
const licenses = Object.values(pkg.packages)
|
const licenses = Object.values(pkg.packages)
|
||||||
.flatMap(properties => properties.licenses ?? []);
|
.flatMap(properties => properties.licenses ?? []);
|
||||||
|
|
||||||
const upstreamUrls = Object.values(pkg.packages)
|
const upstreamUrls = [...new Set(
|
||||||
.map(properties => properties.url)
|
Object.values(pkg.packages)
|
||||||
.filter((url): url is string => !!url)
|
.map(properties => properties.url)
|
||||||
.unique();
|
.filter((url): url is string => !!url),
|
||||||
|
)].sort();
|
||||||
|
|
||||||
const aurUrl = pkg.remote.web_url;
|
const aurUrl = pkg.remote.web_url;
|
||||||
|
|
||||||
const pkgNames = Object.keys(pkg.packages);
|
const pkgNames = Object.keys(pkg.packages);
|
||||||
const pkgValues = Object.values(pkg.packages);
|
const allDepends = Object.values(pkg.packages).flatMap(properties => {
|
||||||
const deps = pkgValues
|
const deps = (properties.depends ?? []).filter(dep => !pkgNames.includes(dep));
|
||||||
.flatMap(properties => (properties.depends ?? []).filter(dep => !pkgNames.includes(dep)))
|
const makeDeps = (properties.make_depends ?? []).filter(dep => !pkgNames.includes(dep)).map(dep => `${dep} (make)`);
|
||||||
.unique();
|
const optDeps = (properties.opt_depends ?? []).filter(dep => !pkgNames.includes(dep)).map(dep => `${dep} (optional)`);
|
||||||
const makeDeps = pkgValues
|
return [...deps, ...makeDeps, ...optDeps];
|
||||||
.flatMap(properties => (properties.make_depends ?? []).filter(dep => !pkgNames.includes(dep)))
|
});
|
||||||
.map(dep => `${dep} (make)`)
|
|
||||||
.unique();
|
|
||||||
const optDeps = pkgValues
|
|
||||||
.flatMap(properties => (properties.opt_depends ?? []).filter(dep => !pkgNames.includes(dep)))
|
|
||||||
.map(dep => `${dep} (optional)`)
|
|
||||||
.unique();
|
|
||||||
const allDepends = [...deps, ...makeDeps, ...optDeps];
|
|
||||||
|
|
||||||
const implicitDepends = dependencies
|
const implicitDepends = dependencies
|
||||||
? Object.values(dependencies.paths).flat()
|
? Object.values(dependencies.paths).flat()
|
||||||
@@ -66,7 +70,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">{listToString(packagesList)}</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 +84,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">{listToString(groups)}</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">{listToString(licenses)}</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 +102,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 +110,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">{listToString(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">{listToString(implicitDepends)}</Typography></Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ 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>,
|
((params.row[field] as string[]) ?? []).map((item, index, items) =>
|
||||||
|
<React.Fragment key={`${item}-${index}`}>{item}{index < items.length - 1 && <br />}</React.Fragment>,
|
||||||
|
),
|
||||||
sortComparator: (left: string, right: string) => left.localeCompare(right),
|
sortComparator: (left: string, right: string) => left.localeCompare(right),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -84,7 +86,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 +115,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 +131,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"),
|
||||||
@@ -175,8 +177,8 @@ export default function PackageTable({ autoRefreshIntervals }: PackageTableProps
|
|||||||
}
|
}
|
||||||
table.setSelectedPackage(String(params.id));
|
table.setSelectedPackage(String(params.id));
|
||||||
}}
|
}}
|
||||||
|
autoHeight
|
||||||
sx={{
|
sx={{
|
||||||
flex: 1,
|
|
||||||
"& .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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,31 +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 { useLocalStorage } from "hooks/useLocalStorage";
|
import { useLocalStorage } from "hooks/useLocalStorage";
|
||||||
import { type Dispatch, type SetStateAction, useEffect, useState } from "react";
|
import { type Dispatch, type SetStateAction, useState } from "react";
|
||||||
|
|
||||||
interface AutoRefreshResult {
|
interface AutoRefreshResult {
|
||||||
interval: number;
|
interval: number;
|
||||||
|
paused: boolean;
|
||||||
setInterval: Dispatch<SetStateAction<number>>;
|
setInterval: Dispatch<SetStateAction<number>>;
|
||||||
setPaused: Dispatch<SetStateAction<boolean>>;
|
setPaused: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAutoRefresh(key: string, defaultInterval: number): AutoRefreshResult {
|
export function useAutoRefresh(key: string, defaultInterval: number = 0): AutoRefreshResult {
|
||||||
const storageKey = `ahriman-${key}`;
|
const [interval, setInterval] = useLocalStorage<number>(`ahriman-${key}`, defaultInterval);
|
||||||
const [interval, setInterval] = useLocalStorage<number>(storageKey, defaultInterval);
|
|
||||||
const [paused, setPaused] = useState(false);
|
const [paused, setPaused] = useState(false);
|
||||||
|
|
||||||
// Apply defaultInterval when it becomes available (e.g. after info endpoint loads)
|
|
||||||
// but only if the user hasn't explicitly set a preference
|
|
||||||
useEffect(() => {
|
|
||||||
if (defaultInterval > 0 && window.localStorage.getItem(storageKey) === null) {
|
|
||||||
setInterval(defaultInterval);
|
|
||||||
}
|
|
||||||
}, [storageKey, defaultInterval, setInterval]);
|
|
||||||
|
|
||||||
const effectiveInterval = paused ? 0 : interval;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
interval: effectiveInterval,
|
interval,
|
||||||
|
paused,
|
||||||
setInterval,
|
setInterval,
|
||||||
setPaused,
|
setPaused,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,7 +23,7 @@ export function useLocalStorage<T>(key: string, initialValue: T): [T, Dispatch<S
|
|||||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||||
try {
|
try {
|
||||||
const item = window.localStorage.getItem(key);
|
const item = window.localStorage.getItem(key);
|
||||||
return item !== null ? (JSON.parse(item) as T) : initialValue;
|
return item ? (JSON.parse(item) as T) : initialValue;
|
||||||
} catch {
|
} catch {
|
||||||
return initialValue;
|
return initialValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,14 +46,14 @@ 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,
|
||||||
});
|
});
|
||||||
|
|
||||||
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,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rows = useMemo(() => packages.map(descriptor => new PackageRow(descriptor)), [packages]);
|
const rows = useMemo(() => packages.map(descriptor => new PackageRow(descriptor)), [packages]);
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,24 +19,31 @@
|
|||||||
*/
|
*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Array<T> {
|
|
||||||
unique(): T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Date {
|
interface Date {
|
||||||
toISOStringShort(): string;
|
toISOStringShort(): string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.prototype.unique = function <T>(): T[] {
|
|
||||||
return [...new Set<T>(this)].sort();
|
|
||||||
};
|
|
||||||
|
|
||||||
// custom formatter to print pretty date, because there is no builtin for this
|
// custom formatter to print pretty date, because there is no builtin for this
|
||||||
Date.prototype.toISOStringShort = function (): string {
|
Date.prototype.toISOStringShort = function (): string {
|
||||||
const pad: (num: number) => string = num => String(num).padStart(2, "0");
|
const pad: (num: number) => string = num => String(num).padStart(2, "0");
|
||||||
|
|||||||
@@ -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.0rc5
|
pkgver=2.20.0rc4
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
|
|||||||
@@ -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\-06" "ahriman 2.20.0rc5" "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.0rc5"
|
__version__ = "2.20.0rc4"
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class Task(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
command = [self.build_command, "-r", str(self.paths.chroot)]
|
command = [self.build_command, "-r", str(self.paths.chroot)]
|
||||||
command.extend(self.archbuild_flags)
|
command.extend(self.archbuild_flags)
|
||||||
command.extend(["--", "-D", str(self.paths.archive)] + self.makechrootpkg_flags)
|
command.extend(["--"] + self.makechrootpkg_flags)
|
||||||
command.extend(["--"] + self.makepkg_flags)
|
command.extend(["--"] + self.makepkg_flags)
|
||||||
if dry_run:
|
if dry_run:
|
||||||
command.extend(["--nobuild"])
|
command.extend(["--nobuild"])
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -53,10 +53,7 @@ def test_build(task_ahriman: Task, mocker: MockerFixture) -> None:
|
|||||||
|
|
||||||
assert task_ahriman.build(local) == [task_ahriman.package.base]
|
assert task_ahriman.build(local) == [task_ahriman.package.base]
|
||||||
check_output_mock.assert_called_once_with(
|
check_output_mock.assert_called_once_with(
|
||||||
"extra-x86_64-build",
|
"extra-x86_64-build", "-r", str(task_ahriman.paths.chroot), "--", "--", "--skippgpcheck",
|
||||||
"-r", str(task_ahriman.paths.chroot),
|
|
||||||
"--", "-D", str(task_ahriman.paths.archive),
|
|
||||||
"--", "--skippgpcheck",
|
|
||||||
exception=pytest.helpers.anyvar(int),
|
exception=pytest.helpers.anyvar(int),
|
||||||
cwd=local,
|
cwd=local,
|
||||||
logger=task_ahriman.logger,
|
logger=task_ahriman.logger,
|
||||||
@@ -79,10 +76,7 @@ def test_build_environment(task_ahriman: Task, mocker: MockerFixture) -> None:
|
|||||||
|
|
||||||
task_ahriman.build(local, **environment, empty=None)
|
task_ahriman.build(local, **environment, empty=None)
|
||||||
check_output_mock.assert_called_once_with(
|
check_output_mock.assert_called_once_with(
|
||||||
"extra-x86_64-build",
|
"extra-x86_64-build", "-r", str(task_ahriman.paths.chroot), "--", "--", "--skippgpcheck",
|
||||||
"-r", str(task_ahriman.paths.chroot),
|
|
||||||
"--", "-D", str(task_ahriman.paths.archive),
|
|
||||||
"--", "--skippgpcheck",
|
|
||||||
exception=pytest.helpers.anyvar(int),
|
exception=pytest.helpers.anyvar(int),
|
||||||
cwd=local,
|
cwd=local,
|
||||||
logger=task_ahriman.logger,
|
logger=task_ahriman.logger,
|
||||||
@@ -102,11 +96,7 @@ def test_build_dry_run(task_ahriman: Task, mocker: MockerFixture) -> None:
|
|||||||
|
|
||||||
assert task_ahriman.build(local, dry_run=True) == [task_ahriman.package.base]
|
assert task_ahriman.build(local, dry_run=True) == [task_ahriman.package.base]
|
||||||
check_output_mock.assert_called_once_with(
|
check_output_mock.assert_called_once_with(
|
||||||
"extra-x86_64-build",
|
"extra-x86_64-build", "-r", str(task_ahriman.paths.chroot), "--", "--", "--skippgpcheck", "--nobuild",
|
||||||
"-r", str(task_ahriman.paths.chroot),
|
|
||||||
"--", "-D", str(task_ahriman.paths.archive),
|
|
||||||
"--", "--skippgpcheck",
|
|
||||||
"--nobuild",
|
|
||||||
exception=pytest.helpers.anyvar(int),
|
exception=pytest.helpers.anyvar(int),
|
||||||
cwd=local,
|
cwd=local,
|
||||||
logger=task_ahriman.logger,
|
logger=task_ahriman.logger,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user