chore: initial project import
This commit is contained in:
commit
3fc4e5ca53
347 files changed
+56172
No files matched your search
@@ -0,0 +1,104 @@
|
||||
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;
|
||||
|
||||
public LineService(AgentService agents, IProbeStore probes)
|
||||
{
|
||||
_agents = agents;
|
||||
_probes = probes;
|
||||
}
|
||||
|
||||
/// <summary>所有线路:每个 Agent 和每个<b>未吊销</b>的 Probe 配成一对。</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)];
|
||||
return [.. agents.Where(include).SelectMany(a => probes.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));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user