refactor!: rename the project from Monitor to MoShiro [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
ci/woodpecker/push/main-amd64-build Pipeline was successful
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
This commit is contained in:
1 parent
27ac7fb787
commit
2e146d4da7
309 files changed
+1565
-1572
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public enum AccKind : byte
|
||||
{
|
||||
+1
-1
@@ -2,7 +2,7 @@ using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>一个时间桶里所有指标的累加器:<c>指标 id → 累加器</c>。</summary>
|
||||
public sealed class MetricSet
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>
|
||||
/// 固定对数桶直方图:100 µs ~ 30 s,每 2 倍 8 个桶(相对误差 ≤ 4.5%)。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>
|
||||
/// 估计两端的时钟偏移和线性漂移:单向时延 x(t) = 偏移 + 漂移·t + 真实的时延变化。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>变点检测(均值漂移):找到使前后两段均值差最显著的位置,用来判断 QoS 从什么时候开始限速。</summary>
|
||||
public static class ChangePoint
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>算子把观测值交给这里;窗口评估器负责按时间分桶。</summary>
|
||||
public interface IObservationSink
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public enum Unit
|
||||
{
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public sealed class TestStepResult
|
||||
{
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>
|
||||
/// 把原始块按时间分桶算成累加器。流式路径(1s 桶)和持久路径(分钟窗口)调用同一份代码,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>线路基础指标(udp_echo / icmp_echo 的回显流)。新增指标 = 加一行声明。</summary>
|
||||
public static class LinkMetrics
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public sealed record PresetDef(string Id, string Label, IReadOnlyList<string> MetricIds);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>连接建立、长连接、UDP 压测(udp_train)的指标。</summary>
|
||||
public static class SessionMetrics
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>TCP 指标:回显的 RTT 与卡顿、内核采样的重传、批量传输的吞吐。</summary>
|
||||
public static class TcpMetrics
|
||||
+1
-1
@@ -3,6 +3,6 @@
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\shared\Monitor.Protocol\Monitor.Protocol.csproj" />
|
||||
<ProjectReference Include="..\..\shared\MoShiro.Protocol\MoShiro.Protocol.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>线路基础质量:RTT、丢包、抖动、连续丢包。</summary>
|
||||
public sealed class BaseModel : ServiceModelBase
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>模型的输入:一个时间段内的导出值(例如 <c>rtt.p95</c>)。没有数据的指标不存在。</summary>
|
||||
public sealed class MetricView
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public enum Dir
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>算子工厂:<c>Ops.Rtt</c>、<c>Ops.Loss(Dir.Up)</c> 等,供 <c>MetricDef</c> 声明使用。</summary>
|
||||
public static class Ops
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>tcp_info 流的辅助:<c>side</c> 为 0 表示 Probe 一侧记录(Probe 时钟),否则为 Agent 一侧。</summary>
|
||||
internal static class TcpInfoTime
|
||||
+6
-6
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>Agent 管理:列表、修改、审批、启停、接管、删除;加入令牌;Probe 身份。</summary>
|
||||
public static class AgentEndpoints
|
||||
@@ -77,7 +77,7 @@ public static class AgentEndpoints
|
||||
AgentView? view = await agents.GetAsync(new NodeId(id), ct);
|
||||
if (view is null)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.AgentNotFound, "agent not found");
|
||||
return Problems.Create(http, MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
|
||||
Concurrency.SetETag(http, view.Agent.RowVersion);
|
||||
@@ -166,7 +166,7 @@ public static class AgentEndpoints
|
||||
private static async Task<IResult> GetProbeAsync(HttpContext http, ProbeService probes, string id, CancellationToken ct) =>
|
||||
await probes.GetAsync(id, ct) is { } view
|
||||
? TypedResults.Json(Mappers.ToDto(view), ApiJsonContext.Default.ProbeDto)
|
||||
: Problems.Create(http, MonitorErrors.ProbeNotFound, "probe not found");
|
||||
: Problems.Create(http, MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
|
||||
private static async Task<IResult> PatchProbeAsync(HttpContext http, ProbeService probes, PatchProbeRequest request, string id, CancellationToken ct)
|
||||
{
|
||||
@@ -211,7 +211,7 @@ public static class AgentEndpoints
|
||||
return Problems.From(http, error);
|
||||
}
|
||||
|
||||
return dropped ? TypedResults.NoContent() : Problems.Create(http, MonitorErrors.ProbeOffline, "this probe has no session with this agent");
|
||||
return dropped ? TypedResults.NoContent() : Problems.Create(http, MoShiroErrors.ProbeOffline, "this probe has no session with this agent");
|
||||
}
|
||||
|
||||
/// <summary>Probe 要连的面板地址:配置的公开地址,没有就用这次请求的地址。</summary>
|
||||
+4
-4
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>登录、退出、当前身份。登录接口限流,失败一律返回 401,不区分“用户不存在”和“密码错误”。</summary>
|
||||
public static class AuthEndpoints
|
||||
@@ -35,7 +35,7 @@ public static class AuthEndpoints
|
||||
string source = http.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
if (!limiter.TryAcquire(source, out int retryAfter))
|
||||
{
|
||||
return Problems.RetryAfter(http, new FleetError(MonitorErrors.TooManyRequests, "too many sign-in attempts"), retryAfter);
|
||||
return Problems.RetryAfter(http, new FleetError(MoShiroErrors.TooManyRequests, "too many sign-in attempts"), retryAfter);
|
||||
}
|
||||
|
||||
Result<LoginResult> result = await auth.LoginAsync(request.Username ?? "", request.Password ?? "", http.RequestAborted);
|
||||
+4
-4
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>构建信息与更新:后端和每个节点的版本、tag、构建时间,CI 签名的发布清单,以及「更新这个节点」的请求。更新本身由节点宿主机上的更新器验签并执行。</summary>
|
||||
public static class BuildEndpoints
|
||||
@@ -54,7 +54,7 @@ public static class BuildEndpoints
|
||||
}
|
||||
|
||||
private static async Task<IResult> RequestUpdateAsync(HttpContext http, ReleaseService releases, string id, CancellationToken ct) =>
|
||||
await releases.RequestUpdateAsync(new NodeId(id), ct) ? TypedResults.NoContent() : Problems.Create(http, MonitorErrors.AgentOffline, "the node is not connected");
|
||||
await releases.RequestUpdateAsync(new NodeId(id), ct) ? TypedResults.NoContent() : Problems.Create(http, MoShiroErrors.AgentOffline, "the node is not connected");
|
||||
|
||||
private static ReleaseDto ToDto(ReleaseOffer offer) => new(offer.Component, offer.Seq, offer.Tag, offer.Version, offer.Digest);
|
||||
|
||||
+7
-7
@@ -3,10 +3,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>原始数据检索与下载、事件、指标重算进度。</summary>
|
||||
public static class DataEndpoints
|
||||
@@ -18,8 +18,8 @@ public static class DataEndpoints
|
||||
group.MapGet("/raw/chunks", ListChunksAsync)
|
||||
.Api("raw_chunks_list", "Raw chunk metadata", Tag).Returns<PageDto<RawChunkMetaDto>>().ProducesProblems(400, 401, 403);
|
||||
group.MapGet("/raw/chunks/{chunk_id}", DownloadChunkAsync)
|
||||
.Api("raw_chunks_get", "Download a raw chunk (application/vnd.monitor.rawchunk)", Tag)
|
||||
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.monitor.rawchunk").ProducesProblems(401, 403, 404, 410);
|
||||
.Api("raw_chunks_get", "Download a raw chunk (application/vnd.moshiro.rawchunk)", Tag)
|
||||
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.moshiro.rawchunk").ProducesProblems(401, 403, 404, 410);
|
||||
group.MapGet("/events", ListEventsAsync)
|
||||
.Api("events_list", "Agent events", Tag).Returns<PageDto<EventDto>>().ProducesProblems(400, 401, 403);
|
||||
group.MapGet("/analysis/recompute", RecomputeAsync)
|
||||
@@ -57,7 +57,7 @@ public static class DataEndpoints
|
||||
{
|
||||
if (!Guid.TryParse(chunk_id, out Guid id))
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.ChunkNotFound, "chunk not found");
|
||||
return Problems.Create(http, MoShiroErrors.ChunkNotFound, "chunk not found");
|
||||
}
|
||||
|
||||
Result<(RawChunkMeta Meta, byte[] Bytes)> result = await raw.GetAsync(new ChunkId(id), http.RequestAborted);
|
||||
@@ -67,7 +67,7 @@ public static class DataEndpoints
|
||||
}
|
||||
|
||||
http.Response.Headers.CacheControl = "private, max-age=3600";
|
||||
return TypedResults.File(found.Bytes, "application/vnd.monitor.rawchunk", chunk_id + ".mnrc");
|
||||
return TypedResults.File(found.Bytes, "application/vnd.moshiro.rawchunk", chunk_id + ".msrc");
|
||||
}
|
||||
|
||||
private static async Task<IResult> ListEventsAsync(
|
||||
+5
-5
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>线路开关:哪些(Probe,Agent)要连。自动连接的总开关是设置 <c>fleet.auto_link</c>;这里看每一对的结果,并且手动强制开、关或取消手动设置。</summary>
|
||||
public static class LinkEndpoints
|
||||
@@ -40,12 +40,12 @@ public static class LinkEndpoints
|
||||
{
|
||||
if (await probes.GetAsync(probe_id, ct) is not { Revoked: false } probe)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.ProbeNotFound, "probe not found");
|
||||
return Problems.Create(http, MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
}
|
||||
|
||||
if (await agents.GetAsync(new NodeId(agent_id), ct) is not { } agent)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.AgentNotFound, "agent not found");
|
||||
return Problems.Create(http, MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
|
||||
await links.SetAsync(probe_id, agent_id, request.Enabled, ct);
|
||||
+3
-3
@@ -3,10 +3,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>API 令牌、站点设置与保留期、用户。令牌明文只在创建的响应里出现一次。</summary>
|
||||
public static class SystemEndpoints
|
||||
+4
-4
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>任务(线路模板、单个 Agent 的覆盖、目标任务)与按需测试。参数按 Kind 的 ParamSpec 校验,并检查带宽预算。</summary>
|
||||
public static class TaskEndpoints
|
||||
@@ -76,7 +76,7 @@ public static class TaskEndpoints
|
||||
TaskRecord? task = await tasks.GetAsync(new TaskId(id), ct);
|
||||
if (task is null)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.TaskNotFound, "task not found");
|
||||
return Problems.Create(http, MoShiroErrors.TaskNotFound, "task not found");
|
||||
}
|
||||
|
||||
Concurrency.SetETag(http, task.RowVersion);
|
||||
+7
-7
@@ -5,17 +5,17 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Json;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Monitor.Backend.Application;
|
||||
using MoShiro.Backend.Application;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>
|
||||
/// Api 层的入口:服务注册、中间件、端点映射。新增资源时在 <see cref="MapMonitorApi"/> 里加一行。
|
||||
/// Api 层的入口:服务注册、中间件、端点映射。新增资源时在 <see cref="MapMoShiroApi"/> 里加一行。
|
||||
/// Api 只调用 Application;错误码到 HTTP 状态的映射只在这一层做一次(<see cref="ErrorCodeHttpMap"/>)。
|
||||
/// </summary>
|
||||
public static class ApiModule
|
||||
{
|
||||
public static IServiceCollection AddMonitorApi(this IServiceCollection services)
|
||||
public static IServiceCollection AddMoShiroApi(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<SseLimiter>();
|
||||
services.Configure<JsonOptions>(options =>
|
||||
@@ -48,7 +48,7 @@ public static class ApiModule
|
||||
/// 跨域(可选):只对 <c>/api/public</c> 和 <c>/api/admin</c> 生效,只放行名单里的来源,且不带凭据(Cookie 不会跨域),
|
||||
/// 所以跨域的前端只能用 Bearer 令牌。需要放在认证和路由之前。
|
||||
/// </summary>
|
||||
public static IApplicationBuilder UseMonitorCors(this IApplicationBuilder app, string? origins)
|
||||
public static IApplicationBuilder UseMoShiroCors(this IApplicationBuilder app, string? origins)
|
||||
{
|
||||
string[] allowed = (origins ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (allowed.Length == 0)
|
||||
@@ -83,7 +83,7 @@ public static class ApiModule
|
||||
}
|
||||
|
||||
/// <summary>认证中间件:解析 Bearer 令牌或会话 Cookie。需要放在路由之后、端点之前。</summary>
|
||||
public static IApplicationBuilder UseMonitorApi(this IApplicationBuilder app)
|
||||
public static IApplicationBuilder UseMoShiroApi(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseExceptionHandler();
|
||||
app.UseStatusCodePages();
|
||||
@@ -91,7 +91,7 @@ public static class ApiModule
|
||||
return app;
|
||||
}
|
||||
|
||||
public static IEndpointRouteBuilder MapMonitorApi(this IEndpointRouteBuilder app)
|
||||
public static IEndpointRouteBuilder MapMoShiroApi(this IEndpointRouteBuilder app)
|
||||
{
|
||||
RouteGroupBuilder publicGroup = app.MapGroup("/api/public").AddEndpointFilter(PublicGuard.InvokeAsync);
|
||||
QueryEndpoints.Map(publicGroup);
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>并发控制:配置类资源返回 ETag(行版本),PATCH/DELETE 支持 If-Match,不匹配时返回 412。</summary>
|
||||
internal static class Concurrency
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
public static class EndpointConventions
|
||||
{
|
||||
+9
-9
@@ -1,13 +1,13 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
public static class HttpContextPrincipalExtensions
|
||||
{
|
||||
private const string Key = "monitor.principal";
|
||||
private const string Key = "moshiro.principal";
|
||||
|
||||
public static Principal GetPrincipal(this HttpContext http) =>
|
||||
http.Items.TryGetValue(Key, out object? value) && value is Principal principal ? principal : Principal.Anonymous;
|
||||
@@ -20,7 +20,7 @@ public static class HttpContextPrincipalExtensions
|
||||
/// </summary>
|
||||
public static class PrincipalResolver
|
||||
{
|
||||
public const string SessionCookie = "monitor_session";
|
||||
public const string SessionCookie = "moshiro_session";
|
||||
public const string CsrfHeader = "X-CSRF-Token";
|
||||
|
||||
public static string? BearerOf(HttpContext http)
|
||||
@@ -63,8 +63,8 @@ public static class AdminGuard
|
||||
if (!principal.IsAdmin)
|
||||
{
|
||||
return principal.Scope != ApiScope.None
|
||||
? Problems.Create(http, MonitorErrors.Forbidden, "the token scope does not allow this operation")
|
||||
: Problems.Create(http, MonitorErrors.Unauthorized, "authentication is required");
|
||||
? Problems.Create(http, MoShiroErrors.Forbidden, "the token scope does not allow this operation")
|
||||
: Problems.Create(http, MoShiroErrors.Unauthorized, "authentication is required");
|
||||
}
|
||||
|
||||
if (principal.Kind == PrincipalKind.Session && !HttpMethods.IsGet(http.Request.Method) && !HttpMethods.IsHead(http.Request.Method) && !HttpMethods.IsOptions(http.Request.Method))
|
||||
@@ -72,7 +72,7 @@ public static class AdminGuard
|
||||
string? csrf = http.Request.Headers[PrincipalResolver.CsrfHeader];
|
||||
if (csrf is null || principal.CsrfToken is null || !AuthService.FixedTimeEquals(csrf, principal.CsrfToken))
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.CsrfFailed, "a valid X-CSRF-Token header is required");
|
||||
return Problems.Create(http, MoShiroErrors.CsrfFailed, "a valid X-CSRF-Token header is required");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public static class PublicGuard
|
||||
HttpContext http = context.HttpContext;
|
||||
if (!http.GetPrincipal().CanRead && !await http.RequestServices.GetRequiredService<SettingsService>().IsSitePublicAsync(http.RequestAborted))
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.SitePrivate, "this site is private; sign in or use a read-only token");
|
||||
return Problems.Create(http, MoShiroErrors.SitePrivate, "this site is private; sign in or use a read-only token");
|
||||
}
|
||||
|
||||
return await next(context);
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Server;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>应用层模型 → REST DTO。public 层的 DTO 只暴露可以公开的字段。</summary>
|
||||
public static class Mappers
|
||||
+15
-15
@@ -1,10 +1,10 @@
|
||||
using System.Diagnostics;
|
||||
using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>错误码到 HTTP 状态的映射:只在 Api 层做一次。</summary>
|
||||
public static class ErrorCodeHttpMap
|
||||
@@ -44,19 +44,19 @@ public static class ErrorCodeHttpMap
|
||||
}
|
||||
}
|
||||
|
||||
Add(400, MonitorErrors.ValidationFailed, MonitorErrors.BudgetExceeded, FleetErrors.InvalidParams);
|
||||
Add(401, MonitorErrors.Unauthorized, MonitorErrors.CsrfFailed, FleetErrors.Unauthorized);
|
||||
Add(403, MonitorErrors.Forbidden, MonitorErrors.SitePrivate, FleetErrors.Forbidden, FleetErrors.ProofInvalid);
|
||||
Add(400, MoShiroErrors.ValidationFailed, MoShiroErrors.BudgetExceeded, FleetErrors.InvalidParams);
|
||||
Add(401, MoShiroErrors.Unauthorized, MoShiroErrors.CsrfFailed, FleetErrors.Unauthorized);
|
||||
Add(403, MoShiroErrors.Forbidden, MoShiroErrors.SitePrivate, FleetErrors.Forbidden, FleetErrors.ProofInvalid);
|
||||
Add(
|
||||
404, MonitorErrors.NotFound, MonitorErrors.AgentNotFound, MonitorErrors.ProbeNotFound, MonitorErrors.TaskNotFound, MonitorErrors.TestNotFound, MonitorErrors.ChunkNotFound,
|
||||
MonitorErrors.TokenNotFound, MonitorErrors.UnknownKind, MonitorErrors.UnknownPlan, FleetErrors.NotFound);
|
||||
Add(409, MonitorErrors.NameConflict, MonitorErrors.ProbeRevoked, MonitorErrors.ProbeOffline, MonitorErrors.TestBusy, MonitorErrors.AgentNotActive, MonitorErrors.AgentOffline, FleetErrors.NameConflict, FleetErrors.NodeOffline);
|
||||
Add(410, MonitorErrors.RawExpired, MonitorErrors.JoinTokenExpired, FleetErrors.JoinTokenExpired);
|
||||
Add(412, MonitorErrors.PreconditionFailed);
|
||||
Add(413, MonitorErrors.PayloadTooLarge);
|
||||
Add(422, MonitorErrors.ProbeRequired, MonitorErrors.RangeTooLarge, MonitorErrors.InvalidStep, MonitorErrors.InvalidRange, MonitorErrors.UnknownMetric, MonitorErrors.UnknownPreset, MonitorErrors.TooManyMetrics, FleetErrors.Unsupported);
|
||||
Add(429, MonitorErrors.TooManyRequests, MonitorErrors.TooManyStreams);
|
||||
Add(503, MonitorErrors.StorageUnavailable, FleetErrors.Timeout);
|
||||
404, MoShiroErrors.NotFound, MoShiroErrors.AgentNotFound, MoShiroErrors.ProbeNotFound, MoShiroErrors.TaskNotFound, MoShiroErrors.TestNotFound, MoShiroErrors.ChunkNotFound,
|
||||
MoShiroErrors.TokenNotFound, MoShiroErrors.UnknownKind, MoShiroErrors.UnknownPlan, FleetErrors.NotFound);
|
||||
Add(409, MoShiroErrors.NameConflict, MoShiroErrors.ProbeRevoked, MoShiroErrors.ProbeOffline, MoShiroErrors.TestBusy, MoShiroErrors.AgentNotActive, MoShiroErrors.AgentOffline, FleetErrors.NameConflict, FleetErrors.NodeOffline);
|
||||
Add(410, MoShiroErrors.RawExpired, MoShiroErrors.JoinTokenExpired, FleetErrors.JoinTokenExpired);
|
||||
Add(412, MoShiroErrors.PreconditionFailed);
|
||||
Add(413, MoShiroErrors.PayloadTooLarge);
|
||||
Add(422, MoShiroErrors.ProbeRequired, MoShiroErrors.RangeTooLarge, MoShiroErrors.InvalidStep, MoShiroErrors.InvalidRange, MoShiroErrors.UnknownMetric, MoShiroErrors.UnknownPreset, MoShiroErrors.TooManyMetrics, FleetErrors.Unsupported);
|
||||
Add(429, MoShiroErrors.TooManyRequests, MoShiroErrors.TooManyStreams);
|
||||
Add(503, MoShiroErrors.StorageUnavailable, FleetErrors.Timeout);
|
||||
map[NotImplemented.Value] = 501;
|
||||
return map;
|
||||
}
|
||||
+6
-6
@@ -3,10 +3,10 @@ using System.Text;
|
||||
using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
internal static class QueryHelpers
|
||||
{
|
||||
@@ -22,7 +22,7 @@ internal static class QueryHelpers
|
||||
}
|
||||
catch (OperationCanceledException) when (!http.RequestAborted.IsCancellationRequested)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.StorageUnavailable, null);
|
||||
return Problems.Create(http, MoShiroErrors.StorageUnavailable, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ internal static class QueryHelpers
|
||||
|
||||
return line.Agent.Agent.Status == Fleet.Server.NodeStatus.Active && (line.Agent.Agent.PublicVisible || PublicGuard.CanSeeHidden(http)) && line.Probe.RevokedAtMs is null
|
||||
? found
|
||||
: Result<LineView>.Fail(MonitorErrors.AgentNotFound, "line not found");
|
||||
: Result<LineView>.Fail(MoShiroErrors.AgentNotFound, "line not found");
|
||||
}
|
||||
|
||||
/// <summary>找到可以展示的线路:已激活,并且(对外可见,或者访问者已认证)。否则 404(不暴露被隐藏的线路存在与否)。</summary>
|
||||
@@ -90,6 +90,6 @@ internal static class QueryHelpers
|
||||
AgentView? view = await agents.GetAsync(new NodeId(id), ct);
|
||||
return view is { Agent.Status: Fleet.Server.NodeStatus.Active } && (view.Agent.PublicVisible || PublicGuard.CanSeeHidden(http))
|
||||
? Result<AgentView>.Ok(view)
|
||||
: Result<AgentView>.Fail(MonitorErrors.AgentNotFound, "agent not found");
|
||||
: Result<AgentView>.Fail(MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary><c>return_session</c>:跨域的前端用——不设 Cookie,把会话令牌放在响应里,之后用 <c>Authorization: Bearer</c> 带上。</summary>
|
||||
public sealed record LoginRequest(string Username, string Password, bool? ReturnSession);
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>
|
||||
/// REST 的 JSON 源生成上下文(snake_case,字段始终存在、未知值写 null,枚举与时间的格式见数据表示规则)。
|
||||
@@ -80,6 +80,6 @@ namespace Monitor.Backend.Api;
|
||||
[JsonSerializable(typeof(CreateUserRequest))]
|
||||
[JsonSerializable(typeof(ChangePasswordRequest))]
|
||||
[JsonSerializable(typeof(RecomputeDto))]
|
||||
[JsonSerializable(typeof(Monitor.Protocol.LocalLimits))]
|
||||
[JsonSerializable(typeof(MoShiro.Protocol.LocalLimits))]
|
||||
[JsonSerializable(typeof(List<Fleet.Rpc.RejectedItem>))]
|
||||
public sealed partial class ApiJsonContext : JsonSerializerContext;
|
||||
+1
-1
@@ -2,7 +2,7 @@ using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>时间点统一使用 RFC 3339 UTC,以 Z 结尾,精确到毫秒(唯一例外:列式时序里的时间轴 <c>t</c> 是 Unix 毫秒)。</summary>
|
||||
public sealed class Rfc3339Converter : JsonConverter<DateTimeOffset>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>汇总:整个时间段由累加器精确合并得出的值(不是各点的平均)。</summary>
|
||||
public sealed record SummaryDto(IReadOnlyDictionary<string, double?> Metrics, IReadOnlyDictionary<string, long> Samples, double Coverage);
|
||||
+1
-1
@@ -6,6 +6,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||
<ProjectReference Include="..\Monitor.Backend.Application\Monitor.Backend.Application.csproj" />
|
||||
<ProjectReference Include="..\MoShiro.Backend.Application\MoShiro.Backend.Application.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>运维端点:<c>/healthz</c>、<c>/metrics</c>(admin 令牌)、安装脚本。</summary>
|
||||
public static class OpsEndpoints
|
||||
@@ -40,8 +40,8 @@ public static class OpsEndpoints
|
||||
if (!principal.IsAdmin)
|
||||
{
|
||||
return principal.CanRead
|
||||
? Problems.Create(http, MonitorErrors.Forbidden, "an admin token is required")
|
||||
: Problems.Create(http, MonitorErrors.Unauthorized, "an admin token is required");
|
||||
? Problems.Create(http, MoShiroErrors.Forbidden, "an admin token is required")
|
||||
: Problems.Create(http, MoShiroErrors.Unauthorized, "an admin token is required");
|
||||
}
|
||||
|
||||
return TypedResults.Text(await ops.RenderPrometheusAsync(http.RequestAborted), "text/plain; version=0.0.4");
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>目录:由代码里的注册表生成。新增 Kind、方案、指标、分组、模型后,目录自动更新,前端不需要改。</summary>
|
||||
public static class CatalogEndpoints
|
||||
@@ -27,12 +27,12 @@ public static class CatalogEndpoints
|
||||
}
|
||||
|
||||
private static IResult Kinds(HttpContext http) =>
|
||||
QueryHelpers.ApplyCatalogCaching(http, string.Join(',', Monitor.Protocol.Kinds.All.Select(k => k.Id)))
|
||||
?? TypedResults.Json(new PageDto<KindDto>([.. Monitor.Protocol.Kinds.All.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoKindDto);
|
||||
QueryHelpers.ApplyCatalogCaching(http, string.Join(',', MoShiro.Protocol.Kinds.All.Select(k => k.Id)))
|
||||
?? TypedResults.Json(new PageDto<KindDto>([.. MoShiro.Protocol.Kinds.All.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoKindDto);
|
||||
|
||||
private static IResult Plans(HttpContext http) =>
|
||||
QueryHelpers.ApplyCatalogCaching(http, string.Join(',', Monitor.Backend.Application.Plans.All.Select(p => p.Id)))
|
||||
?? TypedResults.Json(new PageDto<PlanDto>([.. Monitor.Backend.Application.Plans.All.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoPlanDto);
|
||||
QueryHelpers.ApplyCatalogCaching(http, string.Join(',', MoShiro.Backend.Application.Plans.All.Select(p => p.Id)))
|
||||
?? TypedResults.Json(new PageDto<PlanDto>([.. MoShiro.Backend.Application.Plans.All.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoPlanDto);
|
||||
|
||||
private static IResult Metrics(HttpContext http) =>
|
||||
QueryHelpers.ApplyCatalogCaching(http, string.Join(',', MetricRegistry.All.Select(m => m.Fingerprint)))
|
||||
+5
-5
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>Agent 的主机资源占用:最新值(实时的部分走 SSE 的 <c>hosts</c> 主题)和历史曲线。只有汇总的数字,没有型号和 IP。</summary>
|
||||
public static class HostEndpoints
|
||||
@@ -44,12 +44,12 @@ public static class HostEndpoints
|
||||
AgentView? view = await agents.GetAsync(new NodeId(agent_id), ct);
|
||||
if (view is not { Agent.Status: Fleet.Server.NodeStatus.Active } || !(view.Agent.PublicVisible || PublicGuard.CanSeeHidden(http)))
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.AgentNotFound, "agent not found");
|
||||
return Problems.Create(http, MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
|
||||
if (!RangeResolver.TryParseSpan(range ?? "1h", out long span) || span < Steps.Hour || span > 30 * Steps.Day)
|
||||
{
|
||||
return Problems.From(http, new FleetError(MonitorErrors.InvalidRange, "range must look like 1h, 6h, 24h or 7d (between 1 hour and 30 days)"));
|
||||
return Problems.From(http, new FleetError(MoShiroErrors.InvalidRange, "range must look like 1h, 6h, 24h or 7d (between 1 hour and 30 days)"));
|
||||
}
|
||||
|
||||
long now = time.GetUtcNow().ToUnixTimeMilliseconds();
|
||||
+4
-4
@@ -7,10 +7,10 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>
|
||||
/// 实时推送(SSE):先推送当前快照,再推送增量(不补发历史)。SSE 只负责“更快地获得变化”,
|
||||
@@ -67,7 +67,7 @@ public static class LiveEndpoints
|
||||
string client = http.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
if (!limiter.TryAcquire(client))
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.TooManyStreams, $"at most {options.MaxSsePerClient} live connections per client");
|
||||
return Problems.Create(http, MoShiroErrors.TooManyStreams, $"at most {options.MaxSsePerClient} live connections per client");
|
||||
}
|
||||
|
||||
counters.SseOpened();
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Fleet.Core;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
internal enum LiveTopicKind
|
||||
{
|
||||
@@ -46,7 +46,7 @@ internal sealed record LiveTopic(LiveTopicKind Kind, string Raw, string? Range,
|
||||
|
||||
if (tokens.Count > maxTopics)
|
||||
{
|
||||
return Result<IReadOnlyList<LiveTopic>>.Fail(MonitorErrors.TooManyStreams, $"at most {maxTopics} topics per connection");
|
||||
return Result<IReadOnlyList<LiveTopic>>.Fail(MoShiroErrors.TooManyStreams, $"at most {maxTopics} topics per connection");
|
||||
}
|
||||
|
||||
var topics = new List<LiveTopic>();
|
||||
@@ -83,7 +83,7 @@ internal sealed record LiveTopic(LiveTopicKind Kind, string Raw, string? Range,
|
||||
case "series" when parts.Length == 4:
|
||||
if (!Steps.TryParse(parts[2], out long step) || step > Steps.Minute)
|
||||
{
|
||||
return Result<LiveTopic>.Fail(MonitorErrors.InvalidStep, $"{token}: series topics support steps up to 1m; refetch coarser steps over REST");
|
||||
return Result<LiveTopic>.Fail(MoShiroErrors.InvalidStep, $"{token}: series topics support steps up to 1m; refetch coarser steps over REST");
|
||||
}
|
||||
|
||||
return Result<LiveTopic>.Ok(new LiveTopic(LiveTopicKind.Series, token, null, step, parts[3], parts[1], null));
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using MoShiro.Backend.Application;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>
|
||||
/// 节点的展示信息:系统、架构、国家、运营商。<b>只有这几项,没有任何 IP 地址、城市、ASN、经纬度</b>——
|
||||
@@ -25,7 +25,7 @@ public static class NodeEndpoints
|
||||
|
||||
/// <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));
|
||||
new(MoShiro.Protocol.BuildInfo.Version, Empty(MoShiro.Protocol.BuildInfo.Tag), withCommit ? Empty(MoShiro.Protocol.BuildInfo.Commit) : null, Empty(MoShiro.Protocol.BuildInfo.BuiltAt));
|
||||
|
||||
private static string? Empty(string value) => value.Length == 0 ? null : value;
|
||||
|
||||
+6
-6
@@ -3,11 +3,11 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>展示数据的查询端点:参数解析、校验与缓存头,业务都在 Application.Query。</summary>
|
||||
public static class QueryEndpoints
|
||||
@@ -211,13 +211,13 @@ public static class QueryEndpoints
|
||||
TestDetail? detail = await tests.GetAsync(new TestId(test_id), ct);
|
||||
if (detail is null)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.TestNotFound, "test not found");
|
||||
return Problems.Create(http, MoShiroErrors.TestNotFound, "test not found");
|
||||
}
|
||||
|
||||
Result<AgentView> visible = await QueryHelpers.FindVisibleAgentAsync(http, agents, detail.Test.AgentId.Value, ct);
|
||||
if (!visible.IsSuccess)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.TestNotFound, "test not found");
|
||||
return Problems.Create(http, MoShiroErrors.TestNotFound, "test not found");
|
||||
}
|
||||
|
||||
return TypedResults.Json(Mappers.ToDto(detail), ApiJsonContext.Default.TestDto);
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed class EventService
|
||||
{
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record CreateJoinTokenRequest(string? Note, IReadOnlyList<string>? Labels, bool AutoApprove, int MaxUses, int ExpiresInHours);
|
||||
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>运维:健康检查和 Prometheus 指标(在线数、块入库速率、分析队列长度、重算进度、磁盘占用、SSE 连接数)。</summary>
|
||||
public sealed class OpsService
|
||||
@@ -40,17 +40,17 @@ public sealed class OpsService
|
||||
}
|
||||
|
||||
RecomputeProgress progress = _recompute.Progress;
|
||||
Metric("monitor_agents_online", "Agents with an open control connection.", _gateway.OnlineAgents.Count);
|
||||
Metric("monitor_chunks_ingested_total", "Raw chunks stored.", _counters.ChunksIngested, "counter");
|
||||
Metric("monitor_chunks_duplicate_total", "Raw chunks received more than once.", _counters.ChunksDuplicate, "counter");
|
||||
Metric("monitor_chunks_discarded_total", "Raw chunks rejected as invalid.", _counters.ChunksDiscarded, "counter");
|
||||
Metric("monitor_ingest_bytes_total", "Bytes of raw chunks stored.", _counters.BytesIngested, "counter");
|
||||
Metric("monitor_windows_analyzed_total", "Minute windows computed.", _counters.WindowsAnalyzed, "counter");
|
||||
Metric("monitor_analysis_queue_length", "Minute windows waiting to be computed.", await _metrics.QueueLengthAsync(ct));
|
||||
Metric("monitor_recompute_windows_done", "Windows recomputed by the current backfill.", progress.WindowsDone);
|
||||
Metric("monitor_recompute_windows_total", "Windows to recompute in the current backfill.", progress.WindowsTotal);
|
||||
Metric("monitor_raw_bytes", "Disk used by raw segment files.", _raw.TotalBytes);
|
||||
Metric("monitor_sse_connections", "Open server-sent event connections.", _counters.SseConnections);
|
||||
Metric("moshiro_agents_online", "Agents with an open control connection.", _gateway.OnlineAgents.Count);
|
||||
Metric("moshiro_chunks_ingested_total", "Raw chunks stored.", _counters.ChunksIngested, "counter");
|
||||
Metric("moshiro_chunks_duplicate_total", "Raw chunks received more than once.", _counters.ChunksDuplicate, "counter");
|
||||
Metric("moshiro_chunks_discarded_total", "Raw chunks rejected as invalid.", _counters.ChunksDiscarded, "counter");
|
||||
Metric("moshiro_ingest_bytes_total", "Bytes of raw chunks stored.", _counters.BytesIngested, "counter");
|
||||
Metric("moshiro_windows_analyzed_total", "Minute windows computed.", _counters.WindowsAnalyzed, "counter");
|
||||
Metric("moshiro_analysis_queue_length", "Minute windows waiting to be computed.", await _metrics.QueueLengthAsync(ct));
|
||||
Metric("moshiro_recompute_windows_done", "Windows recomputed by the current backfill.", progress.WindowsDone);
|
||||
Metric("moshiro_recompute_windows_total", "Windows to recompute in the current backfill.", progress.WindowsTotal);
|
||||
Metric("moshiro_raw_bytes", "Disk used by raw segment files.", _raw.TotalBytes);
|
||||
Metric("moshiro_sse_connections", "Open server-sent event connections.", _counters.SseConnections);
|
||||
return text.ToString();
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>原始数据的检索与下载(管理员)。块过了保留期返回 raw_expired,从来不存在返回 chunk_not_found。</summary>
|
||||
public sealed class RawBrowserService
|
||||
@@ -26,7 +26,7 @@ public sealed class RawBrowserService
|
||||
{
|
||||
byte[]? bytes = await _raw.ReadAsync(meta, ct);
|
||||
return bytes is null
|
||||
? Result<(RawChunkMeta, byte[])>.Fail(MonitorErrors.RawExpired, "the raw data has expired")
|
||||
? Result<(RawChunkMeta, byte[])>.Fail(MoShiroErrors.RawExpired, "the raw data has expired")
|
||||
: Result<(RawChunkMeta, byte[])>.Ok((meta, bytes));
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ public sealed class RawBrowserService
|
||||
int days = await _settings.GetIntAsync(SettingsService.TestRawDays, ct);
|
||||
long cutoff = _time.GetUtcNow().ToUnixTimeMilliseconds() - (days * 86_400_000L);
|
||||
return ChunkTimestampMs(id.Value) is { } created && created < cutoff
|
||||
? Result<(RawChunkMeta, byte[])>.Fail(MonitorErrors.RawExpired, "the raw data has expired")
|
||||
: Result<(RawChunkMeta, byte[])>.Fail(MonitorErrors.ChunkNotFound, "chunk not found");
|
||||
? Result<(RawChunkMeta, byte[])>.Fail(MoShiroErrors.RawExpired, "the raw data has expired")
|
||||
: Result<(RawChunkMeta, byte[])>.Fail(MoShiroErrors.ChunkNotFound, "chunk not found");
|
||||
}
|
||||
|
||||
/// <summary>UUIDv7 的前 48 位是毫秒时间戳。</summary>
|
||||
+1
-1
@@ -2,7 +2,7 @@ using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record SettingSpec(string Key, string Type, string Default, long Min, long Max, string Description);
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary><c>monitor.event</c>:Agent 报告的事件(Probe 连上/断开、端点变化、时钟跳变、缺口、CPU 饱和)。</summary>
|
||||
/// <summary><c>moshiro.event</c>:Agent 报告的事件(Probe 连上/断开、端点变化、时钟跳变、缺口、CPU 饱和)。</summary>
|
||||
public sealed class AgentEventService
|
||||
{
|
||||
private readonly AgentRuntime _runtime;
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>Agent 眼中的一个已连接 Probe(Agent 在事件和心跳里上报)。</summary>
|
||||
public sealed record ConnectedProbeInfo(string Id, string? Version, string? Remote, long SinceMs);
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Fleet.Core;
|
||||
using Fleet.Server;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record AgentView(AgentRecord Agent, bool Online, IReadOnlyList<ConnectedProbeInfo> Probes, AgentHeartbeatExtra? Heartbeat, int EndpointChanges, long? HeartbeatAtMs = null, HostSnapshot? Host = null)
|
||||
{
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// <c>monitor.host</c>:Agent 每隔几秒上报的主机资源占用快照。最新值放在内存里并实时推给订阅者;每个 Agent 每分钟存一个样本做历史。
|
||||
/// <c>moshiro.host</c>:Agent 每隔几秒上报的主机资源占用快照。最新值放在内存里并实时推给订阅者;每个 Agent 每分钟存一个样本做历史。
|
||||
/// 快照里没有 CPU 型号、主机名、网卡名和 IP(Agent 根本不采集它们)。
|
||||
/// </summary>
|
||||
public sealed class HostStatusService
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>某一时刻的「线路」开关:自动连接的总开关,加上每一对(Probe,Agent)的手动设置。</summary>
|
||||
public sealed class LinkSnapshot
|
||||
+14
-14
@@ -2,17 +2,17 @@ using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Rpc;
|
||||
using Fleet.Server;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>Monitor 在 Fleet 之上的接入点:注册之后补全公开地址与端口。</summary>
|
||||
public sealed class MonitorEnrollmentHook : IEnrollmentHook
|
||||
/// <summary>MoShiro 在 Fleet 之上的接入点:注册之后补全公开地址与端口。</summary>
|
||||
public sealed class MoShiroEnrollmentHook : IEnrollmentHook
|
||||
{
|
||||
private readonly IAgentStore _agents;
|
||||
private readonly AgentService _service;
|
||||
|
||||
public MonitorEnrollmentHook(IAgentStore agents, AgentService service)
|
||||
public MoShiroEnrollmentHook(IAgentStore agents, AgentService service)
|
||||
{
|
||||
_agents = agents;
|
||||
_service = service;
|
||||
@@ -25,7 +25,7 @@ public sealed class MonitorEnrollmentHook : IEnrollmentHook
|
||||
{
|
||||
try
|
||||
{
|
||||
meta = raw.Deserialize(MonitorJsonContext.Default.AgentEnrollMeta) ?? meta;
|
||||
meta = raw.Deserialize(MoShiroJsonContext.Default.AgentEnrollMeta) ?? meta;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@@ -44,7 +44,7 @@ public sealed class MonitorEnrollmentHook : IEnrollmentHook
|
||||
}
|
||||
|
||||
/// <summary>节点上线、下线、hello、心跳:更新运行时状态,补全 Agent 信息,并推送给 SSE。</summary>
|
||||
public sealed class MonitorNodeEventSink : INodeEventSink
|
||||
public sealed class MoShiroNodeEventSink : INodeEventSink
|
||||
{
|
||||
private readonly IAgentStore _agents;
|
||||
private readonly AgentRuntime _runtime;
|
||||
@@ -54,7 +54,7 @@ public sealed class MonitorNodeEventSink : INodeEventSink
|
||||
private readonly NodeInfoService _nodes;
|
||||
private readonly ReleaseService _releases;
|
||||
|
||||
public MonitorNodeEventSink(IAgentStore agents, AgentRuntime runtime, IEventStore events, LiveHub live, AgentService service, NodeInfoService nodes, ReleaseService releases)
|
||||
public MoShiroNodeEventSink(IAgentStore agents, AgentRuntime runtime, IEventStore events, LiveHub live, AgentService service, NodeInfoService nodes, ReleaseService releases)
|
||||
{
|
||||
_releases = releases;
|
||||
_nodes = nodes;
|
||||
@@ -93,7 +93,7 @@ public sealed class MonitorNodeEventSink : INodeEventSink
|
||||
try
|
||||
{
|
||||
AgentRuntimeState state = _runtime.Get(id);
|
||||
AgentHeartbeatExtra? beat = extra.Deserialize(MonitorJsonContext.Default.AgentHeartbeatExtra);
|
||||
AgentHeartbeatExtra? beat = extra.Deserialize(MoShiroJsonContext.Default.AgentHeartbeatExtra);
|
||||
state.LastHeartbeat = beat;
|
||||
state.LastHeartbeatAtMs = nodeEvent.AtMs;
|
||||
_releases.RecordStatus(id, beat?.Update);
|
||||
@@ -128,7 +128,7 @@ public sealed class MonitorNodeEventSink : INodeEventSink
|
||||
{
|
||||
try
|
||||
{
|
||||
ProbeHelloMeta? meta = raw.Deserialize(MonitorJsonContext.Default.ProbeHelloMeta);
|
||||
ProbeHelloMeta? meta = raw.Deserialize(MoShiroJsonContext.Default.ProbeHelloMeta);
|
||||
await _nodes.SetPlatformAsync(id.Value, NodeKinds.Probe, meta?.Platform, ct);
|
||||
}
|
||||
catch (JsonException)
|
||||
@@ -146,7 +146,7 @@ public sealed class MonitorNodeEventSink : INodeEventSink
|
||||
case NodeEventKind.Heartbeat when nodeEvent.Extra is { } extra:
|
||||
try
|
||||
{
|
||||
_releases.RecordStatus(id, extra.Deserialize(MonitorJsonContext.Default.ProbeHeartbeatExtra)?.Update);
|
||||
_releases.RecordStatus(id, extra.Deserialize(MoShiroJsonContext.Default.ProbeHeartbeatExtra)?.Update);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@@ -162,9 +162,9 @@ public sealed class MonitorNodeEventSink : INodeEventSink
|
||||
var kinds = new List<string>();
|
||||
foreach (string capability in hello.Capabilities)
|
||||
{
|
||||
if (capability.StartsWith(MonitorCapabilities.KindsPrefix, StringComparison.Ordinal))
|
||||
if (capability.StartsWith(MoShiroCapabilities.KindsPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
kinds.AddRange(capability[MonitorCapabilities.KindsPrefix.Length..].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
kinds.AddRange(capability[MoShiroCapabilities.KindsPrefix.Length..].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public sealed class MonitorNodeEventSink : INodeEventSink
|
||||
{
|
||||
try
|
||||
{
|
||||
meta = raw.Deserialize(MonitorJsonContext.Default.AgentHelloMeta) ?? meta;
|
||||
meta = raw.Deserialize(MoShiroJsonContext.Default.AgentHelloMeta) ?? meta;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
+10
-10
@@ -2,9 +2,9 @@ using System.Text.RegularExpressions;
|
||||
using Fleet.Core;
|
||||
using Fleet.Identity;
|
||||
using Fleet.Server;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>本地硬上限:写在 Probe 本机的配置里,面板无法修改(在路由器上建议调低)。</summary>
|
||||
public sealed record ProbeLimits(double MaxTestMbps, long MaxTotalPps);
|
||||
@@ -56,7 +56,7 @@ public sealed partial class ProbeService
|
||||
|
||||
if ((await _probes.ListAsync(ct)).Any(p => string.Equals(p.Name, finalName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return Result<ProbeIdentityFile>.Fail(MonitorErrors.NameConflict, $"a probe named {finalName} already exists");
|
||||
return Result<ProbeIdentityFile>.Fail(MoShiroErrors.NameConflict, $"a probe named {finalName} already exists");
|
||||
}
|
||||
|
||||
using NodeIdentity identity = NodeIdentity.Generate();
|
||||
@@ -80,7 +80,7 @@ public sealed partial class ProbeService
|
||||
{
|
||||
if (await _probes.GetAsync(id, ct) is not { } probe)
|
||||
{
|
||||
return Result<ProbeView>.Fail(MonitorErrors.ProbeNotFound, "probe not found");
|
||||
return Result<ProbeView>.Fail(MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
}
|
||||
|
||||
string trimmed = name.Trim();
|
||||
@@ -91,7 +91,7 @@ public sealed partial class ProbeService
|
||||
|
||||
if ((await _probes.ListAsync(ct)).Any(p => p.Id != id && string.Equals(p.Name, trimmed, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return Result<ProbeView>.Fail(MonitorErrors.NameConflict, $"a probe named {trimmed} already exists");
|
||||
return Result<ProbeView>.Fail(MoShiroErrors.NameConflict, $"a probe named {trimmed} already exists");
|
||||
}
|
||||
|
||||
await _probes.SaveAsync(probe with { Name = trimmed }, ct);
|
||||
@@ -103,12 +103,12 @@ public sealed partial class ProbeService
|
||||
{
|
||||
if (await _probes.GetAsync(id, ct) is not { } probe)
|
||||
{
|
||||
return Result<ProbeIdentityFile>.Fail(MonitorErrors.ProbeNotFound, "probe not found");
|
||||
return Result<ProbeIdentityFile>.Fail(MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
}
|
||||
|
||||
if (probe.RevokedAtMs is not null)
|
||||
{
|
||||
return Result<ProbeIdentityFile>.Fail(MonitorErrors.ProbeRevoked, "a revoked probe cannot get a new key; create a new probe instead");
|
||||
return Result<ProbeIdentityFile>.Fail(MoShiroErrors.ProbeRevoked, "a revoked probe cannot get a new key; create a new probe instead");
|
||||
}
|
||||
|
||||
using NodeIdentity identity = NodeIdentity.Generate();
|
||||
@@ -124,7 +124,7 @@ public sealed partial class ProbeService
|
||||
{
|
||||
if (await _probes.GetAsync(id, ct) is null)
|
||||
{
|
||||
return Result<ProbeView>.Fail(MonitorErrors.ProbeNotFound, "probe not found");
|
||||
return Result<ProbeView>.Fail(MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
}
|
||||
|
||||
await _probes.RevokeAsync(id, _time.GetUtcNow().ToUnixTimeMilliseconds(), ct);
|
||||
@@ -151,12 +151,12 @@ public sealed partial class ProbeService
|
||||
{
|
||||
if (await _probes.GetAsync(probeId, ct) is null)
|
||||
{
|
||||
return Result<bool>.Fail(MonitorErrors.ProbeNotFound, "probe not found");
|
||||
return Result<bool>.Fail(MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
}
|
||||
|
||||
if (await _agents.GetAsync(agentId, ct) is null)
|
||||
{
|
||||
return Result<bool>.Fail(MonitorErrors.AgentNotFound, "agent not found");
|
||||
return Result<bool>.Fail(MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
|
||||
return await _gateway.DisconnectProbeAsync(agentId, new ProbeId(probeId), ct);
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using Fleet.Core;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Monitor.Backend.Analysis;
|
||||
using MoShiro.Backend.Analysis;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 持久路径:分钟窗口就绪(窗口结束 + 宽限,或迟到的块到达)之后从原始块计算 → 写入 metric_1m → 标记降采样脏桶;
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using Fleet.Core;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Monitor.Backend.Analysis;
|
||||
using MoShiro.Backend.Analysis;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record RecomputeProgress(string State, IReadOnlyList<string> Metrics, long WindowsDone, long WindowsTotal, long? FromMs, long? ToMs);
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 从原始块重新计算一个窗口(持久路径与回填共用):读取与窗口重叠的块 → 评估 → 取出窗口对应的累加器。
|
||||
+4
-4
@@ -2,7 +2,7 @@ using Fleet.Server;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public static class ApplicationServiceCollectionExtensions
|
||||
{
|
||||
@@ -10,7 +10,7 @@ public static class ApplicationServiceCollectionExtensions
|
||||
/// 注册 Application 层的全部服务。需要事先注册端口的实现(存储、<see cref="IAgentGateway"/>)和 <see cref="TimeProvider"/>。
|
||||
/// 后台任务按这里的顺序启动:<see cref="StartupTasks"/> 必须在最前面。
|
||||
/// </summary>
|
||||
public static IServiceCollection AddMonitorApplication(this IServiceCollection services, BackendOptions options)
|
||||
public static IServiceCollection AddMoShiroApplication(this IServiceCollection services, BackendOptions options)
|
||||
{
|
||||
services.AddSingleton(options);
|
||||
services.AddHostedService<StartupTasks>();
|
||||
@@ -47,8 +47,8 @@ public static class ApplicationServiceCollectionExtensions
|
||||
services.AddSingleton<RowLoader>();
|
||||
services.AddSingleton<QueryService>();
|
||||
|
||||
services.AddSingleton<IEnrollmentHook, MonitorEnrollmentHook>();
|
||||
services.AddSingleton<INodeEventSink, MonitorNodeEventSink>();
|
||||
services.AddSingleton<IEnrollmentHook, MoShiroEnrollmentHook>();
|
||||
services.AddSingleton<INodeEventSink, MoShiroNodeEventSink>();
|
||||
|
||||
services.AddSingleton<AnalysisScheduler>();
|
||||
services.AddHostedService(provider => provider.GetRequiredService<AnalysisScheduler>());
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public enum PrincipalKind
|
||||
{
|
||||
+4
-4
@@ -2,9 +2,9 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>面板用户、Cookie 会话、API 令牌。密码用 PBKDF2-SHA256,令牌和会话只保存 SHA-256。</summary>
|
||||
public sealed partial class AuthService
|
||||
@@ -34,7 +34,7 @@ public sealed partial class AuthService
|
||||
byte[] hash = Hash(password, salt, iterations);
|
||||
if (user is null || !CryptographicOperations.FixedTimeEquals(hash, user.PasswordHash))
|
||||
{
|
||||
return Result<LoginResult>.Fail(MonitorErrors.Unauthorized, "invalid credentials");
|
||||
return Result<LoginResult>.Fail(MoShiroErrors.Unauthorized, "invalid credentials");
|
||||
}
|
||||
|
||||
byte[] token = RandomNumberGenerator.GetBytes(32);
|
||||
@@ -111,7 +111,7 @@ public sealed partial class AuthService
|
||||
var user = new UserRecord(Ulid.NewPrefixed("usr", _time), username, Hash(password, salt, _options.Pbkdf2Iterations), salt, _options.Pbkdf2Iterations, _time.GetUtcNow().ToUnixTimeMilliseconds());
|
||||
return await _store.CreateUserAsync(user, ct)
|
||||
? Result<UserRecord>.Ok(user)
|
||||
: Result<UserRecord>.Fail(MonitorErrors.NameConflict, "username already exists");
|
||||
: Result<UserRecord>.Fail(MoShiroErrors.NameConflict, "username already exists");
|
||||
}
|
||||
|
||||
public async ValueTask<Result<bool>> ChangePasswordAsync(string userId, string newPassword, CancellationToken ct)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>登录限流:每个来源在滑动的一分钟内最多尝试若干次。</summary>
|
||||
public sealed class LoginRateLimiter
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>进程内计数器,用于 <c>/metrics</c>(Prometheus 格式)。</summary>
|
||||
public sealed class BackendCounters
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>Backend 的可调参数(保留期等也可以通过 settings 改,这里是默认值与硬上限)。</summary>
|
||||
public sealed class BackendOptions
|
||||
{
|
||||
/// <summary>数据目录:<c>monitor.db</c> 与 <c>raw/</c>。</summary>
|
||||
/// <summary>数据目录:<c>moshiro.db</c> 与 <c>raw/</c>。</summary>
|
||||
public string DataDirectory { get; set; } = "data";
|
||||
|
||||
/// <summary>面板的对外地址(用于生成安装命令)。</summary>
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
using Fleet.Core;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Stored"/> 和 <see cref="Duplicate"/> 都会让节点把消息从发件箱里移除。
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Fleet.Core;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 1 秒精度的内存热层:最近 1 小时的累加器。桶以 <see cref="AccumulatorCodec"/> 编码后的字节保存,
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using Fleet.Core;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>重启后热层为空:从原始块回填最近 1 小时(低优先级、逐块让出)。完成之前查询规划器不会选用热层。</summary>
|
||||
public sealed partial class HotLayerBackfill : BackgroundService
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Channels;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>发布到 SSE 订阅者的信号。SSE 只负责“更快地获得变化”,推送的内容都能由 REST 查询重建。</summary>
|
||||
public abstract record LiveSignal;
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<ProjectReference Include="..\Monitor.Backend.Analysis\Monitor.Backend.Analysis.csproj" />
|
||||
<ProjectReference Include="..\MoShiro.Backend.Analysis\MoShiro.Backend.Analysis.csproj" />
|
||||
<ProjectReference Include="..\..\fleet\src\Fleet.Server.Abstractions\Fleet.Server.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\fleet\src\Fleet.Identity\Fleet.Identity.csproj" />
|
||||
</ItemGroup>
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using Fleet.Core;
|
||||
using Fleet.Server;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record AgentRecord(
|
||||
NodeId Id,
|
||||
@@ -45,7 +45,7 @@ public sealed record AgentHello(
|
||||
int? ProbeTcpPort,
|
||||
IReadOnlyList<int>? ProbeUdpPorts);
|
||||
|
||||
/// <summary>注册之后 Monitor 的补充:公开地址和端口。</summary>
|
||||
/// <summary>注册之后 MoShiro 的补充:公开地址和端口。</summary>
|
||||
public sealed record AgentEnrollment(string PublicHost, int ProbeTcpPort, IReadOnlyList<int> ProbeUdpPorts, IReadOnlyList<string> Kinds);
|
||||
|
||||
public sealed record JoinTokenInfo(
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>原始块的索引信息(块本身在段文件里)。</summary>
|
||||
public sealed record RawChunkMeta(
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record TaskRecord(
|
||||
TaskId Id,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 把「ASN 和组织名」归成几个常见的国内运营商:mobile / telecom / unicom / broadnet;别的是 other(显示组织名)。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 一个节点(Agent 或 Probe)用来展示的信息:系统和架构、国家、运营商,以及它自己上报的构建信息(版本、tag、提交、构建时间)。
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 节点的展示信息:系统和架构(节点自报),国家和运营商(Backend 用节点的来源地址查一次)。
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Application 通往 Agent 连接的端口(由 Gateway 实现):下发状态变化、发起按需测试。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record UserRecord(string Id, string Username, byte[] PasswordHash, byte[] PasswordSalt, int Iterations, long CreatedAtMs);
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
using Fleet.Core;
|
||||
using Fleet.Server;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public interface IAgentStore
|
||||
{
|
||||
@@ -137,7 +137,7 @@ public interface IEventStore
|
||||
/// <summary>主机资源占用的历史:每个 Agent 每分钟一个样本。</summary>
|
||||
public interface IHostStore
|
||||
{
|
||||
ValueTask SaveAsync(NodeId agent, long minuteMs, Monitor.Protocol.HostSnapshot snapshot, CancellationToken ct);
|
||||
ValueTask SaveAsync(NodeId agent, long minuteMs, MoShiro.Protocol.HostSnapshot snapshot, CancellationToken ct);
|
||||
|
||||
ValueTask<IReadOnlyList<HostSample>> ReadAsync(NodeId agent, long fromMs, long toMs, CancellationToken ct);
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Backend.Analysis;
|
||||
using MoShiro.Backend.Analysis;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>把源行合并成查询步长的桶:计数相加、直方图逐桶相加,所以结果是精确的,不是各点的平均。</summary>
|
||||
public sealed class BucketedRows
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.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)
|
||||
@@ -68,7 +68,7 @@ public sealed class LineService
|
||||
{
|
||||
if (!LineKey.TryParse(id, out LineKey key))
|
||||
{
|
||||
return Result<LineView>.Fail(MonitorErrors.AgentNotFound, "line not found");
|
||||
return Result<LineView>.Fail(MoShiroErrors.AgentNotFound, "line not found");
|
||||
}
|
||||
|
||||
agentPart = key.Agent.Value;
|
||||
@@ -78,7 +78,7 @@ public sealed class LineService
|
||||
AgentView? agent = await _agents.GetAsync(new NodeId(agentPart), ct);
|
||||
if (agent is null)
|
||||
{
|
||||
return Result<LineView>.Fail(MonitorErrors.AgentNotFound, "line not found");
|
||||
return Result<LineView>.Fail(MoShiroErrors.AgentNotFound, "line not found");
|
||||
}
|
||||
|
||||
IReadOnlyList<ProbeRecord> probes = await _probes.ListAsync(ct);
|
||||
@@ -88,7 +88,7 @@ public sealed class LineService
|
||||
probe = probes.FirstOrDefault(p => p.Id == probePart);
|
||||
if (probe is null)
|
||||
{
|
||||
return Result<LineView>.Fail(MonitorErrors.ProbeNotFound, "probe not found");
|
||||
return Result<LineView>.Fail(MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -96,7 +96,7 @@ public sealed class LineService
|
||||
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");
|
||||
return Result<LineView>.Fail(MoShiroErrors.ProbeRequired, active.Length == 0 ? "there is no active probe" : "several probes exist: use <agent_id>~<probe_id> as the line id");
|
||||
}
|
||||
|
||||
probe = active[0];
|
||||
+7
-7
@@ -1,8 +1,8 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>请求的指标:导出 id(<c>rtt.p95</c>)与评分 id(<c>score.mc</c>)。</summary>
|
||||
public sealed record MetricSelection(IReadOnlyList<string> ExportIds, IReadOnlyList<string> ScoreModelIds)
|
||||
@@ -21,7 +21,7 @@ public sealed record MetricSelection(IReadOnlyList<string> ExportIds, IReadOnlyL
|
||||
PresetDef? preset = MetricRegistry.FindPreset(presetId);
|
||||
if (preset is null)
|
||||
{
|
||||
return Result<MetricSelection>.Fail(MonitorErrors.UnknownPreset, $"unknown preset {presetId}");
|
||||
return Result<MetricSelection>.Fail(MoShiroErrors.UnknownPreset, $"unknown preset {presetId}");
|
||||
}
|
||||
|
||||
ids.AddRange(preset.MetricIds);
|
||||
@@ -36,7 +36,7 @@ public sealed record MetricSelection(IReadOnlyList<string> ExportIds, IReadOnlyL
|
||||
ids = [.. ids.Distinct(StringComparer.Ordinal)];
|
||||
if (ids.Count > maxMetrics)
|
||||
{
|
||||
return Result<MetricSelection>.Fail(MonitorErrors.TooManyMetrics, $"at most {maxMetrics} metrics per request");
|
||||
return Result<MetricSelection>.Fail(MoShiroErrors.TooManyMetrics, $"at most {maxMetrics} metrics per request");
|
||||
}
|
||||
|
||||
var exports = new List<string>();
|
||||
@@ -48,7 +48,7 @@ public sealed record MetricSelection(IReadOnlyList<string> ExportIds, IReadOnlyL
|
||||
string model = id["score.".Length..];
|
||||
if (ModelRegistry.Find(model) is null)
|
||||
{
|
||||
return Result<MetricSelection>.Fail(MonitorErrors.UnknownMetric, $"unknown metric {id}");
|
||||
return Result<MetricSelection>.Fail(MoShiroErrors.UnknownMetric, $"unknown metric {id}");
|
||||
}
|
||||
|
||||
scores.Add(model);
|
||||
@@ -59,7 +59,7 @@ public sealed record MetricSelection(IReadOnlyList<string> ExportIds, IReadOnlyL
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result<MetricSelection>.Fail(MonitorErrors.UnknownMetric, $"unknown metric {id}");
|
||||
return Result<MetricSelection>.Fail(MoShiroErrors.UnknownMetric, $"unknown metric {id}");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Backend.Analysis;
|
||||
using MoShiro.Backend.Analysis;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record SummaryResult(
|
||||
IReadOnlyDictionary<string, double?> Values,
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 展示数据查询:任意时间段、自动步长、数据分层与现场合并、精确汇总、列式结果。
|
||||
@@ -185,7 +185,7 @@ public sealed class QueryService
|
||||
|
||||
if (!MetricRegistry.TryFindExport(exportId, out _, out _))
|
||||
{
|
||||
return Result<CompareResult>.Fail(MonitorErrors.UnknownMetric, $"unknown metric {exportId}");
|
||||
return Result<CompareResult>.Fail(MoShiroErrors.UnknownMetric, $"unknown metric {exportId}");
|
||||
}
|
||||
|
||||
Result<(QueryRange Range, SourcePlan Plan)> planned = Plan(request, "1h");
|
||||
@@ -229,7 +229,7 @@ public sealed class QueryService
|
||||
MetricDef? def = MetricRegistry.Find(metricId);
|
||||
if (def is null || def.Kind != AccKind.Histogram)
|
||||
{
|
||||
return Result<DistributionResult>.Fail(MonitorErrors.UnknownMetric, $"{metricId} is not a histogram metric");
|
||||
return Result<DistributionResult>.Fail(MoShiroErrors.UnknownMetric, $"{metricId} is not a histogram metric");
|
||||
}
|
||||
|
||||
Result<(QueryRange Range, SourcePlan Plan)> planned = Plan(request, "15m");
|
||||
@@ -273,7 +273,7 @@ public sealed class QueryService
|
||||
MetricDef? def = MetricRegistry.Find(metricId);
|
||||
if (def is null || def.Kind != AccKind.Histogram)
|
||||
{
|
||||
return Result<HeatmapResult>.Fail(MonitorErrors.UnknownMetric, $"{metricId} is not a histogram metric");
|
||||
return Result<HeatmapResult>.Fail(MoShiroErrors.UnknownMetric, $"{metricId} is not a histogram metric");
|
||||
}
|
||||
|
||||
Result<(QueryRange Range, SourcePlan Plan)> planned = Plan(request, "6h");
|
||||
+7
-7
@@ -1,9 +1,9 @@
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <param name="Anchor"><c>now</c>(默认)或 <c>latest</c>:相对时间段(<c>range=</c>)以当前时间还是以这条线路最后一个样本为终点。后者让离线的线路显示它最后一段数据,而不是空图。</param>
|
||||
public sealed record RangeRequest(string? Range, DateTimeOffset? From, DateTimeOffset? To, string? Step, string? TimeZone, string? Anchor = null);
|
||||
@@ -63,7 +63,7 @@ public sealed partial class RangeResolver
|
||||
{
|
||||
if (!TryParseSpan(request.Range ?? defaultRange, out long span))
|
||||
{
|
||||
return Result<QueryRange>.Fail(MonitorErrors.InvalidRange, "range must look like 5m, 6h or 7d");
|
||||
return Result<QueryRange>.Fail(MoShiroErrors.InvalidRange, "range must look like 5m, 6h or 7d");
|
||||
}
|
||||
|
||||
to = nowMs;
|
||||
@@ -73,12 +73,12 @@ public sealed partial class RangeResolver
|
||||
|
||||
if (from >= to)
|
||||
{
|
||||
return Result<QueryRange>.Fail(MonitorErrors.InvalidRange, "from must be earlier than to");
|
||||
return Result<QueryRange>.Fail(MoShiroErrors.InvalidRange, "from must be earlier than to");
|
||||
}
|
||||
|
||||
if (to - from > MaxSpanMs)
|
||||
{
|
||||
return Result<QueryRange>.Fail(MonitorErrors.RangeTooLarge, "the range is longer than 400 days");
|
||||
return Result<QueryRange>.Fail(MoShiroErrors.RangeTooLarge, "the range is longer than 400 days");
|
||||
}
|
||||
|
||||
long step;
|
||||
@@ -88,14 +88,14 @@ public sealed partial class RangeResolver
|
||||
}
|
||||
else if (!Steps.TryParse(request.Step, out step))
|
||||
{
|
||||
return Result<QueryRange>.Fail(MonitorErrors.InvalidStep, "step must be one of: " + string.Join(", ", Steps.Common.Select(Steps.Format)));
|
||||
return Result<QueryRange>.Fail(MoShiroErrors.InvalidStep, "step must be one of: " + string.Join(", ", Steps.Common.Select(Steps.Format)));
|
||||
}
|
||||
|
||||
var range = new QueryRange(Steps.FloorTo(from, step), Steps.CeilTo(to, step), step, relative, from, to);
|
||||
if (range.Points > _options.MaxPointsPerSeries)
|
||||
{
|
||||
long suggested = Steps.Common.FirstOrDefault(s => (Steps.CeilTo(to, s) - Steps.FloorTo(from, s)) / s <= _options.MaxPointsPerSeries, Steps.Day);
|
||||
return Result<QueryRange>.Fail(MonitorErrors.RangeTooLarge, $"{range.Points} points exceed the limit of {_options.MaxPointsPerSeries}; use step={Steps.Format(suggested)} or a shorter range");
|
||||
return Result<QueryRange>.Fail(MoShiroErrors.RangeTooLarge, $"{range.Points} points exceed the limit of {_options.MaxPointsPerSeries}; use step={Steps.Format(suggested)} or a shorter range");
|
||||
}
|
||||
|
||||
return Result<QueryRange>.Ok(range);
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public readonly record struct SourceRow(long Start, MetricSet Set, bool Partial);
|
||||
|
||||
@@ -92,7 +92,7 @@ public sealed class RowLoader
|
||||
case DataSource.Raw:
|
||||
if (!_gate.TryEnter())
|
||||
{
|
||||
return Result<IReadOnlyList<SourceRow>>.Fail(MonitorErrors.TooManyRequests, "too many queries are reading raw data; retry shortly or use a coarser step");
|
||||
return Result<IReadOnlyList<SourceRow>>.Fail(MoShiroErrors.TooManyRequests, "too many queries are reading raw data; retry shortly or use a coarser step");
|
||||
}
|
||||
|
||||
try
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public enum DataSource
|
||||
{
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>常用步长:1s 2s 5s 10s 15s 30s 1m 2m 5m 10m 15m 30m 1h 2h 3h 6h 12h 1d。</summary>
|
||||
public static class Steps
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>保留清理:原始段文件(持续任务与按需测试保留期不同)、各层指标、事件。保留期来自 settings,可以在线修改。</summary>
|
||||
public sealed partial class RetentionService : BackgroundService
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 启动时最先执行:截断没有索引记录的段文件尾部、创建初始管理员、创建默认的线路任务模板。
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record EffectiveTask(
|
||||
TaskId Id,
|
||||
+3
-3
@@ -2,9 +2,9 @@ using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Server;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record DesiredTaskSet(long Version, TaskSet Document);
|
||||
|
||||
@@ -65,7 +65,7 @@ public sealed class TaskPlanner : IDisposable
|
||||
document.Peers.Add(new PeerSpec { ProbeId = new ProbeId(probe.Id), PublicKey = Convert.ToBase64String(probe.PublicKey) });
|
||||
}
|
||||
|
||||
string hash = Convert.ToHexStringLower(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(document, MonitorJsonContext.Default.TaskSet)));
|
||||
string hash = Convert.ToHexStringLower(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(document, MoShiroJsonContext.Default.TaskSet)));
|
||||
|
||||
await _gate.WaitAsync(ct);
|
||||
try
|
||||
+7
-7
@@ -2,9 +2,9 @@ using System.Net;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Server;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record TaskInput(
|
||||
string Scope,
|
||||
@@ -97,7 +97,7 @@ public sealed class TaskService
|
||||
TaskRecord? current = await _tasks.GetAsync(id, ct);
|
||||
if (current is null)
|
||||
{
|
||||
return Result<TaskRecord>.Fail(MonitorErrors.TaskNotFound, "task not found");
|
||||
return Result<TaskRecord>.Fail(MoShiroErrors.TaskNotFound, "task not found");
|
||||
}
|
||||
|
||||
IReadOnlyDictionary<string, JsonElement> parameters = patch.Params ?? current.Params;
|
||||
@@ -178,7 +178,7 @@ public sealed class TaskService
|
||||
KindSpec? spec = Kinds.Find(kind);
|
||||
if (spec is null)
|
||||
{
|
||||
return Result<ValidatedTask>.Fail(MonitorErrors.UnknownKind, $"unknown kind {kind}");
|
||||
return Result<ValidatedTask>.Fail(MoShiroErrors.UnknownKind, $"unknown kind {kind}");
|
||||
}
|
||||
|
||||
Scope wanted = scope == EffectiveTasks.LineScope ? Scope.Line : Scope.Target;
|
||||
@@ -206,7 +206,7 @@ public sealed class TaskService
|
||||
|
||||
if (agentId is { } id && await _agents.GetAsync(id, ct) is null)
|
||||
{
|
||||
return Result<ValidatedTask>.Fail(MonitorErrors.AgentNotFound, "agent not found");
|
||||
return Result<ValidatedTask>.Fail(MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
|
||||
if (scope == EffectiveTasks.TargetScope)
|
||||
@@ -259,7 +259,7 @@ public sealed class TaskService
|
||||
long bits = EffectiveTasks.ContinuousLineBudget(EffectiveTasks.Compute(after, agent with { Kinds = [] }, filterByAgentKinds: false));
|
||||
if (bits > ProtocolLimits.MaxLineBudgetBitsPerSecond)
|
||||
{
|
||||
return Result<bool>.Fail(MonitorErrors.BudgetExceeded, $"continuous tasks for {agent.Name} would use {bits / 1000} kbps (limit {ProtocolLimits.MaxLineBudgetBitsPerSecond / 1000} kbps)");
|
||||
return Result<bool>.Fail(MoShiroErrors.BudgetExceeded, $"continuous tasks for {agent.Name} would use {bits / 1000} kbps (limit {ProtocolLimits.MaxLineBudgetBitsPerSecond / 1000} kbps)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ public sealed class TaskService
|
||||
long bits = EffectiveTasks.ContinuousLineBudget(EffectiveTasks.Compute(after, virtualAgent, filterByAgentKinds: false));
|
||||
if (bits > ProtocolLimits.MaxLineBudgetBitsPerSecond)
|
||||
{
|
||||
return Result<bool>.Fail(MonitorErrors.BudgetExceeded, $"continuous line templates would use {bits / 1000} kbps (limit {ProtocolLimits.MaxLineBudgetBitsPerSecond / 1000} kbps)");
|
||||
return Result<bool>.Fail(MoShiroErrors.BudgetExceeded, $"continuous line templates would use {bits / 1000} kbps (limit {ProtocolLimits.MaxLineBudgetBitsPerSecond / 1000} kbps)");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record PhaseSpec(string Kind, IReadOnlyDictionary<string, JsonElement> Overrides, int DurationSeconds);
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
using Fleet.Core;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 后台任务:给已成功结束的测试算结果(所有阶段的块到达之后,或结束一分钟后以现有的块为准),
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Monitor.Backend.Analysis;
|
||||
using MoShiro.Backend.Analysis;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed class StoredTestMetrics
|
||||
{
|
||||
+12
-12
@@ -1,10 +1,10 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Server;
|
||||
using Monitor.Backend.Analysis;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>按需测试(once):校验与排队、下发给 Agent、状态更新、结果查询。</summary>
|
||||
public sealed class TestService
|
||||
@@ -33,18 +33,18 @@ public sealed class TestService
|
||||
PlanSpec? plan = Plans.Find(request.Plan);
|
||||
if (plan is null)
|
||||
{
|
||||
return Result<TestRecord>.Fail(MonitorErrors.UnknownPlan, $"unknown plan {request.Plan}");
|
||||
return Result<TestRecord>.Fail(MoShiroErrors.UnknownPlan, $"unknown plan {request.Plan}");
|
||||
}
|
||||
|
||||
AgentRecord? agent = await _agents.GetAsync(agentId, ct);
|
||||
if (agent is null)
|
||||
{
|
||||
return Result<TestRecord>.Fail(MonitorErrors.AgentNotFound, "agent not found");
|
||||
return Result<TestRecord>.Fail(MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
|
||||
if (agent.Status != NodeStatus.Active)
|
||||
{
|
||||
return Result<TestRecord>.Fail(MonitorErrors.AgentNotActive, "agent is not active");
|
||||
return Result<TestRecord>.Fail(MoShiroErrors.AgentNotActive, "agent is not active");
|
||||
}
|
||||
|
||||
// Agent 在 hello 里声明了它支持的采集方法:方案里有它不支持的就不下发(否则要等到阶段开始才失败)
|
||||
@@ -62,17 +62,17 @@ public sealed class TestService
|
||||
|
||||
if (!_gateway.IsOnline(agentId))
|
||||
{
|
||||
return Result<TestRecord>.Fail(MonitorErrors.AgentOffline, "agent is offline");
|
||||
return Result<TestRecord>.Fail(MoShiroErrors.AgentOffline, "agent is offline");
|
||||
}
|
||||
|
||||
if (!(_runtime.TryGet(agentId, out AgentRuntimeState? state) && state.IsConnected(probeId.Value)))
|
||||
{
|
||||
return Result<TestRecord>.Fail(MonitorErrors.ProbeOffline, $"probe {probeId.Value} has no session with this agent");
|
||||
return Result<TestRecord>.Fail(MoShiroErrors.ProbeOffline, $"probe {probeId.Value} has no session with this agent");
|
||||
}
|
||||
|
||||
if (await _tests.HasActiveAsync(agentId, probeId, ct))
|
||||
{
|
||||
return Result<TestRecord>.Fail(MonitorErrors.TestBusy, "another test is already running on this line");
|
||||
return Result<TestRecord>.Fail(MoShiroErrors.TestBusy, "another test is already running on this line");
|
||||
}
|
||||
|
||||
var testId = new TestId(Ulid.NewPrefixed(TestId.Prefix, _time));
|
||||
@@ -96,7 +96,7 @@ public sealed class TestService
|
||||
TestRecord? test = await _tests.GetAsync(id, ct);
|
||||
if (test is null)
|
||||
{
|
||||
return Result<TestRecord>.Fail(MonitorErrors.TestNotFound, "test not found");
|
||||
return Result<TestRecord>.Fail(MoShiroErrors.TestNotFound, "test not found");
|
||||
}
|
||||
|
||||
if (IsTerminal(test.Status))
|
||||
@@ -115,7 +115,7 @@ public sealed class TestService
|
||||
return Result<TestRecord>.Ok(await _tests.GetAsync(id, ct) ?? test);
|
||||
}
|
||||
|
||||
/// <summary><c>monitor.test.status</c>:只接受该测试所属 Agent 的汇报。</summary>
|
||||
/// <summary><c>moshiro.test.status</c>:只接受该测试所属 Agent 的汇报。</summary>
|
||||
public async ValueTask OnStatusAsync(NodeId agent, TestStatusParams status, CancellationToken ct)
|
||||
{
|
||||
TestRecord? test = await _tests.GetAsync(status.TestId, ct);
|
||||
@@ -244,7 +244,7 @@ public sealed class TestService
|
||||
long bits = kind.BudgetBitsPerSecond(resolved.Values!);
|
||||
if (limitMbps is > 0 && bits > limitMbps * 1_000_000)
|
||||
{
|
||||
return Result<List<TestPhase>>.Fail(MonitorErrors.BudgetExceeded, $"{phase.Kind} needs {bits / 1_000_000.0:0.#} Mbit/s but the agent allows {limitMbps} Mbit/s");
|
||||
return Result<List<TestPhase>>.Fail(MoShiroErrors.BudgetExceeded, $"{phase.Kind} needs {bits / 1_000_000.0:0.#} Mbit/s but the agent allows {limitMbps} Mbit/s");
|
||||
}
|
||||
|
||||
int duration = resolved.Values!.Values.TryGetValue("duration_s", out JsonElement d) && d.TryGetInt64(out long seconds) ? (int)seconds : Math.Max(1, (int)Math.Round(phase.DurationSeconds * phaseScale));
|
||||
+3
-3
@@ -3,9 +3,9 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>面板记下的、CI 签名的发布清单(只留每个组件最新的一份)。<c>Manifest</c> 是签名对象的原始文本,面板一个字节也不改。</summary>
|
||||
public sealed record ReleaseOffer(string Component, long Seq, string Tag, string Version, string Digest, string Manifest, string Signature);
|
||||
@@ -42,7 +42,7 @@ public sealed partial class ReleaseService
|
||||
ReleaseOffer? current = await GetAsync(offer.Component, ct);
|
||||
if (current is not null && offer.Seq <= current.Seq)
|
||||
{
|
||||
return Result<ReleaseOffer>.Fail(MonitorErrors.NameConflict, $"a release with seq {current.Seq} or newer is already registered");
|
||||
return Result<ReleaseOffer>.Fail(MoShiroErrors.NameConflict, $"a release with seq {current.Seq} or newer is already registered");
|
||||
}
|
||||
|
||||
await _settings.SetAsync(Key(offer.Component, "manifest"), offer.Manifest, ct);
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Application;
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>校验失败,带逐字段的原因(键是 JSON 字段路径)。Api 层把它映射成 problem+json 的 <c>errors</c>。</summary>
|
||||
public sealed record ValidationError(string Message, IReadOnlyDictionary<string, string> Fields)
|
||||
: FleetError(MonitorErrors.ValidationFailed, Message)
|
||||
: FleetError(MoShiroErrors.ValidationFailed, Message)
|
||||
{
|
||||
public static ValidationError Single(string field, string message) =>
|
||||
new("Validation failed", new Dictionary<string, string> { [field] = message });
|
||||
Loaded 100 of 309 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user