The panel decides the probe/agent pairs and probes always dial out. A new fleet.auto_link setting connects every probe to every agent (default); with it off only the pairs chosen by hand connect, and any pair can be forced on or off. The Probes page has a link matrix with the switch and per-pair toggles.
100 lines
5.7 KiB
C#
100 lines
5.7 KiB
C#
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;
|
|
|
|
/// <summary>API 令牌、站点设置与保留期、用户。令牌明文只在创建的响应里出现一次。</summary>
|
|
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<PageDto<ApiTokenDto>>().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<CreatedApiTokenDto>(StatusCodes.Status201Created).ProducesProblems(400, 401, 403);
|
|
group.MapDelete("/api-tokens/{id}", RevokeTokenAsync)
|
|
.Api("api_tokens_revoke", "Revoke an API token", TokensTag).Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403);
|
|
|
|
group.MapGet("/settings", ListSettingsAsync)
|
|
.Api("settings_list", "Site settings and retention periods", SettingsTag).Returns<PageDto<SettingDto>>().ProducesProblems(401, 403);
|
|
group.MapPatch("/settings", PatchSettingsAsync)
|
|
.Accepts<Dictionary<string, JsonElement>>("application/merge-patch+json", "application/json")
|
|
.Api("settings_update", "Change settings (merge-patch)", SettingsTag).Returns<PageDto<SettingDto>>().ProducesProblems(400, 401, 403);
|
|
|
|
group.MapGet("/users", ListUsersAsync)
|
|
.Api("users_list", "Panel users", UsersTag).Returns<PageDto<UserDto>>().ProducesProblems(401, 403);
|
|
group.MapPost("/users", CreateUserAsync)
|
|
.Api("users_create", "Create a panel user", UsersTag).Returns<UserDto>(StatusCodes.Status201Created).ProducesProblems(400, 401, 403, 409);
|
|
group.MapPatch("/users/{id}", ChangePasswordAsync)
|
|
.Api("users_update", "Change a user's password", UsersTag).Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(400, 401, 403);
|
|
}
|
|
|
|
private static async Task<IResult> ListTokensAsync(AuthService auth, CancellationToken ct) =>
|
|
TypedResults.Json(new PageDto<ApiTokenDto>([.. (await auth.ListApiTokensAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoApiTokenDto);
|
|
|
|
private static async Task<IResult> CreateTokenAsync(HttpContext http, AuthService auth, CreateApiTokenRequest request)
|
|
{
|
|
Result<CreatedApiToken> 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<IResult> RevokeTokenAsync(AuthService auth, string id, CancellationToken ct)
|
|
{
|
|
await auth.RevokeApiTokenAsync(id, ct);
|
|
return TypedResults.NoContent();
|
|
}
|
|
|
|
private static async Task<IResult> ListSettingsAsync(SettingsService settings, CancellationToken ct) =>
|
|
TypedResults.Json(new PageDto<SettingDto>([.. (await settings.ListAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoSettingDto);
|
|
|
|
private static async Task<IResult> PatchSettingsAsync(HttpContext http, SettingsService settings, AgentService agents, Dictionary<string, JsonElement> changes)
|
|
{
|
|
Result<IReadOnlyList<SettingView>> 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<SettingView>? views, out FleetError? error)
|
|
? TypedResults.Json(new PageDto<SettingDto>([.. views.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoSettingDto)
|
|
: Problems.From(http, error);
|
|
}
|
|
|
|
private static async Task<IResult> ListUsersAsync(AuthService auth, CancellationToken ct) =>
|
|
TypedResults.Json(new PageDto<UserDto>([.. (await auth.ListUsersAsync(ct)).Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoUserDto);
|
|
|
|
private static async Task<IResult> CreateUserAsync(HttpContext http, AuthService auth, CreateUserRequest request)
|
|
{
|
|
Result<UserRecord> 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<IResult> ChangePasswordAsync(HttpContext http, AuthService auth, ChangePasswordRequest request, string id)
|
|
{
|
|
Result<bool> result = await auth.ChangePasswordAsync(id, request.Password ?? "", http.RequestAborted);
|
|
return result.IsSuccess ? TypedResults.NoContent() : Problems.From(http, result.Error);
|
|
}
|
|
}
|