Files
MoShiro/backend/src/Monitor.Backend.Application/Ports/StorePorts.cs
T
justme 86311c7452 feat: node info, build info and acrylic web theme
Squash of recent dev work: return_session login, backend switcher, line
detail timeline, overview strips, node platform/country/ISP info with flags
and icons, build info (version, tag, time) behind site.show_build, and the
acrylic display theme.
2026-10-06 21:24:25 +08:00

216 lines
9.6 KiB
C#

using Fleet.Core;
using Fleet.Server;
using Monitor.Protocol;
namespace Monitor.Backend.Application;
public interface IAgentStore
{
ValueTask<IReadOnlyList<AgentRecord>> ListAsync(CancellationToken ct);
ValueTask<AgentRecord?> GetAsync(NodeId id, CancellationToken ct);
/// <summary>注册之后补充公开地址与端口(仅在记录还是注册时的默认值时才写入)。</summary>
ValueTask ApplyEnrollmentAsync(NodeId id, AgentEnrollment enrollment, CancellationToken ct);
/// <summary>应用 merge-patch。<paramref name="expectedRowVersion"/> 不为 null 且不匹配时返回 precondition_failed。</summary>
ValueTask<Result<AgentRecord>> PatchAsync(NodeId id, AgentPatch patch, long? expectedRowVersion, CancellationToken ct);
/// <summary>pending → active、active ↔ disabled。激活时名称不能与其他已建立的节点冲突。</summary>
ValueTask<Result<AgentRecord>> SetStatusAsync(NodeId id, NodeStatus status, CancellationToken ct);
/// <summary>接管:把同名旧节点的公钥换成待审批节点的公钥,保留历史数据,并删除待审批记录。</summary>
ValueTask<Result<AgentRecord>> TakeoverAsync(NodeId pendingId, CancellationToken ct);
ValueTask DeleteAsync(NodeId id, CancellationToken ct);
ValueTask ApplyHelloAsync(NodeId id, AgentHello hello, CancellationToken ct);
ValueTask SetTaskVersionAsync(NodeId id, long version, string taskHash, CancellationToken ct);
ValueTask<string?> GetTaskHashAsync(NodeId id, CancellationToken ct);
ValueTask SetAppliedTaskStateAsync(NodeId id, long appliedVersion, string? rejectedJson, CancellationToken ct);
}
/// <summary>节点的展示信息(系统、架构、国家、运营商)。</summary>
public interface INodeMetaStore
{
ValueTask<IReadOnlyList<NodeMeta>> ListAsync(CancellationToken ct);
ValueTask SaveAsync(NodeMeta meta, CancellationToken ct);
ValueTask DeleteAsync(string nodeId, CancellationToken ct);
}
/// <summary>查一个 IP(或域名)的归属:国家、ASN、组织。实现负责超时和失败(失败返回 null,不抛)。</summary>
public interface IGeoLookup
{
ValueTask<GeoInfo?> LookupAsync(string host, CancellationToken ct);
}
public interface IJoinTokenStore
{
ValueTask<JoinTokenInfo> CreateAsync(string id, byte[] tokenSha256, string? note, IReadOnlyList<string> labels, bool autoApprove, int maxUses, long expiresAtMs, long nowMs, CancellationToken ct);
ValueTask<IReadOnlyList<JoinTokenInfo>> ListAsync(CancellationToken ct);
ValueTask<bool> RevokeAsync(string id, long nowMs, CancellationToken ct);
ValueTask<bool> DeleteAsync(string id, CancellationToken ct);
}
public interface IProbeStore
{
ValueTask<IReadOnlyList<ProbeRecord>> ListAsync(CancellationToken ct);
ValueTask<ProbeRecord?> GetAsync(string id, CancellationToken ct);
/// <summary>新增或整条更新(轮换密钥、改名都走这里)。</summary>
ValueTask SaveAsync(ProbeRecord probe, CancellationToken ct);
/// <summary>吊销:保留记录,公钥立即从所有 Agent 的名单里移除。已经吊销的返回 false。</summary>
ValueTask<bool> RevokeAsync(string id, long nowMs, CancellationToken ct);
ValueTask<bool> DeleteAsync(string id, CancellationToken ct);
}
public interface ISettingsStore
{
ValueTask<string?> GetAsync(string key, CancellationToken ct);
ValueTask SetAsync(string key, string value, CancellationToken ct);
ValueTask<IReadOnlyDictionary<string, string>> GetAllAsync(CancellationToken ct);
/// <summary>原子地加一并返回新值(用于版本号计数器)。</summary>
ValueTask<long> IncrementAsync(string key, CancellationToken ct);
}
public interface ITaskStore
{
ValueTask<IReadOnlyList<TaskRecord>> ListAsync(CancellationToken ct);
ValueTask<TaskRecord?> GetAsync(TaskId id, CancellationToken ct);
ValueTask CreateAsync(TaskRecord task, CancellationToken ct);
ValueTask<Result<TaskRecord>> UpdateAsync(TaskRecord task, long? expectedRowVersion, CancellationToken ct);
ValueTask DeleteAsync(TaskId id, CancellationToken ct);
}
public interface ITestStore
{
ValueTask CreateAsync(TestRecord test, CancellationToken ct);
ValueTask<TestRecord?> GetAsync(TestId id, CancellationToken ct);
ValueTask<IReadOnlyList<TestRecord>> ListAsync(NodeId? agentId, ProbeId? probeId, int limit, CancellationToken ct);
ValueTask<bool> UpdateStatusAsync(TestId id, string status, int? phase, string? errorCode, long nowMs, CancellationToken ct);
ValueTask SetPhaseCountAsync(TestId id, int phaseCount, CancellationToken ct);
ValueTask<bool> HasActiveAsync(NodeId agentId, ProbeId probeId, CancellationToken ct);
ValueTask SaveResultAsync(TestResultRecord result, CancellationToken ct);
ValueTask<TestResultRecord?> GetResultAsync(TestId id, CancellationToken ct);
/// <summary>已成功结束但还没有结果的测试(用于补算结果)。</summary>
ValueTask<IReadOnlyList<TestRecord>> ListNeedingResultAsync(int limit, CancellationToken ct);
/// <summary>把长时间处于 queued/running 的测试标记为 failed(例如 Backend 重启或节点失联)。</summary>
ValueTask<int> FailStaleAsync(long olderThanMs, string errorCode, long nowMs, CancellationToken ct);
}
public interface IEventStore
{
ValueTask AddAsync(NodeId? agentId, string kind, long atMs, string? detail, CancellationToken ct);
ValueTask<IReadOnlyList<EventRecord>> ListAsync(NodeId? agentId, string? kind, long fromMs, long toMs, long? beforeId, int limit, CancellationToken ct);
ValueTask<int> DeleteOlderThanAsync(long cutoffMs, CancellationToken ct);
}
/// <summary>主机资源占用的历史:每个 Agent 每分钟一个样本。</summary>
public interface IHostStore
{
ValueTask SaveAsync(NodeId agent, long minuteMs, Monitor.Protocol.HostSnapshot snapshot, CancellationToken ct);
ValueTask<IReadOnlyList<HostSample>> ReadAsync(NodeId agent, long fromMs, long toMs, CancellationToken ct);
ValueTask<int> DeleteOlderThanAsync(long cutoffMs, CancellationToken ct);
}
public sealed record HostSample(long AtMs, double? Cpu, long? MemUsed, long? MemTotal, long? DiskUsed, long? DiskTotal, long? NetRxBps, long? NetTxBps, long? UptimeSeconds);
public interface IRawStore
{
/// <summary>追加写入段文件 → 刷盘 → 写索引;<c>chunk_id</c> 已存在时返回 false(调用方仍然确认)。</summary>
ValueTask<bool> AppendAsync(RawChunkAppend chunk, CancellationToken ct);
ValueTask<RawChunkMeta?> GetMetaAsync(ChunkId id, CancellationToken ct);
ValueTask<IReadOnlyList<RawChunkMeta>> FindAsync(RawQuery query, CancellationToken ct);
ValueTask<byte[]?> ReadAsync(RawChunkMeta meta, CancellationToken ct);
/// <summary>删除早于截止日期的段文件和索引。持续任务与按需测试保留期不同。</summary>
ValueTask<int> DeleteExpiredAsync(DateOnly continuousBefore, DateOnly testsBefore, CancellationToken ct);
/// <summary>时间范围内有原始块的序列(用于重算)。</summary>
ValueTask<IReadOnlyList<(NodeId AgentId, string Series)>> ListSeriesAsync(long fromMs, long toMs, CancellationToken ct);
/// <summary>启动时截断没有索引记录的段文件尾部。</summary>
ValueTask RecoverAsync(CancellationToken ct);
long TotalBytes { get; }
}
public interface IMetricStore
{
/// <summary>覆盖写入(recompute 路径)。</summary>
ValueTask UpsertAsync(MetricLevel level, IReadOnlyList<MetricRow> rows, CancellationToken ct);
/// <summary>读取—合并—写回(流式路径:同一个窗口由多个块累加)。</summary>
ValueTask MergeAsync(MetricLevel level, IReadOnlyList<MetricRow> rows, CancellationToken ct);
/// <summary>用新算出的指标替换已有行里的同名指标,其余指标保持不变(回填新增或变更的指标)。</summary>
ValueTask PatchAsync(MetricLevel level, IReadOnlyList<MetricRow> rows, IReadOnlySet<string> replaceMetricIds, CancellationToken ct);
ValueTask<IReadOnlyList<MetricRow>> ReadAsync(MetricLevel level, NodeId agentId, string series, long fromMs, long toMs, CancellationToken ct);
/// <summary>这条序列在某一层里最早的窗口起点;没有数据返回 null。</summary>
ValueTask<long?> EarliestAsync(MetricLevel level, NodeId agentId, string series, CancellationToken ct);
ValueTask<IReadOnlyList<MetricRow>> ReadAgentsAsync(MetricLevel level, IReadOnlyCollection<NodeId> agentIds, string series, long fromMs, long toMs, CancellationToken ct);
ValueTask<int> DeleteOlderThanAsync(MetricLevel level, long cutoffMs, CancellationToken ct);
ValueTask EnqueueAsync(IReadOnlyList<QueueItem> items, CancellationToken ct);
ValueTask<IReadOnlyList<QueueItem>> DequeueDueAsync(long nowMs, int limit, CancellationToken ct);
ValueTask CompleteAsync(IReadOnlyList<QueueItem> items, CancellationToken ct);
ValueTask<int> QueueLengthAsync(CancellationToken ct);
ValueTask MarkRollupDirtyAsync(MetricLevel level, NodeId agentId, string series, long bucketStart, CancellationToken ct);
ValueTask<IReadOnlyList<(MetricLevel Level, NodeId AgentId, string Series, long BucketStart)>> TakeRollupDirtyAsync(int limit, CancellationToken ct);
ValueTask<IReadOnlyList<MetricVersionRecord>> GetVersionsAsync(CancellationToken ct);
ValueTask SetVersionAsync(MetricVersionRecord version, CancellationToken ct);
}
public sealed record StorageHealth(bool Database, bool RawDirectory);
/// <summary>/healthz 用:SQLite 与原始数据目录都可写。</summary>
public interface IStorageHealth
{
ValueTask<StorageHealth> CheckAsync(CancellationToken ct);
}