Files
HickLab3D_Dashboard/resources/views/printers/index.blade.php
T
HickmeisterandClaude Sonnet 5.5 b11538944f
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 28s
Add BamBuddy AMS, queue, history and stats to the print farm page
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 20:14:29 +01:00

33 lines
8.0 KiB
PHP

@extends('layouts.app')
@section('title', 'Print farm')
@section('content')
@php $display = app(\App\Services\Display::class); @endphp
<div class="flex flex-wrap items-end justify-between gap-4"><div><p class="text-xs font-bold uppercase tracking-[.2em] text-brand">Production</p><h2 class="mt-2 text-2xl font-bold">BamBuddy printers</h2><p class="mt-1 text-slate-400">Live state, progress and temperatures from the workshop.</p></div><a href="{{ $bambuddyUrl }}" target="_blank" rel="noopener noreferrer" class="rounded-xl border border-brand/40 px-4 py-3 text-sm font-semibold text-brand hover:bg-brand/10">Open BamBuddy ↗</a></div>
@if($integrationError)<div role="alert" class="rounded-xl border border-amber-500/30 bg-amber-500/10 p-4 text-sm text-amber-200">{{ $integrationError }}</div>@endif
<div class="grid gap-4 lg:grid-cols-2">@forelse($printers as $printer)
@php $live = $printer['live'] ?? []; $progress = min(100, max(0, (float) ($live['progress'] ?? 0))); $printerErrors = is_array($live['hms_errors'] ?? null) ? $live['hms_errors'] : []; @endphp
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-6"><div class="flex justify-between gap-3"><div><h3 class="text-lg font-semibold">{{ $printer['name'] ?? 'Printer' }}</h3><p class="text-sm text-slate-400">{{ $printer['model'] ?? 'Bambu printer' }}</p></div><span @class(['h-fit rounded-full px-3 py-1 text-xs font-semibold', 'bg-brand/10 text-brand' => ($live['connected'] ?? false), 'bg-amber-500/10 text-amber-200' => ! ($live['connected'] ?? false)])>{{ $live['state'] ?? $printer['status'] ?? 'Unavailable' }}</span></div>
<div class="mt-6 flex items-end justify-between gap-4"><div><p class="text-sm text-slate-400">Current job</p><p class="mt-1 max-w-sm truncate font-medium">{{ $live['current_print'] ?? $live['subtask_name'] ?? 'No active job' }}</p></div><strong class="text-2xl">{{ number_format($progress, 0) }}%</strong></div>@if(! empty($live['total_layers']))<p class="mt-1 text-xs text-slate-400">Layer {{ $live['layer_num'] ?? 0 }} of {{ $live['total_layers'] }}</p>@endif
<div class="mt-3 h-2 overflow-hidden rounded-full bg-slate-800"><div class="h-full rounded-full bg-brand" style="width: {{ $progress }}%"></div></div>
<dl class="mt-5 grid grid-cols-3 gap-3 text-sm"><div class="rounded-xl bg-slate-950 p-3"><dt class="text-slate-400">Nozzle</dt><dd class="mt-1 font-semibold">{{ isset($live['temperatures']['nozzle']) ? $live['temperatures']['nozzle'].'°C' : '—' }}</dd></div><div class="rounded-xl bg-slate-950 p-3"><dt class="text-slate-400">Bed</dt><dd class="mt-1 font-semibold">{{ isset($live['temperatures']['bed']) ? $live['temperatures']['bed'].'°C' : '—' }}</dd></div><div class="rounded-xl bg-slate-950 p-3"><dt class="text-slate-400">Remaining</dt><dd class="mt-1 font-semibold">{{ isset($live['remaining_time']) ? $display->duration($live['remaining_time'] * 60) : '—' }}</dd></div></dl>
@foreach(($live['ams'] ?? []) as $unit)
<div class="mt-5"><div class="flex items-center justify-between text-xs text-slate-400"><span>{{ ($unit['is_ams_ht'] ?? false) ? 'AMS HT' : 'AMS' }} {{ ($unit['id'] ?? 0) + 1 }}</span><span>@if(isset($unit['humidity']))Humidity {{ $unit['humidity'] }}%@endif @if(isset($unit['temp'])) · {{ $unit['temp'] }}°C @endif</span></div>
<div class="mt-2 grid grid-cols-4 gap-2">@foreach(($unit['tray'] ?? []) as $tray)@php $loaded = ($tray['exists'] ?? false) && filled($tray['tray_type'] ?? null); $colour = preg_match('/^[0-9A-Fa-f]{6}/', (string) ($tray['tray_color'] ?? '')) ? '#'.substr($tray['tray_color'], 0, 6) : '#1e293b'; $remain = (int) ($tray['remain'] ?? 0); @endphp
<div class="rounded-xl bg-slate-950 p-2 text-center text-xs">@if($loaded)<span class="mx-auto block h-6 w-6 rounded-full border border-slate-600" style="background: {{ $colour }}"></span><p class="mt-1 font-semibold">{{ $tray['tray_type'] }}</p><p class="text-slate-400">{{ $remain > 0 ? $remain.'%' : '—' }}</p>@else<span class="mx-auto block h-6 w-6 rounded-full border border-dashed border-slate-700"></span><p class="mt-1 text-slate-500">Empty</p>@endif</div>@endforeach</div></div>
@endforeach
@if(count($printerErrors))<p class="mt-4 rounded-lg border border-rose-500/30 bg-rose-500/10 p-3 text-sm text-rose-200">{{ count($printerErrors) }} printer alert{{ count($printerErrors) === 1 ? '' : 's' }} · {{ $printerErrors[0]['description'] ?? 'Open BamBuddy for details' }}</p>@endif
</article>
@empty<div class="rounded-2xl border border-dashed border-slate-700 p-10 text-slate-400">No printers returned by BamBuddy.</div>@endforelse</div>
@if(! $integrationError)
<section class="rounded-2xl border border-slate-800 bg-slate-900"><div class="flex items-center justify-between gap-3 p-6"><div><h3 class="text-lg font-semibold">Print queue</h3><p class="mt-1 text-sm text-slate-400">Jobs waiting for a printer.</p></div><span class="rounded-full bg-brand/10 px-3 py-1 text-xs font-semibold text-brand">{{ count($queue) }} queued</span></div>
@forelse($queue as $job)<div class="flex flex-wrap items-center justify-between gap-3 border-t border-slate-800 px-6 py-4 text-sm"><div class="min-w-0"><p class="truncate font-medium">{{ $job['archive_name'] ?? $job['library_file_name'] ?? 'Print job' }}</p><p class="mt-1 text-xs text-slate-400">{{ $job['printer_name'] ?? $job['target_model'] ?? 'Any printer' }}@if(! empty($job['filament_type'])) · {{ $job['filament_type'] }}@endif @if(! empty($job['print_time_seconds'])) · {{ $display->duration($job['print_time_seconds']) }}@endif</p></div><span class="text-xs text-slate-400">{{ $job['waiting_reason'] ?? ucfirst((string) ($job['status'] ?? 'pending')) }}</span></div>
@empty<p class="border-t border-slate-800 px-6 py-6 text-sm text-slate-400">The queue is empty.</p>@endforelse</section>
@if($stats)<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">@foreach([['Total prints', $stats['total_prints'] ?? 0], ['Successful', $stats['successful_prints'] ?? 0], ['Failed', $stats['failed_prints'] ?? 0], ['Print time', number_format((float) ($stats['total_print_time_hours'] ?? 0), 1).' h'], ['Filament used', number_format((float) ($stats['total_filament_grams'] ?? 0) / 1000, 2).' kg']] as [$label, $value])<div class="rounded-2xl border border-slate-800 bg-slate-900 p-5"><p class="text-sm text-slate-400">{{ $label }}</p><p class="mt-2 text-2xl font-bold">{{ $value }}</p></div>@endforeach</div>@endif
<section class="overflow-x-auto rounded-2xl border border-slate-800 bg-slate-900"><div class="p-6"><h3 class="text-lg font-semibold">Recent prints</h3><p class="mt-1 text-sm text-slate-400">Latest archived jobs from BamBuddy.</p></div>
<table class="w-full min-w-[800px] text-left text-sm"><thead class="border-y border-slate-800 text-slate-400"><tr><th class="p-4">Print</th><th class="p-4">Printer</th><th class="p-4">Status</th><th class="p-4">Filament</th><th class="p-4">Duration</th><th class="p-4">Completed</th></tr></thead><tbody class="divide-y divide-slate-800">
@forelse($recentPrints as $archive)@php $status = (string) ($archive['status'] ?? 'unknown'); @endphp<tr><td class="p-4 font-medium">{{ $archive['print_name'] ?? $archive['filename'] ?? 'Print' }}</td><td class="p-4">{{ collect($printers)->firstWhere('id', $archive['printer_id'] ?? null)['name'] ?? '—' }}</td><td class="p-4"><span @class(['rounded-full px-2 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $status === 'completed', 'bg-rose-500/10 text-rose-300' => $status === 'failed', 'bg-amber-500/10 text-amber-200' => ! in_array($status, ['completed', 'failed'], true)])>{{ ucfirst($status) }}</span></td><td class="p-4">{{ $archive['filament_type'] ?? '—' }}@if(! empty($archive['filament_used_grams'])) · {{ number_format((float) $archive['filament_used_grams'], 0) }} g @endif</td><td class="p-4">{{ $display->duration($archive['actual_time_seconds'] ?? $archive['print_time_seconds'] ?? null) }}</td><td class="p-4">{{ $display->dateTime($archive['completed_at'] ?? $archive['started_at'] ?? null) }}</td></tr>
@empty<tr><td colspan="6" class="p-6 text-slate-400">No archived prints yet.</td></tr>@endforelse</tbody></table></section>
@endif
@endsection