@anastops/cli 2.0.1 → 2.1.1
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.
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +220 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +92 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +26 -5
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/ranger-tui/ranger_tui/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/app.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/actions/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/actions/__pycache__/agent_actions.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/actions/__pycache__/session_actions.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/actions/__pycache__/task_actions.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/app.py +113 -22
- package/ranger-tui/ranger_tui/data/__init__.py +2 -1
- package/ranger-tui/ranger_tui/data/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/__pycache__/client.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/client.py +38 -10
- package/ranger-tui/ranger_tui/screens/__init__.py +2 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/command_palette.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/dashboard.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/help.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/loading.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/session.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/task.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/dashboard.py +18 -4
- package/ranger-tui/ranger_tui/screens/loading.py +214 -0
- package/ranger-tui/ranger_tui/screens/session.py +19 -5
- package/ranger-tui/ranger_tui/screens/task.py +18 -4
- package/ranger-tui/ranger_tui/widgets/__pycache__/agents_table.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/header.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/metrics_panel.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/sessions_table.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/tasks_table.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/topbar.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/topbar.py +14 -36
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Loading screen displayed during database connection.
|
|
3
|
+
|
|
4
|
+
Uses a wave animation (dots highlighting left to right) inside the loading box.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from textual.screen import Screen
|
|
11
|
+
from textual.app import ComposeResult
|
|
12
|
+
from textual.containers import Vertical, Horizontal, Container
|
|
13
|
+
from textual.widgets import Static
|
|
14
|
+
from textual.reactive import reactive
|
|
15
|
+
from textual.timer import Timer
|
|
16
|
+
|
|
17
|
+
from ranger_tui.widgets.ranger_image import RangerImage
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class LoadingScreen(Screen):
|
|
24
|
+
"""Loading screen with wave animation inside the box.
|
|
25
|
+
|
|
26
|
+
Uses a wave animation (dots highlighting left to right) inside the loading box.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
CSS = """
|
|
30
|
+
LoadingScreen {
|
|
31
|
+
background: #000000;
|
|
32
|
+
align: center middle;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#loading-box {
|
|
36
|
+
width: auto;
|
|
37
|
+
min-width: 150;
|
|
38
|
+
height: auto;
|
|
39
|
+
border: round #3d5c8c;
|
|
40
|
+
padding: 2 4;
|
|
41
|
+
align: center middle;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#loading-inner {
|
|
45
|
+
width: 100%;
|
|
46
|
+
height: auto;
|
|
47
|
+
align: center middle;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#loading-title-row {
|
|
51
|
+
width: auto;
|
|
52
|
+
height: auto;
|
|
53
|
+
align: center middle;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#loading-title-anastops {
|
|
57
|
+
width: auto;
|
|
58
|
+
height: auto;
|
|
59
|
+
content-align: right middle;
|
|
60
|
+
padding-right: 3;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#loading-image-container {
|
|
64
|
+
width: auto;
|
|
65
|
+
height: auto;
|
|
66
|
+
align: center middle;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#loading-title-ranger {
|
|
70
|
+
width: auto;
|
|
71
|
+
height: auto;
|
|
72
|
+
content-align: left middle;
|
|
73
|
+
padding-left: 3;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#loading-progress-container {
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: auto;
|
|
79
|
+
align: center middle;
|
|
80
|
+
margin-top: 6;
|
|
81
|
+
margin-bottom: 4;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#loading-animation {
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 1;
|
|
87
|
+
text-align: center;
|
|
88
|
+
content-align: center middle;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#loading-status-text {
|
|
92
|
+
width: 100%;
|
|
93
|
+
height: auto;
|
|
94
|
+
text-align: center;
|
|
95
|
+
content-align: center middle;
|
|
96
|
+
color: #7aa2f7;
|
|
97
|
+
margin-top: 1;
|
|
98
|
+
}
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# Reactive variables
|
|
102
|
+
status_text: reactive[str] = reactive("Waiting for connection...")
|
|
103
|
+
loading_animation: reactive[str] = reactive("")
|
|
104
|
+
|
|
105
|
+
# Animation state
|
|
106
|
+
animation_timer: Optional[Timer] = None
|
|
107
|
+
animation_frame: int = 0
|
|
108
|
+
|
|
109
|
+
def _update_loading_animation(self) -> None:
|
|
110
|
+
"""Update loading animation with wave pattern (dots highlighting left to right)."""
|
|
111
|
+
num_dots = 25
|
|
112
|
+
|
|
113
|
+
# Current position of the wave
|
|
114
|
+
current_pos = self.animation_frame % num_dots
|
|
115
|
+
|
|
116
|
+
# Build dots WITHOUT padding spaces - let CSS center them
|
|
117
|
+
dots = []
|
|
118
|
+
for i in range(num_dots):
|
|
119
|
+
# Calculate distance from current wave position
|
|
120
|
+
distance = abs(i - current_pos)
|
|
121
|
+
|
|
122
|
+
if distance == 0:
|
|
123
|
+
dots.append('[bold #7aa2f7]\u25cf[/]') # Full circle (bright blue)
|
|
124
|
+
elif distance == 1:
|
|
125
|
+
dots.append('[#5a7fa8]\u25cb[/]') # Empty circle (medium blue)
|
|
126
|
+
else:
|
|
127
|
+
dots.append('[dim #3d5c8c]\u00b7[/]') # Middle dot (dim blue)
|
|
128
|
+
|
|
129
|
+
# Join with single space between dots - CSS text-align: center will center this
|
|
130
|
+
self.loading_animation = ' '.join(dots)
|
|
131
|
+
self.animation_frame += 1
|
|
132
|
+
|
|
133
|
+
def watch_loading_animation(self, value: str) -> None:
|
|
134
|
+
"""Update loading animation widget when it changes."""
|
|
135
|
+
try:
|
|
136
|
+
animation = self.query_one("#loading-animation", Static)
|
|
137
|
+
animation.update(value)
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
def compose(self) -> ComposeResult:
|
|
142
|
+
with Container(id="loading-box"):
|
|
143
|
+
with Vertical(id="loading-inner"):
|
|
144
|
+
# Horizontal title layout: ANASTOPS [image] RANGER
|
|
145
|
+
with Horizontal(id="loading-title-row"):
|
|
146
|
+
# ANASTOPS title (blue) - large box-drawing ASCII art
|
|
147
|
+
anastops_title = (
|
|
148
|
+
"[bold #7aa2f7]"
|
|
149
|
+
"\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n"
|
|
150
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\n"
|
|
151
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n"
|
|
152
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255a\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u255a\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2588\u2588\u2551\n"
|
|
153
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\n"
|
|
154
|
+
"\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d[/]"
|
|
155
|
+
)
|
|
156
|
+
yield Static(anastops_title, id="loading-title-anastops", markup=True)
|
|
157
|
+
|
|
158
|
+
# Ranger image in center
|
|
159
|
+
with Container(id="loading-image-container"):
|
|
160
|
+
yield RangerImage()
|
|
161
|
+
|
|
162
|
+
# RANGER title (purple) - large box-drawing ASCII art
|
|
163
|
+
ranger_title = (
|
|
164
|
+
"[bold #bb9af7]"
|
|
165
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \n"
|
|
166
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\n"
|
|
167
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\n"
|
|
168
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255a\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\n"
|
|
169
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2551\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\n"
|
|
170
|
+
"\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d\u255a\u2550\u255d \u255a\u2550\u255d[/]"
|
|
171
|
+
)
|
|
172
|
+
yield Static(ranger_title, id="loading-title-ranger", markup=True)
|
|
173
|
+
|
|
174
|
+
# Animation and status text
|
|
175
|
+
with Vertical(id="loading-progress-container"):
|
|
176
|
+
yield Static("", id="loading-animation", markup=True)
|
|
177
|
+
yield Static(self.status_text, id="loading-status-text", markup=True)
|
|
178
|
+
|
|
179
|
+
def on_mount(self) -> None:
|
|
180
|
+
"""Start the loading animation."""
|
|
181
|
+
logger.debug("LoadingScreen mounted - starting wave loading animation")
|
|
182
|
+
self.animation_frame = 0
|
|
183
|
+
# Use 0.1 seconds (10 FPS) for smooth animation
|
|
184
|
+
self.animation_timer = self.set_interval(0.1, self._update_loading_animation)
|
|
185
|
+
|
|
186
|
+
def watch_status_text(self, value: str) -> None:
|
|
187
|
+
"""Update status text when it changes."""
|
|
188
|
+
try:
|
|
189
|
+
status = self.query_one("#loading-status-text", Static)
|
|
190
|
+
status.update(value)
|
|
191
|
+
status.refresh(layout=True)
|
|
192
|
+
self.refresh(layout=True)
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
def set_status(self, text: str) -> None:
|
|
197
|
+
"""Set the status text."""
|
|
198
|
+
self.status_text = text
|
|
199
|
+
|
|
200
|
+
def set_error(self, error_text: str) -> None:
|
|
201
|
+
"""Display error message."""
|
|
202
|
+
self.status_text = f"[bold #f7768e]{error_text}[/]"
|
|
203
|
+
|
|
204
|
+
def hide_spinner(self) -> None:
|
|
205
|
+
"""Hide the loading animation."""
|
|
206
|
+
try:
|
|
207
|
+
# Stop the animation timer
|
|
208
|
+
if self.animation_timer is not None:
|
|
209
|
+
self.animation_timer.stop()
|
|
210
|
+
self.animation_timer = None
|
|
211
|
+
# Clear the animation widget
|
|
212
|
+
self.query_one("#loading-animation", Static).update("")
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
@@ -7,7 +7,7 @@ from textual.app import ComposeResult
|
|
|
7
7
|
from textual.screen import Screen
|
|
8
8
|
from textual.binding import Binding
|
|
9
9
|
from textual.containers import Horizontal
|
|
10
|
-
from textual.widgets import DataTable, Footer, TabbedContent, TabPane, RichLog
|
|
10
|
+
from textual.widgets import DataTable, Footer, TabbedContent, TabPane, RichLog, Static
|
|
11
11
|
|
|
12
12
|
from ranger_tui.data import SessionReport
|
|
13
13
|
from ranger_tui.widgets import TopBar, Sidebar
|
|
@@ -16,22 +16,36 @@ from ranger_tui.theme import format_status, format_model, STATUS_SYMBOLS
|
|
|
16
16
|
|
|
17
17
|
class SessionScreen(Screen):
|
|
18
18
|
"""Session detail screen with tasks and agents."""
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
DEFAULT_CSS = """
|
|
21
|
+
.view-title {
|
|
22
|
+
height: 4;
|
|
23
|
+
content-align: center middle;
|
|
24
|
+
text-style: bold;
|
|
25
|
+
background: $surface;
|
|
26
|
+
border: tall $primary;
|
|
27
|
+
color: $primary;
|
|
28
|
+
text-align: center;
|
|
29
|
+
padding: 0 2;
|
|
30
|
+
}
|
|
31
|
+
"""
|
|
32
|
+
|
|
20
33
|
BINDINGS = [
|
|
21
34
|
Binding("enter", "open_task", "Open", show=True),
|
|
22
35
|
Binding("t", "new_task", "New Task", show=True),
|
|
23
36
|
]
|
|
24
|
-
|
|
37
|
+
|
|
25
38
|
session_id: str = ""
|
|
26
39
|
report: SessionReport | None = None
|
|
27
|
-
|
|
40
|
+
|
|
28
41
|
def __init__(self, session_id: str):
|
|
29
42
|
super().__init__()
|
|
30
43
|
self.session_id = session_id
|
|
31
|
-
|
|
44
|
+
|
|
32
45
|
def compose(self) -> ComposeResult:
|
|
33
46
|
"""Compose the session detail layout."""
|
|
34
47
|
yield TopBar(title="Loading...")
|
|
48
|
+
yield Static("[bold]S E S S I O N D E T A I L S[/]", classes="view-title", markup=True)
|
|
35
49
|
with Horizontal(id="main"):
|
|
36
50
|
with TabbedContent(id="tabs"):
|
|
37
51
|
with TabPane("Tasks", id="tasks-tab"):
|
|
@@ -5,7 +5,7 @@ Task detail screen - View task details, input, output, and errors.
|
|
|
5
5
|
from textual.app import ComposeResult
|
|
6
6
|
from textual.screen import Screen
|
|
7
7
|
from textual.containers import Horizontal
|
|
8
|
-
from textual.widgets import Footer, TabbedContent, TabPane, RichLog, Markdown
|
|
8
|
+
from textual.widgets import Footer, TabbedContent, TabPane, RichLog, Markdown, Static
|
|
9
9
|
|
|
10
10
|
from ranger_tui.data import Task
|
|
11
11
|
from ranger_tui.widgets import TopBar, Sidebar
|
|
@@ -14,17 +14,31 @@ from ranger_tui.theme import STATUS_SYMBOLS
|
|
|
14
14
|
|
|
15
15
|
class TaskScreen(Screen):
|
|
16
16
|
"""Task detail screen."""
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
DEFAULT_CSS = """
|
|
19
|
+
.view-title {
|
|
20
|
+
height: 4;
|
|
21
|
+
content-align: center middle;
|
|
22
|
+
text-style: bold;
|
|
23
|
+
background: $surface;
|
|
24
|
+
border: tall $primary;
|
|
25
|
+
color: $primary;
|
|
26
|
+
text-align: center;
|
|
27
|
+
padding: 0 2;
|
|
28
|
+
}
|
|
29
|
+
"""
|
|
30
|
+
|
|
18
31
|
task_id: str = ""
|
|
19
32
|
task: Task | None = None
|
|
20
|
-
|
|
33
|
+
|
|
21
34
|
def __init__(self, task_id: str):
|
|
22
35
|
super().__init__()
|
|
23
36
|
self.task_id = task_id
|
|
24
|
-
|
|
37
|
+
|
|
25
38
|
def compose(self) -> ComposeResult:
|
|
26
39
|
"""Compose the task detail layout."""
|
|
27
40
|
yield TopBar(title="Loading...")
|
|
41
|
+
yield Static("[bold]T A S K D E T A I L S[/]", classes="view-title", markup=True)
|
|
28
42
|
with Horizontal(id="main"):
|
|
29
43
|
with TabbedContent(id="tabs"):
|
|
30
44
|
with TabPane("Output", id="output-tab"):
|
|
Binary file
|
|
Binary file
|
|
@@ -37,23 +37,19 @@ class TopBar(Widget):
|
|
|
37
37
|
margin: 0 1;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
#topbar-view-title {
|
|
41
|
-
width: auto;
|
|
42
|
-
height: 100%;
|
|
43
|
-
content-align: center middle;
|
|
44
|
-
margin-left: 2;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
.topbar-spacer {
|
|
48
41
|
width: 1fr;
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
#topbar-status-frame {
|
|
52
|
-
width:
|
|
45
|
+
width: 28;
|
|
46
|
+
min-width: 26;
|
|
47
|
+
max-width: 32;
|
|
53
48
|
height: auto;
|
|
54
|
-
|
|
49
|
+
background: $panel;
|
|
55
50
|
border: round $secondary-darken-2;
|
|
56
|
-
padding:
|
|
51
|
+
padding: 1 2;
|
|
52
|
+
content-align: center middle;
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
TopBar > .topbar-status, #topbar-status-frame .topbar-status {
|
|
@@ -74,60 +70,42 @@ class TopBar(Widget):
|
|
|
74
70
|
|
|
75
71
|
mongo_ok: reactive[bool] = reactive(False)
|
|
76
72
|
redis_ok: reactive[bool] = reactive(False)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def __init__(self, title: str | None = None, view_title: str = "") -> None:
|
|
73
|
+
|
|
74
|
+
def __init__(self, title: str | None = None) -> None:
|
|
80
75
|
"""
|
|
81
76
|
Initialize TopBar.
|
|
82
|
-
|
|
77
|
+
|
|
83
78
|
Args:
|
|
84
79
|
title: Optional custom title. If None, uses the standard logo.
|
|
85
|
-
view_title: Current view name (e.g., "Sessions", "Tasks")
|
|
86
80
|
"""
|
|
87
81
|
super().__init__()
|
|
88
82
|
self._custom_title = title
|
|
89
|
-
self.view_title = view_title
|
|
90
83
|
|
|
91
84
|
def compose(self) -> ComposeResult:
|
|
92
85
|
# Left spacer (for centering)
|
|
93
86
|
yield Static("", classes="topbar-spacer")
|
|
94
|
-
|
|
87
|
+
|
|
95
88
|
# ANASTOPS title
|
|
96
89
|
line1_left = "[bold #00d4ff]▄▀█[/] [bold #00c8ff]█▄[/][bold #00c8ff]█[/] [bold #00bcff]▄▀█[/] [bold #00b0ff]█▀[/] [bold #00a4ff]▀█▀[/] [bold #0098ff]█▀█[/] [bold #008cff]█▀█[/] [bold #0080ff]█▀[/]"
|
|
97
90
|
line2_left = "[bold #00d4ff]█▀█[/] [bold #00c8ff]█[/] [bold #00c8ff]█[/] [bold #00bcff]█▀█[/] [bold #00b0ff]▄█[/] [bold #00a4ff]█[/] [bold #0098ff]█▄█[/] [bold #008cff]█▀▀[/] [bold #0080ff]▄█[/]"
|
|
98
91
|
yield Static(line1_left + "\n" + line2_left, id="topbar-title-left", classes="topbar-title-text", markup=True)
|
|
99
|
-
|
|
92
|
+
|
|
100
93
|
# Ranger image in center with circle frame
|
|
101
94
|
with Horizontal(id="topbar-brand"):
|
|
102
95
|
yield RangerImage()
|
|
103
|
-
|
|
96
|
+
|
|
104
97
|
# RANGER title
|
|
105
98
|
line1_right = "[bold #ff00ff]█▀█[/] [bold #e600ff]▄▀█[/] [bold #cc00ff]█▄[/][bold #cc00ff]█[/] [bold #b300ff]█▀▀[/] [bold #9900ff]█▀▀[/] [bold #8000ff]█▀█[/]"
|
|
106
99
|
line2_right = "[bold #ff00ff]█▀▄[/] [bold #e600ff]█▀█[/] [bold #cc00ff]█[/] [bold #cc00ff]█[/] [bold #b300ff]█▄█[/] [bold #9900ff]██▄[/] [bold #8000ff]█▀▄[/]"
|
|
107
100
|
yield Static(line1_right + "\n" + line2_right, id="topbar-title-right", classes="topbar-title-text", markup=True)
|
|
108
|
-
|
|
101
|
+
|
|
109
102
|
# Right spacer (for centering)
|
|
110
103
|
yield Static("", classes="topbar-spacer")
|
|
111
|
-
|
|
104
|
+
|
|
112
105
|
# Status frame on far right
|
|
113
106
|
with Horizontal(id="topbar-status-frame"):
|
|
114
107
|
yield Static("", classes="topbar-status", id="topbar-status", markup=True)
|
|
115
108
|
|
|
116
|
-
def watch_view_title(self, value: str) -> None:
|
|
117
|
-
"""Update view title display."""
|
|
118
|
-
try:
|
|
119
|
-
view_widget = self.query_one("#topbar-view-title", Static)
|
|
120
|
-
if value:
|
|
121
|
-
view_widget.update(f"[bold]{value}[/]")
|
|
122
|
-
else:
|
|
123
|
-
view_widget.update("")
|
|
124
|
-
except Exception:
|
|
125
|
-
pass
|
|
126
|
-
|
|
127
|
-
def set_view_title(self, title: str) -> None:
|
|
128
|
-
"""Set the current view title."""
|
|
129
|
-
self.view_title = title
|
|
130
|
-
|
|
131
109
|
def watch_mongo_ok(self, value: bool) -> None:
|
|
132
110
|
"""Update status when mongo connection changes."""
|
|
133
111
|
self._update_status()
|