diff --git a/src/backend/main.py b/src/backend/main.py
index 6199b0f..9def165 100644
--- a/src/backend/main.py
+++ b/src/backend/main.py
@@ -1,6 +1,7 @@
import logging
import logging.config
+import asyncio
import atexit
import signal
import sys
@@ -37,7 +38,12 @@ def init_logger():
def cleanup() -> None:
+ logging.info('Cleaning up...')
processes.terminate_subprocesses()
+ loop = asyncio.get_event_loop()
+ for task in asyncio.all_tasks(loop=loop):
+ task.cancel()
+ loop.stop()
# Raises SystemExit
sys.exit()
diff --git a/src/backend/plugins/handler.py b/src/backend/plugins/handler.py
index 892fbe9..1f7beb0 100644
--- a/src/backend/plugins/handler.py
+++ b/src/backend/plugins/handler.py
@@ -6,9 +6,11 @@
from api import expose
from plugins.base_plugin import Plugin
from plugins import priority
+from utils.flag import Flag
_PLUGINS: Dict[str, Plugin] = {}
+_PLUGINS_UPDATED = Flag(True)
def load_all() -> None:
@@ -26,6 +28,7 @@ def load_all() -> None:
def load(name: str) -> Optional[Plugin]:
+ _PLUGINS_UPDATED.set()
try:
source = f'plugins.plugins.{name}.backend.main'
plugin: Plugin = importlib.import_module(source).init()
@@ -81,6 +84,7 @@ def get_plugin_names() -> Tuple[str, ...]:
def get_plugin_statuses() -> List[Dict[str, Any]]:
+ _PLUGINS_UPDATED.reset()
result = []
for name, _ in priority.fetch_plugins(reload=False):
if name in _PLUGINS:
@@ -89,3 +93,11 @@ def get_plugin_statuses() -> List[Dict[str, Any]]:
result.append({'name': name, 'status': False})
return result
+
+
+def is_updated() -> bool:
+ return _PLUGINS_UPDATED.get()
+
+
+def set_update_flag() -> None:
+ _PLUGINS_UPDATED.set()
diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt
index 8e0578a..2f6df7d 100644
--- a/src/backend/requirements.txt
+++ b/src/backend/requirements.txt
@@ -1,2 +1,4 @@
+asyncio
fastapi
-uvicorn[standard]
\ No newline at end of file
+sse_starlette
+uvicorn[standard]
diff --git a/src/backend/server/main.py b/src/backend/server/main.py
index 37efa25..e30d957 100644
--- a/src/backend/server/main.py
+++ b/src/backend/server/main.py
@@ -1,12 +1,15 @@
-from typing import Any, Optional
+from typing import Any, AsyncGenerator
+import asyncio
import datetime
import inspect
+import json
import logging
from fastapi import BackgroundTasks, FastAPI, Request, WebSocket
from fastapi.responses import FileResponse, ORJSONResponse, PlainTextResponse
from fastapi.staticfiles import StaticFiles
+from sse_starlette.sse import EventSourceResponse
from api import expose
from db import users
@@ -217,9 +220,21 @@ async def ws_plugins(websocket: WebSocket, plugin: str, endpoint: str) -> Any:
return response
-@app.get('/plugins/status', response_class=ORJSONResponse)
-async def plugin_status(request: Request) -> ORJSONResponse:
+@app.get('/plugins/status', response_class=EventSourceResponse)
+async def plugin_status(request: Request) -> EventSourceResponse:
if not database.uuid_exists(request.cookies.get('auth_cookie')):
- return ORJSONResponse({'ok': False, 'error': 'Auth failed'})
+ return EventSourceResponse(content=iter(('Authentication failed',)), status_code=401)
+ handler.set_update_flag()
- return ORJSONResponse({'ok': True, 'plugins': handler.get_plugin_statuses()})
+ async def event_generator() -> AsyncGenerator[str, None]:
+ while True:
+ if await request.is_disconnected():
+ break
+
+ if handler.is_updated():
+ yield json.dumps(
+ {'ok': True, 'plugins': handler.get_plugin_statuses()}
+ )
+ await asyncio.sleep(2.0)
+
+ return EventSourceResponse(event_generator(), media_type='text/event-stream')
diff --git a/src/backend/utils/flag.py b/src/backend/utils/flag.py
new file mode 100644
index 0000000..0596950
--- /dev/null
+++ b/src/backend/utils/flag.py
@@ -0,0 +1,15 @@
+class Flag:
+ def __init__(self, default: bool = True) -> None:
+ self._flag = default
+
+ def set(self) -> None:
+ self._flag = True
+
+ def reset(self) -> None:
+ self._flag = False
+
+ def get(self) -> bool:
+ return self._flag
+
+ def __repr__(self) -> str:
+ return f'Flag({self._flag})'
diff --git a/src/frontend/src/lib/components/plugins/PluginsContainer.svelte b/src/frontend/src/lib/components/plugins/PluginsContainer.svelte
index 4486afe..74eb767 100644
--- a/src/frontend/src/lib/components/plugins/PluginsContainer.svelte
+++ b/src/frontend/src/lib/components/plugins/PluginsContainer.svelte
@@ -1,28 +1,36 @@
-
{#if plugins.length == 0}
diff --git a/src/frontend/src/lib/components/shared/api/plugins.type.ts b/src/frontend/src/lib/components/shared/api/plugins.type.ts
index 09d8696..2ad3375 100644
--- a/src/frontend/src/lib/components/shared/api/plugins.type.ts
+++ b/src/frontend/src/lib/components/shared/api/plugins.type.ts
@@ -1,3 +1,8 @@
+export type PluginStatusContainer = {
+ ok: boolean;
+ plugins: Array;
+};
+
export type PluginStatus = {
name: string;
status: boolean;
diff --git a/src/frontend/src/lib/components/shared/api/streams.ts b/src/frontend/src/lib/components/shared/api/streams.ts
new file mode 100644
index 0000000..997c8b4
--- /dev/null
+++ b/src/frontend/src/lib/components/shared/api/streams.ts
@@ -0,0 +1,49 @@
+type CallbackFunction = (data: any) => void;
+let pluginStatusStream = undefined;
+
+const createEventSource = (
+ url: string,
+ defaultData: object
+): {
+ subscribe: (listener: CallbackFunction) => () => boolean;
+ fetch: () => object;
+ close: () => void;
+} => {
+ const eventSource = new EventSource(url);
+ const listeners: Set = new Set();
+ let cachedData: object = defaultData;
+
+ eventSource.onmessage = (event: MessageEvent) => {
+ cachedData = JSON.parse(event.data);
+ listeners.forEach((listener) => listener(cachedData));
+ };
+
+ eventSource.onerror = () => {
+ const errorData = defaultData;
+ listeners.forEach((listener) => listener(errorData));
+ };
+
+ return {
+ subscribe: (listener) => {
+ listeners.add(listener);
+ return () => listeners.delete(listener);
+ },
+ close: () => {
+ eventSource.close();
+ },
+ fetch: () => {
+ return cachedData;
+ },
+ };
+};
+
+export function initEventSources() {
+ if (pluginStatusStream == undefined) {
+ pluginStatusStream = createEventSource("/plugins/status", {
+ ok: false,
+ plugins: [],
+ });
+ }
+}
+
+export { pluginStatusStream };
diff --git a/src/frontend/src/lib/components/shared/statusbar/Plugins.svelte b/src/frontend/src/lib/components/shared/statusbar/Plugins.svelte
index 55700e4..663ca39 100644
--- a/src/frontend/src/lib/components/shared/statusbar/Plugins.svelte
+++ b/src/frontend/src/lib/components/shared/statusbar/Plugins.svelte
@@ -1,23 +1,34 @@