Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
654aa3b
refactor(server): own the service monitor check transition in one module
ZingerLittleBee Jul 10, 2026
8b0dbba
refactor(server): share one credential policy across HTTP and WebSock…
ZingerLittleBee Jul 10, 2026
8a6d372
refactor(agent): own capability state and lifecycle in one runtime au…
ZingerLittleBee Jul 10, 2026
c4c53da
docs: record monitor check and capability lifecycle fixes
ZingerLittleBee Jul 11, 2026
c4d9efe
refactor(server): own scheduled task lifecycle in one module
ZingerLittleBee Jul 11, 2026
cc97ddf
refactor(web): centralize server catalog projections
ZingerLittleBee Jul 11, 2026
b9f611a
refactor(server): own agent desired-state sync in one reconciler
ZingerLittleBee Jul 11, 2026
775aa5c
refactor(agent): own connection command state in one runtime
ZingerLittleBee Jul 11, 2026
6d023e6
refactor(web): own server detail navigation policy in one module
ZingerLittleBee Jul 11, 2026
516fb91
refactor(server): dedupe reconcile warn wrappers into the reconciler
ZingerLittleBee Jul 11, 2026
38e0508
docs: record desired-state reconcile fixes in the changelog
ZingerLittleBee Jul 11, 2026
7a039ac
test(server): tolerate ip_quality_sync in first-connect noise filters
ZingerLittleBee Jul 11, 2026
594b8f8
docs: document capability-change desired-state resync
ZingerLittleBee Jul 11, 2026
a985c68
refactor(server): dedupe desired-state reconciler internals
ZingerLittleBee Jul 11, 2026
ab1b88a
refactor(agent): narrow ConnectionRuntime field visibility
ZingerLittleBee Jul 11, 2026
a6c6702
test(server): share the first-connect noise predicate across suites
ZingerLittleBee Jul 11, 2026
1d24c22
refactor(web): type detail range keys as a RangeKey union
ZingerLittleBee Jul 11, 2026
d8bbeea
refactor: type browser update frames as a LiveMetrics partial projection
ZingerLittleBee Jul 11, 2026
bf6b62c
fix(common): keep name in update frames for shipped iOS decoders
ZingerLittleBee Jul 11, 2026
d3a3d27
refactor(server): single-source the streaming upload pending-request …
ZingerLittleBee Jul 11, 2026
e3e5203
refactor(server): unify the two IP-change reaction paths
ZingerLittleBee Jul 11, 2026
ba27e14
refactor(web): route network_probe_update through subscribeBrowserMes…
ZingerLittleBee Jul 11, 2026
58c47d3
refactor(server): share the browser-WS gate and mobile-expiry scaffold
ZingerLittleBee Jul 11, 2026
e3f190f
refactor(web): extract the Metrics-tab chart model into lib/metric-ch…
ZingerLittleBee Jul 11, 2026
bc66a51
refactor(web): share one isoWindow helper across range queries
ZingerLittleBee Jul 11, 2026
4ec3477
style(web): settle lint on chart model and ws test files
ZingerLittleBee Jul 11, 2026
98860a1
fix(web): use existing chart_gpu_usage key for the GPU usage chart title
ZingerLittleBee Jul 11, 2026
b799f1c
refactor(server): generate hourly rollup SQL from a metric column des…
ZingerLittleBee Jul 11, 2026
25a8452
refactor(server): move raw/hourly table selection into the rollup policy
ZingerLittleBee Jul 11, 2026
972e473
refactor(server): route alert metric reads through the rollup descriptor
ZingerLittleBee Jul 11, 2026
e3e868d
refactor(server): normalize public metrics mapping through one row shape
ZingerLittleBee Jul 11, 2026
fcf7fcd
refactor(web): render metric charts from a display descriptor list
ZingerLittleBee Jul 11, 2026
61e841e
docs: record the rollup descriptor decision and glossary terms
ZingerLittleBee Jul 11, 2026
90bbbbc
refactor(agent): extract the Docker availability FSM into a subsystem
ZingerLittleBee Jul 11, 2026
ce81791
refactor(agent): put a MetricsSource seam behind report assembly
ZingerLittleBee Jul 11, 2026
864eed8
refactor(server): funnel effective-capability checks through capabili…
ZingerLittleBee Jul 11, 2026
5c1d03d
refactor(web): generate capability bit definitions from ALL_CAPABILITIES
ZingerLittleBee Jul 11, 2026
3acd704
docs: sync capability codegen note into AGENTS.md
ZingerLittleBee Jul 11, 2026
a3e8f6e
refactor(server): name the IpChange transition helper
ZingerLittleBee Jul 11, 2026
fdd741b
test(server): pin IpChange change-vs-transition semantics
ZingerLittleBee Jul 11, 2026
d10c83d
fix(server): make non-alertable metric columns unresolvable in alerts
ZingerLittleBee Jul 11, 2026
013bfe8
docs: align rollup wording with window-maximum semantics
ZingerLittleBee Jul 11, 2026
b3281be
docs(agent): declare collector elapsed-window stamping order
ZingerLittleBee Jul 11, 2026
c8ab56e
fix(agent): iterate network values directly to satisfy for_kv_map
ZingerLittleBee Jul 11, 2026
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ RBAC: Admin (full access) vs Member (read-only). `require_admin` middleware on w
- **API responses**: All endpoints return `Json<ApiResponse<T>>` wrapping data in `{ data: T }`
- **OpenAPI**: Every endpoint annotated with `#[utoipa::path]`, every DTO with `#[derive(ToSchema)]`. Swagger UI at `/swagger-ui/`
- **Config**: Figment loads TOML then env vars. Prefix `SERVERBEE_`, nested separator `__` (double underscore). Example: `SERVERBEE_ADMIN__PASSWORD` → `admin.password`. **When adding/changing env vars, update `ENV.md` and `apps/docs/content/docs/{en,zh}/configuration.mdx` simultaneously.**
- **Capabilities**: u32 bitmask, defined in `crates/common/src/constants.rs` — `CAP_TERMINAL=1, CAP_EXEC=2, CAP_UPGRADE=4, CAP_PING_ICMP=8, CAP_PING_TCP=16, CAP_PING_HTTP=32, CAP_FILE=64, CAP_DOCKER=128, CAP_SECURITY_EVENTS=256, CAP_FIREWALL_BLOCK=512, CAP_IP_QUALITY=1024`. Default `CAP_DEFAULT=1852` (upgrade + ICMP/TCP/HTTP ping + security events + firewall blocklist + IP quality). **Capabilities are agent-owned**: the agent host computes its own bitmask from the `[capabilities]` config block (allow/deny over `CAP_DEFAULT`, see `compute_local_capabilities`) plus optional `--allow-cap`/`--deny-cap` CLI flags, and reports it in `SystemInfo.agent_local_capabilities`. The server **cannot** modify an agent's capabilities — it persists the reported value into `servers.capabilities` purely as a display mirror (`update_capabilities_mirror`) and gates control-plane requests on it. There is no server-side capability toggle; the web/iOS UI is read-only.
- **Capabilities**: u32 bitmask, defined in `crates/common/src/constants.rs` — `CAP_TERMINAL=1, CAP_EXEC=2, CAP_UPGRADE=4, CAP_PING_ICMP=8, CAP_PING_TCP=16, CAP_PING_HTTP=32, CAP_FILE=64, CAP_DOCKER=128, CAP_SECURITY_EVENTS=256, CAP_FIREWALL_BLOCK=512, CAP_IP_QUALITY=1024`. Default `CAP_DEFAULT=1852` (upgrade + ICMP/TCP/HTTP ping + security events + firewall blocklist + IP quality). **Capabilities are agent-owned**: the agent host computes its own bitmask from the `[capabilities]` config block (allow/deny over `CAP_DEFAULT`, see `compute_local_capabilities`) plus optional `--allow-cap`/`--deny-cap` CLI flags, and reports it in `SystemInfo.agent_local_capabilities`. The server **cannot** modify an agent's capabilities — it persists the reported value into `servers.capabilities` purely as a display mirror (`update_capabilities_mirror`) and gates control-plane requests on it. There is no server-side capability toggle; the web/iOS UI is read-only. Web bit definitions (`apps/web/src/lib/capability-bits.generated.ts`) are generated from `ALL_CAPABILITIES` — run `bun run generate:capabilities` in `apps/web` after changing capability metadata; iOS still mirrors by hand.
- **Migrations**: sea-orm migrations in `crates/server/src/migration/`. Run automatically on startup. **Only implement `up()` — leave `down()` as a no-op (`Ok(())`).** Migrations are not reversible to avoid accidental data loss.

