From bb0d040b0a3aa24b2aca99b01e095255cb6cea2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:03:07 +0000 Subject: [PATCH 1/5] Initial plan From 60a0f8c11d0c9b0c65560ea4e6ca34220f83b8fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:15:20 +0000 Subject: [PATCH 2/5] Rename Print Queue to Project Hub, add printer queue card, fix Prusa control guards Agent-Logs-Url: https://github.com/akinbender/MakerPrompt/sessions/27f94188-df70-497d-b60d-fc1355a18a66 Co-authored-by: akinbender <40242943+akinbender@users.noreply.github.com> --- .../Components/ControlPanel.razor | 176 ++++++++++++++---- .../IPrinterCommunicationService.cs | 13 ++ MakerPrompt.Shared/Layout/NavMenu.razor | 6 + MakerPrompt.Shared/Pages/ProjectHub.razor | 21 +++ .../Properties/Resources.Designer.cs | 8 +- MakerPrompt.Shared/Properties/Resources.resx | 5 +- .../Services/MoonrakerApiService.cs | 49 +++++ .../Services/PrusaConnectApiService.cs | 1 + .../Services/PrusaLinkApiService.cs | 1 + 9 files changed, 241 insertions(+), 39 deletions(-) create mode 100644 MakerPrompt.Shared/Pages/ProjectHub.razor diff --git a/MakerPrompt.Shared/Components/ControlPanel.razor b/MakerPrompt.Shared/Components/ControlPanel.razor index 3f70082..1d17a2a 100644 --- a/MakerPrompt.Shared/Components/ControlPanel.razor +++ b/MakerPrompt.Shared/Components/ControlPanel.razor @@ -1,4 +1,5 @@ @inherits ConnectionComponentBase +@using MakerPrompt.Shared.Services @inject PrinterStorageProvider PrinterStorage @inject IAppLocalStorageProvider AppStorage @@ -66,6 +67,8 @@
@* ── Left: Position + Webcam ── *@ + @if (SupportsDirectControl) + {
@@ -144,6 +147,15 @@
+ } + else + { +
+
+ +
+
+ } @* ── Right: Temps + Extrude + Speed/Flow + Calibration ── *@
@@ -155,18 +167,21 @@ @Localizer[Resources.ControlPanel_Heating]
- @* Preheat presets *@ -
- @foreach (var (label, hotend, bed) in PreheatProfiles) - { - var h = hotend; var b = bed; - - } -
+ @* Preheat presets (control-capable backends only) *@ + @if (SupportsDirectControl) + { +
+ @foreach (var (label, hotend, bed) in PreheatProfiles) + { + var h = hotend; var b = bed; + + } +
+ } @* Hotend *@
@@ -183,11 +198,14 @@
60 ? "bg-warning" : "bg-secondary")" style="width:@($"{Math.Min(Telemetry.HotendTemp / 300.0 * 100, 100):F0}%")">
-
- - °C - -
+ @if (SupportsDirectControl) + { +
+ + °C + +
+ }
@* Bed *@
@@ -204,11 +222,14 @@
-
- - °C - -
+ @if (SupportsDirectControl) + { +
+ + °C + +
+ }
@* Chamber temp (shown when data is available from printer) *@ @if (Telemetry.ChamberTemp > 0 || Telemetry.ChamberTarget > 0) @@ -241,16 +262,21 @@
-
- - % - -
+ @if (SupportsDirectControl) + { +
+ + % + +
+ }
- @* Extrude *@ + @* Extrude (direct-control backends only) *@ + @if (SupportsDirectControl) + {
@@ -283,6 +309,7 @@
+ } @* Speed / Flow *@
@@ -297,26 +324,34 @@ @Localizer[Resources.ControlPanel_PrintSpeed] @Telemetry.FeedRate% -
- - % -
+ @if (SupportsDirectControl) + { +
+ + % +
+ }
-
- - % -
+ @if (SupportsDirectControl) + { +
+ + % +
+ }
- @* Calibration *@ + @* Calibration (direct-control backends only) *@ + @if (SupportsDirectControl) + {
@@ -326,6 +361,45 @@
+ } + + @* Printer Queue (Moonraker only) *@ + @if (SupportsPrinterQueue) + { +
+
+ + Printer Queue + +
+
+ @if (!_printerQueue.Any()) + { +
+ +

No jobs in printer queue

+
+ } + else + { +
+ @foreach (var job in _printerQueue) + { +
+
+ + @job.FileName + @DateTimeOffset.FromUnixTimeSeconds((long)job.TimeAdded).ToLocalTime().ToString("HH:mm") +
+
+ } +
+ } +
+
+ } @* ── File storage ── *@ @@ -346,6 +420,11 @@ private int printSpeed = 100; private int printFlow = 100; + private List _printerQueue = []; + + private bool SupportsDirectControl => PrinterServiceFactory.Current?.SupportsDirectControl ?? true; + private bool SupportsPrinterQueue => PrinterServiceFactory.Current?.SupportsPrinterQueue ?? false; + private static readonly (string Label, int Hotend, int Bed)[] PreheatProfiles = [ ("PLA", 200, 60), @@ -460,6 +539,29 @@ return printer?.SetPrintFlow(printFlow) ?? Task.CompletedTask; } + protected override void OnInitialized() + { + base.OnInitialized(); + if (SupportsPrinterQueue) + _ = RefreshPrinterQueueAsync(); + } + + protected override void HandleConnectionChanged(object? sender, bool connected) + { + base.HandleConnectionChanged(sender, connected); + if (connected && SupportsPrinterQueue) + _ = RefreshPrinterQueueAsync(); + } + + private async Task RefreshPrinterQueueAsync() + { + if (PrinterServiceFactory.Current is MoonrakerApiService moonraker) + { + _printerQueue = await moonraker.GetPrinterQueueAsync(); + await InvokeAsync(StateHasChanged); + } + } + private readonly PrinterTelemetry fallbackTelemetry = new(); private PrinterTelemetry Telemetry => PrinterServiceFactory.Current?.LastTelemetry ?? fallbackTelemetry; } diff --git a/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs b/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs index 0ddcb7a..97b8b54 100644 --- a/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs +++ b/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs @@ -11,6 +11,19 @@ public interface IPrinterCommunicationService : IAsyncDisposable bool IsConnected { get; } bool IsPrinting { get; } + /// + /// True when the backend supports direct printer control commands + /// (temperature set, motion, extrusion, fan, etc.). + /// Monitoring-only backends (PrusaLink, PrusaConnect) return false. + /// + bool SupportsDirectControl => true; + + /// + /// True when the backend exposes a queryable printer-side print queue. + /// Currently only Moonraker supports this. + /// + bool SupportsPrinterQueue => false; + Task ConnectAsync(PrinterConnectionSettings connectionSettings); Task DisconnectAsync(); Task WriteDataAsync(string command); diff --git a/MakerPrompt.Shared/Layout/NavMenu.razor b/MakerPrompt.Shared/Layout/NavMenu.razor index 9d3a082..d02d812 100644 --- a/MakerPrompt.Shared/Layout/NavMenu.razor +++ b/MakerPrompt.Shared/Layout/NavMenu.razor @@ -12,6 +12,12 @@ @Resources.PageTitle_Fleet + } else { diff --git a/MakerPrompt.Shared/Pages/ProjectHub.razor b/MakerPrompt.Shared/Pages/ProjectHub.razor new file mode 100644 index 0000000..a127919 --- /dev/null +++ b/MakerPrompt.Shared/Pages/ProjectHub.razor @@ -0,0 +1,21 @@ +@page "/projecthub" +@inject IAppConfigurationService ConfigService +@inject NavigationManager Navigation +@inject IStringLocalizer localizer + + + +
+ +
+ +@code { + protected override async Task OnInitializedAsync() + { + await ConfigService.InitializeAsync(); + if (!ConfigService.Configuration.FarmModeEnabled) + { + Navigation.NavigateTo("/dashboard", replace: true); + } + } +} diff --git a/MakerPrompt.Shared/Properties/Resources.Designer.cs b/MakerPrompt.Shared/Properties/Resources.Designer.cs index 152196a..0745b46 100644 --- a/MakerPrompt.Shared/Properties/Resources.Designer.cs +++ b/MakerPrompt.Shared/Properties/Resources.Designer.cs @@ -1958,9 +1958,15 @@ public static string PageTitle_Fleet { return ResourceManager.GetString("PageTitle_Fleet", resourceCulture); } } + + public static string PageTitle_ProjectHub { + get { + return ResourceManager.GetString("PageTitle_ProjectHub", resourceCulture); + } + } /// - /// Looks up a localized string similar to Print Queue. + /// Looks up a localized string similar to Project Hub. /// public static string PrintQueue_Title { get { diff --git a/MakerPrompt.Shared/Properties/Resources.resx b/MakerPrompt.Shared/Properties/Resources.resx index ae4d6df..1f91caa 100644 --- a/MakerPrompt.Shared/Properties/Resources.resx +++ b/MakerPrompt.Shared/Properties/Resources.resx @@ -750,8 +750,11 @@ Fleet + + Project Hub + - Print Queue + Project Hub No files available. Connect a printer to browse files. diff --git a/MakerPrompt.Shared/Services/MoonrakerApiService.cs b/MakerPrompt.Shared/Services/MoonrakerApiService.cs index a95c3ac..9174262 100644 --- a/MakerPrompt.Shared/Services/MoonrakerApiService.cs +++ b/MakerPrompt.Shared/Services/MoonrakerApiService.cs @@ -9,6 +9,7 @@ public class MoonrakerApiService : BasePrinterConnectionService, IPrinterCommuni private string _jwtToken = string.Empty; private string _refreshToken = string.Empty; public override PrinterConnectionType ConnectionType { get; } = PrinterConnectionType.Moonraker; + public bool SupportsPrinterQueue => true; public MoonrakerApiService() { @@ -291,6 +292,43 @@ public async Task> GetFilesAsync() }).ToList(); } + /// + /// Returns the current Moonraker print queue (queued job entries). + /// Returns an empty list when not connected or the API call fails. + /// + public async Task> GetPrinterQueueAsync() + { + if (!IsConnected) return []; + + try + { + var response = await Client.GetAsync("/server/print_queue", _cts.Token); + if (!response.IsSuccessStatusCode) return []; + + var json = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(json); + if (!doc.RootElement.TryGetProperty("result", out var result)) return []; + if (!result.TryGetProperty("queued_jobs", out var jobs) || + jobs.ValueKind != JsonValueKind.Array) return []; + + var entries = new List(); + foreach (var job in jobs.EnumerateArray()) + { + entries.Add(new PrintQueueEntry + { + JobId = job.TryGetProperty("job_id", out var id) ? id.GetString() ?? string.Empty : string.Empty, + FileName = job.TryGetProperty("filename", out var fn) ? fn.GetString() ?? string.Empty : string.Empty, + TimeAdded = job.TryGetProperty("time_added", out var ta) ? ta.GetDouble() : 0, + }); + } + return entries; + } + catch + { + return []; + } + } + public async Task OpenReadAsync(string fullPath, CancellationToken cancellationToken = default) { if (!IsConnected) return null; @@ -619,3 +657,14 @@ private sealed record WebcamEntry } } + +/// +/// Represents a single entry in the Moonraker print queue. +/// +public sealed class PrintQueueEntry +{ + public string JobId { get; init; } = string.Empty; + public string FileName { get; init; } = string.Empty; + /// Unix timestamp when the job was added to the queue. + public double TimeAdded { get; init; } +} diff --git a/MakerPrompt.Shared/Services/PrusaConnectApiService.cs b/MakerPrompt.Shared/Services/PrusaConnectApiService.cs index 60fbc35..4601626 100644 --- a/MakerPrompt.Shared/Services/PrusaConnectApiService.cs +++ b/MakerPrompt.Shared/Services/PrusaConnectApiService.cs @@ -33,6 +33,7 @@ public sealed class PrusaConnectApiService : BasePrinterConnectionService, IPrin private string? _printerUuid; public override PrinterConnectionType ConnectionType => PrinterConnectionType.PrusaConnect; + public bool SupportsDirectControl => false; public PrusaConnectApiService() { } diff --git a/MakerPrompt.Shared/Services/PrusaLinkApiService.cs b/MakerPrompt.Shared/Services/PrusaLinkApiService.cs index 3681ef9..97930e7 100644 --- a/MakerPrompt.Shared/Services/PrusaLinkApiService.cs +++ b/MakerPrompt.Shared/Services/PrusaLinkApiService.cs @@ -10,6 +10,7 @@ public class PrusaLinkApiService : BasePrinterConnectionService, IPrinterCommuni private Uri? _baseUri; public override PrinterConnectionType ConnectionType => PrinterConnectionType.PrusaLink; + public bool SupportsDirectControl => false; public PrusaLinkApiService() { From 3ac6dd37e4d9ee06cd307fe534ab54d6629983e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:19:42 +0000 Subject: [PATCH 3/5] Address code review: interface GetPrinterQueueAsync, NavMenu consistency, unused inject Agent-Logs-Url: https://github.com/akinbender/MakerPrompt/sessions/27f94188-df70-497d-b60d-fc1355a18a66 Co-authored-by: akinbender <40242943+akinbender@users.noreply.github.com> --- MakerPrompt.Shared/Components/ControlPanel.razor | 5 ++--- .../Infrastructure/IPrinterCommunicationService.cs | 5 +++++ MakerPrompt.Shared/Layout/NavMenu.razor | 2 +- MakerPrompt.Shared/Pages/ProjectHub.razor | 1 - 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/MakerPrompt.Shared/Components/ControlPanel.razor b/MakerPrompt.Shared/Components/ControlPanel.razor index 1d17a2a..20145b1 100644 --- a/MakerPrompt.Shared/Components/ControlPanel.razor +++ b/MakerPrompt.Shared/Components/ControlPanel.razor @@ -1,5 +1,4 @@ @inherits ConnectionComponentBase -@using MakerPrompt.Shared.Services @inject PrinterStorageProvider PrinterStorage @inject IAppLocalStorageProvider AppStorage @@ -555,9 +554,9 @@ private async Task RefreshPrinterQueueAsync() { - if (PrinterServiceFactory.Current is MoonrakerApiService moonraker) + if (PrinterServiceFactory.Current is { SupportsPrinterQueue: true } printer) { - _printerQueue = await moonraker.GetPrinterQueueAsync(); + _printerQueue = await printer.GetPrinterQueueAsync(); await InvokeAsync(StateHasChanged); } } diff --git a/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs b/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs index 97b8b54..172599e 100644 --- a/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs +++ b/MakerPrompt.Shared/Infrastructure/IPrinterCommunicationService.cs @@ -29,6 +29,11 @@ public interface IPrinterCommunicationService : IAsyncDisposable Task WriteDataAsync(string command); Task GetPrinterTelemetryAsync(); Task> GetFilesAsync(); + /// + /// Returns the printer-side print queue entries. Returns an empty list + /// for backends that do not support a print queue ( is false). + /// + Task> GetPrinterQueueAsync() => Task.FromResult(new List()); Task SetHotendTemp(int targetTemp = 0); Task SetBedTemp(int targetTemp = 0); Task Home(bool x = true, bool y = true, bool z = true); diff --git a/MakerPrompt.Shared/Layout/NavMenu.razor b/MakerPrompt.Shared/Layout/NavMenu.razor index d02d812..9162b13 100644 --- a/MakerPrompt.Shared/Layout/NavMenu.razor +++ b/MakerPrompt.Shared/Layout/NavMenu.razor @@ -15,7 +15,7 @@ } diff --git a/MakerPrompt.Shared/Pages/ProjectHub.razor b/MakerPrompt.Shared/Pages/ProjectHub.razor index a127919..b83a1f9 100644 --- a/MakerPrompt.Shared/Pages/ProjectHub.razor +++ b/MakerPrompt.Shared/Pages/ProjectHub.razor @@ -1,7 +1,6 @@ @page "/projecthub" @inject IAppConfigurationService ConfigService @inject NavigationManager Navigation -@inject IStringLocalizer localizer From 48b78f755b8ea507c0672d70d7d833a9020e8fbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:23:55 +0000 Subject: [PATCH 4/5] Localize printer queue strings, add error handling, fix silent catch comment Agent-Logs-Url: https://github.com/akinbender/MakerPrompt/sessions/27f94188-df70-497d-b60d-fc1355a18a66 Co-authored-by: akinbender <40242943+akinbender@users.noreply.github.com> --- MakerPrompt.Shared/Components/ControlPanel.razor | 14 +++++++++----- .../Properties/Resources.Designer.cs | 12 ++++++++++++ MakerPrompt.Shared/Properties/Resources.resx | 6 ++++++ MakerPrompt.Shared/Services/MoonrakerApiService.cs | 2 ++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/MakerPrompt.Shared/Components/ControlPanel.razor b/MakerPrompt.Shared/Components/ControlPanel.razor index 20145b1..85ce554 100644 --- a/MakerPrompt.Shared/Components/ControlPanel.razor +++ b/MakerPrompt.Shared/Components/ControlPanel.razor @@ -368,8 +368,8 @@
- Printer Queue -
@@ -378,7 +378,7 @@ {
-

No jobs in printer queue

+

@Localizer[Resources.ControlPanel_PrinterQueueEmpty]

} else @@ -542,14 +542,14 @@ { base.OnInitialized(); if (SupportsPrinterQueue) - _ = RefreshPrinterQueueAsync(); + _ = SafeRefreshPrinterQueueAsync(); } protected override void HandleConnectionChanged(object? sender, bool connected) { base.HandleConnectionChanged(sender, connected); if (connected && SupportsPrinterQueue) - _ = RefreshPrinterQueueAsync(); + _ = SafeRefreshPrinterQueueAsync(); } private async Task RefreshPrinterQueueAsync() @@ -561,6 +561,10 @@ } } + // Wraps RefreshPrinterQueueAsync with error handling for fire-and-forget calls + private Task SafeRefreshPrinterQueueAsync() => + RunAsync(RefreshPrinterQueueAsync, Localizer[Resources.ControlPanel_PrinterQueue]); + private readonly PrinterTelemetry fallbackTelemetry = new(); private PrinterTelemetry Telemetry => PrinterServiceFactory.Current?.LastTelemetry ?? fallbackTelemetry; } diff --git a/MakerPrompt.Shared/Properties/Resources.Designer.cs b/MakerPrompt.Shared/Properties/Resources.Designer.cs index 0745b46..c0dbdaf 100644 --- a/MakerPrompt.Shared/Properties/Resources.Designer.cs +++ b/MakerPrompt.Shared/Properties/Resources.Designer.cs @@ -671,6 +671,18 @@ public static string ControlPanel_FanSpeed { return ResourceManager.GetString("ControlPanel_FanSpeed", resourceCulture); } } + + public static string ControlPanel_PrinterQueue { + get { + return ResourceManager.GetString("ControlPanel_PrinterQueue", resourceCulture); + } + } + + public static string ControlPanel_PrinterQueueEmpty { + get { + return ResourceManager.GetString("ControlPanel_PrinterQueueEmpty", resourceCulture); + } + } /// /// Looks up a localized string similar to Heating. diff --git a/MakerPrompt.Shared/Properties/Resources.resx b/MakerPrompt.Shared/Properties/Resources.resx index 1f91caa..791aeaf 100644 --- a/MakerPrompt.Shared/Properties/Resources.resx +++ b/MakerPrompt.Shared/Properties/Resources.resx @@ -483,6 +483,12 @@ Fan speed + + Printer Queue + + + No jobs in printer queue + Home all diff --git a/MakerPrompt.Shared/Services/MoonrakerApiService.cs b/MakerPrompt.Shared/Services/MoonrakerApiService.cs index 9174262..a8fe3da 100644 --- a/MakerPrompt.Shared/Services/MoonrakerApiService.cs +++ b/MakerPrompt.Shared/Services/MoonrakerApiService.cs @@ -325,6 +325,8 @@ public async Task> GetPrinterQueueAsync() } catch { + // Swallow API errors silently — the caller (ControlPanel) handles + // the empty-list case and uses RunAsync for user-facing error reporting. return []; } } From 34a9a1af6bd00130b83d1bb58ec14764a58e0ad8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:15:49 +0000 Subject: [PATCH 5/5] Wire NavPrinters into layout; add PrusaConnect to PrinterConnectionModal Agent-Logs-Url: https://github.com/akinbender/MakerPrompt/sessions/6d78f34c-b92d-460e-b6f5-75ec6ee4e7a0 Co-authored-by: akinbender <40242943+akinbender@users.noreply.github.com> --- .../Components/PrinterConnectionModal.razor | 25 +++++++++++++++---- MakerPrompt.Shared/Layout/MainLayout.razor | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/MakerPrompt.Shared/Components/PrinterConnectionModal.razor b/MakerPrompt.Shared/Components/PrinterConnectionModal.razor index 0f0b8ad..9509666 100644 --- a/MakerPrompt.Shared/Components/PrinterConnectionModal.razor +++ b/MakerPrompt.Shared/Components/PrinterConnectionModal.razor @@ -67,6 +67,20 @@

