@anastops/cli 2.0.1 → 2.1.0
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 +10 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/index.js +0 -0
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAgOnD"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -133,10 +133,16 @@ export async function doctorCommand() {
|
|
|
133
133
|
for (const adapter of registry.list()) {
|
|
134
134
|
const spinner = ora(`Checking ${adapter.name}...`).start();
|
|
135
135
|
try {
|
|
136
|
+
const { appendFileSync } = await import('fs');
|
|
137
|
+
appendFileSync('/tmp/doctor-debug.log', `Checking adapter: ${adapter.name} (${adapter.type})\n`);
|
|
136
138
|
const [installed, authenticated] = await Promise.all([
|
|
137
139
|
adapter.isInstalled(),
|
|
138
|
-
adapter.isAuthenticated().catch(() =>
|
|
140
|
+
adapter.isAuthenticated().catch((err) => {
|
|
141
|
+
appendFileSync('/tmp/doctor-debug.log', `Auth check failed for ${adapter.name}: ${String(err)}\n`);
|
|
142
|
+
return false;
|
|
143
|
+
}),
|
|
139
144
|
]);
|
|
145
|
+
appendFileSync('/tmp/doctor-debug.log', `${adapter.name} - installed: ${installed}, authenticated: ${authenticated}\n`);
|
|
140
146
|
spinner.stop();
|
|
141
147
|
if (!installed) {
|
|
142
148
|
results.push({
|
|
@@ -164,7 +170,9 @@ export async function doctorCommand() {
|
|
|
164
170
|
});
|
|
165
171
|
}
|
|
166
172
|
}
|
|
167
|
-
catch {
|
|
173
|
+
catch (err) {
|
|
174
|
+
const { appendFileSync } = await import('fs');
|
|
175
|
+
appendFileSync('/tmp/doctor-debug.log', `Error checking ${adapter.name}: ${String(err)}\n`);
|
|
168
176
|
spinner.stop();
|
|
169
177
|
results.push({
|
|
170
178
|
name: adapter.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AAStB,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,wBAAwB;IACxB,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IACpC,MAAM,cAAc,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IAC/E,OAAO,CAAC,IAAI,CAAC;QACX,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC9C,OAAO,EAAE,WAAW;QACpB,OAAO,EAAE,cAAc,GAAG,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,SAAS;KAClE,CAAC,CAAC;IAEH,kEAAkE;IAClE,MAAM,aAAa,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,CAAC;IAC/D,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAEnC,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAChC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,iBAAiB;YAC9D,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,SAAS;SACpD,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,8BAA8B;YACvC,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC;IACL,CAAC;IAED,eAAe;IACf,MAAM,aAAa,GAAG,GAAG,CAAC,oBAAoB,CAAC,CAAC,KAAK,EAAE,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACnD,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3C,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,SAAS;YAClB,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,8BAA8B;YACvC,OAAO,EAAE,4BAA4B;SACtC,CAAC,CAAC;IACL,CAAC;IAED,qEAAqE;IACrE,MAAM,YAAY,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC,KAAK,EAAE,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACnD,iEAAiE;QACjE,MAAM,YAAY,GAAG,QAAQ,CAC3B,gEAAgE,EAChE;YACE,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,OAAO;SAClB,CACF,CAAC;QACF,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACjE,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACnC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,uBAAuB;YAClE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,wBAAwB;SAC1D,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAC;QACzD,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,uBAAuB;YAChC,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IAED,uEAAuE;IACvE,MAAM,YAAY,GAAG,GAAG,CAAC,qBAAqB,CAAC,CAAC,KAAK,EAAE,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACnD,iEAAiE;QACjE,MAAM,YAAY,GAAG,QAAQ,CAC3B,kEAAkE,EAClE;YACE,KAAK,EAAE,MAAM;YACb,QAAQ,EAAE,OAAO;SAClB,CACF,CAAC;QACF,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QACnE,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACnC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,uBAAuB;YAClE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,wBAAwB;SAC1D,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,YAAY,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,uBAAuB;YAChC,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IAED,qBAAqB;IACrB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvC,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,EAAE,CAAC;IAE/C,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;QAE3D,IAAI,CAAC;YACH,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACnD,OAAO,CAAC,WAAW,EAAE;gBACrB,OAAO,CAAC,eAAe,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anastops/cli",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Infrastructure CLI for Anastops MCP Server setup and diagnostics",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"prebuild": "node scripts/copy-ranger.js",
|
|
19
19
|
"build": "tsc",
|
|
20
20
|
"dev": "tsc --watch",
|
|
21
|
+
"dev:tsx": "tsx src/index.ts",
|
|
22
|
+
"dev:tsx:watch": "tsx watch src/index.ts",
|
|
21
23
|
"start": "node dist/index.js",
|
|
22
24
|
"test": "vitest run",
|
|
23
25
|
"test:watch": "vitest",
|
|
@@ -28,8 +30,8 @@
|
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
32
|
"@anastops/core": "1.1.0",
|
|
31
|
-
"@anastops/adapters": "1.1.
|
|
32
|
-
"@anastops/mcp-server": "1.1.
|
|
33
|
+
"@anastops/adapters": "1.1.1",
|
|
34
|
+
"@anastops/mcp-server": "1.1.1",
|
|
33
35
|
"commander": "^14.0.2",
|
|
34
36
|
"enquirer": "^2.4.1",
|
|
35
37
|
"chalk": "^5.6.2",
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
42
|
"@types/node": "^25.0.9",
|
|
43
|
+
"tsx": "^4.19.0",
|
|
41
44
|
"typescript": "^5.9.3",
|
|
42
45
|
"vitest": "^4.0.17"
|
|
43
46
|
},
|
|
Binary file
|
|
Binary file
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Main Ranger TUI Application.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
|
|
5
8
|
from textual.app import App
|
|
6
9
|
from textual.binding import Binding
|
|
7
10
|
from textual.reactive import reactive
|
|
@@ -9,84 +12,172 @@ from textual.reactive import reactive
|
|
|
9
12
|
from ranger_tui.config import config
|
|
10
13
|
from ranger_tui.data import DataClient, get_client, Session
|
|
11
14
|
from ranger_tui.theme import RANGER_THEME
|
|
12
|
-
from ranger_tui.screens import DashboardScreen
|
|
15
|
+
from ranger_tui.screens import DashboardScreen, LoadingScreen
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class RangerApp(App):
|
|
16
19
|
"""
|
|
17
20
|
Anastops Ranger TUI - Terminal dashboard for orchestration management.
|
|
18
|
-
|
|
21
|
+
|
|
19
22
|
Navigation:
|
|
20
23
|
- Arrow keys to move
|
|
21
24
|
- Enter to select/open
|
|
22
25
|
- Escape to go back
|
|
23
26
|
- q to quit
|
|
24
27
|
"""
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
TITLE = "Ranger"
|
|
27
30
|
CSS_PATH = "styles/ranger.tcss"
|
|
28
|
-
|
|
31
|
+
|
|
29
32
|
BINDINGS = [
|
|
30
33
|
Binding("q", "quit", "Quit", show=True, priority=True),
|
|
31
34
|
Binding("escape", "back", "Back", show=True),
|
|
32
35
|
Binding("r", "refresh", "Refresh", show=True),
|
|
33
36
|
]
|
|
34
|
-
|
|
37
|
+
|
|
35
38
|
# Reactive state
|
|
36
39
|
sessions: reactive[list[Session]] = reactive(list, init=False)
|
|
37
40
|
mongo_connected: reactive[bool] = reactive(False)
|
|
38
41
|
redis_connected: reactive[bool] = reactive(False)
|
|
39
|
-
|
|
42
|
+
|
|
40
43
|
def __init__(self):
|
|
41
44
|
super().__init__()
|
|
42
45
|
self.client: DataClient = get_client()
|
|
43
|
-
|
|
46
|
+
# Connection task tracking for non-blocking connection
|
|
47
|
+
self._connection_task: asyncio.Task | None = None
|
|
48
|
+
self._connection_start_time: float = 0.0
|
|
49
|
+
self._connection_timeout: float = 10.0 # seconds
|
|
50
|
+
|
|
44
51
|
async def on_mount(self) -> None:
|
|
45
52
|
"""Called when app is mounted."""
|
|
46
53
|
# Register and apply custom theme
|
|
47
54
|
self.register_theme(RANGER_THEME)
|
|
48
55
|
self.theme = "ranger"
|
|
49
|
-
|
|
50
|
-
#
|
|
51
|
-
await self.
|
|
52
|
-
|
|
56
|
+
|
|
57
|
+
# Show loading screen immediately
|
|
58
|
+
await self.push_screen(LoadingScreen())
|
|
59
|
+
|
|
60
|
+
# Defer connection to allow loading screen to render
|
|
61
|
+
self.call_after_refresh(self._initialize_app)
|
|
62
|
+
|
|
63
|
+
async def _initialize_app(self) -> None:
|
|
64
|
+
"""Initialize app after loading screen is rendered.
|
|
65
|
+
|
|
66
|
+
Uses non-blocking background task pattern to allow LoadingScreen
|
|
67
|
+
animation to run immediately while connection attempt proceeds.
|
|
68
|
+
"""
|
|
69
|
+
# Get the loading screen instance
|
|
70
|
+
loading_screen = self.screen_stack[-1]
|
|
71
|
+
|
|
72
|
+
if isinstance(loading_screen, LoadingScreen):
|
|
73
|
+
loading_screen.set_status("Connecting to MongoDB...")
|
|
74
|
+
|
|
75
|
+
# Start connection in background WITHOUT awaiting it
|
|
76
|
+
# This allows the LoadingScreen animation to run immediately
|
|
77
|
+
self._connection_task = asyncio.create_task(self._connect())
|
|
78
|
+
self._connection_start_time = time.time()
|
|
79
|
+
|
|
80
|
+
# Schedule first check in 500ms - animation runs in parallel
|
|
81
|
+
self.set_timer(0.5, lambda: self._check_connection())
|
|
82
|
+
|
|
83
|
+
def _check_connection(self) -> None:
|
|
84
|
+
"""Check connection status periodically (non-blocking).
|
|
85
|
+
|
|
86
|
+
Called by Textual's timer system, allowing animations to continue.
|
|
87
|
+
"""
|
|
88
|
+
if self._connection_task is None:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
loading_screen = self.screen_stack[-1] if self.screen_stack else None
|
|
92
|
+
elapsed = time.time() - self._connection_start_time
|
|
93
|
+
|
|
94
|
+
if self._connection_task.done():
|
|
95
|
+
# Connection completed - handle result asynchronously
|
|
96
|
+
asyncio.create_task(self._handle_connection_complete())
|
|
97
|
+
elif elapsed > self._connection_timeout:
|
|
98
|
+
# Timeout after configured seconds
|
|
99
|
+
self._connection_task.cancel()
|
|
100
|
+
if isinstance(loading_screen, LoadingScreen):
|
|
101
|
+
# Hide the spinner when connection fails
|
|
102
|
+
loading_screen.hide_spinner()
|
|
103
|
+
loading_screen.set_error(
|
|
104
|
+
"⚠ Connection timeout. Please start Docker with 'anastops init' and press 'q' to quit"
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
# Not done yet, check again in 500ms
|
|
108
|
+
# This allows animation to continue running
|
|
109
|
+
self.set_timer(0.5, lambda: self._check_connection())
|
|
110
|
+
|
|
111
|
+
async def _handle_connection_complete(self) -> None:
|
|
112
|
+
"""Handle completed connection task."""
|
|
113
|
+
loading_screen = self.screen_stack[-1] if self.screen_stack else None
|
|
114
|
+
|
|
115
|
+
# Get result and check for exceptions
|
|
116
|
+
if self._connection_task is not None:
|
|
117
|
+
try:
|
|
118
|
+
await self._connection_task
|
|
119
|
+
except asyncio.CancelledError:
|
|
120
|
+
return
|
|
121
|
+
except Exception as e:
|
|
122
|
+
if isinstance(loading_screen, LoadingScreen):
|
|
123
|
+
loading_screen.hide_spinner()
|
|
124
|
+
loading_screen.set_error(f"⚠ Connection error: {str(e)}")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
# Check if connections succeeded
|
|
128
|
+
if not self.mongo_connected and not self.redis_connected:
|
|
129
|
+
if isinstance(loading_screen, LoadingScreen):
|
|
130
|
+
loading_screen.hide_spinner()
|
|
131
|
+
loading_screen.set_error(
|
|
132
|
+
"⚠ Connection failed. Please start Docker with 'anastops init' and press 'q' to quit"
|
|
133
|
+
)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
# Success! Update status
|
|
137
|
+
if isinstance(loading_screen, LoadingScreen):
|
|
138
|
+
loading_screen.set_status("Connection established!")
|
|
139
|
+
|
|
140
|
+
# Small delay to show success message
|
|
141
|
+
await asyncio.sleep(0.5)
|
|
142
|
+
|
|
53
143
|
# Start periodic refresh
|
|
54
144
|
self.set_interval(config.refresh_interval, self._refresh_data)
|
|
55
145
|
await self._refresh_data()
|
|
56
|
-
|
|
57
|
-
#
|
|
146
|
+
|
|
147
|
+
# Transition to dashboard
|
|
148
|
+
self.pop_screen()
|
|
58
149
|
await self.push_screen(DashboardScreen())
|
|
59
|
-
|
|
150
|
+
|
|
60
151
|
async def _connect(self) -> None:
|
|
61
152
|
"""Connect to MongoDB and Redis."""
|
|
62
153
|
await self.client.connect()
|
|
63
154
|
health = await self.client.health_check()
|
|
64
155
|
self.mongo_connected = health.get("mongodb", False)
|
|
65
156
|
self.redis_connected = health.get("redis", False)
|
|
66
|
-
|
|
157
|
+
|
|
67
158
|
async def _refresh_data(self) -> None:
|
|
68
159
|
"""Refresh data from databases."""
|
|
69
160
|
if not self.client.is_connected:
|
|
70
161
|
await self._connect()
|
|
71
|
-
|
|
162
|
+
|
|
72
163
|
if self.client.is_connected:
|
|
73
164
|
self.sessions = await self.client.get_sessions()
|
|
74
|
-
|
|
165
|
+
|
|
75
166
|
# Update connection status
|
|
76
167
|
health = await self.client.health_check()
|
|
77
168
|
self.mongo_connected = health.get("mongodb", False)
|
|
78
169
|
self.redis_connected = health.get("redis", False)
|
|
79
|
-
|
|
80
|
-
def action_quit(self) -> None:
|
|
170
|
+
|
|
171
|
+
async def action_quit(self) -> None:
|
|
81
172
|
"""Quit the application."""
|
|
82
173
|
self.exit()
|
|
83
|
-
|
|
174
|
+
|
|
84
175
|
async def action_refresh(self) -> None:
|
|
85
176
|
"""Manually refresh data."""
|
|
86
177
|
await self._refresh_data()
|
|
87
178
|
self.notify("Refreshed")
|
|
88
|
-
|
|
89
|
-
def action_back(self) -> None:
|
|
179
|
+
|
|
180
|
+
async def action_back(self) -> None:
|
|
90
181
|
"""Go back to previous screen (but not from dashboard)."""
|
|
91
182
|
# Don't pop if we're on the dashboard (root screen)
|
|
92
183
|
if len(self.screen_stack) > 2:
|
|
@@ -3,7 +3,7 @@ Data layer for Ranger TUI.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .client import DataClient, get_client
|
|
6
|
-
from .models import Session, Task, Agent, Artifact, SessionReport
|
|
6
|
+
from .models import Session, Task, Agent, Artifact, SessionReport, SessionMetadata
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"DataClient",
|
|
@@ -13,4 +13,5 @@ __all__ = [
|
|
|
13
13
|
"Agent",
|
|
14
14
|
"Artifact",
|
|
15
15
|
"SessionReport",
|
|
16
|
+
"SessionMetadata",
|
|
16
17
|
]
|
|
Binary file
|
|
Binary file
|
|
@@ -124,16 +124,17 @@ class DataClient:
|
|
|
124
124
|
|
|
125
125
|
cursor = self._db.sessions.find(query).sort("updated_at", -1).limit(limit)
|
|
126
126
|
sessions = []
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
async for doc in cursor:
|
|
129
129
|
sessions.append(self._doc_to_session(doc))
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
# Fetch task counts for all sessions in one aggregation
|
|
132
132
|
if sessions:
|
|
133
133
|
session_ids = [s.id for s in sessions]
|
|
134
134
|
task_counts = await self._get_task_counts_by_session(session_ids)
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
task_metrics = await self._get_task_metrics_by_session(session_ids)
|
|
136
|
+
|
|
137
|
+
# Update session metadata with task counts and metrics
|
|
137
138
|
for session in sessions:
|
|
138
139
|
counts = task_counts.get(session.id, {})
|
|
139
140
|
session.metadata.tasks_total = counts.get("total", 0)
|
|
@@ -142,7 +143,11 @@ class DataClient:
|
|
|
142
143
|
session.metadata.tasks_running = counts.get("running", 0)
|
|
143
144
|
session.metadata.tasks_pending = counts.get("pending", 0)
|
|
144
145
|
session.metadata.tasks_queued = counts.get("queued", 0)
|
|
145
|
-
|
|
146
|
+
|
|
147
|
+
metrics = task_metrics.get(session.id, {"total_tokens": 0, "total_cost": 0.0})
|
|
148
|
+
session.metadata.total_tokens = metrics["total_tokens"]
|
|
149
|
+
session.metadata.total_cost = metrics["total_cost"]
|
|
150
|
+
|
|
146
151
|
return sessions
|
|
147
152
|
|
|
148
153
|
async def _get_task_counts_by_session(
|
|
@@ -152,7 +157,7 @@ class DataClient:
|
|
|
152
157
|
"""Get task counts grouped by session and status."""
|
|
153
158
|
if self._db is None:
|
|
154
159
|
return {}
|
|
155
|
-
|
|
160
|
+
|
|
156
161
|
pipeline = [
|
|
157
162
|
{"$match": {"session_id": {"$in": session_ids}}},
|
|
158
163
|
{"$group": {
|
|
@@ -160,20 +165,43 @@ class DataClient:
|
|
|
160
165
|
"count": {"$sum": 1}
|
|
161
166
|
}},
|
|
162
167
|
]
|
|
163
|
-
|
|
168
|
+
|
|
164
169
|
result: dict[str, dict[str, int]] = {}
|
|
165
170
|
async for doc in self._db.tasks.aggregate(pipeline):
|
|
166
171
|
session_id = doc["_id"]["session_id"]
|
|
167
172
|
status = doc["_id"]["status"]
|
|
168
173
|
count = doc["count"]
|
|
169
|
-
|
|
174
|
+
|
|
170
175
|
if session_id not in result:
|
|
171
176
|
result[session_id] = {"total": 0, "completed": 0, "failed": 0, "running": 0, "pending": 0, "queued": 0}
|
|
172
|
-
|
|
177
|
+
|
|
173
178
|
result[session_id][status] = count
|
|
174
179
|
result[session_id]["total"] += count
|
|
175
|
-
|
|
180
|
+
|
|
176
181
|
return result
|
|
182
|
+
|
|
183
|
+
async def _get_task_metrics_by_session(self, session_ids: list[str]) -> dict[str, dict]:
|
|
184
|
+
"""Calculate total tokens and cost from tasks for each session."""
|
|
185
|
+
if self._db is None:
|
|
186
|
+
return {}
|
|
187
|
+
|
|
188
|
+
pipeline = [
|
|
189
|
+
{"$match": {"session_id": {"$in": session_ids}}},
|
|
190
|
+
{"$group": {
|
|
191
|
+
"_id": "$session_id",
|
|
192
|
+
"total_tokens": {"$sum": "$token_usage.total_tokens"},
|
|
193
|
+
"total_cost": {"$sum": "$token_usage.cost"}
|
|
194
|
+
}},
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
cursor = self._db.tasks.aggregate(pipeline)
|
|
198
|
+
results = {}
|
|
199
|
+
async for doc in cursor:
|
|
200
|
+
results[doc["_id"]] = {
|
|
201
|
+
"total_tokens": doc["total_tokens"],
|
|
202
|
+
"total_cost": doc["total_cost"]
|
|
203
|
+
}
|
|
204
|
+
return results
|
|
177
205
|
|
|
178
206
|
async def get_session(self, session_id: str) -> Optional[Session]:
|
|
179
207
|
"""Get a single session by ID."""
|
|
@@ -6,6 +6,7 @@ from .dashboard import DashboardScreen
|
|
|
6
6
|
from .session import SessionScreen
|
|
7
7
|
from .task import TaskScreen
|
|
8
8
|
from .modals import NewSessionModal, NewTaskModal
|
|
9
|
+
from .loading import LoadingScreen
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
"DashboardScreen",
|
|
@@ -13,4 +14,5 @@ __all__ = [
|
|
|
13
14
|
"TaskScreen",
|
|
14
15
|
"NewSessionModal",
|
|
15
16
|
"NewTaskModal",
|
|
17
|
+
"LoadingScreen",
|
|
16
18
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,8 +6,8 @@ from datetime import datetime, UTC
|
|
|
6
6
|
from textual.app import ComposeResult
|
|
7
7
|
from textual.screen import Screen
|
|
8
8
|
from textual.binding import Binding
|
|
9
|
-
from textual.containers import Horizontal
|
|
10
|
-
from textual.widgets import DataTable, Footer
|
|
9
|
+
from textual.containers import Horizontal, Vertical
|
|
10
|
+
from textual.widgets import DataTable, Footer, Static
|
|
11
11
|
|
|
12
12
|
from ranger_tui.widgets import TopBar, Sidebar
|
|
13
13
|
from ranger_tui.theme import format_status
|
|
@@ -15,16 +15,30 @@ from ranger_tui.theme import format_status
|
|
|
15
15
|
|
|
16
16
|
class DashboardScreen(Screen):
|
|
17
17
|
"""Main dashboard screen showing all sessions."""
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
DEFAULT_CSS = """
|
|
20
|
+
.view-title {
|
|
21
|
+
height: 4;
|
|
22
|
+
content-align: center middle;
|
|
23
|
+
text-style: bold;
|
|
24
|
+
background: $surface;
|
|
25
|
+
border: tall $primary;
|
|
26
|
+
color: $primary;
|
|
27
|
+
text-align: center;
|
|
28
|
+
padding: 0 2;
|
|
29
|
+
}
|
|
30
|
+
"""
|
|
31
|
+
|
|
19
32
|
BINDINGS = [
|
|
20
33
|
Binding("enter", "open_session", "Open", show=True),
|
|
21
34
|
Binding("n", "new_session", "New", show=True),
|
|
22
35
|
Binding("escape", "noop", show=False), # Disable escape on dashboard
|
|
23
36
|
]
|
|
24
|
-
|
|
37
|
+
|
|
25
38
|
def compose(self) -> ComposeResult:
|
|
26
39
|
"""Compose the dashboard layout."""
|
|
27
40
|
yield TopBar()
|
|
41
|
+
yield Static("[bold]S E S S I O N S[/]", classes="view-title", markup=True)
|
|
28
42
|
with Horizontal(id="main"):
|
|
29
43
|
yield DataTable(id="sessions-table", cursor_type="row")
|
|
30
44
|
yield Sidebar(title="Summary")
|
|
@@ -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()
|