refactor!: rename the project from Monitor to MoShiro [CI]
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:
justme committed 2026-10-07 02:03:25 +08:00
1 parent 27ac7fb787
commit 2e146d4da7
309 files changed
+1565 -1572

No files matched your search

@@ -0,0 +1,144 @@
using System.Text.Json;
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);
public sealed record LoginResponse(string UserId, string Username, string CsrfToken, DateTimeOffset ExpiresAt, string? SessionToken);
public sealed record MeDto(string Kind, string Scope, string? UserId, string? Username, string? CsrfToken);
public sealed record LocalLimitsDto(double MaxTotalMbps, long MaxTotalPps, IReadOnlyList<string> AllowedTargetCidrs, int MaxConcurrentTests);
public sealed record RejectedDto(string Id, string Reason);
public sealed record HeartbeatDto(int Sessions, long PpsIn, long PpsOut, long DroppedInvalid, long DroppedRateLimited, long QueueBytes, DateTimeOffset At);
public sealed record AgentDto(
string Id,
string Name,
string Status,
bool Online,
bool ProbeConnected,
IReadOnlyList<ConnectedProbeDto> Probes,
HostDto? Host,
string PublicHost,
int ProbeTcpPort,
IReadOnlyList<int> ProbeUdpPorts,
IReadOnlyList<string> Labels,
bool PublicVisible,
IReadOnlyList<string> Kinds,
string? AgentVersion,
LocalLimitsDto? LocalLimits,
long TaskVersion,
long AppliedTaskVersion,
IReadOnlyList<RejectedDto> Rejected,
HeartbeatDto? Heartbeat,
int EndpointChanges,
DateTimeOffset CreatedAt,
DateTimeOffset? LastSeenAt);
/// <summary>merge-patch:缺省或为 null 的字段不修改。</summary>
public sealed record AgentPatchRequest(string? Name, IReadOnlyList<string>? Labels, string? PublicHost, bool? PublicVisible);
public sealed record JoinTokenDto(string Id, string? Note, IReadOnlyList<string> Labels, bool AutoApprove, int MaxUses, int Uses, DateTimeOffset ExpiresAt, DateTimeOffset? RevokedAt, DateTimeOffset CreatedAt);
public sealed record CreateJoinTokenRequest(string? Note, IReadOnlyList<string>? Labels, bool? AutoApprove, int? MaxUses, int? ExpiresInHours);
/// <summary>令牌明文只在创建的响应里出现一次。</summary>
public sealed record InstallCommandsDto(string Linux, string Macos, string Windows);
public sealed record CreatedJoinTokenDto(JoinTokenDto Token, string Plaintext, InstallCommandsDto InstallCommands);
/// <summary>Agent 眼中连着的一个 Probe(Agent 上报)。</summary>
public sealed record ConnectedProbeDto(string ProbeId, string? Version, string? Remote, DateTimeOffset ConnectedSince);
/// <summary>某个 Probe 现在连着的一个 Agent。</summary>
public sealed record ProbeConnectionDto(string AgentId, string AgentName, bool AgentOnline, string? Version, string? Remote, DateTimeOffset ConnectedSince);
public sealed record ProbeDto(string Id, string Name, DateTimeOffset CreatedAt, DateTimeOffset? RevokedAt, bool Revoked, bool Online, IReadOnlyList<ProbeConnectionDto> Connections);
/// <summary>一对(Probe,Agent)的线路开关:<c>linked</c> 是最终结果,<c>override</c> 是手动设置(<c>null</c> = 跟随自动连接),<c>connected</c> 是现在有没有连着。</summary>
public sealed record LinkDto(string ProbeId, string ProbeName, string AgentId, string AgentName, bool Linked, bool? Override, bool Connected);
public sealed record LinkMatrixDto(bool AutoLink, IReadOnlyList<LinkDto> Items);
public sealed record SetLinkRequest(bool? Enabled);
public sealed record ProbeLimitsDto(double MaxTestMbps, long MaxTotalPps);
/// <summary><c>probe.json</c>:含私钥,只在生成时返回一次。</summary>
public sealed record ProbeIdentityDto(string ProbeId, string PrivateKey, string Server, ProbeLimitsDto Limits);
public sealed record CreateProbeRequest(string? Name);
public sealed record PatchProbeRequest(string? Name);
public sealed record TaskDto(
string Id,
string Scope,
string? AgentId,
string? BaseTaskId,
string Kind,
string? Target,
IReadOnlyDictionary<string, JsonElement> Params,
string Schedule,
bool Enabled,
DateTimeOffset UpdatedAt);
public sealed record CreateTaskRequest(
string Scope,
string Kind,
string? Target,
Dictionary<string, JsonElement>? Params,
string? Schedule,
bool? Enabled,
string? AgentId,
string? BaseTaskId);
public sealed record PatchTaskRequest(Dictionary<string, JsonElement>? Params, string? Schedule, bool? Enabled, string? Target);
public sealed record StartTestRequestDto(string Plan, Dictionary<string, JsonElement>? Params);
public sealed record RawChunkMetaDto(
string ChunkId,
string AgentId,
string Series,
string Kind,
string? TaskId,
string? TestId,
int? Phase,
DateTimeOffset Start,
int DurationMs,
bool Partial,
int Bytes);
public sealed record ApiTokenDto(string Id, string Name, string Scope, DateTimeOffset CreatedAt, DateTimeOffset? LastUsedAt, DateTimeOffset? RevokedAt);
public sealed record CreateApiTokenRequest(string Name, string Scope);
public sealed record CreatedApiTokenDto(ApiTokenDto Token, string Plaintext);
public sealed record SettingDto(string Key, string Type, string Value, string Default, string Description);
public sealed record UserDto(string Id, string Username, DateTimeOffset CreatedAt);
public sealed record CreateUserRequest(string Username, string Password);
public sealed record ChangePasswordRequest(string Password);
public sealed record RecomputeDto(string State, IReadOnlyList<string> Metrics, long WindowsDone, long WindowsTotal, DateTimeOffset? From, DateTimeOffset? To, int QueueLength);
/// <summary>管理端看的构建信息:每个节点自报的版本、tag、提交和构建时间,以及系统。</summary>
public sealed record NodeBuildDto(string Id, string Kind, string? Os, string? OsName, string? Arch, string? Version, string? Tag, string? Commit, string? BuiltAt, NodeUpdateDto? Update = null);
/// <summary>节点宿主机上的更新器报告的状态(<c>up_to_date</c>、<c>available</c>、<c>applying</c>、<c>failed</c>、<c>rolled_back</c>、<c>invalid</c>);没装更新器的节点没有这个字段。</summary>
public sealed record NodeUpdateDto(string State, string? CurrentDigest, string? AvailableTag, string? AvailableVersion, string? Error, DateTimeOffset? At);
/// <summary>面板登记的最新发布清单(CI 签名的;验签在节点上)。</summary>
public sealed record ReleaseDto(string Component, long Seq, string Tag, string Version, string Digest);
public sealed record PublishReleaseRequest(string? Manifest, string? Signature);
public sealed record BuildInfoDto(BuildDto Backend, IReadOnlyList<NodeBuildDto> Nodes);
@@ -0,0 +1,85 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MoShiro.Backend.Api;
/// <summary>
/// REST 的 JSON 源生成上下文(snake_case,字段始终存在、未知值写 null,枚举与时间的格式见数据表示规则)。
/// 新增 DTO 时在这里登记一行,不使用反射。
/// </summary>
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
DictionaryKeyPolicy = JsonKnownNamingPolicy.Unspecified,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Converters = [typeof(Rfc3339Converter)],
NumberHandling = JsonNumberHandling.Strict)]
[JsonSerializable(typeof(ProblemDto))]
[JsonSerializable(typeof(HealthDto))]
[JsonSerializable(typeof(IdDto))]
[JsonSerializable(typeof(OverviewDto))]
[JsonSerializable(typeof(SeriesDto))]
[JsonSerializable(typeof(CompareDto))]
[JsonSerializable(typeof(DistributionDto))]
[JsonSerializable(typeof(HeatmapDto))]
[JsonSerializable(typeof(LineDto))]
[JsonSerializable(typeof(TestDto))]
[JsonSerializable(typeof(PageDto<TestDto>))]
[JsonSerializable(typeof(PageDto<KindDto>))]
[JsonSerializable(typeof(PageDto<PlanDto>))]
[JsonSerializable(typeof(PageDto<MetricDto>))]
[JsonSerializable(typeof(PageDto<PresetDto>))]
[JsonSerializable(typeof(PageDto<ModelDto>))]
[JsonSerializable(typeof(LiveNodeDto))]
[JsonSerializable(typeof(LiveTestDto))]
[JsonSerializable(typeof(LoginRequest))]
[JsonSerializable(typeof(LoginResponse))]
[JsonSerializable(typeof(MeDto))]
[JsonSerializable(typeof(AgentDto))]
[JsonSerializable(typeof(PageDto<AgentDto>))]
[JsonSerializable(typeof(AgentPatchRequest))]
[JsonSerializable(typeof(LinkDto))]
[JsonSerializable(typeof(LinkMatrixDto))]
[JsonSerializable(typeof(SetLinkRequest))]
[JsonSerializable(typeof(BuildDto))]
[JsonSerializable(typeof(ReleaseDto))]
[JsonSerializable(typeof(PageDto<ReleaseDto>))]
[JsonSerializable(typeof(PublishReleaseRequest))]
[JsonSerializable(typeof(AboutDto))]
[JsonSerializable(typeof(NodeBuildDto))]
[JsonSerializable(typeof(BuildInfoDto))]
[JsonSerializable(typeof(NodeInfoDto))]
[JsonSerializable(typeof(PageDto<NodeInfoDto>))]
[JsonSerializable(typeof(JoinTokenDto))]
[JsonSerializable(typeof(PageDto<JoinTokenDto>))]
[JsonSerializable(typeof(CreateJoinTokenRequest))]
[JsonSerializable(typeof(CreatedJoinTokenDto))]
[JsonSerializable(typeof(ProbeIdentityDto))]
[JsonSerializable(typeof(CreateProbeRequest))]
[JsonSerializable(typeof(PatchProbeRequest))]
[JsonSerializable(typeof(ProbeDto))]
[JsonSerializable(typeof(PageDto<HostItemDto>))]
[JsonSerializable(typeof(HostSeriesDto))]
[JsonSerializable(typeof(LiveHostDto))]
[JsonSerializable(typeof(PageDto<ProbeDto>))]
[JsonSerializable(typeof(TaskDto))]
[JsonSerializable(typeof(PageDto<TaskDto>))]
[JsonSerializable(typeof(CreateTaskRequest))]
[JsonSerializable(typeof(PatchTaskRequest))]
[JsonSerializable(typeof(StartTestRequestDto))]
[JsonSerializable(typeof(RawChunkMetaDto))]
[JsonSerializable(typeof(PageDto<RawChunkMetaDto>))]
[JsonSerializable(typeof(PageDto<EventDto>))]
[JsonSerializable(typeof(ApiTokenDto))]
[JsonSerializable(typeof(PageDto<ApiTokenDto>))]
[JsonSerializable(typeof(CreateApiTokenRequest))]
[JsonSerializable(typeof(CreatedApiTokenDto))]
[JsonSerializable(typeof(PageDto<SettingDto>))]
[JsonSerializable(typeof(Dictionary<string, JsonElement>), TypeInfoPropertyName = "SettingsPatch")]
[JsonSerializable(typeof(PageDto<UserDto>))]
[JsonSerializable(typeof(UserDto))]
[JsonSerializable(typeof(CreateUserRequest))]
[JsonSerializable(typeof(ChangePasswordRequest))]
[JsonSerializable(typeof(RecomputeDto))]
[JsonSerializable(typeof(MoShiro.Protocol.LocalLimits))]
[JsonSerializable(typeof(List<Fleet.Rpc.RejectedItem>))]
public sealed partial class ApiJsonContext : JsonSerializerContext;
@@ -0,0 +1,33 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MoShiro.Backend.Api;
/// <summary>时间点统一使用 RFC 3339 UTC,以 Z 结尾,精确到毫秒(唯一例外:列式时序里的时间轴 <c>t</c> 是 Unix 毫秒)。</summary>
public sealed class Rfc3339Converter : JsonConverter<DateTimeOffset>
{
private const string Format = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'";
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
DateTimeOffset.Parse(reader.GetString() ?? throw new JsonException("timestamp must be a string"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) =>
writer.WriteStringValue(value.UtcDateTime.ToString(Format, CultureInfo.InvariantCulture));
}
/// <summary>RFC 9457 problem+json,附加 <c>code</c>(客户端判断的唯一依据)、<c>trace_id</c> 和 400 时的逐字段 <c>errors</c>。</summary>
public sealed record ProblemDto(
string Type,
string Title,
int Status,
string Code,
string? Detail,
string? TraceId,
IReadOnlyDictionary<string, string[]>? Errors);
public sealed record PageDto<T>(IReadOnlyList<T> Items, string? NextCursor);
public sealed record IdDto(string Id);
public sealed record HealthDto(string Status, bool Database, bool RawDirectory);
@@ -0,0 +1,185 @@
namespace MoShiro.Backend.Api;
/// <summary>汇总:整个时间段由累加器精确合并得出的值(不是各点的平均)。</summary>
public sealed record SummaryDto(IReadOnlyDictionary<string, double?> Metrics, IReadOnlyDictionary<string, long> Samples, double Coverage);
public sealed record EdgePartialDto(bool First, bool Last);
public sealed record GapDto(DateTimeOffset From, DateTimeOffset To, string Reason);
public sealed record EventDto(long Id, string? AgentId, string Kind, DateTimeOffset At, string? Detail);
public sealed record TestSummaryDto(string Id, string Plan, string Status, DateTimeOffset RequestedAt, DateTimeOffset? FinishedAt);
public sealed record OverviewLineDto(
string Id,
string AgentId,
string ProbeId,
string ProbeName,
string Name,
IReadOnlyList<string> Labels,
bool Online,
bool ProbeConnected,
int EndpointChanges,
SummaryDto Summary,
TestSummaryDto? LastTest);
public sealed record OverviewDto(string Range, DateTimeOffset From, DateTimeOffset To, string Step, string Source, bool Degraded, IReadOnlyList<OverviewLineDto> Lines);
/// <summary>列式响应:时间轴 <c>t</c>(Unix 毫秒)只出现一次,<c>values</c> 里每个数组的长度都与 <c>t</c> 相同;没有数据的桶是 null。</summary>
public sealed record SeriesDto(
string LineId,
string AgentId,
string ProbeId,
DateTimeOffset From,
DateTimeOffset To,
string Step,
string Source,
bool Live,
bool Degraded,
IReadOnlyList<long> T,
IReadOnlyDictionary<string, double?[]> Values,
SummaryDto Summary,
EdgePartialDto EdgePartial,
IReadOnlyList<GapDto> Gaps,
IReadOnlyList<EventDto> Events,
IReadOnlyDictionary<string, string> Versions);
public sealed record CompareDto(
string Metric,
DateTimeOffset From,
DateTimeOffset To,
string Step,
string Source,
bool Degraded,
IReadOnlyList<long> T,
IReadOnlyDictionary<string, double?[]> Values,
IReadOnlyDictionary<string, double?> Summary);
public sealed record DistributionBucketDto(double Le, long Count);
public sealed record DistributionDto(string Metric, string Unit, IReadOnlyList<DistributionBucketDto> Buckets, double? P50, double? P95, double? P99, long Count);
public sealed record HeatmapDto(
string Metric,
string Step,
string Source,
bool Degraded,
IReadOnlyList<long> T,
IReadOnlyList<double> Edges,
IReadOnlyList<IReadOnlyList<long>> Counts);
/// <summary>一条线路 = 一对(Agent,Probe);<c>id</c> 是 <c>&lt;agent_id&gt;~&lt;probe_id&gt;</c>。</summary>
public sealed record LineDto(
string Id,
string AgentId,
string ProbeId,
string ProbeName,
string Name,
IReadOnlyList<string> Labels,
bool Online,
bool ProbeConnected,
DateTimeOffset? DataSince,
IReadOnlyList<string> Kinds,
TestSummaryDto? LastTest);
public sealed record DimensionDto(string Id, string Label, double Weight, double? Value, double? Score);
public sealed record ScoreDto(double? Score, int Version, bool Estimated, IReadOnlyList<DimensionDto> Dimensions, IReadOnlyList<string> Caveats);
public sealed record TestStepDto(int Index, double TargetMbps, double LossPct, double? JitterP95Ms, double DeliveredMbps);
public sealed record TestResultDto(
IReadOnlyDictionary<string, double> Metrics,
IReadOnlyDictionary<string, ScoreDto> Scores,
IReadOnlyDictionary<string, double?[]> Series,
IReadOnlyList<TestStepDto> Steps,
IReadOnlyList<string> Caveats);
public sealed record TestDto(
string Id,
string AgentId,
string ProbeId,
string Plan,
IReadOnlyList<string> Phases,
string Status,
int? Phase,
string? ErrorCode,
string Trigger,
DateTimeOffset RequestedAt,
DateTimeOffset? StartedAt,
DateTimeOffset? FinishedAt,
TestResultDto? Result);
// ── 目录(由代码注册表生成) ──────────────────────────────────────────
public sealed record ParamDto(string Name, string Type, System.Text.Json.JsonElement Default, double? Min, double? Max, string? Unit, IReadOnlyList<string>? Values, string Description);
public sealed record BlocksDto(string Transport, string Pacer, string Payload, string Behavior, IReadOnlyList<string> Samplers);
public sealed record ColumnDto(string Name, string Type, string Encoding, bool Nullable, string? Role);
public sealed record StreamDto(string Name, IReadOnlyList<ColumnDto> Columns);
public sealed record KindDto(string Id, string Title, string Description, IReadOnlyList<string> Scopes, BlocksDto Blocks, IReadOnlyList<ParamDto> Params, IReadOnlyList<StreamDto> Streams);
public sealed record PhaseDto(string Kind, int DurationS, System.Text.Json.JsonElement Params);
public sealed record PlanDto(string Id, string Title, string Description, IReadOnlyList<PhaseDto> Phases);
public sealed record MetricDto(
string Id,
string DefinitionId,
string Accumulator,
string Stat,
string Label,
string Description,
string Unit,
string Better,
double? Good,
double? Poor,
string Version,
IReadOnlyList<string> From);
public sealed record PresetDto(string Id, string Label, IReadOnlyList<string> Metrics);
public sealed record ModelDto(string Id, string Title, int Version, IReadOnlyList<string> Needs);
public sealed record LiveNodeDto(string Kind, string Id, bool Online, DateTimeOffset At);
public sealed record LiveTestDto(string TestId, string Status, int? Phase, int Phases);
/// <summary>
/// 主机资源占用(展示页能拿到的全部内容):只有汇总的数字,<b>没有 CPU 型号、主机名、网卡名,也没有任何 IP 地址</b>。
/// 这些字段 Agent 根本不采集,所以即使这里写漏了也泄漏不出去。
/// </summary>
public sealed record HostDto(
double? CpuPercent,
long? MemUsed,
long? MemTotal,
long? DiskUsed,
long? DiskTotal,
long? NetRxTotal,
long? NetTxTotal,
long? NetRxBps,
long? NetTxBps,
long? NetLinkMbps,
long? UptimeSeconds,
DateTimeOffset At);
/// <summary>一个 Agent 的最新主机状态;<c>stale</c> 表示超过 15 秒没有更新(离线,或者 Agent 关闭了上报)。</summary>
/// <summary>节点的展示信息:只有系统、架构、国家代码、运营商,没有任何地址;站点打开「显示构建信息」之后还有版本和 tag。</summary>
public sealed record NodeInfoDto(string Id, string Kind, string? Os, string? OsName, string? Arch, string? Country, string? IspKey, string? IspName, string? Version = null, string? Tag = null);
/// <summary>一个程序的构建信息:版本、tag、提交(短)和构建时间(UTC,ISO 8601)。没设置的是 <c>null</c>。</summary>
public sealed record BuildDto(string Version, string? Tag, string? Commit, string? BuiltAt);
/// <summary>公开的「关于」:站点没有打开「显示构建信息」时只有 <c>show_build: false</c>。</summary>
public sealed record AboutDto(bool ShowBuild, BuildDto? Backend);
public sealed record HostItemDto(string AgentId, string Name, bool Online, bool Stale, DateTimeOffset? LastSeenAt, HostDto? Host);
public sealed record LiveHostDto(string AgentId, bool Online, HostDto Host);
/// <summary>主机资源占用的历史(列式,按步长取平均)。</summary>
public sealed record HostSeriesDto(string AgentId, DateTimeOffset From, DateTimeOffset To, string Step, IReadOnlyList<long> T, IReadOnlyDictionary<string, double?[]> Values);