mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-03-04 01:09:48 +00:00
Compare commits
1 Commits
feature/re
...
2.17.2
| Author | SHA1 | Date | |
|---|---|---|---|
| e0f1563f62 |
6
.bandit-test.yml
Normal file
6
.bandit-test.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
skips:
|
||||||
|
- B101
|
||||||
|
- B104
|
||||||
|
- B105
|
||||||
|
- B106
|
||||||
|
- B404
|
||||||
@@ -12,6 +12,3 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.pyd
|
*.pyd
|
||||||
*.pyo
|
*.pyo
|
||||||
|
|
||||||
node_modules/
|
|
||||||
package-lock.json
|
|
||||||
|
|||||||
17
.github/workflows/docker.yml
vendored
17
.github/workflows/docker.yml
vendored
@@ -7,11 +7,6 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
- '!*rc*'
|
- '!*rc*'
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-image:
|
docker-image:
|
||||||
@@ -22,18 +17,18 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: docker/setup-qemu-action@v3
|
- uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Login to docker hub
|
- name: Login to docker hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to github container registry
|
- name: Login to github container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -41,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Extract docker metadata
|
- name: Extract docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
arcan1s/ahriman
|
arcan1s/ahriman
|
||||||
@@ -51,7 +46,7 @@ jobs:
|
|||||||
type=edge
|
type=edge
|
||||||
|
|
||||||
- name: Build an image and push
|
- name: Build an image and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
10
.github/workflows/regress.yml
vendored
10
.github/workflows/regress.yml
vendored
@@ -1,12 +1,6 @@
|
|||||||
name: Regress
|
name: Regress
|
||||||
|
|
||||||
on:
|
on: workflow_dispatch
|
||||||
schedule:
|
|
||||||
- cron: 1 0 * * 0
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-regress-tests:
|
run-regress-tests:
|
||||||
@@ -37,6 +31,8 @@ jobs:
|
|||||||
- repo:/var/lib/ahriman
|
- repo:/var/lib/ahriman
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- run: pacman -Sy
|
- run: pacman -Sy
|
||||||
|
|
||||||
- name: Init repository
|
- name: Init repository
|
||||||
|
|||||||
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@@ -5,24 +5,13 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
make-release:
|
make-release:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
container:
|
|
||||||
image: archlinux:base
|
|
||||||
options: -w /build
|
|
||||||
volumes:
|
|
||||||
- ${{ github.workspace }}:/build
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- run: pacman --noconfirm -Syu base-devel git python-tox
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Extract version
|
- name: Extract version
|
||||||
id: version
|
id: version
|
||||||
@@ -35,13 +24,18 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
filter: 'Release \d+\.\d+\.\d+'
|
filter: 'Release \d+\.\d+\.\d+'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
uses: ConorMacBride/install-package@v1.1.0
|
||||||
|
with:
|
||||||
|
apt: tox
|
||||||
|
|
||||||
- name: Create archive
|
- name: Create archive
|
||||||
run: tox -e archive
|
run: tox -e archive
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ steps.version.outputs.VERSION }}
|
VERSION: ${{ steps.version.outputs.VERSION }}
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
body: |
|
body: |
|
||||||
${{ steps.changelog.outputs.compareurl }}
|
${{ steps.changelog.outputs.compareurl }}
|
||||||
|
|||||||
2
.github/workflows/setup.sh
vendored
2
.github/workflows/setup.sh
vendored
@@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never
|
|||||||
# refresh the image
|
# refresh the image
|
||||||
pacman -Syyu --noconfirm
|
pacman -Syyu --noconfirm
|
||||||
# main dependencies
|
# main dependencies
|
||||||
pacman -S --noconfirm devtools git npm pyalpm python-bcrypt python-filelock python-inflection python-pyelftools python-requests python-systemd sudo
|
pacman -S --noconfirm devtools git pyalpm python-bcrypt python-inflection python-pyelftools python-requests python-systemd sudo
|
||||||
# make dependencies
|
# make dependencies
|
||||||
pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
|
pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
|
||||||
# optional dependencies
|
# optional dependencies
|
||||||
|
|||||||
8
.github/workflows/setup.yml
vendored
8
.github/workflows/setup.yml
vendored
@@ -7,10 +7,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-setup-minimal:
|
run-setup-minimal:
|
||||||
@@ -24,7 +20,7 @@ jobs:
|
|||||||
- ${{ github.workspace }}:/build
|
- ${{ github.workspace }}:/build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup the minimal service in arch linux container
|
- name: Setup the minimal service in arch linux container
|
||||||
run: .github/workflows/setup.sh minimal
|
run: .github/workflows/setup.sh minimal
|
||||||
@@ -40,7 +36,7 @@ jobs:
|
|||||||
options: --privileged -w /build
|
options: --privileged -w /build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup the service in arch linux container
|
- name: Setup the service in arch linux container
|
||||||
run: .github/workflows/setup.sh
|
run: .github/workflows/setup.sh
|
||||||
|
|||||||
10
.github/workflows/tests.sh
vendored
Executable file
10
.github/workflows/tests.sh
vendored
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Install dependencies and run test in container
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# install dependencies
|
||||||
|
pacman --noconfirm -Syyu base-devel python-tox
|
||||||
|
|
||||||
|
# run test and check targets
|
||||||
|
tox
|
||||||
19
.github/workflows/tests.yml
vendored
19
.github/workflows/tests.yml
vendored
@@ -9,10 +9,6 @@ on:
|
|||||||
- master
|
- master
|
||||||
schedule:
|
schedule:
|
||||||
- cron: 1 0 * * *
|
- cron: 1 0 * * *
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-tests:
|
run-tests:
|
||||||
@@ -26,16 +22,7 @@ jobs:
|
|||||||
- ${{ github.workspace }}:/build
|
- ${{ github.workspace }}:/build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- run: pacman --noconfirm -Syu base-devel git npm python-tox
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- run: git config --global --add safe.directory *
|
- name: Run check and tests in arch linux container
|
||||||
|
run: .github/workflows/tests.sh
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run check and tests
|
|
||||||
run: tox
|
|
||||||
|
|
||||||
- name: Generate documentation and check if there are untracked changes
|
|
||||||
run: |
|
|
||||||
tox -e docs
|
|
||||||
[ -z "$(git status --porcelain docs/*.rst)" ]
|
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -99,9 +99,3 @@ status_cache.json
|
|||||||
*.db
|
*.db
|
||||||
|
|
||||||
docs/html/
|
docs/html/
|
||||||
|
|
||||||
# Frontend
|
|
||||||
node_modules/
|
|
||||||
package-lock.json
|
|
||||||
package/share/ahriman/templates/static/index.js
|
|
||||||
package/share/ahriman/templates/static/index.css
|
|
||||||
|
|||||||
45
.pylint.toml
45
.pylint.toml
@@ -1,45 +0,0 @@
|
|||||||
[tool.pylint.main]
|
|
||||||
init-hook = "sys.path.append('tools')"
|
|
||||||
load-plugins = [
|
|
||||||
"pylint.extensions.docparams",
|
|
||||||
"pylint.extensions.bad_builtin",
|
|
||||||
"pylint_plugins.definition_order",
|
|
||||||
"pylint_plugins.import_order",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.pylint.classes]
|
|
||||||
bad-functions = [
|
|
||||||
"print",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.pylint.design]
|
|
||||||
max-parents = 15
|
|
||||||
|
|
||||||
[tool.pylint."messages control"]
|
|
||||||
disable = [
|
|
||||||
"raw-checker-failed",
|
|
||||||
"bad-inline-option",
|
|
||||||
"locally-disabled",
|
|
||||||
"file-ignored",
|
|
||||||
"suppressed-message",
|
|
||||||
"useless-suppression",
|
|
||||||
"deprecated-pragma",
|
|
||||||
"use-symbolic-message-instead",
|
|
||||||
"use-implicit-booleaness-not-comparison-to-string",
|
|
||||||
"use-implicit-booleaness-not-comparison-to-zero",
|
|
||||||
"missing-module-docstring",
|
|
||||||
"line-too-long",
|
|
||||||
"no-name-in-module",
|
|
||||||
"import-outside-toplevel",
|
|
||||||
"invalid-name",
|
|
||||||
"raise-missing-from",
|
|
||||||
"wrong-import-order",
|
|
||||||
"too-few-public-methods",
|
|
||||||
"too-many-instance-attributes",
|
|
||||||
"broad-exception-caught",
|
|
||||||
"fixme",
|
|
||||||
"too-many-arguments",
|
|
||||||
"duplicate-code",
|
|
||||||
"cyclic-import",
|
|
||||||
"too-many-positional-arguments",
|
|
||||||
]
|
|
||||||
651
.pylintrc
Normal file
651
.pylintrc
Normal file
@@ -0,0 +1,651 @@
|
|||||||
|
[MAIN]
|
||||||
|
|
||||||
|
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||||
|
# 3 compatible code, which means that the block might have code that exists
|
||||||
|
# only in one or another interpreter, leading to false positives when analysed.
|
||||||
|
analyse-fallback-blocks=no
|
||||||
|
|
||||||
|
# Clear in-memory caches upon conclusion of linting. Useful if running pylint
|
||||||
|
# in a server-like mode.
|
||||||
|
clear-cache-post-run=no
|
||||||
|
|
||||||
|
# Load and enable all available extensions. Use --list-extensions to see a list
|
||||||
|
# all available extensions.
|
||||||
|
#enable-all-extensions=
|
||||||
|
|
||||||
|
# In error mode, messages with a category besides ERROR or FATAL are
|
||||||
|
# suppressed, and no reports are done by default. Error mode is compatible with
|
||||||
|
# disabling specific errors.
|
||||||
|
#errors-only=
|
||||||
|
|
||||||
|
# Always return a 0 (non-error) status code, even if lint errors are found.
|
||||||
|
# This is primarily useful in continuous integration scripts.
|
||||||
|
#exit-zero=
|
||||||
|
|
||||||
|
# A comma-separated list of package or module names from where C extensions may
|
||||||
|
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||||
|
# run arbitrary code.
|
||||||
|
extension-pkg-allow-list=
|
||||||
|
|
||||||
|
# A comma-separated list of package or module names from where C extensions may
|
||||||
|
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||||
|
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
|
||||||
|
# for backward compatibility.)
|
||||||
|
extension-pkg-whitelist=
|
||||||
|
|
||||||
|
# Return non-zero exit code if any of these messages/categories are detected,
|
||||||
|
# even if score is above --fail-under value. Syntax same as enable. Messages
|
||||||
|
# specified are enabled, while categories only check already-enabled messages.
|
||||||
|
fail-on=
|
||||||
|
|
||||||
|
# Specify a score threshold under which the program will exit with error.
|
||||||
|
fail-under=10
|
||||||
|
|
||||||
|
# Interpret the stdin as a python script, whose filename needs to be passed as
|
||||||
|
# the module_or_package argument.
|
||||||
|
#from-stdin=
|
||||||
|
|
||||||
|
# Files or directories to be skipped. They should be base names, not paths.
|
||||||
|
ignore=CVS
|
||||||
|
|
||||||
|
# Add files or directories matching the regular expressions patterns to the
|
||||||
|
# ignore-list. The regex matches against paths and can be in Posix or Windows
|
||||||
|
# format. Because '\\' represents the directory delimiter on Windows systems,
|
||||||
|
# it can't be used as an escape character.
|
||||||
|
ignore-paths=
|
||||||
|
|
||||||
|
# Files or directories matching the regular expression patterns are skipped.
|
||||||
|
# The regex matches against base names, not paths. The default value ignores
|
||||||
|
# Emacs file locks
|
||||||
|
ignore-patterns=^\.#
|
||||||
|
|
||||||
|
# List of module names for which member attributes should not be checked
|
||||||
|
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||||
|
# and thus existing member attributes cannot be deduced by static analysis). It
|
||||||
|
# supports qualified module names, as well as Unix pattern matching.
|
||||||
|
ignored-modules=
|
||||||
|
|
||||||
|
# Python code to execute, usually for sys.path manipulation such as
|
||||||
|
# pygtk.require().
|
||||||
|
init-hook='import sys; sys.path.append("pylint_plugins")'
|
||||||
|
|
||||||
|
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
|
||||||
|
# number of processors available to use, and will cap the count on Windows to
|
||||||
|
# avoid hangs.
|
||||||
|
jobs=1
|
||||||
|
|
||||||
|
# Control the amount of potential inferred values when inferring a single
|
||||||
|
# object. This can help the performance when dealing with large functions or
|
||||||
|
# complex, nested conditions.
|
||||||
|
limit-inference-results=100
|
||||||
|
|
||||||
|
# List of plugins (as comma separated values of python module names) to load,
|
||||||
|
# usually to register additional checkers.
|
||||||
|
load-plugins=pylint.extensions.docparams,
|
||||||
|
pylint.extensions.bad_builtin,
|
||||||
|
definition_order,
|
||||||
|
import_order,
|
||||||
|
|
||||||
|
# Pickle collected data for later comparisons.
|
||||||
|
persistent=yes
|
||||||
|
|
||||||
|
# Minimum Python version to use for version dependent checks. Will default to
|
||||||
|
# the version used to run pylint.
|
||||||
|
py-version=3.11
|
||||||
|
|
||||||
|
# Discover python modules and packages in the file system subtree.
|
||||||
|
recursive=no
|
||||||
|
|
||||||
|
# Add paths to the list of the source roots. Supports globbing patterns. The
|
||||||
|
# source root is an absolute path or a path relative to the current working
|
||||||
|
# directory used to determine a package namespace for modules located under the
|
||||||
|
# source root.
|
||||||
|
source-roots=
|
||||||
|
|
||||||
|
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||||
|
# user-friendly hints instead of false-positive error messages.
|
||||||
|
suggestion-mode=yes
|
||||||
|
|
||||||
|
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||||
|
# active Python interpreter and may run arbitrary code.
|
||||||
|
unsafe-load-any-extension=no
|
||||||
|
|
||||||
|
# In verbose mode, extra non-checker-related info will be displayed.
|
||||||
|
#verbose=
|
||||||
|
|
||||||
|
|
||||||
|
[BASIC]
|
||||||
|
|
||||||
|
# Naming style matching correct argument names.
|
||||||
|
argument-naming-style=snake_case
|
||||||
|
|
||||||
|
# Regular expression matching correct argument names. Overrides argument-
|
||||||
|
# naming-style. If left empty, argument names will be checked with the set
|
||||||
|
# naming style.
|
||||||
|
#argument-rgx=
|
||||||
|
|
||||||
|
# Naming style matching correct attribute names.
|
||||||
|
attr-naming-style=snake_case
|
||||||
|
|
||||||
|
# Regular expression matching correct attribute names. Overrides attr-naming-
|
||||||
|
# style. If left empty, attribute names will be checked with the set naming
|
||||||
|
# style.
|
||||||
|
#attr-rgx=
|
||||||
|
|
||||||
|
bad-functions=print,
|
||||||
|
|
||||||
|
# Bad variable names which should always be refused, separated by a comma.
|
||||||
|
bad-names=foo,
|
||||||
|
bar,
|
||||||
|
baz,
|
||||||
|
toto,
|
||||||
|
tutu,
|
||||||
|
tata
|
||||||
|
|
||||||
|
# Bad variable names regexes, separated by a comma. If names match any regex,
|
||||||
|
# they will always be refused
|
||||||
|
bad-names-rgxs=
|
||||||
|
|
||||||
|
# Naming style matching correct class attribute names.
|
||||||
|
class-attribute-naming-style=any
|
||||||
|
|
||||||
|
# Regular expression matching correct class attribute names. Overrides class-
|
||||||
|
# attribute-naming-style. If left empty, class attribute names will be checked
|
||||||
|
# with the set naming style.
|
||||||
|
#class-attribute-rgx=
|
||||||
|
|
||||||
|
# Naming style matching correct class constant names.
|
||||||
|
class-const-naming-style=UPPER_CASE
|
||||||
|
|
||||||
|
# Regular expression matching correct class constant names. Overrides class-
|
||||||
|
# const-naming-style. If left empty, class constant names will be checked with
|
||||||
|
# the set naming style.
|
||||||
|
#class-const-rgx=
|
||||||
|
|
||||||
|
# Naming style matching correct class names.
|
||||||
|
class-naming-style=PascalCase
|
||||||
|
|
||||||
|
# Regular expression matching correct class names. Overrides class-naming-
|
||||||
|
# style. If left empty, class names will be checked with the set naming style.
|
||||||
|
#class-rgx=
|
||||||
|
|
||||||
|
# Naming style matching correct constant names.
|
||||||
|
const-naming-style=UPPER_CASE
|
||||||
|
|
||||||
|
# Regular expression matching correct constant names. Overrides const-naming-
|
||||||
|
# style. If left empty, constant names will be checked with the set naming
|
||||||
|
# style.
|
||||||
|
#const-rgx=
|
||||||
|
|
||||||
|
# Minimum line length for functions/classes that require docstrings, shorter
|
||||||
|
# ones are exempt.
|
||||||
|
docstring-min-length=-1
|
||||||
|
|
||||||
|
# Naming style matching correct function names.
|
||||||
|
function-naming-style=snake_case
|
||||||
|
|
||||||
|
# Regular expression matching correct function names. Overrides function-
|
||||||
|
# naming-style. If left empty, function names will be checked with the set
|
||||||
|
# naming style.
|
||||||
|
#function-rgx=
|
||||||
|
|
||||||
|
# Good variable names which should always be accepted, separated by a comma.
|
||||||
|
good-names=i,
|
||||||
|
j,
|
||||||
|
k,
|
||||||
|
ex,
|
||||||
|
Run,
|
||||||
|
_
|
||||||
|
|
||||||
|
# Good variable names regexes, separated by a comma. If names match any regex,
|
||||||
|
# they will always be accepted
|
||||||
|
good-names-rgxs=
|
||||||
|
|
||||||
|
# Include a hint for the correct naming format with invalid-name.
|
||||||
|
include-naming-hint=no
|
||||||
|
|
||||||
|
# Naming style matching correct inline iteration names.
|
||||||
|
inlinevar-naming-style=any
|
||||||
|
|
||||||
|
# Regular expression matching correct inline iteration names. Overrides
|
||||||
|
# inlinevar-naming-style. If left empty, inline iteration names will be checked
|
||||||
|
# with the set naming style.
|
||||||
|
#inlinevar-rgx=
|
||||||
|
|
||||||
|
# Naming style matching correct method names.
|
||||||
|
method-naming-style=snake_case
|
||||||
|
|
||||||
|
# Regular expression matching correct method names. Overrides method-naming-
|
||||||
|
# style. If left empty, method names will be checked with the set naming style.
|
||||||
|
#method-rgx=
|
||||||
|
|
||||||
|
# Naming style matching correct module names.
|
||||||
|
module-naming-style=snake_case
|
||||||
|
|
||||||
|
# Regular expression matching correct module names. Overrides module-naming-
|
||||||
|
# style. If left empty, module names will be checked with the set naming style.
|
||||||
|
#module-rgx=
|
||||||
|
|
||||||
|
# Colon-delimited sets of names that determine each other's naming style when
|
||||||
|
# the name regexes allow several styles.
|
||||||
|
name-group=
|
||||||
|
|
||||||
|
# Regular expression which should only match function or class names that do
|
||||||
|
# not require a docstring.
|
||||||
|
no-docstring-rgx=
|
||||||
|
|
||||||
|
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||||
|
# to this list to register other decorators that produce valid properties.
|
||||||
|
# These decorators are taken in consideration only for invalid-name.
|
||||||
|
property-classes=abc.abstractproperty
|
||||||
|
|
||||||
|
# Regular expression matching correct type alias names. If left empty, type
|
||||||
|
# alias names will be checked with the set naming style.
|
||||||
|
#typealias-rgx=
|
||||||
|
|
||||||
|
# Regular expression matching correct type variable names. If left empty, type
|
||||||
|
# variable names will be checked with the set naming style.
|
||||||
|
#typevar-rgx=
|
||||||
|
|
||||||
|
# Naming style matching correct variable names.
|
||||||
|
variable-naming-style=snake_case
|
||||||
|
|
||||||
|
# Regular expression matching correct variable names. Overrides variable-
|
||||||
|
# naming-style. If left empty, variable names will be checked with the set
|
||||||
|
# naming style.
|
||||||
|
#variable-rgx=
|
||||||
|
|
||||||
|
|
||||||
|
[CLASSES]
|
||||||
|
|
||||||
|
# Warn about protected attribute access inside special methods
|
||||||
|
check-protected-access-in-special-methods=no
|
||||||
|
|
||||||
|
# List of method names used to declare (i.e. assign) instance attributes.
|
||||||
|
defining-attr-methods=__init__,
|
||||||
|
__new__,
|
||||||
|
setUp,
|
||||||
|
asyncSetUp,
|
||||||
|
__post_init__
|
||||||
|
|
||||||
|
# List of member names, which should be excluded from the protected access
|
||||||
|
# warning.
|
||||||
|
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
|
||||||
|
|
||||||
|
# List of valid names for the first argument in a class method.
|
||||||
|
valid-classmethod-first-arg=cls
|
||||||
|
|
||||||
|
# List of valid names for the first argument in a metaclass class method.
|
||||||
|
valid-metaclass-classmethod-first-arg=mcs
|
||||||
|
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
|
||||||
|
# List of regular expressions of class ancestor names to ignore when counting
|
||||||
|
# public methods (see R0903)
|
||||||
|
exclude-too-few-public-methods=
|
||||||
|
|
||||||
|
# List of qualified class names to ignore when counting class parents (see
|
||||||
|
# R0901)
|
||||||
|
ignored-parents=
|
||||||
|
|
||||||
|
# Maximum number of arguments for function / method.
|
||||||
|
max-args=5
|
||||||
|
|
||||||
|
# Maximum number of attributes for a class (see R0902).
|
||||||
|
max-attributes=7
|
||||||
|
|
||||||
|
# Maximum number of boolean expressions in an if statement (see R0916).
|
||||||
|
max-bool-expr=5
|
||||||
|
|
||||||
|
# Maximum number of branch for function / method body.
|
||||||
|
max-branches=12
|
||||||
|
|
||||||
|
# Maximum number of locals for function / method body.
|
||||||
|
max-locals=15
|
||||||
|
|
||||||
|
# Maximum number of parents for a class (see R0901).
|
||||||
|
max-parents=15
|
||||||
|
|
||||||
|
# Maximum number of public methods for a class (see R0904).
|
||||||
|
max-public-methods=20
|
||||||
|
|
||||||
|
# Maximum number of return / yield for function / method body.
|
||||||
|
max-returns=6
|
||||||
|
|
||||||
|
# Maximum number of statements in function / method body.
|
||||||
|
max-statements=50
|
||||||
|
|
||||||
|
# Minimum number of public methods for a class (see R0903).
|
||||||
|
min-public-methods=2
|
||||||
|
|
||||||
|
|
||||||
|
[EXCEPTIONS]
|
||||||
|
|
||||||
|
# Exceptions that will emit a warning when caught.
|
||||||
|
overgeneral-exceptions=builtins.BaseException,builtins.Exception
|
||||||
|
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
|
||||||
|
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||||
|
expected-line-ending-format=
|
||||||
|
|
||||||
|
# Regexp for a line that is allowed to be longer than the limit.
|
||||||
|
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||||
|
|
||||||
|
# Number of spaces of indent required inside a hanging or continued line.
|
||||||
|
indent-after-paren=4
|
||||||
|
|
||||||
|
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||||
|
# tab).
|
||||||
|
indent-string=' '
|
||||||
|
|
||||||
|
# Maximum number of characters on a single line.
|
||||||
|
max-line-length=100
|
||||||
|
|
||||||
|
# Maximum number of lines in a module.
|
||||||
|
max-module-lines=1000
|
||||||
|
|
||||||
|
# Allow the body of a class to be on the same line as the declaration if body
|
||||||
|
# contains single statement.
|
||||||
|
single-line-class-stmt=no
|
||||||
|
|
||||||
|
# Allow the body of an if to be on the same line as the test if there is no
|
||||||
|
# else.
|
||||||
|
single-line-if-stmt=no
|
||||||
|
|
||||||
|
|
||||||
|
[IMPORTS]
|
||||||
|
|
||||||
|
# List of modules that can be imported at any level, not just the top level
|
||||||
|
# one.
|
||||||
|
allow-any-import-level=
|
||||||
|
|
||||||
|
# Allow explicit reexports by alias from a package __init__.
|
||||||
|
allow-reexport-from-package=no
|
||||||
|
|
||||||
|
# Allow wildcard imports from modules that define __all__.
|
||||||
|
allow-wildcard-with-all=no
|
||||||
|
|
||||||
|
# Deprecated modules which should not be used, separated by a comma.
|
||||||
|
deprecated-modules=
|
||||||
|
|
||||||
|
# Output a graph (.gv or any supported image format) of external dependencies
|
||||||
|
# to the given file (report RP0402 must not be disabled).
|
||||||
|
ext-import-graph=
|
||||||
|
|
||||||
|
# Output a graph (.gv or any supported image format) of all (i.e. internal and
|
||||||
|
# external) dependencies to the given file (report RP0402 must not be
|
||||||
|
# disabled).
|
||||||
|
import-graph=
|
||||||
|
|
||||||
|
# Output a graph (.gv or any supported image format) of internal dependencies
|
||||||
|
# to the given file (report RP0402 must not be disabled).
|
||||||
|
int-import-graph=
|
||||||
|
|
||||||
|
# Force import order to recognize a module as part of the standard
|
||||||
|
# compatibility libraries.
|
||||||
|
known-standard-library=
|
||||||
|
|
||||||
|
# Force import order to recognize a module as part of a third party library.
|
||||||
|
known-third-party=enchant
|
||||||
|
|
||||||
|
# Couples of modules and preferred modules, separated by a comma.
|
||||||
|
preferred-modules=
|
||||||
|
|
||||||
|
|
||||||
|
[LOGGING]
|
||||||
|
|
||||||
|
# The type of string formatting that logging methods do. `old` means using %
|
||||||
|
# formatting, `new` is for `{}` formatting.
|
||||||
|
logging-format-style=old
|
||||||
|
|
||||||
|
# Logging modules to check that the string format arguments are in logging
|
||||||
|
# function parameter format.
|
||||||
|
logging-modules=logging
|
||||||
|
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
|
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||||
|
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
|
||||||
|
# UNDEFINED.
|
||||||
|
confidence=HIGH,
|
||||||
|
CONTROL_FLOW,
|
||||||
|
INFERENCE,
|
||||||
|
INFERENCE_FAILURE,
|
||||||
|
UNDEFINED
|
||||||
|
|
||||||
|
# Disable the message, report, category or checker with the given id(s). You
|
||||||
|
# can either give multiple identifiers separated by comma (,) or put this
|
||||||
|
# option multiple times (only on the command line, not in the configuration
|
||||||
|
# file where it should appear only once). You can also use "--disable=all" to
|
||||||
|
# disable everything first and then re-enable specific checks. For example, if
|
||||||
|
# you want to run only the similarities checker, you can use "--disable=all
|
||||||
|
# --enable=similarities". If you want to run only the classes checker, but have
|
||||||
|
# no Warning level messages displayed, use "--disable=all --enable=classes
|
||||||
|
# --disable=W".
|
||||||
|
disable=raw-checker-failed,
|
||||||
|
bad-inline-option,
|
||||||
|
locally-disabled,
|
||||||
|
file-ignored,
|
||||||
|
suppressed-message,
|
||||||
|
useless-suppression,
|
||||||
|
deprecated-pragma,
|
||||||
|
use-symbolic-message-instead,
|
||||||
|
missing-module-docstring,
|
||||||
|
line-too-long,
|
||||||
|
no-name-in-module,
|
||||||
|
import-outside-toplevel,
|
||||||
|
invalid-name,
|
||||||
|
raise-missing-from,
|
||||||
|
wrong-import-order,
|
||||||
|
too-few-public-methods,
|
||||||
|
too-many-instance-attributes,
|
||||||
|
broad-except,
|
||||||
|
fixme,
|
||||||
|
too-many-arguments,
|
||||||
|
duplicate-code,
|
||||||
|
cyclic-import,
|
||||||
|
too-many-positional-arguments,
|
||||||
|
|
||||||
|
# Enable the message, report, category or checker with the given id(s). You can
|
||||||
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
# multiple time (only on the command line, not in the configuration file where
|
||||||
|
# it should appear only once). See also the "--disable" option for examples.
|
||||||
|
enable=c-extension-no-member
|
||||||
|
|
||||||
|
|
||||||
|
[METHOD_ARGS]
|
||||||
|
|
||||||
|
# List of qualified names (i.e., library.method) which require a timeout
|
||||||
|
# parameter e.g. 'requests.api.get,requests.api.post'
|
||||||
|
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
|
||||||
|
|
||||||
|
|
||||||
|
[MISCELLANEOUS]
|
||||||
|
|
||||||
|
# List of note tags to take in consideration, separated by a comma.
|
||||||
|
notes=FIXME,
|
||||||
|
XXX,
|
||||||
|
TODO
|
||||||
|
|
||||||
|
# Regular expression of note tags to take in consideration.
|
||||||
|
notes-rgx=
|
||||||
|
|
||||||
|
|
||||||
|
[REFACTORING]
|
||||||
|
|
||||||
|
# Maximum number of nested blocks for function / method body
|
||||||
|
max-nested-blocks=5
|
||||||
|
|
||||||
|
# Complete name of functions that never returns. When checking for
|
||||||
|
# inconsistent-return-statements if a never returning function is called then
|
||||||
|
# it will be considered as an explicit return statement and no message will be
|
||||||
|
# printed.
|
||||||
|
never-returning-functions=sys.exit,argparse.parse_error
|
||||||
|
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
|
||||||
|
# Python expression which should return a score less than or equal to 10. You
|
||||||
|
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
|
||||||
|
# 'convention', and 'info' which contain the number of messages in each
|
||||||
|
# category, as well as 'statement' which is the total number of statements
|
||||||
|
# analyzed. This score is used by the global evaluation report (RP0004).
|
||||||
|
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
|
||||||
|
|
||||||
|
# Template used to display messages. This is a python new-style format string
|
||||||
|
# used to format the message information. See doc for all details.
|
||||||
|
msg-template=
|
||||||
|
|
||||||
|
# Set the output format. Available formats are text, parseable, colorized, json
|
||||||
|
# and msvs (visual studio). You can also give a reporter class, e.g.
|
||||||
|
# mypackage.mymodule.MyReporterClass.
|
||||||
|
#output-format=
|
||||||
|
|
||||||
|
# Tells whether to display a full report or only the messages.
|
||||||
|
reports=no
|
||||||
|
|
||||||
|
# Activate the evaluation score.
|
||||||
|
score=yes
|
||||||
|
|
||||||
|
|
||||||
|
[SIMILARITIES]
|
||||||
|
|
||||||
|
# Comments are removed from the similarity computation
|
||||||
|
ignore-comments=yes
|
||||||
|
|
||||||
|
# Docstrings are removed from the similarity computation
|
||||||
|
ignore-docstrings=yes
|
||||||
|
|
||||||
|
# Imports are removed from the similarity computation
|
||||||
|
ignore-imports=yes
|
||||||
|
|
||||||
|
# Signatures are removed from the similarity computation
|
||||||
|
ignore-signatures=yes
|
||||||
|
|
||||||
|
# Minimum lines number of a similarity.
|
||||||
|
min-similarity-lines=4
|
||||||
|
|
||||||
|
|
||||||
|
[SPELLING]
|
||||||
|
|
||||||
|
# Limits count of emitted suggestions for spelling mistakes.
|
||||||
|
max-spelling-suggestions=4
|
||||||
|
|
||||||
|
# Spelling dictionary name. No available dictionaries : You need to install
|
||||||
|
# both the python package and the system dependency for enchant to work..
|
||||||
|
spelling-dict=
|
||||||
|
|
||||||
|
# List of comma separated words that should be considered directives if they
|
||||||
|
# appear at the beginning of a comment and should not be checked.
|
||||||
|
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
||||||
|
|
||||||
|
# List of comma separated words that should not be checked.
|
||||||
|
spelling-ignore-words=
|
||||||
|
|
||||||
|
# A path to a file that contains the private dictionary; one word per line.
|
||||||
|
spelling-private-dict-file=
|
||||||
|
|
||||||
|
# Tells whether to store unknown words to the private dictionary (see the
|
||||||
|
# --spelling-private-dict-file option) instead of raising a message.
|
||||||
|
spelling-store-unknown-words=no
|
||||||
|
|
||||||
|
|
||||||
|
[STRING]
|
||||||
|
|
||||||
|
# This flag controls whether inconsistent-quotes generates a warning when the
|
||||||
|
# character used as a quote delimiter is used inconsistently within a module.
|
||||||
|
check-quote-consistency=no
|
||||||
|
|
||||||
|
# This flag controls whether the implicit-str-concat should generate a warning
|
||||||
|
# on implicit string concatenation in sequences defined over several lines.
|
||||||
|
check-str-concat-over-line-jumps=no
|
||||||
|
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
|
||||||
|
# List of decorators that produce context managers, such as
|
||||||
|
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||||
|
# produce valid context managers.
|
||||||
|
contextmanager-decorators=contextlib.contextmanager
|
||||||
|
|
||||||
|
# List of members which are set dynamically and missed by pylint inference
|
||||||
|
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||||
|
# expressions are accepted.
|
||||||
|
generated-members=
|
||||||
|
|
||||||
|
# Tells whether to warn about missing members when the owner of the attribute
|
||||||
|
# is inferred to be None.
|
||||||
|
ignore-none=yes
|
||||||
|
|
||||||
|
# This flag controls whether pylint should warn about no-member and similar
|
||||||
|
# checks whenever an opaque object is returned when inferring. The inference
|
||||||
|
# can return multiple potential results while evaluating a Python object, but
|
||||||
|
# some branches might not be evaluated, which results in partial inference. In
|
||||||
|
# that case, it might be useful to still emit no-member and other checks for
|
||||||
|
# the rest of the inferred objects.
|
||||||
|
ignore-on-opaque-inference=yes
|
||||||
|
|
||||||
|
# List of symbolic message names to ignore for Mixin members.
|
||||||
|
ignored-checks-for-mixins=no-member,
|
||||||
|
not-async-context-manager,
|
||||||
|
not-context-manager,
|
||||||
|
attribute-defined-outside-init
|
||||||
|
|
||||||
|
# List of class names for which member attributes should not be checked (useful
|
||||||
|
# for classes with dynamically set attributes). This supports the use of
|
||||||
|
# qualified names.
|
||||||
|
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
|
||||||
|
|
||||||
|
# Show a hint with possible names when a member name was not found. The aspect
|
||||||
|
# of finding the hint is based on edit distance.
|
||||||
|
missing-member-hint=yes
|
||||||
|
|
||||||
|
# The minimum edit distance a name should have in order to be considered a
|
||||||
|
# similar match for a missing member name.
|
||||||
|
missing-member-hint-distance=1
|
||||||
|
|
||||||
|
# The total number of similar names that should be taken in consideration when
|
||||||
|
# showing a hint for a missing member.
|
||||||
|
missing-member-max-choices=1
|
||||||
|
|
||||||
|
# Regex pattern to define which classes are considered mixins.
|
||||||
|
mixin-class-rgx=.*[Mm]ixin
|
||||||
|
|
||||||
|
# List of decorators that change the signature of a decorated function.
|
||||||
|
signature-mutators=
|
||||||
|
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
|
||||||
|
# List of additional names supposed to be defined in builtins. Remember that
|
||||||
|
# you should avoid defining new builtins when possible.
|
||||||
|
additional-builtins=
|
||||||
|
|
||||||
|
# Tells whether unused global variables should be treated as a violation.
|
||||||
|
allow-global-unused-variables=yes
|
||||||
|
|
||||||
|
# List of names allowed to shadow builtins
|
||||||
|
allowed-redefined-builtins=
|
||||||
|
|
||||||
|
# List of strings which can identify a callback function by name. A callback
|
||||||
|
# name must start or end with one of those strings.
|
||||||
|
callbacks=cb_,
|
||||||
|
_cb
|
||||||
|
|
||||||
|
# A regular expression matching the name of dummy variables (i.e. expected to
|
||||||
|
# not be used).
|
||||||
|
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
||||||
|
|
||||||
|
# Argument names that match this expression will be ignored.
|
||||||
|
ignored-argument-names=_.*|^ignored_|^unused_
|
||||||
|
|
||||||
|
# Tells whether we should check for unused import in __init__ files.
|
||||||
|
init-import=no
|
||||||
|
|
||||||
|
# List of qualified module names which can have objects that can redefine
|
||||||
|
# builtins.
|
||||||
|
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[pytest]
|
|
||||||
addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec
|
|
||||||
asyncio_default_fixture_loop_scope = function
|
|
||||||
asyncio_mode = auto
|
|
||||||
spec_test_format = {result} {docstring_summary}
|
|
||||||
@@ -9,7 +9,13 @@ build:
|
|||||||
|
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
- requirements: docs/requirements.txt
|
- method: pip
|
||||||
|
path: .
|
||||||
|
extra_requirements:
|
||||||
|
- docs
|
||||||
|
- s3
|
||||||
|
- validator
|
||||||
|
- web
|
||||||
|
|
||||||
formats:
|
formats:
|
||||||
- pdf
|
- pdf
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
>>> clazz = Clazz()
|
>>> clazz = Clazz()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CLAZZ_ATTRIBUTE: ClassVar[int] = 42
|
CLAZZ_ATTRIBUTE = 42
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -96,7 +96,6 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
* Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated.
|
* Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated.
|
||||||
* For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typing.Optional` (e.g. `str | None` instead of `Optional[str]`).
|
* For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typing.Optional` (e.g. `str | None` instead of `Optional[str]`).
|
||||||
* `classmethod` should (almost) always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead.
|
* `classmethod` should (almost) always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead.
|
||||||
* Class attributes must be decorated as `ClassVar[...]`.
|
|
||||||
* Recommended order of function definitions in class:
|
* Recommended order of function definitions in class:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -165,11 +164,6 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
|
|
||||||
# Blank line again and package imports
|
# Blank line again and package imports
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
# Multiline import example
|
|
||||||
from ahriman.core.database.operations import (
|
|
||||||
AuthOperations,
|
|
||||||
BuildOperations,
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* 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.
|
||||||
@@ -220,7 +214,6 @@ Again, the most checks can be performed by `tox` command, though some additional
|
|||||||
* It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced.
|
* It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced.
|
||||||
* On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release.
|
* On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release.
|
||||||
* Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise.
|
* Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise.
|
||||||
* `Generator` return type is not allowed. Generator functions must return generic `Iterator` object. Documentation should be described as `Yields`, however, because of pylint checks. Unfortunately, `Iterable` return type is not available for generators also, because of specific `contextlib.contextmanager` case.
|
|
||||||
|
|
||||||
### Other checks
|
### Other checks
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
|
|||||||
RUN pacman -S --noconfirm --asdeps \
|
RUN pacman -S --noconfirm --asdeps \
|
||||||
devtools \
|
devtools \
|
||||||
git \
|
git \
|
||||||
npm \
|
|
||||||
pyalpm \
|
pyalpm \
|
||||||
python-bcrypt \
|
python-bcrypt \
|
||||||
python-filelock \
|
|
||||||
python-inflection \
|
python-inflection \
|
||||||
python-pyelftools \
|
python-pyelftools \
|
||||||
python-requests \
|
python-requests \
|
||||||
@@ -42,7 +40,6 @@ RUN pacman -S --noconfirm --asdeps \
|
|||||||
pacman -S --noconfirm --asdeps \
|
pacman -S --noconfirm --asdeps \
|
||||||
git \
|
git \
|
||||||
python-aiohttp \
|
python-aiohttp \
|
||||||
python-aiohttp-openmetrics \
|
|
||||||
python-boto3 \
|
python-boto3 \
|
||||||
python-cerberus \
|
python-cerberus \
|
||||||
python-cryptography \
|
python-cryptography \
|
||||||
@@ -115,7 +112,6 @@ RUN pacman -S --noconfirm ahriman
|
|||||||
RUN pacman -S --noconfirm --asdeps \
|
RUN pacman -S --noconfirm --asdeps \
|
||||||
python-aioauth-client \
|
python-aioauth-client \
|
||||||
python-aiohttp-apispec-git \
|
python-aiohttp-apispec-git \
|
||||||
python-aiohttp-openmetrics \
|
|
||||||
python-aiohttp-security \
|
python-aiohttp-security \
|
||||||
python-aiohttp-session \
|
python-aiohttp-session \
|
||||||
python-boto3 \
|
python-boto3 \
|
||||||
|
|||||||
2904
docs/_static/architecture.dot
vendored
2904
docs/_static/architecture.dot
vendored
File diff suppressed because it is too large
Load Diff
@@ -100,14 +100,6 @@ ahriman.application.handlers.rebuild module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.application.handlers.reload module
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.application.handlers.reload
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.application.handlers.remove module
|
ahriman.application.handlers.remove module
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
ahriman.core.archive package
|
|
||||||
============================
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
ahriman.core.archive.archive\_tree module
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.archive.archive_tree
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.core.archive.archive\_trigger module
|
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.archive.archive_trigger
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.archive
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
@@ -12,14 +12,6 @@ ahriman.core.build\_tools.package\_archive module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.core.build\_tools.package\_version module
|
|
||||||
-------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.build_tools.package_version
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.core.build\_tools.sources module
|
ahriman.core.build\_tools.sources module
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -124,22 +124,6 @@ ahriman.core.database.migrations.m014\_auditlog module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.core.database.migrations.m015\_logs\_process\_id module
|
|
||||||
---------------------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.database.migrations.m015_logs_process_id
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.core.database.migrations.m016\_archive module
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.database.migrations.m016_archive
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|||||||
@@ -92,14 +92,6 @@ ahriman.core.formatters.repository\_printer module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.core.formatters.repository\_stats\_printer module
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.formatters.repository_stats_printer
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.core.formatters.status\_printer module
|
ahriman.core.formatters.status\_printer module
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
ahriman.core.housekeeping package
|
|
||||||
=================================
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
ahriman.core.housekeeping.archive\_rotation\_trigger module
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.housekeeping.archive_rotation_trigger
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.core.housekeeping.logs\_rotation\_trigger module
|
|
||||||
--------------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.housekeeping.logs_rotation_trigger
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.housekeeping
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
@@ -28,14 +28,6 @@ ahriman.core.repository.executor module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.core.repository.explorer module
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.repository.explorer
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.core.repository.package\_info module
|
ahriman.core.repository.package\_info module
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ Subpackages
|
|||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
ahriman.core.alpm
|
ahriman.core.alpm
|
||||||
ahriman.core.archive
|
|
||||||
ahriman.core.auth
|
ahriman.core.auth
|
||||||
ahriman.core.build_tools
|
ahriman.core.build_tools
|
||||||
ahriman.core.configuration
|
ahriman.core.configuration
|
||||||
@@ -16,7 +15,6 @@ Subpackages
|
|||||||
ahriman.core.distributed
|
ahriman.core.distributed
|
||||||
ahriman.core.formatters
|
ahriman.core.formatters
|
||||||
ahriman.core.gitremote
|
ahriman.core.gitremote
|
||||||
ahriman.core.housekeeping
|
|
||||||
ahriman.core.http
|
ahriman.core.http
|
||||||
ahriman.core.log
|
ahriman.core.log
|
||||||
ahriman.core.report
|
ahriman.core.report
|
||||||
|
|||||||
@@ -100,14 +100,6 @@ ahriman.models.log\_handler module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.models.log\_record module
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.models.log_record
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.models.log\_record\_id module
|
ahriman.models.log\_record\_id module
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
@@ -244,14 +236,6 @@ ahriman.models.repository\_paths module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.models.repository\_stats module
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.models.repository_stats
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.models.result module
|
ahriman.models.result module
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
@@ -268,14 +252,6 @@ ahriman.models.scan\_paths module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.models.series\_statistics module
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.models.series_statistics
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.models.sign\_settings module
|
ahriman.models.sign\_settings module
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,6 @@ ahriman.web.middlewares.exception\_handler module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.middlewares.metrics\_handler module
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.middlewares.metrics_handler
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|||||||
@@ -39,14 +39,6 @@ ahriman.web.routes module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.server\_info module
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.server_info
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.web module
|
ahriman.web.web module
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,6 @@ ahriman.web.schemas package
|
|||||||
Submodules
|
Submodules
|
||||||
----------
|
----------
|
||||||
|
|
||||||
ahriman.web.schemas.any\_schema module
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.any_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.aur\_package\_schema module
|
ahriman.web.schemas.aur\_package\_schema module
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
@@ -20,14 +12,6 @@ ahriman.web.schemas.aur\_package\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.auth\_info\_schema module
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.auth_info_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.auth\_schema module
|
ahriman.web.schemas.auth\_schema module
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
@@ -36,14 +20,6 @@ ahriman.web.schemas.auth\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.auto\_refresh\_interval\_schema module
|
|
||||||
----------------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.auto_refresh_interval_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.build\_options\_schema module
|
ahriman.web.schemas.build\_options\_schema module
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
|
|
||||||
@@ -60,14 +36,6 @@ ahriman.web.schemas.changes\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.configuration\_schema module
|
|
||||||
------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.configuration_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.counters\_schema module
|
ahriman.web.schemas.counters\_schema module
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
@@ -124,14 +92,6 @@ ahriman.web.schemas.info\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.info\_v2\_schema module
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.info_v2_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.internal\_status\_schema module
|
ahriman.web.schemas.internal\_status\_schema module
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
@@ -156,14 +116,6 @@ ahriman.web.schemas.login\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.logs\_rotate\_schema module
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.logs_rotate_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.logs\_schema module
|
ahriman.web.schemas.logs\_schema module
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
@@ -172,14 +124,6 @@ ahriman.web.schemas.logs\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.logs\_search\_schema module
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.logs_search_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.oauth2\_schema module
|
ahriman.web.schemas.oauth2\_schema module
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
@@ -316,14 +260,6 @@ ahriman.web.schemas.repository\_id\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.repository\_stats\_schema module
|
|
||||||
----------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.repository_stats_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.search\_schema module
|
ahriman.web.schemas.search\_schema module
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
@@ -348,6 +284,14 @@ ahriman.web.schemas.update\_flags\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
ahriman.web.schemas.versioned\_log\_schema module
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
.. automodule:: ahriman.web.schemas.versioned_log_schema
|
||||||
|
:members:
|
||||||
|
:no-undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.worker\_schema module
|
ahriman.web.schemas.worker\_schema module
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -12,22 +12,6 @@ ahriman.web.views.v1.service.add module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.views.v1.service.config module
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.views.v1.service.config
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.views.v1.service.logs module
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.views.v1.service.logs
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.views.v1.service.pgp module
|
ahriman.web.views.v1.service.pgp module
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,6 @@ ahriman.web.views.v1.status.info module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.views.v1.status.metrics module
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.views.v1.status.metrics
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.views.v1.status.repositories module
|
ahriman.web.views.v1.status.repositories module
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ Subpackages
|
|||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
ahriman.web.views.v2.packages
|
ahriman.web.views.v2.packages
|
||||||
ahriman.web.views.v2.status
|
|
||||||
|
|
||||||
Module contents
|
Module contents
|
||||||
---------------
|
---------------
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
ahriman.web.views.v2.status package
|
|
||||||
===================================
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
ahriman.web.views.v2.status.info module
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.views.v2.status.info
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.views.v2.status
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
@@ -40,7 +40,6 @@ This package contains everything required for the most of application actions an
|
|||||||
* ``ahriman.core.distributed`` package with triggers and helpers for distributed build system.
|
* ``ahriman.core.distributed`` package with triggers and helpers for distributed build system.
|
||||||
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
||||||
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
||||||
* ``ahriman.core.housekeeping`` package provides few triggers for removing old data.
|
|
||||||
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
||||||
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
||||||
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
||||||
@@ -120,20 +119,6 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
|||||||
|
|
||||||
/var/lib/ahriman/
|
/var/lib/ahriman/
|
||||||
├── ahriman.db
|
├── ahriman.db
|
||||||
├── archive
|
|
||||||
│ ├── packages
|
|
||||||
│ │ └── a
|
|
||||||
│ │ └── ahriman
|
|
||||||
│ │ └── ahriman-2.0.0-1-any.pkg.tar.zst
|
|
||||||
│ └── repos
|
|
||||||
│ └── 2026
|
|
||||||
│ └── 01
|
|
||||||
│ └── 01
|
|
||||||
│ └── aur
|
|
||||||
│ └── x86_64
|
|
||||||
│ ├── ahriman-2.0.0-1-any.pkg.tar.zst -> ../../../../../../packages/a/ahriman/ahriman-2.0.0-1-any.pkg.tar.zst
|
|
||||||
│ ├── aur.db -> aur.db.tar.gz
|
|
||||||
│ └── aur.db.tar.gz
|
|
||||||
├── cache
|
├── cache
|
||||||
├── chroot
|
├── chroot
|
||||||
│ └── aur
|
│ └── aur
|
||||||
@@ -153,7 +138,6 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
|||||||
└── repository
|
└── repository
|
||||||
└── aur
|
└── aur
|
||||||
└── x86_64
|
└── x86_64
|
||||||
├── ahriman-2.0.0-1-any.pkg.tar.zst -> ../../../archive/packages/a/ahriman/ahriman-2.0.0-1-any.pkg.tar.zst
|
|
||||||
├── aur.db -> aur.db.tar.gz
|
├── aur.db -> aur.db.tar.gz
|
||||||
├── aur.db.tar.gz
|
├── aur.db.tar.gz
|
||||||
├── aur.files -> aur.files.tar.gz
|
├── aur.files -> aur.files.tar.gz
|
||||||
@@ -161,18 +145,11 @@ Having default root as ``/var/lib/ahriman`` (differs from container though), the
|
|||||||
|
|
||||||
There are multiple subdirectories, some of them are commons for any repository, but some of them are not.
|
There are multiple subdirectories, some of them are commons for any repository, but some of them are not.
|
||||||
|
|
||||||
* ``archive`` is the package archive directory. It is common for all repositories and architectures and contains two subdirectories:
|
|
||||||
|
|
||||||
* ``archive/packages/{first_letter}/{package_base}`` stores the actual built package files and their signatures.
|
|
||||||
* ``archive/repos/{YYYY}/{MM}/{DD}/{repository}/{architecture}`` contains daily repository snapshots. Each snapshot is a repository database with symlinks pointing to the corresponding packages in the ``archive/packages`` tree.
|
|
||||||
|
|
||||||
The archive also allows the build process to skip rebuilding a package if a matching version already exists.
|
|
||||||
|
|
||||||
* ``cache`` is a directory with locally stored PKGBUILD's and VCS packages. It is common for all repositories and architectures.
|
* ``cache`` is a directory with locally stored PKGBUILD's and VCS packages. It is common for all repositories and architectures.
|
||||||
* ``chroot/{repository}`` is a chroot directory for ``devtools``. It is specific for each repository, but shared for different architectures inside (the ``devtools`` handles architectures automatically).
|
* ``chroot/{repository}`` is a chroot directory for ``devtools``. It is specific for each repository, but shared for different architectures inside (the ``devtools`` handles architectures automatically).
|
||||||
* ``packages/{repository}/{architecture}`` is a directory with prebuilt packages. When a package is built, first it will be uploaded to this directory and later will be handled by update process. It is architecture and repository specific.
|
* ``packages/{repository}/{architecture}`` is a directory with prebuilt packages. When a package is built, first it will be uploaded to this directory and later will be handled by update process. It is architecture and repository specific.
|
||||||
* ``pacman/{repository}/{architecture}`` is the repository and architecture specific caches for pacman's databases.
|
* ``pacman/{repository}/{architecture}`` is the repository and architecture specific caches for pacman's databases.
|
||||||
* ``repository/{repository}/{architecture}`` is a repository packages directory. Package files in this directory are symlinks to the archive.
|
* ``repository/{repository}/{architecture}`` is a repository packages directory.
|
||||||
|
|
||||||
Normally you should avoid direct interaction with the application tree. For tree migration process refer to the :doc:`migration notes <migrations/index>`.
|
Normally you should avoid direct interaction with the application tree. For tree migration process refer to the :doc:`migration notes <migrations/index>`.
|
||||||
|
|
||||||
@@ -436,11 +413,10 @@ Web application
|
|||||||
Web application requires the following python packages to be installed:
|
Web application requires the following python packages to be installed:
|
||||||
|
|
||||||
* Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates).
|
* Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates).
|
||||||
* Additional web features also require ``aiohttp-apispec`` (autogenerated documentation, optional), ``aiohttp_cors`` (CORS support, required by documentation).
|
* Additional web features also require ``aiohttp-apispec`` (autogenerated documentation), ``aiohttp_cors`` (CORS support, required by documentation).
|
||||||
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
|
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
|
||||||
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
|
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
|
||||||
* In addition if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket2`` library is required.
|
* In addition if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket2`` library is required.
|
||||||
* Application metrics will be automatically enabled after installing ``aiohttp-openmetrics`` package.
|
|
||||||
|
|
||||||
Middlewares
|
Middlewares
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
|
|||||||
12
docs/conf.py
12
docs/conf.py
@@ -15,8 +15,9 @@ import sys
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ahriman import __version__
|
||||||
|
|
||||||
|
|
||||||
# support package imports
|
|
||||||
basedir = Path(__file__).resolve().parent.parent / "src"
|
basedir = Path(__file__).resolve().parent.parent / "src"
|
||||||
sys.path.insert(0, str(basedir))
|
sys.path.insert(0, str(basedir))
|
||||||
|
|
||||||
@@ -28,7 +29,6 @@ copyright = f"2021-{datetime.date.today().year}, ahriman team"
|
|||||||
author = "ahriman team"
|
author = "ahriman team"
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
from ahriman import __version__
|
|
||||||
release = __version__
|
release = __version__
|
||||||
|
|
||||||
|
|
||||||
@@ -91,13 +91,7 @@ autoclass_content = "both"
|
|||||||
|
|
||||||
autodoc_member_order = "groupwise"
|
autodoc_member_order = "groupwise"
|
||||||
|
|
||||||
autodoc_mock_imports = [
|
autodoc_mock_imports = ["cryptography", "pyalpm"]
|
||||||
"aioauth_client",
|
|
||||||
"aiohttp_security",
|
|
||||||
"aiohttp_session",
|
|
||||||
"cryptography",
|
|
||||||
"pyalpm",
|
|
||||||
]
|
|
||||||
|
|
||||||
autodoc_default_options = {
|
autodoc_default_options = {
|
||||||
"no-undoc-members": True,
|
"no-undoc-members": True,
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ will try to read value from ``SECRET`` environment variable. In case if the requ
|
|||||||
|
|
||||||
will eventually lead ``key`` option in section ``section1`` to be set to the value of ``HOME`` environment variable (if available).
|
will eventually lead ``key`` option in section ``section1`` to be set to the value of ``HOME`` environment variable (if available).
|
||||||
|
|
||||||
Moreover, configuration can be read from environment variables directly by following the same naming convention, e.g. in the example above, one can have environment variable named ``section1:key`` (e.g. ``section1:key=$HOME``) and it will be substituted to the configuration with the highest priority.
|
|
||||||
|
|
||||||
There is also additional subcommand which will allow to validate configuration and print found errors. In order to do so, run ``service-config-validate`` subcommand, e.g.:
|
There is also additional subcommand which will allow to validate configuration and print found errors. In order to do so, run ``service-config-validate`` subcommand, e.g.:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
@@ -97,15 +95,6 @@ libalpm and AUR related configuration. Group name can refer to architecture, e.g
|
|||||||
* ``sync_files_database`` - download files database from mirror, boolean, required.
|
* ``sync_files_database`` - download files database from mirror, boolean, required.
|
||||||
* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands). If set to ``no``, databases must be synchronized manually.
|
* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands). If set to ``no``, databases must be synchronized manually.
|
||||||
|
|
||||||
``aur`` group
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Archlinux User Repository related configuration.
|
|
||||||
|
|
||||||
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
|
|
||||||
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
|
|
||||||
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
|
||||||
|
|
||||||
``auth`` group
|
``auth`` group
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@@ -118,6 +107,7 @@ Base authorization settings. ``OAuth`` provider requires ``aioauth-client`` libr
|
|||||||
* ``cookie_secret_key`` - secret key which will be used for cookies encryption, string, optional. It must be 32 bytes URL-safe base64-encoded and can be generated as following ``base64.urlsafe_b64encode(os.urandom(32)).decode("utf8")``. If not set, it will be generated automatically; note, however, that in this case, all sessions will be automatically invalidated during the service restart.
|
* ``cookie_secret_key`` - secret key which will be used for cookies encryption, string, optional. It must be 32 bytes URL-safe base64-encoded and can be generated as following ``base64.urlsafe_b64encode(os.urandom(32)).decode("utf8")``. If not set, it will be generated automatically; note, however, that in this case, all sessions will be automatically invalidated during the service restart.
|
||||||
* ``full_access_group`` - name of the secondary group (e.g. ``wheel``) to be used as admin group in the service, string, required in case if ``pam`` is used.
|
* ``full_access_group`` - name of the secondary group (e.g. ``wheel``) to be used as admin group in the service, string, required in case if ``pam`` is used.
|
||||||
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service in seconds, integer, optional, default is 7 days.
|
* ``max_age`` - parameter which controls both cookie expiration and token expiration inside the service in seconds, integer, optional, default is 7 days.
|
||||||
|
* ``oauth_icon`` - OAuth2 login button icon, string, optional, default is ``google``. Must be valid `Bootstrap icon <https://icons.getbootstrap.com/>`__ name.
|
||||||
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
|
* ``oauth_provider`` - OAuth2 provider class name as is in ``aioauth-client`` (e.g. ``GoogleClient``, ``GithubClient`` etc), string, required in case if ``oauth`` is used.
|
||||||
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
|
* ``oauth_scopes`` - scopes list for OAuth2 provider, which will allow retrieving user email (which is used for checking user permissions), e.g. ``https://www.googleapis.com/auth/userinfo.email`` for ``GoogleClient`` or ``user:email`` for ``GithubClient``, space separated list of strings, required in case if ``oauth`` is used.
|
||||||
* ``permit_root_login`` - allow login as root user, boolean, optional, default ``no``.
|
* ``permit_root_login`` - allow login as root user, boolean, optional, default ``no``.
|
||||||
@@ -147,8 +137,6 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
|
|||||||
|
|
||||||
Base repository settings.
|
Base repository settings.
|
||||||
|
|
||||||
* ``architecture`` - repository architecture, string. This field is read-only and generated automatically from run options if possible.
|
|
||||||
* ``name`` - repository name, string. This field is read-only and generated automatically from run options if possible.
|
|
||||||
* ``root`` - root path for application, string, required.
|
* ``root`` - root path for application, string, required.
|
||||||
|
|
||||||
``sign:*`` groups
|
``sign:*`` groups
|
||||||
@@ -166,9 +154,7 @@ Reporting to web service related settings. In most cases there is fallback to we
|
|||||||
|
|
||||||
* ``enabled`` - enable reporting to web service, boolean, optional, default ``yes`` for backward compatibility.
|
* ``enabled`` - enable reporting to web service, boolean, optional, default ``yes`` for backward compatibility.
|
||||||
* ``address`` - remote web service address with protocol, string, optional. In case of websocket, the ``http+unix`` scheme and URL encoded address (e.g. ``%2Fvar%2Flib%2Fahriman`` for ``/var/lib/ahriman``) must be used, e.g. ``http+unix://%2Fvar%2Flib%2Fahriman%2Fsocket``. In case if none set, it will be guessed from ``web`` section.
|
* ``address`` - remote web service address with protocol, string, optional. In case of websocket, the ``http+unix`` scheme and URL encoded address (e.g. ``%2Fvar%2Flib%2Fahriman`` for ``/var/lib/ahriman``) must be used, e.g. ``http+unix://%2Fvar%2Flib%2Fahriman%2Fsocket``. In case if none set, it will be guessed from ``web`` section.
|
||||||
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
|
|
||||||
* ``password`` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
* ``password`` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||||
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
|
|
||||||
* ``suppress_http_log_errors`` - suppress HTTP log errors, boolean, optional, default ``no``. If set to ``yes``, any HTTP log errors (e.g. if web server is not available, but HTTP logging is enabled) will be suppressed.
|
* ``suppress_http_log_errors`` - suppress HTTP log errors, boolean, optional, default ``no``. If set to ``yes``, any HTTP log errors (e.g. if web server is not available, but HTTP logging is enabled) will be suppressed.
|
||||||
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||||
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||||
@@ -179,7 +165,6 @@ Reporting to web service related settings. In most cases there is fallback to we
|
|||||||
Web server settings. This feature requires ``aiohttp`` libraries to be installed.
|
Web server settings. This feature requires ``aiohttp`` libraries to be installed.
|
||||||
|
|
||||||
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
|
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
|
||||||
* ``autorefresh_intervals`` - enable page auto refresh options, space separated list of integers, optional. The first defined interval will be used as default. If no intervals set, the auto refresh buttons will be disabled. If first element of the list equals ``0``, auto refresh will be disabled by default.
|
|
||||||
* ``enable_archive_upload`` - allow to upload packages via HTTP (i.e. call of ``/api/v1/service/upload`` uri), boolean, optional, default ``no``.
|
* ``enable_archive_upload`` - allow to upload packages via HTTP (i.e. call of ``/api/v1/service/upload`` uri), boolean, optional, default ``no``.
|
||||||
* ``host`` - host to bind, string, optional.
|
* ``host`` - host to bind, string, optional.
|
||||||
* ``index_url`` - full URL of the repository index page, string, optional.
|
* ``index_url`` - full URL of the repository index page, string, optional.
|
||||||
@@ -187,21 +172,13 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
|
|||||||
* ``port`` - port to bind, integer, optional.
|
* ``port`` - port to bind, integer, optional.
|
||||||
* ``service_only`` - disable status routes (including logs), boolean, optional, default ``no``.
|
* ``service_only`` - disable status routes (including logs), boolean, optional, default ``no``.
|
||||||
* ``static_path`` - path to directory with static files, string, required.
|
* ``static_path`` - path to directory with static files, string, required.
|
||||||
* ``template`` - Jinja2 template name for the index page, string, optional, default ``build-status.jinja2``.
|
|
||||||
* ``templates`` - path to templates directories, space separated list of paths, required.
|
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||||
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
||||||
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
|
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
|
||||||
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
||||||
|
|
||||||
``archive`` group
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Describes settings for packages archives management extensions.
|
|
||||||
|
|
||||||
* ``keep_built_packages`` - keep this amount of built packages with different versions, integer, required. ``0`` will effectively disable archives removal.
|
|
||||||
|
|
||||||
``keyring`` group
|
``keyring`` group
|
||||||
-----------------
|
--------------------
|
||||||
|
|
||||||
Keyring package generator plugin.
|
Keyring package generator plugin.
|
||||||
|
|
||||||
@@ -219,13 +196,6 @@ Keyring generator plugin
|
|||||||
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
||||||
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
||||||
|
|
||||||
``logs-rotation`` group
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
This section describes settings for the ``ahriman.core.housekeeping.LogsRotationTrigger`` plugin.
|
|
||||||
|
|
||||||
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, required. Logs will be cleared at the end of each process.
|
|
||||||
|
|
||||||
``mirrorlist`` group
|
``mirrorlist`` group
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@@ -247,7 +217,7 @@ Mirrorlist generator plugin
|
|||||||
``remote-pull`` group
|
``remote-pull`` group
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Remote git source synchronization settings. Unlike ``upload`` triggers those triggers are used for PKGBUILD synchronization - fetch from remote repository PKGBUILDs before updating process.
|
Remote git source synchronization settings. Unlike ``Upload`` triggers those triggers are used for PKGBUILD synchronization - fetch from remote repository PKGBUILDs before updating process.
|
||||||
|
|
||||||
It supports authorization; to do so you'd need to prefix the URL with authorization part, e.g. ``https://key:token@github.com/arcan1s/ahriman.git``. It is highly recommended to use application tokens instead of your user authorization details. Alternatively, you can use any other option supported by git, e.g.:
|
It supports authorization; to do so you'd need to prefix the URL with authorization part, e.g. ``https://key:token@github.com/arcan1s/ahriman.git``. It is highly recommended to use application tokens instead of your user authorization details. Alternatively, you can use any other option supported by git, e.g.:
|
||||||
|
|
||||||
@@ -261,7 +231,6 @@ Available options are:
|
|||||||
Remote pull trigger
|
Remote pull trigger
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* ``type`` - type of the pull, string, optional, must be set to ``gitremote`` if exists.
|
|
||||||
* ``pull_url`` - URL of the remote repository from which PKGBUILDs can be pulled before build process, string, required.
|
* ``pull_url`` - URL of the remote repository from which PKGBUILDs can be pulled before build process, string, required.
|
||||||
* ``pull_branch`` - branch of the remote repository from which PKGBUILDs can be pulled before build process, string, optional, default is ``master``.
|
* ``pull_branch`` - branch of the remote repository from which PKGBUILDs can be pulled before build process, string, optional, default is ``master``.
|
||||||
|
|
||||||
@@ -282,7 +251,6 @@ Available options are:
|
|||||||
Remote push trigger
|
Remote push trigger
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* ``type`` - type of the push, string, optional, must be set to ``gitremote`` if exists.
|
|
||||||
* ``commit_email`` - git commit email, string, optional, default is ``ahriman@localhost``.
|
* ``commit_email`` - git commit email, string, optional, default is ``ahriman@localhost``.
|
||||||
* ``commit_user`` - git commit user, string, optional, default is ``ahriman``.
|
* ``commit_user`` - git commit user, string, optional, default is ``ahriman``.
|
||||||
* ``push_url`` - URL of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
|
* ``push_url`` - URL of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
|
||||||
@@ -378,8 +346,6 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
|
|||||||
* ``chat_id`` - telegram chat id, either string with ``@`` or integer value, required.
|
* ``chat_id`` - telegram chat id, either string with ``@`` or integer value, required.
|
||||||
* ``homepage`` - link to homepage, string, optional.
|
* ``homepage`` - link to homepage, string, optional.
|
||||||
* ``link_path`` - prefix for HTML links, string, required.
|
* ``link_path`` - prefix for HTML links, string, required.
|
||||||
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
|
|
||||||
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
|
|
||||||
* ``rss_url`` - link to RSS feed, string, optional.
|
* ``rss_url`` - link to RSS feed, string, optional.
|
||||||
* ``template`` - Jinja2 template name, string, required.
|
* ``template`` - Jinja2 template name, string, required.
|
||||||
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
||||||
@@ -405,7 +371,6 @@ Type will be read from several sources:
|
|||||||
This feature requires GitHub key creation (see below). Section name must be either ``github`` (plus optional architecture name, e.g. ``github:x86_64``) or random name with ``type`` set.
|
This feature requires GitHub key creation (see below). Section name must be either ``github`` (plus optional architecture name, e.g. ``github:x86_64``) or random name with ``type`` set.
|
||||||
|
|
||||||
* ``type`` - type of the upload, string, optional, must be set to ``github`` if exists.
|
* ``type`` - type of the upload, string, optional, must be set to ``github`` if exists.
|
||||||
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
|
|
||||||
* ``owner`` - GitHub repository owner, string, required.
|
* ``owner`` - GitHub repository owner, string, required.
|
||||||
* ``password`` - created GitHub API key. In order to create it do the following:
|
* ``password`` - created GitHub API key. In order to create it do the following:
|
||||||
|
|
||||||
@@ -415,7 +380,6 @@ This feature requires GitHub key creation (see below). Section name must be eith
|
|||||||
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
|
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
|
||||||
|
|
||||||
* ``repository`` - GitHub repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
|
* ``repository`` - GitHub repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
|
||||||
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
|
|
||||||
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||||
* ``use_full_release_name`` - if set to ``yes``, the release will contain both repository name and architecture, and only architecture otherwise, boolean, optional, default ``no`` (legacy behavior).
|
* ``use_full_release_name`` - if set to ``yes``, the release will contain both repository name and architecture, and only architecture otherwise, boolean, optional, default ``no`` (legacy behavior).
|
||||||
* ``username`` - GitHub authorization user, string, required. Basically the same as ``owner``.
|
* ``username`` - GitHub authorization user, string, required. Basically the same as ``owner``.
|
||||||
@@ -426,8 +390,6 @@ This feature requires GitHub key creation (see below). Section name must be eith
|
|||||||
Section name must be either ``remote-service`` (plus optional architecture name, e.g. ``remote-service:x86_64``) or random name with ``type`` set.
|
Section name must be either ``remote-service`` (plus optional architecture name, e.g. ``remote-service:x86_64``) or random name with ``type`` set.
|
||||||
|
|
||||||
* ``type`` - type of the report, string, optional, must be set to ``remote-service`` if exists.
|
* ``type`` - type of the report, string, optional, must be set to ``remote-service`` if exists.
|
||||||
* ``max_retries`` - maximum amount of retries of HTTP requests, integer, optional, default ``0``.
|
|
||||||
* ``retry_backoff`` - retry exponential backoff, float, optional, default ``0.0``.
|
|
||||||
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||||
|
|
||||||
``rsync`` type
|
``rsync`` type
|
||||||
|
|||||||
@@ -56,13 +56,6 @@ Though originally I've created ahriman by trying to improve the project, it stil
|
|||||||
|
|
||||||
It is automation tools for ``repoctl`` mentioned above. Except for using shell it looks pretty cool and also offers some additional features like patches, remote synchronization (isn't it?) and reporting.
|
It is automation tools for ``repoctl`` mentioned above. Except for using shell it looks pretty cool and also offers some additional features like patches, remote synchronization (isn't it?) and reporting.
|
||||||
|
|
||||||
`AURCache <https://github.com/Lukas-Heiligenbrunner/AURCache>`__
|
|
||||||
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
||||||
|
|
||||||
That's really cool project if you are looking for simple service to build AUR packages. It provides very informative dashboard and easy to configure and use. However, it doesn't provide direct way to control build process (e.g. it is neither trivial to build packages for architectures which are not supported by default nor to change build flags).
|
|
||||||
|
|
||||||
Also this application relies on docker setup (e.g. builders are only available as special docker containers). In addition, it uses ``paru`` to build packages instead of ``devtools``.
|
|
||||||
|
|
||||||
How to check service logs
|
How to check service logs
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
# This file was autogenerated by uv via the following command:
|
|
||||||
# uv pip compile --group pyproject.toml:docs --extra s3 --extra validator --extra web --output-file docs/requirements.txt pyproject.toml
|
|
||||||
aiohappyeyeballs==2.6.1
|
|
||||||
# via aiohttp
|
|
||||||
aiohttp==3.11.18
|
|
||||||
# via
|
|
||||||
# ahriman (pyproject.toml)
|
|
||||||
# aiohttp-cors
|
|
||||||
# aiohttp-jinja2
|
|
||||||
aiohttp-cors==0.8.1
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
aiohttp-jinja2==1.6
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
aiosignal==1.3.2
|
|
||||||
# via aiohttp
|
|
||||||
alabaster==1.0.0
|
|
||||||
# via sphinx
|
|
||||||
argparse-manpage==4.6
|
|
||||||
# via ahriman (pyproject.toml:docs)
|
|
||||||
attrs==25.3.0
|
|
||||||
# via aiohttp
|
|
||||||
babel==2.17.0
|
|
||||||
# via sphinx
|
|
||||||
bcrypt==4.3.0
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
boto3==1.38.11
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
botocore==1.38.11
|
|
||||||
# via
|
|
||||||
# boto3
|
|
||||||
# s3transfer
|
|
||||||
cerberus==1.3.7
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
certifi==2025.4.26
|
|
||||||
# via requests
|
|
||||||
charset-normalizer==3.4.2
|
|
||||||
# via requests
|
|
||||||
docutils==0.21.2
|
|
||||||
# via
|
|
||||||
# sphinx
|
|
||||||
# sphinx-argparse
|
|
||||||
# sphinx-rtd-theme
|
|
||||||
filelock==3.24.0
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
frozenlist==1.6.0
|
|
||||||
# via
|
|
||||||
# aiohttp
|
|
||||||
# aiosignal
|
|
||||||
idna==3.10
|
|
||||||
# via
|
|
||||||
# requests
|
|
||||||
# yarl
|
|
||||||
imagesize==1.4.1
|
|
||||||
# via sphinx
|
|
||||||
inflection==0.5.1
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
jinja2==3.1.6
|
|
||||||
# via
|
|
||||||
# aiohttp-jinja2
|
|
||||||
# sphinx
|
|
||||||
jmespath==1.0.1
|
|
||||||
# via
|
|
||||||
# boto3
|
|
||||||
# botocore
|
|
||||||
markupsafe==3.0.2
|
|
||||||
# via jinja2
|
|
||||||
multidict==6.4.3
|
|
||||||
# via
|
|
||||||
# aiohttp
|
|
||||||
# yarl
|
|
||||||
packaging==25.0
|
|
||||||
# via sphinx
|
|
||||||
propcache==0.3.1
|
|
||||||
# via
|
|
||||||
# aiohttp
|
|
||||||
# yarl
|
|
||||||
pydeps==3.0.1
|
|
||||||
# via ahriman (pyproject.toml:docs)
|
|
||||||
pyelftools==0.32
|
|
||||||
# via ahriman (pyproject.toml)
|
|
||||||
pygments==2.19.1
|
|
||||||
# via sphinx
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
# via botocore
|
|
||||||
requests==2.32.3
|
|
||||||
# via
|
|
||||||
# ahriman (pyproject.toml)
|
|
||||||
# sphinx
|
|
||||||
roman-numerals-py==3.1.0
|
|
||||||
# via sphinx
|
|
||||||
s3transfer==0.12.0
|
|
||||||
# via boto3
|
|
||||||
shtab==1.7.2
|
|
||||||
# via ahriman (pyproject.toml:docs)
|
|
||||||
six==1.17.0
|
|
||||||
# via python-dateutil
|
|
||||||
snowballstemmer==3.0.0.1
|
|
||||||
# via sphinx
|
|
||||||
sphinx==8.2.3
|
|
||||||
# via
|
|
||||||
# ahriman (pyproject.toml:docs)
|
|
||||||
# sphinx-argparse
|
|
||||||
# sphinx-rtd-theme
|
|
||||||
# sphinxcontrib-jquery
|
|
||||||
sphinx-argparse==0.5.2
|
|
||||||
# via ahriman (pyproject.toml:docs)
|
|
||||||
sphinx-rtd-theme==3.0.2
|
|
||||||
# via ahriman (pyproject.toml:docs)
|
|
||||||
sphinxcontrib-applehelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-devhelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-htmlhelp==2.1.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-jquery==4.1
|
|
||||||
# via sphinx-rtd-theme
|
|
||||||
sphinxcontrib-jsmath==1.0.1
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-qthelp==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
sphinxcontrib-serializinghtml==2.0.0
|
|
||||||
# via sphinx
|
|
||||||
stdlib-list==0.11.1
|
|
||||||
# via pydeps
|
|
||||||
urllib3==2.4.0
|
|
||||||
# via
|
|
||||||
# botocore
|
|
||||||
# requests
|
|
||||||
yarl==1.20.0
|
|
||||||
# via aiohttp
|
|
||||||
@@ -12,22 +12,19 @@ Initial setup
|
|||||||
|
|
||||||
sudo ahriman -a x86_64 -r aur service-setup ...
|
sudo ahriman -a x86_64 -r aur service-setup ...
|
||||||
|
|
||||||
.. admonition:: Details
|
``service-setup`` literally does the following steps:
|
||||||
:collapsible: closed
|
|
||||||
|
|
||||||
``service-setup`` literally does the following steps:
|
#.
|
||||||
|
Create ``/var/lib/ahriman/.makepkg.conf`` with ``makepkg.conf`` overrides if required (at least you might want to set ``PACKAGER``):
|
||||||
|
|
||||||
#.
|
.. code-block:: shell
|
||||||
Create ``/var/lib/ahriman/.makepkg.conf`` with ``makepkg.conf`` overrides if required (at least you might want to set ``PACKAGER``):
|
|
||||||
|
|
||||||
.. code-block:: shell
|
echo 'PACKAGER="ahriman bot <ahriman@example.com>"' | sudo -u ahriman tee -a /var/lib/ahriman/.makepkg.conf
|
||||||
|
|
||||||
echo 'PACKAGER="ahriman bot <ahriman@example.com>"' | sudo -u ahriman tee -a /var/lib/ahriman/.makepkg.conf
|
#.
|
||||||
|
Configure build tools (it is required for correct dependency management system):
|
||||||
|
|
||||||
#.
|
#.
|
||||||
Configure build tools (it is required for correct dependency management system):
|
|
||||||
|
|
||||||
#.
|
|
||||||
Create build command (you can choose any name for command, basically it should be ``{name}-{arch}-build``):
|
Create build command (you can choose any name for command, basically it should be ``{name}-{arch}-build``):
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
@@ -70,7 +67,7 @@ Initial setup
|
|||||||
echo 'ahriman ALL=(ALL) NOPASSWD:SETENV: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
|
echo 'ahriman ALL=(ALL) NOPASSWD:SETENV: CARCHBUILD_CMD' | tee -a /etc/sudoers.d/ahriman
|
||||||
chmod 400 /etc/sudoers.d/ahriman
|
chmod 400 /etc/sudoers.d/ahriman
|
||||||
|
|
||||||
This command supports several arguments, kindly refer to its help message.
|
This command supports several arguments, kindly refer to its help message.
|
||||||
|
|
||||||
#.
|
#.
|
||||||
Start and enable ``ahriman@.timer`` via ``systemctl``:
|
Start and enable ``ahriman@.timer`` via ``systemctl``:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Triggers
|
Triggers
|
||||||
========
|
========
|
||||||
|
|
||||||
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides several types of extensions - reporting, files uploading, PKGBUILD synchronization, repository archiving, housekeeping and distributed builds support. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and should implement at least one of the abstract methods:
|
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides three types of extensions - reporting, files uploading and PKGBUILD synchronization. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and should implement at least one of the abstract methods:
|
||||||
|
|
||||||
* ``on_result`` - trigger action which will be called after build process, the build result and the list of repository packages will be supplied as arguments.
|
* ``on_result`` - trigger action which will be called after build process, the build result and the list of repository packages will be supplied as arguments.
|
||||||
* ``on_start`` - trigger action which will be called right before the start of the application process.
|
* ``on_start`` - trigger action which will be called right before the start of the application process.
|
||||||
@@ -14,11 +14,6 @@ Built-in triggers
|
|||||||
|
|
||||||
For the configuration details and settings explanation kindly refer to the :doc:`documentation <configuration>`.
|
For the configuration details and settings explanation kindly refer to the :doc:`documentation <configuration>`.
|
||||||
|
|
||||||
``ahriman.core.archive.ArchiveTrigger``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
This trigger provides date-based snapshots of the repository. It organizes packages into a daily directory tree (``repos/YYYY/MM/DD``) with its own pacman database. On each run it creates symlinks from the daily snapshot to the actual package archives and maintains the database accordingly. It also takes care of cleaning up broken symlinks and empty directories for packages which have been removed.
|
|
||||||
|
|
||||||
``ahriman.core.distributed.WorkerLoaderTrigger``
|
``ahriman.core.distributed.WorkerLoaderTrigger``
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@@ -41,16 +36,6 @@ In order to update those packages you would need to clone your repository separa
|
|||||||
|
|
||||||
This trigger will be called right after build process (``on_result``). It will pick PKGBUILDs for the updated packages, pull them (together with any other files) and commit and push changes to remote repository. No real use cases, but the most of user repositories do it.
|
This trigger will be called right after build process (``on_result``). It will pick PKGBUILDs for the updated packages, pull them (together with any other files) and commit and push changes to remote repository. No real use cases, but the most of user repositories do it.
|
||||||
|
|
||||||
``ahriman.core.housekeeping.ArchiveRotationTrigger``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
This trigger removes old package versions from the archive directory. It implements ``on_result`` and, after each build, compares available versions for updated packages and removes the older ones, keeping only the last N versions as configured by ``keep_built_packages`` option.
|
|
||||||
|
|
||||||
``ahriman.core.housekeeping.LogsRotationTrigger``
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Simple trigger to rotate build logs. It implements ``on_result`` and removes old log records after each build process, keeping only the last N records as configured by ``keep_last_logs`` option.
|
|
||||||
|
|
||||||
``ahriman.core.report.ReportTrigger``
|
``ahriman.core.report.ReportTrigger``
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import js from "@eslint/js";
|
|
||||||
import stylistic from "@stylistic/eslint-plugin";
|
|
||||||
import reactHooks from "eslint-plugin-react-hooks";
|
|
||||||
import reactRefresh from "eslint-plugin-react-refresh";
|
|
||||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{ ignores: ["dist"] },
|
|
||||||
{
|
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
|
|
||||||
files: ["src/**/*.{ts,tsx}"],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
"react-hooks": reactHooks,
|
|
||||||
"react-refresh": reactRefresh,
|
|
||||||
"simple-import-sort": simpleImportSort,
|
|
||||||
"@stylistic": stylistic,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...reactHooks.configs.recommended.rules,
|
|
||||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
|
||||||
|
|
||||||
// imports
|
|
||||||
"simple-import-sort/imports": "error",
|
|
||||||
"simple-import-sort/exports": "error",
|
|
||||||
|
|
||||||
// brackets
|
|
||||||
"curly": "error",
|
|
||||||
"@stylistic/brace-style": ["error", "1tbs"],
|
|
||||||
|
|
||||||
// stylistic
|
|
||||||
"@stylistic/array-bracket-spacing": ["error", "never"],
|
|
||||||
"@stylistic/arrow-parens": ["error", "as-needed"],
|
|
||||||
"@stylistic/comma-dangle": ["error", "always-multiline"],
|
|
||||||
"@stylistic/comma-spacing": ["error", { before: false, after: true }],
|
|
||||||
"@stylistic/eol-last": ["error", "always"],
|
|
||||||
"@stylistic/indent": ["error", 4],
|
|
||||||
"@stylistic/jsx-quotes": ["error", "prefer-double"],
|
|
||||||
"@stylistic/max-len": ["error", {
|
|
||||||
code: 120,
|
|
||||||
ignoreComments: true,
|
|
||||||
ignoreStrings: true,
|
|
||||||
ignoreTemplateLiterals: true,
|
|
||||||
ignoreUrls: true,
|
|
||||||
}],
|
|
||||||
"@stylistic/no-extra-parens": ["error", "all"],
|
|
||||||
"@stylistic/no-multi-spaces": "error",
|
|
||||||
"@stylistic/no-multiple-empty-lines": ["error", { max: 1 }],
|
|
||||||
"@stylistic/no-trailing-spaces": "error",
|
|
||||||
"@stylistic/object-curly-spacing": ["error", "always"],
|
|
||||||
"@stylistic/quotes": ["error", "double"],
|
|
||||||
"@stylistic/semi": ["error", "always"],
|
|
||||||
|
|
||||||
// typescript
|
|
||||||
"@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
|
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", { allowExpressions: true }],
|
|
||||||
"@typescript-eslint/no-deprecated": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>ahriman</title>
|
|
||||||
|
|
||||||
<link rel="icon" href="/static/favicon.ico" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ahriman-frontend",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"version": "2.20.0-rc4",
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc && vite build",
|
|
||||||
"dev": "vite",
|
|
||||||
"lint": "eslint src/",
|
|
||||||
"lint:fix": "eslint --fix src/",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@emotion/react": "^11.11.0",
|
|
||||||
"@emotion/styled": "^11.11.0",
|
|
||||||
"@mui/icons-material": "^7.3.8",
|
|
||||||
"@mui/material": "^7.3.8",
|
|
||||||
"@mui/x-data-grid": "^8.27.3",
|
|
||||||
"@tanstack/react-query": "^5.0.0",
|
|
||||||
"chart.js": "^4.5.0",
|
|
||||||
"react": "^19.2.4",
|
|
||||||
"react-chartjs-2": "^5.2.0",
|
|
||||||
"react-dom": "^19.2.4",
|
|
||||||
"react-syntax-highlighter": "^16.1.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@eslint/js": "^9.39.3",
|
|
||||||
"@stylistic/eslint-plugin": "^5.9.0",
|
|
||||||
"@types/react": "^19.2.14",
|
|
||||||
"@types/react-dom": "^19.2.3",
|
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
|
||||||
"eslint": "^9.39.3",
|
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
||||||
"typescript": "^5.3.0",
|
|
||||||
"typescript-eslint": "^8.56.1",
|
|
||||||
"vite": "^7.3.1",
|
|
||||||
"vite-tsconfig-paths": "^6.1.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import CssBaseline from "@mui/material/CssBaseline";
|
|
||||||
import { ThemeProvider } from "@mui/material/styles";
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
||||||
import AppLayout from "components/layout/AppLayout";
|
|
||||||
import { AuthProvider } from "contexts/AuthProvider";
|
|
||||||
import { ClientProvider } from "contexts/ClientProvider";
|
|
||||||
import { NotificationProvider } from "contexts/NotificationProvider";
|
|
||||||
import { RepositoryProvider } from "contexts/RepositoryProvider";
|
|
||||||
import type React from "react";
|
|
||||||
import Theme from "theme/Theme";
|
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
staleTime: 30_000,
|
|
||||||
retry: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function App(): React.JSX.Element {
|
|
||||||
return (
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<ThemeProvider theme={Theme}>
|
|
||||||
<CssBaseline />
|
|
||||||
<ClientProvider>
|
|
||||||
<AuthProvider>
|
|
||||||
<RepositoryProvider>
|
|
||||||
<NotificationProvider>
|
|
||||||
<AppLayout />
|
|
||||||
</NotificationProvider>
|
|
||||||
</RepositoryProvider>
|
|
||||||
</AuthProvider>
|
|
||||||
</ClientProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { BaseClient } from "api/client/BaseClient";
|
|
||||||
import { FetchMixin } from "api/client/FetchMixin";
|
|
||||||
import { ServiceMixin } from "api/client/ServiceMixin";
|
|
||||||
import type { LoginRequest } from "models/LoginRequest";
|
|
||||||
import { applyMixins } from "utils";
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
|
|
||||||
export class AhrimanClient extends BaseClient {
|
|
||||||
|
|
||||||
async login(data: LoginRequest): Promise<void> {
|
|
||||||
return this.request("/api/v1/login", { method: "POST", json: data });
|
|
||||||
}
|
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
|
||||||
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]);
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
export class ApiError extends Error {
|
|
||||||
status: number;
|
|
||||||
statusText: string;
|
|
||||||
body: string;
|
|
||||||
|
|
||||||
constructor(status: number, statusText: string, body: string) {
|
|
||||||
super(`${status} ${statusText}`);
|
|
||||||
this.status = status;
|
|
||||||
this.statusText = statusText;
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
|
|
||||||
get detail(): string {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(this.body) as Record<string, string>;
|
|
||||||
return parsed.error ?? (this.body || this.message);
|
|
||||||
} catch {
|
|
||||||
return this.body || this.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static errorDetail(exception: unknown): string {
|
|
||||||
return exception instanceof ApiError ? exception.detail : String(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import type { RequestOptions } from "api/client/RequestOptions";
|
|
||||||
|
|
||||||
export class BaseClient {
|
|
||||||
|
|
||||||
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
|
|
||||||
const { method, query, json } = options;
|
|
||||||
|
|
||||||
let fullUrl = url;
|
|
||||||
if (query) {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
for (const [key, value] of Object.entries(query)) {
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
params.set(key, String(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fullUrl += `?${params.toString()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers: Record<string, string> = {
|
|
||||||
Accept: "application/json",
|
|
||||||
};
|
|
||||||
if (json !== undefined) {
|
|
||||||
headers["Content-Type"] = "application/json";
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestInit: RequestInit = {
|
|
||||||
method: method || (json ? "POST" : "GET"),
|
|
||||||
headers,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (json !== undefined) {
|
|
||||||
requestInit.body = JSON.stringify(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(fullUrl, requestInit);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const body = await response.text();
|
|
||||||
throw new ApiError(response.status, response.statusText, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentType = response.headers.get("Content-Type") ?? "";
|
|
||||||
if (contentType.includes("application/json")) {
|
|
||||||
return await response.json() as T;
|
|
||||||
}
|
|
||||||
return await response.text() as T;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { BaseClient } from "api/client/BaseClient";
|
|
||||||
import type { Changes } from "models/Changes";
|
|
||||||
import type { Dependencies } from "models/Dependencies";
|
|
||||||
import type { Event } from "models/Event";
|
|
||||||
import type { InfoResponse } from "models/InfoResponse";
|
|
||||||
import type { InternalStatus } from "models/InternalStatus";
|
|
||||||
import type { LogRecord } from "models/LogRecord";
|
|
||||||
import type { PackageStatus } from "models/PackageStatus";
|
|
||||||
import type { Patch } from "models/Patch";
|
|
||||||
import { RepositoryId } from "models/RepositoryId";
|
|
||||||
|
|
||||||
export class FetchMixin extends BaseClient {
|
|
||||||
|
|
||||||
async fetchPackage(packageBase: string, repository: RepositoryId): Promise<PackageStatus[]> {
|
|
||||||
return this.request<PackageStatus[]>(`/api/v1/packages/${encodeURIComponent(packageBase)}`, {
|
|
||||||
query: repository.toQuery(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPackageChanges(packageBase: string, repository: RepositoryId): Promise<Changes> {
|
|
||||||
return this.request<Changes>(`/api/v1/packages/${encodeURIComponent(packageBase)}/changes`, {
|
|
||||||
query: repository.toQuery(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPackageDependencies(packageBase: string, repository: RepositoryId): Promise<Dependencies> {
|
|
||||||
return this.request<Dependencies>(`/api/v1/packages/${encodeURIComponent(packageBase)}/dependencies`, {
|
|
||||||
query: repository.toQuery(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPackageEvents(repository: RepositoryId, objectId?: string, limit?: number): Promise<Event[]> {
|
|
||||||
const query: Record<string, string | number> = repository.toQuery();
|
|
||||||
if (objectId) {
|
|
||||||
query.object_id = objectId;
|
|
||||||
}
|
|
||||||
if (limit) {
|
|
||||||
query.limit = limit;
|
|
||||||
}
|
|
||||||
return this.request<Event[]>("/api/v1/events", { query });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPackageLogs(
|
|
||||||
packageBase: string,
|
|
||||||
repository: RepositoryId,
|
|
||||||
version?: string,
|
|
||||||
processId?: string,
|
|
||||||
head?: boolean,
|
|
||||||
): Promise<LogRecord[]> {
|
|
||||||
const query: Record<string, string | boolean> = { ...repository.toQuery() };
|
|
||||||
if (version) {
|
|
||||||
query.version = version;
|
|
||||||
}
|
|
||||||
if (processId) {
|
|
||||||
query.process_id = processId;
|
|
||||||
}
|
|
||||||
if (head) {
|
|
||||||
query.head = true;
|
|
||||||
}
|
|
||||||
return this.request<LogRecord[]>(`/api/v2/packages/${encodeURIComponent(packageBase)}/logs`, { query });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPackagePatches(packageBase: string): Promise<Patch[]> {
|
|
||||||
return this.request<Patch[]>(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchPackages(repository: RepositoryId): Promise<PackageStatus[]> {
|
|
||||||
return this.request<PackageStatus[]>("/api/v1/packages", { query: repository.toQuery() });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchServerInfo(): Promise<InfoResponse> {
|
|
||||||
const info = await this.request<InfoResponse>("/api/v2/info");
|
|
||||||
info.repositories = info.repositories.map(repositories =>
|
|
||||||
new RepositoryId(repositories.architecture, repositories.repository),
|
|
||||||
);
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchServerStatus(repository: RepositoryId): Promise<InternalStatus> {
|
|
||||||
return this.request<InternalStatus>("/api/v1/status", { query: repository.toQuery() });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
export interface RequestOptions {
|
|
||||||
method?: string;
|
|
||||||
query?: Record<string, string | number | boolean>;
|
|
||||||
json?: unknown;
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { BaseClient } from "api/client/BaseClient";
|
|
||||||
import type { AURPackage } from "models/AURPackage";
|
|
||||||
import type { PackageActionRequest } from "models/PackageActionRequest";
|
|
||||||
import type { PGPKey } from "models/PGPKey";
|
|
||||||
import type { PGPKeyRequest } from "models/PGPKeyRequest";
|
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
|
|
||||||
export class ServiceMixin extends BaseClient {
|
|
||||||
|
|
||||||
async servicePackageAdd(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
|
||||||
return this.request("/api/v1/service/add", { method: "POST", query: repository.toQuery(), json: data });
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePackagePatchRemove(packageBase: string, key: string): Promise<void> {
|
|
||||||
return this.request(`/api/v1/packages/${encodeURIComponent(packageBase)}/patches/${encodeURIComponent(key)}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePackageRemove(repository: RepositoryId, packages: string[]): Promise<void> {
|
|
||||||
return this.request("/api/v1/service/remove", {
|
|
||||||
method: "POST",
|
|
||||||
query: repository.toQuery(),
|
|
||||||
json: { packages },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePackageRequest(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
|
||||||
return this.request("/api/v1/service/request", {
|
|
||||||
method: "POST",
|
|
||||||
query: repository.toQuery(),
|
|
||||||
json: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePackageSearch(query: string): Promise<AURPackage[]> {
|
|
||||||
return this.request<AURPackage[]>("/api/v1/service/search", { query: { for: query } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePackageUpdate(repository: RepositoryId, data: PackageActionRequest): Promise<void> {
|
|
||||||
return this.request("/api/v1/service/update", {
|
|
||||||
method: "POST",
|
|
||||||
query: repository.toQuery(),
|
|
||||||
json: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePGPFetch(key: string, server: string): Promise<PGPKey> {
|
|
||||||
return this.request<PGPKey>("/api/v1/service/pgp", { query: { key, server } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async servicePGPImport(data: PGPKeyRequest): Promise<void> {
|
|
||||||
return this.request("/api/v1/service/pgp", { method: "POST", json: data });
|
|
||||||
}
|
|
||||||
|
|
||||||
async serviceRebuild(repository: RepositoryId, packages: string[]): Promise<void> {
|
|
||||||
return this.request("/api/v1/service/rebuild", {
|
|
||||||
method: "POST",
|
|
||||||
query: repository.toQuery(),
|
|
||||||
json: { packages },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import {
|
|
||||||
ArcElement,
|
|
||||||
BarElement,
|
|
||||||
CategoryScale,
|
|
||||||
Chart as ChartJS,
|
|
||||||
Legend,
|
|
||||||
LinearScale,
|
|
||||||
LineElement,
|
|
||||||
PointElement,
|
|
||||||
Tooltip,
|
|
||||||
} from "chart.js";
|
|
||||||
|
|
||||||
ChartJS.register(
|
|
||||||
ArcElement,
|
|
||||||
BarElement,
|
|
||||||
CategoryScale,
|
|
||||||
Legend,
|
|
||||||
LinearScale,
|
|
||||||
LineElement,
|
|
||||||
PointElement,
|
|
||||||
Tooltip,
|
|
||||||
);
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { Event } from "models/Event";
|
|
||||||
import type React from "react";
|
|
||||||
import { Line } from "react-chartjs-2";
|
|
||||||
|
|
||||||
interface EventDurationLineChartProps {
|
|
||||||
events: Event[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function EventDurationLineChart({ events }: EventDurationLineChartProps): React.JSX.Element {
|
|
||||||
const updateEvents = events.filter(event => event.event === "package-updated");
|
|
||||||
const data = {
|
|
||||||
labels: updateEvents.map(event => new Date(event.created * 1000).toISOStringShort()),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "update duration, s",
|
|
||||||
data: updateEvents.map(event => event.data?.took ?? 0),
|
|
||||||
cubicInterpolationMode: "monotone" as const,
|
|
||||||
tension: 0.4,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Line data={data} options={{ responsive: true }} />;
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { blue, indigo } from "@mui/material/colors";
|
|
||||||
import type { RepositoryStats } from "models/RepositoryStats";
|
|
||||||
import type React from "react";
|
|
||||||
import { Bar } from "react-chartjs-2";
|
|
||||||
|
|
||||||
interface PackageCountBarChartProps {
|
|
||||||
stats: RepositoryStats;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageCountBarChart({ stats }: PackageCountBarChartProps): React.JSX.Element {
|
|
||||||
return <Bar
|
|
||||||
data={{
|
|
||||||
labels: ["packages"],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "archives",
|
|
||||||
data: [stats.packages ?? 0],
|
|
||||||
backgroundColor: blue[500],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "bases",
|
|
||||||
data: [stats.bases ?? 0],
|
|
||||||
backgroundColor: indigo[300],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}}
|
|
||||||
options={{
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
responsive: true,
|
|
||||||
scales: {
|
|
||||||
x: { stacked: true },
|
|
||||||
y: { stacked: true },
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { BuildStatus } from "models/BuildStatus.ts";
|
|
||||||
import type { Counters } from "models/Counters";
|
|
||||||
import type React from "react";
|
|
||||||
import { Pie } from "react-chartjs-2";
|
|
||||||
import { StatusColors } from "theme/StatusColors";
|
|
||||||
|
|
||||||
interface StatusPieChartProps {
|
|
||||||
counters: Counters;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function StatusPieChart({ counters }: StatusPieChartProps): React.JSX.Element {
|
|
||||||
const labels = ["unknown", "pending", "building", "failed", "success"] as BuildStatus[];
|
|
||||||
const data = {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "packages in status",
|
|
||||||
data: labels.map(label => counters[label]),
|
|
||||||
backgroundColor: labels.map(label => StatusColors[label]),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Pie data={data} options={{ responsive: true }} />;
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import CheckIcon from "@mui/icons-material/Check";
|
|
||||||
import TimerIcon from "@mui/icons-material/Timer";
|
|
||||||
import TimerOffIcon from "@mui/icons-material/TimerOff";
|
|
||||||
import { IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Tooltip } from "@mui/material";
|
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
interface AutoRefreshControlProps {
|
|
||||||
intervals: AutoRefreshInterval[];
|
|
||||||
currentInterval: number;
|
|
||||||
onIntervalChange: (interval: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AutoRefreshControl({
|
|
||||||
intervals,
|
|
||||||
currentInterval,
|
|
||||||
onIntervalChange,
|
|
||||||
}: AutoRefreshControlProps): React.JSX.Element | null {
|
|
||||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
if (intervals.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const enabled = currentInterval > 0;
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<Tooltip title="Auto-refresh">
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={event => setAnchorEl(event.currentTarget)}
|
|
||||||
color={enabled ? "primary" : "default"}
|
|
||||||
>
|
|
||||||
{enabled ? <TimerIcon fontSize="small" /> : <TimerOffIcon fontSize="small" />}
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Menu
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
open={Boolean(anchorEl)}
|
|
||||||
onClose={() => setAnchorEl(null)}
|
|
||||||
>
|
|
||||||
<MenuItem
|
|
||||||
selected={!enabled}
|
|
||||||
onClick={() => {
|
|
||||||
onIntervalChange(0);
|
|
||||||
setAnchorEl(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
{!enabled && <CheckIcon fontSize="small" />}
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>Off</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
{intervals.map(interval =>
|
|
||||||
<MenuItem
|
|
||||||
key={interval.interval}
|
|
||||||
selected={enabled && interval.interval === currentInterval}
|
|
||||||
onClick={() => {
|
|
||||||
onIntervalChange(interval.interval);
|
|
||||||
setAnchorEl(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
{enabled && interval.interval === currentInterval && <CheckIcon fontSize="small" />}
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{interval.text}</ListItemText>
|
|
||||||
</MenuItem>,
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
</>;
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box } from "@mui/material";
|
|
||||||
import CopyButton from "components/common/CopyButton";
|
|
||||||
import React, { type RefObject } from "react";
|
|
||||||
|
|
||||||
interface CodeBlockProps {
|
|
||||||
preRef?: RefObject<HTMLElement | null>;
|
|
||||||
getText: () => string;
|
|
||||||
height?: number | string;
|
|
||||||
onScroll?: () => void;
|
|
||||||
wordBreak?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CodeBlock({
|
|
||||||
preRef,
|
|
||||||
getText,
|
|
||||||
height,
|
|
||||||
onScroll,
|
|
||||||
wordBreak,
|
|
||||||
}: CodeBlockProps): React.JSX.Element {
|
|
||||||
return <Box sx={{ position: "relative" }}>
|
|
||||||
<Box
|
|
||||||
ref={preRef}
|
|
||||||
component="pre"
|
|
||||||
onScroll={onScroll}
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "grey.100",
|
|
||||||
p: 2,
|
|
||||||
borderRadius: 1,
|
|
||||||
overflow: "auto",
|
|
||||||
height,
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
fontFamily: "monospace",
|
|
||||||
...wordBreak ? { whiteSpace: "pre-wrap", wordBreak: "break-all" } : {},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<code>
|
|
||||||
{getText()}
|
|
||||||
</code>
|
|
||||||
</Box>
|
|
||||||
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
|
|
||||||
<CopyButton getText={getText} />
|
|
||||||
</Box>
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import CheckIcon from "@mui/icons-material/Check";
|
|
||||||
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
||||||
import { IconButton, Tooltip } from "@mui/material";
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
interface CopyButtonProps {
|
|
||||||
getText: () => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CopyButton({ getText }: CopyButtonProps): React.JSX.Element {
|
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
const timer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
||||||
|
|
||||||
useEffect(() => () => clearTimeout(timer.current), []);
|
|
||||||
|
|
||||||
const handleCopy: () => Promise<void> = async () => {
|
|
||||||
await navigator.clipboard.writeText(getText());
|
|
||||||
setCopied(true);
|
|
||||||
clearTimeout(timer.current);
|
|
||||||
timer.current = setTimeout(() => setCopied(false), 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Tooltip title={copied ? "Copied!" : "Copy"}>
|
|
||||||
<IconButton size="small" aria-label={copied ? "Copied" : "Copy"} onClick={() => void handleCopy()}>
|
|
||||||
{copied ? <CheckIcon fontSize="small" /> : <ContentCopyIcon fontSize="small" />}
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>;
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
|
||||||
import { DialogTitle, IconButton, type SxProps, type Theme } from "@mui/material";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
interface DialogHeaderProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
onClose: () => void;
|
|
||||||
sx?: SxProps<Theme>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DialogHeader({ children, onClose, sx }: DialogHeaderProps): React.JSX.Element {
|
|
||||||
return <DialogTitle sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", ...sx }}>
|
|
||||||
{children}
|
|
||||||
<IconButton aria-label="Close" onClick={onClose} size="small" sx={{ color: "inherit" }}>
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
</DialogTitle>;
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
|
|
||||||
import { useRepository } from "hooks/useRepository";
|
|
||||||
import type { SelectedRepositoryResult } from "hooks/useSelectedRepository";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
export default function RepositorySelect({
|
|
||||||
repositorySelect,
|
|
||||||
}: { repositorySelect: SelectedRepositoryResult }): React.JSX.Element {
|
|
||||||
const { repositories, current } = useRepository();
|
|
||||||
|
|
||||||
return <FormControl fullWidth margin="normal">
|
|
||||||
<InputLabel>repository</InputLabel>
|
|
||||||
<Select
|
|
||||||
value={repositorySelect.selectedKey || (current?.key ?? "")}
|
|
||||||
label="repository"
|
|
||||||
onChange={event => repositorySelect.setSelectedKey(event.target.value)}
|
|
||||||
>
|
|
||||||
{repositories.map(repository =>
|
|
||||||
<MenuItem key={repository.key} value={repository.key}>
|
|
||||||
{repository.label}
|
|
||||||
</MenuItem>,
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface StringListProps {
|
|
||||||
items: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function StringList({ items }: StringListProps): React.JSX.Element {
|
|
||||||
return <>{items.join("\n")}</>;
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box, Dialog, DialogContent, Grid, Typography } from "@mui/material";
|
|
||||||
import { skipToken, useQuery } from "@tanstack/react-query";
|
|
||||||
import PackageCountBarChart from "components/charts/PackageCountBarChart";
|
|
||||||
import StatusPieChart from "components/charts/StatusPieChart";
|
|
||||||
import DialogHeader from "components/common/DialogHeader";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useRepository } from "hooks/useRepository";
|
|
||||||
import type { InternalStatus } from "models/InternalStatus";
|
|
||||||
import type React from "react";
|
|
||||||
import { StatusHeaderStyles } from "theme/StatusColors";
|
|
||||||
|
|
||||||
interface DashboardDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DashboardDialog({ open, onClose }: DashboardDialogProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const { current } = useRepository();
|
|
||||||
|
|
||||||
const { data: status } = useQuery<InternalStatus>({
|
|
||||||
queryKey: current ? QueryKeys.status(current) : ["status"],
|
|
||||||
queryFn: current ? () => client.fetchServerStatus(current) : skipToken,
|
|
||||||
enabled: open,
|
|
||||||
});
|
|
||||||
|
|
||||||
const headerStyle = status ? StatusHeaderStyles[status.status.status] : {};
|
|
||||||
|
|
||||||
return <Dialog open={open} onClose={onClose} maxWidth="lg" fullWidth>
|
|
||||||
<DialogHeader onClose={onClose} sx={headerStyle}>
|
|
||||||
System health
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
{status &&
|
|
||||||
<>
|
|
||||||
<Grid container spacing={2} sx={{ mt: 1 }}>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2" color="text.secondary" align="right">Repository name</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2">{status.repository}</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2" color="text.secondary" align="right">Repository architecture</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2">{status.architecture}</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid container spacing={2} sx={{ mt: 1 }}>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2" color="text.secondary" align="right">Current status</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2">{status.status.status}</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2" color="text.secondary" align="right">Updated at</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 6, md: 3 }}>
|
|
||||||
<Typography variant="body2">{new Date(status.status.timestamp * 1000).toISOStringShort()}</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid container spacing={2} sx={{ mt: 2 }}>
|
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
|
||||||
<Box sx={{ maxHeight: 300 }}>
|
|
||||||
<PackageCountBarChart stats={status.stats} />
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
|
||||||
<Box sx={{ maxHeight: 300, display: "flex", justifyContent: "center", alignItems: "center" }}>
|
|
||||||
<StatusPieChart counters={status.packages} />
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>;
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
|
||||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
TextField,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import CodeBlock from "components/common/CodeBlock";
|
|
||||||
import DialogHeader from "components/common/DialogHeader";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useNotification } from "hooks/useNotification";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
interface KeyImportDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function KeyImportDialog({ open, onClose }: KeyImportDialogProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const { showSuccess, showError } = useNotification();
|
|
||||||
|
|
||||||
const [fingerprint, setFingerprint] = useState("");
|
|
||||||
const [server, setServer] = useState("keyserver.ubuntu.com");
|
|
||||||
const [keyBody, setKeyBody] = useState("");
|
|
||||||
|
|
||||||
const handleClose = (): void => {
|
|
||||||
setFingerprint("");
|
|
||||||
setServer("keyserver.ubuntu.com");
|
|
||||||
setKeyBody("");
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFetch: () => Promise<void> = async () => {
|
|
||||||
if (!fingerprint || !server) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const result = await client.servicePGPFetch(fingerprint, server);
|
|
||||||
setKeyBody(result.key);
|
|
||||||
} catch (exception) {
|
|
||||||
const detail = ApiError.errorDetail(exception);
|
|
||||||
showError("Action failed", `Could not fetch key: ${detail}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImport: () => Promise<void> = async () => {
|
|
||||||
if (!fingerprint || !server) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await client.servicePGPImport({ key: fingerprint, server });
|
|
||||||
handleClose();
|
|
||||||
showSuccess("Success", `Key ${fingerprint} has been imported`);
|
|
||||||
} catch (exception) {
|
|
||||||
const detail = ApiError.errorDetail(exception);
|
|
||||||
showError("Action failed", `Could not import key ${fingerprint} from ${server}: ${detail}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="lg" fullWidth>
|
|
||||||
<DialogHeader onClose={handleClose}>
|
|
||||||
Import key from PGP server
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<TextField
|
|
||||||
label="fingerprint"
|
|
||||||
placeholder="PGP key fingerprint"
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
value={fingerprint}
|
|
||||||
onChange={event => setFingerprint(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
label="key server"
|
|
||||||
placeholder="PGP key server"
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
value={server}
|
|
||||||
onChange={event => setServer(event.target.value)}
|
|
||||||
/>
|
|
||||||
{keyBody &&
|
|
||||||
<Box sx={{ mt: 2 }}>
|
|
||||||
<CodeBlock getText={() => keyBody} height={300} />
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => void handleImport()} variant="contained" startIcon={<PlayArrowIcon />}>import</Button>
|
|
||||||
<Button onClick={() => void handleFetch()} variant="contained" color="success" startIcon={<RefreshIcon />}>fetch</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>;
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import PersonIcon from "@mui/icons-material/Person";
|
|
||||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
|
||||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
IconButton,
|
|
||||||
InputAdornment,
|
|
||||||
TextField,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import DialogHeader from "components/common/DialogHeader";
|
|
||||||
import { useAuth } from "hooks/useAuth";
|
|
||||||
import { useNotification } from "hooks/useNotification";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
interface LoginDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LoginDialog({ open, onClose }: LoginDialogProps): React.JSX.Element {
|
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const { login } = useAuth();
|
|
||||||
const { showSuccess, showError } = useNotification();
|
|
||||||
|
|
||||||
const handleClose = (): void => {
|
|
||||||
setUsername("");
|
|
||||||
setPassword("");
|
|
||||||
setShowPassword(false);
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit: () => Promise<void> = async () => {
|
|
||||||
if (!username || !password) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await login(username, password);
|
|
||||||
handleClose();
|
|
||||||
showSuccess("Logged in", `Successfully logged in as ${username}`);
|
|
||||||
} catch (exception) {
|
|
||||||
const detail = ApiError.errorDetail(exception);
|
|
||||||
if (username === "admin" && password === "admin") {
|
|
||||||
showError("Login error", "You've entered a password for user \"root\", did you make a typo in username?");
|
|
||||||
} else {
|
|
||||||
showError("Login error", `Could not login as ${username}: ${detail}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
|
|
||||||
<DialogHeader onClose={handleClose}>
|
|
||||||
Login
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<TextField
|
|
||||||
label="username"
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
value={username}
|
|
||||||
onChange={event => setUsername(event.target.value)}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
label="password"
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
value={password}
|
|
||||||
onChange={event => setPassword(event.target.value)}
|
|
||||||
onKeyDown={event => {
|
|
||||||
if (event.key === "Enter") {
|
|
||||||
void handleSubmit();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
slotProps={{
|
|
||||||
input: {
|
|
||||||
endAdornment:
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton aria-label={showPassword ? "Hide password" : "Show password"} onClick={() => setShowPassword(!showPassword)} edge="end" size="small">
|
|
||||||
{showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => void handleSubmit()} variant="contained" startIcon={<PersonIcon />}>login</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>;
|
|
||||||
}
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
|
||||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
|
||||||
import {
|
|
||||||
Autocomplete,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Dialog,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
FormControlLabel,
|
|
||||||
IconButton,
|
|
||||||
TextField,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import DialogHeader from "components/common/DialogHeader";
|
|
||||||
import RepositorySelect from "components/common/RepositorySelect";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useDebounce } from "hooks/useDebounce";
|
|
||||||
import { useNotification } from "hooks/useNotification";
|
|
||||||
import { useSelectedRepository } from "hooks/useSelectedRepository";
|
|
||||||
import type { AURPackage } from "models/AURPackage";
|
|
||||||
import React, { useRef, useState } from "react";
|
|
||||||
|
|
||||||
interface EnvironmentVariable {
|
|
||||||
id: number;
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PackageAddDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageAddDialog({ open, onClose }: PackageAddDialogProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const { showSuccess, showError } = useNotification();
|
|
||||||
const repositorySelect = useSelectedRepository();
|
|
||||||
|
|
||||||
const [packageName, setPackageName] = useState("");
|
|
||||||
const [refreshDatabase, setRefreshDatabase] = useState(true);
|
|
||||||
const [environmentVariables, setEnvironmentVariables] = useState<EnvironmentVariable[]>([]);
|
|
||||||
const variableIdCounter = useRef(0);
|
|
||||||
|
|
||||||
const handleClose = (): void => {
|
|
||||||
setPackageName("");
|
|
||||||
repositorySelect.reset();
|
|
||||||
setRefreshDatabase(true);
|
|
||||||
setEnvironmentVariables([]);
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const debouncedSearch = useDebounce(packageName, 500);
|
|
||||||
|
|
||||||
const { data: searchResults = [] } = useQuery<AURPackage[]>({
|
|
||||||
queryKey: QueryKeys.search(debouncedSearch),
|
|
||||||
queryFn: () => client.servicePackageSearch(debouncedSearch),
|
|
||||||
enabled: debouncedSearch.length >= 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleAdd: () => Promise<void> = async () => {
|
|
||||||
if (!packageName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const repository = repositorySelect.selectedRepository;
|
|
||||||
if (!repository) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const patches = environmentVariables.filter(variable => variable.key);
|
|
||||||
await client.servicePackageAdd(repository, {
|
|
||||||
packages: [packageName],
|
|
||||||
patches,
|
|
||||||
refresh: refreshDatabase,
|
|
||||||
});
|
|
||||||
handleClose();
|
|
||||||
showSuccess("Success", `Packages ${packageName} have been added`);
|
|
||||||
} catch (exception) {
|
|
||||||
const detail = ApiError.errorDetail(exception);
|
|
||||||
showError("Action failed", `Package addition failed: ${detail}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRequest: () => Promise<void> = async () => {
|
|
||||||
if (!packageName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const repository = repositorySelect.selectedRepository;
|
|
||||||
if (!repository) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const patches = environmentVariables.filter(variable => variable.key);
|
|
||||||
await client.servicePackageRequest(repository, {
|
|
||||||
packages: [packageName],
|
|
||||||
patches,
|
|
||||||
});
|
|
||||||
handleClose();
|
|
||||||
showSuccess("Success", `Packages ${packageName} have been requested`);
|
|
||||||
} catch (exception) {
|
|
||||||
const detail = ApiError.errorDetail(exception);
|
|
||||||
showError("Action failed", `Package request failed: ${detail}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
|
||||||
<DialogHeader onClose={handleClose}>
|
|
||||||
Add new packages
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<RepositorySelect repositorySelect={repositorySelect} />
|
|
||||||
|
|
||||||
<Autocomplete
|
|
||||||
freeSolo
|
|
||||||
options={searchResults.map(pkg => pkg.package)}
|
|
||||||
inputValue={packageName}
|
|
||||||
onInputChange={(_, value) => setPackageName(value)}
|
|
||||||
renderOption={(props, option) => {
|
|
||||||
const pkg = searchResults.find(pkg => pkg.package === option);
|
|
||||||
return (
|
|
||||||
<li {...props} key={option}>
|
|
||||||
{option}{pkg ? ` (${pkg.description})` : ""}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
renderInput={params =>
|
|
||||||
<TextField {...params} label="package" placeholder="AUR package" margin="normal" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox checked={refreshDatabase} onChange={(_, checked) => setRefreshDatabase(checked)} />}
|
|
||||||
label="update pacman databases"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
startIcon={<AddIcon />}
|
|
||||||
onClick={() => {
|
|
||||||
const id = variableIdCounter.current++;
|
|
||||||
setEnvironmentVariables(prev => [...prev, { id, key: "", value: "" }]);
|
|
||||||
}}
|
|
||||||
sx={{ mt: 1 }}
|
|
||||||
>
|
|
||||||
add environment variable
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{environmentVariables.map(variable =>
|
|
||||||
<Box key={variable.id} sx={{ display: "flex", gap: 1, mt: 1, alignItems: "center" }}>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
placeholder="name"
|
|
||||||
value={variable.key}
|
|
||||||
onChange={event => {
|
|
||||||
const newKey = event.target.value;
|
|
||||||
setEnvironmentVariables(prev =>
|
|
||||||
prev.map(entry => entry.id === variable.id ? { ...entry, key: newKey } : entry),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
sx={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
<Box>=</Box>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
placeholder="value"
|
|
||||||
value={variable.value}
|
|
||||||
onChange={event => {
|
|
||||||
const newValue = event.target.value;
|
|
||||||
setEnvironmentVariables(prev =>
|
|
||||||
prev.map(entry => entry.id === variable.id ? { ...entry, value: newValue } : entry),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
sx={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
<IconButton size="small" color="error" aria-label="Remove variable" onClick={() => setEnvironmentVariables(prev => prev.filter(entry => entry.id !== variable.id))}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Box>,
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => void handleAdd()} variant="contained" startIcon={<PlayArrowIcon />}>add</Button>
|
|
||||||
<Button onClick={() => void handleRequest()} variant="contained" color="success" startIcon={<AddIcon />}>request</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>;
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box, Dialog, DialogContent, Tab, Tabs } from "@mui/material";
|
|
||||||
import { skipToken, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import DialogHeader from "components/common/DialogHeader";
|
|
||||||
import BuildLogsTab from "components/package/BuildLogsTab";
|
|
||||||
import ChangesTab from "components/package/ChangesTab";
|
|
||||||
import EventsTab from "components/package/EventsTab";
|
|
||||||
import PackageDetailsGrid from "components/package/PackageDetailsGrid";
|
|
||||||
import PackageInfoActions from "components/package/PackageInfoActions";
|
|
||||||
import PackagePatchesList from "components/package/PackagePatchesList";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useAuth } from "hooks/useAuth";
|
|
||||||
import { useAutoRefresh } from "hooks/useAutoRefresh";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useNotification } from "hooks/useNotification";
|
|
||||||
import { useRepository } from "hooks/useRepository";
|
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
|
||||||
import type { Dependencies } from "models/Dependencies";
|
|
||||||
import type { PackageStatus } from "models/PackageStatus";
|
|
||||||
import type { Patch } from "models/Patch";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { StatusHeaderStyles } from "theme/StatusColors";
|
|
||||||
import { defaultInterval } from "utils";
|
|
||||||
|
|
||||||
interface PackageInfoDialogProps {
|
|
||||||
packageBase: string | null;
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
autoRefreshIntervals: AutoRefreshInterval[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageInfoDialog({
|
|
||||||
packageBase,
|
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
autoRefreshIntervals,
|
|
||||||
}: PackageInfoDialogProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const { current } = useRepository();
|
|
||||||
const { isAuthorized } = useAuth();
|
|
||||||
const { showSuccess, showError } = useNotification();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [localPackageBase, setLocalPackageBase] = useState(packageBase);
|
|
||||||
if (packageBase !== null && packageBase !== localPackageBase) {
|
|
||||||
setLocalPackageBase(packageBase);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
|
||||||
const [refreshDatabase, setRefreshDatabase] = useState(true);
|
|
||||||
|
|
||||||
const handleClose = (): void => {
|
|
||||||
setTabIndex(0);
|
|
||||||
setRefreshDatabase(true);
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const autoRefresh = useAutoRefresh("package-info-autoreload-button", defaultInterval(autoRefreshIntervals));
|
|
||||||
|
|
||||||
const { data: packageData } = useQuery<PackageStatus[]>({
|
|
||||||
queryKey: localPackageBase && current ? QueryKeys.package(localPackageBase, current) : ["packages"],
|
|
||||||
queryFn: localPackageBase && current ? () => client.fetchPackage(localPackageBase, current) : skipToken,
|
|
||||||
enabled: open,
|
|
||||||
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: dependencies } = useQuery<Dependencies>({
|
|
||||||
queryKey: localPackageBase && current ? QueryKeys.dependencies(localPackageBase, current) : ["dependencies"],
|
|
||||||
queryFn: localPackageBase && current
|
|
||||||
? () => client.fetchPackageDependencies(localPackageBase, current) : skipToken,
|
|
||||||
enabled: open,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: patches = [] } = useQuery<Patch[]>({
|
|
||||||
queryKey: localPackageBase ? QueryKeys.patches(localPackageBase) : ["patches"],
|
|
||||||
queryFn: localPackageBase ? () => client.fetchPackagePatches(localPackageBase) : skipToken,
|
|
||||||
enabled: open,
|
|
||||||
});
|
|
||||||
|
|
||||||
const description: PackageStatus | undefined = packageData?.[0];
|
|
||||||
const pkg = description?.package;
|
|
||||||
const status = description?.status;
|
|
||||||
const headerStyle = status ? StatusHeaderStyles[status.status] : {};
|
|
||||||
|
|
||||||
const handleUpdate: () => Promise<void> = async () => {
|
|
||||||
if (!localPackageBase || !current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await client.servicePackageAdd(current, { packages: [localPackageBase], refresh: refreshDatabase });
|
|
||||||
showSuccess("Success", `Run update for packages ${localPackageBase}`);
|
|
||||||
} catch (exception) {
|
|
||||||
showError("Action failed", `Package update failed: ${ApiError.errorDetail(exception)}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemove: () => Promise<void> = async () => {
|
|
||||||
if (!localPackageBase || !current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await client.servicePackageRemove(current, [localPackageBase]);
|
|
||||||
showSuccess("Success", `Packages ${localPackageBase} have been removed`);
|
|
||||||
onClose();
|
|
||||||
} catch (exception) {
|
|
||||||
showError("Action failed", `Could not remove package: ${ApiError.errorDetail(exception)}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeletePatch: (key: string) => Promise<void> = async key => {
|
|
||||||
if (!localPackageBase) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await client.servicePackagePatchRemove(localPackageBase, key);
|
|
||||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.patches(localPackageBase) });
|
|
||||||
} catch (exception) {
|
|
||||||
showError("Action failed", `Could not delete variable: ${ApiError.errorDetail(exception)}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="lg" fullWidth>
|
|
||||||
<DialogHeader onClose={handleClose} sx={headerStyle}>
|
|
||||||
{pkg && status
|
|
||||||
? `${pkg.base} ${status.status} at ${new Date(status.timestamp * 1000).toISOStringShort()}`
|
|
||||||
: localPackageBase ?? ""}
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
{pkg &&
|
|
||||||
<>
|
|
||||||
<PackageDetailsGrid pkg={pkg} dependencies={dependencies} />
|
|
||||||
<PackagePatchesList
|
|
||||||
patches={patches}
|
|
||||||
editable={isAuthorized}
|
|
||||||
onDelete={key => void handleDeletePatch(key)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: "divider", mt: 2 }}>
|
|
||||||
<Tabs value={tabIndex} onChange={(_, index: number) => setTabIndex(index)}>
|
|
||||||
<Tab label="Build logs" />
|
|
||||||
<Tab label="Changes" />
|
|
||||||
<Tab label="Events" />
|
|
||||||
</Tabs>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{tabIndex === 0 && localPackageBase && current &&
|
|
||||||
<BuildLogsTab
|
|
||||||
packageBase={localPackageBase}
|
|
||||||
repository={current}
|
|
||||||
refreshInterval={autoRefresh.interval}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{tabIndex === 1 && localPackageBase && current &&
|
|
||||||
<ChangesTab packageBase={localPackageBase} repository={current} />
|
|
||||||
}
|
|
||||||
{tabIndex === 2 && localPackageBase && current &&
|
|
||||||
<EventsTab packageBase={localPackageBase} repository={current} />
|
|
||||||
}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<PackageInfoActions
|
|
||||||
isAuthorized={isAuthorized}
|
|
||||||
refreshDatabase={refreshDatabase}
|
|
||||||
onRefreshDatabaseChange={setRefreshDatabase}
|
|
||||||
onUpdate={() => void handleUpdate()}
|
|
||||||
onRemove={() => void handleRemove()}
|
|
||||||
autoRefreshIntervals={autoRefreshIntervals}
|
|
||||||
autoRefreshInterval={autoRefresh.interval}
|
|
||||||
onAutoRefreshIntervalChange={autoRefresh.setInterval}
|
|
||||||
/>
|
|
||||||
</Dialog>;
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
|
||||||
import { Button, Dialog, DialogActions, DialogContent, TextField } from "@mui/material";
|
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import DialogHeader from "components/common/DialogHeader";
|
|
||||||
import RepositorySelect from "components/common/RepositorySelect";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useNotification } from "hooks/useNotification";
|
|
||||||
import { useSelectedRepository } from "hooks/useSelectedRepository";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
interface PackageRebuildDialogProps {
|
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageRebuildDialog({ open, onClose }: PackageRebuildDialogProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const { showSuccess, showError } = useNotification();
|
|
||||||
const repositorySelect = useSelectedRepository();
|
|
||||||
|
|
||||||
const [dependency, setDependency] = useState("");
|
|
||||||
|
|
||||||
const handleClose = (): void => {
|
|
||||||
setDependency("");
|
|
||||||
repositorySelect.reset();
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRebuild: () => Promise<void> = async () => {
|
|
||||||
if (!dependency) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const repository = repositorySelect.selectedRepository;
|
|
||||||
if (!repository) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await client.serviceRebuild(repository, [dependency]);
|
|
||||||
handleClose();
|
|
||||||
showSuccess("Success", `Repository rebuild has been run for packages which depend on ${dependency}`);
|
|
||||||
} catch (exception) {
|
|
||||||
const detail = ApiError.errorDetail(exception);
|
|
||||||
showError("Action failed", `Repository rebuild failed: ${detail}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
|
||||||
<DialogHeader onClose={handleClose}>
|
|
||||||
Rebuild depending packages
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<RepositorySelect repositorySelect={repositorySelect} />
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label="dependency"
|
|
||||||
placeholder="packages dependency"
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
value={dependency}
|
|
||||||
onChange={event => setDependency(event.target.value)}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={() => void handleRebuild()} variant="contained" startIcon={<PlayArrowIcon />}>rebuild</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>;
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box, Container } from "@mui/material";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import LoginDialog from "components/dialogs/LoginDialog";
|
|
||||||
import Footer from "components/layout/Footer";
|
|
||||||
import Navbar from "components/layout/Navbar";
|
|
||||||
import PackageTable from "components/table/PackageTable";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useAuth } from "hooks/useAuth";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useRepository } from "hooks/useRepository";
|
|
||||||
import type { InfoResponse } from "models/InfoResponse";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export default function AppLayout(): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const { setAuthState } = useAuth();
|
|
||||||
const { setRepositories } = useRepository();
|
|
||||||
const [loginOpen, setLoginOpen] = useState(false);
|
|
||||||
|
|
||||||
const { data: info } = useQuery<InfoResponse>({
|
|
||||||
queryKey: QueryKeys.info,
|
|
||||||
queryFn: () => client.fetchServerInfo(),
|
|
||||||
staleTime: Infinity,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sync info to contexts when loaded
|
|
||||||
useEffect(() => {
|
|
||||||
if (info) {
|
|
||||||
setAuthState({ enabled: info.auth.enabled, username: info.auth.username ?? null });
|
|
||||||
setRepositories(info.repositories);
|
|
||||||
}
|
|
||||||
}, [info, setAuthState, setRepositories]);
|
|
||||||
|
|
||||||
return <Container maxWidth="xl">
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", py: 1, gap: 1 }}>
|
|
||||||
<a href="https://github.com/arcan1s/ahriman" title="logo">
|
|
||||||
<img src="/static/logo.svg" width={30} height={30} alt="" />
|
|
||||||
</a>
|
|
||||||
<Box sx={{ flex: 1 }}>
|
|
||||||
<Navbar />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<PackageTable
|
|
||||||
autoRefreshIntervals={info?.autorefresh_intervals ?? []}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Footer
|
|
||||||
version={info?.version ?? ""}
|
|
||||||
docsEnabled={info?.docs_enabled ?? false}
|
|
||||||
indexUrl={info?.index_url}
|
|
||||||
onLoginClick={() => info?.auth.external ? window.location.assign("/api/v1/login") : setLoginOpen(true)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LoginDialog open={loginOpen} onClose={() => setLoginOpen(false)} />
|
|
||||||
</Container>;
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import GitHubIcon from "@mui/icons-material/GitHub";
|
|
||||||
import HomeIcon from "@mui/icons-material/Home";
|
|
||||||
import LoginIcon from "@mui/icons-material/Login";
|
|
||||||
import LogoutIcon from "@mui/icons-material/Logout";
|
|
||||||
import { Box, Button, Link, Typography } from "@mui/material";
|
|
||||||
import { useAuth } from "hooks/useAuth";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
interface FooterProps {
|
|
||||||
version: string;
|
|
||||||
docsEnabled: boolean;
|
|
||||||
indexUrl?: string;
|
|
||||||
onLoginClick: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Footer({ version, docsEnabled, indexUrl, onLoginClick }: FooterProps): React.JSX.Element {
|
|
||||||
const { enabled: authEnabled, username, logout } = useAuth();
|
|
||||||
|
|
||||||
return <Box
|
|
||||||
component="footer"
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
borderTop: 1,
|
|
||||||
borderColor: "divider",
|
|
||||||
mt: 2,
|
|
||||||
py: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", gap: 2, alignItems: "center" }}>
|
|
||||||
<Link href="https://github.com/arcan1s/ahriman" underline="hover" color="inherit" sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
|
||||||
<GitHubIcon fontSize="small" />
|
|
||||||
<Typography variant="body2">ahriman {version}</Typography>
|
|
||||||
</Link>
|
|
||||||
<Link href="https://github.com/arcan1s/ahriman/releases" underline="hover" color="text.secondary" variant="body2">
|
|
||||||
releases
|
|
||||||
</Link>
|
|
||||||
<Link href="https://github.com/arcan1s/ahriman/issues" underline="hover" color="text.secondary" variant="body2">
|
|
||||||
report a bug
|
|
||||||
</Link>
|
|
||||||
{docsEnabled &&
|
|
||||||
<Link href="/api-docs" underline="hover" color="text.secondary" variant="body2">
|
|
||||||
api
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{indexUrl &&
|
|
||||||
<Box>
|
|
||||||
<Link href={indexUrl} underline="hover" color="inherit" sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
|
||||||
<HomeIcon fontSize="small" />
|
|
||||||
<Typography variant="body2">repo index</Typography>
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
|
|
||||||
{authEnabled &&
|
|
||||||
<Box>
|
|
||||||
{username ?
|
|
||||||
<Button size="small" startIcon={<LogoutIcon />} onClick={() => void logout()} sx={{ textTransform: "none" }}>
|
|
||||||
logout ({username})
|
|
||||||
</Button>
|
|
||||||
:
|
|
||||||
<Button size="small" startIcon={<LoginIcon />} onClick={onLoginClick} sx={{ textTransform: "none" }}>
|
|
||||||
login
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box, Tab, Tabs } from "@mui/material";
|
|
||||||
import { useRepository } from "hooks/useRepository";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
export default function Navbar(): React.JSX.Element | null {
|
|
||||||
const { repositories, current, setCurrent } = useRepository();
|
|
||||||
|
|
||||||
if (repositories.length === 0 || !current) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentIndex = repositories.findIndex(repository =>
|
|
||||||
repository.architecture === current.architecture && repository.repository === current.repository,
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
|
||||||
<Tabs
|
|
||||||
value={currentIndex >= 0 ? currentIndex : 0}
|
|
||||||
onChange={(_, newValue: number) => {
|
|
||||||
const repository = repositories[newValue];
|
|
||||||
if (repository) {
|
|
||||||
setCurrent(repository);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
variant="scrollable"
|
|
||||||
scrollButtons="auto"
|
|
||||||
>
|
|
||||||
{repositories.map(repository =>
|
|
||||||
<Tab
|
|
||||||
key={repository.key}
|
|
||||||
label={repository.label}
|
|
||||||
/>,
|
|
||||||
)}
|
|
||||||
</Tabs>
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import ListIcon from "@mui/icons-material/List";
|
|
||||||
import { Box, Button, Menu, MenuItem, Typography } from "@mui/material";
|
|
||||||
import { keepPreviousData, skipToken, useQuery } from "@tanstack/react-query";
|
|
||||||
import CodeBlock from "components/common/CodeBlock";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useAutoScroll } from "hooks/useAutoScroll";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import type { LogRecord } from "models/LogRecord";
|
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
|
||||||
|
|
||||||
interface Logs {
|
|
||||||
version: string;
|
|
||||||
processId: string;
|
|
||||||
created: number;
|
|
||||||
logs: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BuildLogsTabProps {
|
|
||||||
packageBase: string;
|
|
||||||
repository: RepositoryId;
|
|
||||||
refreshInterval: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertLogs(records: LogRecord[], filter?: (record: LogRecord) => boolean): string {
|
|
||||||
const filtered = filter ? records.filter(filter) : records;
|
|
||||||
return filtered
|
|
||||||
.map(record => `[${new Date(record.created * 1000).toISOString()}] ${record.message}`)
|
|
||||||
.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BuildLogsTab({
|
|
||||||
packageBase,
|
|
||||||
repository,
|
|
||||||
refreshInterval,
|
|
||||||
}: BuildLogsTabProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const [selectedVersionKey, setSelectedVersionKey] = useState<string | null>(null);
|
|
||||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
const { data: allLogs } = useQuery<LogRecord[]>({
|
|
||||||
queryKey: QueryKeys.logs(packageBase, repository),
|
|
||||||
queryFn: () => client.fetchPackageLogs(packageBase, repository),
|
|
||||||
enabled: !!packageBase,
|
|
||||||
refetchInterval: refreshInterval > 0 ? refreshInterval : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build version selectors from all logs
|
|
||||||
const versions = useMemo<Logs[]>(() => {
|
|
||||||
if (!allLogs || allLogs.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const grouped: Record<string, LogRecord & { minCreated: number }> = {};
|
|
||||||
for (const record of allLogs) {
|
|
||||||
const key = `${record.version}-${record.process_id}`;
|
|
||||||
const existing = grouped[key];
|
|
||||||
if (!existing) {
|
|
||||||
grouped[key] = { ...record, minCreated: record.created };
|
|
||||||
} else {
|
|
||||||
existing.minCreated = Math.min(existing.minCreated, record.created);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.values(grouped)
|
|
||||||
.sort((left, right) => right.minCreated - left.minCreated)
|
|
||||||
.map(record => ({
|
|
||||||
version: record.version,
|
|
||||||
processId: record.process_id,
|
|
||||||
created: record.minCreated,
|
|
||||||
logs: convertLogs(
|
|
||||||
allLogs,
|
|
||||||
right => record.version === right.version && record.process_id === right.process_id,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}, [allLogs]);
|
|
||||||
|
|
||||||
// Compute active index from selected version key, defaulting to newest (index 0)
|
|
||||||
const activeIndex = useMemo(() => {
|
|
||||||
if (selectedVersionKey) {
|
|
||||||
const index = versions.findIndex(record => `${record.version}-${record.processId}` === selectedVersionKey);
|
|
||||||
if (index >= 0) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}, [versions, selectedVersionKey]);
|
|
||||||
|
|
||||||
const activeVersion = versions[activeIndex];
|
|
||||||
const activeVersionKey = activeVersion ? `${activeVersion.version}-${activeVersion.processId}` : null;
|
|
||||||
|
|
||||||
// Refresh active version logs
|
|
||||||
const { data: versionLogs } = useQuery<LogRecord[]>({
|
|
||||||
queryKey: QueryKeys.logsVersion(packageBase, repository, activeVersion?.version ?? "", activeVersion?.processId ?? ""),
|
|
||||||
queryFn: activeVersion
|
|
||||||
? () => client.fetchPackageLogs(packageBase, repository, activeVersion.version, activeVersion.processId)
|
|
||||||
: skipToken,
|
|
||||||
placeholderData: keepPreviousData,
|
|
||||||
refetchInterval: refreshInterval > 0 ? refreshInterval : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Derive displayed logs: prefer fresh polled data when available
|
|
||||||
const displayedLogs = useMemo(() => {
|
|
||||||
if (versionLogs && versionLogs.length > 0) {
|
|
||||||
return convertLogs(versionLogs);
|
|
||||||
}
|
|
||||||
return activeVersion?.logs ?? "";
|
|
||||||
}, [versionLogs, activeVersion]);
|
|
||||||
|
|
||||||
const { preRef, handleScroll, scrollToBottom, resetScroll } = useAutoScroll();
|
|
||||||
|
|
||||||
// Reset scroll tracking when active version changes
|
|
||||||
useEffect(() => {
|
|
||||||
resetScroll();
|
|
||||||
}, [activeVersionKey, resetScroll]);
|
|
||||||
|
|
||||||
// Scroll to bottom on new logs
|
|
||||||
useEffect(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, [displayedLogs, scrollToBottom]);
|
|
||||||
|
|
||||||
return <Box sx={{ display: "flex", gap: 1, mt: 1 }}>
|
|
||||||
<Box>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
aria-label="Select version"
|
|
||||||
startIcon={<ListIcon />}
|
|
||||||
onClick={event => setAnchorEl(event.currentTarget)}
|
|
||||||
/>
|
|
||||||
<Menu
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
open={Boolean(anchorEl)}
|
|
||||||
onClose={() => setAnchorEl(null)}
|
|
||||||
>
|
|
||||||
{versions.map((logs, index) =>
|
|
||||||
<MenuItem
|
|
||||||
key={`${logs.version}-${logs.processId}`}
|
|
||||||
selected={index === activeIndex}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedVersionKey(`${logs.version}-${logs.processId}`);
|
|
||||||
setAnchorEl(null);
|
|
||||||
resetScroll();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body2">{new Date(logs.created * 1000).toISOStringShort()}</Typography>
|
|
||||||
</MenuItem>,
|
|
||||||
)}
|
|
||||||
{versions.length === 0 &&
|
|
||||||
<MenuItem disabled>No logs available</MenuItem>
|
|
||||||
}
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ flex: 1 }}>
|
|
||||||
<CodeBlock
|
|
||||||
preRef={preRef}
|
|
||||||
getText={() => displayedLogs}
|
|
||||||
height={400}
|
|
||||||
onScroll={handleScroll}
|
|
||||||
wordBreak
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box } from "@mui/material";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import CopyButton from "components/common/CopyButton";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import type { Changes } from "models/Changes";
|
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
import React 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);
|
|
||||||
|
|
||||||
interface ChangesTabProps {
|
|
||||||
packageBase: string;
|
|
||||||
repository: RepositoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChangesTab({ packageBase, repository }: ChangesTabProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
|
|
||||||
const { data } = useQuery<Changes>({
|
|
||||||
queryKey: QueryKeys.changes(packageBase, repository),
|
|
||||||
queryFn: () => client.fetchPackageChanges(packageBase, repository),
|
|
||||||
enabled: !!packageBase,
|
|
||||||
});
|
|
||||||
|
|
||||||
const changesText = data?.changes ?? "";
|
|
||||||
|
|
||||||
return <Box sx={{ position: "relative", mt: 1 }}>
|
|
||||||
<SyntaxHighlighter
|
|
||||||
language="diff"
|
|
||||||
style={githubGist}
|
|
||||||
customStyle={{
|
|
||||||
padding: "16px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
overflow: "auto",
|
|
||||||
height: 400,
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
fontFamily: "monospace",
|
|
||||||
margin: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{changesText}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
|
|
||||||
<CopyButton getText={() => changesText} />
|
|
||||||
</Box>
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box } from "@mui/material";
|
|
||||||
import { DataGrid, type GridColDef } from "@mui/x-data-grid";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import EventDurationLineChart from "components/charts/EventDurationLineChart";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import type { Event } from "models/Event";
|
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
interface EventsTabProps {
|
|
||||||
packageBase: string;
|
|
||||||
repository: RepositoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventRow {
|
|
||||||
id: number;
|
|
||||||
timestamp: string;
|
|
||||||
event: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns: GridColDef<EventRow>[] = [
|
|
||||||
{ field: "timestamp", headerName: "date", width: 180, align: "right", headerAlign: "right" },
|
|
||||||
{ field: "event", headerName: "event", flex: 1 },
|
|
||||||
{ field: "message", headerName: "description", flex: 2 },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function EventsTab({ packageBase, repository }: EventsTabProps): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
|
|
||||||
const { data: events = [] } = useQuery<Event[]>({
|
|
||||||
queryKey: QueryKeys.events(repository, packageBase),
|
|
||||||
queryFn: () => client.fetchPackageEvents(repository, packageBase, 30),
|
|
||||||
enabled: !!packageBase,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rows: EventRow[] = events.map((event, index) => ({
|
|
||||||
id: index,
|
|
||||||
timestamp: new Date(event.created * 1000).toISOStringShort(),
|
|
||||||
event: event.event,
|
|
||||||
message: event.message ?? "",
|
|
||||||
}));
|
|
||||||
|
|
||||||
return <Box sx={{ mt: 1 }}>
|
|
||||||
<EventDurationLineChart events={events} />
|
|
||||||
<DataGrid
|
|
||||||
rows={rows}
|
|
||||||
columns={columns}
|
|
||||||
density="compact"
|
|
||||||
initialState={{
|
|
||||||
sorting: { sortModel: [{ field: "timestamp", sort: "desc" }] },
|
|
||||||
}}
|
|
||||||
pageSizeOptions={[10, 25]}
|
|
||||||
sx={{ height: 400, mt: 1 }}
|
|
||||||
disableRowSelectionOnClick
|
|
||||||
/>
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Grid, Link, Typography } from "@mui/material";
|
|
||||||
import StringList from "components/common/StringList";
|
|
||||||
import type { Dependencies } from "models/Dependencies";
|
|
||||||
import type { Package } from "models/Package";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface PackageDetailsGridProps {
|
|
||||||
pkg: Package;
|
|
||||||
dependencies?: Dependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageDetailsGrid({ pkg, dependencies }: PackageDetailsGridProps): React.JSX.Element {
|
|
||||||
const packagesList = Object.entries(pkg.packages)
|
|
||||||
.map(([name, properties]) => `${name}${properties.description ? ` (${properties.description})` : ""}`);
|
|
||||||
|
|
||||||
const groups = Object.values(pkg.packages)
|
|
||||||
.flatMap(properties => properties.groups ?? []);
|
|
||||||
|
|
||||||
const licenses = Object.values(pkg.packages)
|
|
||||||
.flatMap(properties => properties.licenses ?? []);
|
|
||||||
|
|
||||||
const upstreamUrls = Object.values(pkg.packages)
|
|
||||||
.map(properties => properties.url)
|
|
||||||
.filter((url): url is string => !!url)
|
|
||||||
.unique();
|
|
||||||
|
|
||||||
const aurUrl = pkg.remote.web_url;
|
|
||||||
|
|
||||||
const pkgNames = Object.keys(pkg.packages);
|
|
||||||
const pkgValues = Object.values(pkg.packages);
|
|
||||||
const deps = pkgValues
|
|
||||||
.flatMap(properties => (properties.depends ?? []).filter(dep => !pkgNames.includes(dep)))
|
|
||||||
.unique();
|
|
||||||
const makeDeps = pkgValues
|
|
||||||
.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
|
|
||||||
? Object.values(dependencies.paths).flat()
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<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: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={packagesList.unique()} /></Typography></Grid>
|
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">version</Typography></Grid>
|
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2">{pkg.version}</Typography></Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid container spacing={1} sx={{ mt: 0.5 }}>
|
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">packager</Typography></Grid>
|
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2">{pkg.packager ?? ""}</Typography></Grid>
|
|
||||||
<Grid size={{ xs: 4, md: 1 }} />
|
|
||||||
<Grid size={{ xs: 8, md: 5 }} />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<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: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={groups.unique()} /></Typography></Grid>
|
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">licenses</Typography></Grid>
|
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={licenses.unique()} /></Typography></Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<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: 8, md: 5 }}>
|
|
||||||
{upstreamUrls.map(url =>
|
|
||||||
<Link key={url} href={url} target="_blank" rel="noopener" underline="hover" display="block" variant="body2">
|
|
||||||
{url}
|
|
||||||
</Link>,
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">AUR</Typography></Grid>
|
|
||||||
<Grid size={{ xs: 8, md: 5 }}>
|
|
||||||
<Typography variant="body2">
|
|
||||||
{aurUrl &&
|
|
||||||
<Link href={aurUrl} target="_blank" rel="noopener" underline="hover">AUR link</Link>
|
|
||||||
}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<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: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={allDepends} /></Typography></Grid>
|
|
||||||
<Grid size={{ xs: 4, md: 1 }}><Typography variant="body2" color="text.secondary" align="right">implicitly depends</Typography></Grid>
|
|
||||||
<Grid size={{ xs: 8, md: 5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-line" }}><StringList items={implicitDepends.unique()} /></Typography></Grid>
|
|
||||||
</Grid>
|
|
||||||
</>;
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
|
||||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
|
||||||
import { Button, Checkbox, DialogActions, FormControlLabel } from "@mui/material";
|
|
||||||
import AutoRefreshControl from "components/common/AutoRefreshControl";
|
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
interface PackageInfoActionsProps {
|
|
||||||
isAuthorized: boolean;
|
|
||||||
refreshDatabase: boolean;
|
|
||||||
onRefreshDatabaseChange: (checked: boolean) => void;
|
|
||||||
onUpdate: () => void;
|
|
||||||
onRemove: () => void;
|
|
||||||
autoRefreshIntervals: AutoRefreshInterval[];
|
|
||||||
autoRefreshInterval: number;
|
|
||||||
onAutoRefreshIntervalChange: (interval: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageInfoActions({
|
|
||||||
isAuthorized,
|
|
||||||
refreshDatabase,
|
|
||||||
onRefreshDatabaseChange,
|
|
||||||
onUpdate,
|
|
||||||
onRemove,
|
|
||||||
autoRefreshIntervals,
|
|
||||||
autoRefreshInterval,
|
|
||||||
onAutoRefreshIntervalChange,
|
|
||||||
}: PackageInfoActionsProps): React.JSX.Element {
|
|
||||||
return <DialogActions sx={{ flexWrap: "wrap", gap: 1 }}>
|
|
||||||
{isAuthorized &&
|
|
||||||
<>
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox checked={refreshDatabase} onChange={(_, checked) => onRefreshDatabaseChange(checked)} size="small" />}
|
|
||||||
label="update pacman databases"
|
|
||||||
/>
|
|
||||||
<Button onClick={onUpdate} variant="contained" color="success" startIcon={<PlayArrowIcon />} size="small">
|
|
||||||
update
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onRemove} variant="contained" color="error" startIcon={<DeleteIcon />} size="small">
|
|
||||||
remove
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
<AutoRefreshControl
|
|
||||||
intervals={autoRefreshIntervals}
|
|
||||||
currentInterval={autoRefreshInterval}
|
|
||||||
onIntervalChange={onAutoRefreshIntervalChange}
|
|
||||||
/>
|
|
||||||
</DialogActions>;
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
|
||||||
import { Box, IconButton, TextField, Typography } from "@mui/material";
|
|
||||||
import type { Patch } from "models/Patch";
|
|
||||||
import type React from "react";
|
|
||||||
|
|
||||||
interface PackagePatchesListProps {
|
|
||||||
patches: Patch[];
|
|
||||||
editable: boolean;
|
|
||||||
onDelete: (key: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackagePatchesList({
|
|
||||||
patches,
|
|
||||||
editable,
|
|
||||||
onDelete,
|
|
||||||
}: PackagePatchesListProps): React.JSX.Element | null {
|
|
||||||
if (patches.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Box sx={{ mt: 2 }}>
|
|
||||||
<Typography variant="h6" gutterBottom>Environment variables</Typography>
|
|
||||||
{patches.map(patch =>
|
|
||||||
<Box key={patch.key} sx={{ display: "flex", alignItems: "center", gap: 1, mb: 0.5 }}>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
value={patch.key}
|
|
||||||
disabled
|
|
||||||
sx={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
<Box>=</Box>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
value={JSON.stringify(patch.value)}
|
|
||||||
disabled
|
|
||||||
sx={{ flex: 1 }}
|
|
||||||
/>
|
|
||||||
{editable &&
|
|
||||||
<IconButton size="small" color="error" onClick={() => onDelete(patch.key)}>
|
|
||||||
<DeleteIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
</Box>,
|
|
||||||
)}
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Box, Link } from "@mui/material";
|
|
||||||
import {
|
|
||||||
DataGrid,
|
|
||||||
GRID_CHECKBOX_SELECTION_COL_DEF,
|
|
||||||
type GridColDef,
|
|
||||||
type GridFilterModel,
|
|
||||||
type GridRenderCellParams,
|
|
||||||
type GridRowId,
|
|
||||||
useGridApiRef,
|
|
||||||
} from "@mui/x-data-grid";
|
|
||||||
import StringList from "components/common/StringList";
|
|
||||||
import DashboardDialog from "components/dialogs/DashboardDialog";
|
|
||||||
import KeyImportDialog from "components/dialogs/KeyImportDialog";
|
|
||||||
import PackageAddDialog from "components/dialogs/PackageAddDialog";
|
|
||||||
import PackageInfoDialog from "components/dialogs/PackageInfoDialog";
|
|
||||||
import PackageRebuildDialog from "components/dialogs/PackageRebuildDialog";
|
|
||||||
import PackageTableToolbar from "components/table/PackageTableToolbar";
|
|
||||||
import StatusCell from "components/table/StatusCell";
|
|
||||||
import { useDebounce } from "hooks/useDebounce";
|
|
||||||
import { usePackageTable } from "hooks/usePackageTable";
|
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
|
||||||
import type { PackageRow } from "models/PackageRow";
|
|
||||||
import React, { useMemo } from "react";
|
|
||||||
|
|
||||||
interface PackageTableProps {
|
|
||||||
autoRefreshIntervals: AutoRefreshInterval[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
||||||
|
|
||||||
function createListColumn(
|
|
||||||
field: keyof PackageRow,
|
|
||||||
headerName: string,
|
|
||||||
options: { flex?: number; minWidth?: number; width?: number },
|
|
||||||
): GridColDef<PackageRow> {
|
|
||||||
return {
|
|
||||||
field,
|
|
||||||
headerName,
|
|
||||||
...options,
|
|
||||||
valueGetter: (value: string[]) => (value ?? []).join(" "),
|
|
||||||
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
|
||||||
<Box sx={{ whiteSpace: "pre-line" }}><StringList items={(params.row[field] as string[]) ?? []} /></Box>,
|
|
||||||
sortComparator: (left: string, right: string) => left.localeCompare(right),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageTable({ autoRefreshIntervals }: PackageTableProps): React.JSX.Element {
|
|
||||||
const table = usePackageTable(autoRefreshIntervals);
|
|
||||||
const apiRef = useGridApiRef();
|
|
||||||
const debouncedSearch = useDebounce(table.searchText, 300);
|
|
||||||
|
|
||||||
const effectiveFilterModel: GridFilterModel = useMemo(
|
|
||||||
() => ({
|
|
||||||
...table.filterModel,
|
|
||||||
quickFilterValues: debouncedSearch ? debouncedSearch.split(/\s+/) : undefined,
|
|
||||||
}),
|
|
||||||
[table.filterModel, debouncedSearch],
|
|
||||||
);
|
|
||||||
|
|
||||||
const columns: GridColDef<PackageRow>[] = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
field: "base",
|
|
||||||
headerName: "package base",
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 150,
|
|
||||||
renderCell: (params: GridRenderCellParams<PackageRow>) =>
|
|
||||||
params.row.webUrl ?
|
|
||||||
<Link href={params.row.webUrl} target="_blank" rel="noopener" underline="hover">
|
|
||||||
{params.value as string}
|
|
||||||
</Link>
|
|
||||||
: params.value as string,
|
|
||||||
},
|
|
||||||
{ field: "version", headerName: "version", width: 180, align: "right", headerAlign: "right" },
|
|
||||||
createListColumn("packages", "packages", { flex: 1, minWidth: 120 }),
|
|
||||||
createListColumn("groups", "groups", { width: 150 }),
|
|
||||||
createListColumn("licenses", "licenses", { width: 150 }),
|
|
||||||
{ field: "packager", headerName: "packager", width: 150 },
|
|
||||||
{
|
|
||||||
field: "timestamp",
|
|
||||||
headerName: "last update",
|
|
||||||
width: 180,
|
|
||||||
align: "right",
|
|
||||||
headerAlign: "right",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: "status",
|
|
||||||
headerName: "status",
|
|
||||||
width: 120,
|
|
||||||
align: "center",
|
|
||||||
headerAlign: "center",
|
|
||||||
renderCell: (params: GridRenderCellParams<PackageRow>) => <StatusCell status={params.row.status} />,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Box>
|
|
||||||
<PackageTableToolbar
|
|
||||||
hasSelection={table.selectionModel.length > 0}
|
|
||||||
isAuthorized={table.isAuthorized}
|
|
||||||
status={table.status}
|
|
||||||
searchText={table.searchText}
|
|
||||||
onSearchChange={table.setSearchText}
|
|
||||||
autoRefresh={{
|
|
||||||
autoRefreshIntervals,
|
|
||||||
currentInterval: table.autoRefreshInterval,
|
|
||||||
onIntervalChange: table.onAutoRefreshIntervalChange,
|
|
||||||
}}
|
|
||||||
actions={{
|
|
||||||
onDashboardClick: () => table.setDialogOpen("dashboard"),
|
|
||||||
onAddClick: () => table.setDialogOpen("add"),
|
|
||||||
onUpdateClick: () => void table.handleUpdate(),
|
|
||||||
onRefreshDbClick: () => void table.handleRefreshDb(),
|
|
||||||
onRebuildClick: () => table.setDialogOpen("rebuild"),
|
|
||||||
onRemoveClick: () => void table.handleRemove(),
|
|
||||||
onKeyImportClick: () => table.setDialogOpen("keyImport"),
|
|
||||||
onReloadClick: table.handleReload,
|
|
||||||
onExportClick: () => apiRef.current?.exportDataAsCsv(),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DataGrid
|
|
||||||
apiRef={apiRef}
|
|
||||||
rows={table.rows}
|
|
||||||
columns={columns}
|
|
||||||
loading={table.isLoading}
|
|
||||||
getRowHeight={() => "auto"}
|
|
||||||
checkboxSelection
|
|
||||||
disableRowSelectionOnClick
|
|
||||||
rowSelectionModel={{ type: "include", ids: new Set<GridRowId>(table.selectionModel) }}
|
|
||||||
onRowSelectionModelChange={model => {
|
|
||||||
if (model.type === "exclude") {
|
|
||||||
const excludeIds = new Set([...model.ids].map(String));
|
|
||||||
table.setSelectionModel(table.rows.map(row => row.id).filter(id => !excludeIds.has(id)));
|
|
||||||
} else {
|
|
||||||
table.setSelectionModel([...model.ids].map(String));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
paginationModel={table.paginationModel}
|
|
||||||
onPaginationModelChange={table.setPaginationModel}
|
|
||||||
pageSizeOptions={PAGE_SIZE_OPTIONS}
|
|
||||||
columnVisibilityModel={table.columnVisibility}
|
|
||||||
onColumnVisibilityModelChange={table.setColumnVisibility}
|
|
||||||
filterModel={effectiveFilterModel}
|
|
||||||
onFilterModelChange={table.setFilterModel}
|
|
||||||
initialState={{
|
|
||||||
sorting: { sortModel: [{ field: "base", sort: "asc" }] },
|
|
||||||
}}
|
|
||||||
onCellClick={(params, event) => {
|
|
||||||
// Don't open info dialog when clicking checkbox or link
|
|
||||||
if (params.field === GRID_CHECKBOX_SELECTION_COL_DEF.field) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((event.target as HTMLElement).closest("a")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
table.setSelectedPackage(String(params.id));
|
|
||||||
}}
|
|
||||||
autoHeight
|
|
||||||
sx={{
|
|
||||||
"& .MuiDataGrid-row": { cursor: "pointer" },
|
|
||||||
}}
|
|
||||||
density="compact"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DashboardDialog open={table.dialogOpen === "dashboard"} onClose={() => table.setDialogOpen(null)} />
|
|
||||||
<PackageAddDialog open={table.dialogOpen === "add"} onClose={() => table.setDialogOpen(null)} />
|
|
||||||
<PackageRebuildDialog open={table.dialogOpen === "rebuild"} onClose={() => table.setDialogOpen(null)} />
|
|
||||||
<KeyImportDialog open={table.dialogOpen === "keyImport"} onClose={() => table.setDialogOpen(null)} />
|
|
||||||
<PackageInfoDialog
|
|
||||||
packageBase={table.selectedPackage}
|
|
||||||
open={table.selectedPackage !== null}
|
|
||||||
onClose={() => table.setSelectedPackage(null)}
|
|
||||||
autoRefreshIntervals={autoRefreshIntervals}
|
|
||||||
/>
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
|
||||||
import ClearIcon from "@mui/icons-material/Clear";
|
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
|
||||||
import FileDownloadIcon from "@mui/icons-material/FileDownload";
|
|
||||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
|
||||||
import InventoryIcon from "@mui/icons-material/Inventory";
|
|
||||||
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
|
||||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
|
||||||
import ReplayIcon from "@mui/icons-material/Replay";
|
|
||||||
import SearchIcon from "@mui/icons-material/Search";
|
|
||||||
import VpnKeyIcon from "@mui/icons-material/VpnKey";
|
|
||||||
import { Box, Button, Divider, IconButton, InputAdornment, Menu, MenuItem, TextField, Tooltip } from "@mui/material";
|
|
||||||
import AutoRefreshControl from "components/common/AutoRefreshControl";
|
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
|
||||||
import type { BuildStatus } from "models/BuildStatus";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { StatusColors } from "theme/StatusColors";
|
|
||||||
|
|
||||||
export interface AutoRefreshProps {
|
|
||||||
autoRefreshIntervals: AutoRefreshInterval[];
|
|
||||||
currentInterval: number;
|
|
||||||
onIntervalChange: (interval: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolbarActions {
|
|
||||||
onDashboardClick: () => void;
|
|
||||||
onAddClick: () => void;
|
|
||||||
onUpdateClick: () => void;
|
|
||||||
onRefreshDbClick: () => void;
|
|
||||||
onRebuildClick: () => void;
|
|
||||||
onRemoveClick: () => void;
|
|
||||||
onKeyImportClick: () => void;
|
|
||||||
onReloadClick: () => void;
|
|
||||||
onExportClick: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PackageTableToolbarProps {
|
|
||||||
hasSelection: boolean;
|
|
||||||
isAuthorized: boolean;
|
|
||||||
status?: BuildStatus;
|
|
||||||
searchText: string;
|
|
||||||
onSearchChange: (text: string) => void;
|
|
||||||
autoRefresh: AutoRefreshProps;
|
|
||||||
actions: ToolbarActions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PackageTableToolbar({
|
|
||||||
hasSelection,
|
|
||||||
isAuthorized,
|
|
||||||
status,
|
|
||||||
searchText,
|
|
||||||
onSearchChange,
|
|
||||||
autoRefresh,
|
|
||||||
actions,
|
|
||||||
}: PackageTableToolbarProps): React.JSX.Element {
|
|
||||||
const [packagesAnchorEl, setPackagesAnchorEl] = useState<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
return <Box sx={{ display: "flex", gap: 1, mb: 1, flexWrap: "wrap", alignItems: "center" }}>
|
|
||||||
<Tooltip title="System health">
|
|
||||||
<IconButton
|
|
||||||
aria-label="System health"
|
|
||||||
onClick={actions.onDashboardClick}
|
|
||||||
sx={{
|
|
||||||
borderColor: status ? StatusColors[status] : undefined,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderStyle: "solid",
|
|
||||||
color: status ? StatusColors[status] : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<InfoOutlinedIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{isAuthorized &&
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
startIcon={<InventoryIcon />}
|
|
||||||
onClick={event => setPackagesAnchorEl(event.currentTarget)}
|
|
||||||
>
|
|
||||||
packages
|
|
||||||
</Button>
|
|
||||||
<Menu
|
|
||||||
anchorEl={packagesAnchorEl}
|
|
||||||
open={Boolean(packagesAnchorEl)}
|
|
||||||
onClose={() => setPackagesAnchorEl(null)}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={() => {
|
|
||||||
setPackagesAnchorEl(null); actions.onAddClick();
|
|
||||||
}}>
|
|
||||||
<AddIcon fontSize="small" sx={{ mr: 1 }} /> add
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() => {
|
|
||||||
setPackagesAnchorEl(null); actions.onUpdateClick();
|
|
||||||
}}>
|
|
||||||
<PlayArrowIcon fontSize="small" sx={{ mr: 1 }} /> update
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() => {
|
|
||||||
setPackagesAnchorEl(null); actions.onRefreshDbClick();
|
|
||||||
}}>
|
|
||||||
<DownloadIcon fontSize="small" sx={{ mr: 1 }} /> update pacman databases
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() => {
|
|
||||||
setPackagesAnchorEl(null); actions.onRebuildClick();
|
|
||||||
}}>
|
|
||||||
<ReplayIcon fontSize="small" sx={{ mr: 1 }} /> rebuild
|
|
||||||
</MenuItem>
|
|
||||||
<Divider />
|
|
||||||
<MenuItem onClick={() => {
|
|
||||||
setPackagesAnchorEl(null); actions.onRemoveClick();
|
|
||||||
}} disabled={!hasSelection}>
|
|
||||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> remove
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
|
|
||||||
<Button variant="contained" color="info" startIcon={<VpnKeyIcon />} onClick={actions.onKeyImportClick}>
|
|
||||||
import key
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Button variant="outlined" color="secondary" startIcon={<RefreshIcon />} onClick={actions.onReloadClick}>
|
|
||||||
reload
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<AutoRefreshControl
|
|
||||||
intervals={autoRefresh.autoRefreshIntervals}
|
|
||||||
currentInterval={autoRefresh.currentInterval}
|
|
||||||
onIntervalChange={autoRefresh.onIntervalChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
aria-label="Search packages"
|
|
||||||
placeholder="search packages..."
|
|
||||||
value={searchText}
|
|
||||||
onChange={event => onSearchChange(event.target.value)}
|
|
||||||
slotProps={{
|
|
||||||
input: {
|
|
||||||
startAdornment:
|
|
||||||
<InputAdornment position="start">
|
|
||||||
<SearchIcon fontSize="small" />
|
|
||||||
</InputAdornment>
|
|
||||||
,
|
|
||||||
endAdornment: searchText ?
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton size="small" aria-label="Clear search" onClick={() => onSearchChange("")}>
|
|
||||||
<ClearIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
sx={{ minWidth: 200 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tooltip title="Export CSV">
|
|
||||||
<IconButton size="small" aria-label="Export CSV" onClick={actions.onExportClick}>
|
|
||||||
<FileDownloadIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Chip } from "@mui/material";
|
|
||||||
import type { BuildStatus } from "models/BuildStatus";
|
|
||||||
import type React from "react";
|
|
||||||
import { StatusColors } from "theme/StatusColors";
|
|
||||||
|
|
||||||
interface StatusCellProps {
|
|
||||||
status: BuildStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function StatusCell({ status }: StatusCellProps): React.JSX.Element {
|
|
||||||
return <Chip
|
|
||||||
label={status}
|
|
||||||
size="small"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: StatusColors[status],
|
|
||||||
color: "common.white",
|
|
||||||
fontWeight: 500,
|
|
||||||
}}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { createContext } from "react";
|
|
||||||
|
|
||||||
interface AuthState {
|
|
||||||
enabled: boolean;
|
|
||||||
username: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthContextValue extends AuthState {
|
|
||||||
isAuthorized: boolean;
|
|
||||||
setAuthState: (state: AuthState) => void;
|
|
||||||
login: (username: string, password: string) => Promise<void>;
|
|
||||||
logout: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AuthContext = createContext<AuthContextValue | null>(null);
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { AuthContext } from "contexts/AuthContext";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import React, { type ReactNode, useCallback, useMemo, useState } from "react";
|
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
|
||||||
const client = useClient();
|
|
||||||
const [state, setState] = useState({ enabled: true, username: null as string | null });
|
|
||||||
|
|
||||||
const login = useCallback(async (username: string, password: string) => {
|
|
||||||
await client.login({ username, password });
|
|
||||||
setState(prev => ({ ...prev, username }));
|
|
||||||
}, [client]);
|
|
||||||
|
|
||||||
const doLogout = useCallback(async () => {
|
|
||||||
await client.logout();
|
|
||||||
setState(prev => ({ ...prev, username: null }));
|
|
||||||
}, [client]);
|
|
||||||
|
|
||||||
const isAuthorized = useMemo(() => !state.enabled || state.username !== null, [state.enabled, state.username]);
|
|
||||||
|
|
||||||
const value = useMemo(() => ({
|
|
||||||
...state, isAuthorized, setAuthState: setState, login, logout: doLogout,
|
|
||||||
}), [state, isAuthorized, login, doLogout]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</AuthContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { AhrimanClient } from "api/client/AhrimanClient";
|
|
||||||
import { createContext } from "react";
|
|
||||||
|
|
||||||
export const ClientContext = createContext<AhrimanClient | null>(null);
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { AhrimanClient } from "api/client/AhrimanClient";
|
|
||||||
import { ClientContext } from "contexts/ClientContext";
|
|
||||||
import React, { type ReactNode, useMemo } from "react";
|
|
||||||
|
|
||||||
export function ClientProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
|
||||||
const client = useMemo(() => new AhrimanClient(), []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ClientContext.Provider value={client}>
|
|
||||||
{children}
|
|
||||||
</ClientContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { AlertColor } from "@mui/material";
|
|
||||||
|
|
||||||
export interface Notification {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
message: string;
|
|
||||||
severity: AlertColor;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { createContext } from "react";
|
|
||||||
|
|
||||||
export interface NotificationContextValue {
|
|
||||||
showSuccess: (title: string, message: string) => void;
|
|
||||||
showError: (title: string, message: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const NotificationContext = createContext<NotificationContextValue | null>(null);
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { Alert, Slide } from "@mui/material";
|
|
||||||
import type { Notification } from "contexts/Notification";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
interface NotificationItemProps {
|
|
||||||
notification: Notification;
|
|
||||||
onClose: (id: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NotificationItem({ notification, onClose }: NotificationItemProps): React.JSX.Element {
|
|
||||||
const [show, setShow] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => setShow(false), 5000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Slide direction="down" in={show} mountOnEnter unmountOnExit onExited={() => onClose(notification.id)}>
|
|
||||||
<Alert
|
|
||||||
onClose={() => setShow(false)}
|
|
||||||
severity={notification.severity}
|
|
||||||
variant="filled"
|
|
||||||
sx={{ width: "100%", pointerEvents: "auto" }}
|
|
||||||
>
|
|
||||||
<strong>{notification.title}</strong>
|
|
||||||
{notification.message && ` - ${notification.message}`}
|
|
||||||
</Alert>
|
|
||||||
</Slide>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { type AlertColor, Box } from "@mui/material";
|
|
||||||
import type { Notification } from "contexts/Notification";
|
|
||||||
import { NotificationContext } from "contexts/NotificationContext";
|
|
||||||
import NotificationItem from "contexts/NotificationItem";
|
|
||||||
import React, { type ReactNode, useCallback, useMemo, useRef, useState } from "react";
|
|
||||||
|
|
||||||
export function NotificationProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
|
||||||
const nextId = useRef(0);
|
|
||||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
||||||
|
|
||||||
const addNotification = useCallback((title: string, message: string, severity: AlertColor) => {
|
|
||||||
const id = nextId.current++;
|
|
||||||
setNotifications(prev => [...prev, { id, title, message, severity }]);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const removeNotification = useCallback((id: number) => {
|
|
||||||
setNotifications(prev => prev.filter(notification => notification.id !== id));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const showSuccess = useCallback(
|
|
||||||
(title: string, message: string) => addNotification(title, message, "success"),
|
|
||||||
[addNotification],
|
|
||||||
);
|
|
||||||
const showError = useCallback(
|
|
||||||
(title: string, message: string) => addNotification(title, message, "error"),
|
|
||||||
[addNotification],
|
|
||||||
);
|
|
||||||
|
|
||||||
const value = useMemo(() => ({ showSuccess, showError }), [showSuccess, showError]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NotificationContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "fixed",
|
|
||||||
top: 16,
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
zIndex: theme => theme.zIndex.snackbar,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: 1,
|
|
||||||
maxWidth: 500,
|
|
||||||
width: "100%",
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{notifications.map(notification =>
|
|
||||||
<NotificationItem key={notification.id} notification={notification} onClose={removeNotification} />,
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</NotificationContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
import { createContext } from "react";
|
|
||||||
|
|
||||||
export interface RepositoryContextValue {
|
|
||||||
repositories: RepositoryId[];
|
|
||||||
current: RepositoryId | null;
|
|
||||||
setRepositories: (repositories: RepositoryId[]) => void;
|
|
||||||
setCurrent: (repository: RepositoryId) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RepositoryContext = createContext<RepositoryContextValue | null>(null);
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { RepositoryContext } from "contexts/RepositoryContext";
|
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
import React, { type ReactNode, useCallback, useMemo, useState, useSyncExternalStore } from "react";
|
|
||||||
|
|
||||||
function subscribeToHash(callback: () => void): () => void {
|
|
||||||
window.addEventListener("hashchange", callback);
|
|
||||||
return () => window.removeEventListener("hashchange", callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHashSnapshot(): string {
|
|
||||||
return window.location.hash.replace("#", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RepositoryProvider({ children }: { children: ReactNode }): React.JSX.Element {
|
|
||||||
const [repositories, setRepositories] = useState<RepositoryId[]>([]);
|
|
||||||
const hash = useSyncExternalStore(subscribeToHash, getHashSnapshot);
|
|
||||||
|
|
||||||
const current = useMemo(() => {
|
|
||||||
if (repositories.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return repositories.find(repository => repository.key === hash) ?? repositories[0] ?? null;
|
|
||||||
}, [repositories, hash]);
|
|
||||||
|
|
||||||
const setCurrent = useCallback((repository: RepositoryId) => {
|
|
||||||
window.location.hash = repository.key;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const value = useMemo(() => ({
|
|
||||||
repositories, current, setRepositories, setCurrent,
|
|
||||||
}), [repositories, current, setCurrent]);
|
|
||||||
|
|
||||||
return <RepositoryContext.Provider value={value}>{children}</RepositoryContext.Provider>;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { RepositoryId } from "models/RepositoryId";
|
|
||||||
|
|
||||||
export const QueryKeys = {
|
|
||||||
|
|
||||||
changes: (packageBase: string, repository: RepositoryId) => ["changes", repository.key, packageBase] as const,
|
|
||||||
|
|
||||||
dependencies: (packageBase: string, repository: RepositoryId) => ["dependencies", repository.key, packageBase] as const,
|
|
||||||
|
|
||||||
events: (repository: RepositoryId, objectId?: string) => ["events", repository.key, objectId] as const,
|
|
||||||
|
|
||||||
info: ["info"] as const,
|
|
||||||
|
|
||||||
logs: (packageBase: string, repository: RepositoryId) => ["logs", repository.key, packageBase] as const,
|
|
||||||
|
|
||||||
logsVersion: (packageBase: string, repository: RepositoryId, version: string, processId: string) =>
|
|
||||||
["logs", repository.key, packageBase, version, processId] as const,
|
|
||||||
|
|
||||||
package: (packageBase: string, repository: RepositoryId) => ["packages", repository.key, packageBase] as const,
|
|
||||||
|
|
||||||
packages: (repository: RepositoryId) => ["packages", repository.key] as const,
|
|
||||||
|
|
||||||
patches: (packageBase: string) => ["patches", packageBase] as const,
|
|
||||||
|
|
||||||
search: (query: string) => ["search", query] as const,
|
|
||||||
|
|
||||||
status: (repository: RepositoryId) => ["status", repository.key] as const,
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { AuthContext, type AuthContextValue } from "contexts/AuthContext";
|
|
||||||
import { useContextNotNull } from "hooks/useContextNotNull";
|
|
||||||
|
|
||||||
export function useAuth(): AuthContextValue {
|
|
||||||
return useContextNotNull(AuthContext);
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { useLocalStorage } from "hooks/useLocalStorage";
|
|
||||||
import { type Dispatch, type SetStateAction, useEffect, useState } from "react";
|
|
||||||
|
|
||||||
interface AutoRefreshResult {
|
|
||||||
interval: number;
|
|
||||||
setInterval: Dispatch<SetStateAction<number>>;
|
|
||||||
setPaused: Dispatch<SetStateAction<boolean>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAutoRefresh(key: string, defaultInterval: number): AutoRefreshResult {
|
|
||||||
const storageKey = `ahriman-${key}`;
|
|
||||||
const [interval, setInterval] = useLocalStorage<number>(storageKey, defaultInterval);
|
|
||||||
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 {
|
|
||||||
interval: effectiveInterval,
|
|
||||||
setInterval,
|
|
||||||
setPaused,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { type RefObject, useRef } from "react";
|
|
||||||
|
|
||||||
interface UseAutoScrollResult {
|
|
||||||
preRef: RefObject<HTMLElement | null>;
|
|
||||||
handleScroll: () => void;
|
|
||||||
scrollToBottom: () => void;
|
|
||||||
resetScroll: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAutoScroll(): UseAutoScrollResult {
|
|
||||||
const preRef = useRef<HTMLElement>(null);
|
|
||||||
const initialScrollDone = useRef(false);
|
|
||||||
const wasAtBottom = useRef(true);
|
|
||||||
|
|
||||||
const handleScroll: () => void = () => {
|
|
||||||
if (preRef.current) {
|
|
||||||
const element = preRef.current;
|
|
||||||
wasAtBottom.current = element.scrollTop + element.clientHeight >= element.scrollHeight - 50;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetScroll: () => void = () => {
|
|
||||||
initialScrollDone.current = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// scroll to bottom on initial load, then only if already near bottom and no active text selection
|
|
||||||
const scrollToBottom: () => void = () => {
|
|
||||||
if (!preRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const element = preRef.current;
|
|
||||||
if (!initialScrollDone.current) {
|
|
||||||
element.scrollTop = element.scrollHeight;
|
|
||||||
initialScrollDone.current = true;
|
|
||||||
} else {
|
|
||||||
const hasSelection = !document.getSelection()?.isCollapsed;
|
|
||||||
if (wasAtBottom.current && !hasSelection) {
|
|
||||||
element.scrollTop = element.scrollHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { preRef, handleScroll, scrollToBottom, resetScroll };
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { AhrimanClient } from "api/client/AhrimanClient";
|
|
||||||
import { ClientContext } from "contexts/ClientContext";
|
|
||||||
import { useContextNotNull } from "hooks/useContextNotNull";
|
|
||||||
|
|
||||||
export function useClient(): AhrimanClient {
|
|
||||||
return useContextNotNull(ClientContext);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { type Context, useContext } from "react";
|
|
||||||
|
|
||||||
export function useContextNotNull<T>(context: Context<T | null>): T {
|
|
||||||
const ctx = useContext(context);
|
|
||||||
if (ctx === null) {
|
|
||||||
throw new Error("must be used within a Provider");
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export function useDebounce<T>(value: T, delay: number): T {
|
|
||||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = setTimeout(() => setDebouncedValue(value), delay);
|
|
||||||
return () => clearTimeout(handler);
|
|
||||||
}, [value, delay]);
|
|
||||||
|
|
||||||
return debouncedValue;
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { type Dispatch, type SetStateAction, useCallback, useState } from "react";
|
|
||||||
|
|
||||||
export function useLocalStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] {
|
|
||||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
||||||
try {
|
|
||||||
const item = window.localStorage.getItem(key);
|
|
||||||
return item !== null ? (JSON.parse(item) as T) : initialValue;
|
|
||||||
} catch {
|
|
||||||
return initialValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const setValue: Dispatch<SetStateAction<T>> = useCallback(
|
|
||||||
value => {
|
|
||||||
setStoredValue(prev => {
|
|
||||||
const nextValue = value instanceof Function ? value(prev) : value;
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(nextValue));
|
|
||||||
return nextValue;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[key],
|
|
||||||
);
|
|
||||||
|
|
||||||
return [storedValue, setValue];
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { NotificationContext, type NotificationContextValue } from "contexts/NotificationContext";
|
|
||||||
import { useContextNotNull } from "hooks/useContextNotNull";
|
|
||||||
|
|
||||||
export function useNotification(): NotificationContextValue {
|
|
||||||
return useContextNotNull(NotificationContext);
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { ApiError } from "api/client/ApiError";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useNotification } from "hooks/useNotification";
|
|
||||||
import { useRepository } from "hooks/useRepository";
|
|
||||||
|
|
||||||
export interface UsePackageActionsResult {
|
|
||||||
handleReload: () => void;
|
|
||||||
handleUpdate: () => Promise<void>;
|
|
||||||
handleRefreshDb: () => Promise<void>;
|
|
||||||
handleRemove: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePackageActions(
|
|
||||||
selectionModel: string[],
|
|
||||||
setSelectionModel: (model: string[]) => void,
|
|
||||||
): UsePackageActionsResult {
|
|
||||||
const client = useClient();
|
|
||||||
const { current } = useRepository();
|
|
||||||
const { showSuccess, showError } = useNotification();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const handleReload: () => void = () => {
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.packages(current) });
|
|
||||||
void queryClient.invalidateQueries({ queryKey: QueryKeys.status(current) });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUpdate: () => Promise<void> = async () => {
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (selectionModel.length === 0) {
|
|
||||||
await client.servicePackageUpdate(current, { packages: [] });
|
|
||||||
showSuccess("Success", "Repository update has been run");
|
|
||||||
} else {
|
|
||||||
await client.servicePackageAdd(current, { packages: selectionModel });
|
|
||||||
showSuccess("Success", `Run update for packages ${selectionModel.join(", ")}`);
|
|
||||||
}
|
|
||||||
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", `Packages update failed: ${detail}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRefreshDb: () => Promise<void> = async () => {
|
|
||||||
if (!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 handleRemove: () => Promise<void> = async () => {
|
|
||||||
if (!current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selectionModel.length === 0) {
|
|
||||||
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 {
|
|
||||||
handleReload,
|
|
||||||
handleUpdate,
|
|
||||||
handleRefreshDb,
|
|
||||||
handleRemove,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import { skipToken, useQuery } from "@tanstack/react-query";
|
|
||||||
import { QueryKeys } from "hooks/QueryKeys";
|
|
||||||
import { useAuth } from "hooks/useAuth";
|
|
||||||
import { useAutoRefresh } from "hooks/useAutoRefresh";
|
|
||||||
import { useClient } from "hooks/useClient";
|
|
||||||
import { useRepository } from "hooks/useRepository";
|
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
|
||||||
import type { BuildStatus } from "models/BuildStatus";
|
|
||||||
import { PackageRow } from "models/PackageRow";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
import { defaultInterval } from "utils";
|
|
||||||
|
|
||||||
export interface UsePackageDataResult {
|
|
||||||
rows: PackageRow[];
|
|
||||||
isLoading: boolean;
|
|
||||||
isAuthorized: boolean;
|
|
||||||
status: BuildStatus | undefined;
|
|
||||||
autoRefresh: ReturnType<typeof useAutoRefresh>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePackageData(autoRefreshIntervals: AutoRefreshInterval[]): UsePackageDataResult {
|
|
||||||
const client = useClient();
|
|
||||||
const { current } = useRepository();
|
|
||||||
const { isAuthorized } = useAuth();
|
|
||||||
|
|
||||||
const autoRefresh = useAutoRefresh("table-autoreload-button", defaultInterval(autoRefreshIntervals));
|
|
||||||
|
|
||||||
const { data: packages = [], isLoading } = useQuery({
|
|
||||||
queryKey: current ? QueryKeys.packages(current) : ["packages"],
|
|
||||||
queryFn: current ? () => client.fetchPackages(current) : skipToken,
|
|
||||||
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: status } = useQuery({
|
|
||||||
queryKey: current ? QueryKeys.status(current) : ["status"],
|
|
||||||
queryFn: current ? () => client.fetchServerStatus(current) : skipToken,
|
|
||||||
refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rows = useMemo(() => packages.map(descriptor => new PackageRow(descriptor)), [packages]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows,
|
|
||||||
isLoading,
|
|
||||||
isAuthorized,
|
|
||||||
status: status?.status.status,
|
|
||||||
autoRefresh,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021-2026 ahriman team.
|
|
||||||
*
|
|
||||||
* This file is part of ahriman
|
|
||||||
* (see https://github.com/arcan1s/ahriman).
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import type { GridFilterModel } from "@mui/x-data-grid";
|
|
||||||
import { usePackageActions } from "hooks/usePackageActions";
|
|
||||||
import { usePackageData } from "hooks/usePackageData";
|
|
||||||
import { useTableState } from "hooks/useTableState";
|
|
||||||
import type { AutoRefreshInterval } from "models/AutoRefreshInterval";
|
|
||||||
import type { BuildStatus } from "models/BuildStatus";
|
|
||||||
import type { PackageRow } from "models/PackageRow";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
export interface UsePackageTableResult {
|
|
||||||
rows: PackageRow[];
|
|
||||||
isLoading: boolean;
|
|
||||||
isAuthorized: boolean;
|
|
||||||
status: BuildStatus | undefined;
|
|
||||||
|
|
||||||
selectionModel: string[];
|
|
||||||
setSelectionModel: (model: string[]) => void;
|
|
||||||
|
|
||||||
dialogOpen: "dashboard" | "add" | "rebuild" | "keyImport" | null;
|
|
||||||
setDialogOpen: (dialog: "dashboard" | "add" | "rebuild" | "keyImport" | null) => void;
|
|
||||||
selectedPackage: string | null;
|
|
||||||
setSelectedPackage: (base: string | null) => void;
|
|
||||||
|
|
||||||
paginationModel: { pageSize: number; page: number };
|
|
||||||
setPaginationModel: (model: { pageSize: number; page: number }) => void;
|
|
||||||
columnVisibility: Record<string, boolean>;
|
|
||||||
setColumnVisibility: (model: Record<string, boolean>) => void;
|
|
||||||
filterModel: GridFilterModel;
|
|
||||||
setFilterModel: (model: GridFilterModel) => void;
|
|
||||||
searchText: string;
|
|
||||||
setSearchText: (text: string) => void;
|
|
||||||
|
|
||||||
autoRefreshInterval: number;
|
|
||||||
onAutoRefreshIntervalChange: (interval: number) => void;
|
|
||||||
|
|
||||||
handleReload: () => void;
|
|
||||||
handleUpdate: () => Promise<void>;
|
|
||||||
handleRefreshDb: () => Promise<void>;
|
|
||||||
handleRemove: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePackageTable(autoRefreshIntervals: AutoRefreshInterval[]): UsePackageTableResult {
|
|
||||||
const { rows, isLoading, isAuthorized, status, autoRefresh } = usePackageData(autoRefreshIntervals);
|
|
||||||
const tableState = useTableState();
|
|
||||||
const actions = usePackageActions(tableState.selectionModel, tableState.setSelectionModel);
|
|
||||||
|
|
||||||
// Pause auto-refresh when dialog is open
|
|
||||||
const isDialogOpen = tableState.dialogOpen !== null || tableState.selectedPackage !== null;
|
|
||||||
const setPaused = autoRefresh.setPaused;
|
|
||||||
useEffect(() => {
|
|
||||||
setPaused(isDialogOpen);
|
|
||||||
}, [isDialogOpen, setPaused]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows,
|
|
||||||
isLoading,
|
|
||||||
isAuthorized,
|
|
||||||
status,
|
|
||||||
|
|
||||||
...tableState,
|
|
||||||
|
|
||||||
autoRefreshInterval: autoRefresh.interval,
|
|
||||||
onAutoRefreshIntervalChange: autoRefresh.setInterval,
|
|
||||||
|
|
||||||
...actions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user