Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions internal/webassets/dist/assets/index-DYhF9AXz.css

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion internal/webassets/dist/assets/index-NUtcblDI.css

This file was deleted.

4 changes: 2 additions & 2 deletions internal/webassets/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<script src="/theme-init.js"></script>
<title>FlowLens</title>
<script type="module" crossorigin src="/assets/index-B9YDBDoG.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-NUtcblDI.css">
<script type="module" crossorigin src="/assets/index-DlKVpUwn.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DYhF9AXz.css">
</head>
<body>
<div id="root"></div>
Expand Down
106 changes: 69 additions & 37 deletions test/e2e/stage4-demo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({

await page.setViewportSize({ width: 1440, height: 1000 });
await page.goto("./");
await expect(page.locator("main.app")).toHaveAttribute(
await expect(page.locator(".app-shell")).toHaveAttribute(
"data-source-mode",
"demo",
);
Expand All @@ -30,7 +30,38 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
const themeToggleBox = await page.locator(".theme-toggle").boundingBox();
expect(liveStatusBox).not.toBeNull();
expect(themeToggleBox).not.toBeNull();
expect(themeToggleBox!.x - (liveStatusBox!.x + liveStatusBox!.width)).toBeGreaterThanOrEqual(18);
expect(
themeToggleBox!.x - (liveStatusBox!.x + liveStatusBox!.width),
).toBeGreaterThanOrEqual(6);
const layers = await page.evaluate(() => ({
header: Number.parseInt(
getComputedStyle(document.querySelector(".topbar")!).zIndex,
10,
),
range: Number.parseInt(
getComputedStyle(document.querySelector(".range-wrap")!).zIndex,
10,
),
}));
expect(layers.header).toBeGreaterThan(layers.range);

const rangeBeforeScroll = await page.locator(".range-wrap").boundingBox();
expect(rangeBeforeScroll).not.toBeNull();
await page.evaluate(
(scrollTop) => window.scrollTo(0, scrollTop),
Math.max(0, rangeBeforeScroll!.y - 24),
);
const overlapOwner = await page.evaluate(() => {
const header = document.querySelector(".topbar")!.getBoundingClientRect();
const range = document
.querySelector(".range-wrap")!
.getBoundingClientRect();
return document
.elementFromPoint(range.left + range.width / 2, header.bottom - 4)
?.closest(".topbar")?.className;
});
expect(overlapOwner).toContain("topbar");
await page.evaluate(() => window.scrollTo(0, 0));
await expect(page.getByRole("button", { name: "跟随系统" })).toBeVisible();
await expect(page.getByRole("button", { name: "退出" })).toBeVisible();
await expect(page.getByRole("navigation")).toHaveCount(0);
Expand All @@ -43,9 +74,7 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
await expect(page.getByRole("heading", { name: "流量总览" })).toHaveClass(
/page-title/,
);
await expect(
page.getByRole("heading", { name: "实时吞吐" }),
).toBeVisible();
await expect(page.getByRole("heading", { name: "实时吞吐" })).toBeVisible();
await expect(
page.getByRole("heading", { name: "实时目标分析" }),
).toBeVisible();
Expand All @@ -64,9 +93,7 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
);
expect(liveMetricBoxes).toHaveLength(6);
expect(
liveMetricBoxes.every(
(box) => Math.abs(box.y - liveMetricBoxes[0]!.y) < 2,
),
liveMetricBoxes.every((box) => Math.abs(box.y - liveMetricBoxes[0]!.y) < 2),
).toBe(true);
await expect(page.getByText(/占全局 51\.6%/)).toBeVisible();
const topologyBox = await page.locator(".topology-panel").boundingBox();
Expand All @@ -87,21 +114,29 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
const number = (selector: string, property: string) => {
const element = document.querySelector(selector);
if (!(element instanceof HTMLElement)) return null;
return Number.parseFloat(getComputedStyle(element).getPropertyValue(property));
return Number.parseFloat(
getComputedStyle(element).getPropertyValue(property),
);
};
return {
dashboardGap: number(".dashboard", "gap"),
panelPadding: number(".hero-panel", "padding-top"),
panelRadius: number(".hero-panel", "border-top-left-radius"),
pageTitleSize: number(".page-title", "font-size"),
panelTitleSize: number(".panel-head h2", "font-size"),
summaryValueSize: number(".chart-summary .chart-value strong", "font-size"),
chartHeight: document.querySelector(".chart-shell")?.getBoundingClientRect()
.height,
topologyHeight: document.querySelector(".topology")?.getBoundingClientRect()
.height,
targetHeight: document.querySelector(".target-item")?.getBoundingClientRect()
.height,
summaryValueSize: number(
".chart-summary .chart-value strong",
"font-size",
),
chartHeight: document
.querySelector(".chart-shell")
?.getBoundingClientRect().height,
topologyHeight: document
.querySelector(".topology")
?.getBoundingClientRect().height,
targetHeight: document
.querySelector(".target-item")
?.getBoundingClientRect().height,
};
});
expect(density.dashboardGap).toBeLessThanOrEqual(14);
Expand All @@ -113,8 +148,14 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
expect(density.chartHeight).toBeLessThanOrEqual(260);
expect(density.topologyHeight).toBeLessThanOrEqual(280);
expect(density.targetHeight).toBeLessThanOrEqual(60);
const firstTarget = await page.locator(".targets-panel .target-item").nth(0).boundingBox();
const secondTarget = await page.locator(".targets-panel .target-item").nth(1).boundingBox();
const firstTarget = await page
.locator(".targets-panel .target-item")
.nth(0)
.boundingBox();
const secondTarget = await page
.locator(".targets-panel .target-item")
.nth(1)
.boundingBox();
expect(firstTarget).not.toBeNull();
expect(secondTarget).not.toBeNull();
expect(Math.abs(firstTarget!.x - secondTarget!.x)).toBeLessThan(2);
Expand All @@ -128,12 +169,8 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");