### Frontend
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Security

- **WebSocket handlers share the HTTP credential policy** -- The browser, terminal, and Docker-logs WebSocket endpoints each carried a private copy of the credential ladder (cookie → API key → Bearer), so precedence, forced-password, and mobile-expiry rules could silently drift between four copies. All transports now resolve through one shared policy in the auth middleware. Two edge cases tightened along the way: a user flagged for a forced password change can no longer fall through from their session cookie to a weaker credential on WebSocket upgrades, and a fixed-lifetime mobile session now enforces its expiry on long-lived sockets whichever header carried it

### Fixed

- **Manual service-monitor checks follow the full check policy** -- The "Check now" endpoint assembled the check transition through its own code path: the result record and the monitor state were written in two separate statements, a slow check could overlap the scheduled one for the same monitor, and the maintenance gate and notifications were skipped entirely -- a manual success after failures cleared the failing state and permanently swallowed the recovery notification. Both callers now execute one shared transition: record and state commit atomically, a concurrent check for the same monitor is refused with `409 Conflict`, and failure/recovery notifications fire under the same maintenance-window rules regardless of who triggered the check

- **Temporary capability grants now bound running work** -- A temporary `terminal` grant's expiry blocked new sessions but left live PTY sessions running indefinitely, and the security-events pipeline was started once at boot from the permanent capability set, so a temporary `security_events` grant could never start it (nor could a persisted grant enable it across a restart) and expiry never stopped it. Capability state is now owned by one process-wide authority on the agent: expiry or revocation closes live terminal sessions immediately, and the security pipeline starts and stops as the capability becomes effective or lapses

