还没有 Probe。点击右上角「创建 Probe」,由后端生成身份,再把 probe.json 放到家宽机器上。
diff --git a/web/display/src/display.css b/web/display/src/display.css
index 7ae5cbb..420ed80 100644
--- a/web/display/src/display.css
+++ b/web/display/src/display.css
@@ -1205,19 +1205,11 @@ input {
}
/* 版本、tag:等宽的小字 */
-.fact .v.mono-tag,
-.probe-tag {
+.fact .v.mono-tag {
font-family: var(--font-mono);
font-size: 11px;
}
-.probe-tag {
- padding: 0 6px;
- border-radius: 6px;
- background: color-mix(in srgb, var(--app-ink) 6%, transparent);
- color: var(--app-ink-muted);
-}
-
.build-info {
display: flex;
flex-wrap: wrap;
@@ -1227,3 +1219,35 @@ input {
font-family: var(--font-mono);
font-size: 11px;
}
+
+/* 总览卡片里的线路标题:名字太长就省略号,右边只放一个短标签,不换行 */
+.line-top {
+ min-width: 0;
+}
+
+.line-top .line-name {
+ flex: 1 1 auto;
+ min-width: 0;
+ white-space: nowrap;
+}
+
+.line-top .line-name .arrow {
+ flex: none;
+}
+
+.line-top .line-name .name {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.line-top .line-name .flag,
+.line-top .line-name .isp {
+ flex: none;
+}
+
+.line-top .line-range,
+.line-top .chip {
+ flex: none;
+ white-space: nowrap;
+}
diff --git a/web/display/src/pages/Overview.tsx b/web/display/src/pages/Overview.tsx
index e20562a..e4410b1 100644
--- a/web/display/src/pages/Overview.tsx
+++ b/web/display/src/pages/Overview.tsx
@@ -95,18 +95,13 @@ function Card({ agent, host, range, infos }: { agent: AgentCard; host: HostItem
return (
-
- → {infos.get(l.probe_id)?.country ? : null}
- {l.probe_name}
+
+ →
+ {infos.get(l.probe_id)?.country ? : null}
+ {l.probe_name}
+ {infos.get(l.probe_id)?.isp_key ? : null}
- {infos.get(l.probe_id)?.isp_key ? : null}
- {infos.get(l.probe_id)?.tag || infos.get(l.probe_id)?.version ? (
-
- {infos.get(l.probe_id)?.tag || infos.get(l.probe_id)?.version}
-
- ) : null}
- 近 {OVERVIEW_RANGES.find((r) => r.value === range)?.label ?? range}
- {!l.probe_connected && l.online ? Probe 未连接 : null}
+ {!l.probe_connected && l.online ? 未连接 : {OVERVIEW_RANGES.find((r) => r.value === range)?.label ?? range}}
diff --git a/web/shared/api/client.ts b/web/shared/api/client.ts
index 78035d7..04d459d 100644
--- a/web/shared/api/client.ts
+++ b/web/shared/api/client.ts
@@ -83,5 +83,6 @@ export const api = {
get:
(path: string, query?: Query, init?: RequestInit) => request('GET', withQuery(path, query), undefined, init),
post: (path: string, body?: unknown) => request('POST', path, body ?? {}),
patch: (path: string, body: unknown) => request('PATCH', path, body),
+ put: (path: string, body: unknown) => request('PUT', path, body),
del: (path: string) => request('DELETE', path),
}
diff --git a/web/shared/api/schema.ts b/web/shared/api/schema.ts
index eb8765f..a5175ff 100644
--- a/web/shared/api/schema.ts
+++ b/web/shared/api/schema.ts
@@ -851,6 +851,40 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/api/admin/links": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Every probe/agent pair: whether it is linked, the manual override and whether it is connected */
+ get: operations["links_list"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/admin/links/{probe_id}/{agent_id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ /** Force a link on or off, or give it back to the automatic setting (enabled: null) */
+ put: operations["links_set"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/healthz": {
parameters: {
query?: never;
@@ -1250,6 +1284,19 @@ export interface components {
kinds: string[];
last_test: null | components["schemas"]["TestSummaryDto"];
};
+ LinkDto: {
+ probe_id: string;
+ probe_name: string;
+ agent_id: string;
+ agent_name: string;
+ linked: boolean;
+ override: null | boolean;
+ connected: boolean;
+ };
+ LinkMatrixDto: {
+ auto_link: boolean;
+ items: components["schemas"]["LinkDto"][];
+ };
LocalLimitsDto: {
/** Format: double */
max_total_mbps: number | string;
@@ -1594,6 +1641,9 @@ export interface components {
[key: string]: string;
};
};
+ SetLinkRequest: {
+ enabled: null | boolean;
+ };
SettingDto: {
key: string;
type: string;
@@ -4760,6 +4810,107 @@ export interface operations {
};
};
};
+ links_list: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["LinkMatrixDto"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ /** @description Forbidden */
+ 403: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ };
+ };
+ links_set: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ probe_id: string;
+ agent_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["SetLinkRequest"];
+ };
+ };
+ responses: {
+ /** @description OK */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["LinkDto"];
+ };
+ };
+ /** @description Bad Request */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ /** @description Forbidden */
+ 403: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ /** @description Not Found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/problem+json": components["schemas"]["ProblemDto"];
+ };
+ };
+ };
+ };
health_get: {
parameters: {
query?: never;
diff --git a/web/shared/api/types.ts b/web/shared/api/types.ts
index f678e3c..d011294 100644
--- a/web/shared/api/types.ts
+++ b/web/shared/api/types.ts
@@ -19,6 +19,8 @@ export type BuildInfoDto = Narrow
export type NodeBuildDto = Narrow
export type NodeUpdateDto = Narrow
export type ReleaseDto = Narrow
+export type LinkDto = Narrow
+export type LinkMatrixDto = Narrow
export type OverviewDto = Narrow
export type OverviewLineDto = Narrow
export type SeriesDto = Narrow
diff --git a/web/shared/i18n.ts b/web/shared/i18n.ts
index 3f1047c..beb105f 100644
--- a/web/shared/i18n.ts
+++ b/web/shared/i18n.ts
@@ -143,6 +143,7 @@ export function translateCode(code: string): string | undefined {
const SETTINGS: Record = {
'site.public': '匿名访客能不能读展示页的数据(关闭时只有登录的管理员能看)',
+ 'fleet.auto_link': '所有 Probe 自动连接所有 Agent(关闭后只连在「Probe → 线路」里手动选中的)',
'site.show_build': '展示页底部和节点卡片上显示后端、前端、Agent、Probe 的版本、tag 和构建时间',
'retention.raw_days': '持续任务的原始数据块保留多少天',
'retention.test_raw_days': '按需测试的原始数据块保留多少天',