using System.Text.Json; using Fleet.Core; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Monitor.Backend.Application; using Monitor.Protocol; namespace Monitor.Backend.Api; /// API 令牌、站点设置与保留期、用户。令牌明文只在创建的响应里出现一次。 public static class SystemEndpoints { private const string TokensTag = "admin-api-tokens"; private const string SettingsTag = "admin-settings"; private const string UsersTag = "admin-users"; public static void Map(RouteGroupBuilder group) { group.MapGet("/api-tokens", ListTokensAsync) .Api("api_tokens_list", "API tokens", TokensTag).Returns>().ProducesProblems(401, 403); group.MapPost("/api-tokens", CreateTokenAsync) .Api("api_tokens_create", "Create an API token (scope read or admin); the plaintext is returned only once", TokensTag) .Returns(StatusCodes.Status201Created).ProducesProblems(400, 401, 403); group.MapDelete("/api-tokens/{id}", RevokeTokenAsync) .Api("api_tokens_revoke", "Revoke an API token", TokensTag).Returns(StatusCodes.Status204NoContent).ProducesProblems(401, 403); group.MapGet("/settings", ListSettingsAsync) .Api("settings_list", "Site settings and retention periods", SettingsTag).Returns>().ProducesProblems(401, 403); group.MapPatch("/settings", PatchSettingsAsync) .Accepts>("application/merge-patch+json", "application/json") .Api("settings_update", "Change settings (merge-patch)", SettingsTag).Returns>().ProducesProblems(400, 401, 403); group.MapGet("/users", ListUsersAsync) .Api("users_list", "Panel users", UsersTag).Returns>().ProducesProblems(401, 403); group.MapPost("/users", CreateUserAsync) .Api("users_create", "Create a panel user", UsersTag).Returns(StatusCodes.Status201Created).ProducesProblems(400, 401, 403, 409); group.MapPatch("/users/{id}", ChangePasswordAsync) .Api("users_update", "Change a user's password", UsersTag).Returns(StatusCodes.Status204NoContent).ProducesProblems(400, 401, 403); } private static async Task ListTokensAsync(AuthService auth, CancellationToken ct) => TypedResults.Json(new PageDto([.. (await auth.ListApiTokensAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoApiTokenDto); private static async Task CreateTokenAsync(HttpContext http, AuthService auth, CreateApiTokenRequest request) { Result result = await auth.CreateApiTokenAsync(request.Name ?? "", request.Scope ?? "", http.RequestAborted); if (!result.TryGet(out CreatedApiToken? created, out FleetError? error)) { return Problems.From(http, error); } http.Response.Headers.CacheControl = "no-store"; return TypedResults.Json(new CreatedApiTokenDto(Mappers.ToDto(created.Record), created.Plaintext), ApiJsonContext.Default.CreatedApiTokenDto, statusCode: StatusCodes.Status201Created); } private static async Task RevokeTokenAsync(AuthService auth, string id, CancellationToken ct) { await auth.RevokeApiTokenAsync(id, ct); return TypedResults.NoContent(); } private static async Task ListSettingsAsync(SettingsService settings, CancellationToken ct) => TypedResults.Json(new PageDto([.. (await settings.ListAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoSettingDto); private static async Task PatchSettingsAsync(HttpContext http, SettingsService settings, AgentService agents, Dictionary changes) { Result> result = await settings.PatchAsync(changes, http.RequestAborted); if (result.IsSuccess && changes.ContainsKey(SettingsService.AutoLink)) { await agents.AfterAgentSetChangedAsync(http.RequestAborted); // 自动连接开关变了:重新下发各 Probe 的 Agent 列表和各 Agent 允许的 Probe } return result.TryGet(out IReadOnlyList? views, out FleetError? error) ? TypedResults.Json(new PageDto([.. views.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoSettingDto) : Problems.From(http, error); } private static async Task ListUsersAsync(AuthService auth, CancellationToken ct) => TypedResults.Json(new PageDto([.. (await auth.ListUsersAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoUserDto); private static async Task CreateUserAsync(HttpContext http, AuthService auth, CreateUserRequest request) { Result result = await auth.CreateUserAsync(request.Username ?? "", request.Password ?? "", http.RequestAborted); if (!result.TryGet(out UserRecord? user, out FleetError? error)) { return Problems.From(http, error); } http.Response.Headers.Location = "/api/admin/users"; return TypedResults.Json(Mappers.ToDto(user), ApiJsonContext.Default.UserDto, statusCode: StatusCodes.Status201Created); } private static async Task ChangePasswordAsync(HttpContext http, AuthService auth, ChangePasswordRequest request, string id) { Result result = await auth.ChangePasswordAsync(id, request.Password ?? "", http.RequestAborted); return result.IsSuccess ? TypedResults.NoContent() : Problems.From(http, result.Error); } }