- **Capability changes now resync everything the agent executes for the server** -- When an agent reported a capability change mid-connection (a grant expiring, a config edit followed by `SIGHUP`), the server updated its mirror but never re-derived what the agent should be running: a host that revoked its ping capabilities kept executing the last-synced ping task list, and a revoked firewall capability left ServerBee's nftables entries in place until the next reconnect. Every capability change now triggers a full desired-state reconcile -- ping tasks, network probes, IP-quality services, and the firewall blocklist are re-filtered against the new capability set and pushed (or reset) immediately

- **Editing a network probe target now reaches connected agents** -- Creating or deleting a global probe target and changing the probe settings pushed a fresh `NetworkProbeSync`, but editing an existing target's address or type did not, so connected agents kept probing the old target until they reconnected. Target updates now push the refreshed target list like every other probe mutation

- **A failed firewall blocklist reset is no longer recorded as done** -- When an agent acknowledged a blocklist reset with `ok=false` (for example `nft` missing or permission denied), the server cleared its record of what the agent had applied anyway, so operators saw the host as wiped while the rules stayed live. A successful ack is now the only evidence that clears the per-server apply state; a failed ack keeps the last-known state visible and the wipe is retried on the next connection reconcile

## [1.0.0-alpha.11] - 2026-07-03

