fix: scope inflight snapshots and compact mobile dashboard (#2650)

This commit is contained in:
RockChinQ
2026-10-11 23:08:31 +08:00
committed by GitHub
parent 0b5b4cca59
commit 13446ecfd3
5 changed files with 57 additions and 2 deletions
@@ -1674,6 +1674,15 @@ class MonitoringService(ExecutionDetailsMixin):
}
async def get_inflight_snapshot(self, context: TenantContext, previous: list[dict] | None = None) -> dict:
"""SSE producers outlive the HTTP handler's tenant scope."""
workspace = require_workspace_uuid(context)
tenant_scope = getattr(self.ap.persistence_mgr, 'tenant_scope', None)
if callable(tenant_scope):
async with tenant_scope(workspace):
return await self._get_inflight_snapshot(context, previous)
return await self._get_inflight_snapshot(context, previous)
async def _get_inflight_snapshot(self, context: TenantContext, previous: list[dict] | None = None) -> dict:
"""Bounded Workspace snapshot without dashboard aggregates or token payloads."""
from ....utils.inflight import inflight_hub
@@ -759,6 +759,24 @@ async def test_inflight_snapshot_is_scoped_and_keeps_terminal_transition(engine,
)
session.add(persistence_agent_run.AgentRunEvent(run_id='flight-visible', sequence=1, type='tool.started'))
await session.commit()
# SSE loaders execute after the HTTP handler's tenant scope has exited.
# Cloud persistence rejects implicit execute_async calls in that context.
from contextlib import asynccontextmanager
original_execute = service.ap.persistence_mgr.execute_async
unscoped_execute = AsyncMock(side_effect=AssertionError('Cloud persistence requires an explicit tenant scope'))
@asynccontextmanager
async def tenant_scope(workspace):
assert workspace == WORKSPACE
service.ap.persistence_mgr.execute_async = original_execute
try:
yield
finally:
service.ap.persistence_mgr.execute_async = unscoped_execute
service.ap.persistence_mgr.tenant_scope = tenant_scope
service.ap.persistence_mgr.execute_async = unscoped_execute
snapshot = await service.get_inflight_snapshot(WORKSPACE)
rows = {r['id']: r for r in snapshot['items']}
assert 'flight-secret' not in rows
@@ -201,7 +201,7 @@ export default function MonitoringFilters({
onPipelinesChange([...next]);
};
return (
<div className="grid min-w-0 flex-1 grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5">
<div className="grid min-w-0 flex-1 grid-cols-2 gap-3 lg:grid-cols-5">
{/* Bot Filter */}
<div className="min-w-0 space-y-1.5">
<Label
+1 -1
View File
@@ -223,7 +223,7 @@ function MonitoringPageContent() {
<div className="w-full h-full overflow-y-auto overflow-x-hidden">
{/* Filters and Refresh Button - Sticky */}
{!error && (
<div className="sticky top-0 z-10 -mt-1 pb-5 pt-1 bg-background">
<div className="relative z-10 -mt-1 pb-3 pt-1 bg-background lg:sticky lg:top-0 lg:pb-5">
<div>
<Card className="flex flex-col gap-4 p-4 xl:flex-row xl:items-end">
<MonitoringFilters
+28
View File
@@ -0,0 +1,28 @@
import { expect, test } from '@playwright/test';
import { installLangBotApiMocks } from './fixtures/langbot-api';
for (const width of [375, 430]) {
test(`dashboard filters fit mobile ${width}`, async ({ page }) => {
await page.setViewportSize({ width, height: 812 });
await installLangBotApiMocks(page, { authenticated: true });
await page.goto('/home/monitoring');
const bot = page.getByRole('combobox', { name: 'Bot', exact: true });
await expect(bot).toBeVisible();
const grid = bot.locator('..').locator('..');
expect(
await grid.evaluate(
(el) => getComputedStyle(el).gridTemplateColumns.split(' ').length,
),
).toBe(2);
const box = await grid.boundingBox();
expect(box!.x).toBeGreaterThanOrEqual(0);
expect(box!.x + box!.width).toBeLessThanOrEqual(width);
await expect(
page.getByRole('button', { name: /Reset Filters/i }),
).toBeVisible();
await page.screenshot({
path: `test-results/dashboard-${width}.png`,
fullPage: true,
});
});
}