- Add Virtual Printer feature - emulate Bambu printer on network

- Virtual printer appears in Bambu Studio/Orca Slicer via SSDP discovery
  - Secure TLS/MQTT communication with auto-generated certificates
  - Queue mode (pending uploads) or auto-start mode
  - Configurable access code for authentication
  - Docker support with network_mode: host and certificate persistence
  - Fix backup/restore for virtual printer settings (auto-save no longer overwrites)
This commit is contained in:
maziggy
2025-12-29 15:52:48 +01:00
parent c3c36719a5
commit 88e84ca45a
31 changed files with 5068 additions and 439 deletions
+74
View File
@@ -2342,3 +2342,77 @@ export const discoveryApi = {
stopSubnetScan: () =>
request<SubnetScanStatus>('/discovery/scan/stop', { method: 'POST' }),
};
// Virtual Printer types
export interface VirtualPrinterStatus {
enabled: boolean;
running: boolean;
mode: 'immediate' | 'queue';
name: string;
serial: string;
model: string;
pending_files: number;
}
export interface VirtualPrinterSettings {
enabled: boolean;
access_code_set: boolean;
mode: 'immediate' | 'queue';
status: VirtualPrinterStatus;
}
export interface PendingUpload {
id: number;
filename: string;
file_size: number;
source_ip: string | null;
status: string;
tags: string | null;
notes: string | null;
project_id: number | null;
uploaded_at: string;
}
// Virtual Printer API
export const virtualPrinterApi = {
getSettings: () => request<VirtualPrinterSettings>('/settings/virtual-printer'),
updateSettings: (data: {
enabled?: boolean;
access_code?: string;
mode?: 'immediate' | 'queue';
}) => {
const params = new URLSearchParams();
if (data.enabled !== undefined) params.set('enabled', String(data.enabled));
if (data.access_code !== undefined) params.set('access_code', data.access_code);
if (data.mode !== undefined) params.set('mode', data.mode);
return request<VirtualPrinterSettings>(`/settings/virtual-printer?${params.toString()}`, {
method: 'PUT',
});
},
};
// Pending Uploads API
export const pendingUploadsApi = {
list: () => request<PendingUpload[]>('/pending-uploads/'),
getCount: () => request<{ count: number }>('/pending-uploads/count'),
get: (id: number) => request<PendingUpload>(`/pending-uploads/${id}`),
archive: (id: number, data?: { tags?: string; notes?: string; project_id?: number }) =>
request<{ id: number; print_name: string; filename: string }>(`/pending-uploads/${id}/archive`, {
method: 'POST',
body: JSON.stringify(data || {}),
}),
discard: (id: number) =>
request<{ success: boolean }>(`/pending-uploads/${id}`, { method: 'DELETE' }),
archiveAll: () =>
request<{ archived: number; failed: number }>('/pending-uploads/archive-all', { method: 'POST' }),
discardAll: () =>
request<{ discarded: number }>('/pending-uploads/discard-all', { method: 'DELETE' }),
};