diff --git a/.github/workflows/setup.sh b/.github/workflows/setup.sh
index e61926cd..48dd2118 100755
--- a/.github/workflows/setup.sh
+++ b/.github/workflows/setup.sh
@@ -16,7 +16,7 @@ pacman -S --noconfirm --asdeps base-devel python-build python-flit python-instal
# optional dependencies
if [[ -z $MINIMAL_INSTALL ]]; then
# web server
- pacman -S --noconfirm python-aioauth-client python-aiohttp python-aiohttp-apispec-git python-aiohttp-cors python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-cryptography python-jinja
+ pacman -S --noconfirm python-aioauth-client python-aiohttp python-aiohttp-apispec-git python-aiohttp-cors python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-aiohttp-sse-git python-cryptography python-jinja
# additional features
pacman -S --noconfirm gnupg ipython python-boto3 python-cerberus python-matplotlib rsync
fi
diff --git a/docker/Dockerfile b/docker/Dockerfile
index bdbe5ba6..0bac9e1a 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -37,6 +37,7 @@ RUN pacman -S --noconfirm --asdeps \
python-build \
python-flit \
python-installer \
+ python-setuptools \
python-tox \
python-wheel
RUN pacman -S --noconfirm --asdeps \
@@ -58,6 +59,7 @@ RUN runuser -u build -- install-aur-package \
python-aiohttp-jinja2 \
python-aiohttp-session \
python-aiohttp-security \
+ python-aiohttp-sse-git \
python-requests-unixsocket2
# install ahriman
diff --git a/docs/ahriman.core.status.rst b/docs/ahriman.core.status.rst
index b0096df3..3e57418c 100644
--- a/docs/ahriman.core.status.rst
+++ b/docs/ahriman.core.status.rst
@@ -12,6 +12,14 @@ ahriman.core.status.client module
:no-undoc-members:
:show-inheritance:
+ahriman.core.status.event\_bus module
+-------------------------------------
+
+.. automodule:: ahriman.core.status.event_bus
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
+
ahriman.core.status.local\_client module
----------------------------------------
diff --git a/docs/ahriman.web.schemas.rst b/docs/ahriman.web.schemas.rst
index 7a697397..4c2f4399 100644
--- a/docs/ahriman.web.schemas.rst
+++ b/docs/ahriman.web.schemas.rst
@@ -92,6 +92,14 @@ ahriman.web.schemas.error\_schema module
:no-undoc-members:
:show-inheritance:
+ahriman.web.schemas.event\_bus\_filter\_schema module
+-----------------------------------------------------
+
+.. automodule:: ahriman.web.schemas.event_bus_filter_schema
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
+
ahriman.web.schemas.event\_schema module
----------------------------------------
@@ -356,6 +364,14 @@ ahriman.web.schemas.search\_schema module
:no-undoc-members:
:show-inheritance:
+ahriman.web.schemas.sse\_schema module
+--------------------------------------
+
+.. automodule:: ahriman.web.schemas.sse_schema
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
+
ahriman.web.schemas.status\_schema module
-----------------------------------------
diff --git a/docs/ahriman.web.views.v1.auditlog.rst b/docs/ahriman.web.views.v1.auditlog.rst
index 05cc602f..1b9105f4 100644
--- a/docs/ahriman.web.views.v1.auditlog.rst
+++ b/docs/ahriman.web.views.v1.auditlog.rst
@@ -4,6 +4,14 @@ ahriman.web.views.v1.auditlog package
Submodules
----------
+ahriman.web.views.v1.auditlog.event\_bus module
+-----------------------------------------------
+
+.. automodule:: ahriman.web.views.v1.auditlog.event_bus
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
+
ahriman.web.views.v1.auditlog.events module
-------------------------------------------
diff --git a/docs/configuration.rst b/docs/configuration.rst
index e346277c..e1248fc6 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -188,6 +188,7 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
* ``host`` - host to bind, string, optional.
* ``index_url`` - full URL of the repository index page, string, optional.
* ``max_body_size`` - max body size in bytes to be validated for archive upload, integer, optional. If not set, validation will be disabled.
+* ``max_queue_size`` - max queue size for server sent event streams, integer, optional, default ``0``. If set to ``0``, queue is unlimited.
* ``port`` - port to bind, integer, optional.
* ``service_only`` - disable status routes (including logs), boolean, optional, default ``no``.
* ``static_path`` - path to directory with static files, string, required.
@@ -195,7 +196,7 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
* ``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_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. If set to ``0``, wait infinitely.
``archive`` group
-----------------
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 54fe9240..0dc651b3 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -7,10 +7,13 @@ aiohttp==3.11.18
# ahriman (pyproject.toml)
# aiohttp-cors
# aiohttp-jinja2
+ # aiohttp-sse
aiohttp-cors==0.8.1
# via ahriman (pyproject.toml)
aiohttp-jinja2==1.6
# via ahriman (pyproject.toml)
+aiohttp-sse==2.2.0
+ # via ahriman (pyproject.toml)
aiosignal==1.3.2
# via aiohttp
alabaster==1.0.0
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 431485a7..42efabbe 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -21,6 +21,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import AppLayout from "components/layout/AppLayout";
import { AuthProvider } from "contexts/AuthProvider";
import { ClientProvider } from "contexts/ClientProvider";
+import { EventStreamProvider } from "contexts/EventStreamProvider";
import { NotificationProvider } from "contexts/NotificationProvider";
import { RepositoryProvider } from "contexts/RepositoryProvider";
import { ThemeProvider } from "contexts/ThemeProvider";
@@ -42,7 +43,9 @@ export default function App(): React.JSX.Element {
-
+
+
+
diff --git a/frontend/src/components/common/AutoRefreshControl.tsx b/frontend/src/components/common/AutoRefreshControl.tsx
deleted file mode 100644
index 479281c4..00000000
--- a/frontend/src/components/common/AutoRefreshControl.tsx
+++ /dev/null
@@ -1,91 +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 .
- */
-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 {
- currentInterval: number;
- intervals: AutoRefreshInterval[];
- onIntervalChange: (interval: number) => void;
-}
-
-export default function AutoRefreshControl({
- currentInterval,
- intervals,
- onIntervalChange,
-}: AutoRefreshControlProps): React.JSX.Element | null {
- const [anchorEl, setAnchorEl] = useState(null);
-
- if (intervals.length === 0) {
- return null;
- }
-
- const enabled = currentInterval > 0;
-
- return <>
-
- setAnchorEl(event.currentTarget)}
- size="small"
- >
- {enabled ? : }
-
-
-
- >;
-}
diff --git a/frontend/src/components/dialogs/PackageInfoDialog.tsx b/frontend/src/components/dialogs/PackageInfoDialog.tsx
index 8066b260..0a608596 100644
--- a/frontend/src/components/dialogs/PackageInfoDialog.tsx
+++ b/frontend/src/components/dialogs/PackageInfoDialog.tsx
@@ -32,27 +32,22 @@ import PkgbuildTab from "components/package/PkgbuildTab";
import { type TabKey, tabs } from "components/package/TabKey";
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 {
- autoRefreshIntervals: AutoRefreshInterval[];
onClose: () => void;
open: boolean;
packageBase: string | null;
}
export default function PackageInfoDialog({
- autoRefreshIntervals,
onClose,
open,
packageBase,
@@ -77,14 +72,11 @@ export default function PackageInfoDialog({
onClose();
};
- const autoRefresh = useAutoRefresh("package-info-autoreload-button", defaultInterval(autoRefreshIntervals));
-
const { data: packageData } = useQuery({
enabled: open,
queryFn: localPackageBase && currentRepository ?
() => client.fetch.fetchPackage(localPackageBase, currentRepository) : skipToken,
queryKey: localPackageBase && currentRepository ? QueryKeys.package(localPackageBase, currentRepository) : ["packages"],
- refetchInterval: autoRefresh.interval > 0 ? autoRefresh.interval : false,
});
const { data: dependencies } = useQuery({
@@ -182,7 +174,6 @@ export default function PackageInfoDialog({
{activeTab === "logs" && localPackageBase && currentRepository &&
}
@@ -207,11 +198,8 @@ export default function PackageInfoDialog({
void handleHoldToggle()}
onRefreshDatabaseChange={setRefreshDatabase}
onRemove={() => void handleRemove()}
diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx
index feb20241..dde95ff3 100644
--- a/frontend/src/components/layout/AppLayout.tsx
+++ b/frontend/src/components/layout/AppLayout.tsx
@@ -69,7 +69,7 @@ export default function AppLayout(): React.JSX.Element {
-
+