feat: probe host stats, node cards with a separate lines panel grouped by carrier, Windows host sampling, manual carrier override [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
ci/woodpecker/push/main-amd64-build Pipeline was successful
This commit is contained in:
1 parent
bac1c27e72
commit
88b1a0a9bb
36 files changed
+750
-185
No files matched your search
@@ -4,8 +4,8 @@ using MoShiro.Protocol;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// <c>moshiro.host</c>:Agent 每隔几秒上报的主机资源占用快照。最新值放在内存里并实时推给订阅者;每个 Agent 每分钟存一个样本做历史。
|
||||
/// 快照里没有 CPU 型号、主机名、网卡名和 IP(Agent 根本不采集它们)。
|
||||
/// <c>moshiro.host</c>:Agent 和 Probe 每隔几秒上报的主机资源占用快照。最新值放在内存里并实时推给订阅者;每个节点每分钟存一个样本做历史。
|
||||
/// 快照里没有 CPU 型号、主机名、网卡名和 IP(节点根本不采集它们)。
|
||||
/// </summary>
|
||||
public sealed class HostStatusService
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed record ChunkSignal(NodeId AgentId, string Series) : LiveSignal;
|
||||
public sealed record TestSignal(TestId TestId) : LiveSignal;
|
||||
|
||||
/// <summary>某个 Agent 的主机资源占用有了新的快照(实时推给展示页)。</summary>
|
||||
public sealed record HostSignal(NodeId AgentId, HostSnapshot Host) : LiveSignal;
|
||||
public sealed record HostSignal(NodeId Node, HostSnapshot Host) : LiveSignal;
|
||||
|
||||
public sealed class LiveSubscription : IDisposable
|
||||
{
|
||||
|
||||
@@ -66,6 +66,17 @@ public static class IspClassifier
|
||||
return (Other, Shorten(org));
|
||||
}
|
||||
|
||||
/// <summary>手动指定运营商时的中文名;不认识的取值返回 null。</summary>
|
||||
public static string? NameOf(string key) => key switch
|
||||
{
|
||||
Telecom => "中国电信",
|
||||
Unicom => "中国联通",
|
||||
Mobile => "中国移动",
|
||||
Broadnet => "中国广电",
|
||||
Other => "其他",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private static bool Has(string text, string needle) => text.Contains(needle, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>组织名常常很长(「Hangzhou Alibaba Advertising Co.,Ltd.」):去掉公司后缀,最多留 28 个字符。</summary>
|
||||
|
||||
@@ -16,7 +16,8 @@ public sealed record NodeInfo(
|
||||
string? Version = null,
|
||||
string? Tag = null,
|
||||
string? Commit = null,
|
||||
string? BuiltAt = null);
|
||||
string? BuiltAt = null,
|
||||
bool IspManual = false);
|
||||
|
||||
/// <summary>
|
||||
/// 存起来的完整记录(只给 Backend 内部用):多了一个「查询用的主机」,用来判断地址变了没有、要不要重新查。
|
||||
|
||||
@@ -42,6 +42,31 @@ public sealed class NodeInfoService
|
||||
return [.. _cache.Values.Select(m => m.Info)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动指定节点的运营商(telecom / unicom / mobile / broadnet / other);传 null 回到自动识别(下次节点出现时重新查)。
|
||||
/// 返回 false 表示取值不认识。
|
||||
/// </summary>
|
||||
public async ValueTask<bool> SetIspAsync(string nodeId, string kind, string? key, CancellationToken ct)
|
||||
{
|
||||
await EnsureLoadedAsync(ct);
|
||||
NodeMeta current = _cache.TryGetValue(nodeId, out NodeMeta? found) ? found : Empty(nodeId, kind);
|
||||
if (key is null)
|
||||
{
|
||||
// 清掉手动值,并让下一次心跳重新查询
|
||||
await SaveAsync(current with { Info = current.Info with { IspKey = null, IspName = null, IspManual = false }, GeoHost = null, GeoAtMs = 0, UpdatedAtMs = Now() }, ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
string? name = IspClassifier.NameOf(key);
|
||||
if (name is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await SaveAsync(current with { Info = current.Info with { IspKey = key, IspName = name, IspManual = true }, UpdatedAtMs = Now() }, ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>节点自报的系统、架构和构建信息;没有变化就不写库。</summary>
|
||||
public async ValueTask SetPlatformAsync(string nodeId, string kind, PlatformInfo? platform, CancellationToken ct)
|
||||
{
|
||||
@@ -101,8 +126,15 @@ public sealed class NodeInfoService
|
||||
return;
|
||||
}
|
||||
|
||||
(string key, string name) = IspClassifier.Classify(geo.Asn, geo.Organization);
|
||||
string country = geo.Country.ToUpperInvariant();
|
||||
if (current.Info.IspManual)
|
||||
{
|
||||
// 运营商是手动指定的:只更新国家,不覆盖运营商
|
||||
await SaveAsync(current with { Info = current.Info with { Country = country }, GeoHost = host, GeoAtMs = now, UpdatedAtMs = now }, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
(string key, string name) = IspClassifier.Classify(geo.Asn, geo.Organization);
|
||||
await SaveAsync(current with { Info = current.Info with { Country = country, IspKey = key, IspName = Clean(name, 40) }, GeoHost = host, GeoAtMs = now, UpdatedAtMs = now }, ct);
|
||||
}
|
||||
finally
|
||||
|
||||
Reference in new issue
Block a user