Compare commits
12 Commits
6aab42b03e
...
opencv_byp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c395d06db4 | ||
|
|
27844b5b44 | ||
|
|
7af47718e6 | ||
|
|
4eb2a40186 | ||
|
|
d2787c02fd | ||
|
|
1bb9686f7b | ||
|
|
1581739228 | ||
|
|
6330529d03 | ||
|
|
ff8b45b648 | ||
|
|
8ecf2864ef | ||
|
|
5983d80293 | ||
|
|
aee1615d36 |
14
Dockerfile
@@ -10,11 +10,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
supervisor \
|
supervisor \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install bambu-lab-cloud-api without deps (opencv-python is declared but unused at runtime)
|
# Install bambu-lab-cloud-api without deps (opencv-python is declared but unused at runtime).
|
||||||
|
# Then stub out opencv-python so pip's resolver considers it satisfied and won't try to
|
||||||
|
# build it from source (no C compiler, no armv7l wheel available).
|
||||||
RUN pip install --no-cache-dir bambu-lab-cloud-api --no-deps && \
|
RUN pip install --no-cache-dir bambu-lab-cloud-api --no-deps && \
|
||||||
pip install --no-cache-dir paho-mqtt requests flask flask-cors flask-limiter
|
pip install --no-cache-dir paho-mqtt requests flask flask-cors flask-limiter && \
|
||||||
|
python3 -c "import site, pathlib; \
|
||||||
|
d = pathlib.Path(site.getsitepackages()[0]) / 'opencv_python-4.99.0.dist-info'; \
|
||||||
|
d.mkdir(); \
|
||||||
|
(d / 'METADATA').write_text('Metadata-Version: 2.1\nName: opencv-python\nVersion: 4.99.0\n'); \
|
||||||
|
(d / 'INSTALLER').write_text('pip\n'); \
|
||||||
|
(d / 'RECORD').write_text('')"
|
||||||
|
|
||||||
# Install project and remaining dependencies
|
# Install project and remaining dependencies (pip sees opencv-python already satisfied)
|
||||||
COPY pyproject.toml .
|
COPY pyproject.toml .
|
||||||
RUN pip install --no-cache-dir ".[standalone]"
|
RUN pip install --no-cache-dir ".[standalone]"
|
||||||
|
|
||||||
|
|||||||
10
README.md
@@ -125,7 +125,15 @@ This downloads all required software (takes a few minutes the first time).
|
|||||||
|
|
||||||
### Step 5a: First-Time Authentication
|
### Step 5a: First-Time Authentication
|
||||||
|
|
||||||
The first time you connect, Bambu Lab requires email verification. You need to run the collector **interactively** (not in the background) so you can enter the 6-digit code:
|
The first time you connect, Bambu Lab requires email verification. You need to run the collector **interactively** (not in the background) so you can enter the 6-digit code.
|
||||||
|
|
||||||
|
First, set up the database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm bambu-run python standalone/manage.py migrate --noinput
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the collector (this is what triggers Bambu Lab to send the verification email):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose run --rm bambu-run python standalone/manage.py bambu_collector --once
|
docker compose run --rm bambu-run python standalone/manage.py bambu_collector --once
|
||||||
|
|||||||
@@ -687,27 +687,28 @@ class BambuPrinter:
|
|||||||
print("BambuLab Authentication")
|
print("BambuLab Authentication")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print(f"Authenticating as: {self.username}")
|
print(f"Authenticating as: {self.username}")
|
||||||
print("This may require email verification (2FA)...")
|
print()
|
||||||
|
print(">>> ACTION MAY BE REQUIRED <<<")
|
||||||
|
print("Bambu Lab will send a 6-digit verification code to your")
|
||||||
|
print("registered email. Watch this terminal — if a prompt")
|
||||||
|
print(f"appears below, enter the code and press Enter.")
|
||||||
|
print(f"(You have {verification_code_timeout} seconds to respond.)")
|
||||||
|
print("=" * 60)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
auth = BambuAuthenticator()
|
auth = BambuAuthenticator()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self._silent:
|
# Always show stdout during auth — suppress_stdout would hide
|
||||||
with suppress_stdout():
|
# interactive prompts from the library (e.g. verification code input).
|
||||||
token = auth.get_or_create_token(
|
token = auth.get_or_create_token(
|
||||||
username=self.username,
|
username=self.username,
|
||||||
password=self.password
|
password=self.password
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
token = auth.get_or_create_token(
|
|
||||||
username=self.username,
|
|
||||||
password=self.password
|
|
||||||
)
|
|
||||||
|
|
||||||
self._token = token
|
self._token = token
|
||||||
print("Authentication successful!")
|
print("Authentication successful!")
|
||||||
print(f"Token: {token[:20]}...{token[-10:]}")
|
print(f"Token: {token}")
|
||||||
print("=" * 60 + "\n")
|
print("=" * 60 + "\n")
|
||||||
logger.info("BambuLab token obtained successfully")
|
logger.info("BambuLab token obtained successfully")
|
||||||
return token
|
return token
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-data-message {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
/* Card styling */
|
/* Card styling */
|
||||||
.infra-card-warning {
|
.infra-card-warning {
|
||||||
background: linear-gradient(135deg, #ffc107 0%, #ffb300 100%);
|
background: linear-gradient(135deg, #ffc107 0%, #ffb300 100%);
|
||||||
|
|||||||
@@ -4,10 +4,30 @@
|
|||||||
let nozzleTempChart, bedTempChart, printProgressChart, fanSpeedsChart;
|
let nozzleTempChart, bedTempChart, printProgressChart, fanSpeedsChart;
|
||||||
let wifiSignalChart, amsConditionsChart, layerProgressChart, filamentTimelineChart;
|
let wifiSignalChart, amsConditionsChart, layerProgressChart, filamentTimelineChart;
|
||||||
|
|
||||||
|
function showNoDataMessage(canvasId) {
|
||||||
|
const canvas = document.getElementById(canvasId);
|
||||||
|
if (!canvas) return;
|
||||||
|
const container = canvas.closest('.chart-container');
|
||||||
|
if (!container) return;
|
||||||
|
canvas.style.display = 'none';
|
||||||
|
const msg = document.createElement('div');
|
||||||
|
msg.className = 'no-data-message d-flex align-items-center justify-content-center h-100 text-body-secondary';
|
||||||
|
msg.textContent = 'No data available for this period';
|
||||||
|
container.appendChild(msg);
|
||||||
|
}
|
||||||
|
|
||||||
function initPrinterCharts(printerData, apiUrl) {
|
function initPrinterCharts(printerData, apiUrl) {
|
||||||
// Apply filament card colors
|
// Apply filament card colors
|
||||||
applyFilamentColors();
|
applyFilamentColors();
|
||||||
|
|
||||||
|
// If no data, show placeholder messages and exit early
|
||||||
|
if (!printerData.timestamps || printerData.timestamps.length === 0) {
|
||||||
|
['nozzleTempChart', 'bedTempChart', 'printProgressChart', 'fanSpeedsChart',
|
||||||
|
'wifiSignalChart', 'amsConditionsChart', 'layerProgressChart', 'filamentTimelineChart'
|
||||||
|
].forEach(showNoDataMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Register the annotation plugin
|
// Register the annotation plugin
|
||||||
if (typeof Chart !== 'undefined' && typeof ChartAnnotation !== 'undefined') {
|
if (typeof Chart !== 'undefined' && typeof ChartAnnotation !== 'undefined') {
|
||||||
Chart.register(ChartAnnotation);
|
Chart.register(ChartAnnotation);
|
||||||
|
|||||||
1672
bambu_run/static/bambu_run/vendors/coreui-icons-free.svg
vendored
Normal file
|
After Width: | Height: | Size: 410 KiB |
@@ -1,55 +1,101 @@
|
|||||||
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-coreui-theme="dark">
|
<html lang="en" data-coreui-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!-- Apply saved theme immediately to prevent flash -->
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
var t = localStorage.getItem('bambu-run-theme') || 'dark';
|
||||||
|
if (t === 'auto') t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
document.documentElement.setAttribute('data-coreui-theme', t);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<title>{% block title %}Bambu Run{% endblock %}</title>
|
<title>{% block title %}Bambu Run{% endblock %}</title>
|
||||||
<!-- CoreUI 5.3 CSS CDN -->
|
<!-- 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/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">
|
<link href="https://cdn.jsdelivr.net/npm/@coreui/icons@3.0.1/css/all.min.css" rel="stylesheet">
|
||||||
|
<!-- Bootstrap Icons (for bi-vinyl filament icon) -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||||
{% block extra_css %}{% endblock %}
|
{% block extra_css %}{% endblock %}
|
||||||
|
{% block extra_head %}{% endblock %}
|
||||||
<style>
|
<style>
|
||||||
.sidebar-brand { padding: 1rem; font-size: 1.25rem; font-weight: 700; }
|
/* Sidebar brand sizing and padding */
|
||||||
|
.sidebar-brand {
|
||||||
|
padding: 1rem 1rem 1.25rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
min-height: 56px;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
/* Hide brand text when sidebar is narrow */
|
||||||
|
.sidebar-narrow-unfoldable:not(:hover) .sidebar-brand-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
/* Gap between brand icon and text */
|
||||||
|
.sidebar-brand img + .sidebar-brand-text {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
/* Sidebar collapse layout — standalone only */
|
||||||
|
.wrapper { transition: margin-left 0.15s ease-out; }
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.sidebar ~ .wrapper { margin-left: 256px; }
|
||||||
|
.sidebar.sidebar-narrow ~ .wrapper,
|
||||||
|
.sidebar.sidebar-narrow-unfoldable ~ .wrapper { margin-left: 56px; }
|
||||||
|
}
|
||||||
|
@media (max-width: 991.98px) {
|
||||||
|
.sidebar ~ .wrapper { margin-left: 0; }
|
||||||
|
}
|
||||||
|
/* Theme toggle icon visibility — driven by data-coreui-theme on <html> */
|
||||||
|
[data-coreui-theme="dark"] .theme-icon-light { display: none; }
|
||||||
|
[data-coreui-theme="light"] .theme-icon-dark { display: none; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="sidebar sidebar-dark sidebar-fixed" id="sidebar">
|
<div class="sidebar sidebar-dark sidebar-fixed" id="sidebar">
|
||||||
<div class="sidebar-brand d-none d-md-flex">
|
<div class="sidebar-brand d-none d-md-flex">
|
||||||
Bambu Run
|
{% block sidebar_brand_icon %}{% endblock %}
|
||||||
|
<span class="sidebar-brand-text">Bambu Run</span>
|
||||||
</div>
|
</div>
|
||||||
<ul class="sidebar-nav" data-coreui="navigation">
|
<ul class="sidebar-nav" data-coreui="navigation">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'bambu_run:printer_dashboard' %}">
|
<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>
|
<svg class="nav-icon"><use href="{% static 'bambu_run/vendors/coreui-icons-free.svg' %}#cil-expand-down"></use></svg>
|
||||||
3D Printer
|
3D Printer
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'bambu_run:filament_list' %}">
|
<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>
|
<i class="nav-icon bi bi-vinyl"></i>
|
||||||
Filament Inventory
|
Filament Inventory
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="sidebar-footer border-top d-flex">
|
||||||
|
<button class="sidebar-toggler" type="button"></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wrapper d-flex flex-column min-vh-100">
|
<div class="wrapper d-flex flex-column min-vh-100">
|
||||||
<header class="header header-sticky p-0 mb-4">
|
<header class="header header-sticky p-0 mb-4">
|
||||||
<div class="container-fluid px-4">
|
<div class="container-fluid px-4">
|
||||||
<button class="header-toggler" type="button" onclick="document.getElementById('sidebar').classList.toggle('show')">
|
<button class="header-toggler d-lg-none" type="button"
|
||||||
<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>
|
onclick="coreui.Sidebar.getInstance(document.querySelector('#sidebar')).toggle()">
|
||||||
|
<svg class="icon icon-lg"><use href="{% static 'bambu_run/vendors/coreui-icons-free.svg' %}#cil-menu"></use></svg>
|
||||||
</button>
|
</button>
|
||||||
<ul class="header-nav ms-auto">
|
<ul class="header-nav ms-auto">
|
||||||
|
{% block theme_toggle %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<button class="nav-link" id="themeToggle" type="button">
|
<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>
|
<svg class="icon icon-lg theme-icon-dark"><use href="{% static 'bambu_run/vendors/coreui-icons-free.svg' %}#cil-moon"></use></svg>
|
||||||
|
<svg class="icon icon-lg theme-icon-light"><use href="{% static 'bambu_run/vendors/coreui-icons-free.svg' %}#cil-sun"></use></svg>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{% if user.is_authenticated %}
|
{% endblock %}
|
||||||
<li class="nav-item">
|
{% block logout_nav %}{% endblock %}
|
||||||
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -62,18 +108,31 @@
|
|||||||
|
|
||||||
<footer class="footer px-4">
|
<footer class="footer px-4">
|
||||||
<div>Bambu Run</div>
|
<div>Bambu Run</div>
|
||||||
<div class="ms-auto">Powered by <a href="https://github.com/runnanli/Bambu-Run">Bambu Run</a></div>
|
<div class="ms-auto">Powered by <a href="https://github.com/RunLit/Bambu-Run.git">Bambu Run</a></div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CoreUI 5.3 JS CDN -->
|
<!-- 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 src="https://cdn.jsdelivr.net/npm/@coreui/coreui@5.3.0/dist/js/coreui.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Theme toggle
|
// Sidebar narrow-toggle with state persistence
|
||||||
|
const sidebarToggler = document.querySelector('.sidebar-toggler');
|
||||||
|
const sidebar = document.querySelector('#sidebar');
|
||||||
|
if (sidebarToggler && sidebar) {
|
||||||
|
if (localStorage.getItem('bambu-run-sidebar-narrow') === 'true') {
|
||||||
|
sidebar.classList.add('sidebar-narrow-unfoldable');
|
||||||
|
}
|
||||||
|
sidebarToggler.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault(); e.stopPropagation();
|
||||||
|
const isNarrow = sidebar.classList.contains('sidebar-narrow-unfoldable');
|
||||||
|
sidebar.classList.toggle('sidebar-narrow-unfoldable', !isNarrow);
|
||||||
|
localStorage.setItem('bambu-run-sidebar-narrow', String(!isNarrow));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Simple 2-state theme toggle (standalone default)
|
||||||
const themeToggle = document.getElementById('themeToggle');
|
const themeToggle = document.getElementById('themeToggle');
|
||||||
const savedTheme = localStorage.getItem('bambu-run-theme') || 'dark';
|
|
||||||
document.documentElement.setAttribute('data-coreui-theme', savedTheme);
|
|
||||||
|
|
||||||
if (themeToggle) {
|
if (themeToggle) {
|
||||||
themeToggle.addEventListener('click', function() {
|
themeToggle.addEventListener('click', function() {
|
||||||
const current = document.documentElement.getAttribute('data-coreui-theme');
|
const current = document.documentElement.getAttribute('data-coreui-theme');
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
<h6 class="mb-0">Tray {{ filament.tray_id }}</h6>
|
<h6 class="mb-0">Tray {{ filament.tray_id }}</h6>
|
||||||
{% if filament.filament_pk %}
|
{% if filament.filament_pk %}
|
||||||
<a href="{% url 'bambu_run:filament_detail' filament.filament_pk %}" class="text-decoration-none" title="View in inventory">
|
<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>
|
<svg class="icon icon-sm text-body-secondary"><use href="{% static 'bambu_run/vendors/coreui-icons-free.svg' %}#cil-external-link"></use></svg>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -235,11 +235,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<button type="button" class="btn btn-primary btn-sm" id="refreshPrinterCharts">
|
<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>
|
<svg class="icon"><use href="{% static 'bambu_run/vendors/coreui-icons-free.svg' %}#cil-reload"></use></svg>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-secondary btn-sm" id="resetPrinterCharts">
|
<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>
|
<svg class="icon"><use href="{% static 'bambu_run/vendors/coreui-icons-free.svg' %}#cil-action-undo"></use></svg>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
docs/BambuRun.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
75
docs/LOCAL_DEBUG_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Setup Local Environment for Debug
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker Desktop running on macOS
|
||||||
|
- Your Bambu Lab account email + password
|
||||||
|
- Bambu-Run source at /Users/runnanli/src/Bambu-Run
|
||||||
|
|
||||||
|
---
|
||||||
|
### Step 1 — Create .env
|
||||||
|
|
||||||
|
Create /Users/runnanli/src/Bambu-Run/.env:
|
||||||
|
BAMBU_USERNAME=your_bambulab_email@example.com
|
||||||
|
BAMBU_PASSWORD=your_bambulab_password
|
||||||
|
TIMEZONE=Australia/Melbourne
|
||||||
|
No DB vars needed — SQLite is the default when DB_NAME is absent.
|
||||||
|
|
||||||
|
---
|
||||||
|
### Step 2 — Build the image
|
||||||
|
|
||||||
|
cd /Users/runnanli/src/Bambu-Run
|
||||||
|
docker compose build
|
||||||
|
Takes a few minutes first time.
|
||||||
|
|
||||||
|
---
|
||||||
|
### Step 3 — Run database migrations
|
||||||
|
|
||||||
|
docker compose run --rm bambu-run python standalone/manage.py migrate --noinput
|
||||||
|
|
||||||
|
---
|
||||||
|
### Step 4 — First-time Bambu Lab authentication (email verification)
|
||||||
|
|
||||||
|
docker compose run --rm bambu-run python standalone/manage.py bambu_collector --once
|
||||||
|
|
||||||
|
You'll be prompted for a 6-digit code sent to your email. Enter it.
|
||||||
|
On success the token is printed:
|
||||||
|
Token: eyJhbGci...
|
||||||
|
|
||||||
|
Add it to .env:
|
||||||
|
BAMBU_TOKEN=eyJhbGci...paste_full_token_here
|
||||||
|
Future restarts will skip email verification.
|
||||||
|
|
||||||
|
---
|
||||||
|
### Step 5 — Start everything
|
||||||
|
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
Supervisord starts three processes: migrate (idempotent), web (gunicorn on :8000), collector (polls printer continuously).
|
||||||
|
|
||||||
|
---
|
||||||
|
### Step 6 — Create a login account
|
||||||
|
|
||||||
|
docker compose exec bambu-run python standalone/manage.py createsuperuser
|
||||||
|
|
||||||
|
---
|
||||||
|
### Step 7 — Open the dashboard
|
||||||
|
|
||||||
|
http://localhost:8000
|
||||||
|
|
||||||
|
---
|
||||||
|
### Useful commands
|
||||||
|
|
||||||
|
#### Watch live logs
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
#### Stop
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
#### Rebuild after code changes
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- SQLite lives inside Docker volume bambu_data — persists across restarts
|
||||||
|
- If charts are blank: printer must be on; give collector ~1 minute to start polling
|
||||||
@@ -38,6 +38,7 @@ dependencies = [
|
|||||||
standalone = [
|
standalone = [
|
||||||
"gunicorn",
|
"gunicorn",
|
||||||
"python-dotenv",
|
"python-dotenv",
|
||||||
|
"whitenoise",
|
||||||
]
|
]
|
||||||
dev = [
|
dev = [
|
||||||
"ruff",
|
"ruff",
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# Ensure the project root (/app) is on sys.path so that both 'standalone'
|
||||||
|
# and 'bambu_run' are importable regardless of where this script is invoked from.
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "standalone.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "standalone.settings")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ SECRET_KEY = os.environ.get(
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = os.environ.get("DEBUG", "True").lower() in ("true", "1", "yes")
|
DEBUG = os.environ.get("DEBUG", "True").lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
@@ -34,6 +34,7 @@ INSTALLED_APPS = [
|
|||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
@@ -90,6 +91,8 @@ USE_TZ = True
|
|||||||
# Static files
|
# Static files
|
||||||
STATIC_URL = "static/"
|
STATIC_URL = "static/"
|
||||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||||
|
STATICFILES_DIRS = [BASE_DIR / "standalone" / "static"]
|
||||||
|
STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
@@ -101,7 +104,7 @@ LOGOUT_REDIRECT_URL = "/accounts/login/"
|
|||||||
|
|
||||||
# Bambu Run settings
|
# Bambu Run settings
|
||||||
BAMBU_RUN_TIMEZONE = os.environ.get("TIMEZONE", "UTC")
|
BAMBU_RUN_TIMEZONE = os.environ.get("TIMEZONE", "UTC")
|
||||||
BAMBU_RUN_BASE_TEMPLATE = "bambu_run/base.html"
|
BAMBU_RUN_BASE_TEMPLATE = "standalone_base.html"
|
||||||
|
|
||||||
# Printer connection — read from environment
|
# Printer connection — read from environment
|
||||||
PRINTER_IP = os.environ.get("PRINTER_IP", "")
|
PRINTER_IP = os.environ.get("PRINTER_IP", "")
|
||||||
|
|||||||
BIN
standalone/static/favicon-128.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
standalone/static/favicon-16.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
standalone/static/favicon-32.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
standalone/static/favicon-48.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
standalone/static/favicon-64.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
standalone/static/favicon_original.png
Normal file
|
After Width: | Height: | Size: 260 KiB |
21
standalone/templates/standalone_base.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends "bambu_run/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="icon" type="image/png" href="{% static 'favicon-32.png' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar_brand_icon %}
|
||||||
|
<img src="{% static 'favicon-64.png' %}" alt="Bambu Run" width="32" height="32" style="flex-shrink:0;">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block logout_nav %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<form method="post" action="{% url 'logout' %}" style="margin:0;">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="nav-link">Logout</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||