Skip to content

Commit

Permalink
Render docker logs
Browse files Browse the repository at this point in the history
  • Loading branch information
jeckel committed Oct 23, 2024
1 parent d561bc3 commit 1bb125a
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 156 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"dependency-injector>=4.42.0",
"docker>=7.1.0",
"pydantic-settings>=2.5.2",
"pydantic>=2.9.2",
"textual>=0.83.0",
"typer>=0.12.5",
Expand Down
6 changes: 3 additions & 3 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from rich import print

from composer_utils import composer_updatable
from service_locator import Container
from models import Project
from presentation import MainApp
from settings import settings

app = typer.Typer()

Expand Down Expand Up @@ -33,12 +33,12 @@ def debug(project_path: str) -> None:
print("Validation error:", e)
exit(1)

# print(project.composer_json)
print(composer_updatable(project))


def main() -> None:
app(prog_name=settings.__app_name__)
container = Container()
app()


if __name__ == "__main__":
Expand Down
32 changes: 32 additions & 0 deletions src/presentation/component/terminal_modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,38 @@


class TerminalModal(ModalScreen[bool]):
CSS = """
TerminalModal {
align: center middle;
Container {
margin: 4 8;
Horizontal > Label {
padding: 0 1;
}
RichLog {
padding: 1 1;
}
}
.modal_title {
height: 1;
background: $primary-background;
width: 100%;
padding: 0 1;
}
.button_container {
height: 3;
background: $primary-background;
width: 100%;
align: center middle;
Button {
margin: 0 1;
}
}
}
"""

def __init__(
self,
command: str | list[str],
Expand Down
54 changes: 12 additions & 42 deletions src/presentation/docker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,26 @@
from threading import Thread

from rich.text import Text
from textual.app import ComposeResult
from textual.containers import Container
from textual.widgets import Label, TabPane, Button, RichLog
import docker
from textual.containers import Horizontal
from textual.widgets import TabPane, Button, Select

from models import Project
import subprocess
from textual.widgets import DataTable
from textual import work, on
from textual import on

from presentation.docker.container_table import ContainerTable
from presentation.docker.container_log_widget import ContainerLogWidget
from presentation.docker.container_select import ContainerSelect


class DockerPan(TabPane):
def __init__(self, project: Project, **kwargs):
self.project = project
super().__init__(**kwargs, title="Docker", id="docker-pan")
self.data_table = ContainerTable(title="Containers", project=self.project, classes="table_container")
self.docker_logs = RichLog(
id="docker_log",
highlight=True,
markup=True,
classes="modal_container",
)
self.docker_logs = ContainerLogWidget()

def compose(self) -> ComposeResult:
with Container(id="project_docker"):
with Horizontal(id="docker_container_select_container"):
yield ContainerSelect()
yield Button.success(" Refresh", id="docker_refresh")
yield self.data_table
yield self.docker_logs

@on(Button.Pressed, "#docker_refresh")
def action_refresh(self):
self.data_table.refresh_containers()

@on(DataTable.RowSelected)
def on_docker_container_selected(self, event: DataTable.RowSelected) -> None:
container_name = event.row_key.value
self.stream_logs(container_name)

@work(exclusive=True, thread=True)
def stream_logs(self, container_id: str):
# Create a Docker client
self.docker_logs.clear()
self.docker_logs.write(f"Logs for container {container_id}:\n")
client = docker.from_env()
container = client.containers.get(container_id)
yield self.docker_logs

# Stream the logs from the container
for log in container.logs(stream=True, follow=True):
# Convert bytes to string and update the logs widget
log_message = log.decode('utf-8').strip()
self.docker_logs.write(log_message)
@on(Select.Changed)
def select_changed(self, event: Select.Changed) -> None:
self.docker_logs.stream_logs(event.value)
22 changes: 22 additions & 0 deletions src/presentation/docker/container_log_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from textual.widgets import RichLog
from textual import work

from service_locator import Container


class ContainerLogWidget(RichLog):
def __init__(self, **kargs):
super().__init__(
id="docker_log",
highlight=True,
markup=True,
**kargs)

@work(exclusive=True, thread=True)
def stream_logs(self, container_id: str):
self.clear()
self.border_title = f"Logs for container {container_id}"

for log in Container.docker_client().get_container_logs(container_id):
# Convert bytes to string and update the logs widget
self.write(log.decode('utf-8').strip())
11 changes: 11 additions & 0 deletions src/presentation/docker/container_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from textual.widgets import Select

from service_locator import Container


class ContainerSelect(Select):
def __init__(self, **kargs):
super().__init__(
((docker_container.name, docker_container.id) for docker_container in Container.docker_client().get_running_containers()),
**kargs
)
34 changes: 0 additions & 34 deletions src/presentation/docker/container_table.py

This file was deleted.

19 changes: 19 additions & 0 deletions src/service_locator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from dependency_injector import containers, providers

from services import DockerClient


class Container(containers.DeclarativeContainer):
config = providers.Configuration()
docker_client = providers.Singleton(DockerClient)

# api_client = providers.Singleton(
# ApiClient,
# api_key=config.api_key,
# timeout=config.timeout,
# )
#
# service = providers.Factory(
# Service,
# api_client=api_client,
# )
1 change: 1 addition & 0 deletions src/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .docker import DockerClient
14 changes: 14 additions & 0 deletions src/services/docker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import docker

class DockerClient:
def __init__(self):
self.client = docker.from_env()

def get_running_containers(self) -> list:
"""Fetches a list of running containers."""
return self.client.containers.list()

def get_container_logs(self, container_id):
"""Fetches logs from a specific container."""
container = self.client.containers.get(container_id)
return container.logs(stream=True, follow=True)
19 changes: 0 additions & 19 deletions src/settings.py

This file was deleted.

44 changes: 11 additions & 33 deletions src/tcss/layout.tcss
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
#project_container {
layout: grid;
grid-size: 2 1;
}

#project_summary {
layout: grid;
grid-size: 2 2;
Expand Down Expand Up @@ -39,36 +34,19 @@ ComposerScriptButton {
width: auto;
}

TabPane {
padding: 0 0;
margin: 0 0;
}

# #####
# Terminal Modal

TerminalModal {
align: center middle;

#modal_container {
margin: 5 10;
}

.modal_title {
height: 1;
background: $primary-background;
width: 100%;
DockerPan {
Horizontal {
height: 3;
padding: 0 1;
}
.button_container {
height: 3;
background: $primary-background;
width: 100%;
align: center middle;
ContainerLogWidget {
height: auto;
border-title-color: $accent;
border: $primary-background round;
}
}


TerminalModal > Container > Horizontal > Label {
padding: 0 1;
width: 97;
}
TerminalModal > Container> RichLog {
padding: 1 1;
}
Loading

0 comments on commit 1bb125a

Please sign in to comment.