mirror of
https://github.com/RunLit/Bambu-Run.git
synced 2026-06-22 22:19:03 +01:00
Schema (migration 0004): - PrinterMetrics: nozzle_temp_left, nozzle_target_temp_left, nozzle_diameter_left, nozzle_type_left (all nullable) - Filament: ams_unit_id (nullable int), ams_type (AMS/AMS 2 Pro/AMS HT) - AMS_INFO_TO_TYPE map and AMS_TYPE_CHOICES on models Parser (mqtt_client.py): - Decode bit-packed temps from device.extruder.info[] for left/right nozzle - Emit per-nozzle fields in get_snapshot(); legacy keys mirror right side - AMS unit type from info code per unit dict Collector (bambu_collector.py): - Write left-nozzle fields to PrinterMetrics - Set ams_unit_id + ams_type on Filament records - Fix: poll MQTTClient.connected before pushall (not BambuPrinter._connected) - Add 5s post-pushall wait in --once mode so response arrives before collect Views: API and dashboard include left-nozzle series; is_dual_nozzle flag Templates: dual-nozzle cards + chart; AMS-type badge + filter on filament list Charts: left nozzle temp chart with conditional render Forms: fix tray_id max=3 → max=15; add ams_unit_id, ams_type fields
227 lines
11 KiB
HTML
227 lines
11 KiB
HTML
{% extends bambu_run_base_template %}
|
|
{% load static %}
|
|
|
|
{% block extra_css %}
|
|
<link rel="stylesheet" href="{% static 'bambu_run/css/dashboard.css' %}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<h1>Filament Inventory</h1>
|
|
<p class="text-body-secondary">Manage your 3D printer filament spools</p>
|
|
</div>
|
|
{% if not is_basic_user %}
|
|
<div class="col-auto">
|
|
<a href="{% url 'bambu_run:filament_type_list' %}" class="btn btn-outline-info me-2">
|
|
<i class="bi bi-list-ul"></i> Manage Types
|
|
</a>
|
|
<a href="{% url 'bambu_run:filament_color_list' %}" class="btn btn-outline-info me-2">
|
|
<i class="bi bi-palette"></i> Manage Colors
|
|
</a>
|
|
<a href="{% url 'bambu_run:filament_create' %}" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle"></i> Add Filament
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card infra-card-info">
|
|
<div class="card-body">
|
|
<div class="stat-label">Total Spools</div>
|
|
<div class="stat-value">{{ total_spools }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card infra-card-success">
|
|
<div class="card-body">
|
|
<div class="stat-label">Loaded in AMS</div>
|
|
<div class="stat-value">{{ loaded_spools }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card infra-card-warning">
|
|
<div class="card-body">
|
|
<div class="stat-label">Low Filament (<20%)</div>
|
|
<div class="stat-value">{{ low_filaments }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="card mb-4">
|
|
<div class="card-body">
|
|
<form method="get" class="row g-3">
|
|
<div class="col-md-3">
|
|
<input type="text" name="search" class="form-control" placeholder="Search..." value="{{ request.GET.search }}">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<select name="type" class="form-select">
|
|
<option value="">All Types</option>
|
|
{% for type in filament_types %}
|
|
<option value="{{ type }}" {% if request.GET.type == type %}selected{% endif %}>{{ type }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select name="loaded" class="form-select">
|
|
<option value="">All Spools</option>
|
|
<option value="yes" {% if request.GET.loaded == 'yes' %}selected{% endif %}>Loaded in AMS</option>
|
|
<option value="no" {% if request.GET.loaded == 'no' %}selected{% endif %}>Not Loaded</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select name="ams_type" class="form-select">
|
|
<option value="">All AMS Types</option>
|
|
{% for at in ams_type_choices %}
|
|
<option value="{{ at }}" {% if request.GET.ams_type == at %}selected{% endif %}>{{ at }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="submit" class="btn btn-secondary">Filter</button>
|
|
<a href="{% url 'bambu_run:filament_list' %}" class="btn btn-outline-secondary">Reset</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filament List -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th class="align-middle">SN</th>
|
|
<th class="align-middle">Color</th>
|
|
<th class="align-middle">Brand</th>
|
|
<th class="align-middle">Type</th>
|
|
<th class="align-middle">Sub Type</th>
|
|
<th class="align-middle">Remaining</th>
|
|
<th class="align-middle">Location</th>
|
|
<th class="align-middle">Created By</th>
|
|
<th class="align-middle">Last Used</th>
|
|
<th class="align-middle">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for filament in filaments %}
|
|
<tr>
|
|
<td class="align-middle">
|
|
{% if filament.tray_uuid %}
|
|
<span class="font-monospace small"
|
|
data-bs-toggle="tooltip"
|
|
data-bs-placement="top"
|
|
title="{{ filament.tray_uuid }}"
|
|
style="cursor: help;">
|
|
{{ filament.tray_uuid|slice:":8" }}...
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="align-middle">
|
|
<div class="d-flex align-items-center">
|
|
{% if filament.is_transparent %}
|
|
<div style="width: 30px; height: 30px; border-radius: 4px; margin-right: 10px; border: 1px solid #ddd; background: repeating-conic-gradient(#ccc 0% 25%, #fff 0% 50%) 0 0/10px 10px;" title="Clear / Transparent"></div>
|
|
{% else %}
|
|
<div style="width: 30px; height: 30px; background-color: {{ filament.color_hex|default:'#999' }}; border-radius: 4px; margin-right: 10px; border: 1px solid #ddd;"></div>
|
|
{% endif %}
|
|
{{ filament.color }}
|
|
</div>
|
|
</td>
|
|
<td class="align-middle">{{ filament.brand }}</td>
|
|
<td class="align-middle"><span class="badge bg-secondary">{{ filament.type }}</span></td>
|
|
<td class="align-middle">
|
|
{% if filament.sub_type %}
|
|
<span class="badge bg-info">{{ filament.sub_type }}</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="align-middle">
|
|
<div class="progress" style="height: 20px;">
|
|
<div class="progress-bar {% if filament.remaining_percent < 20 %}bg-danger{% elif filament.remaining_percent < 50 %}bg-warning{% else %}bg-success{% endif %}"
|
|
style="width: {{ filament.remaining_percent }}%;">
|
|
{{ filament.remaining_percent }}%
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="align-middle">
|
|
{% if filament.is_loaded_in_ams %}
|
|
<span class="badge bg-success">
|
|
{% if filament.ams_type %}{{ filament.ams_type }}{% else %}AMS{% endif %}
|
|
{% if filament.ams_unit_id is not None %}#{{ filament.ams_unit_id }}{% endif %}
|
|
· Tray {{ filament.current_tray_id }}
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Storage</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="align-middle">
|
|
{% if filament.created_by == 'Auto Detection' %}
|
|
<span class="badge bg-primary">Auto</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Manual</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="align-middle">{{ filament.last_used|date:"Y-m-d H:i"|default:"Never" }}</td>
|
|
<td class="align-middle">
|
|
<a href="{% url 'bambu_run:filament_detail' filament.pk %}" class="btn btn-sm btn-info">View</a>
|
|
{% if not is_basic_user %}
|
|
<a href="{% url 'bambu_run:filament_update' filament.pk %}" class="btn btn-sm btn-warning">Edit</a>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="10" class="text-center text-muted">No filaments found. <a href="{% url 'bambu_run:filament_create' %}">Add your first spool!</a></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<nav>
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item"><a class="page-link" href="?page=1">First</a></li>
|
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a></li>
|
|
{% endif %}
|
|
|
|
<li class="page-item active"><span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span></li>
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a></li>
|
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last</a></li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Enable Bootstrap tooltips for SN hover
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|