await page.getByRole("button", { name: "今天" }).click();
await expect(
page.getByRole("heading", { name: "历史流量" }),
).toBeVisible();
await expect(
page.getByText("SQLite 聚合 · 自动选择分辨率"),
).toHaveCount(0);
await expect(page.getByRole("heading", { name: "历史流量" })).toBeVisible();
await expect(page.getByText("SQLite 聚合 · 自动选择分辨率")).toHaveCount(0);
const historyDescription = page.getByRole("button", {
name: "查看“历史流量”说明",
});
Expand All @@ -153,9 +190,7 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
const historyConfidenceBox = await page
.locator(".confidence-panel")
.boundingBox();
const historyTargetsBox = await page
.locator(".targets-panel")
.boundingBox();
const historyTargetsBox = await page.locator(".targets-panel").boundingBox();
expect(historyDashboardBox).not.toBeNull();
expect(historyTopologyBox).not.toBeNull();
expect(historyConfidenceBox).not.toBeNull();
Expand All @@ -166,9 +201,9 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
expect(historyConfidenceBox!.y).toBeGreaterThan(
historyTopologyBox!.y + historyTopologyBox!.height,
);
expect(
Math.abs(historyConfidenceBox!.y - historyTargetsBox!.y),
).toBeLessThan(2);
expect(Math.abs(historyConfidenceBox!.y - historyTargetsBox!.y)).toBeLessThan(
2,
);
expect(
Math.abs(historyConfidenceBox!.height - historyTargetsBox!.height),
).toBeLessThan(2);
Expand Down Expand Up @@ -207,9 +242,7 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
await page.getByRole("button", { name: "端口", exact: true }).click();
await expect(page.getByLabel("端口分布")).toBeVisible();
await expect(page.getByLabel(/流量拓扑/)).toHaveCount(0);
await page
.getByRole("button", { name: "Endpoint", exact: true })
.click();
await page.getByRole("button", { name: "Endpoint", exact: true }).click();
await expect(page.getByLabel(/流量拓扑/)).toBeVisible();

