mirror of
https://github.com/RunLit/Bambu-Run.git
synced 2026-06-22 22:19:03 +01:00
Initial spin-off of bambu-run from my private project separation
This commit is contained in:
88
bambu_run/templates/bambu_run/base.html
Normal file
88
bambu_run/templates/bambu_run/base.html
Normal file
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-coreui-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Bambu Run{% endblock %}</title>
|
||||
<!-- CoreUI 5.3 CSS CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/@coreui/coreui@5.3.0/dist/css/coreui.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/css/all.min.css" rel="stylesheet">
|
||||
{% block extra_css %}{% endblock %}
|
||||
<style>
|
||||
.sidebar-brand { padding: 1rem; font-size: 1.25rem; font-weight: 700; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar sidebar-dark sidebar-fixed" id="sidebar">
|
||||
<div class="sidebar-brand d-none d-md-flex">
|
||||
Bambu Run
|
||||
</div>
|
||||
<ul class="sidebar-nav" data-coreui="navigation">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'bambu_run:printer_dashboard' %}">
|
||||
<svg class="nav-icon"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-print"></use></svg>
|
||||
3D Printer
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'bambu_run:filament_list' %}">
|
||||
<svg class="nav-icon"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-layers"></use></svg>
|
||||
Filament Inventory
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="wrapper d-flex flex-column min-vh-100">
|
||||
<header class="header header-sticky p-0 mb-4">
|
||||
<div class="container-fluid px-4">
|
||||
<button class="header-toggler" type="button" onclick="document.getElementById('sidebar').classList.toggle('show')">
|
||||
<svg class="icon icon-lg"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-menu"></use></svg>
|
||||
</button>
|
||||
<ul class="header-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<button class="nav-link" id="themeToggle" type="button">
|
||||
<svg class="icon icon-lg"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-moon"></use></svg>
|
||||
</button>
|
||||
</li>
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="body flex-grow-1">
|
||||
<div class="container-lg px-4">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer px-4">
|
||||
<div>Bambu Run</div>
|
||||
<div class="ms-auto">Powered by <a href="https://github.com/runnanli/Bambu-Run">Bambu Run</a></div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- CoreUI 5.3 JS CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@coreui/coreui@5.3.0/dist/js/coreui.bundle.min.js"></script>
|
||||
<script>
|
||||
// Theme toggle
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const savedTheme = localStorage.getItem('bambu-run-theme') || 'dark';
|
||||
document.documentElement.setAttribute('data-coreui-theme', savedTheme);
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', function() {
|
||||
const current = document.documentElement.getAttribute('data-coreui-theme');
|
||||
const next = current === 'dark' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-coreui-theme', next);
|
||||
localStorage.setItem('bambu-run-theme', next);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
{% extends bambu_run_base_template %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1>Delete Filament Color</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning">
|
||||
<h5><i class="bi bi-exclamation-triangle"></i> Warning</h5>
|
||||
<p>Are you sure you want to delete this filament color?</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5>Color Details:</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<div style="width: 100px; height: 100px; background-color: {{ object.get_hex_color }}; border-radius: 8px; border: 2px solid #ddd;"></div>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<p><strong>Color Name:</strong> {{ object.color_name }}</p>
|
||||
<p><strong>Hex Code:</strong> <span class="font-monospace">{{ object.get_hex_color }}</span></p>
|
||||
<p><strong>Type:</strong> {{ object.filament_type }}</p>
|
||||
<p><strong>Sub Type:</strong> {{ object.filament_sub_type|default:"-" }}</p>
|
||||
<p><strong>Brand:</strong> {{ object.brand }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'bambu_run:filament_color_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash"></i> Yes, Delete Color
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
80
bambu_run/templates/bambu_run/filament_color_form.html
Normal file
80
bambu_run/templates/bambu_run/filament_color_form.html
Normal file
@@ -0,0 +1,80 @@
|
||||
{% extends bambu_run_base_template %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Filament Color</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<h5>Color Information</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Color Name *</label>
|
||||
{{ form.color_name }}
|
||||
<small class="form-text text-muted">e.g., Black, Orange, Signal White</small>
|
||||
{% if form.color_name.errors %}
|
||||
<div class="text-danger">{{ form.color_name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Color Hex Code *</label>
|
||||
{{ form.color_hex_input }}
|
||||
<small class="form-text text-muted">Format: #RRGGBB (without FF padding)</small>
|
||||
{% if form.color_hex_input.errors %}
|
||||
<div class="text-danger">{{ form.color_hex_input.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h5>Filament Type (for matching)</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Filament Type</label>
|
||||
{{ form.filament_type_fk }}
|
||||
<small class="form-text text-muted">Select from the filament type registry</small>
|
||||
{% if form.filament_type_fk.errors %}
|
||||
<div class="text-danger">{{ form.filament_type_fk.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden fields for backward compatibility -->
|
||||
{{ form.color_code }}
|
||||
{{ form.filament_type }}
|
||||
{{ form.filament_sub_type }}
|
||||
{{ form.brand }}
|
||||
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'bambu_run:filament_color_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{% if form.instance.pk %}Update Color{% else %}Add Color{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger mt-3">
|
||||
<strong>Please correct the following errors:</strong>
|
||||
<ul>
|
||||
{% for field, errors in form.errors.items %}
|
||||
{% for error in errors %}
|
||||
<li>{{ field }}: {{ error }}</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
109
bambu_run/templates/bambu_run/filament_color_list.html
Normal file
109
bambu_run/templates/bambu_run/filament_color_list.html
Normal file
@@ -0,0 +1,109 @@
|
||||
{% extends bambu_run_base_template %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<h1>Filament Color Database</h1>
|
||||
<p class="text-muted">Manage filament colors for auto-matching</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<a href="{% url 'bambu_run:filament_color_create' %}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Add New Color
|
||||
</a>
|
||||
<a href="{% url 'bambu_run:filament_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Inventory
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Card -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Summary</h5>
|
||||
<p class="card-text">
|
||||
<strong>Total Colors:</strong> {{ total_colors }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="align-middle">Color Preview</th>
|
||||
<th class="align-middle">Color Name</th>
|
||||
<th class="align-middle">Hex Code</th>
|
||||
<th class="align-middle">Type</th>
|
||||
<th class="align-middle">Sub Type</th>
|
||||
<th class="align-middle">Brand</th>
|
||||
<th class="align-middle">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for color in colors %}
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
<div style="width: 50px; height: 50px; background-color: {{ color.get_hex_color }}; border-radius: 4px; border: 2px solid #ddd;"></div>
|
||||
</td>
|
||||
<td class="align-middle"><strong>{{ color.color_name }}</strong></td>
|
||||
<td class="align-middle">
|
||||
<span class="font-monospace">{{ color.get_hex_color }}</span>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<span class="badge bg-secondary">{{ color.filament_type }}</span>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
{% if color.filament_sub_type %}
|
||||
<span class="badge bg-info">{{ color.filament_sub_type }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle">{{ color.brand }}</td>
|
||||
<td class="align-middle">
|
||||
<a href="{% url 'bambu_run:filament_color_update' color.pk %}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<a href="{% url 'bambu_run:filament_color_delete' color.pk %}" class="btn btn-sm btn-danger">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted">
|
||||
No colors found. <a href="{% url 'bambu_run:filament_color_create' %}">Add your first color!</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 %}
|
||||
311
bambu_run/templates/bambu_run/filament_detail.html
Normal file
311
bambu_run/templates/bambu_run/filament_detail.html
Normal file
@@ -0,0 +1,311 @@
|
||||
{% 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 }}</h1>
|
||||
<p class="text-body-secondary">Filament Spool Details</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a href="{% url 'bambu_run:filament_update' filament.pk %}" class="btn btn-warning">Edit</a>
|
||||
<a href="{% url 'bambu_run:filament_list' %}" class="btn btn-secondary">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filament Info Cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6>Color</h6>
|
||||
<div class="d-flex align-items-center">
|
||||
<div style="width: 50px; height: 50px; background-color: {{ filament.color_hex|default:'#999' }}; border-radius: 8px; margin-right: 15px; border: 2px solid #ddd;"></div>
|
||||
<div>
|
||||
<strong>{{ filament.color }}</strong><br>
|
||||
<small class="text-muted">{{ filament.color_hex }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6>Specifications</h6>
|
||||
<p class="mb-1"><strong>Type:</strong> {{ filament.type }}</p>
|
||||
{% if filament.sub_type %}
|
||||
<p class="mb-1"><strong>Sub Type:</strong> {{ filament.sub_type }}</p>
|
||||
{% endif %}
|
||||
<p class="mb-1"><strong>Brand:</strong> {{ filament.brand }}</p>
|
||||
<p class="mb-0"><strong>Diameter:</strong> {{ filament.diameter }}mm</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6>Remaining</h6>
|
||||
<div class="progress mb-2" style="height: 25px;">
|
||||
<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>
|
||||
<small>{{ filament.remaining_weight_grams|default:"?" }}g of {{ filament.initial_weight_grams|default:"?" }}g</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6>Location</h6>
|
||||
{% if filament.is_loaded_in_ams %}
|
||||
<span class="badge bg-success fs-6">AMS Tray {{ filament.current_tray_id }}</span>
|
||||
<p class="mb-0 mt-2"><small>Loaded: {{ filament.last_loaded_date|date:"Y-m-d H:i" }}</small></p>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary fs-6">Storage</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Chart -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<strong>Chart Filters</strong>
|
||||
<span class="text-muted" id="filamentDateRange">(Last 24 Hours)</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<!-- Date Range -->
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<label class="form-label mb-0 small text-body-secondary">From:</label>
|
||||
<input type="date" class="form-control form-control-sm" id="filamentStartDate" style="width: auto;">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<label class="form-label mb-0 small text-body-secondary">To:</label>
|
||||
<input type="date" class="form-control form-control-sm" id="filamentEndDate" style="width: auto;">
|
||||
</div>
|
||||
<!-- Full Day Checkbox -->
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="filamentFullDayCheckbox" checked>
|
||||
<label class="form-check-label small" for="filamentFullDayCheckbox">Full Day</label>
|
||||
</div>
|
||||
<!-- Time Range -->
|
||||
<div class="d-flex align-items-center gap-1" id="filamentTimeRangeControls">
|
||||
<label class="form-label mb-0 small text-body-secondary">Time:</label>
|
||||
<select class="form-select form-select-sm" id="filamentStartTime" style="width: auto;" disabled></select>
|
||||
<span class="text-body-secondary">-</span>
|
||||
<select class="form-select form-select-sm" id="filamentEndTime" style="width: auto;" disabled></select>
|
||||
</div>
|
||||
<!-- Buttons -->
|
||||
<button type="button" class="btn btn-primary btn-sm" id="refreshFilamentChart">
|
||||
<svg class="icon"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-reload"></use></svg>
|
||||
Refresh
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="resetFilamentChart">
|
||||
<svg class="icon"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-action-undo"></use></svg>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container" style="height: 300px;">
|
||||
<canvas id="usageChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Print Jobs -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5>Print Jobs Using This Filament</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if print_usages %}
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Project</th>
|
||||
<th>Date</th>
|
||||
<th>Tray</th>
|
||||
<th>Consumed</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for usage in print_usages %}
|
||||
<tr>
|
||||
<td>{{ usage.print_job.project_name }}</td>
|
||||
<td>{{ usage.print_job.start_time|date:"Y-m-d H:i" }}</td>
|
||||
<td>Tray {{ usage.tray_id }}</td>
|
||||
<td>{{ usage.consumed_percent|default:"?" }}% ({{ usage.consumed_grams|default:"?" }}g)</td>
|
||||
<td><span class="badge bg-{% if usage.print_job.final_status == 'FINISH' %}success{% else %}danger{% endif %}">{{ usage.print_job.final_status }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">No print jobs recorded yet</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Purchase Info -->
|
||||
{% if filament.purchase_date or filament.purchase_price or filament.supplier %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5>Purchase Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% if filament.purchase_date %}
|
||||
<div class="col-md-4">
|
||||
<strong>Purchase Date:</strong> {{ filament.purchase_date|date:"Y-m-d" }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if filament.purchase_price %}
|
||||
<div class="col-md-4">
|
||||
<strong>Price:</strong> ${{ filament.purchase_price }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if filament.supplier %}
|
||||
<div class="col-md-4">
|
||||
<strong>Supplier:</strong> {{ filament.supplier }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if filament.notes %}
|
||||
<hr>
|
||||
<strong>Notes:</strong>
|
||||
<p>{{ filament.notes }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script>
|
||||
<script>
|
||||
const filamentId = {{ filament.pk }};
|
||||
let usageChart = null;
|
||||
|
||||
// Populate time selects
|
||||
const startTimeSelect = document.getElementById('filamentStartTime');
|
||||
const endTimeSelect = document.getElementById('filamentEndTime');
|
||||
for (let h = 0; h < 24; h++) {
|
||||
for (let m = 0; m < 60; m += 30) {
|
||||
const timeStr = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
|
||||
startTimeSelect.add(new Option(timeStr, timeStr));
|
||||
endTimeSelect.add(new Option(timeStr, timeStr));
|
||||
}
|
||||
}
|
||||
startTimeSelect.value = '00:00';
|
||||
endTimeSelect.value = '23:30';
|
||||
|
||||
// Initialize date inputs to last 24 hours
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
document.getElementById('filamentStartDate').value = yesterday.toISOString().split('T')[0];
|
||||
document.getElementById('filamentEndDate').value = now.toISOString().split('T')[0];
|
||||
|
||||
// Full day checkbox handler
|
||||
document.getElementById('filamentFullDayCheckbox').addEventListener('change', function() {
|
||||
const isFullDay = this.checked;
|
||||
startTimeSelect.disabled = isFullDay;
|
||||
endTimeSelect.disabled = isFullDay;
|
||||
});
|
||||
|
||||
// Fetch and render chart
|
||||
async function fetchFilamentUsageData() {
|
||||
const startDate = document.getElementById('filamentStartDate').value;
|
||||
const endDate = document.getElementById('filamentEndDate').value;
|
||||
const isFullDay = document.getElementById('filamentFullDayCheckbox').checked;
|
||||
const startTime = isFullDay ? '00:00' : startTimeSelect.value;
|
||||
const endTime = isFullDay ? '23:59' : endTimeSelect.value;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (startDate) params.append('start_date', startDate);
|
||||
if (endDate) params.append('end_date', endDate);
|
||||
if (startTime) params.append('start_time', startTime);
|
||||
if (endTime) params.append('end_time', endTime);
|
||||
|
||||
try {
|
||||
const response = await fetch(`{% url 'bambu_run:filament_usage_api' filament.pk %}?${params.toString()}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Update date range display
|
||||
const dateRangeSpan = document.getElementById('filamentDateRange');
|
||||
if (startDate && endDate) {
|
||||
dateRangeSpan.textContent = `(${startDate} to ${endDate})`;
|
||||
} else {
|
||||
dateRangeSpan.textContent = '(Last 24 Hours)';
|
||||
}
|
||||
|
||||
// Update chart
|
||||
if (usageChart) {
|
||||
usageChart.data.labels = data.timestamps;
|
||||
usageChart.data.datasets[0].data = data.remaining;
|
||||
usageChart.update();
|
||||
} else {
|
||||
const ctx = document.getElementById('usageChart').getContext('2d');
|
||||
usageChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: data.timestamps,
|
||||
datasets: [{
|
||||
label: 'Remaining %',
|
||||
data: data.remaining,
|
||||
borderColor: 'rgb(75, 192, 192)',
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
||||
tension: 0.3,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching filament usage data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('refreshFilamentChart').addEventListener('click', fetchFilamentUsageData);
|
||||
document.getElementById('resetFilamentChart').addEventListener('click', function() {
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
document.getElementById('filamentStartDate').value = yesterday.toISOString().split('T')[0];
|
||||
document.getElementById('filamentEndDate').value = now.toISOString().split('T')[0];
|
||||
document.getElementById('filamentFullDayCheckbox').checked = true;
|
||||
startTimeSelect.disabled = true;
|
||||
endTimeSelect.disabled = true;
|
||||
fetchFilamentUsageData();
|
||||
});
|
||||
|
||||
// Initial load
|
||||
fetchFilamentUsageData();
|
||||
</script>
|
||||
{% endblock %}
|
||||
303
bambu_run/templates/bambu_run/filament_form.html
Normal file
303
bambu_run/templates/bambu_run/filament_form.html
Normal file
@@ -0,0 +1,303 @@
|
||||
{% extends bambu_run_base_template %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Filament Spool</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<h5>Identification</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Spool Serial Number (SN)</label>
|
||||
{{ form.tray_uuid }}
|
||||
<small class="form-text text-muted">Auto-filled from MQTT tray_uuid</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">RFID Chip ID (tag_uid)</label>
|
||||
{{ form.tag_uid }}
|
||||
<small class="form-text text-muted">Auto-filled from MQTT RFID</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Custom Tag ID (Optional)</label>
|
||||
{{ form.tag_id }}
|
||||
<small class="form-text text-muted">User-defined barcode/label</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Created By</label>
|
||||
{{ form.created_by }}
|
||||
<small class="form-text text-muted">How this filament was added</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h5>Specifications</h5>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Type *</label>
|
||||
{{ form.type }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Sub Type</label>
|
||||
{{ form.sub_type }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Brand *</label>
|
||||
{{ form.brand }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Color *</label>
|
||||
{{ form.color }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Color Picker</label>
|
||||
{{ form.color_hex }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">{{ form.color_hex_text.label }}</label>
|
||||
{{ form.color_hex_text }}
|
||||
<small class="form-text text-muted">e.g. #0A2CA5</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Diameter (mm)</label>
|
||||
{{ form.diameter }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Initial Weight (g)</label>
|
||||
{{ form.initial_weight_grams }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h5>Current Status</h5>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Remaining %</label>
|
||||
{{ form.remaining_percent }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Remaining Weight (g)</label>
|
||||
{{ form.remaining_weight_grams }}
|
||||
<small class="form-text text-muted">Auto-calculated</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="form-check">
|
||||
{{ form.is_loaded_in_ams }}
|
||||
<label class="form-check-label">Loaded in AMS</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">AMS Tray ID (0-3)</label>
|
||||
{{ form.current_tray_id }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<h5>Purchase Info (Optional)</h5>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Purchase Date</label>
|
||||
{{ form.purchase_date }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Price</label>
|
||||
{{ form.purchase_price }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Supplier</label>
|
||||
{{ form.supplier }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Notes</label>
|
||||
{{ form.notes }}
|
||||
</div>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Please correct the following errors:</strong>
|
||||
{{ form.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a href="{% url 'bambu_run:filament_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
{% if form.instance.pk %}
|
||||
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal" id="deleteBtn">
|
||||
<i class="bi bi-trash-fill me-1"></i>Delete
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if form.instance.pk %}
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="deleteModalLabel">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>Delete Filament Spool
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="post" action="{% url 'bambu_run:filament_delete' form.instance.pk %}" id="deleteForm">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger mb-3" role="alert">
|
||||
<strong>Warning: This action cannot be undone!</strong>
|
||||
</div>
|
||||
<p>You are about to permanently delete:</p>
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-body">
|
||||
<strong>{{ form.instance }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<p>This will remove:</p>
|
||||
<ul>
|
||||
<li>This filament spool record</li>
|
||||
<li>All associated usage history</li>
|
||||
<li>All filament snapshots</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<label for="deleteConfirmText" class="form-label">
|
||||
To confirm deletion, type <strong class="text-danger">DELETE</strong> in the box below:
|
||||
</label>
|
||||
<input type="text" id="deleteConfirmText" class="form-control form-control-lg" placeholder="Type DELETE to confirm" autocomplete="off">
|
||||
<div class="form-text">Must be in capital letters</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" id="confirmDeleteBtn" class="btn btn-danger" disabled>
|
||||
<i class="bi bi-trash-fill me-1"></i>Confirm Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Sync color picker and text input
|
||||
const colorPicker = document.getElementById('id_color_hex_picker');
|
||||
const colorText = document.getElementById('id_color_hex_text');
|
||||
|
||||
if (colorPicker && colorText) {
|
||||
colorPicker.addEventListener('input', function() {
|
||||
colorText.value = this.value.toUpperCase();
|
||||
});
|
||||
|
||||
colorText.addEventListener('input', function() {
|
||||
const value = this.value.trim();
|
||||
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
|
||||
colorPicker.value = value;
|
||||
this.classList.remove('is-invalid');
|
||||
} else if (value.length === 7) {
|
||||
this.classList.add('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
if (colorText.value && /^#[0-9A-Fa-f]{6}$/.test(colorText.value)) {
|
||||
colorPicker.value = colorText.value;
|
||||
} else if (colorPicker.value && !colorText.value) {
|
||||
colorText.value = colorPicker.value.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete confirmation logic
|
||||
const deleteConfirmText = document.getElementById('deleteConfirmText');
|
||||
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
|
||||
const deleteForm = document.getElementById('deleteForm');
|
||||
const deleteModal = document.getElementById('deleteModal');
|
||||
|
||||
if (deleteConfirmText && confirmDeleteBtn) {
|
||||
deleteConfirmText.addEventListener('input', function() {
|
||||
const value = this.value.trim();
|
||||
if (value === 'DELETE') {
|
||||
confirmDeleteBtn.disabled = false;
|
||||
this.classList.remove('is-invalid');
|
||||
this.classList.add('is-valid');
|
||||
} else {
|
||||
confirmDeleteBtn.disabled = true;
|
||||
this.classList.remove('is-valid');
|
||||
if (value.length > 0) {
|
||||
this.classList.add('is-invalid');
|
||||
} else {
|
||||
this.classList.remove('is-invalid');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (deleteForm) {
|
||||
deleteForm.addEventListener('submit', function(e) {
|
||||
if (confirmDeleteBtn.disabled) {
|
||||
e.preventDefault();
|
||||
alert('Please type DELETE to confirm deletion');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (deleteModal) {
|
||||
deleteModal.addEventListener('hidden.bs.modal', function() {
|
||||
deleteConfirmText.value = '';
|
||||
confirmDeleteBtn.disabled = true;
|
||||
deleteConfirmText.classList.remove('is-valid', 'is-invalid');
|
||||
});
|
||||
|
||||
deleteModal.addEventListener('shown.bs.modal', function() {
|
||||
deleteConfirmText.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Backup modal opener
|
||||
const deleteBtn = document.getElementById('deleteBtn');
|
||||
if (deleteBtn && deleteModal) {
|
||||
deleteBtn.addEventListener('click', function() {
|
||||
if (!deleteModal.classList.contains('show')) {
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
const modalInstance = bootstrap.Modal.getOrCreateInstance(deleteModal);
|
||||
modalInstance.show();
|
||||
} else if (typeof coreui !== 'undefined' && coreui.Modal) {
|
||||
const modalInstance = coreui.Modal.getOrCreateInstance(deleteModal);
|
||||
modalInstance.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
206
bambu_run/templates/bambu_run/filament_list.html
Normal file
206
bambu_run/templates/bambu_run/filament_list.html
Normal file
@@ -0,0 +1,206 @@
|
||||
{% 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>
|
||||
<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>
|
||||
</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-3">
|
||||
<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-3">
|
||||
<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">
|
||||
<div style="width: 30px; height: 30px; background-color: {{ filament.color_hex|default:'#999' }}; border-radius: 4px; margin-right: 10px; border: 1px solid #ddd;"></div>
|
||||
{{ 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">AMS 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>
|
||||
<a href="{% url 'bambu_run:filament_update' filament.pk %}" class="btn btn-sm btn-warning">Edit</a>
|
||||
</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 %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends bambu_run_base_template %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1>Delete Filament Type</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning">
|
||||
<h5><i class="bi bi-exclamation-triangle"></i> Warning</h5>
|
||||
<p>Are you sure you want to delete this filament type?</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5>Type Details:</h5>
|
||||
<p><strong>Type:</strong> {{ object.type }}</p>
|
||||
<p><strong>Sub Type:</strong> {{ object.sub_type|default:"-" }}</p>
|
||||
<p><strong>Brand:</strong> {{ object.brand }}</p>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'bambu_run:filament_type_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash"></i> Yes, Delete Type
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
92
bambu_run/templates/bambu_run/filament_type_form.html
Normal file
92
bambu_run/templates/bambu_run/filament_type_form.html
Normal file
@@ -0,0 +1,92 @@
|
||||
{% extends bambu_run_base_template %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Filament Type</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Type *</label>
|
||||
<div class="input-group">
|
||||
{{ form.type }}
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-coreui-toggle="dropdown" aria-expanded="false"></button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" id="type-dropdown"></ul>
|
||||
</div>
|
||||
<small class="form-text text-muted">Base material: PLA, PETG, ABS, etc.</small>
|
||||
{% if form.type.errors %}
|
||||
<div class="text-danger">{{ form.type.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Sub Type</label>
|
||||
<div class="input-group">
|
||||
{{ form.sub_type }}
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-coreui-toggle="dropdown" aria-expanded="false"></button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" id="sub-type-dropdown"></ul>
|
||||
</div>
|
||||
<small class="form-text text-muted">Optional: PLA Basic, PLA Matte, etc.</small>
|
||||
{% if form.sub_type.errors %}
|
||||
<div class="text-danger">{{ form.sub_type.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Brand *</label>
|
||||
<div class="input-group">
|
||||
{{ form.brand }}
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
|
||||
data-coreui-toggle="dropdown" aria-expanded="false"></button>
|
||||
<ul class="dropdown-menu dropdown-menu-end" id="brand-dropdown"></ul>
|
||||
</div>
|
||||
{% if form.brand.errors %}
|
||||
<div class="text-danger">{{ form.brand.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'bambu_run:filament_type_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{% if form.instance.pk %}Update Type{% else %}Add Type{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger mt-3">
|
||||
<strong>Please correct the following errors:</strong>
|
||||
<ul>
|
||||
{% for field, errors in form.errors.items %}
|
||||
{% for error in errors %}
|
||||
<li>{{ field }}: {{ error }}</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ existing_types|json_script:"existing-types" }}
|
||||
{{ existing_sub_types|json_script:"existing-sub-types" }}
|
||||
{{ existing_brands|json_script:"existing-brands" }}
|
||||
{{ preset_types|json_script:"preset-types" }}
|
||||
{{ preset_sub_types|json_script:"preset-sub-types" }}
|
||||
{{ preset_brands|json_script:"preset-brands" }}
|
||||
<script src="{% static 'bambu_run/js/filament_type_form.js' %}"></script>
|
||||
{% endblock %}
|
||||
99
bambu_run/templates/bambu_run/filament_type_list.html
Normal file
99
bambu_run/templates/bambu_run/filament_type_list.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% extends bambu_run_base_template %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<h1>Filament Type Registry</h1>
|
||||
<p class="text-muted">Manage filament types (material, sub-type, brand)</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<a href="{% url 'bambu_run:filament_type_create' %}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Add New Type
|
||||
</a>
|
||||
<a href="{% url 'bambu_run:filament_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Inventory
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Card -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Summary</h5>
|
||||
<p class="card-text">
|
||||
<strong>Total Types:</strong> {{ total_types }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Type List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="align-middle">Type</th>
|
||||
<th class="align-middle">Sub Type</th>
|
||||
<th class="align-middle">Brand</th>
|
||||
<th class="align-middle">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ft in types %}
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
<span class="badge bg-secondary">{{ ft.type }}</span>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
{% if ft.sub_type %}
|
||||
<span class="badge bg-info">{{ ft.sub_type }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="align-middle">{{ ft.brand }}</td>
|
||||
<td class="align-middle">
|
||||
<a href="{% url 'bambu_run:filament_type_update' ft.pk %}" class="btn btn-sm btn-warning">Edit</a>
|
||||
<a href="{% url 'bambu_run:filament_type_delete' ft.pk %}" class="btn btn-sm btn-danger">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted">
|
||||
No filament types found. <a href="{% url 'bambu_run:filament_type_create' %}">Add your first type!</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 %}
|
||||
390
bambu_run/templates/bambu_run/printer_dashboard.html
Normal file
390
bambu_run/templates/bambu_run/printer_dashboard.html
Normal file
@@ -0,0 +1,390 @@
|
||||
{% 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>3D Printer Dashboard</h1>
|
||||
<p class="text-body-secondary">
|
||||
Real-time monitoring for {{ device_name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{ error }}</div>
|
||||
{% else %}
|
||||
|
||||
<!-- Summary Cards Row -->
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Nozzle Temperature Card -->
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card infra-card-warning">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="stat-label">Nozzle Temp</div>
|
||||
<div class="stat-value">{{ stats.nozzle_temp|floatformat:1 }}°C</div>
|
||||
</div>
|
||||
<i class="bi bi-thermometer-high" style="font-size: 2rem; opacity: 0.3;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bed Temperature Card -->
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card infra-card-danger">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="stat-label">Bed Temp</div>
|
||||
<div class="stat-value">{{ stats.bed_temp|floatformat:1 }}°C</div>
|
||||
</div>
|
||||
<i class="bi bi-thermometer-half" style="font-size: 2rem; opacity: 0.3;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Print Progress Card -->
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card infra-card-info">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="stat-label">Print Progress</div>
|
||||
<div class="stat-value">{{ stats.print_percent }}%</div>
|
||||
</div>
|
||||
<i class="bi bi-pie-chart-fill" style="font-size: 2rem; opacity: 0.3;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chamber Light Card -->
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card {% if stats.chamber_light == 'on' %}infra-card-success{% else %}infra-card-secondary{% endif %}">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<div class="stat-label">Chamber Light</div>
|
||||
<div class="stat-value">{{ stats.chamber_light|upper }}</div>
|
||||
</div>
|
||||
<i class="bi bi-lightbulb-fill" style="font-size: 2rem; opacity: 0.3;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Print Job Info -->
|
||||
{% if stats.subtask_name and stats.subtask_name != 'No active print' %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Current Print Job</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<strong>Job Name:</strong> {{ stats.subtask_name }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>State:</strong> {{ stats.gcode_state }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Progress:</strong> {{ stats.print_percent }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- AMS Status Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>AMS Status</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<strong>Temperature:</strong>
|
||||
{% if stats.ams_temp %}
|
||||
{{ stats.ams_temp|floatformat:1 }}°C
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>Humidity:</strong>
|
||||
{% if stats.ams_humidity %}
|
||||
{{ stats.ams_humidity }}%
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filaments Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Filaments</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if stats.filaments %}
|
||||
<div class="row g-3">
|
||||
{% for filament in stats.filaments %}
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card filament-card" data-filament-color="{{ filament.color|slice:':6' }}">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h6 class="mb-0">Tray {{ filament.tray_id }}</h6>
|
||||
{% if filament.filament_pk %}
|
||||
<a href="{% url 'bambu_run:filament_detail' filament.filament_pk %}" class="text-decoration-none" title="View in inventory">
|
||||
<svg class="icon icon-sm text-body-secondary"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-external-link"></use></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="mb-1 small"><strong>{{ filament.type }}</strong> - {{ filament.brand }}</p>
|
||||
{% if filament.color_name %}<p class="mb-1 small text-body-secondary">{{ filament.color_name }}</p>{% endif %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="small">Remaining</span>
|
||||
<span class="badge filament-badge">{{ filament.remain_percent }}%</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 10px; background-color: rgba(0,0,0,0.1);">
|
||||
<div class="progress-bar filament-progress" role="progressbar" style="width: {{ filament.remain_percent }}%;" aria-valuenow="{{ filament.remain_percent }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if stats.external_spool.type %}
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card filament-card" data-filament-color="{{ stats.external_spool.color|slice:':6' }}">
|
||||
<div class="card-body">
|
||||
<h6 class="mb-2">External Spool</h6>
|
||||
<p class="mb-1 small"><strong>{{ stats.external_spool.type }}</strong> - External</p>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="small">Remaining</span>
|
||||
<span class="badge filament-badge">{{ stats.external_spool.remain }}%</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 10px; background-color: rgba(0,0,0,0.1);">
|
||||
<div class="progress-bar filament-progress" role="progressbar" style="width: {{ stats.external_spool.remain }}%;" aria-valuenow="{{ stats.external_spool.remain }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-body-secondary">No filament data available</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date/Time Filter Controls -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<strong>Chart Filters</strong>
|
||||
<span class="text-muted" id="printerDateRange">(Last 24 Hours)</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<!-- Date Range -->
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<label class="form-label mb-0 small text-body-secondary">From:</label>
|
||||
<input type="date" class="form-control form-control-sm" id="printerStartDate" style="width: auto;">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<label class="form-label mb-0 small text-body-secondary">To:</label>
|
||||
<input type="date" class="form-control form-control-sm" id="printerEndDate" style="width: auto;">
|
||||
</div>
|
||||
<!-- Full Day Checkbox -->
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="printerFullDayCheckbox" checked>
|
||||
<label class="form-check-label small" for="printerFullDayCheckbox">Full Day</label>
|
||||
</div>
|
||||
<!-- Time Range -->
|
||||
<div class="d-flex align-items-center gap-1" id="printerTimeRangeControls">
|
||||
<label class="form-label mb-0 small text-body-secondary">Time:</label>
|
||||
<select class="form-select form-select-sm" id="printerStartTime" style="width: auto;" disabled></select>
|
||||
<span class="text-body-secondary">-</span>
|
||||
<select class="form-select form-select-sm" id="printerEndTime" style="width: auto;" disabled></select>
|
||||
</div>
|
||||
<!-- Buttons -->
|
||||
<button type="button" class="btn btn-primary btn-sm" id="refreshPrinterCharts">
|
||||
<svg class="icon"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-reload"></use></svg>
|
||||
Refresh
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="resetPrinterCharts">
|
||||
<svg class="icon"><use xlink:href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/sprites/free.svg#cil-action-undo"></use></svg>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filament Timeline Chart - Full Width -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">Filament Remaining Timeline</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="filamentTimelineChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Section -->
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Nozzle Temperature Chart -->
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">Nozzle Temperature</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="nozzleTempChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bed Temperature Chart -->
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">Bed Temperature</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="bedTempChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Print Progress Chart -->
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">Print Progress</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="printProgressChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fan Speeds Chart -->
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">Fan Speeds</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="fanSpeedsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- WiFi Signal Chart -->
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">WiFi Signal Strength</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="wifiSignalChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AMS Conditions Chart -->
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">AMS Conditions</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="amsConditionsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Layer Progress Chart -->
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">Layer Progress</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container">
|
||||
<canvas id="layerProgressChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<p class="text-body-secondary text-end">
|
||||
Last updated: {{ stats.timestamp }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3.0.1"></script>
|
||||
<script src="{% static 'bambu_run/js/printer_charts.js' %}"></script>
|
||||
<script src="{% static 'bambu_run/js/printer_charts_control.js' %}"></script>
|
||||
<div id="printerApiUrl" data-url="{% url 'bambu_run:printer_api' %}" style="display: none;"></div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const printerData = {{ printer_data_json|safe }};
|
||||
const apiUrl = '{% url "bambu_run:printer_api" %}';
|
||||
initPrinterCharts(printerData, apiUrl);
|
||||
|
||||
// Add project markers if they exist
|
||||
if (printerData.project_markers && printerData.project_markers.length > 0) {
|
||||
setTimeout(function() {
|
||||
addProjectMarkersToCharts(printerData.project_markers, printerData.timestamps);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user