@localizer[Resources.NavConnection_DemoServiceDescription]

} + else if (_editConnectionType == PrinterConnectionType.PrusaConnect) + { +
+

@localizer[Resources.NavConnect_PrusaConnectDescription]

+
+
+ + +
+
+ + +
+ } else {
@@ -165,11 +179,9 @@ private PrinterConnectionType _editConnectionType = PrinterConnectionType.Demo; private List _availablePorts = new(); private readonly List BaudRates = [9600, 19200, 38400, 57600, 115200, 250000]; - // PrusaConnect uses the fleet-picker flow (PrusaConnectProvider), not this modal. + // PrusaConnect uses UUID + Bearer token from the mobile API. private static readonly PrinterConnectionType[] _connectionTypes = - Enum.GetValues() - .Where(t => t != PrinterConnectionType.PrusaConnect) - .ToArray(); + Enum.GetValues().ToArray(); public async Task ShowAddAsync() { @@ -225,7 +237,10 @@ } // Validate URL for API-based backends before attempting anything. - if (_editConnectionType != PrinterConnectionType.Serial && _editConnectionType != PrinterConnectionType.Demo + // PrusaConnect uses UUID + API key, not a URL — skip URL validation for it. + if (_editConnectionType != PrinterConnectionType.Serial + && _editConnectionType != PrinterConnectionType.Demo + && _editConnectionType != PrinterConnectionType.PrusaConnect && string.IsNullOrWhiteSpace(_editApiSettings.Url)) { _connectionError = "URL is required. Enter the printer\'s IP address or hostname (e.g. http://192.168.1.100)."; diff --git a/MakerPrompt.Shared/Layout/MainLayout.razor b/MakerPrompt.Shared/Layout/MainLayout.razor index b336e13..1eccdbe 100644 --- a/MakerPrompt.Shared/Layout/MainLayout.razor +++ b/MakerPrompt.Shared/Layout/MainLayout.razor @@ -14,7 +14,7 @@ @ConfigService.Configuration.FarmName }
- +