axio-tools-docker 0.2.3__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- axio_tools_docker-0.3.2/.gitignore +12 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/Makefile +1 -1
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/PKG-INFO +1 -1
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/pyproject.toml +1 -1
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/plugin.py +1 -1
- axio_tools_docker-0.3.2/src/axio_tools_docker/settings.py +5 -0
- axio_tools_docker-0.3.2/src/axio_tools_docker/tui/__init__.py +15 -0
- axio_tools_docker-0.3.2/src/axio_tools_docker/tui/docker.py +152 -0
- axio_tools_docker-0.2.3/src/axio_tools_docker/settings.py +0 -170
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/.github/workflows/publish.yml +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/.github/workflows/tests.yml +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/LICENSE +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/README.md +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/__init__.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/config.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/handler.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/src/axio_tools_docker/manager.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_config.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_handler.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_manager.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/tests/test_plugin.py +0 -0
- {axio_tools_docker-0.2.3 → axio_tools_docker-0.3.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axio-tools-docker
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Docker sandbox tools for Axio
|
|
5
5
|
Project-URL: Homepage, https://github.com/axio-agent/axio-tools-docker
|
|
6
6
|
Project-URL: Repository, https://github.com/axio-agent/axio-tools-docker
|
|
@@ -79,7 +79,7 @@ class DockerPlugin:
|
|
|
79
79
|
return self._tools
|
|
80
80
|
|
|
81
81
|
def settings_screen(self) -> Any:
|
|
82
|
-
from .
|
|
82
|
+
from .tui import DockerSettingsScreen
|
|
83
83
|
|
|
84
84
|
return DockerSettingsScreen(self._manager, config=self._config, global_config=self._global_config)
|
|
85
85
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""TUI screens for axio-tools-docker (requires textual)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from .docker import DockerSettingsScreen
|
|
7
|
+
|
|
8
|
+
__all__ = ["DockerSettingsScreen"]
|
|
9
|
+
except ImportError as _e:
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
warnings.warn(
|
|
13
|
+
f"axio-tools-docker TUI screens are unavailable: {_e}. Install textual: pip install axio[tui]",
|
|
14
|
+
stacklevel=1,
|
|
15
|
+
)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Settings screen for Docker sandbox configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from textual.app import ComposeResult
|
|
8
|
+
from textual.binding import Binding
|
|
9
|
+
from textual.containers import Container, Horizontal
|
|
10
|
+
from textual.screen import ModalScreen
|
|
11
|
+
from textual.widgets import Button, Checkbox, Input, Static
|
|
12
|
+
|
|
13
|
+
from axio_tools_docker.config import SandboxConfig
|
|
14
|
+
from axio_tools_docker.manager import SandboxManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DockerSettingsScreen(ModalScreen[None]):
|
|
18
|
+
"""Settings screen for Docker sandbox configuration."""
|
|
19
|
+
|
|
20
|
+
BINDINGS = [Binding("escape", "cancel", "Cancel")]
|
|
21
|
+
CSS = """
|
|
22
|
+
DockerSettingsScreen { align: center middle; }
|
|
23
|
+
#docker-settings {
|
|
24
|
+
width: 70;
|
|
25
|
+
height: auto;
|
|
26
|
+
max-height: 90%;
|
|
27
|
+
border: heavy $accent;
|
|
28
|
+
background: $panel;
|
|
29
|
+
padding: 1 2;
|
|
30
|
+
}
|
|
31
|
+
#docker-settings Input { margin-bottom: 1; }
|
|
32
|
+
#docker-settings Checkbox { margin-bottom: 1; }
|
|
33
|
+
.docker-buttons { height: auto; margin-top: 1; }
|
|
34
|
+
.docker-buttons Button { margin: 0 1; }
|
|
35
|
+
.docker-container-actions { height: auto; margin-top: 1; margin-bottom: 1; }
|
|
36
|
+
.docker-container-actions Button { margin: 0 1; }
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
manager: SandboxManager,
|
|
42
|
+
config: Any = None,
|
|
43
|
+
global_config: Any = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
super().__init__()
|
|
46
|
+
self._manager = manager
|
|
47
|
+
self._config = config
|
|
48
|
+
self._global_config = global_config
|
|
49
|
+
|
|
50
|
+
def compose(self) -> ComposeResult:
|
|
51
|
+
cfg = self._manager.config
|
|
52
|
+
docker_ok = self._manager.docker_available()
|
|
53
|
+
status = "[green]Docker available[/]" if docker_ok else "[red]Docker not found[/]"
|
|
54
|
+
running = self._manager.container_running
|
|
55
|
+
|
|
56
|
+
with Container(id="docker-settings"):
|
|
57
|
+
yield Static("[bold]Docker Sandbox Settings[/]")
|
|
58
|
+
yield Static(f"Status: {status}", id="docker-status")
|
|
59
|
+
yield Static(
|
|
60
|
+
"Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
|
|
61
|
+
id="docker-container-status",
|
|
62
|
+
)
|
|
63
|
+
with Horizontal(classes="docker-container-actions"):
|
|
64
|
+
yield Button("Prepare", id="btn-prepare", variant="success", disabled=running)
|
|
65
|
+
yield Button("Stop", id="btn-stop", variant="error", disabled=not running)
|
|
66
|
+
yield Button("Recreate", id="btn-recreate", variant="warning", disabled=not running)
|
|
67
|
+
yield Static("Image:")
|
|
68
|
+
yield Input(value=cfg.image, placeholder="e.g. python:latest", id="docker-image")
|
|
69
|
+
yield Static("Memory limit:")
|
|
70
|
+
yield Input(value=cfg.memory, placeholder="e.g. 256m", id="docker-memory")
|
|
71
|
+
yield Static("CPU limit:")
|
|
72
|
+
yield Input(value=cfg.cpus, placeholder="e.g. 1.0", id="docker-cpus")
|
|
73
|
+
yield Checkbox("Allow network access", cfg.network, id="docker-network")
|
|
74
|
+
with Horizontal(classes="docker-buttons"):
|
|
75
|
+
yield Button("Save", id="btn-save", variant="primary")
|
|
76
|
+
yield Button("Cancel", id="btn-cancel")
|
|
77
|
+
|
|
78
|
+
def on_mount(self) -> None:
|
|
79
|
+
self.query_one("#docker-image", Input).focus()
|
|
80
|
+
|
|
81
|
+
def _update_container_status(self) -> None:
|
|
82
|
+
running = self._manager.container_running
|
|
83
|
+
status_widget = self.query_one("#docker-container-status", Static)
|
|
84
|
+
status_widget.update(
|
|
85
|
+
"Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
|
|
86
|
+
)
|
|
87
|
+
self.query_one("#btn-prepare", Button).disabled = running
|
|
88
|
+
self.query_one("#btn-stop", Button).disabled = not running
|
|
89
|
+
self.query_one("#btn-recreate", Button).disabled = not running
|
|
90
|
+
|
|
91
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
92
|
+
if event.button.id == "btn-cancel":
|
|
93
|
+
self.dismiss(None)
|
|
94
|
+
elif event.button.id == "btn-save":
|
|
95
|
+
self._save()
|
|
96
|
+
elif event.button.id == "btn-prepare":
|
|
97
|
+
self.app.run_worker(self._do_prepare())
|
|
98
|
+
elif event.button.id == "btn-stop":
|
|
99
|
+
self.app.run_worker(self._do_stop())
|
|
100
|
+
elif event.button.id == "btn-recreate":
|
|
101
|
+
self.app.run_worker(self._do_recreate())
|
|
102
|
+
|
|
103
|
+
async def _do_prepare(self) -> None:
|
|
104
|
+
self.notify("Preparing container...")
|
|
105
|
+
try:
|
|
106
|
+
await self._manager._ensure_container()
|
|
107
|
+
self._update_container_status()
|
|
108
|
+
self.notify("Container ready")
|
|
109
|
+
except RuntimeError as exc:
|
|
110
|
+
self.notify(str(exc), severity="error")
|
|
111
|
+
|
|
112
|
+
async def _do_stop(self) -> None:
|
|
113
|
+
await self._manager.close()
|
|
114
|
+
self._update_container_status()
|
|
115
|
+
self.notify("Container stopped")
|
|
116
|
+
|
|
117
|
+
async def _do_recreate(self) -> None:
|
|
118
|
+
self.notify("Recreating container...")
|
|
119
|
+
try:
|
|
120
|
+
await self._manager.recreate()
|
|
121
|
+
self._update_container_status()
|
|
122
|
+
self.notify("Container recreated")
|
|
123
|
+
except RuntimeError as exc:
|
|
124
|
+
self.notify(str(exc), severity="error")
|
|
125
|
+
self._update_container_status()
|
|
126
|
+
|
|
127
|
+
def _save(self) -> None:
|
|
128
|
+
image = self.query_one("#docker-image", Input).value.strip()
|
|
129
|
+
memory = self.query_one("#docker-memory", Input).value.strip()
|
|
130
|
+
cpus = self.query_one("#docker-cpus", Input).value.strip()
|
|
131
|
+
network = self.query_one("#docker-network", Checkbox).value
|
|
132
|
+
|
|
133
|
+
cfg = SandboxConfig(
|
|
134
|
+
image=image or "python:latest",
|
|
135
|
+
memory=memory or "256m",
|
|
136
|
+
cpus=cpus or "1.0",
|
|
137
|
+
network=network,
|
|
138
|
+
)
|
|
139
|
+
self._manager.config = cfg
|
|
140
|
+
self.app.run_worker(self._persist(cfg))
|
|
141
|
+
self.dismiss(None)
|
|
142
|
+
|
|
143
|
+
async def _persist(self, cfg: SandboxConfig) -> None:
|
|
144
|
+
db = self._config or self._global_config
|
|
145
|
+
if db is None:
|
|
146
|
+
return
|
|
147
|
+
await db.delete_prefix("docker.")
|
|
148
|
+
for key, value in cfg.to_dict().items():
|
|
149
|
+
await db.set(f"docker.{key}", value)
|
|
150
|
+
|
|
151
|
+
def action_cancel(self) -> None:
|
|
152
|
+
self.dismiss(None)
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
"""TUI settings screen for Docker sandbox configuration."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
-
|
|
7
|
-
try:
|
|
8
|
-
from textual.app import ComposeResult
|
|
9
|
-
from textual.binding import Binding
|
|
10
|
-
from textual.containers import Container, Horizontal
|
|
11
|
-
from textual.screen import ModalScreen
|
|
12
|
-
from textual.widgets import Button, Checkbox, Input, Static
|
|
13
|
-
|
|
14
|
-
_HAS_TEXTUAL = True
|
|
15
|
-
except ImportError:
|
|
16
|
-
_HAS_TEXTUAL = False
|
|
17
|
-
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
from .config import SandboxConfig
|
|
20
|
-
from .manager import SandboxManager
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if _HAS_TEXTUAL:
|
|
24
|
-
|
|
25
|
-
class DockerSettingsScreen(ModalScreen[None]):
|
|
26
|
-
"""Settings screen for Docker sandbox configuration."""
|
|
27
|
-
|
|
28
|
-
BINDINGS = [Binding("escape", "cancel", "Cancel")]
|
|
29
|
-
CSS = """
|
|
30
|
-
DockerSettingsScreen { align: center middle; }
|
|
31
|
-
#docker-settings {
|
|
32
|
-
width: 70;
|
|
33
|
-
height: auto;
|
|
34
|
-
max-height: 90%;
|
|
35
|
-
border: heavy $accent;
|
|
36
|
-
background: $panel;
|
|
37
|
-
padding: 1 2;
|
|
38
|
-
}
|
|
39
|
-
#docker-settings Input { margin-bottom: 1; }
|
|
40
|
-
#docker-settings Checkbox { margin-bottom: 1; }
|
|
41
|
-
.docker-buttons { height: auto; margin-top: 1; }
|
|
42
|
-
.docker-buttons Button { margin: 0 1; }
|
|
43
|
-
.docker-container-actions { height: auto; margin-top: 1; margin-bottom: 1; }
|
|
44
|
-
.docker-container-actions Button { margin: 0 1; }
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
def __init__(
|
|
48
|
-
self,
|
|
49
|
-
manager: SandboxManager,
|
|
50
|
-
config: Any = None,
|
|
51
|
-
global_config: Any = None,
|
|
52
|
-
) -> None:
|
|
53
|
-
super().__init__()
|
|
54
|
-
self._manager = manager
|
|
55
|
-
self._config = config
|
|
56
|
-
self._global_config = global_config
|
|
57
|
-
|
|
58
|
-
def compose(self) -> ComposeResult:
|
|
59
|
-
cfg = self._manager.config
|
|
60
|
-
docker_ok = self._manager.docker_available()
|
|
61
|
-
status = "[green]Docker available[/]" if docker_ok else "[red]Docker not found[/]"
|
|
62
|
-
running = self._manager.container_running
|
|
63
|
-
|
|
64
|
-
with Container(id="docker-settings"):
|
|
65
|
-
yield Static("[bold]Docker Sandbox Settings[/]")
|
|
66
|
-
yield Static(f"Status: {status}", id="docker-status")
|
|
67
|
-
yield Static(
|
|
68
|
-
"Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
|
|
69
|
-
id="docker-container-status",
|
|
70
|
-
)
|
|
71
|
-
with Horizontal(classes="docker-container-actions"):
|
|
72
|
-
yield Button("Prepare", id="btn-prepare", variant="success", disabled=running)
|
|
73
|
-
yield Button("Stop", id="btn-stop", variant="error", disabled=not running)
|
|
74
|
-
yield Button("Recreate", id="btn-recreate", variant="warning", disabled=not running)
|
|
75
|
-
yield Static("Image:")
|
|
76
|
-
yield Input(value=cfg.image, placeholder="e.g. python:latest", id="docker-image")
|
|
77
|
-
yield Static("Memory limit:")
|
|
78
|
-
yield Input(value=cfg.memory, placeholder="e.g. 256m", id="docker-memory")
|
|
79
|
-
yield Static("CPU limit:")
|
|
80
|
-
yield Input(value=cfg.cpus, placeholder="e.g. 1.0", id="docker-cpus")
|
|
81
|
-
yield Checkbox("Allow network access", cfg.network, id="docker-network")
|
|
82
|
-
with Horizontal(classes="docker-buttons"):
|
|
83
|
-
yield Button("Save", id="btn-save", variant="primary")
|
|
84
|
-
yield Button("Cancel", id="btn-cancel")
|
|
85
|
-
|
|
86
|
-
def on_mount(self) -> None:
|
|
87
|
-
self.query_one("#docker-image", Input).focus()
|
|
88
|
-
|
|
89
|
-
def _update_container_status(self) -> None:
|
|
90
|
-
running = self._manager.container_running
|
|
91
|
-
status_widget = self.query_one("#docker-container-status", Static)
|
|
92
|
-
status_widget.update(
|
|
93
|
-
"Container: [green]running[/]" if running else "Container: [dim]stopped[/]",
|
|
94
|
-
)
|
|
95
|
-
self.query_one("#btn-prepare", Button).disabled = running
|
|
96
|
-
self.query_one("#btn-stop", Button).disabled = not running
|
|
97
|
-
self.query_one("#btn-recreate", Button).disabled = not running
|
|
98
|
-
|
|
99
|
-
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
100
|
-
if event.button.id == "btn-cancel":
|
|
101
|
-
self.dismiss(None)
|
|
102
|
-
elif event.button.id == "btn-save":
|
|
103
|
-
self._save()
|
|
104
|
-
elif event.button.id == "btn-prepare":
|
|
105
|
-
self.app.run_worker(self._do_prepare())
|
|
106
|
-
elif event.button.id == "btn-stop":
|
|
107
|
-
self.app.run_worker(self._do_stop())
|
|
108
|
-
elif event.button.id == "btn-recreate":
|
|
109
|
-
self.app.run_worker(self._do_recreate())
|
|
110
|
-
|
|
111
|
-
async def _do_prepare(self) -> None:
|
|
112
|
-
self.notify("Preparing container...")
|
|
113
|
-
try:
|
|
114
|
-
await self._manager._ensure_container()
|
|
115
|
-
self._update_container_status()
|
|
116
|
-
self.notify("Container ready")
|
|
117
|
-
except RuntimeError as exc:
|
|
118
|
-
self.notify(str(exc), severity="error")
|
|
119
|
-
|
|
120
|
-
async def _do_stop(self) -> None:
|
|
121
|
-
await self._manager.close()
|
|
122
|
-
self._update_container_status()
|
|
123
|
-
self.notify("Container stopped")
|
|
124
|
-
|
|
125
|
-
async def _do_recreate(self) -> None:
|
|
126
|
-
self.notify("Recreating container...")
|
|
127
|
-
try:
|
|
128
|
-
await self._manager.recreate()
|
|
129
|
-
self._update_container_status()
|
|
130
|
-
self.notify("Container recreated")
|
|
131
|
-
except RuntimeError as exc:
|
|
132
|
-
self.notify(str(exc), severity="error")
|
|
133
|
-
self._update_container_status()
|
|
134
|
-
|
|
135
|
-
def _save(self) -> None:
|
|
136
|
-
from .config import SandboxConfig
|
|
137
|
-
|
|
138
|
-
image = self.query_one("#docker-image", Input).value.strip()
|
|
139
|
-
memory = self.query_one("#docker-memory", Input).value.strip()
|
|
140
|
-
cpus = self.query_one("#docker-cpus", Input).value.strip()
|
|
141
|
-
network = self.query_one("#docker-network", Checkbox).value
|
|
142
|
-
|
|
143
|
-
cfg = SandboxConfig(
|
|
144
|
-
image=image or "python:latest",
|
|
145
|
-
memory=memory or "256m",
|
|
146
|
-
cpus=cpus or "1.0",
|
|
147
|
-
network=network,
|
|
148
|
-
)
|
|
149
|
-
self._manager.config = cfg
|
|
150
|
-
self.app.run_worker(self._persist(cfg))
|
|
151
|
-
self.dismiss(None)
|
|
152
|
-
|
|
153
|
-
async def _persist(self, cfg: SandboxConfig) -> None:
|
|
154
|
-
db = self._config or self._global_config
|
|
155
|
-
if db is None:
|
|
156
|
-
return
|
|
157
|
-
await db.delete_prefix("docker.")
|
|
158
|
-
for key, value in cfg.to_dict().items():
|
|
159
|
-
await db.set(f"docker.{key}", value)
|
|
160
|
-
|
|
161
|
-
def action_cancel(self) -> None:
|
|
162
|
-
self.dismiss(None)
|
|
163
|
-
|
|
164
|
-
else:
|
|
165
|
-
|
|
166
|
-
class DockerSettingsScreen: # type: ignore[no-redef]
|
|
167
|
-
"""Stub when textual is not installed."""
|
|
168
|
-
|
|
169
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
170
|
-
raise ImportError("textual is required for Docker settings screen")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|