Compression { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Layout/MainLayout.razor b/extra/admin-api/Utilities/Spacebar.Client/Layout/MainLayout.razor
new file mode 100644
index 000000000..e7554be8d
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Layout/MainLayout.razor
@@ -0,0 +1,16 @@
+@inherits LayoutComponentBase
+
+
+
+
+
+
+
+ @Body
+
+
+
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Layout/MainLayout.razor.css b/extra/admin-api/Utilities/Spacebar.Client/Layout/MainLayout.razor.css
new file mode 100644
index 000000000..ecf25e5b2
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Layout/MainLayout.razor.css
@@ -0,0 +1,77 @@
+.page {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+}
+
+main {
+ flex: 1;
+}
+
+.sidebar {
+ background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
+}
+
+.top-row {
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #d6d5d5;
+ justify-content: flex-end;
+ height: 3.5rem;
+ display: flex;
+ align-items: center;
+}
+
+ .top-row ::deep a, .top-row ::deep .btn-link {
+ white-space: nowrap;
+ margin-left: 1.5rem;
+ text-decoration: none;
+ }
+
+ .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
+ text-decoration: underline;
+ }
+
+ .top-row ::deep a:first-child {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+@media (max-width: 640.98px) {
+ .top-row {
+ justify-content: space-between;
+ }
+
+ .top-row ::deep a, .top-row ::deep .btn-link {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 641px) {
+ .page {
+ flex-direction: row;
+ }
+
+ .sidebar {
+ width: 250px;
+ height: 100vh;
+ position: sticky;
+ top: 0;
+ }
+
+ .top-row {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ }
+
+ .top-row.auth ::deep a:first-child {
+ flex: 1;
+ text-align: right;
+ width: 0;
+ }
+
+ .top-row, article {
+ padding-left: 2rem !important;
+ padding-right: 1.5rem !important;
+ }
+}
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Layout/NavMenu.razor b/extra/admin-api/Utilities/Spacebar.Client/Layout/NavMenu.razor
new file mode 100644
index 000000000..ccc73abe4
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Layout/NavMenu.razor
@@ -0,0 +1,39 @@
+
+
+
+
+@code {
+ private bool collapseNavMenu = true;
+
+ private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
+
+ private void ToggleNavMenu() {
+ collapseNavMenu = !collapseNavMenu;
+ }
+
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Layout/NavMenu.razor.css b/extra/admin-api/Utilities/Spacebar.Client/Layout/NavMenu.razor.css
new file mode 100644
index 000000000..617b89cc8
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Layout/NavMenu.razor.css
@@ -0,0 +1,83 @@
+.navbar-toggler {
+ background-color: rgba(255, 255, 255, 0.1);
+}
+
+.top-row {
+ min-height: 3.5rem;
+ background-color: rgba(0,0,0,0.4);
+}
+
+.navbar-brand {
+ font-size: 1.1rem;
+}
+
+.bi {
+ display: inline-block;
+ position: relative;
+ width: 1.25rem;
+ height: 1.25rem;
+ margin-right: 0.75rem;
+ top: -1px;
+ background-size: cover;
+}
+
+.bi-house-door-fill-nav-menu {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
+}
+
+.bi-plus-square-fill-nav-menu {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
+}
+
+.bi-list-nested-nav-menu {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
+}
+
+.nav-item {
+ font-size: 0.9rem;
+ padding-bottom: 0.5rem;
+}
+
+ .nav-item:first-of-type {
+ padding-top: 1rem;
+ }
+
+ .nav-item:last-of-type {
+ padding-bottom: 1rem;
+ }
+
+ .nav-item ::deep a {
+ color: #d7d7d7;
+ border-radius: 4px;
+ height: 3rem;
+ display: flex;
+ align-items: center;
+ line-height: 3rem;
+ }
+
+.nav-item ::deep a.active {
+ background-color: rgba(255,255,255,0.37);
+ color: white;
+}
+
+.nav-item ::deep a:hover {
+ background-color: rgba(255,255,255,0.1);
+ color: white;
+}
+
+@media (min-width: 641px) {
+ .navbar-toggler {
+ display: none;
+ }
+
+ .collapse {
+ /* Never collapse the sidebar for wide screens */
+ display: block;
+ }
+
+ .nav-scrollable {
+ /* Allow sidebar to scroll for tall menus */
+ height: calc(100vh - 3.5rem);
+ overflow-y: auto;
+ }
+}
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Pages/Auth/Login.razor b/extra/admin-api/Utilities/Spacebar.Client/Pages/Auth/Login.razor
new file mode 100644
index 000000000..a7ab18286
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Pages/Auth/Login.razor
@@ -0,0 +1,92 @@
+@page "/login"
+@using ArcaneLibs.Blazor.Components
+@using ArcaneLibs.Extensions
+@using Spacebar.Client.Core
+@using Spacebar.Client.WebCore
+@inject SpacebarClientWellKnownResolverService clientWellKnownResolver
+@inject SessionStore sessionStore
+@inject SpacebarClientProviderService clientProvider
+
+Log in
+
+Instance (server name):
+
+[🧭 Discover]
+
+Email:
+
+
+Password:
+
+
+Log in
+
+@ServerValidationStatus
+
+@code{
+
+ private string ServerName {
+ get;
+ set {
+ field = value;
+ CheckServer(value);
+ }
+ } = "spacebar.chat";
+
+ private string Username { get; set; } = "";
+ private string Password { get; set; } = "";
+
+ private string? ServerValidationStatus;
+
+ private async Task SetStatus(string? status) {
+ ServerValidationStatus = status;
+ StateHasChanged();
+ await Task.Yield();
+ }
+
+ private async Task CheckServer(string serverName) {
+ await SetStatus("Checking server: " + serverName);
+ try {
+ await clientWellKnownResolver.ResolveClientWellKnown(serverName);
+ await SetStatus($"Server {serverName} is valid!");
+ }
+ catch (Exception e) {
+ await SetStatus("Could not validate server: " + e.Message);
+ }
+ }
+
+ private async Task LoginAsync() {
+ try {
+ await SetStatus($"Looking up {ServerName}...");
+ var cwk = await clientWellKnownResolver.ResolveClientWellKnown(ServerName);
+
+ await SetStatus($"Trying to log in to {ServerName}...");
+ var usc = await clientProvider.GetUnauthenticatedClientAsync(ServerName);
+ var lrsp = await usc.LoginAsync(new() {
+ Login = Username,
+ Password = Password
+ });
+
+ await SetStatus($"Logged in as user ID {lrsp.UserId}, fetching profile...");
+ var asc = await clientProvider.GetAuthenticatedClientAsync(ServerName, lrsp.Token!);
+ var cu = await asc.GetCurrentUser();
+ await SetStatus($"Logged in as {cu.Username}#{cu.Discriminator} ({cu.Id})! Saving...");
+ await sessionStore.AddSession(new SessionEntry() {
+ ServerName = ServerName,
+ AccessToken = lrsp.Token!,
+ ProfileCache = new() {
+ Id = cu.Id,
+ Username = cu.Username,
+ Discriminator = cu.Discriminator,
+ AvatarUrl = cu.Avatar ?? "",
+ }
+ }, setCurrent: true);
+
+ // await SetStatus(lrsp.ToJson());
+ }
+ catch (Exception e) {
+ await SetStatus("Failed to log in: " + e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Pages/Channels/@me.razor b/extra/admin-api/Utilities/Spacebar.Client/Pages/Channels/@me.razor
new file mode 100644
index 000000000..28709841d
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Pages/Channels/@me.razor
@@ -0,0 +1,28 @@
+@* Workaround for Rider "Bad compile constant value" bug *@
+@attribute [Route(PageUri)]
+@using ArcaneLibs.Blazor.Components.Services
+@using Spacebar.Client.Core
+@using Spacebar.Models.Gateway
+@inject JsConsoleService jsConsole
+
+@@me
+@Client?.ApiHttpClient.BaseAddress
+@Client?.Gateway.RawClientWebSocket.State
+
+@code {
+ private const string PageUri = "/channels/@me";
+
+ [Parameter, CascadingParameter]
+ public required AuthenticatedSpacebarClient? Client {
+ get;
+ set { field = value; Console.WriteLine("Set client: " + value); }
+ }
+
+ protected override async Task OnParametersSetAsync() {
+ if (Client == null) return;
+ Client.Gateway.OnGatewayMessage.Add(async msg => {
+ await jsConsole.Log("Received gateway message: ", msg);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Pages/Home.razor b/extra/admin-api/Utilities/Spacebar.Client/Pages/Home.razor
new file mode 100644
index 000000000..fd39d3fc1
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Pages/Home.razor
@@ -0,0 +1,26 @@
+@page "/"
+@page "/app"
+@using ArcaneLibs.Blazor.Components
+@using ArcaneLibs.Extensions
+@using Spacebar.Client.Core
+@using Spacebar.Client.WebCore
+@inject SpacebarClientWellKnownResolverService cswkrs
+@inject SessionStore sessionStore
+
+Home
+
+Hello, world!
+Trigger session picker
+@Ss.ToJson()
+@Res.ToJson()
+
+@code{
+ private SpacebarClientWellKnown? Res { get; set; }
+ private SessionEntry? Ss { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ Ss = await sessionStore.GetCurrentSessionAsync();
+ Res = await cswkrs.ResolveClientWellKnown(Ss?.ServerName ?? "spacebar.chat");
+ }
+
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Pages/NotFound.razor b/extra/admin-api/Utilities/Spacebar.Client/Pages/NotFound.razor
new file mode 100644
index 000000000..917ada1d2
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Pages/NotFound.razor
@@ -0,0 +1,5 @@
+@page "/not-found"
+@layout MainLayout
+
+Not Found
+Sorry, the content you are looking for does not exist.
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Program.cs b/extra/admin-api/Utilities/Spacebar.Client/Program.cs
new file mode 100644
index 000000000..7d7d88e10
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Program.cs
@@ -0,0 +1,19 @@
+using ArcaneLibs.Blazor.Components.Services;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using Spacebar.Client;
+using Spacebar.Client.Core;
+using Spacebar.Client.WebCore;
+
+var builder = WebAssemblyHostBuilder.CreateDefault(args);
+builder.RootComponents.Add("#app");
+builder.RootComponents.Add("head::after");
+
+builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+
+await builder.Build().RunAsync();
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Properties/launchSettings.json b/extra/admin-api/Utilities/Spacebar.Client/Properties/launchSettings.json
new file mode 100644
index 000000000..77aa8afd4
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Properties/launchSettings.json
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "applicationUrl": "http://localhost:5086",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
+ "applicationUrl": "https://localhost:7015;http://localhost:5086",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/extra/admin-api/Utilities/Spacebar.Client/Spacebar.Client.csproj b/extra/admin-api/Utilities/Spacebar.Client/Spacebar.Client.csproj
new file mode 100644
index 000000000..aae396d10
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/Spacebar.Client.csproj
@@ -0,0 +1,39 @@
+
+
+
+ net10.0
+ enable
+ enable
+ true
+ service-worker-assets.js
+
+
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Utilities/Spacebar.Client/WebCore/SessionStore.cs b/extra/admin-api/Utilities/Spacebar.Client/WebCore/SessionStore.cs
new file mode 100644
index 000000000..ae98ee570
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/WebCore/SessionStore.cs
@@ -0,0 +1,61 @@
+using System.Text.Json.Serialization;
+using ArcaneLibs.Blazor.Components.Services;
+
+namespace Spacebar.Client.WebCore;
+
+public class SessionStore(ILogger logger, LocalStorageService localStorage) {
+ public const string CurrentSessionKey = "chat.spacebar.client.current_session";
+ public const string AllSessionsKey = "chat.spacebar.client.sessions";
+ public async Task GetCurrentSessionAsync() {
+ if (!await localStorage.ContainsKeyAsync(CurrentSessionKey)) return null;
+ var entryId = await localStorage.GetItemFromJsonAsync(CurrentSessionKey);
+ var entries = await GetAllSessionsAsync();
+ return entries[entryId];
+ }
+
+ public async Task> GetAllSessionsAsync() {
+ if (!await localStorage.ContainsKeyAsync(AllSessionsKey)) return [];
+ var data = await localStorage.GetItemFromJsonAsync>(AllSessionsKey);
+ return data ?? [];
+ }
+
+ public async Task AddSession(SessionEntry sessionEntry, bool setCurrent = false) {
+ var sessions = await GetAllSessionsAsync();
+ var newId = Guid.NewGuid();
+ sessions.Add(newId, sessionEntry);
+ await localStorage.SetItemAsJsonAsync(AllSessionsKey, sessions);
+
+ if (setCurrent) {
+ await localStorage.SetItemAsJsonAsync(CurrentSessionKey, newId);
+ }
+ }
+
+ public async Task SetCurrentSessionAsync(Guid sessionId) {
+ await localStorage.SetItemAsJsonAsync(CurrentSessionKey, sessionId);
+ }
+}
+
+public class SessionEntry {
+ [JsonPropertyName("server_name")]
+ public required string ServerName { get; set; }
+
+ [JsonPropertyName("access_token")]
+ public required string AccessToken { get; set; }
+
+ [JsonPropertyName("profile_cache")]
+ public required ProfileCacheData? ProfileCache { get; set; }
+
+ public class ProfileCacheData {
+ [JsonPropertyName("id"), JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
+ public required long Id { get; set; }
+
+ [JsonPropertyName("username")]
+ public required string Username { get; set; }
+
+ [JsonPropertyName("discriminator")]
+ public string? Discriminator { get; set; }
+
+ [JsonPropertyName("avatar_url")]
+ public required string AvatarUrl { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/_Imports.razor b/extra/admin-api/Utilities/Spacebar.Client/_Imports.razor
new file mode 100644
index 000000000..551081647
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/_Imports.razor
@@ -0,0 +1,10 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.AspNetCore.Components.WebAssembly.Http
+@using Microsoft.JSInterop
+@using Spacebar.Client
+@using Spacebar.Client.Layout
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/app.css b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/app.css
new file mode 100644
index 000000000..7c6e626e9
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/app.css
@@ -0,0 +1,171 @@
+@import url('jetbrains-mono/jetbrains-mono.css');
+
+html, body {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ background-color: #222;
+ color: #aaa;
+}
+
+pre, .code {
+ font-family: 'JetBrains Mono', monospace;
+}
+
+.dbgBanner {
+ transition: 0.25s;
+ display: block;
+ width: 100%;
+ transform: scale(1, 1);
+ height: 1.5em;
+}
+
+ .dbgBanner.hidden {
+ transition: 0.5s;
+ transform: scale(1, 0);
+ height: 0;
+ }
+
+#app > div > main > div {
+ background-color: #333;
+ border-bottom: none;
+}
+
+.table, .table-striped > tbody > tr:nth-of-type(odd), .table-hover > tbody > tr:hover {
+ color: unset;
+}
+
+h1:focus {
+ outline: none;
+}
+
+a, .btn-link {
+ color: #0071c1;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
+.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
+ box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
+}
+
+.content {
+ padding-top: 1.1rem;
+}
+
+.valid.modified:not([type=checkbox]) {
+ outline: 1px solid #26b050;
+}
+
+.invalid {
+ outline: 1px solid red;
+}
+
+.validation-message {
+ color: red;
+}
+
+#blazor-error-ui {
+ color-scheme: light only;
+ background: lightyellow;
+ bottom: 0;
+ box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
+ box-sizing: border-box;
+ display: none;
+ left: 0;
+ padding: 0.6rem 1.25rem 0.7rem 1.25rem;
+ position: fixed;
+ width: 100%;
+ z-index: 1000;
+}
+
+ #blazor-error-ui .dismiss {
+ cursor: pointer;
+ position: absolute;
+ right: 0.75rem;
+ top: 0.5rem;
+ }
+
+.blazor-error-boundary {
+ background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
+ padding: 1rem 1rem 1rem 3.7rem;
+ color: white;
+}
+
+ .blazor-error-boundary::after {
+ content: "An error has occurred."
+ }
+
+.loading-progress {
+ position: absolute;
+ display: block;
+ width: 8rem;
+ height: 8rem;
+ inset: 20vh 0 auto 0;
+ margin: 0 auto 0 auto;
+}
+
+ .loading-progress circle {
+ fill: none;
+ stroke: #e0e0e0;
+ stroke-width: 0.6rem;
+ transform-origin: 50% 50%;
+ transform: rotate(-90deg);
+ }
+
+ .loading-progress circle:nth-child(2) {
+ stroke: #1b6ec2;
+ stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
+ transition: stroke-dasharray 0.05s ease-in-out;
+ }
+
+ .loading-progress foreignObject {
+ width: 100%;
+ height: 100%;
+ }
+
+ .loading-progress foreignObject>img {
+ width: 50%;
+ height: 50%;
+ position: absolute;
+ object-fit: contain;
+ object-position: center;
+ left: calc(25%);
+ top: calc(25%);
+ }
+
+ .loading-progress foreignObject>img:nth-of-type(1) {
+ opacity: calc(100% - var(--blazor-load-percentage, 0%));
+ transition: 0.5s;
+ }
+
+ .loading-progress foreignObject>img:nth-of-type(2) {
+ opacity: var(--blazor-load-percentage, 0%);
+ transition: 0.5s;
+ }
+
+.loading-progress-text {
+ position: absolute;
+ text-align: center;
+ font-weight: bold;
+ inset: calc(30vh + 3.25rem) 0 auto 0.2rem;
+}
+
+ .loading-progress-text:after {
+ content: var(--blazor-load-percentage-text, "Loading");
+ }
+
+code {
+ color: #c02d76;
+}
+
+.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
+ color: var(--bs-secondary-color);
+ text-align: end;
+}
+
+.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
+ text-align: start;
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/jetbrains-mono.css b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/jetbrains-mono.css
new file mode 100644
index 000000000..78aedd2b1
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/jetbrains-mono.css
@@ -0,0 +1,118 @@
+/* source: https://gist.github.com/aasmpro/95776294ecf48bd7d0562504bad848ea */
+
+/* normal fonts */
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 100;
+ src: url("./ttf/JetBrainsMono-Thin.ttf") format("truetype");
+ src: url("./webfonts/JetBrainsMono-Thin.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 200;
+ src: url("./webfonts/JetBrainsMono-ExtraLight.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 300;
+ src: url("./webfonts/JetBrainsMono-Light.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 400;
+ src: url("./webfonts/JetBrainsMono-Regular.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 500;
+ src: url("./webfonts/JetBrainsMono-Medium.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 600;
+ src: url("./webfonts/JetBrainsMono-SemiBold.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 700;
+ src: url("./webfonts/JetBrainsMono-Bold.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: normal;
+ font-weight: 800;
+ src: url("./webfonts/JetBrainsMono-ExtraBold.woff2") format("woff2");
+}
+
+/* italic fonts */
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 100;
+ src: url("./webfonts/JetBrainsMono-ThinItalic.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 200;
+ src: url("./webfonts/JetBrainsMono-ExtraLightItalic.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 300;
+ src: url("./webfonts/JetBrainsMono-LightItalic.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 400;
+ src: url("./webfonts/JetBrainsMono-Italic.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 500;
+ src: url("./webfonts/JetBrainsMono-MediumItalic.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 600;
+ src: url("./webfonts/JetBrainsMono-SemiBoldItalic.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 700;
+ src: url("./webfonts/JetBrainsMono-BoldItalic.woff2") format("woff2");
+}
+
+@font-face {
+ font-family: JetBrainsMono;
+ font-style: italic;
+ font-weight: 800;
+ src: url("./webfonts/JetBrainsMono-ExtraBoldItalic.woff2") format("woff2");
+}
\ No newline at end of file
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Bold.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Bold.woff2
new file mode 100644
index 000000000..4917f4341
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Bold.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-BoldItalic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-BoldItalic.woff2
new file mode 100644
index 000000000..536d3f715
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-BoldItalic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBold.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBold.woff2
new file mode 100644
index 000000000..8f88c5464
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBold.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBoldItalic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBoldItalic.woff2
new file mode 100644
index 000000000..d1478bacc
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraBoldItalic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLight.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLight.woff2
new file mode 100644
index 000000000..b97239f32
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLight.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLightItalic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLightItalic.woff2
new file mode 100644
index 000000000..be01aaca5
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ExtraLightItalic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Italic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Italic.woff2
new file mode 100644
index 000000000..d60c270e8
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Italic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Light.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Light.woff2
new file mode 100644
index 000000000..653849873
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Light.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-LightItalic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-LightItalic.woff2
new file mode 100644
index 000000000..66ca3d2b9
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-LightItalic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Medium.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Medium.woff2
new file mode 100644
index 000000000..669d04cdf
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Medium.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-MediumItalic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-MediumItalic.woff2
new file mode 100644
index 000000000..80cfd15e0
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-MediumItalic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Regular.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Regular.woff2
new file mode 100644
index 000000000..40da42765
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Regular.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBold.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBold.woff2
new file mode 100644
index 000000000..5ead7b0d6
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBold.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBoldItalic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBoldItalic.woff2
new file mode 100644
index 000000000..c5dd294b4
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-SemiBoldItalic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Thin.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Thin.woff2
new file mode 100644
index 000000000..17270e459
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-Thin.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ThinItalic.woff2 b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ThinItalic.woff2
new file mode 100644
index 000000000..a6432151c
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/css/jetbrains-mono/webfonts/JetBrainsMono-ThinItalic.woff2 differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/favicon.png b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/favicon.png
new file mode 100644
index 000000000..8422b5969
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/favicon.png differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/icon-192.png b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/icon-192.png
new file mode 100644
index 000000000..166f56da7
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/icon-192.png differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/icon-512.png b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/icon-512.png
new file mode 100644
index 000000000..c2dd4842d
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/icon-512.png differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/img/icon.png b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/img/icon.png
new file mode 100644
index 000000000..36b59c61e
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/img/icon.png differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/img/icon_white.png b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/img/icon_white.png
new file mode 100644
index 000000000..0058bfa9e
Binary files /dev/null and b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/img/icon_white.png differ
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/index.html b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/index.html
new file mode 100644
index 000000000..fa8a79b8e
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ Spacebar.Client
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ An unhandled error has occurred.
+
Reload
+
🗙
+
+
+
+
+
+
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/manifest.webmanifest b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/manifest.webmanifest
new file mode 100644
index 000000000..a30ef2e29
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/manifest.webmanifest
@@ -0,0 +1,22 @@
+{
+ "name": "Spacebar.Client",
+ "short_name": "Spacebar.Client",
+ "id": "./",
+ "start_url": "./",
+ "display": "standalone",
+ "background_color": "#ffffff",
+ "theme_color": "#03173d",
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "icon-512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ },
+ {
+ "src": "icon-192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ }
+ ]
+}
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/service-worker.js b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/service-worker.js
new file mode 100644
index 000000000..fe614daee
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/service-worker.js
@@ -0,0 +1,4 @@
+// In development, always fetch from the network and do not enable offline support.
+// This is because caching would make development more difficult (changes would not
+// be reflected on the first load after each change).
+self.addEventListener('fetch', () => { });
diff --git a/extra/admin-api/Utilities/Spacebar.Client/wwwroot/service-worker.published.js b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/service-worker.published.js
new file mode 100644
index 000000000..51a0e5c7a
--- /dev/null
+++ b/extra/admin-api/Utilities/Spacebar.Client/wwwroot/service-worker.published.js
@@ -0,0 +1,55 @@
+// Caution! Be sure you understand the caveats before publishing an application with
+// offline support. See https://aka.ms/blazor-offline-considerations
+
+self.importScripts('./service-worker-assets.js');
+self.addEventListener('install', event => event.waitUntil(onInstall(event)));
+self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
+self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
+
+const cacheNamePrefix = 'offline-cache-';
+const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
+const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /\.webmanifest$/ ];
+const offlineAssetsExclude = [ /^service-worker\.js$/ ];
+
+// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'.
+const base = "/";
+const baseUrl = new URL(base, self.origin);
+const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href);
+
+async function onInstall(event) {
+ console.info('Service worker: Install');
+
+ // Fetch and cache all matching items from the assets manifest
+ const assetsRequests = self.assetsManifest.assets
+ .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
+ .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
+ .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
+ await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
+}
+
+async function onActivate(event) {
+ console.info('Service worker: Activate');
+
+ // Delete unused caches
+ const cacheKeys = await caches.keys();
+ await Promise.all(cacheKeys
+ .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
+ .map(key => caches.delete(key)));
+}
+
+async function onFetch(event) {
+ let cachedResponse = null;
+ if (event.request.method === 'GET') {
+ // For all navigation requests, try to serve index.html from cache,
+ // unless that request is for an offline resource.
+ // If you need some URLs to be server-rendered, edit the following check to exclude those URLs
+ const shouldServeIndexHtml = event.request.mode === 'navigate'
+ && !manifestUrlList.some(url => url === event.request.url);
+
+ const request = shouldServeIndexHtml ? 'index.html' : event.request;
+ const cache = await caches.open(cacheName);
+ cachedResponse = await cache.match(request);
+ }
+
+ return cachedResponse || fetch(event.request);
+}
diff --git a/nix/testVm/configuration.nix b/nix/testVm/configuration.nix
index a610e8edf..f732ffd78 100644
--- a/nix/testVm/configuration.nix
+++ b/nix/testVm/configuration.nix
@@ -59,7 +59,7 @@ in
};
offload = {
- enable = true;
+ enable = false;
gateway = {
enableIdentify = true;
enableGuildMembers = true;
@@ -77,12 +77,12 @@ in
};
cdnCs = {
- enable = false;
+ enable = true;
extraConfiguration.ConnectionStrings.Spacebar = csConnectionString;
};
uApi = {
- enable = true;
+ enable = false;
extraConfiguration.ConnectionStrings.Spacebar = csConnectionString;
};