ci/woodpecker/push/main-amd64-build Pipeline was successful
The panel decides which probes link to which agents (probes dial out): an auto-link setting plus per-pair manual overrides with a link matrix in the admin, and the hovered overview card is lifted above its neighbours.
108 lines
4.1 KiB
C#
108 lines
4.1 KiB
C#
using Fleet.Core;
|
||
using Monitor.Protocol;
|
||
|
||
namespace Monitor.Backend.Application;
|
||
|
||
/// <summary>一条线路 = 一对(Agent,Probe)。存储里它的序列是 <c>line:<probe_id></c>,对外的 id 是 <c><agent_id>~<probe_id></c>。</summary>
|
||
public readonly record struct LineKey(NodeId Agent, ProbeId Probe)
|
||
{
|
||
public string Series => RawChunkHeader.LinePrefix + Probe.Value;
|
||
|
||
public string Id => Agent.Value + "~" + Probe.Value;
|
||
|
||
public static bool TryParse(string text, out LineKey key)
|
||
{
|
||
int split = text.IndexOf('~', StringComparison.Ordinal);
|
||
if (split <= 0 || split == text.Length - 1)
|
||
{
|
||
key = default;
|
||
return false;
|
||
}
|
||
|
||
key = new LineKey(new NodeId(text[..split]), new ProbeId(text[(split + 1)..]));
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/// <summary>一条线路的展示视图:Agent 的状态、Probe 记录,以及这一对现在是否连着。</summary>
|
||
public sealed record LineView(AgentView Agent, ProbeRecord Probe)
|
||
{
|
||
public LineKey Key => new(Agent.Agent.Id, new ProbeId(Probe.Id));
|
||
|
||
public bool Connected => Agent.Probes.Any(p => p.Id == Probe.Id);
|
||
|
||
public ConnectedProbeInfo? Connection => Agent.Probes.FirstOrDefault(p => p.Id == Probe.Id);
|
||
}
|
||
|
||
/// <summary>线路目录:把 id 解析成(Agent,Probe),列出所有线路。</summary>
|
||
public sealed class LineService
|
||
{
|
||
private readonly AgentService _agents;
|
||
private readonly IProbeStore _probes;
|
||
private readonly LinkService _links;
|
||
|
||
public LineService(AgentService agents, IProbeStore probes, LinkService links)
|
||
{
|
||
_links = links;
|
||
_agents = agents;
|
||
_probes = probes;
|
||
}
|
||
|
||
/// <summary>所有线路:每个 Agent 和每个<b>未吊销</b>的 Probe 配成一对,只留下线路开着的(见 <see cref="LinkService"/>);关掉的线路历史数据还在,按 id 仍然查得到。</summary>
|
||
public async ValueTask<IReadOnlyList<LineView>> ListAsync(Func<AgentView, bool> include, CancellationToken ct)
|
||
{
|
||
IReadOnlyList<AgentView> agents = await _agents.ListAsync(ct);
|
||
IReadOnlyList<ProbeRecord> probes = [.. (await _probes.ListAsync(ct)).Where(p => p.RevokedAtMs is null)];
|
||
LinkSnapshot links = await _links.SnapshotAsync(ct);
|
||
return [.. agents.Where(include).SelectMany(a => probes.Where(p => links.IsLinked(p.Id, a.Agent.Id.Value)).Select(p => new LineView(a, p)))];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 线路 id 是 <c>agent~probe</c>。只给 Agent id 时:恰好有一个未吊销的 Probe 就用它,否则要求明确指定(<c>probe_required</c>)。
|
||
/// </summary>
|
||
public async ValueTask<Result<LineView>> ResolveAsync(string id, CancellationToken ct)
|
||
{
|
||
string agentPart = id;
|
||
string? probePart = null;
|
||
if (id.Contains('~', StringComparison.Ordinal))
|
||
{
|
||
if (!LineKey.TryParse(id, out LineKey key))
|
||
{
|
||
return Result<LineView>.Fail(MonitorErrors.AgentNotFound, "line not found");
|
||
}
|
||
|
||
agentPart = key.Agent.Value;
|
||
probePart = key.Probe.Value;
|
||
}
|
||
|
||
AgentView? agent = await _agents.GetAsync(new NodeId(agentPart), ct);
|
||
if (agent is null)
|
||
{
|
||
return Result<LineView>.Fail(MonitorErrors.AgentNotFound, "line not found");
|
||
}
|
||
|
||
IReadOnlyList<ProbeRecord> probes = await _probes.ListAsync(ct);
|
||
ProbeRecord? probe;
|
||
if (probePart is not null)
|
||
{
|
||
probe = probes.FirstOrDefault(p => p.Id == probePart);
|
||
if (probe is null)
|
||
{
|
||
return Result<LineView>.Fail(MonitorErrors.ProbeNotFound, "probe not found");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ProbeRecord[] active = [.. probes.Where(p => p.RevokedAtMs is null)];
|
||
if (active.Length != 1)
|
||
{
|
||
return Result<LineView>.Fail(MonitorErrors.ProbeRequired, active.Length == 0 ? "there is no active probe" : "several probes exist: use <agent_id>~<probe_id> as the line id");
|
||
}
|
||
|
||
probe = active[0];
|
||
}
|
||
|
||
return Result<LineView>.Ok(new LineView(agent, probe));
|
||
}
|
||
}
|