summary refs log tree commit diff
path: root/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages
diff options
context:
space:
mode:
authorEmma [it/its]@Rory& <root@rory.gay>2025-04-08 18:55:55 +0200
committerEmma [it/its]@Rory& <root@rory.gay>2025-10-05 21:34:40 +0200
commitabb1b570a4343d5e4fde34f55c1a2bf403f62981 (patch)
tree1986363f5bf369a03ee1f18ed3170da73d859c38 /extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages
parentRewrite access tokens, initial admin api (diff)
downloadserver-ts-abb1b570a4343d5e4fde34f55c1a2bf403f62981.tar.xz
Local changes
Diffstat (limited to 'extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages')
-rw-r--r--extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Home.razor107
-rw-r--r--extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Login.razor57
-rw-r--r--extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Users.razor72
-rw-r--r--extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/UsersDelete.razor61
4 files changed, 297 insertions, 0 deletions
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Home.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Home.razor
new file mode 100644

index 000000000..812a61521 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Home.razor
@@ -0,0 +1,107 @@ +@page "/" +@using System.Net.Http.Headers +@using Spacebar.AdminAPI.TestClient.Services +@inject Config Config +@inject ILocalStorageService LocalStorage + +<PageTitle>Home</PageTitle> + +<span style="@($"color: {(IsApiUrlValid ? "green" : "red")};")">Spacebar API URL: </span> +<InputText @bind-Value:get="Config.ApiUrl" @bind-Value:set="@(async (v) => { + Config.ApiUrl = v!; + await ValidateAndSaveConfig(); + })"/> +<br/> + +<!-- <span style="@($"color: {(IsGatewayUrlValid ? "green" : "red")};")">Spacebar Gateway URL: </span> --> +<!-- <InputText @bind-Value="GatewayUrl" /> --> +<!-- <br /> --> + +<span style="@($"color: {(IsCdnUrlValid ? "green" : "red")};")">Spacebar CDN URL: </span> +<InputText @bind-Value:get="Config.CdnUrl" @bind-Value:set="@(async (v) => { + Config.CdnUrl = v!; + await ValidateAndSaveConfig(); + })"/> +<br/> + +<span style="@($"color: {(IsAdminApiUrlValid ? "green" : "red")};")">Spacebar Admin API URL: </span> +<InputText @bind-Value:get="Config.AdminUrl" @bind-Value:set="@(async (v) => { + Config.AdminUrl = v!; + await ValidateAndSaveConfig(); + })"/> +<br/> + +<span style="@($"color: {(IsAccessTokenValid ? "green" : "red")};")">Access Token: </span> +<InputText @bind-Value:get="Config.AccessToken" @bind-Value:set="@(async (v) => { + Config.AccessToken = v!; + await ValidateAndSaveConfig(); + })"/> +<a href="/login">New access token</a> +<br/> + +@code { + + private bool IsApiUrlValid { get; set; } + + // private bool IsGatewayUrlValid { get; set; } + private bool IsCdnUrlValid { get; set; } + private bool IsAdminApiUrlValid { get; set; } + private bool IsAccessTokenValid { get; set; } + + protected override async Task OnInitializedAsync() { + await ValidateAndSaveConfig(); + } + + private async Task ValidateAndSaveConfig() { + await LocalStorage.SetItemAsync("sb_admin_tc_config", Config); + + using var hc = new HttpClient(); + HttpResponseMessage response; + try { + response = await hc.GetAsync(Config.ApiUrl + "/api/v9/policies/instance/domains"); + IsApiUrlValid = response.IsSuccessStatusCode; + } + catch { + IsApiUrlValid = false; + } + + StateHasChanged(); + + // response = await hc.GetAsync(Config.GatewayUrl + "/api/v9/policies/instance"); + // IsGatewayUrlValid = response.IsSuccessStatusCode; + // StateHasChanged(); + + try { + response = await hc.GetAsync(Config.CdnUrl + "/ping"); + IsCdnUrlValid = response.IsSuccessStatusCode; + } + catch { + IsCdnUrlValid = false; + } + + StateHasChanged(); + + try { + response = await hc.GetAsync(Config.AdminUrl + "/_spacebar/admin/ping"); + IsAdminApiUrlValid = response.IsSuccessStatusCode; + } + catch { + IsAdminApiUrlValid = false; + } + + StateHasChanged(); + + try { + var request = new HttpRequestMessage(HttpMethod.Get, Config.AdminUrl + "/_spacebar/admin/whoami"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken); + response = await hc.SendAsync(request); + IsAccessTokenValid = response.IsSuccessStatusCode; + } + catch { + IsAccessTokenValid = false; + } + + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Login.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Login.razor new file mode 100644
index 000000000..ca1205ed8 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Login.razor
@@ -0,0 +1,57 @@ +@page "/Login" +@using System.Text.Json.Nodes +@using Spacebar.AdminAPI.TestClient.Services +@inject ILocalStorageService LocalStorage +@inject Config Config +@inject NavigationManager Navigation +<h3>Login</h3> + +<span>Email: </span> +<InputText @bind-Value="Email"/> +<br/> + +<span>Password: </span> +<InputText type="password" @bind-Value="Password"/> +<br/> + +<button @onclick="DoLogin">Login</button> +<br/> + +<pre style="color: red; font-family: 'JetBrains Mono',monospace">@Error</pre> + + +@code { + private string Email { get; set; } + private string Password { get; set; } + private string Error { get; set; } + + private async Task DoLogin() { + HttpResponseMessage response; + using var hc = new HttpClient(); + + try { + response = await hc.PostAsJsonAsync(Config.ApiUrl + "/api/v9/auth/login", new { + login = Email, + password = Password, + login_source = "Spacebar Admin API Test Client", + undelete = false + }); + } + catch (Exception e) { + Error = e.ToString(); + return; + } + + if (!response.IsSuccessStatusCode) { + Error = await response.Content.ReadAsStringAsync(); + return; + } + + var content = await response.Content.ReadFromJsonAsync<JsonObject>(); + var accessToken = content!["token"].ToString(); + Config.AccessToken = accessToken; + await LocalStorage.SetItemAsync("sb_admin_tc_config", Config); + Navigation.NavigateTo("/", true, true); + } + +} \ No newline at end of file diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Users.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Users.razor new file mode 100644
index 000000000..13c346617 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/Users.razor
@@ -0,0 +1,72 @@ +@page "/Users" +@using System.Net.Http.Headers +@using System.Reflection +@using Spacebar.AdminApi.Models +@using Spacebar.AdminAPI.TestClient.Services +@using ArcaneLibs.Blazor.Components +@inject Config Config +@inject ILocalStorageService LocalStorage + +<PageTitle>Users</PageTitle> + +<details> + <summary>Displayed columns</summary> + @foreach (var column in DisplayedColumns) { + var value = column.Value; + <span> + <InputCheckbox @bind-Value:get="@(value)" @bind-Value:set="@(b => { + DisplayedColumns[column.Key] = b; + StateHasChanged(); + })"/> + @column.Key.Name + </span> + <br/> + } +</details> + +<table class="table table-bordered"> + @{ + var columns = DisplayedColumns.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToList(); + } + <thead> + <tr> + @foreach (var column in columns) { + <th>@column.Name</th> + } + <th>Actions</th> + </tr> + </thead> + <tbody> + @foreach (var user in UserList) { + <tr> + @foreach (var column in columns) { + <td>@column.GetValue(user)</td> + } + <td> + <LinkButton href="@($"/Users/Delete/{user.Id}")" Color="#ff0000">Delete</LinkButton> + </td> + </tr> + } + </tbody> +</table> + +@code { + + private Dictionary<PropertyInfo, bool> DisplayedColumns { get; set; } = typeof(UserModel).GetProperties() + .ToDictionary(p => p, p => p.Name == "Username" || p.Name == "Id" || p.Name == "MessageCount"); + + private List<UserModel> UserList { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + using var hc = new HttpClient(); + hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken); + var response = await hc.GetAsync(Config.AdminUrl + "/_spacebar/admin/users/"); + if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync()); + var content = response.Content.ReadFromJsonAsAsyncEnumerable<UserModel>(); + await foreach (var user in content) { + UserList.Add(user); + StateHasChanged(); + } + } + +} \ No newline at end of file diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/UsersDelete.razor b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/UsersDelete.razor new file mode 100644
index 000000000..084a372fe --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Pages/UsersDelete.razor
@@ -0,0 +1,61 @@ +@page "/Users/Delete/{Id}" +@using System.Net.Http.Headers +@using System.Text.Json +@using System.Text.Json.Nodes +@using ArcaneLibs.Extensions +@using Spacebar.AdminApi.Models +@using Spacebar.AdminAPI.TestClient.Services +@inject Config Config +<h3>UsersDelete - @Id</h3> + +Deleted @ChannelDeleteProgress.Sum(x=>x.Value.Deleted) messages so far! +@foreach (var (channel, progress) in ChannelDeleteProgress.Where(x=>x.Value.Deleted != x.Value.Total).OrderByDescending(x=>x.Value.Progress)) { + <div>@channel: @progress.Total total, @progress.Deleted deleted</div> + <progress max="@progress.Total" value="@progress.Deleted"></progress> +} + +@code { + + [Parameter] + public required string Id { get; set; } + + private Dictionary<string, DeleteProgress> ChannelDeleteProgress { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + using var hc = new HttpClient(); + hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Config.AccessToken); + var response = await hc.GetAsync(Config.AdminUrl + $"/_spacebar/admin/Users/{Id}/delete?messageDeleteChunkSize=100"); + if (!response.IsSuccessStatusCode) throw new Exception(await response.Content.ReadAsStringAsync()); + var content = response.Content.ReadFromJsonAsAsyncEnumerable<AsyncActionResult>(); + await foreach (var actionResult in content) { + Console.WriteLine(actionResult.ToJson(indent: false)); + switch (actionResult.MessageType) { + case "STATS": { + var data = JsonSerializer.Deserialize<JsonObject>(actionResult.Data.ToJson()); + ChannelDeleteProgress = data!["messages_per_channel"]! + .Deserialize<Dictionary<string, int>>()! + .ToDictionary(x=>x.Key, x=>new DeleteProgress { Total = x.Value }); + break; + } + case "BULK_DELETED": { + var data = JsonSerializer.Deserialize<JsonObject>(actionResult.Data.ToJson()); + ChannelDeleteProgress[data!["channel_id"]!.ToString()].Deleted += data!["deleted"]!.GetValue<int>(); + break; + } + default: { + Console.WriteLine($"Unknown message type: {actionResult.MessageType}"); + break; + } + } + + StateHasChanged(); + await Task.Delay(1); + } + } + + private class DeleteProgress { + public int Total { get; set; } + public int Deleted { get; set; } = 0; + public float Progress => (float)Deleted / Total; + } +} \ No newline at end of file