diff --git a/Docs/Reference/Index.md b/Docs/Reference/Index.md index bd65a7f..c06f7a6 100644 --- a/Docs/Reference/Index.md +++ b/Docs/Reference/Index.md @@ -8,7 +8,7 @@ Every documented source file (209 total), alphabetical by path. See the [TOC](TO | [Magnetar.Protocol/Discovery/WebServiceDiscoveryManifest.cs](files/Magnetar.Protocol/Discovery/WebServiceDiscoveryManifest.cs.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | class | Plain DTO written by the Quasar supervisor to `service-manifest.json` (path resolved by `MagnetarPaths.GetWebServiceManifestPath()`) so that Quasar.Bootstrap and other local processes can discover the running Quasar web service without a pre-configured port. | | [Magnetar.Protocol/Magnetar.Protocol.csproj](files/Magnetar.Protocol/Magnetar.Protocol.csproj.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | project file | MSBuild project file for the `Magnetar.Protocol` shared contract library. Targets `netstandard2.0` so the assembly can be loaded by both the Quasar Blazor Server supervisor (net8+) and the in-DS `Quasar.Agent` plugin (which runs inside the Space Engineers process). | | [Magnetar.Protocol/Model/AgentHello.cs](files/Magnetar.Protocol/Model/AgentHello.cs.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | class | Handshake payload sent by `Quasar.Agent` to the Quasar supervisor immediately after the WebSocket connection is established. Carries all static identity information needed by the supervisor to register the agent connection. | -| [Magnetar.Protocol/Model/AgentSnapshot.cs](files/Magnetar.Protocol/Model/AgentSnapshot.cs.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | class | Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, recent deaths, and loaded plugin list. | +| [Magnetar.Protocol/Model/AgentSnapshot.cs](files/Magnetar.Protocol/Model/AgentSnapshot.cs.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | class | Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, registered PluginSdk chat commands, recent deaths, and loaded plugin list. | | [Magnetar.Protocol/Model/ChatMessageSnapshot.cs](files/Magnetar.Protocol/Model/ChatMessageSnapshot.cs.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | class | Immutable-style DTO representing a single in-game chat message captured for transmission in `AgentSnapshot.RecentChat`, including whether the message was emitted by the dedicated server/Good.bot rather than a player. | | [Magnetar.Protocol/Model/DeathEventSnapshot.cs](files/Magnetar.Protocol/Model/DeathEventSnapshot.cs.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | class | Sealed DTO representing a single player death event captured for transmission in `AgentSnapshot.RecentDeaths`. Carries victim, optional killer and weapon, death classification, and timestamp. | | [Magnetar.Protocol/Model/EntityDeleteRequest.cs](files/Magnetar.Protocol/Model/EntityDeleteRequest.cs.md) | [Magnetar.Protocol](Modules/Magnetar.Protocol.md) | class | Minimal request DTO carrying the target entity ID for the `ServerCommandType.DeleteEntity` command. Serialized as JSON into `ServerCommandEnvelope.Payload`. | @@ -41,7 +41,7 @@ Every documented source file (209 total), alphabetical by path. See the [TOC](TO | [Quasar.Agent/AgentProfilerPatches.cs](files/Quasar.Agent/AgentProfilerPatches.cs.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | class | Mode-aware Harmony patch registrar for Quasar's profiler telemetry. `SafeContinuous` patches only named high-level Space Engineers server methods for low-overhead continuous timing. `DeepContinuous` keeps those patches and adds detailed network-event method hooks plus IL call-site transpilers for session components, replication simulation, entity update dispatch, parallel waits/callbacks, and physics stepping internals. `Off` skips profiler patches. | | [Quasar.Agent/AgentProfilerTranspiler.cs](files/Quasar.Agent/AgentProfilerTranspiler.cs.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | class | Generic Harmony `CodeInstruction` transpiler used by deep profiler mode. It wraps selected `call` / `callvirt` instructions with `AgentProfiler.BeginCallSite` and `AgentProfiler.EndCallSite`, giving Quasar-native call-site attribution without external patch-manager MSIL helpers. | | [Quasar.Agent/EntityInspector.cs](files/Quasar.Agent/EntityInspector.cs.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | class | `EntityInspector` is an internal static helper that queries and manipulates live `MyEntity` instances on the game thread, mapping them to transport-friendly `EntitySummary` DTOs for the Quasar admin UI. It supports paginated, filtered entity listing and direct entity deletion. | -| [Quasar.Agent/GameBridge.cs](files/Quasar.Agent/GameBridge.cs.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | class | `GameBridge` is the central game-thread façade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`. | +| [Quasar.Agent/GameBridge.cs](files/Quasar.Agent/GameBridge.cs.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | class | `GameBridge` is the central game-thread façade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, registered PluginSdk chat commands, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`. | | [Quasar.Agent/PluginLogOutbox.cs](files/Quasar.Agent/PluginLogOutbox.cs.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | class | Bounded, thread-safe buffer that captures plugin log lines emitted in-process by the PluginSdk Quasar log sink and hands them to `AgentConnection` in batches for streaming to Quasar (as `PluginLogBatch` / `WireMessageKind.PluginLogs`). The buffer survives Quasar outages: lines accumulate while disconnected and are flushed on reconnect, so the supervisor's "Recent plugin logs" panel is backfilled rather than losing everything captured while Quasar was down. | | [Quasar.Agent/Quasar.Agent.csproj](files/Quasar.Agent/Quasar.Agent.csproj.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | project file | MSBuild project file for `Quasar.Agent`, a `netstandard2.0` class library (x64-only) that produces `Quasar.Agent.dll` — the Magnetar/Space Engineers plugin assembly. All game and PluginSdk references are `Private="False"` (provided by the host at runtime). Harmony is a package dependency because the agent applies profiler patches in-process. | | [Quasar.Agent/StopCommand.cs](files/Quasar.Agent/StopCommand.cs.md) | [Quasar.Agent](Modules/Quasar.Agent.md) | class | `StopCommand` is a Quasar-owned PluginSdk command module for the root `!stop` in-game admin command. It overrides Magnetar's earlier `stop` root by being registered later from `AdminPlugin`, acknowledges the caller, reports an admin stop to Quasar through a static hook wired by `AdminPlugin`, then calls `ServerControl.SaveAndQuit()` on a worker task so the world is saved and the dedicated server process exits. | @@ -51,7 +51,7 @@ Every documented source file (209 total), alphabetical by path. See the [TOC](TO | [Quasar.Bootstrap/Quasar.Bootstrap.csproj](files/Quasar.Bootstrap/Quasar.Bootstrap.csproj.md) | [Quasar.Bootstrap](Modules/Quasar.Bootstrap.md) | project file | MSBuild project file for `Quasar.Bootstrap`, a `net10.0` console executable that targets `linux-x64` and `win-x64`. RID-targeted publish restores and publishes the `Quasar` worker as a single-file sub-app into a `WebService/` subfolder. | | [Quasar/Components/App.razor](files/Quasar/Components/App.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | The root HTML document component for the Blazor Server application. It renders the full `` skeleton, wires MudBlazor, ApexCharts and app CSS, loads the Blazor WebAssembly/server JS runtime, and hosts `` and `` as the two top-level interactive components. | | [Quasar/Components/Dashboard/ServerCard.razor](files/Quasar/Components/Dashboard/ServerCard.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Card component for a single managed server shown on the Dashboard card layout. Displays the server display name, status chip (OFF / STARTING / CONNECTING / OPEN / STOPPING / RESTARTING / CRASHED / FAULTED), host/world caption, last message or health summary, management icon buttons (console, clone, template, edit, delete), lifecycle action buttons, and `ServerDetailPanel` body content. The Start button can be disabled by the dashboard while managed runtime prerequisites are still preparing. | -| [Quasar/Components/Dashboard/ServerDetailPanel.razor](files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons, a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined "Affinity " chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert. | +| [Quasar/Components/Dashboard/ServerDetailPanel.razor](files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons (Save disabled while the runtime is `Starting`/`Stopping`/`Restarting`), a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined "Affinity " chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert. | | [Quasar/Components/Layout/BrandingHeadContent.razor](files/Quasar/Components/Layout/BrandingHeadContent.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | A lightweight head-content component that reactively updates the page favicon whenever `BrandingService` fires its `Changed` event. Rendered inside `MainLayout` so the favicon tracks live branding changes without a full page reload. | | [Quasar/Components/Layout/MainLayout.razor](files/Quasar/Components/Layout/MainLayout.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Top-level application shell layout. Provides the MudBlazor theme/provider setup, theme-configured hover-list CSS variables, a responsive app bar with branding, update notification bell, theme-mode switcher, auth (login/logout) controls, and a Quasar power dialog trigger. It hosts a collapsible side drawer with `NavMenu`, the main content area that renders `@Body`, and a full-screen overlay while Quasar is restarting or shutting down. | | [Quasar/Components/Layout/MainLayout.razor.css](files/Quasar/Components/Layout/MainLayout.razor.css.md) | [Quasar.Components](Modules/Quasar.Components.md) | CSS | Scoped CSS for `MainLayout.razor`. Styles the brand logo mark in the app bar and the Blazor framework error banner. | @@ -67,7 +67,7 @@ Every documented source file (209 total), alphabetical by path. See the [TOC](TO | [Quasar/Components/Pages/AnalyticsPanelDialog.razor](files/Quasar/Components/Pages/AnalyticsPanelDialog.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Modal dialog opened from `Analytics.razor` to edit the display settings of a single analytics chart panel. Returns an `AnalyticsPanelDialogResult` containing the updated visibility, order, column span, and row span when the user clicks Save. | | [Quasar/Components/Pages/Appearance.razor](files/Quasar/Components/Pages/Appearance.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Routable page (`/settings/appearance`) for live branding customisation. Allows editing the app name, subtitle, theme preset, individual palette colors (light and dark), Quasar hover-list colors, logos (light-mode and dark-mode), and favicon. Changes to logos and favicon take effect immediately on save to disk; palette and name changes apply across all open sessions after the top-level Save is clicked. | | [Quasar/Components/Pages/Backup.razor](files/Quasar/Components/Pages/Backup.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Routable page (`@page "/backup"`) for configuring the stored-backup folder and creating, restoring, scheduling, and managing Quasar configuration, server, and world backups. Gated by `@attribute [Authorize(Policy = QuasarPolicyNames.CanManageSecurity)]` and `@implements IDisposable`. | -| [Quasar/Components/Pages/Chat.razor](files/Quasar/Components/Pages/Chat.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. | +| [Quasar/Components/Pages/Chat.razor](files/Quasar/Components/Pages/Chat.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, command-mode autocomplete sourced from registered PluginSdk commands, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. | | [Quasar/Components/Pages/ConfigProfilePendingChangesDialog.razor](files/Quasar/Components/Pages/ConfigProfilePendingChangesDialog.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Small confirmation dialog displayed by `Configs.razor` when the user tries to switch config templates while the current template has unsaved edits. Returns a `PendingChangesAction` discriminated union (Cancel, Discard, Save) to let the caller decide how to handle the pending state. | | [Quasar/Components/Pages/ConfigProfileQuickCreateDialog.razor](files/Quasar/Components/Pages/ConfigProfileQuickCreateDialog.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | Modal dialog for quickly creating a new `QuasarConfigProfile` (config template) from the Home dashboard setup wizard. Validates that a name is provided, persists the new profile via `QuasarConfigProfileCatalog`, and returns the created `QuasarConfigProfile` to the caller on success. | | [Quasar/Components/Pages/Configs.razor](files/Quasar/Components/Pages/Configs.razor.md) | [Quasar.Components](Modules/Quasar.Components.md) | Blazor component | The `/configs` page: a full editor for reusable Magnetar config templates (`QuasarConfigProfile`) that are applied to assigned dedicated servers at startup. A sidebar lists/creates/clones/deletes templates; the main column edits World settings, Plugins, Mods, and Developer dev-folders across tabbed and collapsible panels, generating only the active tab and opened panel bodies. QoL features include searchable/jump-to world options, a refreshable plugin catalog, Steam Workshop search, world-template mod merge, dead Workshop mod cleanup, unsaved-change guarding, and integration with the plugin-manifest picker dialog for registering local dev folders. (This page has no charts; the analytics charting work lives in `Analytics.razor`.) | @@ -154,7 +154,7 @@ Every documented source file (209 total), alphabetical by path. See the [TOC](TO | [Quasar/Services/Backup/QuasarBackupSettingsService.cs](files/Quasar/Services/Backup/QuasarBackupSettingsService.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | Singleton store for automatic-backup rules and the stored-backup folder setting. Persists schedule rules to `backup-settings.json` (`MagnetarPaths.GetQuasarBackupSettingsPath()`) in the Quasar data directory and picks up external schedule edits via a debounced (250 ms) `FileSystemWatcher`, mirroring `BrandingService`. It also patches `Quasar:BackupDirectory` in the data-directory `appsettings.json` for the Backup page and applies the resolved path to the live `WebServiceOptions`. | | [Quasar/Services/Backup/ServerRestoreCoordinator.cs](files/Quasar/Services/Backup/ServerRestoreCoordinator.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | `ServerRestoreCoordinator` tracks which managed server unique names currently have a backup restore in progress. Restores rewrite server files in place, so callers can use this coordinator to prevent a server start or a second restore from racing against the same server data. | | [Quasar/Services/BrandingPresets.cs](files/Quasar/Services/BrandingPresets.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | `BrandingPresets` is a static catalogue of the four built-in UI theme presets (Quasar Default, Midnight Blue, Slate, High Contrast). It exposes `GetLightPalette` / `GetDarkPalette` factory methods that layer identity/surface colour overrides on top of the base `ThemePalette.QuasarLight()` / `QuasarDark()` palettes, keeping all variants internally coherent. `BrandingPresetDefinition` is the companion display record. | -| [Quasar/Services/BrandingService.cs](files/Quasar/Services/BrandingService.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | `BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into `{WebRootPath}/branding/`, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout. | +| [Quasar/Services/BrandingService.cs](files/Quasar/Services/BrandingService.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | `BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into the data-root `Branding/` directory so they survive web-service updates, migrates legacy `wwwroot/branding` files when present, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout. | | [Quasar/Services/BrowserLauncher.cs](files/Quasar/Services/BrowserLauncher.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | `BrowserLauncher` is a static helper that decides whether to open a browser on startup and cross-platform launches the system default browser at a given URL. On Linux it requires a display server (`DISPLAY` or `WAYLAND_DISPLAY`) to be available. | | [Quasar/Services/DedicatedServerCatalog.cs](files/Quasar/Services/DedicatedServerCatalog.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | `DedicatedServerCatalog` is the authoritative, persisted registry of all `DedicatedServerDefinition` entries managed by Quasar. It loads definitions from `server.json` files on disk at startup, watches the directory for external edits (debounced 250 ms reload), provides thread-safe upsert/delete with atomic file writes, maintains a history archive of every change, and fires a `Changed` event consumed by the supervisor and UI. | | [Quasar/Services/DedicatedServerRuntimePreparer.cs](files/Quasar/Services/DedicatedServerRuntimePreparer.cs.md) | [Quasar.Services.Core](Modules/Quasar.Services.Core.md) | class | `DedicatedServerRuntimePreparer` transforms a `DedicatedServerDefinition` into a fully staged on-disk runtime immediately before a dedicated server process is launched. It writes the runtime DS config XML including the per-server advertised `ServerName`/`WorldName`, the Magnetar plugin sources/profile XML, the world `Sandbox_config.sbc` session settings and mod list, and the `LastSession.sbl` pointer file; deploys the bundled Quasar.Agent DLLs plus runtime-specific Harmony dependency; exposes bundled-vs-deployed agent hash comparison for manual refresh warnings; seeds the world from a template if needed; and computes the final command-line arguments string. The output is a `PreparedDedicatedServerLaunch` record. | diff --git a/Docs/Reference/Modules/Magnetar.Protocol.md b/Docs/Reference/Modules/Magnetar.Protocol.md index 6972fdc..13d8d28 100644 --- a/Docs/Reference/Modules/Magnetar.Protocol.md +++ b/Docs/Reference/Modules/Magnetar.Protocol.md @@ -2,7 +2,7 @@ *Module `Magnetar.Protocol` — 29 files.* See the [handbook TOC](../TOC.md) and the [file Index](../Index.md). -Shared `netstandard2.0` contract library referenced by the Quasar supervisor, the in-DS [Quasar.Agent](Quasar.Agent.md), and [Quasar.Bootstrap](Quasar.Bootstrap.md). It defines the entire agent↔supervisor wire protocol: the tagged-union `AgentWireMessage` envelope with `WireMessageKind` discriminators, the server-command request/response triple (`ServerCommandEnvelope` / `ServerCommandResult` / `ServerCommandType`), the handshake (`AgentHello`) and periodic telemetry (`AgentSnapshot` carrying `ServerMetrics`, `PlayerSnapshot`, chat/death events and plugin info), the entity-browser and plugin-config DTOs, the `WebServiceDiscoveryManifest` used to locate a running supervisor, the `IQuasarConfigProvider` bridge, and runtime helpers (`MagnetarPaths`, `QuasarActiveReleasePointer`, `QuasarReleaseVersion`, `QuasarWebReleaseLayout`). It has zero external dependencies by design so it can also load inside the .NET-Framework game process. +Shared `netstandard2.0` contract library referenced by the Quasar supervisor, the in-DS [Quasar.Agent](Quasar.Agent.md), and [Quasar.Bootstrap](Quasar.Bootstrap.md). It defines the entire agent↔supervisor wire protocol: the tagged-union `AgentWireMessage` envelope with `WireMessageKind` discriminators, the server-command request/response triple (`ServerCommandEnvelope` / `ServerCommandResult` / `ServerCommandType`), the handshake (`AgentHello`) and periodic telemetry (`AgentSnapshot` carrying `ServerMetrics`, `PlayerSnapshot`, chat/death events, PluginSdk command suggestions, and plugin info), the entity-browser and plugin-config DTOs, the `WebServiceDiscoveryManifest` used to locate a running supervisor, the `IQuasarConfigProvider` bridge, and runtime helpers (`MagnetarPaths`, `QuasarActiveReleasePointer`, `QuasarReleaseVersion`, `QuasarWebReleaseLayout`). It has zero external dependencies by design so it can also load inside the .NET-Framework game process. ## Files @@ -12,7 +12,7 @@ Shared `netstandard2.0` contract library referenced by the Quasar supervisor, th | [Magnetar.Protocol/Discovery/WebServiceDiscoveryManifest.cs](../files/Magnetar.Protocol/Discovery/WebServiceDiscoveryManifest.cs.md) | class | Plain DTO written by the Quasar supervisor to `service-manifest.json` (path resolved by `MagnetarPaths.GetWebServiceManifestPath()`) so that Quasar.Bootstrap and other local processes can discover the running Quasar web service without a pre-configured port. | | [Magnetar.Protocol/Magnetar.Protocol.csproj](../files/Magnetar.Protocol/Magnetar.Protocol.csproj.md) | project file | MSBuild project file for the `Magnetar.Protocol` shared contract library. Targets `netstandard2.0` so the assembly can be loaded by both the Quasar Blazor Server supervisor (net8+) and the in-DS `Quasar.Agent` plugin (which runs inside the Space Engineers process). | | [Magnetar.Protocol/Model/AgentHello.cs](../files/Magnetar.Protocol/Model/AgentHello.cs.md) | class | Handshake payload sent by `Quasar.Agent` to the Quasar supervisor immediately after the WebSocket connection is established. Carries all static identity information needed by the supervisor to register the agent connection. | -| [Magnetar.Protocol/Model/AgentSnapshot.cs](../files/Magnetar.Protocol/Model/AgentSnapshot.cs.md) | class | Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, recent deaths, and loaded plugin list. | +| [Magnetar.Protocol/Model/AgentSnapshot.cs](../files/Magnetar.Protocol/Model/AgentSnapshot.cs.md) | class | Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, registered PluginSdk chat commands, recent deaths, and loaded plugin list. | | [Magnetar.Protocol/Model/ChatMessageSnapshot.cs](../files/Magnetar.Protocol/Model/ChatMessageSnapshot.cs.md) | class | Immutable-style DTO representing a single in-game chat message captured for transmission in `AgentSnapshot.RecentChat`, including whether the message was emitted by the dedicated server/Good.bot rather than a player. | | [Magnetar.Protocol/Model/DeathEventSnapshot.cs](../files/Magnetar.Protocol/Model/DeathEventSnapshot.cs.md) | class | Sealed DTO representing a single player death event captured for transmission in `AgentSnapshot.RecentDeaths`. Carries victim, optional killer and weapon, death classification, and timestamp. | | [Magnetar.Protocol/Model/EntityDeleteRequest.cs](../files/Magnetar.Protocol/Model/EntityDeleteRequest.cs.md) | class | Minimal request DTO carrying the target entity ID for the `ServerCommandType.DeleteEntity` command. Serialized as JSON into `ServerCommandEnvelope.Payload`. | diff --git a/Docs/Reference/Modules/Quasar.Agent.md b/Docs/Reference/Modules/Quasar.Agent.md index be181ab..123ce11 100644 --- a/Docs/Reference/Modules/Quasar.Agent.md +++ b/Docs/Reference/Modules/Quasar.Agent.md @@ -16,7 +16,7 @@ The plugin loaded inside each Space Engineers Dedicated Server (`netstandard2.0` | [Quasar.Agent/AgentProfilerPatches.cs](../files/Quasar.Agent/AgentProfilerPatches.cs.md) | class | Mode-aware Harmony patch registrar for Quasar's profiler telemetry. `SafeContinuous` patches only named high-level Space Engineers server methods for low-overhead continuous timing. `DeepContinuous` keeps those patches and adds detailed network-event method hooks plus IL call-site transpilers for session components, replication simulation, entity update dispatch, parallel waits/callbacks, and physics stepping internals. `Off` skips profiler patches. | | [Quasar.Agent/AgentProfilerTranspiler.cs](../files/Quasar.Agent/AgentProfilerTranspiler.cs.md) | class | Generic Harmony `CodeInstruction` transpiler used by deep profiler mode. It wraps selected `call` / `callvirt` instructions with `AgentProfiler.BeginCallSite` and `AgentProfiler.EndCallSite`, giving Quasar-native call-site attribution without external patch-manager MSIL helpers. | | [Quasar.Agent/EntityInspector.cs](../files/Quasar.Agent/EntityInspector.cs.md) | class | `EntityInspector` is an internal static helper that queries and manipulates live `MyEntity` instances on the game thread, mapping them to transport-friendly `EntitySummary` DTOs for the Quasar admin UI. It supports paginated, filtered entity listing and direct entity deletion. | -| [Quasar.Agent/GameBridge.cs](../files/Quasar.Agent/GameBridge.cs.md) | class | `GameBridge` is the central game-thread façade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`. | +| [Quasar.Agent/GameBridge.cs](../files/Quasar.Agent/GameBridge.cs.md) | class | `GameBridge` is the central game-thread façade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, registered PluginSdk chat commands, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`. | | [Quasar.Agent/PluginLogOutbox.cs](../files/Quasar.Agent/PluginLogOutbox.cs.md) | class | Bounded, thread-safe buffer that captures plugin log lines emitted in-process by the PluginSdk Quasar log sink and hands them to `AgentConnection` in batches for streaming to Quasar (as `PluginLogBatch` / `WireMessageKind.PluginLogs`). The buffer survives Quasar outages: lines accumulate while disconnected and are flushed on reconnect, so the supervisor's "Recent plugin logs" panel is backfilled rather than losing everything captured while Quasar was down. | | [Quasar.Agent/Quasar.Agent.csproj](../files/Quasar.Agent/Quasar.Agent.csproj.md) | project file | MSBuild project file for `Quasar.Agent`, a `netstandard2.0` class library (x64-only) that produces `Quasar.Agent.dll` — the Magnetar/Space Engineers plugin assembly. All game and PluginSdk references are `Private="False"` (provided by the host at runtime). Harmony is a package dependency because the agent applies profiler patches in-process. | | [Quasar.Agent/StopCommand.cs](../files/Quasar.Agent/StopCommand.cs.md) | class | `StopCommand` is a Quasar-owned PluginSdk command module for the root `!stop` in-game admin command. It overrides Magnetar's earlier `stop` root by being registered later from `AdminPlugin`, acknowledges the caller, reports an admin stop to Quasar through a static hook wired by `AdminPlugin`, then calls `ServerControl.SaveAndQuit()` on a worker task so the world is saved and the dedicated server process exits. | diff --git a/Docs/Reference/Modules/Quasar.Components.md b/Docs/Reference/Modules/Quasar.Components.md index 37dd233..55bbc81 100644 --- a/Docs/Reference/Modules/Quasar.Components.md +++ b/Docs/Reference/Modules/Quasar.Components.md @@ -10,7 +10,7 @@ The Blazor Server user interface, built with MudBlazor. Routable pages cover the | --- | --- | --- | | [Quasar/Components/App.razor](../files/Quasar/Components/App.razor.md) | Blazor component | The root HTML document component for the Blazor Server application. It renders the full `` skeleton, wires MudBlazor, ApexCharts and app CSS, loads the Blazor WebAssembly/server JS runtime, and hosts `` and `` as the two top-level interactive components. | | [Quasar/Components/Dashboard/ServerCard.razor](../files/Quasar/Components/Dashboard/ServerCard.razor.md) | Blazor component | Card component for a single managed server shown on the Dashboard card layout. Displays the server display name, status chip (OFF / STARTING / CONNECTING / OPEN / STOPPING / RESTARTING / CRASHED / FAULTED), host/world caption, last message or health summary, management icon buttons (console, clone, template, edit, delete), lifecycle action buttons, and `ServerDetailPanel` body content. The Start button can be disabled by the dashboard while managed runtime prerequisites are still preparing. | -| [Quasar/Components/Dashboard/ServerDetailPanel.razor](../files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md) | Blazor component | Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons, a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined "Affinity " chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert. | +| [Quasar/Components/Dashboard/ServerDetailPanel.razor](../files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md) | Blazor component | Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons (Save disabled while the runtime is `Starting`/`Stopping`/`Restarting`), a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined "Affinity " chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert. | | [Quasar/Components/Layout/BrandingHeadContent.razor](../files/Quasar/Components/Layout/BrandingHeadContent.razor.md) | Blazor component | A lightweight head-content component that reactively updates the page favicon whenever `BrandingService` fires its `Changed` event. Rendered inside `MainLayout` so the favicon tracks live branding changes without a full page reload. | | [Quasar/Components/Layout/MainLayout.razor](../files/Quasar/Components/Layout/MainLayout.razor.md) | Blazor component | Top-level application shell layout. Provides the MudBlazor theme/provider setup, theme-configured hover-list CSS variables, a responsive app bar with branding, update notification bell, theme-mode switcher, auth (login/logout) controls, and a Quasar power dialog trigger. It hosts a collapsible side drawer with `NavMenu`, the main content area that renders `@Body`, and a full-screen overlay while Quasar is restarting or shutting down. | | [Quasar/Components/Layout/MainLayout.razor.css](../files/Quasar/Components/Layout/MainLayout.razor.css.md) | CSS | Scoped CSS for `MainLayout.razor`. Styles the brand logo mark in the app bar and the Blazor framework error banner. | @@ -26,7 +26,7 @@ The Blazor Server user interface, built with MudBlazor. Routable pages cover the | [Quasar/Components/Pages/AnalyticsPanelDialog.razor](../files/Quasar/Components/Pages/AnalyticsPanelDialog.razor.md) | Blazor component | Modal dialog opened from `Analytics.razor` to edit the display settings of a single analytics chart panel. Returns an `AnalyticsPanelDialogResult` containing the updated visibility, order, column span, and row span when the user clicks Save. | | [Quasar/Components/Pages/Appearance.razor](../files/Quasar/Components/Pages/Appearance.razor.md) | Blazor component | Routable page (`/settings/appearance`) for live branding customisation. Allows editing the app name, subtitle, theme preset, individual palette colors (light and dark), Quasar hover-list colors, logos (light-mode and dark-mode), and favicon. Changes to logos and favicon take effect immediately on save to disk; palette and name changes apply across all open sessions after the top-level Save is clicked. | | [Quasar/Components/Pages/Backup.razor](../files/Quasar/Components/Pages/Backup.razor.md) | Blazor component | Routable page (`@page "/backup"`) for configuring the stored-backup folder and creating, restoring, scheduling, and managing Quasar configuration, server, and world backups. Gated by `@attribute [Authorize(Policy = QuasarPolicyNames.CanManageSecurity)]` and `@implements IDisposable`. | -| [Quasar/Components/Pages/Chat.razor](../files/Quasar/Components/Pages/Chat.razor.md) | Blazor component | Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. | +| [Quasar/Components/Pages/Chat.razor](../files/Quasar/Components/Pages/Chat.razor.md) | Blazor component | Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, command-mode autocomplete sourced from registered PluginSdk commands, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. | | [Quasar/Components/Pages/ConfigProfilePendingChangesDialog.razor](../files/Quasar/Components/Pages/ConfigProfilePendingChangesDialog.razor.md) | Blazor component | Small confirmation dialog displayed by `Configs.razor` when the user tries to switch config templates while the current template has unsaved edits. Returns a `PendingChangesAction` discriminated union (Cancel, Discard, Save) to let the caller decide how to handle the pending state. | | [Quasar/Components/Pages/ConfigProfileQuickCreateDialog.razor](../files/Quasar/Components/Pages/ConfigProfileQuickCreateDialog.razor.md) | Blazor component | Modal dialog for quickly creating a new `QuasarConfigProfile` (config template) from the Home dashboard setup wizard. Validates that a name is provided, persists the new profile via `QuasarConfigProfileCatalog`, and returns the created `QuasarConfigProfile` to the caller on success. | | [Quasar/Components/Pages/Configs.razor](../files/Quasar/Components/Pages/Configs.razor.md) | Blazor component | The `/configs` page: a full editor for reusable Magnetar config templates (`QuasarConfigProfile`) that are applied to assigned dedicated servers at startup. A sidebar lists/creates/clones/deletes templates; the main column edits World settings, Plugins, Mods, and Developer dev-folders across tabbed and collapsible panels, generating only the active tab and opened panel bodies. QoL features include searchable/jump-to world options, a refreshable plugin catalog, Steam Workshop search, world-template mod merge, dead Workshop mod cleanup, unsaved-change guarding, and integration with the plugin-manifest picker dialog for registering local dev folders. (This page has no charts; the analytics charting work lives in `Analytics.razor`.) | diff --git a/Docs/Reference/Modules/Quasar.Services.Core.md b/Docs/Reference/Modules/Quasar.Services.Core.md index c3d97d1..000b366 100644 --- a/Docs/Reference/Modules/Quasar.Services.Core.md +++ b/Docs/Reference/Modules/Quasar.Services.Core.md @@ -18,7 +18,7 @@ The heart of the supervisor and its supporting services. `DedicatedServerSupervi | [Quasar/Services/Backup/QuasarBackupSettingsService.cs](../files/Quasar/Services/Backup/QuasarBackupSettingsService.cs.md) | class | Singleton store for automatic-backup rules and the stored-backup folder setting. Persists schedule rules to `backup-settings.json` (`MagnetarPaths.GetQuasarBackupSettingsPath()`) in the Quasar data directory and picks up external schedule edits via a debounced (250 ms) `FileSystemWatcher`, mirroring `BrandingService`. It also patches `Quasar:BackupDirectory` in the data-directory `appsettings.json` for the Backup page and applies the resolved path to the live `WebServiceOptions`. | | [Quasar/Services/Backup/ServerRestoreCoordinator.cs](../files/Quasar/Services/Backup/ServerRestoreCoordinator.cs.md) | class | `ServerRestoreCoordinator` tracks which managed server unique names currently have a backup restore in progress. Restores rewrite server files in place, so callers can use this coordinator to prevent a server start or a second restore from racing against the same server data. | | [Quasar/Services/BrandingPresets.cs](../files/Quasar/Services/BrandingPresets.cs.md) | class | `BrandingPresets` is a static catalogue of the four built-in UI theme presets (Quasar Default, Midnight Blue, Slate, High Contrast). It exposes `GetLightPalette` / `GetDarkPalette` factory methods that layer identity/surface colour overrides on top of the base `ThemePalette.QuasarLight()` / `QuasarDark()` palettes, keeping all variants internally coherent. `BrandingPresetDefinition` is the companion display record. | -| [Quasar/Services/BrandingService.cs](../files/Quasar/Services/BrandingService.cs.md) | class | `BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into `{WebRootPath}/branding/`, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout. | +| [Quasar/Services/BrandingService.cs](../files/Quasar/Services/BrandingService.cs.md) | class | `BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into the data-root `Branding/` directory so they survive web-service updates, migrates legacy `wwwroot/branding` files when present, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout. | | [Quasar/Services/BrowserLauncher.cs](../files/Quasar/Services/BrowserLauncher.cs.md) | class | `BrowserLauncher` is a static helper that decides whether to open a browser on startup and cross-platform launches the system default browser at a given URL. On Linux it requires a display server (`DISPLAY` or `WAYLAND_DISPLAY`) to be available. | | [Quasar/Services/DedicatedServerCatalog.cs](../files/Quasar/Services/DedicatedServerCatalog.cs.md) | class | `DedicatedServerCatalog` is the authoritative, persisted registry of all `DedicatedServerDefinition` entries managed by Quasar. It loads definitions from `server.json` files on disk at startup, watches the directory for external edits (debounced 250 ms reload), provides thread-safe upsert/delete with atomic file writes, maintains a history archive of every change, and fires a `Changed` event consumed by the supervisor and UI. | | [Quasar/Services/DedicatedServerRuntimePreparer.cs](../files/Quasar/Services/DedicatedServerRuntimePreparer.cs.md) | class | `DedicatedServerRuntimePreparer` transforms a `DedicatedServerDefinition` into a fully staged on-disk runtime immediately before a dedicated server process is launched. It writes the runtime DS config XML including the per-server advertised `ServerName`/`WorldName`, the Magnetar plugin sources/profile XML, the world `Sandbox_config.sbc` session settings and mod list, and the `LastSession.sbl` pointer file; deploys the bundled Quasar.Agent DLLs plus runtime-specific Harmony dependency; exposes bundled-vs-deployed agent hash comparison for manual refresh warnings; seeds the world from a template if needed; and computes the final command-line arguments string. The output is a `PreparedDedicatedServerLaunch` record. | diff --git a/Docs/Reference/data/generate_docs.py b/Docs/Reference/data/generate_docs.py index daf7c52..4489dd7 100644 --- a/Docs/Reference/data/generate_docs.py +++ b/Docs/Reference/data/generate_docs.py @@ -38,7 +38,7 @@ def doc_link(src_path, prefix): "envelope with `WireMessageKind` discriminators, the server-command request/response triple " "(`ServerCommandEnvelope` / `ServerCommandResult` / `ServerCommandType`), the handshake " "(`AgentHello`) and periodic telemetry (`AgentSnapshot` carrying `ServerMetrics`, `PlayerSnapshot`, " - "chat/death events and plugin info), the entity-browser and plugin-config DTOs, the " + "chat/death events, PluginSdk command suggestions, and plugin info), the entity-browser and plugin-config DTOs, the " "`WebServiceDiscoveryManifest` used to locate a running supervisor, the `IQuasarConfigProvider` " "bridge, and runtime helpers (`MagnetarPaths`, `QuasarActiveReleasePointer`, " "`QuasarReleaseVersion`, `QuasarWebReleaseLayout`). It has zero external " diff --git a/Docs/Reference/data/groups.json b/Docs/Reference/data/groups.json index c9f7dfb..6d261f4 100644 --- a/Docs/Reference/data/groups.json +++ b/Docs/Reference/data/groups.json @@ -1,15 +1,32 @@ { "g01-protocol": [ + "Magnetar.Protocol/Bridge/IQuasarConfigProvider.cs", + "Magnetar.Protocol/Discovery/WebServiceDiscoveryManifest.cs", + "Magnetar.Protocol/Magnetar.Protocol.csproj", + "Magnetar.Protocol/Model/AgentHello.cs", "Magnetar.Protocol/Model/AgentSnapshot.cs", "Magnetar.Protocol/Model/ChatMessageSnapshot.cs", + "Magnetar.Protocol/Model/DeathEventSnapshot.cs", + "Magnetar.Protocol/Model/EntityDeleteRequest.cs", + "Magnetar.Protocol/Model/EntityListFilter.cs", + "Magnetar.Protocol/Model/EntityListResult.cs", + "Magnetar.Protocol/Model/EntitySummary.cs", "Magnetar.Protocol/Model/KickedPlayerSnapshot.cs", + "Magnetar.Protocol/Model/PlayerSnapshot.cs", + "Magnetar.Protocol/Model/PluginConfigData.cs", + "Magnetar.Protocol/Model/PluginConfigSnapshot.cs", + "Magnetar.Protocol/Model/PluginConfigUpdateRequest.cs", "Magnetar.Protocol/Model/PluginLogBatch.cs", + "Magnetar.Protocol/Model/PluginRuntimeInfo.cs", "Magnetar.Protocol/Model/ProfilerSnapshot.cs", "Magnetar.Protocol/Model/ServerMetrics.cs", "Magnetar.Protocol/Runtime/MagnetarPaths.cs", + "Magnetar.Protocol/Runtime/QuasarActiveReleasePointer.cs", "Magnetar.Protocol/Runtime/QuasarReleaseVersion.cs", "Magnetar.Protocol/Runtime/QuasarWebReleaseLayout.cs", "Magnetar.Protocol/Transport/AgentWireMessage.cs", + "Magnetar.Protocol/Transport/ServerCommandEnvelope.cs", + "Magnetar.Protocol/Transport/ServerCommandResult.cs", "Magnetar.Protocol/Transport/ServerCommandType.cs", "Magnetar.Protocol/Transport/WireMessageKind.cs" ], @@ -21,21 +38,32 @@ "Quasar.Agent/AgentProfilerMode.cs", "Quasar.Agent/AgentProfilerPatches.cs", "Quasar.Agent/AgentProfilerTranspiler.cs", + "Quasar.Agent/EntityInspector.cs", "Quasar.Agent/GameBridge.cs", "Quasar.Agent/PluginLogOutbox.cs", "Quasar.Agent/Quasar.Agent.csproj", + "Quasar.Agent/StopCommand.cs", + "Quasar.Agent/WebServiceLocator.cs", "Quasar.Bootstrap/Program.cs", + "Quasar.Bootstrap/Properties/launchSettings.json", "Quasar.Bootstrap/Quasar.Bootstrap.csproj" ], "g03-models-host": [ + "Quasar/Models/BrandingSettings.cs", "Quasar/Models/CpuAffinitySpec.cs", "Quasar/Models/DedicatedServerDefinition.cs", + "Quasar/Models/DedicatedServerGoalState.cs", + "Quasar/Models/DedicatedServerHealthState.cs", + "Quasar/Models/DedicatedServerProcessPriority.cs", + "Quasar/Models/DedicatedServerProcessState.cs", "Quasar/Models/DedicatedServerRuntimeSnapshot.cs", + "Quasar/Models/KnownPlayerRecord.cs", "Quasar/Models/ManagedServerRuntime.cs", "Quasar/Models/QuasarBackupManifest.cs", "Quasar/Models/QuasarBackupSettings.cs", "Quasar/Models/QuasarConfigProfile.cs", "Quasar/Models/QuasarRestoreReport.cs", + "Quasar/Models/QuasarWorldTemplate.cs", "Quasar/Program.cs", "Quasar/Properties/launchSettings.json", "Quasar/Quasar.csproj", @@ -50,29 +78,37 @@ "g04-services-core-a": [ "Quasar/Services/AgentRegistry.cs", "Quasar/Services/AgentSocketHandler.cs", + "Quasar/Services/AtomicFileWriter.cs", "Quasar/Services/Backup/AutomaticBackupService.cs", "Quasar/Services/Backup/BackupCompatibility.cs", "Quasar/Services/Backup/BackupFormatMigrations.cs", "Quasar/Services/Backup/QuasarBackupService.cs", "Quasar/Services/Backup/QuasarBackupSettingsService.cs", "Quasar/Services/Backup/ServerRestoreCoordinator.cs", + "Quasar/Services/BrandingPresets.cs", + "Quasar/Services/BrandingService.cs", + "Quasar/Services/BrowserLauncher.cs", "Quasar/Services/DedicatedServerCatalog.cs", "Quasar/Services/DedicatedServerRuntimePreparer.cs", "Quasar/Services/DedicatedServerSupervisor.cs", + "Quasar/Services/EntityService.cs", + "Quasar/Services/FileBrowserService.cs", "Quasar/Services/IdentifierSlug.cs", "Quasar/Services/KnownPlayerCatalog.cs", "Quasar/Services/ManagedDedicatedServerRuntimeResolver.cs", "Quasar/Services/ManagedRuntimeOptions.cs", "Quasar/Services/ManagedRuntimeWarmupService.cs", - "Quasar/Services/PluginCatalogRefreshService.cs", - "Quasar/Services/QuasarConfigMetadata.cs" + "Quasar/Services/PluginCatalogRefreshService.cs" ], "g05-services-core-b": [ + "Quasar/Services/PluginManifestReader.cs", + "Quasar/Services/QuasarConfigMetadata.cs", "Quasar/Services/QuasarConfigProfileCatalog.cs", "Quasar/Services/QuasarDevFolderCatalog.cs", "Quasar/Services/QuasarLoggingConfigurator.cs", "Quasar/Services/QuasarPluginCatalogService.cs", "Quasar/Services/QuasarShutdownService.cs", + "Quasar/Services/QuasarTheme.cs", "Quasar/Services/QuasarWorkshopModResolver.cs", "Quasar/Services/QuasarWorldTemplateCatalog.cs", "Quasar/Services/ServerManagementActions.cs", @@ -84,6 +120,7 @@ "Quasar/Services/Updates/QuasarUpdateSnapshot.cs", "Quasar/Services/WebServiceManifestHostedService.cs", "Quasar/Services/WebServiceOptions.cs", + "Quasar/Services/WebServiceState.cs", "Quasar/Services/WorldSandboxConfigEditor.cs", "Quasar/Services/WorldTemplateImportLocationService.cs" ], @@ -92,26 +129,45 @@ "Quasar/Services/Analytics/AnalyticsSeriesService.cs", "Quasar/Services/Analytics/AnalyticsStoreOptions.cs", "Quasar/Services/Analytics/AnalyticsViewConfig.cs", + "Quasar/Services/Analytics/MetricSample.cs", + "Quasar/Services/Analytics/MetricSampleFactory.cs", "Quasar/Services/Analytics/MetricsStoreService.cs", "Quasar/Services/Analytics/ProfilerSnapshotValidator.cs", "Quasar/Services/Analytics/ProfilerStoreService.cs", + "Quasar/Services/Analytics/RrdCircularBuffer.cs", + "Quasar/Services/Analytics/RrdRollupBuffer.cs", "Quasar/Services/Analytics/ServerMetricsStore.cs", "Quasar/Services/PluginSdk/PluginConfigDtos.cs", + "Quasar/Services/PluginSdk/PluginConfigService.cs", + "Quasar/Services/PluginSdk/PluginLogEntry.cs", "Quasar/Services/PluginSdk/PluginLogStream.cs" ], "g07-auth-discord": [ + "Quasar/Services/Auth/QuasarAuthConstants.cs", + "Quasar/Services/Auth/QuasarAuthOptions.cs", "Quasar/Services/Auth/QuasarAuthSettingsService.cs", + "Quasar/Services/Auth/QuasarRoleMapper.cs", + "Quasar/Services/Auth/RbacConfig.cs", + "Quasar/Services/Auth/RbacConfigCatalog.cs", "Quasar/Services/Auth/TrustedNetworkEvaluator.cs", + "Quasar/Services/Discord/DeathMessagesCatalog.cs", + "Quasar/Services/Discord/DeathMessagesConfig.cs", + "Quasar/Services/Discord/DiscordAnalyticsExportService.cs", "Quasar/Services/Discord/DiscordBotService.cs", "Quasar/Services/Discord/DiscordChatRelayService.cs", "Quasar/Services/Discord/DiscordCommandDispatcher.cs", "Quasar/Services/Discord/DiscordCommandRouter.cs", + "Quasar/Services/Discord/DiscordDeathRelayService.cs", + "Quasar/Services/Discord/DiscordLogRelayService.cs", "Quasar/Services/Discord/DiscordOptions.cs", + "Quasar/Services/Discord/DiscordOptionsCatalog.cs", + "Quasar/Services/Discord/DiscordRateLimiter.cs", "Quasar/Services/Discord/DiscordSimSpeedAlertService.cs" ], "g08-components-pages-a": [ "Quasar/Components/Pages/Analytics.razor", "Quasar/Components/Pages/Analytics.razor.css", + "Quasar/Components/Pages/AnalyticsPanelDialog.razor", "Quasar/Components/Pages/Appearance.razor", "Quasar/Components/Pages/Backup.razor", "Quasar/Components/Pages/Chat.razor", @@ -123,38 +179,52 @@ "Quasar/Components/Pages/Discord.razor", "Quasar/Components/Pages/DiscordConsoleDialog.razor", "Quasar/Components/Pages/Entities.razor", + "Quasar/Components/Pages/Error.razor", "Quasar/Components/Pages/FolderPickerDialog.razor", + "Quasar/Components/Pages/FolderPickerDialog.razor.css", "Quasar/Components/Pages/Home.razor", - "Quasar/Components/Pages/Home.razor.css" + "Quasar/Components/Pages/Home.razor.css", + "Quasar/Components/Pages/Hosts.razor" ], "g09-components-pages-b": [ - "Quasar/Components/Pages/Hosts.razor", + "Quasar/Components/Pages/MergeWorldTemplateModsDialog.razor", + "Quasar/Components/Pages/NotFound.razor", "Quasar/Components/Pages/Players.razor", "Quasar/Components/Pages/PluginCatalogDescriptionDialog.razor", + "Quasar/Components/Pages/PluginManifestPickerDialog.razor", "Quasar/Components/Pages/Plugins.razor", "Quasar/Components/Pages/Security.razor", "Quasar/Components/Pages/ServerConsoleDialog.razor", "Quasar/Components/Pages/ServerDeleteDialog.razor", "Quasar/Components/Pages/ServerEditorDialog.razor", + "Quasar/Components/Pages/ServerEditorDialog.razor.css", "Quasar/Components/Pages/Servers.razor", "Quasar/Components/Pages/ServersPageDialog.razor", "Quasar/Components/Pages/SteamWorkshopApiKeyDialog.razor", "Quasar/Components/Pages/Updates.razor", "Quasar/Components/Pages/WorldTemplateFromServerDialog.razor", "Quasar/Components/Pages/WorldTemplateQuickImportDialog.razor", - "Quasar/Components/Pages/WorldTemplates.razor" + "Quasar/Components/Pages/WorldTemplates.razor", + "Quasar/Components/Pages/WorldTemplatesPageDialog.razor" ], "g10-components-shared": [ "Quasar/Components/App.razor", "Quasar/Components/Dashboard/ServerCard.razor", "Quasar/Components/Dashboard/ServerDetailPanel.razor", + "Quasar/Components/Layout/BrandingHeadContent.razor", "Quasar/Components/Layout/MainLayout.razor", + "Quasar/Components/Layout/MainLayout.razor.css", "Quasar/Components/Layout/NavMenu.razor", "Quasar/Components/Layout/QuasarControlAction.cs", "Quasar/Components/Layout/QuasarControlDialog.razor", "Quasar/Components/Layout/QuasarControlDialog.razor.css", + "Quasar/Components/Layout/ReconnectModal.razor", + "Quasar/Components/Layout/ReconnectModal.razor.css", + "Quasar/Components/Layout/ReconnectModal.razor.js", "Quasar/Components/PluginConfigEditor.razor", + "Quasar/Components/PluginConfigEditor.razor.css", "Quasar/Components/PluginLogPanel.razor", + "Quasar/Components/Routes.razor", "Quasar/Components/Shared/CopyablePath.razor", "Quasar/Components/_Imports.razor" ] diff --git a/Docs/Reference/data/manifest.json b/Docs/Reference/data/manifest.json index 81c1503..abb308b 100644 --- a/Docs/Reference/data/manifest.json +++ b/Docs/Reference/data/manifest.json @@ -44,8 +44,8 @@ "path": "Magnetar.Protocol/Model/AgentSnapshot.cs", "name": "AgentSnapshot.cs", "ext": ".cs", - "size": 1450, - "sha256": "b1638ef09de8a497140cfaceac1df89765544973f5cba32a7e3ff5ca746b27da", + "size": 1550, + "sha256": "22572f1a4a1ec71588c09505f2c2e24b0e58c52158ed87018d70ace6feaf6cf4", "module": "Magnetar.Protocol", "tier": 1, "status": "pending" @@ -204,8 +204,8 @@ "path": "Magnetar.Protocol/Runtime/MagnetarPaths.cs", "name": "MagnetarPaths.cs", "ext": ".cs", - "size": 9326, - "sha256": "2702e329ec00f286eb5a3b1ec818569b65632bb2cadc940b4bd133f72966fe7b", + "size": 9431, + "sha256": "6e824b87f8b9d17ea2b5df145bcbb4039ea9c8363eacf911b056f76396a79da1", "module": "Magnetar.Protocol", "tier": 1, "status": "pending" @@ -374,8 +374,8 @@ "path": "Quasar.Agent/GameBridge.cs", "name": "GameBridge.cs", "ext": ".cs", - "size": 55243, - "sha256": "97cea2944a2e766fbd17c4dd00f9278fc487341c6a24250c802747ede26dfc17", + "size": 60103, + "sha256": "6401ff78f4df476d8d81cff486985010e9ffca56a4596bdef6cf0eb210c3f14b", "module": "Quasar.Agent", "tier": 1, "status": "pending" @@ -464,8 +464,8 @@ "path": "Quasar/Components/Dashboard/ServerCard.razor", "name": "ServerCard.razor", "ext": ".razor", - "size": 10168, - "sha256": "23148de8a45f222a4e68f3f6dc1a9d4c32bb85d0f65e9eea4064f671e1800551", + "size": 10120, + "sha256": "5285be6c70584b93ea1ecf6a63f0a3dca4b7fd664f6332bbe69fece324d5bb01", "module": "Quasar.Components", "tier": 2, "status": "pending" @@ -474,8 +474,8 @@ "path": "Quasar/Components/Dashboard/ServerDetailPanel.razor", "name": "ServerDetailPanel.razor", "ext": ".razor", - "size": 21240, - "sha256": "eec30a0007460ef05d912e1be2f95bd48ed5f338f0437f4c898ec56c2bef4cee", + "size": 21608, + "sha256": "214be3f8ff75767e7a0a43be7318370e18d10fcac641f698811b224abb4375e8", "module": "Quasar.Components", "tier": 2, "status": "pending" @@ -634,8 +634,8 @@ "path": "Quasar/Components/Pages/Chat.razor", "name": "Chat.razor", "ext": ".razor", - "size": 13603, - "sha256": "8d7d8b8b7c888aab83f82f572639e410a05d7eeaa6b353570fc71de16d4717e7", + "size": 16668, + "sha256": "ce9006a838df9436e996354311e1a10dec5a28a6083e29a41891fc6255354ae2", "module": "Quasar.Components", "tier": 2, "status": "pending" @@ -754,8 +754,8 @@ "path": "Quasar/Components/Pages/Home.razor", "name": "Home.razor", "ext": ".razor", - "size": 48384, - "sha256": "6967a7c017b49d8adfcab95d990bc38d8fea44f903850e600b35e00cb82d7d0a", + "size": 49988, + "sha256": "578dc72410a55feccb3c3c86442eba91180b6202e6bd8606667acc104c3226a8", "module": "Quasar.Components", "tier": 2, "status": "pending" @@ -894,8 +894,8 @@ "path": "Quasar/Components/Pages/Servers.razor", "name": "Servers.razor", "ext": ".razor", - "size": 39601, - "sha256": "9e6d32deec855703089cbf6549ae816073371756ece9068288fe5fa9c4add119", + "size": 39549, + "sha256": "364c91b58257ee12b85dd84826fd8be7d398d9a01f3c24af57e4c143bef68f8c", "module": "Quasar.Components", "tier": 2, "status": "pending" @@ -1184,8 +1184,8 @@ "path": "Quasar/Program.cs", "name": "Program.cs", "ext": ".cs", - "size": 41517, - "sha256": "4c7983e35ba24e23e09c28de9005bbc932e8113a0341e0943126bb5e2272720c", + "size": 41206, + "sha256": "da3f1b528fbf049a02045327d2813fbcd6b86fb97b684c04ef2b6d8e60246fcb", "module": "Quasar.Host", "tier": 1, "status": "pending" @@ -1464,8 +1464,8 @@ "path": "Quasar/Services/Backup/QuasarBackupService.cs", "name": "QuasarBackupService.cs", "ext": ".cs", - "size": 45761, - "sha256": "550c740402b9ca600005f294b18e89de3c23a725e68a17eb8ebeb560c45c04bc", + "size": 45526, + "sha256": "2eebeff7508ca25b503ded50ec3de0460b6669eee11797811e6fb9a8d5025115", "module": "Quasar.Services.Core", "tier": 2, "status": "pending" @@ -1504,8 +1504,8 @@ "path": "Quasar/Services/BrandingService.cs", "name": "BrandingService.cs", "ext": ".cs", - "size": 9910, - "sha256": "6e5f3638166dada2bde1036e458ae75cf5a17652f76da02e8a53f1d0f3f17973", + "size": 11062, + "sha256": "44cf382aa0586c33a44b4a88e3c35b67f830a414c7a7fbae6ed7d6f83fc3bd4a", "module": "Quasar.Services.Core", "tier": 1, "status": "pending" @@ -1544,8 +1544,8 @@ "path": "Quasar/Services/DedicatedServerSupervisor.cs", "name": "DedicatedServerSupervisor.cs", "ext": ".cs", - "size": 106745, - "sha256": "e40a94d44312c33ed8097b893f663d5f2b5bef485f0a6e21d7c551742b30bb0f", + "size": 110154, + "sha256": "c75b130174fcc43d2fbe0f5b047f8bd66643061f73147705dba6bc16647adef6", "module": "Quasar.Services.Core", "tier": 1, "status": "pending" diff --git a/Docs/Reference/data/module_index.json b/Docs/Reference/data/module_index.json index 1f5ae77..be7aedc 100644 --- a/Docs/Reference/data/module_index.json +++ b/Docs/Reference/data/module_index.json @@ -33,7 +33,7 @@ "name": "AgentSnapshot.cs", "kind": "class", "tier": 1, - "summary": "Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, recent deaths, and loaded plugin list." + "summary": "Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, registered PluginSdk chat commands, recent deaths, and loaded plugin list." }, { "path": "Magnetar.Protocol/Model/ChatMessageSnapshot.cs", @@ -266,7 +266,7 @@ "name": "GameBridge.cs", "kind": "class", "tier": 1, - "summary": "`GameBridge` is the central game-thread fa\u00e7ade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`." + "summary": "`GameBridge` is the central game-thread fa\u00e7ade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, registered PluginSdk chat commands, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`." }, { "path": "Quasar.Agent/PluginLogOutbox.cs", @@ -340,7 +340,7 @@ "name": "ServerDetailPanel.razor", "kind": "Blazor component", "tier": 2, - "summary": "Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons, a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined \"Affinity \" chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert." + "summary": "Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons (Save disabled while the runtime is `Starting`/`Stopping`/`Restarting`), a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined \"Affinity \" chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert." }, { "path": "Quasar/Components/Layout/BrandingHeadContent.razor", @@ -452,7 +452,7 @@ "name": "Chat.razor", "kind": "Blazor component", "tier": 2, - "summary": "Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`." + "summary": "Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, command-mode autocomplete sourced from registered PluginSdk commands, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`." }, { "path": "Quasar/Components/Pages/ConfigProfilePendingChangesDialog.razor", @@ -983,7 +983,7 @@ "name": "BrandingService.cs", "kind": "class", "tier": 1, - "summary": "`BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into `{WebRootPath}/branding/`, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout." + "summary": "`BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into the data-root `Branding/` directory so they survive web-service updates, migrates legacy `wwwroot/branding` files when present, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout." }, { "path": "Quasar/Services/BrowserLauncher.cs", diff --git a/Docs/Reference/data/path_map.json b/Docs/Reference/data/path_map.json index 69aa084..ebcc12a 100644 --- a/Docs/Reference/data/path_map.json +++ b/Docs/Reference/data/path_map.json @@ -1,14 +1,31 @@ { + "Magnetar.Protocol/Bridge/IQuasarConfigProvider.cs": "Docs/Reference/files/Magnetar.Protocol/Bridge/IQuasarConfigProvider.cs.md", + "Magnetar.Protocol/Discovery/WebServiceDiscoveryManifest.cs": "Docs/Reference/files/Magnetar.Protocol/Discovery/WebServiceDiscoveryManifest.cs.md", + "Magnetar.Protocol/Magnetar.Protocol.csproj": "Docs/Reference/files/Magnetar.Protocol/Magnetar.Protocol.csproj.md", + "Magnetar.Protocol/Model/AgentHello.cs": "Docs/Reference/files/Magnetar.Protocol/Model/AgentHello.cs.md", "Magnetar.Protocol/Model/AgentSnapshot.cs": "Docs/Reference/files/Magnetar.Protocol/Model/AgentSnapshot.cs.md", "Magnetar.Protocol/Model/ChatMessageSnapshot.cs": "Docs/Reference/files/Magnetar.Protocol/Model/ChatMessageSnapshot.cs.md", + "Magnetar.Protocol/Model/DeathEventSnapshot.cs": "Docs/Reference/files/Magnetar.Protocol/Model/DeathEventSnapshot.cs.md", + "Magnetar.Protocol/Model/EntityDeleteRequest.cs": "Docs/Reference/files/Magnetar.Protocol/Model/EntityDeleteRequest.cs.md", + "Magnetar.Protocol/Model/EntityListFilter.cs": "Docs/Reference/files/Magnetar.Protocol/Model/EntityListFilter.cs.md", + "Magnetar.Protocol/Model/EntityListResult.cs": "Docs/Reference/files/Magnetar.Protocol/Model/EntityListResult.cs.md", + "Magnetar.Protocol/Model/EntitySummary.cs": "Docs/Reference/files/Magnetar.Protocol/Model/EntitySummary.cs.md", "Magnetar.Protocol/Model/KickedPlayerSnapshot.cs": "Docs/Reference/files/Magnetar.Protocol/Model/KickedPlayerSnapshot.cs.md", + "Magnetar.Protocol/Model/PlayerSnapshot.cs": "Docs/Reference/files/Magnetar.Protocol/Model/PlayerSnapshot.cs.md", + "Magnetar.Protocol/Model/PluginConfigData.cs": "Docs/Reference/files/Magnetar.Protocol/Model/PluginConfigData.cs.md", + "Magnetar.Protocol/Model/PluginConfigSnapshot.cs": "Docs/Reference/files/Magnetar.Protocol/Model/PluginConfigSnapshot.cs.md", + "Magnetar.Protocol/Model/PluginConfigUpdateRequest.cs": "Docs/Reference/files/Magnetar.Protocol/Model/PluginConfigUpdateRequest.cs.md", "Magnetar.Protocol/Model/PluginLogBatch.cs": "Docs/Reference/files/Magnetar.Protocol/Model/PluginLogBatch.cs.md", + "Magnetar.Protocol/Model/PluginRuntimeInfo.cs": "Docs/Reference/files/Magnetar.Protocol/Model/PluginRuntimeInfo.cs.md", "Magnetar.Protocol/Model/ProfilerSnapshot.cs": "Docs/Reference/files/Magnetar.Protocol/Model/ProfilerSnapshot.cs.md", "Magnetar.Protocol/Model/ServerMetrics.cs": "Docs/Reference/files/Magnetar.Protocol/Model/ServerMetrics.cs.md", "Magnetar.Protocol/Runtime/MagnetarPaths.cs": "Docs/Reference/files/Magnetar.Protocol/Runtime/MagnetarPaths.cs.md", + "Magnetar.Protocol/Runtime/QuasarActiveReleasePointer.cs": "Docs/Reference/files/Magnetar.Protocol/Runtime/QuasarActiveReleasePointer.cs.md", "Magnetar.Protocol/Runtime/QuasarReleaseVersion.cs": "Docs/Reference/files/Magnetar.Protocol/Runtime/QuasarReleaseVersion.cs.md", "Magnetar.Protocol/Runtime/QuasarWebReleaseLayout.cs": "Docs/Reference/files/Magnetar.Protocol/Runtime/QuasarWebReleaseLayout.cs.md", "Magnetar.Protocol/Transport/AgentWireMessage.cs": "Docs/Reference/files/Magnetar.Protocol/Transport/AgentWireMessage.cs.md", + "Magnetar.Protocol/Transport/ServerCommandEnvelope.cs": "Docs/Reference/files/Magnetar.Protocol/Transport/ServerCommandEnvelope.cs.md", + "Magnetar.Protocol/Transport/ServerCommandResult.cs": "Docs/Reference/files/Magnetar.Protocol/Transport/ServerCommandResult.cs.md", "Magnetar.Protocol/Transport/ServerCommandType.cs": "Docs/Reference/files/Magnetar.Protocol/Transport/ServerCommandType.cs.md", "Magnetar.Protocol/Transport/WireMessageKind.cs": "Docs/Reference/files/Magnetar.Protocol/Transport/WireMessageKind.cs.md", "Quasar.Agent/AdminPlugin.cs": "Docs/Reference/files/Quasar.Agent/AdminPlugin.cs.md", @@ -18,21 +35,31 @@ "Quasar.Agent/AgentProfilerMode.cs": "Docs/Reference/files/Quasar.Agent/AgentProfilerMode.cs.md", "Quasar.Agent/AgentProfilerPatches.cs": "Docs/Reference/files/Quasar.Agent/AgentProfilerPatches.cs.md", "Quasar.Agent/AgentProfilerTranspiler.cs": "Docs/Reference/files/Quasar.Agent/AgentProfilerTranspiler.cs.md", + "Quasar.Agent/EntityInspector.cs": "Docs/Reference/files/Quasar.Agent/EntityInspector.cs.md", "Quasar.Agent/GameBridge.cs": "Docs/Reference/files/Quasar.Agent/GameBridge.cs.md", "Quasar.Agent/PluginLogOutbox.cs": "Docs/Reference/files/Quasar.Agent/PluginLogOutbox.cs.md", "Quasar.Agent/Quasar.Agent.csproj": "Docs/Reference/files/Quasar.Agent/Quasar.Agent.csproj.md", + "Quasar.Agent/StopCommand.cs": "Docs/Reference/files/Quasar.Agent/StopCommand.cs.md", + "Quasar.Agent/WebServiceLocator.cs": "Docs/Reference/files/Quasar.Agent/WebServiceLocator.cs.md", "Quasar.Bootstrap/Program.cs": "Docs/Reference/files/Quasar.Bootstrap/Program.cs.md", + "Quasar.Bootstrap/Properties/launchSettings.json": "Docs/Reference/files/Quasar.Bootstrap/Properties/launchSettings.json.md", "Quasar.Bootstrap/Quasar.Bootstrap.csproj": "Docs/Reference/files/Quasar.Bootstrap/Quasar.Bootstrap.csproj.md", "Quasar/Components/App.razor": "Docs/Reference/files/Quasar/Components/App.razor.md", "Quasar/Components/Dashboard/ServerCard.razor": "Docs/Reference/files/Quasar/Components/Dashboard/ServerCard.razor.md", "Quasar/Components/Dashboard/ServerDetailPanel.razor": "Docs/Reference/files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md", + "Quasar/Components/Layout/BrandingHeadContent.razor": "Docs/Reference/files/Quasar/Components/Layout/BrandingHeadContent.razor.md", "Quasar/Components/Layout/MainLayout.razor": "Docs/Reference/files/Quasar/Components/Layout/MainLayout.razor.md", + "Quasar/Components/Layout/MainLayout.razor.css": "Docs/Reference/files/Quasar/Components/Layout/MainLayout.razor.css.md", "Quasar/Components/Layout/NavMenu.razor": "Docs/Reference/files/Quasar/Components/Layout/NavMenu.razor.md", "Quasar/Components/Layout/QuasarControlAction.cs": "Docs/Reference/files/Quasar/Components/Layout/QuasarControlAction.cs.md", "Quasar/Components/Layout/QuasarControlDialog.razor": "Docs/Reference/files/Quasar/Components/Layout/QuasarControlDialog.razor.md", "Quasar/Components/Layout/QuasarControlDialog.razor.css": "Docs/Reference/files/Quasar/Components/Layout/QuasarControlDialog.razor.css.md", + "Quasar/Components/Layout/ReconnectModal.razor": "Docs/Reference/files/Quasar/Components/Layout/ReconnectModal.razor.md", + "Quasar/Components/Layout/ReconnectModal.razor.css": "Docs/Reference/files/Quasar/Components/Layout/ReconnectModal.razor.css.md", + "Quasar/Components/Layout/ReconnectModal.razor.js": "Docs/Reference/files/Quasar/Components/Layout/ReconnectModal.razor.js.md", "Quasar/Components/Pages/Analytics.razor": "Docs/Reference/files/Quasar/Components/Pages/Analytics.razor.md", "Quasar/Components/Pages/Analytics.razor.css": "Docs/Reference/files/Quasar/Components/Pages/Analytics.razor.css.md", + "Quasar/Components/Pages/AnalyticsPanelDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/AnalyticsPanelDialog.razor.md", "Quasar/Components/Pages/Appearance.razor": "Docs/Reference/files/Quasar/Components/Pages/Appearance.razor.md", "Quasar/Components/Pages/Backup.razor": "Docs/Reference/files/Quasar/Components/Pages/Backup.razor.md", "Quasar/Components/Pages/Chat.razor": "Docs/Reference/files/Quasar/Components/Pages/Chat.razor.md", @@ -44,17 +71,23 @@ "Quasar/Components/Pages/Discord.razor": "Docs/Reference/files/Quasar/Components/Pages/Discord.razor.md", "Quasar/Components/Pages/DiscordConsoleDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/DiscordConsoleDialog.razor.md", "Quasar/Components/Pages/Entities.razor": "Docs/Reference/files/Quasar/Components/Pages/Entities.razor.md", + "Quasar/Components/Pages/Error.razor": "Docs/Reference/files/Quasar/Components/Pages/Error.razor.md", "Quasar/Components/Pages/FolderPickerDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/FolderPickerDialog.razor.md", + "Quasar/Components/Pages/FolderPickerDialog.razor.css": "Docs/Reference/files/Quasar/Components/Pages/FolderPickerDialog.razor.css.md", "Quasar/Components/Pages/Home.razor": "Docs/Reference/files/Quasar/Components/Pages/Home.razor.md", "Quasar/Components/Pages/Home.razor.css": "Docs/Reference/files/Quasar/Components/Pages/Home.razor.css.md", "Quasar/Components/Pages/Hosts.razor": "Docs/Reference/files/Quasar/Components/Pages/Hosts.razor.md", + "Quasar/Components/Pages/MergeWorldTemplateModsDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/MergeWorldTemplateModsDialog.razor.md", + "Quasar/Components/Pages/NotFound.razor": "Docs/Reference/files/Quasar/Components/Pages/NotFound.razor.md", "Quasar/Components/Pages/Players.razor": "Docs/Reference/files/Quasar/Components/Pages/Players.razor.md", "Quasar/Components/Pages/PluginCatalogDescriptionDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/PluginCatalogDescriptionDialog.razor.md", + "Quasar/Components/Pages/PluginManifestPickerDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/PluginManifestPickerDialog.razor.md", "Quasar/Components/Pages/Plugins.razor": "Docs/Reference/files/Quasar/Components/Pages/Plugins.razor.md", "Quasar/Components/Pages/Security.razor": "Docs/Reference/files/Quasar/Components/Pages/Security.razor.md", "Quasar/Components/Pages/ServerConsoleDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/ServerConsoleDialog.razor.md", "Quasar/Components/Pages/ServerDeleteDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/ServerDeleteDialog.razor.md", "Quasar/Components/Pages/ServerEditorDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/ServerEditorDialog.razor.md", + "Quasar/Components/Pages/ServerEditorDialog.razor.css": "Docs/Reference/files/Quasar/Components/Pages/ServerEditorDialog.razor.css.md", "Quasar/Components/Pages/Servers.razor": "Docs/Reference/files/Quasar/Components/Pages/Servers.razor.md", "Quasar/Components/Pages/ServersPageDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/ServersPageDialog.razor.md", "Quasar/Components/Pages/SteamWorkshopApiKeyDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/SteamWorkshopApiKeyDialog.razor.md", @@ -62,18 +95,28 @@ "Quasar/Components/Pages/WorldTemplateFromServerDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/WorldTemplateFromServerDialog.razor.md", "Quasar/Components/Pages/WorldTemplateQuickImportDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/WorldTemplateQuickImportDialog.razor.md", "Quasar/Components/Pages/WorldTemplates.razor": "Docs/Reference/files/Quasar/Components/Pages/WorldTemplates.razor.md", + "Quasar/Components/Pages/WorldTemplatesPageDialog.razor": "Docs/Reference/files/Quasar/Components/Pages/WorldTemplatesPageDialog.razor.md", "Quasar/Components/PluginConfigEditor.razor": "Docs/Reference/files/Quasar/Components/PluginConfigEditor.razor.md", + "Quasar/Components/PluginConfigEditor.razor.css": "Docs/Reference/files/Quasar/Components/PluginConfigEditor.razor.css.md", "Quasar/Components/PluginLogPanel.razor": "Docs/Reference/files/Quasar/Components/PluginLogPanel.razor.md", + "Quasar/Components/Routes.razor": "Docs/Reference/files/Quasar/Components/Routes.razor.md", "Quasar/Components/Shared/CopyablePath.razor": "Docs/Reference/files/Quasar/Components/Shared/CopyablePath.razor.md", "Quasar/Components/_Imports.razor": "Docs/Reference/files/Quasar/Components/_Imports.razor.md", + "Quasar/Models/BrandingSettings.cs": "Docs/Reference/files/Quasar/Models/BrandingSettings.cs.md", "Quasar/Models/CpuAffinitySpec.cs": "Docs/Reference/files/Quasar/Models/CpuAffinitySpec.cs.md", "Quasar/Models/DedicatedServerDefinition.cs": "Docs/Reference/files/Quasar/Models/DedicatedServerDefinition.cs.md", + "Quasar/Models/DedicatedServerGoalState.cs": "Docs/Reference/files/Quasar/Models/DedicatedServerGoalState.cs.md", + "Quasar/Models/DedicatedServerHealthState.cs": "Docs/Reference/files/Quasar/Models/DedicatedServerHealthState.cs.md", + "Quasar/Models/DedicatedServerProcessPriority.cs": "Docs/Reference/files/Quasar/Models/DedicatedServerProcessPriority.cs.md", + "Quasar/Models/DedicatedServerProcessState.cs": "Docs/Reference/files/Quasar/Models/DedicatedServerProcessState.cs.md", "Quasar/Models/DedicatedServerRuntimeSnapshot.cs": "Docs/Reference/files/Quasar/Models/DedicatedServerRuntimeSnapshot.cs.md", + "Quasar/Models/KnownPlayerRecord.cs": "Docs/Reference/files/Quasar/Models/KnownPlayerRecord.cs.md", "Quasar/Models/ManagedServerRuntime.cs": "Docs/Reference/files/Quasar/Models/ManagedServerRuntime.cs.md", "Quasar/Models/QuasarBackupManifest.cs": "Docs/Reference/files/Quasar/Models/QuasarBackupManifest.cs.md", "Quasar/Models/QuasarBackupSettings.cs": "Docs/Reference/files/Quasar/Models/QuasarBackupSettings.cs.md", "Quasar/Models/QuasarConfigProfile.cs": "Docs/Reference/files/Quasar/Models/QuasarConfigProfile.cs.md", "Quasar/Models/QuasarRestoreReport.cs": "Docs/Reference/files/Quasar/Models/QuasarRestoreReport.cs.md", + "Quasar/Models/QuasarWorldTemplate.cs": "Docs/Reference/files/Quasar/Models/QuasarWorldTemplate.cs.md", "Quasar/Program.cs": "Docs/Reference/files/Quasar/Program.cs.md", "Quasar/Properties/launchSettings.json": "Docs/Reference/files/Quasar/Properties/launchSettings.json.md", "Quasar/Quasar.csproj": "Docs/Reference/files/Quasar/Quasar.csproj.md", @@ -83,11 +126,21 @@ "Quasar/Services/Analytics/AnalyticsSeriesService.cs": "Docs/Reference/files/Quasar/Services/Analytics/AnalyticsSeriesService.cs.md", "Quasar/Services/Analytics/AnalyticsStoreOptions.cs": "Docs/Reference/files/Quasar/Services/Analytics/AnalyticsStoreOptions.cs.md", "Quasar/Services/Analytics/AnalyticsViewConfig.cs": "Docs/Reference/files/Quasar/Services/Analytics/AnalyticsViewConfig.cs.md", + "Quasar/Services/Analytics/MetricSample.cs": "Docs/Reference/files/Quasar/Services/Analytics/MetricSample.cs.md", + "Quasar/Services/Analytics/MetricSampleFactory.cs": "Docs/Reference/files/Quasar/Services/Analytics/MetricSampleFactory.cs.md", "Quasar/Services/Analytics/MetricsStoreService.cs": "Docs/Reference/files/Quasar/Services/Analytics/MetricsStoreService.cs.md", "Quasar/Services/Analytics/ProfilerSnapshotValidator.cs": "Docs/Reference/files/Quasar/Services/Analytics/ProfilerSnapshotValidator.cs.md", "Quasar/Services/Analytics/ProfilerStoreService.cs": "Docs/Reference/files/Quasar/Services/Analytics/ProfilerStoreService.cs.md", + "Quasar/Services/Analytics/RrdCircularBuffer.cs": "Docs/Reference/files/Quasar/Services/Analytics/RrdCircularBuffer.cs.md", + "Quasar/Services/Analytics/RrdRollupBuffer.cs": "Docs/Reference/files/Quasar/Services/Analytics/RrdRollupBuffer.cs.md", "Quasar/Services/Analytics/ServerMetricsStore.cs": "Docs/Reference/files/Quasar/Services/Analytics/ServerMetricsStore.cs.md", + "Quasar/Services/AtomicFileWriter.cs": "Docs/Reference/files/Quasar/Services/AtomicFileWriter.cs.md", + "Quasar/Services/Auth/QuasarAuthConstants.cs": "Docs/Reference/files/Quasar/Services/Auth/QuasarAuthConstants.cs.md", + "Quasar/Services/Auth/QuasarAuthOptions.cs": "Docs/Reference/files/Quasar/Services/Auth/QuasarAuthOptions.cs.md", "Quasar/Services/Auth/QuasarAuthSettingsService.cs": "Docs/Reference/files/Quasar/Services/Auth/QuasarAuthSettingsService.cs.md", + "Quasar/Services/Auth/QuasarRoleMapper.cs": "Docs/Reference/files/Quasar/Services/Auth/QuasarRoleMapper.cs.md", + "Quasar/Services/Auth/RbacConfig.cs": "Docs/Reference/files/Quasar/Services/Auth/RbacConfig.cs.md", + "Quasar/Services/Auth/RbacConfigCatalog.cs": "Docs/Reference/files/Quasar/Services/Auth/RbacConfigCatalog.cs.md", "Quasar/Services/Auth/TrustedNetworkEvaluator.cs": "Docs/Reference/files/Quasar/Services/Auth/TrustedNetworkEvaluator.cs.md", "Quasar/Services/Backup/AutomaticBackupService.cs": "Docs/Reference/files/Quasar/Services/Backup/AutomaticBackupService.cs.md", "Quasar/Services/Backup/BackupCompatibility.cs": "Docs/Reference/files/Quasar/Services/Backup/BackupCompatibility.cs.md", @@ -95,22 +148,37 @@ "Quasar/Services/Backup/QuasarBackupService.cs": "Docs/Reference/files/Quasar/Services/Backup/QuasarBackupService.cs.md", "Quasar/Services/Backup/QuasarBackupSettingsService.cs": "Docs/Reference/files/Quasar/Services/Backup/QuasarBackupSettingsService.cs.md", "Quasar/Services/Backup/ServerRestoreCoordinator.cs": "Docs/Reference/files/Quasar/Services/Backup/ServerRestoreCoordinator.cs.md", + "Quasar/Services/BrandingPresets.cs": "Docs/Reference/files/Quasar/Services/BrandingPresets.cs.md", + "Quasar/Services/BrandingService.cs": "Docs/Reference/files/Quasar/Services/BrandingService.cs.md", + "Quasar/Services/BrowserLauncher.cs": "Docs/Reference/files/Quasar/Services/BrowserLauncher.cs.md", "Quasar/Services/DedicatedServerCatalog.cs": "Docs/Reference/files/Quasar/Services/DedicatedServerCatalog.cs.md", "Quasar/Services/DedicatedServerRuntimePreparer.cs": "Docs/Reference/files/Quasar/Services/DedicatedServerRuntimePreparer.cs.md", "Quasar/Services/DedicatedServerSupervisor.cs": "Docs/Reference/files/Quasar/Services/DedicatedServerSupervisor.cs.md", + "Quasar/Services/Discord/DeathMessagesCatalog.cs": "Docs/Reference/files/Quasar/Services/Discord/DeathMessagesCatalog.cs.md", + "Quasar/Services/Discord/DeathMessagesConfig.cs": "Docs/Reference/files/Quasar/Services/Discord/DeathMessagesConfig.cs.md", + "Quasar/Services/Discord/DiscordAnalyticsExportService.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordAnalyticsExportService.cs.md", "Quasar/Services/Discord/DiscordBotService.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordBotService.cs.md", "Quasar/Services/Discord/DiscordChatRelayService.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordChatRelayService.cs.md", "Quasar/Services/Discord/DiscordCommandDispatcher.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordCommandDispatcher.cs.md", "Quasar/Services/Discord/DiscordCommandRouter.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordCommandRouter.cs.md", + "Quasar/Services/Discord/DiscordDeathRelayService.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordDeathRelayService.cs.md", + "Quasar/Services/Discord/DiscordLogRelayService.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordLogRelayService.cs.md", "Quasar/Services/Discord/DiscordOptions.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordOptions.cs.md", + "Quasar/Services/Discord/DiscordOptionsCatalog.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordOptionsCatalog.cs.md", + "Quasar/Services/Discord/DiscordRateLimiter.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordRateLimiter.cs.md", "Quasar/Services/Discord/DiscordSimSpeedAlertService.cs": "Docs/Reference/files/Quasar/Services/Discord/DiscordSimSpeedAlertService.cs.md", + "Quasar/Services/EntityService.cs": "Docs/Reference/files/Quasar/Services/EntityService.cs.md", + "Quasar/Services/FileBrowserService.cs": "Docs/Reference/files/Quasar/Services/FileBrowserService.cs.md", "Quasar/Services/IdentifierSlug.cs": "Docs/Reference/files/Quasar/Services/IdentifierSlug.cs.md", "Quasar/Services/KnownPlayerCatalog.cs": "Docs/Reference/files/Quasar/Services/KnownPlayerCatalog.cs.md", "Quasar/Services/ManagedDedicatedServerRuntimeResolver.cs": "Docs/Reference/files/Quasar/Services/ManagedDedicatedServerRuntimeResolver.cs.md", "Quasar/Services/ManagedRuntimeOptions.cs": "Docs/Reference/files/Quasar/Services/ManagedRuntimeOptions.cs.md", "Quasar/Services/ManagedRuntimeWarmupService.cs": "Docs/Reference/files/Quasar/Services/ManagedRuntimeWarmupService.cs.md", "Quasar/Services/PluginCatalogRefreshService.cs": "Docs/Reference/files/Quasar/Services/PluginCatalogRefreshService.cs.md", + "Quasar/Services/PluginManifestReader.cs": "Docs/Reference/files/Quasar/Services/PluginManifestReader.cs.md", "Quasar/Services/PluginSdk/PluginConfigDtos.cs": "Docs/Reference/files/Quasar/Services/PluginSdk/PluginConfigDtos.cs.md", + "Quasar/Services/PluginSdk/PluginConfigService.cs": "Docs/Reference/files/Quasar/Services/PluginSdk/PluginConfigService.cs.md", + "Quasar/Services/PluginSdk/PluginLogEntry.cs": "Docs/Reference/files/Quasar/Services/PluginSdk/PluginLogEntry.cs.md", "Quasar/Services/PluginSdk/PluginLogStream.cs": "Docs/Reference/files/Quasar/Services/PluginSdk/PluginLogStream.cs.md", "Quasar/Services/QuasarConfigMetadata.cs": "Docs/Reference/files/Quasar/Services/QuasarConfigMetadata.cs.md", "Quasar/Services/QuasarConfigProfileCatalog.cs": "Docs/Reference/files/Quasar/Services/QuasarConfigProfileCatalog.cs.md", @@ -118,6 +186,7 @@ "Quasar/Services/QuasarLoggingConfigurator.cs": "Docs/Reference/files/Quasar/Services/QuasarLoggingConfigurator.cs.md", "Quasar/Services/QuasarPluginCatalogService.cs": "Docs/Reference/files/Quasar/Services/QuasarPluginCatalogService.cs.md", "Quasar/Services/QuasarShutdownService.cs": "Docs/Reference/files/Quasar/Services/QuasarShutdownService.cs.md", + "Quasar/Services/QuasarTheme.cs": "Docs/Reference/files/Quasar/Services/QuasarTheme.cs.md", "Quasar/Services/QuasarWorkshopModResolver.cs": "Docs/Reference/files/Quasar/Services/QuasarWorkshopModResolver.cs.md", "Quasar/Services/QuasarWorldTemplateCatalog.cs": "Docs/Reference/files/Quasar/Services/QuasarWorldTemplateCatalog.cs.md", "Quasar/Services/ServerManagementActions.cs": "Docs/Reference/files/Quasar/Services/ServerManagementActions.cs.md", @@ -129,6 +198,7 @@ "Quasar/Services/Updates/QuasarUpdateSnapshot.cs": "Docs/Reference/files/Quasar/Services/Updates/QuasarUpdateSnapshot.cs.md", "Quasar/Services/WebServiceManifestHostedService.cs": "Docs/Reference/files/Quasar/Services/WebServiceManifestHostedService.cs.md", "Quasar/Services/WebServiceOptions.cs": "Docs/Reference/files/Quasar/Services/WebServiceOptions.cs.md", + "Quasar/Services/WebServiceState.cs": "Docs/Reference/files/Quasar/Services/WebServiceState.cs.md", "Quasar/Services/WorldSandboxConfigEditor.cs": "Docs/Reference/files/Quasar/Services/WorldSandboxConfigEditor.cs.md", "Quasar/Services/WorldTemplateImportLocationService.cs": "Docs/Reference/files/Quasar/Services/WorldTemplateImportLocationService.cs.md", "Quasar/appsettings.Development.json": "Docs/Reference/files/Quasar/appsettings.Development.json.md", diff --git a/Docs/Reference/files/Magnetar.Protocol/Model/AgentSnapshot.cs.md b/Docs/Reference/files/Magnetar.Protocol/Model/AgentSnapshot.cs.md index 7dd378d..580b578 100644 --- a/Docs/Reference/files/Magnetar.Protocol/Model/AgentSnapshot.cs.md +++ b/Docs/Reference/files/Magnetar.Protocol/Model/AgentSnapshot.cs.md @@ -3,7 +3,7 @@ **Module:** Magnetar.Protocol **Kind:** class **Tier:** 1 ## Summary -Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, recent deaths, and loaded plugin list. +Periodic snapshot pushed by `Quasar.Agent` to the Quasar supervisor containing the full observable state of one running SE dedicated server: identity fields, runtime status, scalar performance metrics, current profiler mode, optional profiler timing data, online human players, hidden NPC/bot player ids, kicked players (serving a kick cooldown), recent chat, registered PluginSdk chat commands, recent deaths, and loaded plugin list. ## Structure Namespace: `Magnetar.Protocol.Model` @@ -25,6 +25,7 @@ Class `AgentSnapshot` (concrete, no base type): | `HiddenPlayerSteamIds` / `HiddenPlayerIdentityIds` | `List` | Steam/identity ids for online `MyPlayer` entries filtered out as zero-SteamId, bot, or NPC identities, allowing Quasar to purge stale known-player rows without hiding those entities from entity inspection. | | `KickedPlayers` | `List` | Offline players currently serving a server-side kick cooldown (separate from `Players`). | | `RecentChat` | `List` | Chat messages since last snapshot. | +| `ChatCommands` | `List` | Registered PluginSdk chat-command suggestions for the Chat page command-mode autocomplete. | | `RecentDeaths` | `List` | Death events since last snapshot. | | `Plugins` | `List` | Loaded plugin registry. | @@ -34,6 +35,7 @@ Class `AgentSnapshot` (concrete, no base type): - [`Magnetar.Protocol/Model/PlayerSnapshot.cs`](PlayerSnapshot.cs.md) - [`Magnetar.Protocol/Model/KickedPlayerSnapshot.cs`](KickedPlayerSnapshot.cs.md) - [`Magnetar.Protocol/Model/ChatMessageSnapshot.cs`](ChatMessageSnapshot.cs.md) +- [`Magnetar.Protocol/Model/ChatCommandSnapshot.cs`](ChatCommandSnapshot.cs.md) - [`Magnetar.Protocol/Model/DeathEventSnapshot.cs`](DeathEventSnapshot.cs.md) - [`Magnetar.Protocol/Model/PluginRuntimeInfo.cs`](PluginRuntimeInfo.cs.md) - [`Magnetar.Protocol/Transport/AgentWireMessage.cs`](../Transport/AgentWireMessage.cs.md) — carried as the `Snapshot` field. diff --git a/Docs/Reference/files/Magnetar.Protocol/Model/ChatCommandSnapshot.cs.md b/Docs/Reference/files/Magnetar.Protocol/Model/ChatCommandSnapshot.cs.md new file mode 100644 index 0000000..f8a4367 --- /dev/null +++ b/Docs/Reference/files/Magnetar.Protocol/Model/ChatCommandSnapshot.cs.md @@ -0,0 +1,29 @@ +# Magnetar.Protocol/Model/ChatCommandSnapshot.cs + +**Module:** Magnetar.Protocol **Kind:** class **Tier:** 1 + +## Summary +DTO describing one registered PluginSdk chat command reported by `Quasar.Agent` in `AgentSnapshot.ChatCommands`. Carries the full command text (`!prefix path`), generated syntax including arguments, help/description text, owner id, root title, minimum promote level, and path segments so Quasar can offer command autocomplete without referencing `PluginSdk`. + +## Structure +Namespace: `Magnetar.Protocol.Model` + +Class `ChatCommandSnapshot` (concrete, no base type): + +| Property | Type | Description | +|---|---|---| +| `Text` | `string` | Command text to insert/send, e.g. `!ess save`. | +| `Syntax` | `string` | Generated PluginSdk usage string, including required/optional arguments. | +| `Prefix` | `string` | Root command prefix without `!`. | +| `Path` | `string` | Space-separated path under the prefix. Empty for root/default commands. | +| `Description` / `HelpText` | `string` | Short overview text and longer help text from the SDK attributes. | +| `Title` | `string` | Human-readable command root title. | +| `OwnerId` | `string` | Assembly/plugin owner id assigned by the Magnetar command registry. | +| `MinimumPromoteLevel` | `string` | Required Space Engineers promote level as text. | +| `PathSegments` | `List` | Original command path split into tokens. | + +## Dependencies +- [`Magnetar.Protocol/Model/AgentSnapshot.cs`](AgentSnapshot.cs.md) — embeds a list of registered chat command snapshots. + +## Notes +The DTO deliberately stores plain strings instead of PluginSdk or VRage types so `Magnetar.Protocol` remains a version-neutral `netstandard2.0` wire contract. diff --git a/Docs/Reference/files/Magnetar.Protocol/Runtime/MagnetarPaths.cs.md b/Docs/Reference/files/Magnetar.Protocol/Runtime/MagnetarPaths.cs.md index 4008adf..ce4d3e3 100644 --- a/Docs/Reference/files/Magnetar.Protocol/Runtime/MagnetarPaths.cs.md +++ b/Docs/Reference/files/Magnetar.Protocol/Runtime/MagnetarPaths.cs.md @@ -10,7 +10,7 @@ Namespace `Magnetar.Protocol.Runtime`; `public static class MagnetarPaths`. Pure - Root: `GetQuasarDirectory()` (env override → `ApplicationData` → `AppContext.BaseDirectory`), `GetRuntimeDirectory()` (back-compat alias of the root). - Web-service manifest: `GetWebServiceDirectory()` (the root itself), `GetWebServiceManifestPath()` → `service-manifest.json`. -- Supervisor files: `GetQuasarLogDirectory()`, `GetQuasarServerLogDirectory(uniqueName)` → `Logs/Magnetars//`, `GetQuasarSupervisorStatePath()`, `GetQuasarKnownPlayersPath()` → `known-players.json`, `GetQuasarKnownPlayerSettingsPath()` → `known-player-settings.json`, `GetQuasarDiscordOptionsPath()`, `GetQuasarDataHandlingConsentPath()` → `data-handling-consent.json`, `GetQuasarBrandingPath()`, `GetQuasarBrandingDirectory(webRootPath)`, `GetQuasarDeathMessagesPath()`, `GetQuasarWorkshopOptionsPath()`, `GetQuasarDataProtectionKeyringDirectory()`, `GetQuasarBackupSettingsPath()` → `backup-settings.json`, `GetQuasarBackupsDirectory()` → default `Backups/` storage folder used when `Quasar:BackupDirectory` is empty. +- Supervisor files: `GetQuasarLogDirectory()`, `GetQuasarServerLogDirectory(uniqueName)` → `Logs/Magnetars//`, `GetQuasarSupervisorStatePath()`, `GetQuasarKnownPlayersPath()` → `known-players.json`, `GetQuasarKnownPlayerSettingsPath()` → `known-player-settings.json`, `GetQuasarDiscordOptionsPath()`, `GetQuasarDataHandlingConsentPath()` → `data-handling-consent.json`, `GetQuasarBrandingPath()`, `GetQuasarBrandingDirectory()` → data-root `Branding/` asset storage, compatibility overload `GetQuasarBrandingDirectory(webRootPath)`, `GetQuasarDeathMessagesPath()`, `GetQuasarWorkshopOptionsPath()`, `GetQuasarDataProtectionKeyringDirectory()`, `GetQuasarBackupSettingsPath()` → `backup-settings.json`, `GetQuasarBackupsDirectory()` → default `Backups/` storage folder used when `Quasar:BackupDirectory` is empty. - Per-Magnetar server data (`Magnetars//`): `GetQuasarServersDirectory()`, `GetQuasarServerDirectory()`, `GetQuasarServerDedicatedServerAppDataDirectory()` (DS `-path`), `GetQuasarServerMagnetarAppDataDirectory()` (DS `-config`), `GetQuasarServerDefinitionPath()` → `server.json`, `GetQuasarServerHistoryDirectory()`, `GetQuasarServerAnalyticsPath()` → `analytics.jsonl`. - World templates (`WorldTemplates//`): `GetQuasarWorldTemplatesDirectory()`, `GetLegacyQuasarWorldProfilesDirectory()` (legacy `WorldProfiles/`), `GetQuasarWorldTemplateDirectory()`, `GetQuasarWorldTemplateDefinitionPath()` → `template.json`, `GetQuasarWorldTemplateWorldDirectory()`, `GetQuasarWorldTemplateHistoryDirectory()`. - Bootstrap update/release staging (`Updates/`): `GetQuasarUpdatesDirectory()`, `GetQuasarStagingDirectory()` → `Updates/Staged/`, `GetQuasarActiveReleasePath()` → `Updates/active-release.json`, `GetQuasarAppSettingsBasePath()` → `Updates/appsettings.base.json` (release-base ancestor for appsettings rollover), `GetQuasarBootstrapUpdateRequestPath()` → `Updates/bootstrap-update-request.json` (worker request consumed by Bootstrap to run launcher self-update immediately). @@ -24,4 +24,4 @@ Namespace `Magnetar.Protocol.Runtime`; `public static class MagnetarPaths`. Pure - `System`, `System.IO` (BCL only). ## Notes -Cross-platform by design; the `QUASAR_DATA_DIR` override is the single switch to relocate all state (e.g. containerised/multi-tenant deployments). Name segments (server unique names, world template ids, managed web release versions) must be sanitized before becoming directory names — `SanitizePathSegment` is private, so callers go through the typed helpers. `GetLegacyQuasarWorldProfilesDirectory()` exists only for migration. +Cross-platform by design; the `QUASAR_DATA_DIR` override is the single switch to relocate all state (e.g. containerised/multi-tenant deployments). Runtime branding assets live under this data root rather than web `wwwroot`, so custom logos/favicons survive web-service release updates. Name segments (server unique names, world template ids, managed web release versions) must be sanitized before becoming directory names — `SanitizePathSegment` is private, so callers go through the typed helpers. `GetLegacyQuasarWorldProfilesDirectory()` exists only for migration. diff --git a/Docs/Reference/files/Quasar.Agent/GameBridge.cs.md b/Docs/Reference/files/Quasar.Agent/GameBridge.cs.md index afe61bb..c5645c6 100644 --- a/Docs/Reference/files/Quasar.Agent/GameBridge.cs.md +++ b/Docs/Reference/files/Quasar.Agent/GameBridge.cs.md @@ -3,7 +3,7 @@ **Module:** Quasar.Agent **Kind:** class **Tier:** 1 ## Summary -`GameBridge` is the central game-thread façade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`. +`GameBridge` is the central game-thread façade for `AgentConnection`. It collects session telemetry (metrics, current profiler mode/snapshot, human players, hidden NPC/bot player ids, kicked players, chat, registered PluginSdk chat commands, deaths, plugins), builds `AgentHello` / `AgentSnapshot` wire messages, and executes server commands (chat, blocking save, save-and-stop, profiler mode change, kick, ban, promote, clear-kick-cooldown, entity list/delete) by marshalling work onto the game thread via `MySandboxGame.Invoke`. Save/stop commands route through Magnetar PluginSdk `ServerControl` so Quasar observes completed disk saves before treating the command as successful. Metrics include process CPU derived from `Process.TotalProcessorTime`, simspeed/sim CPU from `Sync`, memory, human player count, PCU, active entities/grids, total blocks, floating objects, latest world-save time, and unsaved game-time progress since the last checkpoint. It enumerates loaded plugins from `MyPlugins.Plugins` (including Pulsar child plugins) for runtime inventory, dedupes configured fallback plugin paths against loaded plugins by path stem, parent dev-folder name, manifest ``, and manifest ``, and exposes plugin configuration through `IQuasarConfigProvider` or Magnetar PluginSdk `PluginConfig` reflection. Chat history normalizes dedicated-server/Good.bot messages to author `Server` and marks `ChatMessageSnapshot.IsServerMessage`. ## Structure **Namespace:** `Quasar.Agent` @@ -39,10 +39,11 @@ ## Dependencies - [`Quasar.Agent/EntityInspector.cs`](EntityInspector.cs.md) - `Magnetar.Protocol.Bridge` — `IQuasarConfigProvider` -- `Magnetar.Protocol.Model` — `AgentHello`, `AgentSnapshot`, `ServerMetrics`, `PlayerSnapshot`, `ChatMessageSnapshot`, `DeathEventSnapshot`, `PluginConfigSnapshot`, `PluginConfigData`, `PluginRuntimeInfo`, `ServerCommandEnvelope`, `ServerCommandResult`, `ServerCommandType` +- `Magnetar.Protocol.Model` — `AgentHello`, `AgentSnapshot`, `ServerMetrics`, `PlayerSnapshot`, `ChatMessageSnapshot`, `ChatCommandSnapshot`, `DeathEventSnapshot`, `PluginConfigSnapshot`, `PluginConfigData`, `PluginRuntimeInfo`, `ServerCommandEnvelope`, `ServerCommandResult`, `ServerCommandType` - [`Quasar.Agent/AgentProfiler.cs`](AgentProfiler.cs.md) - `Magnetar.Protocol.Transport` — wire transport types - `PluginSdk` — `ServerControl` blocking save / save-and-quit facade bound by Magnetar +- `PluginSdk.Commands` — `ServerCommands.Registrar` reflection source for registered chat-command suggestions - `PluginSdk.Config` — `PluginConfig`, `ConfigStorage`, `ConfigOptionAttribute` - `VRage.Plugins` — `IPlugin`, `MyPlugins` - `Sandbox` — `MySandboxGame` @@ -61,6 +62,7 @@ - Plugin config reads (`GetPluginConfigs`) are intentionally off-thread for responsiveness; applies are marshalled to the game thread. - Private `GetKickedPlayers(MySession)` populates `AgentSnapshot.KickedPlayers` by reading `MyMultiplayer.Static.KickedClients` and `MyMultiplayerBase.KICK_TIMEOUT_MS` to compute the remaining cooldown per SteamId. - Private `GetRecentChat()` reads `MyDedicatedServer.GlobalChatHistory`; messages with SteamId 0, author `Good.bot`, or author `Server` are treated as server-authored, exposed as `Server`, and flagged with `IsServerMessage`. +- Private `GetChatCommands()` reflects Magnetar's live `ServerCommands.Registrar`/`CommandRegistry` to emit `ChatCommandSnapshot` rows. It is best-effort and returns an empty list if the host registry shape is unavailable, keeping the agent compatible with older Magnetar builds. - `ConfigProviderAdapter` uses `MethodInfo` reflection to invoke generic `ConfigStorage.SaveJson` / `LoadJson` because `T` is only known at runtime. - `ApplyConfigJson` for SDK configs copies only properties decorated with `[ConfigOption]` to preserve non-option fields. - Runtime plugin inventory uses `EnumeratePlugins()` so Magnetar/Pulsar-loaded plugins appear even when `MySandboxGame.ConfigDedicated.Plugins` is empty; configured plugin paths are still added as `declared` fallback rows only when not already represented by a loaded plugin. XML manifest fallback rows are matched against loaded plugins by full path, path stem, parent dev-folder/source name, ``, and ``, preventing duplicate local dev-folder rows. diff --git a/Docs/Reference/files/Quasar/Components/Dashboard/ServerCard.razor.md b/Docs/Reference/files/Quasar/Components/Dashboard/ServerCard.razor.md index 39cf65a..4ad2182 100644 --- a/Docs/Reference/files/Quasar/Components/Dashboard/ServerCard.razor.md +++ b/Docs/Reference/files/Quasar/Components/Dashboard/ServerCard.razor.md @@ -26,7 +26,7 @@ No `@page` route — used as a child component. **Private helpers:** - `ProcessState` — derives `DedicatedServerProcessState` from `Runtime?.State`. -- `IsProcessActive`, `CanStart`, `CanStop`, `CanKillStarting`, `CanRestart` — lifecycle button visibility logic. Start is shown only for `Stopped`, `Crashed`, and `Faulted`; Stop is shown for `Starting` (cancel launch) and `Running`; Kill is shown for `Starting`/`Restarting`; Restart is shown only for `Running`. No lifecycle button is shown during `Stopping`; Delete is disabled while the process is active. +- `IsProcessActive`, `CanStart`, `CanStop`, `CanKillStarting`, `CanRestart` — lifecycle button visibility logic. Start is shown only for `Stopped`, `Crashed`, and `Faulted`; Stop is shown only for stable `Running`; Kill is shown for `Starting`/`Restarting`; Restart is shown only for `Running`. No lifecycle button is shown during `Stopping`; Delete is disabled while the process is active. - `CanCreateTemplate` — delegates to `ServerManagementActions.CanCreateWorldTemplate`. - `OpenConsoleAsync`, `CloneAsync`, `CreateTemplateAsync`, `EditAsync`, `DeleteAsync` — delegate to `ServerManagementActions`. - `GetDisplayName()` — prefers `Server.DisplayName`, falls back to `Agent.ServerDisplayName`, then `UniqueName`. diff --git a/Docs/Reference/files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md b/Docs/Reference/files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md index 7c17181..46706aa 100644 --- a/Docs/Reference/files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md +++ b/Docs/Reference/files/Quasar/Components/Dashboard/ServerDetailPanel.razor.md @@ -3,7 +3,7 @@ **Module:** Quasar.Components **Kind:** Blazor component **Tier:** 2 ## Summary -Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons, a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined "Affinity " chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert. +Detail panel embedded inside `ServerCard`. When the agent snapshot is absent it shows a waiting/error message and basic process state chips. When a snapshot is present it renders live metrics chips, Refresh/Save buttons (Save disabled while the runtime is `Starting`/`Stopping`/`Restarting`), a chat broadcast field, a players table with player identity/status columns and a rightmost unlabeled action menu column, a recent-chat list, and recent command results. The Plugins chip compares loaded runtime plugins against the assigned config profile's selected plugins, displays `loaded/total`, and turns warning-colored when the loaded count differs from the configured total; if no profile is available it falls back to the aggregate agent plugin metric. The Save chip shows save-in-progress state, or the latest world-save local time plus unsaved in-game progress as `MM:SS`, with a tooltip containing the full local timestamp. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. In both the snapshot-present and snapshot-absent states, an outlined "Affinity " chip (Memory icon) is shown in the metrics chip rows when `Server.CpuAffinity` is set, and mod-download failures captured from runtime output are shown as an explicit error alert. ## Structure No `@page` route — used as a child component. @@ -23,6 +23,7 @@ No `@page` route — used as a child component. **Key private state:** - `_chatText` — bound to the broadcast message field. +- `ProcessState`, `IsUnstable`, `CanSaveWorld` — derived runtime state used to block save during transitions. - `_menuOpen` — suppresses re-render while a player action menu is open via `ShouldRender()`. **Key private methods:** diff --git a/Docs/Reference/files/Quasar/Components/Pages/Chat.razor.md b/Docs/Reference/files/Quasar/Components/Pages/Chat.razor.md index 82d49d9..820404d 100644 --- a/Docs/Reference/files/Quasar/Components/Pages/Chat.razor.md +++ b/Docs/Reference/files/Quasar/Components/Pages/Chat.razor.md @@ -3,7 +3,7 @@ **Module:** Quasar.Components **Kind:** Blazor component **Tier:** 2 ## Summary -Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. +Routable page (`/chat`) that gives admins a full-width chat and command console for managed servers. It combines a server dropdown, live recent-chat feed from the selected agent snapshot, a chat/command input that sends text through `ServerCommandType.SendChat`, command-mode autocomplete sourced from registered PluginSdk commands, quick Refresh/Save/Restart actions, and recent command-result feedback. Server-authored chat (`IsServerMessage`, SteamId 0, `Good.bot`, or `Server`) is displayed as `Server`. ## Structure - **Route:** `@page "/chat"` @@ -12,13 +12,13 @@ Routable page (`/chat`) that gives admins a full-width chat and command console - **Key UI sections:** - Header with connected-agent count. - Server selector built from configured server definitions plus connected unmanaged agents. - - Quick actions: Refresh and Save dispatch agent commands; Restart delegates to `DedicatedServerSupervisor.RestartServerAsync`. + - Quick actions: Refresh and Save dispatch agent commands; Save is disabled while the selected server is `Starting`/`Stopping`/`Restarting`; Restart delegates to `DedicatedServerSupervisor.RestartServerAsync` and is also disabled during those unstable states. - Status chips for connected selected agents (players, world, agent connected). - Scrollable `.admin-chat-list` showing `AgentSnapshot.RecentChat` oldest-to-newest. - - Chat/command input: `Command mode` only changes labels and button styling; both modes intentionally send the full typed text as chat so plugin/game chat-command handlers receive the original prefix. + - Chat/command input: normal chat uses a multiline `MudTextField`; command mode uses `MudAutocomplete` over `AgentSnapshot.ChatCommands` but still sends the selected/full typed text as chat so plugin/game chat-command handlers receive the original prefix. - Recent command results from `AgentRuntimeState.CommandResults`. -- **Key state:** `_selectedUniqueName`, `_inputText`, `_commandMode`, `_lastRenderedLatestMessageTicks`, `_scrollPending`; computed `HasSelectedDefinition` gates supervisor-only Restart. -- **Key methods:** `BuildServerOptions`, `EnsureSelectedServer`, `SendAgentCommandAsync`, `RestartSelectedServerAsync`, `HandleInputKeyDownAsync`, `ScrollToBottomAsync`, `FormatAuthor` / `IsServerMessage`, `FormatTimestamp`. +- **Key state:** `_selectedUniqueName`, `_inputText`, `_commandMode`, `_lastRenderedLatestMessageTicks`, `_scrollPending`; computed `SelectedRuntime`, `IsSelectedUnstable`, `CanSaveSelected`, and `CanRestartSelected` gate unstable lifecycle actions. +- **Key methods:** `BuildServerOptions`, `EnsureSelectedServer`, `SearchCommandSuggestionsAsync`, `SendAgentCommandAsync`, `RestartSelectedServerAsync`, `HandleInputKeyDownAsync`, `ScrollToBottomAsync`, `FormatAuthor` / `IsServerMessage`, `FormatTimestamp`. - **Event subscriptions:** `Registry.Changed` and `ServerCatalog.Changed` refresh the dropdown and selected-agent view. - **Private type:** `ServerOption` record (UniqueName, DisplayName). @@ -27,6 +27,7 @@ Routable page (`/chat`) that gives admins a full-width chat and command console - [`Quasar/Services/DedicatedServerCatalog.cs`](../../Services/DedicatedServerCatalog.cs.md) — configured server list and display names - [`Quasar/Services/DedicatedServerSupervisor.cs`](../../Services/DedicatedServerSupervisor.cs.md) — restart action - `Magnetar.Protocol/Model/ChatMessageSnapshot.cs` +- [`Magnetar.Protocol/Model/ChatCommandSnapshot.cs`](../../../Magnetar.Protocol/Model/ChatCommandSnapshot.cs.md) - `Magnetar.Protocol/Transport/ServerCommandEnvelope.cs` - `Magnetar.Protocol/Transport/ServerCommandType.cs` - [`Quasar/Services/TextSanitizer.cs`](../../Services/TextSanitizer.cs.md) diff --git a/Docs/Reference/files/Quasar/Components/Pages/Home.razor.md b/Docs/Reference/files/Quasar/Components/Pages/Home.razor.md index 6e31b73..0597b5e 100644 --- a/Docs/Reference/files/Quasar/Components/Pages/Home.razor.md +++ b/Docs/Reference/files/Quasar/Components/Pages/Home.razor.md @@ -20,7 +20,7 @@ Routable dashboard and primary server control surface at `/`. Shows a top-of-das 5. Wait for Quasar.Agent — live state + agent-attach chips per `LaunchedServers`. Plus a Back button when past step 0. - Global health-monitoring info alert (`MudAlert`) — a single top-of-dashboard "Health monitoring disabled for this Quasar instance (development mode or configuration)" shown when `Supervisor.HealthMonitoringDisabled` is true, instead of repeating the message on every server card. - - Problem banner (`MudAlert`) — first unhealthy/crashed/faulted, else first warning instance message. + - Problem banner (`MudAlert`) — first crashed/faulted server with a `Clear Error Status` action, else first unhealthy server, else first warning instance message. - Managed Runtime panel — visible while warmup is pending/running/failed or any runtime component is actively checking/downloading/installing. It shows overall warmup/update state plus SteamCMD, Magnetar, and Dedicated Server component rows with status text, copyable diagnostic paths, and determinate/indeterminate `MudProgressLinear` progress. This keeps the panel visible for hourly Magnetar update checks even after initial readiness has completed. Start buttons are disabled until startup readiness is complete. A Retry button appears on failed Dedicated Server or Magnetar rows. - KPI summary grid (4 cards): Online Servers, Players Online, Health Warnings (warning tint when > 0), Errors (error tint when > 0). - Server view toolbar — title plus Cards/List buttons. `SetServerView` updates the URL to `/` or `/?view=list` with replace navigation. @@ -29,8 +29,8 @@ Routable dashboard and primary server control surface at `/`. Shows a top-of-das - **Setup-wizard state (fields):** `_setupWizardRequested`, `_setupWizardDismissed`, `_setupWizardActive`, `_skippedSetupSteps` (HashSet), `_setupStepOverride`. - **Wizard visibility:** `ShowSetupWizard => (_setupWizardActive || _setupWizardRequested) && !_setupWizardDismissed`. `OnInitialized` sets `_setupWizardActive = ConfiguredServerCount == 0`, so the wizard auto-opens only until the first server exists; once shown it stays for the session. `ShowSetupWizardAgain` re-requests it; `HideSetupWizard` dismisses it. - **Step model:** `IsSetupStepComplete(0..4)` keyed on config-profile count / world-template count (or skipped) / server count / running count / connected-agent count; `CurrentSetupStep` returns the first incomplete step (or a clamped override); `SetupProgressPercent`, `SkipCurrentSetupStep`, `GoToPreviousSetupStep`. -- **KPI/state computed props:** `OnlineServerCount`, `PlayersOnline`, `ConfiguredServerCount`, `RunningServerCount`, `WarningServerCount`, `UnhealthyServerCount`, `ConnectedAgentCount`, `ProblemBanner`, `StartableServers`, `LaunchedServers`, `IsLaunchBlocked`, `ShowDataHandlingConsentPrompt`, `ServerView`, `IsCardsView`, `IsListView`. -- **Actions:** `SaveDataHandlingConsentAsync` persists the YES/NO choice and reports success/failure; `StartAsync` blocks with a warning snackbar while managed runtime warmup is incomplete, otherwise sets goal On and explicitly starts via `Supervisor.StartServerAsync` so `Crashed`/`Faulted` can be operator-retried; `RetryRuntimeWarmupAsync` calls `RuntimeWarmup.RetryAsync` from failed Dedicated Server or Magnetar runtime rows; `StopAsync` confirms then sets goal Off; `KillStartingAsync` confirms then calls `Supervisor.KillStartingServerAsync`; `RestartAsync` calls `Supervisor.RestartServerAsync`; `SetServerView` switches card/list URL state; `OpenConfigProfileFromServerListAsync` opens `ConfigsPageDialog` with `InitialProfileId` when a config link is clicked from the embedded list. `ShowFullScreenPageDialogAsync` opens full-screen `MudDialog`s and accepts optional `DialogParameters`. +- **KPI/state computed props:** `OnlineServerCount`, `PlayersOnline`, `ConfiguredServerCount`, `RunningServerCount`, `WarningServerCount`, `UnhealthyServerCount`, `ConnectedAgentCount`, `ProblemBanner` (severity/message/clearable unique name), `StartableServers`, `LaunchedServers`, `IsLaunchBlocked`, `ShowDataHandlingConsentPrompt`, `ServerView`, `IsCardsView`, `IsListView`. +- **Actions:** `SaveDataHandlingConsentAsync` persists the YES/NO choice and reports success/failure; `StartAsync` blocks with a warning snackbar while managed runtime warmup is incomplete, otherwise sets goal On and explicitly starts via `Supervisor.StartServerAsync` so `Crashed`/`Faulted` can be operator-retried; `RetryRuntimeWarmupAsync` calls `RuntimeWarmup.RetryAsync` from failed Dedicated Server or Magnetar runtime rows; `StopAsync` confirms then sets goal Off; `ClearErrorStatusAsync` calls `Supervisor.ClearErrorStatusAsync` to acknowledge a crashed/faulted server and clear it to stopped/goal Off; `KillStartingAsync` confirms then calls `Supervisor.KillStartingServerAsync`; `RestartAsync` calls `Supervisor.RestartServerAsync`; `SetServerView` switches card/list URL state; `OpenConfigProfileFromServerListAsync` opens `ConfigsPageDialog` with `InitialProfileId` when a config link is clicked from the embedded list. `ShowFullScreenPageDialogAsync` opens full-screen `MudDialog`s and accepts optional `DialogParameters`. - **Helpers:** `GetRuntime`/`GetAgent`/`IsRunning`/`IsOpen`, `GetServerSetupSummary`, `GetSetupRuntimeSummary`, `GetSetup*Text`/`GetSetup*Color`, `GetRuntimeWarmup*`, `GetRuntimeComponent*`, `CanRetryRuntimeComponent`, `GetProblemCardClass`, `GetNextSetupHint`, `GetSetupProgressText`. - **Event subscriptions** (subscribed in `OnInitialized`, released in `Dispose`): `Registry.Changed`, `ServerCatalog.Changed`, `Supervisor.Changed`, `ConfigProfiles.Changed`, `WorldTemplates.Changed`, `RuntimeWarmup.Changed`, `DataHandlingConsent.Changed`; `HandleRegistryChanged` marshals `StateHasChanged`. diff --git a/Docs/Reference/files/Quasar/Components/Pages/Servers.razor.md b/Docs/Reference/files/Quasar/Components/Pages/Servers.razor.md index ac076c0..8968fdd 100644 --- a/Docs/Reference/files/Quasar/Components/Pages/Servers.razor.md +++ b/Docs/Reference/files/Quasar/Components/Pages/Servers.razor.md @@ -25,7 +25,7 @@ Embeddable server-management component used by the dashboard list view and full- - `OpenConsoleDialogAsync` — opens `ServerConsoleDialog`. - `CreateWorldTemplateAsync` — validates the server is stopped and its world path has `Sandbox.sbc`, opens `WorldTemplateFromServerDialog`, then `WorldTemplates.ImportAsync`. - `OpenConfigProfileAsync` — invokes `ConfigProfileSelected` or navigates to `/configs?profileId=...`. -- **Lifecycle controls:** `StartAsync` first respects `LaunchBlocked`, then sets goal On and explicitly starts so `Crashed`/`Faulted` can be operator-retried; `StopAsync` confirms then sets goal Off; `KillStartingAsync` confirms then calls `Supervisor.KillStartingServerAsync`; `RestartAsync` calls `Supervisor.RestartServerAsync`; `DeleteAsync` confirms (servers must be stopped), then `ServerCatalog.DeleteAsync` + `Registry.PruneDisconnectedByUniqueName`. `Starting` shows Stop and Kill; `Restarting` shows Kill; `Running` shows Stop and Restart; `Stopped`/`Crashed`/`Faulted` show Start. +- **Lifecycle controls:** `StartAsync` first respects `LaunchBlocked`, then sets goal On and explicitly starts so `Crashed`/`Faulted` can be operator-retried; `StopAsync` confirms then sets goal Off; `KillStartingAsync` confirms then calls `Supervisor.KillStartingServerAsync`; `RestartAsync` calls `Supervisor.RestartServerAsync`; `DeleteAsync` confirms (servers must be stopped), then `ServerCatalog.DeleteAsync` + `Registry.PruneDisconnectedByUniqueName`. `Starting` shows Kill only; `Restarting` shows Kill; `Running` shows Stop and Restart; `Stopped`/`Crashed`/`Faulted` show Start. - **Helpers:** `GetDisplayName`, `GetPlayerCount` (uses configured `MaxPlayers` when available), `GetProcessDisplay`, `GetConfigProfileName`/`CanOpenConfigProfile`, `GetStateText`/`GetStateColor`, `IsRunning`, `CanStartServer`, `CanStopServer`, `CanKillStartingServer`, `CanRestartServer`, `CanCreateWorldTemplate`, `GetAttachmentStatus`, plus sort-value helpers. - **Blank/clone factories/helpers:** `CreateBlank` seeds defaults (port via `AllocateNextPort` from 27016, `ServerIP` 0.0.0.0, health/restart policy fields, health monitoring driven by `Options.DisableServerHealthMonitoring`); `MakeCopyIdentifier` generates a unique `-copy` name; clone helpers choose world mode, guard independent paths, find latest SE `Backup/` snapshots, copy directories, and compare normalized paths; `NormalizeWhitespace`. - Subscribes to `ServerCatalog`, `Supervisor`, `Registry`, `ConfigProfiles`, `WorldTemplates` `.Changed` in `OnInitialized`, releases in `Dispose`; `HandleChanged` marshals `StateHasChanged`. diff --git a/Docs/Reference/files/Quasar/Program.cs.md b/Docs/Reference/files/Quasar/Program.cs.md index 16981c5..57aa68e 100644 --- a/Docs/Reference/files/Quasar/Program.cs.md +++ b/Docs/Reference/files/Quasar/Program.cs.md @@ -29,7 +29,7 @@ Namespace `Quasar`; `public class Program` with `static void Main(string[] args) ### Middleware + endpoints Pipeline: exception handler (prod) → forwarded headers (`X-Forwarded-For` / proto / host from loopback or configured `TrustedProxies`) → status-code re-execute (`/not-found`) → `UseWebSockets` (30 s keep-alive) → `UseAuthentication` → inline trusted-network principal injection → `UseAuthorization` → `UseAntiforgery`. -Endpoints: `GET /api/health` (status/worker/host/version/baseUrl/connectedAgents/configuredServers/runningServers), `GET /api/discovery` (manifest), `GET /api/analytics/series` (browser-fetched chart series), `GET /api/servers/{uniqueName}/logs/server/download` (streams the newest `SpaceEngineersDedicated*.log` for a configured server), `GET /api/servers/{uniqueName}/logs/magnetar/download` (streams that server's Magnetar `info.log`), `GET /api/discord/log/download` (streams the dedicated Discord integration log) — server log downloads require `CanView` and the Discord log download requires `CanManageDiscord` when auth is enabled, `GET /login` (Steam challenge or unavailable page), `GET /logout`, `GET /access-denied` (standalone branded 403 page for authenticated users lacking a Quasar role), `POST /api/internal/drain` (launcher-token + trusted-network gated; `delaySeconds`/`stopServers` params), `GET /api/backup/download` (`QuasarBackupService.CreateBackup` -> streams a fresh ZIP), `GET /api/backup/download/{name}` (downloads an existing stored backup by name from the configured backup directory) — both `RequireAuthorization(CanManageSecurity)` when auth enabled, `Map /ws/agent` -> `AgentSocketHandler`, `MapStaticAssets()`, `/branding` physical static files, `MapRazorComponents()` (interactive server; `RequireAuthorization(CanView)` when auth enabled). +Endpoints: `GET /api/health` (status/worker/host/version/baseUrl/connectedAgents/configuredServers/runningServers), `GET /api/discovery` (manifest), `GET /api/analytics/series` (browser-fetched chart series), `GET /api/servers/{uniqueName}/logs/server/download` (streams the newest `SpaceEngineersDedicated*.log` for a configured server), `GET /api/servers/{uniqueName}/logs/magnetar/download` (streams that server's Magnetar `info.log`), `GET /api/discord/log/download` (streams the dedicated Discord integration log) — server log downloads require `CanView` and the Discord log download requires `CanManageDiscord` when auth is enabled, `GET /login` (Steam challenge or unavailable page), `GET /logout`, `GET /access-denied` (standalone branded 403 page for authenticated users lacking a Quasar role), `POST /api/internal/drain` (launcher-token + trusted-network gated; `delaySeconds`/`stopServers` params), `GET /api/backup/download` (`QuasarBackupService.CreateBackup` -> streams a fresh ZIP), `GET /api/backup/download/{name}` (downloads an existing stored backup by name from the configured backup directory) — both `RequireAuthorization(CanManageSecurity)` when auth enabled, `Map /ws/agent` -> `AgentSocketHandler`, `MapStaticAssets()`, `/branding` physical static files served from the persistent Quasar data directory, `MapRazorComponents()` (interactive server; `RequireAuthorization(CanView)` when auth enabled). ### POSIX signals + helpers On Linux/macOS, SIGINT/SIGTERM handlers either `StopApplication` (when preserving managed servers) or `QuasarShutdownService.ShutdownAsync`. Helpers: `CreateForwardedHeadersOptions`, `AddTrustedProxy`, `DownloadLogFile`, `ResolveLatestDedicatedServerLogPath`, `ResolveMagnetarInfoLogPath`, `CompositeDisposable`, `EmptyDisposable`, `SanitizeReturnUrl`, `ExtractSteamId`, `AddOrReplaceClaim`, `ShouldUseSourceStaticWebAssets`, `ShouldListenOnAnyInterface`. @@ -49,6 +49,6 @@ On Linux/macOS, SIGINT/SIGTERM handlers either `StopApplication` (when preservin - `/api/internal/drain` requires both a matching `X-Quasar-Launcher-Token` header and trusted-network origin — it is the Bootstrap launcher's drain/shutdown hook; `stopServers` distinguishes draining only the Quasar process vs. taking managed servers down first via `QuasarShutdownService`. - The forwarded-headers middleware trusts loopback proxies by default and configured `TrustedProxies` entries as exact IPs or CIDR ranges; this lets reverse proxies supply the real browser IP before the trusted-network bypass runs. - The trusted-network bypass middleware runs after `UseAuthentication` and injects an operator principal for trusted-origin requests, allowing access without Steam login. -- Branding uploads are served via `PhysicalFileProvider` at `/branding` (outside the build-time static-asset manifest). The standalone auth fallback pages embed their own CSS and static Quasar logo/favicon because they run outside the Blazor shell. +- Branding uploads are served via `PhysicalFileProvider` at `/branding` from `MagnetarPaths.GetQuasarBrandingDirectory()` (outside the build-time static-asset manifest and outside release folders). The standalone auth fallback pages embed their own CSS and static Quasar logo/favicon because they run outside the Blazor shell. - `/api/health`'s "running" count includes `Starting`, `Running`, `Restarting`, and `Stopping` states. - `/api/analytics/series` requires `CanView` when auth is enabled and is fetched outside the Blazor circuit to keep analytics payloads off SignalR. The endpoint returns both scalar and profiler-backed charts using the same response shape. diff --git a/Docs/Reference/files/Quasar/Services/Backup/QuasarBackupService.cs.md b/Docs/Reference/files/Quasar/Services/Backup/QuasarBackupService.cs.md index 4a5c356..07d161c 100644 --- a/Docs/Reference/files/Quasar/Services/Backup/QuasarBackupService.cs.md +++ b/Docs/Reference/files/Quasar/Services/Backup/QuasarBackupService.cs.md @@ -30,7 +30,7 @@ Const `CurrentFormatVersion = 1`. `BackupDirectory` exposes the resolved configu | `RestoreFromFileAsync(string fileName, CancellationToken)` | `Task` restoring from a stored backup file. | | `RestoreAsync(Stream zipStream, CancellationToken)` | `Task`; copies to a temporary seekable file, reads the manifest, validates via `BackupCompatibility.Evaluate`, then dispatches restore by `BackupKind`. | -Constructor deps: `ILogger`, `WebServiceOptions _options`, `IWebHostEnvironment environment` (to resolve webRoot for the branding dir via `MagnetarPaths.GetQuasarBrandingDirectory(webRootPath)`), `KnownPlayerCatalog _knownPlayers`, `QuasarDevFolderCatalog _devFolders`, `DedicatedServerCatalog _servers`, `DedicatedServerSupervisor _supervisor`, `ServerRestoreCoordinator _restoreCoordinator`. +Constructor deps: `ILogger`, `WebServiceOptions _options`, `KnownPlayerCatalog _knownPlayers`, `QuasarDevFolderCatalog _devFolders`, `DedicatedServerCatalog _servers`, `DedicatedServerSupervisor _supervisor`, `ServerRestoreCoordinator _restoreCoordinator`. Branding assets are read from the persistent data-root branding directory via `MagnetarPaths.GetQuasarBrandingDirectory()`. Configuration restore merges by overwriting files at their on-disk path (configs/templates/servers with new IDs added, matching IDs replaced). Server definition entries under `data/Magnetars/**/server.json` and `server/server.json` are deserialized and reserialized with stopped intent instead of being extracted raw. Server restore writes server/config/runtime entries to the target server paths; world restore requires the target server to exist and skips world config. Zip-slip guards keep all entries inside their resolved target roots. Configuration restore calls `_knownPlayers.ReloadFromDisk()` and `_devFolders.ReloadFromDisk()` (catalogs without a file watcher) and returns a report with `RestartRecommended = true`. diff --git a/Docs/Reference/files/Quasar/Services/BrandingService.cs.md b/Docs/Reference/files/Quasar/Services/BrandingService.cs.md index 47ec9d4..3244441 100644 --- a/Docs/Reference/files/Quasar/Services/BrandingService.cs.md +++ b/Docs/Reference/files/Quasar/Services/BrandingService.cs.md @@ -3,7 +3,7 @@ **Module:** Quasar.Services.Core **Kind:** class **Tier:** 1 ## Summary -`BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into `{WebRootPath}/branding/`, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout. +`BrandingService` is the singleton store for all branding and theme configuration. It persists settings to `branding.json` in the Quasar data directory, saves uploaded logo and favicon assets into the data-root `Branding/` directory so they survive web-service updates, migrates legacy `wwwroot/branding` files when present, and live-reloads after external edits via a debounced `FileSystemWatcher`. It also builds the `MudTheme` consumed by the Blazor layout. ## Structure Namespace: `Quasar.Services` @@ -17,7 +17,7 @@ Namespace: `Quasar.Services` | `GetSettings()` | Returns a deep-cloned copy safe for UI draft editing | | `BuildMudTheme()` | Constructs `MudTheme` from current light/dark palettes and `QuasarTheme.Default.LayoutProperties` | | `SaveAsync(BrandingSettings, CancellationToken)` | Normalises, serialises, writes via `AtomicFileWriter`, updates in-memory state, fires `Changed` | -| `SaveLogoAsync(bool isDark, Stream, string extension, CancellationToken)` | Writes logo asset to `{brandingDir}/logo-dark|light.{ext}?v={cachebust}` and calls `SaveAsync` | +| `SaveLogoAsync(bool isDark, Stream, string extension, CancellationToken)` | Writes logo asset to the data-root branding directory as `logo-dark|light.{ext}?v={cachebust}` and calls `SaveAsync` | | `SaveFaviconAsync(Stream, string extension, CancellationToken)` | Writes favicon asset and calls `SaveAsync` | | `ResetToDefaultAsync(CancellationToken)` | Saves a default-normalised `BrandingSettings` | | `Dispose()` | Disposes watcher and debounce CTS | @@ -33,4 +33,4 @@ Internal file watcher uses a 250 ms debounce (`ScheduleReload`) and compares a J - MudBlazor — `MudTheme`, `PaletteLight`, `PaletteDark` ## Notes -Assets are written with a cache-busting query string (`?v={unix-ms}`). Old assets sharing the same `baseName` are deleted before writing the new one. Extension sanitisation strips any invalid filename characters. The snapshot-based change detection prevents spurious `Changed` events when Quasar itself writes the file (the watcher sees its own writes). +Assets are written with a cache-busting query string (`?v={unix-ms}`). Old assets sharing the same `baseName` are deleted before writing the new one. Extension sanitisation strips any invalid filename characters. The snapshot-based change detection prevents spurious `Changed` events when Quasar itself writes the file (the watcher sees its own writes). Legacy assets under the current release's `wwwroot/branding` are copied into the persistent data directory if no matching persistent file exists. diff --git a/Docs/Reference/files/Quasar/Services/DedicatedServerSupervisor.cs.md b/Docs/Reference/files/Quasar/Services/DedicatedServerSupervisor.cs.md index 013f573..49d974a 100644 --- a/Docs/Reference/files/Quasar/Services/DedicatedServerSupervisor.cs.md +++ b/Docs/Reference/files/Quasar/Services/DedicatedServerSupervisor.cs.md @@ -24,10 +24,11 @@ Namespace: `Quasar.Services` | `StopServerAsync(uniqueName, forceAfter?, ct)` | Cancels in-flight launch prep, sends `SaveWorld` + `StopServer` to the agent when attached, waits for exit, kills the process tree if the grace window expires. The default grace window is 30 seconds. | | `KillStartingServerAsync(...)` | Immediate launch cancel/kill path for `Starting`/`Restarting`: flips goal Off, cancels launch prep if no process handle exists yet, or kills the starting process tree immediately when it does. | | `RestartServerAsync(...)` | Sets goal On + AutoStart, logs deployed-vs-bundled agent hash drift when present, then stops and starts so launch prep redeploys the current agent. | +| `ClearErrorStatusAsync(...)` | Acknowledges a `Crashed`/`Faulted` or stale unhealthy stopped server by flipping goal Off, clearing restart/attach counters and mod-download failures, resetting health, and returning it to `Stopped`. Used by the Dashboard problem banner's `Clear Error Status` action. | | `BeginLauncherDrain()` | Sets `_preserveManagedServersOnShutdown = true` and persists synchronously — called before a worker-only restart so the next worker can re-adopt. | | `Dispose()` | Cancels the persist-debounce CTS and the shutdown CTS. | -**`ReconcileAsync`** — per server: liveness vs goal state → Start/Stop/Restart; auto-starts only clean `Stopped` servers while goal is On, leaving `Crashed`/`Faulted` for explicit operator Start; retries `Starting` processes whose Quasar.Agent attach grace expires when `AutoRestartOnUnhealthy` is enabled; unhealthy auto-restart for running processes (`AutoRestartOnUnhealthy`, throttled by `CanScheduleHealthRestart`); maximum-uptime restart; daily scheduled restart; both planned restarts honour `AvoidSimultaneousScheduledRestarts` via `CanRunPlannedRestart`. Also promotes Starting/Restarting → Running once the agent reports `IsRunning`, applies `ReadyProcessPriority` once healthy, and checks connected running servers for bundled-vs-deployed Quasar.Agent hash drift. Hash drift only logs/sets a status warning; it never auto-schedules a Magnetar restart. The reconcile loop and start path also apply per-server CPU affinity to the live process. +**`ReconcileAsync`** — per server: liveness vs goal state → Start/Stop/Restart; auto-starts only clean `Stopped` servers while goal is On, leaving `Crashed`/`Faulted` for explicit operator Start; normalizes stale `Stopping`/cancelled `Restarting` states with no active process back to `Stopped`; retries `Starting` processes whose Quasar.Agent attach grace expires when `AutoRestartOnUnhealthy` is enabled; unhealthy auto-restart for running processes (`AutoRestartOnUnhealthy`, throttled by `CanScheduleHealthRestart`); maximum-uptime restart; daily scheduled restart; both planned restarts honour `AvoidSimultaneousScheduledRestarts` via `CanRunPlannedRestart`. Also promotes Starting/Restarting → Running once the agent reports `IsRunning`, applies `ReadyProcessPriority` once healthy, and checks connected running servers for bundled-vs-deployed Quasar.Agent hash drift. Hash drift only logs/sets a status warning; it never auto-schedules a Magnetar restart. The reconcile loop and start path also apply per-server CPU affinity to the live process. **`HandleProcessExitedAsync` restart budget** — unexpected exits use `RestartOnCrash`, `RestartDelaySeconds`, and `MaxRestartAttempts` (default 3). When the consecutive restart budget is exhausted the state becomes `Faulted` with a restart-limit message, and reconciliation does not keep launching the server until an operator presses Start. @@ -69,4 +70,4 @@ Private nested types: `ManagedServerState` (full mutable per-server state incl. ## Notes -`_preserveManagedServersOnShutdown` defaults from `WebServiceOptions.PreserveManagedServersOnShutdown`: managed servers are left running on a normal Quasar stop (they are detached via Magnetar `-daemon`/setsid) and reconnect when Quasar returns; the persisted PID snapshot is how the next worker re-adopts them. `StartInProgress` plus `StartCancellation` closes a TOCTOU race where two concurrent reconciles could both launch a process and lets Stop/Kill cancel the pre-process launch window. Default stop/restart grace is 30 seconds before the supervisor kills a process tree that did not exit after the agent stop request. Process priority applies in two phases (`StartupProcessPriority` at launch, `ReadyProcessPriority` once healthy/agent-online); on Linux it prefers the setuid `/usr/local/bin/quasar-renice` helper when installed and falls back to `renice -n {nice} -p {pid}` otherwise. Linux native-library search paths come from `ResolvedDedicatedServerRuntime.NativeLibrarySearchPaths` and are prepended to the inherited `LD_LIBRARY_PATH`, mainly to expose SteamCMD's `linux64/steamclient.so` runtime to Steam GameServer init on headless hosts. Managed server launches are not allowed until the warmup service has prepared managed SteamCMD and Dedicated Server prerequisites. Per-server launch diagnostics are UI-owned and persisted on the server definition, not in appsettings. Simulation-progress health re-baselines (skips judgement) during active world saves. State persists with a 150 ms debounce on every change and synchronously on shutdown/drain. DS log retention is enforced on Quasar-captured stdout/stderr archives, not Magnetar-internal files. +`_preserveManagedServersOnShutdown` defaults from `WebServiceOptions.PreserveManagedServersOnShutdown`: managed servers are left running on a normal Quasar stop (they are detached via Magnetar `-daemon`/setsid) and reconnect when Quasar returns; the persisted PID snapshot is how the next worker re-adopts them. `StartInProgress` plus `StartCancellation` closes a TOCTOU race where two concurrent reconciles could both launch a process and lets Stop/Kill cancel the pre-process launch window. Start/restart requests are ignored while a server is already stopping, and repeated stop requests are ignored while the state is already `Stopping`. Default stop/restart grace is 30 seconds before the supervisor kills a process tree that did not exit after the agent stop request. Process priority applies in two phases (`StartupProcessPriority` at launch, `ReadyProcessPriority` once healthy/agent-online); on Linux it prefers the setuid `/usr/local/bin/quasar-renice` helper when installed and falls back to `renice -n {nice} -p {pid}` otherwise. Linux native-library search paths come from `ResolvedDedicatedServerRuntime.NativeLibrarySearchPaths` and are prepended to the inherited `LD_LIBRARY_PATH`, mainly to expose SteamCMD's `linux64/steamclient.so` runtime to Steam GameServer init on headless hosts. Managed server launches are not allowed until the warmup service has prepared managed SteamCMD and Dedicated Server prerequisites. Per-server launch diagnostics are UI-owned and persisted on the server definition, not in appsettings. Simulation-progress health re-baselines (skips judgement) during active world saves. State persists with a 150 ms debounce on every change and synchronously on shutdown/drain. DS log retention is enforced on Quasar-captured stdout/stderr archives, not Magnetar-internal files. diff --git a/Docs/StateMachines/DedicatedServerLifecycle.md b/Docs/StateMachines/DedicatedServerLifecycle.md index e019081..7aed7cf 100644 --- a/Docs/StateMachines/DedicatedServerLifecycle.md +++ b/Docs/StateMachines/DedicatedServerLifecycle.md @@ -49,11 +49,13 @@ stateDiagram-v2 ## Process state The observed supervisor lifecycle. The UI treats `Starting`, `Stopping`, and -`Restarting` as transitionary states. `Starting` shows `Stop` and an immediate -`Kill` action so an accidental or wedged launch can be cancelled before the -agent attaches; `Restarting` shows `Kill` to cancel the pending relaunch. -`Running` shows `Stop`/`Restart`; `Stopped`, `Crashed`, and `Faulted` show -`Start`. +`Restarting` as transitionary states: `Start`, `Stop`, `Restart`, and `Save` +actions are disabled while a server is already transitioning. `Starting` and +`Restarting` keep an immediate `Kill` action so an accidental or wedged launch +can still be cancelled before the agent attaches. `Running` shows +`Stop`/`Restart`; `Stopped`, `Crashed`, and `Faulted` show `Start`. The +Dashboard problem banner can clear `Crashed`/`Faulted` error status back to +`Stopped` after setting the goal `Off`. Restart is a supervisor-owned stop/start sequence. Reconciliation does not auto-schedule an agent-refresh restart. If a connected running server's deployed @@ -70,7 +72,7 @@ stateDiagram-v2 Stopped --> Restarting: goal On (restart pending) Starting --> Running: agent attached, snapshot ready Starting --> Restarting: attach grace expired, retry budget remains - Starting --> Stopping: operator Stop/Kill (cancel launch) + Starting --> Stopping: operator Kill (cancel launch) Starting --> Faulted: launch prep failure / attach retries exhausted Running --> Stopping: goal Off Running --> Restarting: unhealthy, uptime or scheduled @@ -83,8 +85,9 @@ stateDiagram-v2 Stopping --> Faulted: stop failure Crashed --> Restarting: RestartOnCrash, within budget Crashed --> Starting: operator Start retry - Crashed --> Stopped: goal Off + Crashed --> Stopped: goal Off / clear error status Faulted --> Starting: operator Start after cause fixed + Faulted --> Stopped: clear error status ``` ![Dedicated server process state](diagrams/ds-process-state.png) @@ -97,7 +100,7 @@ stateDiagram-v2 | `Stopping` | Graceful stop in progress; waiting for exit. | `Stopped`, `Faulted` | | `Restarting` | Intentional restart sequence in progress. | `Starting`, `Running`, `Faulted` | | `Crashed` | Process exited unexpectedly. | `Starting`, `Restarting`, `Stopped` | -| `Faulted` | Launch/restart failed or attempts exhausted. | `Starting` after the cause is fixed | +| `Faulted` | Launch/restart failed or attempts exhausted. | `Starting` after the cause is fixed, `Stopped` after clear error status | **Restart policy & faults** (all in [`DedicatedServerSupervisor`](../../Quasar/Services/DedicatedServerSupervisor.cs)): @@ -113,6 +116,10 @@ stateDiagram-v2 failure, process start failure) or from crash-restart budget exhaustion. An explicit operator `Start` resets the streak and retries after the cause is fixed; the reconcile loop does not auto-retry `Crashed`/`Faulted` states. +- Clear error status is an explicit operator acknowledgement for a non-running + `Crashed`/`Faulted` server. It sets the goal `Off`, resets health/restart + counters and mod-download failure details, and returns the process state to + `Stopped`. - Agent attach retries: while a process is still `Starting`, health monitoring waits `AgentStartupGraceSeconds` for Quasar.Agent. If it does not attach and `AutoRestartOnUnhealthy` is enabled, the supervisor kills the starting process, diff --git a/Magnetar.Protocol/Model/AgentSnapshot.cs b/Magnetar.Protocol/Model/AgentSnapshot.cs index 98d9cf6..9c2fd38 100644 --- a/Magnetar.Protocol/Model/AgentSnapshot.cs +++ b/Magnetar.Protocol/Model/AgentSnapshot.cs @@ -39,6 +39,8 @@ public class AgentSnapshot public List RecentChat { get; set; } = new List(); + public List ChatCommands { get; set; } = new List(); + public List RecentDeaths { get; set; } = new List(); public List Plugins { get; set; } = new List(); diff --git a/Magnetar.Protocol/Model/ChatCommandSnapshot.cs b/Magnetar.Protocol/Model/ChatCommandSnapshot.cs new file mode 100644 index 0000000..fdb3651 --- /dev/null +++ b/Magnetar.Protocol/Model/ChatCommandSnapshot.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Magnetar.Protocol.Model; + +public class ChatCommandSnapshot +{ + public string Text { get; set; } = string.Empty; + + public string Syntax { get; set; } = string.Empty; + + public string Prefix { get; set; } = string.Empty; + + public string Path { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + + public string HelpText { get; set; } = string.Empty; + + public string Title { get; set; } = string.Empty; + + public string OwnerId { get; set; } = string.Empty; + + public string MinimumPromoteLevel { get; set; } = string.Empty; + + public List PathSegments { get; set; } = new List(); +} diff --git a/Magnetar.Protocol/Runtime/MagnetarPaths.cs b/Magnetar.Protocol/Runtime/MagnetarPaths.cs index 13a56cb..d4d3085 100644 --- a/Magnetar.Protocol/Runtime/MagnetarPaths.cs +++ b/Magnetar.Protocol/Runtime/MagnetarPaths.cs @@ -64,8 +64,11 @@ public static string GetQuasarDataHandlingConsentPath() => public static string GetQuasarBrandingPath() => Path.Combine(GetQuasarDirectory(), "branding.json"); + public static string GetQuasarBrandingDirectory() => + Path.Combine(GetQuasarDirectory(), "Branding"); + public static string GetQuasarBrandingDirectory(string webRootPath) => - Path.Combine(webRootPath, "branding"); + GetQuasarBrandingDirectory(); public static string GetQuasarDeathMessagesPath() => Path.Combine(GetQuasarDirectory(), "death-messages.json"); diff --git a/Quasar.Agent/GameBridge.cs b/Quasar.Agent/GameBridge.cs index 80081ad..955217f 100644 --- a/Quasar.Agent/GameBridge.cs +++ b/Quasar.Agent/GameBridge.cs @@ -15,6 +15,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using PluginSdk; +using PluginSdk.Commands; using PluginSdk.Config; using Sandbox; using Sandbox.Engine.Networking; @@ -233,6 +234,7 @@ private AgentSnapshot BuildSnapshot(AgentHello hello = null) HiddenPlayerIdentityIds = GetHiddenPlayerIdentityIds(session), KickedPlayers = GetKickedPlayers(session), RecentChat = GetRecentChat(), + ChatCommands = GetChatCommands(), RecentDeaths = GetRecentDeaths(), Plugins = GetPlugins(), }; @@ -1031,6 +1033,130 @@ private List GetPlugins() return result; } + private List GetChatCommands() + { + var result = new List(); + + try + { + object registrar = ServerCommands.Registrar; + if (registrar == null) + return result; + + object registry = GetFieldValue(registrar, "registry"); + if (registry == null) + return result; + + foreach (object root in EnumerateCommandRoots(registry)) + { + var prefix = GetStringProperty(root, "Prefix"); + var title = GetStringProperty(root, "Title"); + var ownerId = GetStringProperty(root, "OwnerId"); + + AddChatCommandSnapshot(result, GetPropertyValue(root, "Default"), prefix, title, ownerId); + + var enumerateCommands = root.GetType().GetMethod( + "EnumerateCommands", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (enumerateCommands?.Invoke(root, null) is IEnumerable commands) + { + foreach (object command in commands) + AddChatCommandSnapshot(result, command, prefix, title, ownerId); + } + } + } + catch + { + } + + return result + .Where(command => !string.IsNullOrWhiteSpace(command.Text)) + .GroupBy(command => command.Text, StringComparer.OrdinalIgnoreCase) + .Select(group => group.First()) + .OrderBy(command => command.Text, StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + private static IEnumerable EnumerateCommandRoots(object registry) + { + object roots = GetFieldValue(registry, "roots"); + if (roots is IDictionary dictionary) + { + foreach (object value in dictionary.Values) + yield return value; + } + } + + private static void AddChatCommandSnapshot( + List result, + object command, + string fallbackPrefix, + string title, + string fallbackOwnerId) + { + if (command == null) + return; + + var prefix = GetStringProperty(command, "Prefix"); + if (string.IsNullOrWhiteSpace(prefix)) + prefix = fallbackPrefix; + + if (string.IsNullOrWhiteSpace(prefix)) + return; + + var segments = GetStringListProperty(command, "Path"); + var path = string.Join(" ", segments); + var text = "!" + prefix + (path.Length == 0 ? string.Empty : " " + path); + + result.Add(new ChatCommandSnapshot + { + Text = text, + Syntax = GetStringProperty(command, "Syntax"), + Prefix = prefix, + Path = path, + Description = GetStringProperty(command, "Description"), + HelpText = GetStringProperty(command, "HelpText"), + Title = title, + OwnerId = FirstNonEmpty(GetStringProperty(command, "OwnerId"), fallbackOwnerId), + MinimumPromoteLevel = GetPropertyValue(command, "MinPromoteLevel")?.ToString() ?? string.Empty, + PathSegments = segments, + }); + } + + private static object GetFieldValue(object instance, string fieldName) + { + return instance.GetType() + .GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + ?.GetValue(instance); + } + + private static object GetPropertyValue(object instance, string propertyName) + { + return instance.GetType() + .GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + ?.GetValue(instance, null); + } + + private static string GetStringProperty(object instance, string propertyName) + { + return GetPropertyValue(instance, propertyName) as string ?? string.Empty; + } + + private static List GetStringListProperty(object instance, string propertyName) + { + var result = new List(); + if (GetPropertyValue(instance, propertyName) is not IEnumerable values) + return result; + + foreach (object value in values) + { + if (value is string text && !string.IsNullOrWhiteSpace(text)) + result.Add(text); + } + + return result; + } + private static string GetPluginVersion(IPlugin plugin) { try diff --git a/Quasar/Components/Dashboard/ServerCard.razor b/Quasar/Components/Dashboard/ServerCard.razor index b7a5fa7..44e689b 100644 --- a/Quasar/Components/Dashboard/ServerCard.razor +++ b/Quasar/Components/Dashboard/ServerCard.razor @@ -143,8 +143,7 @@ or DedicatedServerProcessState.Crashed or DedicatedServerProcessState.Faulted; - private bool CanStop => ProcessState is DedicatedServerProcessState.Starting - or DedicatedServerProcessState.Running; + private bool CanStop => ProcessState == DedicatedServerProcessState.Running; private bool CanKillStarting => ProcessState is DedicatedServerProcessState.Starting or DedicatedServerProcessState.Restarting; diff --git a/Quasar/Components/Dashboard/ServerDetailPanel.razor b/Quasar/Components/Dashboard/ServerDetailPanel.razor index 095407d..f7b9db9 100644 --- a/Quasar/Components/Dashboard/ServerDetailPanel.razor +++ b/Quasar/Components/Dashboard/ServerDetailPanel.razor @@ -80,7 +80,7 @@ else Refresh - + Save @@ -198,6 +198,14 @@ else private string _chatText = string.Empty; + private DedicatedServerProcessState ProcessState => Runtime?.State ?? DedicatedServerProcessState.Stopped; + + private bool IsUnstable => ProcessState is DedicatedServerProcessState.Starting + or DedicatedServerProcessState.Stopping + or DedicatedServerProcessState.Restarting; + + private bool CanSaveWorld => Agent?.IsConnected == true && !IsUnstable; + // True while a player's action menu is open. The parent re-renders this panel on every // live snapshot; doing so under an open menu would close it, so we hold renders until the // menu is dismissed (click outside / Escape / selection close it natively). diff --git a/Quasar/Components/Pages/Chat.razor b/Quasar/Components/Pages/Chat.razor index c99e3ff..1eec8be 100644 --- a/Quasar/Components/Pages/Chat.razor +++ b/Quasar/Components/Pages/Chat.razor @@ -35,10 +35,10 @@ Refresh - + Save - + Restart @@ -86,15 +86,35 @@ - + @if (_commandMode) + { + + } + else + { + + } message.TimestampTicksUtc) .ToList() ?? []; + private IReadOnlyList SelectedCommandSuggestions => SelectedAgent?.Snapshot?.ChatCommands ?? []; + + private DedicatedServerRuntimeSnapshot? SelectedRuntime => Supervisor.GetSnapshots() + .FirstOrDefault(snapshot => string.Equals(snapshot.UniqueName, _selectedUniqueName, StringComparison.OrdinalIgnoreCase)); + private bool HasSelectedDefinition => ServerDefinitions.Any(server => string.Equals(server.UniqueName, _selectedUniqueName, StringComparison.OrdinalIgnoreCase)); private bool CanSendAgentCommand => SelectedAgent?.IsConnected == true; + private bool CanSaveSelected => CanSendAgentCommand && !IsSelectedUnstable; + + private bool CanRestartSelected => HasSelectedDefinition && !IsSelectedUnstable; + + private bool IsSelectedUnstable => IsUnstable(SelectedRuntime?.State ?? DedicatedServerProcessState.Stopped); + protected override void OnInitialized() { Registry.Changed += HandleChanged; @@ -200,6 +231,19 @@ await SendInputAsync(); } + private Task> SearchCommandSuggestionsAsync(string? value, CancellationToken cancellationToken) + { + var query = NormalizeCommandQuery(value); + var suggestions = SelectedCommandSuggestions + .Where(command => MatchesCommandSuggestion(command, query)) + .OrderBy(command => command.Text, StringComparer.OrdinalIgnoreCase) + .Select(command => command.Text) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Take(8); + + return Task.FromResult(suggestions); + } + private async Task SendInputAsync() { var text = _inputText.Trim(); @@ -244,6 +288,12 @@ if (string.IsNullOrWhiteSpace(_selectedUniqueName)) return; + if (IsSelectedUnstable) + { + Snackbar.Add("Server is already starting, stopping, or restarting.", Severity.Warning); + return; + } + try { await Supervisor.RestartServerAsync(_selectedUniqueName); @@ -350,5 +400,28 @@ }; } + private static bool IsUnstable(DedicatedServerProcessState state) + { + return state is DedicatedServerProcessState.Starting + or DedicatedServerProcessState.Stopping + or DedicatedServerProcessState.Restarting; + } + + private static string NormalizeCommandQuery(string? value) + { + var query = value?.Trim() ?? string.Empty; + return query.StartsWith('!') ? query[1..] : query; + } + + private static bool MatchesCommandSuggestion(ChatCommandSnapshot command, string query) + { + if (string.IsNullOrWhiteSpace(query)) + return true; + + return command.Text.Contains(query, StringComparison.OrdinalIgnoreCase) || + command.Syntax.Contains(query, StringComparison.OrdinalIgnoreCase) || + command.Description.Contains(query, StringComparison.OrdinalIgnoreCase); + } + private sealed record ServerOption(string UniqueName, string DisplayName); } diff --git a/Quasar/Components/Pages/Home.razor b/Quasar/Components/Pages/Home.razor index d0a6d1b..11fe0ed 100644 --- a/Quasar/Components/Pages/Home.razor +++ b/Quasar/Components/Pages/Home.razor @@ -373,10 +373,22 @@ } -@if (ProblemBanner is not null) +@if (ProblemBanner is { } problemBanner) { - - @ProblemBanner.Value.Message + + + @problemBanner.Message + @if (!string.IsNullOrWhiteSpace(problemBanner.ClearErrorUniqueName)) + { + + Clear Error Status + + } + } @@ -570,19 +582,23 @@ else snapshot.HealthState == DedicatedServerHealthState.Unhealthy || snapshot.State is DedicatedServerProcessState.Crashed or DedicatedServerProcessState.Faulted); - private (Severity Severity, string Message)? ProblemBanner + private (Severity Severity, string Message, string? ClearErrorUniqueName)? ProblemBanner { get { - var error = RuntimeSnapshotList.FirstOrDefault(snapshot => - snapshot.HealthState == DedicatedServerHealthState.Unhealthy || + var stateError = RuntimeSnapshotList.FirstOrDefault(snapshot => snapshot.State is DedicatedServerProcessState.Crashed or DedicatedServerProcessState.Faulted); - if (error is not null) - return (Severity.Error, $"{error.UniqueName}: {FirstNonEmpty(error.HealthSummary, error.LastMessage, "Server needs attention.")}"); + if (stateError is not null) + return (Severity.Error, $"{stateError.UniqueName}: {FirstNonEmpty(stateError.HealthSummary, stateError.LastMessage, "Server needs attention.")}", stateError.UniqueName); + + var healthError = RuntimeSnapshotList.FirstOrDefault(snapshot => + snapshot.HealthState == DedicatedServerHealthState.Unhealthy); + if (healthError is not null) + return (Severity.Error, $"{healthError.UniqueName}: {FirstNonEmpty(healthError.HealthSummary, healthError.LastMessage, "Server needs attention.")}", null); var warning = RuntimeSnapshotList.FirstOrDefault(snapshot => snapshot.HealthState == DedicatedServerHealthState.Warning); if (warning is not null) - return (Severity.Warning, $"{warning.UniqueName}: {FirstNonEmpty(warning.HealthSummary, warning.LastMessage, "Health warning.")}"); + return (Severity.Warning, $"{warning.UniqueName}: {FirstNonEmpty(warning.HealthSummary, warning.LastMessage, "Health warning.")}", null); return null; } @@ -832,6 +848,23 @@ else } } + private async Task ClearErrorStatusAsync(string uniqueName) + { + try + { + var cleared = await Supervisor.ClearErrorStatusAsync(uniqueName); + Snackbar.Add( + cleared + ? $"Error status cleared for '{uniqueName}'." + : $"No clearable error status for '{uniqueName}'.", + cleared ? Severity.Success : Severity.Info); + } + catch (Exception exception) + { + Snackbar.Add(exception.Message, Severity.Error); + } + } + private async Task KillStartingAsync(string uniqueName) { if (!await ConfirmDestructiveActionAsync( diff --git a/Quasar/Components/Pages/Servers.razor b/Quasar/Components/Pages/Servers.razor index 35e5d58..c7cfa9c 100644 --- a/Quasar/Components/Pages/Servers.razor +++ b/Quasar/Components/Pages/Servers.razor @@ -853,8 +853,7 @@ private bool CanStopServer(string uniqueName) { var state = GetRuntime(uniqueName)?.State ?? DedicatedServerProcessState.Stopped; - return state is DedicatedServerProcessState.Starting - or DedicatedServerProcessState.Running; + return state == DedicatedServerProcessState.Running; } private bool CanKillStartingServer(string uniqueName) diff --git a/Quasar/Program.cs b/Quasar/Program.cs index 2826446..8c4a3a6 100644 --- a/Quasar/Program.cs +++ b/Quasar/Program.cs @@ -396,13 +396,9 @@ or DedicatedServerProcessState.Restarting app.MapStaticAssets(); - // Runtime-uploaded branding assets (logos, favicon) live outside the - // build-time static-asset manifest, so serve them with the classic - // static-file middleware from the physical branding directory. - var brandingWebRootPath = string.IsNullOrWhiteSpace(app.Environment.WebRootPath) - ? Path.Combine(app.Environment.ContentRootPath, "wwwroot") - : app.Environment.WebRootPath; - var brandingAssetsDirectory = MagnetarPaths.GetQuasarBrandingDirectory(brandingWebRootPath); + // Runtime-uploaded branding assets live in the Quasar data directory + // so web-service updates do not replace custom logos or favicons. + var brandingAssetsDirectory = MagnetarPaths.GetQuasarBrandingDirectory(); Directory.CreateDirectory(brandingAssetsDirectory); app.UseStaticFiles(new StaticFileOptions { diff --git a/Quasar/Services/Backup/QuasarBackupService.cs b/Quasar/Services/Backup/QuasarBackupService.cs index 2cfee65..331471f 100644 --- a/Quasar/Services/Backup/QuasarBackupService.cs +++ b/Quasar/Services/Backup/QuasarBackupService.cs @@ -99,7 +99,6 @@ private static readonly (string Subdirectory, string FileName)[] DefinitionFiles public QuasarBackupService( ILogger logger, WebServiceOptions options, - IWebHostEnvironment environment, KnownPlayerCatalog knownPlayers, QuasarDevFolderCatalog devFolders, DedicatedServerCatalog servers, @@ -114,10 +113,7 @@ public QuasarBackupService( _supervisor = supervisor; _restoreCoordinator = restoreCoordinator; - var webRootPath = string.IsNullOrWhiteSpace(environment.WebRootPath) - ? Path.Combine(environment.ContentRootPath, "wwwroot") - : environment.WebRootPath; - _brandingAssetsDirectory = MagnetarPaths.GetQuasarBrandingDirectory(webRootPath); + _brandingAssetsDirectory = MagnetarPaths.GetQuasarBrandingDirectory(); } /// Builds a backup archive in memory with a timestamped download name. diff --git a/Quasar/Services/BrandingService.cs b/Quasar/Services/BrandingService.cs index a346f8f..8d2b133 100644 --- a/Quasar/Services/BrandingService.cs +++ b/Quasar/Services/BrandingService.cs @@ -9,7 +9,7 @@ namespace Quasar.Services; /// /// Singleton store for branding and theme configuration. Persists to /// branding.json in the Quasar data directory and writes uploaded logo / -/// favicon assets into {WebRootPath}/branding/. Mirrors the file-watch +/// favicon assets into the Quasar data directory. Mirrors the file-watch /// debounce pattern used by so /// external edits are picked up live. /// @@ -33,10 +33,11 @@ public BrandingService(ILogger logger, IWebHostEnvironment envi { _logger = logger; - var webRootPath = string.IsNullOrWhiteSpace(environment.WebRootPath) + var legacyWebRootPath = string.IsNullOrWhiteSpace(environment.WebRootPath) ? Path.Combine(environment.ContentRootPath, "wwwroot") : environment.WebRootPath; - _brandingAssetsDirectory = MagnetarPaths.GetQuasarBrandingDirectory(webRootPath); + _brandingAssetsDirectory = MagnetarPaths.GetQuasarBrandingDirectory(); + MigrateLegacyBrandingAssets(Path.Combine(legacyWebRootPath, "branding")); _settings = LoadSettings(); _snapshot = CreateSnapshot(_settings); @@ -178,6 +179,34 @@ private void RemoveExistingAssets(string baseName) } } + private void MigrateLegacyBrandingAssets(string legacyDirectory) + { + try + { + if (string.IsNullOrWhiteSpace(legacyDirectory) || + !Directory.Exists(legacyDirectory) || + string.Equals( + Path.GetFullPath(legacyDirectory), + Path.GetFullPath(_brandingAssetsDirectory), + StringComparison.OrdinalIgnoreCase)) + { + return; + } + + Directory.CreateDirectory(_brandingAssetsDirectory); + foreach (var legacyPath in Directory.EnumerateFiles(legacyDirectory, "*", SearchOption.TopDirectoryOnly)) + { + var destination = Path.Combine(_brandingAssetsDirectory, Path.GetFileName(legacyPath)); + if (!File.Exists(destination)) + File.Copy(legacyPath, destination); + } + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Failed migrating legacy branding assets from {Path}.", legacyDirectory); + } + } + private static string NormalizeExtension(string extension) { if (string.IsNullOrWhiteSpace(extension)) diff --git a/Quasar/Services/DedicatedServerSupervisor.cs b/Quasar/Services/DedicatedServerSupervisor.cs index be37289..1a6dca3 100644 --- a/Quasar/Services/DedicatedServerSupervisor.cs +++ b/Quasar/Services/DedicatedServerSupervisor.cs @@ -210,6 +210,9 @@ public async Task StartServerAsync(string uniqueName, CancellationToken cancella if (!_states.TryGetValue(uniqueName, out state)) throw new InvalidOperationException($"Unknown Quasar server '{uniqueName}'."); + if (state.State == DedicatedServerProcessState.Stopping) + return; + if (IsProcessActive(state.Process)) return; @@ -300,6 +303,9 @@ public async Task StopServerAsync(string uniqueName, TimeSpan? forceAfter, Cance return; process = state.Process; + if (state.State == DedicatedServerProcessState.Stopping) + return; + state.StopRequested = true; state.IsRestartPending = false; state.StartCancellation?.Cancel(); @@ -436,6 +442,17 @@ public async Task RestartServerAsync(string uniqueName, CancellationToken cancel if (definition is null) throw new InvalidOperationException($"Unknown Quasar server '{uniqueName}'."); + lock (_sync) + { + if (_states.TryGetValue(uniqueName, out var state) && + state.State is DedicatedServerProcessState.Starting + or DedicatedServerProcessState.Stopping + or DedicatedServerProcessState.Restarting) + { + return; + } + } + var agentDeployment = _runtimePreparer.GetAgentDeploymentComparison(definition); if (agentDeployment.HasMismatch) { @@ -452,6 +469,56 @@ public async Task RestartServerAsync(string uniqueName, CancellationToken cancel await StartServerAsync(uniqueName, cancellationToken); } + public async Task ClearErrorStatusAsync(string uniqueName, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(uniqueName)) + return false; + + var definition = _catalog.GetServer(uniqueName); + if (definition is null) + throw new InvalidOperationException($"Unknown Quasar server '{uniqueName}'."); + + if (definition.GoalState != DedicatedServerGoalState.Off) + await _catalog.SetGoalStateAsync(uniqueName, DedicatedServerGoalState.Off, cancellationToken); + + var changed = false; + lock (_sync) + { + if (!_states.TryGetValue(uniqueName, out var state)) + return false; + + if (IsProcessActive(state.Process)) + return false; + + if (state.State is not (DedicatedServerProcessState.Crashed or DedicatedServerProcessState.Faulted) && + state.HealthState != DedicatedServerHealthState.Unhealthy) + { + return false; + } + + state.Definition.GoalState = DedicatedServerGoalState.Off; + state.Process = null; + state.ProcessId = null; + state.State = DedicatedServerProcessState.Stopped; + state.StopRequested = false; + state.IsRestartPending = false; + state.StartCancellation?.Cancel(); + state.RestartAttempts = 0; + state.AgentAttachRetryAttempts = 0; + state.LastExitCode = null; + state.StoppedAtUtc = DateTimeOffset.UtcNow; + state.LastMessage = "Error status cleared."; + state.ModDownloadFailures.Clear(); + ResetHealthTracking(state); + changed = true; + } + + if (changed) + NotifyChanged(); + + return changed; + } + public void Dispose() { // Take ownership under the same lock used by SchedulePersistState so we cannot @@ -518,6 +585,31 @@ private async Task ReconcileAsync(CancellationToken cancellationToken) var processActive = IsProcessActive(state.Process); var goalState = state.Definition.GoalState; var agent = agents.TryGetValue(state.UniqueName, out var currentAgent) ? currentAgent : null; + + if (!processActive && + !state.StartInProgress && + state.State == DedicatedServerProcessState.Stopping) + { + state.State = DedicatedServerProcessState.Stopped; + state.StopRequested = false; + state.IsRestartPending = false; + state.LastMessage = "Stopped."; + ResetHealthTracking(state); + healthChanged = true; + } + + if (!processActive && + goalState == DedicatedServerGoalState.Off && + state.State == DedicatedServerProcessState.Restarting) + { + state.State = DedicatedServerProcessState.Stopped; + state.StopRequested = false; + state.IsRestartPending = false; + state.LastMessage = "Restart cancelled."; + ResetHealthTracking(state); + healthChanged = true; + } + var health = EvaluateHealth( state, agent,