Files
MoShiro/backend/src/Monitor.Backend.Api/Public/NodeEndpoints.cs
T
justme 1e5c660d05 feat(backend): report node platform, geo and build info
Agents and probes report OS, arch and build (version, tag, commit, time).
The backend looks up country and ISP of public addresses only, stores them
in node_meta (schema v5) and exposes display info via /api/public/nodes,
/api/public/about and /api/admin/build-info behind site.show_build.
2026-10-06 21:22:04 +08:00

70 lines
3.4 KiB
C#

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Monitor.Backend.Application;
namespace Monitor.Backend.Api;
/// <summary>
/// 节点的展示信息:系统、架构、国家、运营商。<b>只有这几项,没有任何 IP 地址、城市、ASN、经纬度</b>——
/// 这些在 Backend 里只在查询的那一刻用到,存起来的记录里也没有,所以访客(和任何别的接口)都拿不到。
/// </summary>
public static class NodeEndpoints
{
private const string Tag = "public-nodes";
public static void Map(RouteGroupBuilder group)
{
group.MapGet("/nodes", ListAsync)
.Api("nodes_list", "Display info of the visible agents and probes: system, architecture, country and ISP (never an address)", Tag)
.Returns<PageDto<NodeInfoDto>>().ProducesProblems(403);
group.MapGet("/about", AboutAsync)
.Api("about_get", "The backend's version, tag and build time, if the site shows build info", Tag)
.Returns<AboutDto>().ProducesProblems(403);
}
/// <summary>这个后端自己的构建信息;没有(本地构建)的字段是 <c>null</c>。</summary>
internal static BuildDto Backend(bool withCommit) =>
new(Monitor.Protocol.BuildInfo.Version, Empty(Monitor.Protocol.BuildInfo.Tag), withCommit ? Empty(Monitor.Protocol.BuildInfo.Commit) : null, Empty(Monitor.Protocol.BuildInfo.BuiltAt));
private static string? Empty(string value) => value.Length == 0 ? null : value;
private static async Task<IResult> AboutAsync(HttpContext http, SettingsService settings)
{
bool show = await settings.IsShowBuildAsync(http.RequestAborted);
http.Response.Headers.CacheControl = "no-store";
return TypedResults.Json(new AboutDto(show, show ? Backend(withCommit: false) : null), ApiJsonContext.Default.AboutDto);
}
private static async Task<IResult> ListAsync(HttpContext http, AgentService agents, ProbeService probes, NodeInfoService nodes, SettingsService settings, CancellationToken ct)
{
// 和别的公开接口一样:看得见的 Agent(隐藏的只有登录后才看得见)和没有被吊销的 Probe
var visible = new HashSet<string>(StringComparer.Ordinal);
foreach (AgentView view in await agents.ListAsync(ct))
{
if (view.Agent.Status == Fleet.Server.NodeStatus.Active && (view.Agent.PublicVisible || PublicGuard.CanSeeHidden(http)))
{
visible.Add(view.Agent.Id.Value);
}
}
foreach (ProbeView probe in await probes.ListAsync(ct))
{
if (!probe.Revoked)
{
visible.Add(probe.Probe.Id);
}
}
// 版本和 tag 只在站点打开「显示构建信息」时才给访客;关着就是 null
bool showBuild = await settings.IsShowBuildAsync(ct);
var items = (await nodes.ListAsync(ct))
.Where(n => visible.Contains(n.NodeId))
.OrderBy(n => n.NodeId, StringComparer.Ordinal)
.Select(n => new NodeInfoDto(n.NodeId, n.Kind, n.Os, n.OsName, n.Arch, n.Country, n.IspKey, n.IspName, showBuild ? n.Version : null, showBuild ? n.Tag : null))
.ToList();
http.Response.Headers.CacheControl = "no-store";
return TypedResults.Json(new PageDto<NodeInfoDto>(items, null), ApiJsonContext.Default.PageDtoNodeInfoDto);
}
}