Skip to content

Commit

Permalink
fix: make container state more reliable and reflect in ui
Browse files Browse the repository at this point in the history
  • Loading branch information
pommee committed Aug 13, 2024
1 parent 14a5d8a commit 776f2e9
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 27 deletions.
6 changes: 5 additions & 1 deletion application/docker_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ def live_container_logs(self, logs: LogLines, stop_event: Event):
last_fetch = time.time()

while not stop_event.is_set():
new_logs = self.selected_container.logs(since=last_fetch)
try:
new_logs = self.selected_container.logs(since=last_fetch)
except Exception:
# Container might have been removed.
stop_event.set()

if new_logs:
last_fetch = time.time()
Expand Down
11 changes: 7 additions & 4 deletions application/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import logging
import subprocess
from threading import Thread
Expand Down Expand Up @@ -214,11 +215,13 @@ def _run_threads(self):
statistics_thread = Thread(
target=self.content_window.live_statistics_task, daemon=True
)
status_events_thread = Thread(
target=self.query_one(PockerContainers).live_status_events_task, daemon=True
)
loop = asyncio.get_event_loop()
Thread(
target=self.query_one(PockerContainers).live_status_events_task,
args=(loop,),
).start()

statistics_thread.start()
status_events_thread.start()

def set_header_statuses(self):
logs = self.query_one("#logs", LogLines)
Expand Down
81 changes: 59 additions & 22 deletions application/widget/containers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import asyncio

from docker.models.containers import Container
from textual.app import ComposeResult
from textual.widget import Widget
Expand Down Expand Up @@ -60,28 +62,63 @@ async def on_mount(self) -> None:
reverse=True,
)

def live_status_events_task(self):
async def _validate_if_any_container_selected(self):
for child in self.list_view.walk_children():
if child.has_class("selected"):
return

first_child = self.list_view.query()[0]
first_child.add_class("selected")

def live_status_events_task(self, loop: asyncio.AbstractEventLoop):
event: dict[str, str]
for event in self.docker_manager.client.events(decode=True):
if event["Type"] == "container" and event["status"] in [
"start",
"stop",
"die",
]:
if event["Type"] == "container":
container_name = event.get("Actor").get("Attributes").get("name")
container_list_item: ListItem = self.list_view.get_child_by_id(
container_name
)
status = ""
was_selected: bool = container_list_item.has_class("selected")

if event["status"] == "start":
status = "running"
elif event["status"] == "stop":
status = "stopped"
else:
status = "down"

container_list_item.set_classes(status)
if was_selected:
container_list_item.add_class("selected")
match event["status"]:
case "start":
asyncio.run_coroutine_threadsafe(
self._container_started(container_name), loop
)
case "stop":
asyncio.run_coroutine_threadsafe(
self._container_status_changed(container_name, "stopping"),
loop,
)
case "die":
asyncio.run_coroutine_threadsafe(
self._container_status_changed(container_name, "down"), loop
)
case "destroy":
asyncio.run_coroutine_threadsafe(
self._container_destroyed(container_name), loop
)

async def _container_started(self, container_name: str):
for child in self.list_view.walk_children():
if child.id == container_name:
child.remove_class("down")
child.set_class(True, "running")
await self._validate_if_any_container_selected()
return

await self.list_view.append(
ListItem(Label(container_name), id=container_name, classes="running")
)
await self._validate_if_any_container_selected()

async def _container_status_changed(self, container_name: str, status: str):
container_list_item: ListItem = self.list_view.get_child_by_id(container_name)
was_selected: bool = container_list_item.has_class("selected")

container_list_item.set_classes(status)
if was_selected:
container_list_item.add_class("selected")

async def _container_destroyed(self, container_name: str):
for x in self.list_view.walk_children():
if x.id == container_name:
self.list_view.remove_children(f"#{container_name}")
break

await self._validate_if_any_container_selected()

0 comments on commit 776f2e9

Please sign in to comment.