await page.getByRole("button", { name: "7 天" }).click();
Expand Down Expand Up @@ -282,7 +315,8 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
expect(desktopClose).not.toBeNull();
expect(
Math.abs(
desktopAliasInput!.y + desktopAliasInput!.height / 2 -
desktopAliasInput!.y +
desktopAliasInput!.height / 2 -
(desktopAliasActions!.y + desktopAliasActions!.height / 2),
),
).toBeLessThan(2);
Expand Down Expand Up @@ -330,9 +364,7 @@ test("offline Demo exposes one rich responsive statistics dashboard", async ({
expect(sourceTwo!.x).toBeGreaterThan(sourceOne!.x + sourceOne!.width);
expect(gateway!.y).toBeGreaterThan(sourceOne!.y + sourceOne!.height);
expect(Math.abs(gateway!.width - topology!.width)).toBeLessThan(2);
expect(firstTopologyTarget!.y).toBeGreaterThan(
gateway!.y + gateway!.height,
);
expect(firstTopologyTarget!.y).toBeGreaterThan(gateway!.y + gateway!.height);
await expect(page.locator(".dashboard")).toHaveCSS(
"grid-template-columns",
/310px|300px|1fr/,
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/stage4-production-csp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ test("production bundle works with the shipped CSP and named SSE events", async
});

await page.goto("http://127.0.0.1:4175/");
await expect(page.locator("main.app")).toHaveAttribute(
await expect(page.locator(".app-shell")).toHaveAttribute(
"data-source-mode",
"app",
);
Expand Down
8 changes: 4 additions & 4 deletions web/src/app/Shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function Shell({
}) {
const logoutLabel = logoutFailed ? "退出失败,请重试" : "退出";
return (
<main className="app" data-source-mode={sourceMode}>
<div className="app-shell" data-source-mode={sourceMode}>
<header className="topbar">
<a
className="brand"
Expand All @@ -35,7 +35,7 @@ export function Shell({
<div className="brand-mark" aria-hidden="true" />
<div>
<strong>FlowLens</strong>
<span className="eyebrow">edge telemetry</span>
<span className="eyebrow">network telemetry</span>
</div>
</a>
<div className="top-actions">
Expand Down Expand Up @@ -65,8 +65,8 @@ export function Shell({
) : null}
</div>
</header>
{children}
<main className="app">{children}</main>
<AppFooter version={version} />
</main>
</div>
);
}
6 changes: 6 additions & 0 deletions web/src/features/dashboard/test/DashboardPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ describe("DashboardPage", () => {
"href",
"https://github.com/Willxup/flowlens",
);
const globalHeader = brand.closest("header");
const main = screen.getByRole("main");
expect(globalHeader).toHaveClass("topbar");
expect(main).toHaveClass("app");
expect(main).not.toContainElement(globalHeader);
expect(globalHeader?.parentElement).toBe(main.parentElement);
expect(screen.getByText("采集正常")).toBeInTheDocument();
expect(await screen.findByText("© 2026")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "License" })).toHaveAttribute(
Expand Down
4 changes: 2 additions & 2 deletions web/src/styles/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ select:focus-visible {
width: max-content;
max-width: min(320px, calc(100vw - 20px));
position: fixed;
z-index: 100;
z-index: var(--layer-tooltip);
display: block;
padding: 8px 11px;
border: 1px solid var(--line-strong);
Expand Down Expand Up @@ -629,7 +629,7 @@ select:focus-visible {
.dialog-backdrop {
position: fixed;
inset: 0;
z-index: 50;
z-index: var(--layer-modal);
display: grid;
place-items: center;
padding: 18px;
Expand Down
Loading
Loading