### Security
Expand Down
4 changes: 4 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
- **Network tab** — the server-detail tab hosting the network quality detail. Admin gets the full experience (chart, traceroute, target management, CSV export); the public status variant is the redacted summary (targets + anomalies only).
- **Server detail tabs** — the top-level structure of the server detail page: Metrics (default, includes the cost/traffic/uptime overview blocks), Network, Traffic, Security, IP Quality. Tab and time range are URL-driven (`?tab=`, `?range=`); the `range` window is shared across tabs.
- **Standalone public network page** — `/status/network/$serverId`; exists only as a fallback for status pages configured with `show_server_detail=false` but `show_network=true` (see ADR-0001).
- **Live metrics (实时指标帧)** — the partial projection of a server carried by WS `update` frames: the fields an agent report can populate (usage, speeds, loads, connection counts), plus `name` kept purely for decoder compatibility. Static facts are absent from the wire; clients keep their cached values when merging (see ADR-0002). _Avoid_: partial status, update payload.
- **Full server status** — the complete ~35-field snapshot (`ServerStatus`) carried by `full_sync` and REST. The only sources allowed to seed the catalog or overwrite static facts (totals, os, tags, geo, enrollment).
- **Rollup policy (降采样策略)** — how raw metric records compress over time: per-column aggregation (AVG vs window MAX), the raw/hourly table switch point, and which columns alerts may read. Owned by `service::rollup`; declared per column in the **metric column descriptor** (see ADR-0003). _Avoid_: aggregation logic, downsampling code.
- **Metric column descriptor** — the one-row-per-column table (`METRIC_COLUMNS`) declaring a scalar metric's SQL name, rollup aggregation, alert rule type, and typed accessor. Adding a scalar metric means adding one descriptor row (plus entity/migration, which the compiler enforces). The web analogue is `METRIC_CHART_SPECS` for chart display.
12 changes: 0 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/docs/content/docs/en/capabilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ Grants are persisted to `<state_dir>/capability_grants.json` (default `/var/lib/
- **Survives restarts** — if the agent restarts mid-window, the grant is reloaded and remains active for the rest of its original window.
- **Expires at the original deadline** — restarting does not extend a grant. A grant for `30m` expires 30 minutes after it was issued regardless of how many times the agent restarts in between.
- **Fails safe to OFF** — if the grants file is missing, empty, corrupt, or written by an unknown schema version, the agent treats it as "no grants" and the capability stays off. A temporary grant can never *strengthen* the permanent set; it can only flip an otherwise-off bit on for its window.
- **Expiry bounds running work, not just new requests** — when a `terminal` grant expires or is revoked, live terminal sessions opened under it are closed immediately (the browser sees the session end), and a `security_events` grant starts the agent's security pipeline the moment it becomes effective and stops it when the window closes — including grants issued while the agent was already running.

### What the server sees

Expand All @@ -146,6 +147,7 @@ When a grant becomes active (or expires/is revoked), the agent re-reports its ef
- The change is mirrored into `servers.capabilities` and broadcast to browsers, so the UI updates in real time.
- The transition is written to the [audit log](/en/docs/admin) as `capability_temporarily_granted`, `capability_grant_expired`, or `capability_grant_revoked`.
- A temporary grant of a **high-risk** capability (`terminal`, `exec`, `file`, or `docker`) also evaluates the event-driven `capability_grant_detected` [alert rule](/en/docs/alerts). Expiry, revoke, and low-risk grants are audited but do not fire an alert.
- Everything the agent executes for the server is re-synced against the new capability set: ping tasks are re-filtered, network probe targets and IP-quality services are re-pushed, and the firewall blocklist is reset (and re-sent only while `firewall_block` is still effective). Revoking a ping capability stops those probes immediately, and revoking `firewall_block` removes ServerBee's nftables rules from the host without waiting for a reconnect.

<Callout type="warn">
A grant issued while the agent is **disconnected** from the server is applied locally and shows up on reconnect, but it does **not** produce a `granted` audit entry or alert. The server only sees the post-reconnect capability state, not the moment of transition, so it cannot distinguish a freshly-granted capability from one that was already active. Plan around this if you rely on the alert as a tripwire.
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/content/docs/zh/capabilities.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ serverbee-agent grants
- **跨重启存活** —— 若 Agent 在时间窗中重启,授予会被重新加载,并在原始时间窗内保持生效。
- **在原始截止时刻过期** —— 重启不会延长授予。`30m` 的授予在发起后 30 分钟过期,无论期间 Agent 重启多少次。
- **失败时安全归零(OFF)** —— 若 grants 文件缺失、为空、损坏,或由未知 schema 版本写入,Agent 会将其视作「无授予」,对应能力保持关闭。临时授予永远不会*增强*永久能力集,它只能在自己的时间窗内把一个原本关闭的位翻为开启。
- **过期约束的是运行中的工作,而不只是新请求** —— `terminal` 授予过期或被撤销时,在该授予下打开的终端会话会被立即关闭(浏览器端会看到会话结束);`security_events` 授予生效的那一刻就会启动 Agent 的安全事件管道,时间窗结束时将其停止 —— 包括在 Agent 已运行期间发起的授予。

### Server 看到什么

Expand All @@ -146,6 +147,7 @@ serverbee-agent grants
- 变更会镜像到 `servers.capabilities` 并广播给浏览器,UI 实时更新。
- 该转换会写入 [审计日志](/zh/docs/admin),动作为 `capability_temporarily_granted`、`capability_grant_expired` 或 `capability_grant_revoked`。
- 对**高危**能力(`terminal`、`exec`、`file`、`docker`)的临时授予还会评估事件驱动的 `capability_grant_detected` [告警规则](/zh/docs/alerts)。过期、撤销以及低风险授予会被审计,但不触发告警。
- Agent 为该服务器执行的全部工作会按新的能力集重新同步:ping 任务重新过滤、网络探测目标与 IP 质量服务重新下发、防火墙黑名单先重置(仅当 `firewall_block` 仍生效时才会重新推送)。撤销 ping 能力会立即停止对应探测;撤销 `firewall_block` 会立刻移除 ServerBee 在主机上的 nftables 规则,无需等待重连。

<Callout type="warn">
在 Agent 与 Server **断连**期间发起的授予,会在本地生效并在重连后展示出来,但**不会**产生 `granted` 审计记录或告警。Server 只能看到重连后的能力状态,看不到转换发生的瞬间,因此无法区分「刚刚授予」与「本就生效」的能力。若你把该告警当作触发器(tripwire)使用,请考虑这一点。
Expand Down
55 changes: 55 additions & 0 deletions apps/ios/ServerBeeTests/BrowserMessageDecodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,61 @@ final class BrowserMessageDecodingTests: XCTestCase {
XCTAssertEqual(servers[0].country, "US")
}

/// Regression: since ADR-0002 the server's `update` frame carries the
/// LiveMetrics partial projection — `id` + `name` plus live fields only,
/// with every static key (mem_total, os, tags, has_token, ...) absent.
/// `id` and `name` are the only keys this decoder requires; if either
/// requirement grows, shipped builds silently drop every update frame.
func test_decode_update_acceptsLiveMetricsPartialProjection() throws {
let json = """
{
"type": "update",
"servers": [
{
"id": "s1",
"name": "srv-agent-name",
"online": true,
"last_active": 1779834851,
"uptime": 123456,
"cpu": 42.5,
"mem_used": 4294967296,
"swap_used": 1024,
"disk_used": 10737418240,
"net_in_speed": 12345,
"net_out_speed": 67890,
"net_in_transfer": 111,
"net_out_transfer": 222,
"load1": 1.25,
"load5": 0.8,
"load15": 0.6,
"tcp_conn": 34,
"udp_conn": 5,
"process_count": 128,
"disk_read_bytes_per_sec": 100,
"disk_write_bytes_per_sec": 50
}
]
}
"""
let msg = try decode(json)
guard case .update(let servers) = msg else {
return XCTFail("Expected .update, got \(msg)")
}

XCTAssertEqual(servers[0].id, "s1")
XCTAssertEqual(servers[0].cpuUsage, 42.5)
XCTAssertEqual(servers[0].swapUsed, 1024)
XCTAssertEqual(servers[0].uptime, 123_456)
// Static facts are absent from the wire: they must decode to nil so
// merge(from:) keeps the values already learned from full_sync/REST.
XCTAssertNil(servers[0].memoryTotal)
XCTAssertNil(servers[0].swapTotal)
XCTAssertNil(servers[0].diskTotal)
XCTAssertNil(servers[0].os)
XCTAssertNil(servers[0].tags)
XCTAssertNil(servers[0].hasToken)
}

/// Regression: the live browser WS frame sends `last_active` as a Unix epoch
/// **integer** (and includes swap/transfer/disk-io/tags/has_token). The old
/// decoder typed `last_active` as String, which threw `typeMismatch` and
Expand Down
3 changes: 2 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"generate:api-types": "cargo run --manifest-path ../../Cargo.toml --example dump_openapi > openapi.json && npx openapi-typescript openapi.json -o src/lib/api-types.ts"
"generate:api-types": "cargo run --manifest-path ../../Cargo.toml --example dump_openapi > openapi.json && npx openapi-typescript openapi.json -o src/lib/api-types.ts",
"generate:capabilities": "cargo run --manifest-path ../../Cargo.toml --example dump_capabilities_ts > src/lib/capability-bits.generated.ts"
},
"dependencies": {
"@base-ui/react": "^1.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import type { WidgetInput } from '@/hooks/use-dashboard'
import { useDashboardEditor } from '@/hooks/use-dashboard-editor'
import type { ServerMetrics } from '@/hooks/use-servers-ws'
import type { ServerMetrics } from '@/lib/server-catalog'
import type { Dashboard, DashboardWithWidgets } from '@/lib/widget-types'
import { DashboardGrid } from './dashboard-grid'
import { DashboardSwitcher } from './dashboard-switcher'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/dashboard/dashboard-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import 'react-grid-layout/css/styles.css'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import type { ServerMetrics } from '@/hooks/use-servers-ws'
import type { ServerMetrics } from '@/lib/server-catalog'
import { cn } from '@/lib/utils'
import type { DashboardWidget, SizingStrategy, WidgetTypeDefinition } from '@/lib/widget-types'
import { WIDGET_TYPES } from '@/lib/widget-types'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/dashboard/module-widget-host.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ActionsHelper } from '@serverbee/widget-sdk'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollArea } from '@/components/ui/scroll-area'
import type { ServerMetrics } from '@/hooks/use-servers-ws'
import type { ServerMetrics } from '@/lib/server-catalog'
import { parseConfig } from '@/lib/widget-helpers'
import type { DashboardWidget } from '@/lib/widget-types'
import { useWidgetRegistry } from '@/widgets-runtime/registry'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/dashboard/widget-config-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Label } from '@/components/ui/label'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import type { ServerMetrics } from '@/hooks/use-servers-ws'
import type { ServerMetrics } from '@/lib/server-catalog'
import { parseConfig } from '@/lib/widget-helpers'
import type {
AlertListConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ServerMetrics } from '@/hooks/use-servers-ws'
import type { ServerMetrics } from '@/lib/server-catalog'
import { parseConfig } from '@/lib/widget-helpers'
import type { DashboardWidget } from '@/lib/widget-types'

Expand Down
Loading
Loading