Skip to content

Commit

Permalink
feat: initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
pommee committed Jun 1, 2024
0 parents commit 2a0e428
Show file tree
Hide file tree
Showing 10 changed files with 1,723 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.pyc
test.py
__pycache__
.vscode
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dev:
textual run --dev application.main:UI
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

![image](./resources/image.png)

# Pocker

I found Docker Desktop to be bloated, heavy, slow, resource-hogging and contains features that I rarely ever use.
Not to say that it's bad; just not for me.

# Introduction

Pocker is a lightweight CLI tool to help with docker related tasks. For example,

* View containers/images.
* See logs and attributes.
* Manage status of containers.

The tool is heavily based on [docker-py](https://docker-py.readthedocs.io/en/stable/index.html) and [textual](https://github.com/textualize/textual/).
A big thanks goes over to the creator and contributors of textual as it makes for a very sleek and easy interface.

## Prerequisite

> [!NOTE]
> Pipx must be installed.
```shell
pip install pipx
```

## Installation

### Install from remote repository

This will install latest version available from main.
```shell
pipx install git+https://github.com/pommee/Pocker@main
```

### Install from local repository
```bash
git clone https://github.com/pommee/Pocker.git
cd Pocker
pipx install .
```

## Usage

Start by running

```shell
pocker
```

Keybinds can be seen in the footer when started or by pressing `?`.
73 changes: 73 additions & 0 deletions application/docker_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import logging
from threading import Event
import time
import docker
from docker.models.containers import Container, ContainerCollection
from textual.widgets import RichLog
from textual.logging import TextualHandler

logging.basicConfig(
level="INFO",
handlers=[TextualHandler()],
)


class DockerManager:
def __init__(self) -> None:
self.client = docker.from_env()
self.containers: ContainerCollection = self.client.containers.list(all=True)
self.images = self.client.images.list(all=True)
self.selected_container = self.containers[0].name

def container(self, container_name: str) -> Container:
return self.client.containers.get(container_name)

def curr_container(self):
return self.container(self.selected_container)

def logs(self):
return (
self.container(self.selected_container)
.logs(follow=False)
.decode("utf-8")
.strip()
)

def attributes(self):
return self.container(self.selected_container).attrs

def status(self, container_name: str):
is_running = self.container(container_name).attrs.get("State").get("Running")

status = "[U]"
if is_running is True:
status = "running"
if not is_running:
status = "down"
return status

def live_container_logs(self, logs: RichLog, stop_event: Event):
logs.clear()

while not stop_event.is_set():
if len(logs.lines) == 0:
last_fetch = time.time()
# If logs are empty, write initial logs
logs.write(
self.container(self.selected_container)
.logs()
.decode("utf-8")
.strip()
)

new_logs = (
self.container(self.selected_container)
.logs(since=last_fetch)
.decode("utf-8")
)

if len(new_logs) > 0:
last_fetch = time.time()
logs.write(new_logs.rstrip())

time.sleep(0.2)
77 changes: 77 additions & 0 deletions application/help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import webbrowser

from rich.text import Text

from textual import on
from textual.app import ComposeResult
from textual.containers import Center, VerticalScroll
from textual.screen import ModalScreen
from textual.widgets import Static, Markdown, Footer

HELP_MD = """
Pocker is a tool for the terminal to do Docker related tasks.
Repository: [https://github.com/pommee/Pocker](https://github.com/pommee/Pocker)
Author: [pommee](https://github.com/pommee)
---
- `esc` closes this window.
### Navigation
- `q` to quit pocker.
- `?` shows this help modal.
- `l` updates the content window with logs of selected container.
- `a` same as above but displays attributes.
- `f` display content window in full-size.
- `w` will wrap logs/attributes to avoid horizontal scrolling.
### Other keys
- `ctrl+f` or `/` Show find dialog.
"""

TITLE = rf"""
_____ _____ _______ _ _ _______ ______
|_____] | | | |____/ |______ |_____/
| |_____| |_____ | \_ |______ | \_
"""


def get_title() -> Text:
lines = TITLE.splitlines(keepends=True)
return Text.assemble(
*zip(
lines,
[
"#6076FF",
"#0087FF",
"#00888B",
"#008157",
],
)
)


class HelpScreen(ModalScreen):
BINDINGS = [
("escape", "dismiss"),
]

def compose(self) -> ComposeResult:
yield Footer()
with VerticalScroll() as vertical_scroll:
with Center():
yield Static(get_title(), classes="title")
yield Markdown(HELP_MD)
vertical_scroll.border_title = "Help"

@on(Markdown.LinkClicked)
def on_markdown_link_clicked(self, event: Markdown.LinkClicked) -> None:
self.action_go(event.href)

def action_go(self, href: str) -> None:
webbrowser.open(href)
Loading

0 comments on commit 2a0e428

Please sign in to comment.