feat: add compact unified dashboard
This commit is contained in:
@@ -668,5 +668,119 @@ class Sub2APILogsTests(unittest.TestCase):
|
||||
self.assertIn("logs token not configured", err.getvalue())
|
||||
|
||||
|
||||
class DashboardLayoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_dashboard_fits_three_tables_at_80_by_24_and_focuses_each(self) -> None:
|
||||
from textual.app import App
|
||||
|
||||
mod = load_module()
|
||||
mod.fetch_payload = lambda *args, **kwargs: {
|
||||
"source_name": "test",
|
||||
"totals": {"total_accounts": 1, "usable_accounts": 1, "today_cost_usd": 1},
|
||||
"accounts": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "account-one",
|
||||
"routing_group": "fast",
|
||||
"provider": "openai",
|
||||
"kind": "pay_as_you_go",
|
||||
"status": "ok",
|
||||
}
|
||||
],
|
||||
}
|
||||
mod.fetch_optional_payload = lambda *args, **kwargs: ({}, "")
|
||||
mod.fetch_logs_payload = lambda *args, **kwargs: {
|
||||
"data": {
|
||||
"total": 1,
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"api_key_name": "key-one",
|
||||
"account_name": "account-one",
|
||||
"model": "gpt-5.5-codex",
|
||||
"duration_ms": 1200,
|
||||
"created_at": "2026-07-24T12:00:00+08:00",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
mod.fetch_merged_errors = lambda *args, **kwargs: {
|
||||
"items": [
|
||||
{
|
||||
"_node": "cn",
|
||||
"id": 1,
|
||||
"status_code": 500,
|
||||
"account_name": "account-one",
|
||||
"requested_model": "gpt-5.5-codex",
|
||||
"phase": "upstream",
|
||||
"type": "api_error",
|
||||
"created_at": "2026-07-24T12:01:00+08:00",
|
||||
}
|
||||
],
|
||||
"sources": {"cn": {"ok": True, "total": 1}},
|
||||
}
|
||||
|
||||
captured = {}
|
||||
original_run = App.run
|
||||
App.run = lambda self, *args, **kwargs: captured.setdefault("app", self)
|
||||
try:
|
||||
rc = mod.run_textual(
|
||||
"https://example/accounts",
|
||||
"",
|
||||
"https://example/usage",
|
||||
"token",
|
||||
"https://example/cn",
|
||||
"https://example/us",
|
||||
999,
|
||||
999,
|
||||
999,
|
||||
1,
|
||||
100,
|
||||
100,
|
||||
"24h",
|
||||
)
|
||||
finally:
|
||||
App.run = original_run
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
app = captured["app"]
|
||||
async with app.run_test(size=(80, 24)) as pilot:
|
||||
await pilot.pause()
|
||||
await pilot.pause()
|
||||
screen = app.screen
|
||||
self.assertEqual(type(screen).__name__, "DashboardScreen")
|
||||
self.assertEqual(screen.query_one("#filter").region.height, 1)
|
||||
self.assertEqual(screen.query_one("#detail").region.height, 2)
|
||||
for selector in ("#accounts", "#logs", "#errors"):
|
||||
table = screen.query_one(selector)
|
||||
self.assertGreaterEqual(table.region.height, 5)
|
||||
self.assertLessEqual(table.virtual_size.width, table.region.width)
|
||||
for key, expected_id in (("l", "logs"), ("e", "errors"), ("a", "accounts")):
|
||||
await pilot.press(key)
|
||||
await pilot.pause()
|
||||
self.assertEqual(screen.focused.id, expected_id)
|
||||
|
||||
|
||||
class PageSelectionTests(unittest.TestCase):
|
||||
def test_dedicated_page_flags_are_mutually_exclusive(self) -> None:
|
||||
mod = load_module()
|
||||
old_token = os.environ.pop("SHUSUB2_LOGS_TOKEN", None)
|
||||
old_file = os.environ.get("SHUSUB2_LOGS_TOKEN_FILE")
|
||||
os.environ["SHUSUB2_LOGS_TOKEN_FILE"] = "/nonexistent/shusub2/logs-token"
|
||||
err = io.StringIO()
|
||||
try:
|
||||
with contextlib.redirect_stderr(err):
|
||||
rc = mod.main(["--accounts", "--logs", "--no-version-check"])
|
||||
finally:
|
||||
if old_token is not None:
|
||||
os.environ["SHUSUB2_LOGS_TOKEN"] = old_token
|
||||
if old_file is None:
|
||||
os.environ.pop("SHUSUB2_LOGS_TOKEN_FILE", None)
|
||||
else:
|
||||
os.environ["SHUSUB2_LOGS_TOKEN_FILE"] = old_file
|
||||
|
||||
self.assertEqual(rc, 2)
|
||||
self.assertIn("choose only one", err.getvalue())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user