-
Notifications
You must be signed in to change notification settings - Fork 0
feat: define initial ui and project structure #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from textual.app import App, ComposeResult | ||
| from textual.screen import Screen | ||
| from textual.widgets import Placeholder | ||
|
|
||
|
|
||
| class Header(Placeholder): | ||
| pass | ||
|
|
||
|
|
||
| class Footer(Placeholder): | ||
| pass | ||
|
|
||
|
|
||
| class TweetScreen(Screen): | ||
| def compose(self) -> ComposeResult: | ||
| yield Header(id="Header") | ||
| yield Footer(id="Footer") | ||
|
|
||
|
|
||
| class LayoutApp(App): | ||
| def on_mount(self) -> None: | ||
| self.push_screen(TweetScreen()) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app = LayoutApp() | ||
| app.run() |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,81 @@ | ||||||||||||||||||||
| """Main application class""" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import logging | ||||||||||||||||||||
| from threading import active_count | ||||||||||||||||||||
|
|
||||||||||||||||||||
| from textual.app import App | ||||||||||||||||||||
| from textual import on | ||||||||||||||||||||
|
|
||||||||||||||||||||
| from .screens.main_screen import MainScreen | ||||||||||||||||||||
| from .services.docker_service import DockerService | ||||||||||||||||||||
| from .utils.colors import ColorManager | ||||||||||||||||||||
| from .utils.config import config | ||||||||||||||||||||
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
Comment on lines
+12
to
+13
|
||||||||||||||||||||
| from .utils.config import config | |
| try: | |
| from .utils.config import config | |
| except ImportError: | |
| from types import SimpleNamespace | |
| config = SimpleNamespace( | |
| default_colors=["red", "green", "blue", "yellow", "magenta", "cyan"], | |
| update_interval=1.0 | |
| ) |
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempting to access config.default_colors, but config is not defined in utils/config.py. This will cause an AttributeError at runtime.
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempting to access config.update_interval, but config is not defined in utils/config.py. This will cause an AttributeError at runtime.
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling methods on docker_service, but DockerService class is not defined. These method calls will fail at runtime.
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MainScreen class doesn't have a 'refresh_metrics' method. Based on the structure, this should likely call 'self.main_screen.metrics.refresh_metrics(container_count, monitoring)'.
| self.main_screen.refresh_metrics(container_count, monitoring) | |
| self.main_screen.metrics.refresh_metrics(container_count, monitoring) |
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling refresh_metrics on main_screen, but MainScreen class has no such method defined. This will result in an AttributeError.
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling get_log_messages on docker_service, but this method is not defined in the DockerService class. This will fail at runtime.
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling start_monitoring on docker_service, but this method is not defined. This will fail at runtime.
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling stop_monitoring on docker_service, but this method is not defined. This will fail at runtime.
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method calls 'self.handle_refresh_containers()' which is not defined in this class. The method should either be implemented or this should post a RefreshContainers message.
| def handle_refresh_containers(self) -> None: | |
| """Refresh the list of containers and update the UI.""" | |
| containers = self.docker_service.get_containers() | |
| self.main_screen.refresh_container_list(containers) |
Copilot
AI
Oct 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method handle_refresh_containers is called but not defined in the DockerLogsApp class. This will result in an AttributeError.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| """ | ||
| Main app | ||
| """ | ||
|
|
||
| import docker | ||
| import random | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,5 @@ | ||||||||||||||
| """Screens package for DockerLogs app.""" | ||||||||||||||
|
|
||||||||||||||
| from .main_screen import MainScreen | ||||||||||||||
|
|
||||||||||||||
| __all__ = ["MainScreen"] | ||||||||||||||
|
Comment on lines
+3
to
+5
|
||||||||||||||
| from .main_screen import MainScreen | |
| __all__ = ["MainScreen"] | |
| __all__ = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| """ Main log viewer screen """ | ||
|
|
||
| from textual.app import Screen | ||
| from textual.containers import Vertical, Container | ||
| from textual import on | ||
| from textual.app import ComposeResult | ||
|
|
||
| from ..widgets.header import Header | ||
| from ..widgets.footer import Footer | ||
| from ..widgets.log_viewer_widget import LogViewerWidget | ||
| from ..widgets.controls_widget import ControlsWidget | ||
| from ..widgets.metrics_widget import MetricsWidget | ||
|
|
||
| class MainScreen(Screen): | ||
| """Main screen with log viewer, controls and metrics.""" | ||
|
|
||
| CSS = """ | ||
| #metrics-container { | ||
| height: 3; | ||
| border: solid $primary; | ||
| } | ||
|
|
||
| #controls-container { | ||
| height: 3; | ||
| border: solid $secondary; | ||
| } | ||
|
|
||
| #logs-container { | ||
| border: solid $accent; | ||
| } | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| super().__init__() | ||
| self.metrics = MetricsWidget() | ||
| self.controls = ControlsWidget() | ||
| self.log_viewer = LogViewerWidget() | ||
|
|
||
| def compose(self) -> ComposeResult: | ||
| yield Header() | ||
| with Container(id="metrics-container"): | ||
| yield self.metrics | ||
| with Container(id="controls-container"): | ||
| yield self.controls | ||
| with Container(id="logs-container"): | ||
| yield self.log_viewer | ||
| yield Footer() | ||
|
|
||
| @on(ControlsWidget.StartMonitoring) | ||
| def handle_start_monitoring(self) -> None: | ||
| self.post_message(self.StartMonitoring()) | ||
|
|
||
| @on(ControlsWidget.StopMonitoring) | ||
| def handle_stop_monitoring(self) -> None: | ||
| self.post_message(self.StopMonitoring()) | ||
|
|
||
| @on(ControlsWidget.ClearLogs) | ||
| def handle_clear_logs(self) -> None: | ||
| self.post_message(self.ClearLogs()) | ||
|
|
||
| @on(ControlsWidget.RefreshContainers) | ||
| def handle_refresh_containers(self) -> None: | ||
| """Handle refresh containers message.""" | ||
| self.post_message(self.RefreshContainers()) | ||
|
|
||
| class StartMonitoring: | ||
| pass | ||
|
|
||
| class StopMonitoring: | ||
| pass | ||
|
|
||
| class ClearLogs: | ||
| pass | ||
|
|
||
| class RefreshContainers: | ||
| pass | ||
|
Comment on lines
+66
to
+76
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """ Docker services """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| """ Color utils """ | ||
|
|
||
| import random | ||
| from typing import Dict, List | ||
|
|
||
| class ColorManager: | ||
| """ Manages color assignments for Docker containers. """ | ||
|
|
||
| def __init__(self, colors: List[str]): | ||
| self.colors = colors | ||
| self.container_colors: Dict[str, str] = {} | ||
|
|
||
| def random_color(self) -> str: | ||
| """Generate a random hex color.""" | ||
| return f"#{random.randint(0, 0xFFFFFF):06x}" | ||
|
|
||
| def assign_colors(self, container_names: List[str]) -> None: | ||
| """Assign random colors to a list of container names.""" | ||
| self.container_colors = {name: self.random_color() for name in container_names} | ||
|
|
||
| def get_color(self, container_name: str) -> str: | ||
| """Get the color assigned to a specific container name.""" | ||
| return self.container_colors.get(container_name, "#FFFFFF") # Default to white if not found |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """ | ||
| Config utils | ||
| """ |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||
| """Widgets package for Docker Logs app.""" | ||||||
|
|
||||||
| from .header import Header | ||||||
| from .footer import Footer | ||||||
| from .metrics_widget import MetricsWidget | ||||||
| from .controls_widget import ControlsWidget | ||||||
| from .log_viewer_widget import LogViewerWidget | ||||||
|
|
||||||
| __all__ = [ | ||||||
| "Header", | ||||||
| "Footer", | ||||||
|
||||||
| "Footer", | |
| "Footer", |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| """Controls widget for Docker Logs app.""" | ||
|
|
||
| from textual.containers import Horizontal | ||
| from textual.widgets import Button | ||
| from textual import on | ||
| from textual.message import Message | ||
|
|
||
| class ControlsWidget(Horizontal): | ||
| """Widget with control buttons.""" | ||
|
|
||
| def __init__(self): | ||
| super().__init__() | ||
|
|
||
| def compose(self): | ||
| yield Button("Start", id="start", variant="success") | ||
| yield Button("Stop", id="stop", variant="error") | ||
| yield Button("Refresh", id="refresh", variant="primary") | ||
| yield Button("Clear", id="clear", variant="warning") | ||
|
|
||
| @on(Button.Pressed, "#start") | ||
| def start_monitoring(self) -> None: | ||
| self.post_message(self.StartMonitoring()) | ||
|
|
||
| @on(Button.Pressed, "#stop") | ||
| def stop_monitoring(self) -> None: | ||
| self.post_message(self.StopMonitoring()) | ||
|
|
||
| @on(Button.Pressed, "#refresh") | ||
| def refresh_logs(self) -> None: | ||
| self.post_message(self.RefreshContainers()) | ||
|
|
||
| @on(Button.Pressed, "#clear") | ||
| def clear_logs(self) -> None: | ||
| self.post_message(self.ClearLogs()) | ||
|
|
||
| class StartMonitoring(Message): | ||
| pass | ||
|
|
||
| class StopMonitoring(Message): | ||
| pass | ||
|
|
||
| class RefreshContainers(Message): | ||
| pass | ||
|
|
||
| class ClearLogs(Message): | ||
| pass |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| """Footer widget for Docker Logs app.""" | ||
|
|
||
| from textual.widgets import Footer as TextualFooter | ||
|
|
||
| class Footer(TextualFooter): | ||
| """Custom footer with keybindings.""" | ||
|
|
||
| def __init__(self): | ||
| super().__init__() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| """Header Widget for DockerLogs app.""" | ||
|
|
||
| from textual.widgets import Header as TextualHeader | ||
| from textual.reactive import reactive | ||
|
|
||
| class Header(TextualHeader): | ||
| """Custom header with app title and status.""" | ||
|
|
||
| status = reactive("Stopped") | ||
|
|
||
| def __init__(self): | ||
| super().__init__() | ||
| self.title = "Docker Log Monitor" | ||
|
|
||
| def watch_status(self, status: str) -> None: | ||
| self.sub_title = f"Status: {status}" |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||
| """Log viewer widget.""" | ||||||
|
|
||||||
| from textual.widget import RichLog | ||||||
|
||||||
| from textual.widget import RichLog | |
| from textual.widgets import RichLog |
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after colon in the type annotation for 'color' parameter. Should be 'color: str' to conform to PEP 8 style guidelines.
| def write_log(self, container_name: str, message: str, color:str) -> None: | |
| def write_log(self, container_name: str, message: str, color: str) -> None: |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||
| """Metrics Widget for Docker Logs app.""" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from textual.widget import static | ||||||||||||||||||||||||||||||||||||||||||
| from textual.reactive import reactive | ||||||||||||||||||||||||||||||||||||||||||
| from threading import active_count | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| class MetricsWidget(static): | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+7
|
||||||||||||||||||||||||||||||||||||||||||
| from textual.widget import static | |
| from textual.reactive import reactive | |
| from threading import active_count | |
| class MetricsWidget(static): | |
| from textual.widgets import Static | |
| from textual.reactive import reactive | |
| from threading import active_count | |
| class MetricsWidget(Static): |
Copilot
AI
Oct 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class inherits from 'static' (lowercase), but should inherit from 'Static' (capitalized). This is a consequence of the incorrect import on line 3 and will cause a NameError.
| from textual.widget import static | |
| from textual.reactive import reactive | |
| from threading import active_count | |
| class MetricsWidget(static): | |
| from textual.widget import Static | |
| from textual.reactive import reactive | |
| from threading import active_count | |
| class MetricsWidget(Static): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempting to import
DockerServicefrom a file that only contains a docstring comment. The docker_service.py file needs to implement the DockerService class before this import will work.