feat: expand shusub2 dashboard columns
This commit is contained in:
@@ -84,10 +84,12 @@ Environment variables override the config file:
|
||||
`shusub2` opens a single dashboard with Accounts, Keys, Logs, and Errors
|
||||
tables stacked vertically. Press `a`, `k`, `l`, or `e` to focus a table, `/` to
|
||||
filter all four tables, and `r` to refresh all data. The two-line detail area
|
||||
follows the selected row.
|
||||
follows the selected row. Accounts, Keys, Logs, and Errors use green, yellow,
|
||||
magenta, and red section styling respectively.
|
||||
|
||||
The dashboard uses compact columns sized to fit an 80-column terminal without
|
||||
horizontal scrolling:
|
||||
The dashboard sizes every column from the fetched content so wide terminals show
|
||||
complete keys, accounts, and models. On a narrow terminal, a table scrolls
|
||||
horizontally instead of truncating a field:
|
||||
|
||||
```text
|
||||
ACCOUNT | Group | Today | Daily | 5h | 7d | Avail
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "shusub2"
|
||||
version = "0.2.8"
|
||||
version = "0.2.9"
|
||||
description = "Terminal UI for Sub2API account quota and daily usage"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
+10
-37
@@ -19,7 +19,7 @@ from typing import Any
|
||||
|
||||
|
||||
APP_NAME = "shusub2"
|
||||
FALLBACK_VERSION = "0.2.8"
|
||||
FALLBACK_VERSION = "0.2.9"
|
||||
DEFAULT_API_URL = "http://127.0.0.1:18318/api/tui/accounts"
|
||||
DEFAULT_CONFIG_FILE = "~/.config/shusub2/api-url"
|
||||
DEFAULT_STATUS_CONFIG_FILE = "~/.config/shusub2/status-url"
|
||||
@@ -1258,57 +1258,30 @@ def run_textual(
|
||||
yield Footer()
|
||||
|
||||
@staticmethod
|
||||
def configure_table(table: DataTable, columns: tuple[tuple[str, int], ...]) -> None:
|
||||
def configure_table(table: DataTable, columns: tuple[str, ...]) -> None:
|
||||
table.cursor_type = "row"
|
||||
table.zebra_stripes = True
|
||||
for label, width in columns:
|
||||
table.add_column(label, width=width)
|
||||
# Content-sized columns preserve complete values on wide terminals;
|
||||
# narrow terminals use DataTable's horizontal scrolling instead.
|
||||
for label in columns:
|
||||
table.add_column(label)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.configure_table(
|
||||
self.query_one("#accounts", DataTable),
|
||||
(
|
||||
("ACCOUNT", 16),
|
||||
("Group", 5),
|
||||
("Today", 8),
|
||||
("Daily", 10),
|
||||
("5h", 9),
|
||||
("7d", 9),
|
||||
("Avail", 7),
|
||||
),
|
||||
("ACCOUNT", "Group", "Today", "Daily", "5h", "7d", "Avail"),
|
||||
)
|
||||
self.configure_table(
|
||||
self.query_one("#keys", DataTable),
|
||||
(
|
||||
("KEY", 16),
|
||||
("Today", 9),
|
||||
("Tokens", 8),
|
||||
("Req", 6),
|
||||
),
|
||||
("KEY", "Today", "Tokens", "Req"),
|
||||
)
|
||||
self.configure_table(
|
||||
self.query_one("#logs", DataTable),
|
||||
(
|
||||
("LOG KEY", 10),
|
||||
("Account", 10),
|
||||
("Model", 12),
|
||||
("Cost", 7),
|
||||
("Latency", 7),
|
||||
("Time", 11),
|
||||
("Age", 7),
|
||||
),
|
||||
("LOG KEY", "Account", "Model", "Cost", "Latency", "Time", "Age"),
|
||||
)
|
||||
self.configure_table(
|
||||
self.query_one("#errors", DataTable),
|
||||
(
|
||||
("ERR", 4),
|
||||
("Status", 6),
|
||||
("Key", 12),
|
||||
("Account", 11),
|
||||
("Model", 12),
|
||||
("Time", 11),
|
||||
("Age", 8),
|
||||
),
|
||||
("ERR", "Status", "Key", "Account", "Model", "Time", "Age"),
|
||||
)
|
||||
self.refresh_all(refresh=True)
|
||||
self.set_interval(refresh_seconds, self.refresh_accounts)
|
||||
|
||||
+14
-8
@@ -678,7 +678,7 @@ class Sub2APILogsTests(unittest.TestCase):
|
||||
|
||||
|
||||
class DashboardLayoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_dashboard_fits_three_tables_at_80_by_24_and_focuses_each(self) -> None:
|
||||
async def test_dashboard_supports_wide_columns_at_80_by_24_and_focuses_each(self) -> None:
|
||||
from textual.app import App
|
||||
|
||||
mod = load_module()
|
||||
@@ -688,7 +688,7 @@ class DashboardLayoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
"accounts": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "account-one",
|
||||
"name": "account-one-with-a-complete-expanded-dashboard-name",
|
||||
"routing_group": "fast",
|
||||
"provider": "openai",
|
||||
"kind": "pay_as_you_go",
|
||||
@@ -708,9 +708,9 @@ class DashboardLayoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"api_key_name": "key-one",
|
||||
"account_name": "account-one",
|
||||
"model": "gpt-5.5-codex",
|
||||
"api_key_name": "wmy-production-key-with-a-complete-expanded-name",
|
||||
"account_name": "account-one-with-a-complete-expanded-dashboard-name",
|
||||
"model": "gpt-5.5-codex-with-a-complete-expanded-dashboard-model-name",
|
||||
"duration_ms": 1200,
|
||||
"created_at": "2026-07-24T12:00:00+08:00",
|
||||
}
|
||||
@@ -723,8 +723,9 @@ class DashboardLayoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
"_node": "cn",
|
||||
"id": 1,
|
||||
"status_code": 500,
|
||||
"account_name": "account-one",
|
||||
"requested_model": "gpt-5.5-codex",
|
||||
"api_key_name": "wmy-production-key-with-a-complete-expanded-name",
|
||||
"account_name": "account-one-with-a-complete-expanded-dashboard-name",
|
||||
"requested_model": "gpt-5.5-codex-with-a-complete-expanded-dashboard-model-name",
|
||||
"phase": "upstream",
|
||||
"type": "api_error",
|
||||
"created_at": "2026-07-24T12:01:00+08:00",
|
||||
@@ -767,7 +768,12 @@ class DashboardLayoutTests(unittest.IsolatedAsyncioTestCase):
|
||||
tables = [screen.query_one(selector) for selector in ("#accounts", "#keys", "#logs", "#errors")]
|
||||
for table in tables:
|
||||
self.assertGreaterEqual(table.region.height, 4)
|
||||
self.assertLessEqual(table.virtual_size.width, table.region.width)
|
||||
logs_table = screen.query_one("#logs")
|
||||
errors_table = screen.query_one("#errors")
|
||||
self.assertGreater(logs_table.virtual_size.width, logs_table.region.width)
|
||||
self.assertGreater(errors_table.virtual_size.width, errors_table.region.width)
|
||||
self.assertGreater(logs_table.max_scroll_x, 0)
|
||||
self.assertGreater(errors_table.max_scroll_x, 0)
|
||||
self.assertEqual(len({table.styles.background for table in tables}), 4)
|
||||
self.assertIn("wmy", str(screen.query_one("#summary").render()))
|
||||
for key, expected_id in (("k", "keys"), ("l", "logs"), ("e", "errors"), ("a", "accounts")):
|
||||
|
||||
Reference in New Issue
Block a user