update: added assets manager and extra nvs settings on cloudflash

This commit is contained in:
2026-03-19 11:11:29 +02:00
parent d0ac4f1d91
commit 29bbaead86
17 changed files with 2369 additions and 446 deletions

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { useParams, useNavigate } from "react-router-dom";
import QRCode from "qrcode";
import { MapContainer, TileLayer, Marker } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
@@ -198,6 +199,32 @@ function LogLevelSelect({ value, onChange }) {
);
}
function QrModal({ value, onClose }) {
const canvasRef = useRef(null);
useEffect(() => {
if (!value || !canvasRef.current) return;
QRCode.toCanvas(canvasRef.current, value, { width: 220, margin: 2, color: { dark: "#e3e5ea", light: "#1f2937" } });
}, [value]);
useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose(); };
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [onClose]);
if (!value) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center" style={{ backgroundColor: "rgba(0,0,0,0.7)" }}
onClick={onClose}>
<div className="rounded-xl border p-5 shadow-2xl flex flex-col items-center gap-3"
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
onClick={(e) => e.stopPropagation()}>
<p className="text-xs font-mono" style={{ color: "var(--text-muted)" }}>{value}</p>
<canvas ref={canvasRef} style={{ borderRadius: 8 }} />
<p className="text-xs" style={{ color: "var(--text-muted)" }}>Click outside or press ESC to close</p>
</div>
</div>
);
}
function SectionModal({ open, title, onCancel, onSave, saving, disabled, size = "max-w-lg", children }) {
if (!open) return null;
return (
@@ -1151,6 +1178,12 @@ const LOG_LEVEL_STYLES = {
function parseFirestoreDate(str) {
if (!str) return null;
// Handle Firestore Timestamp objects serialised as {_seconds, _nanoseconds} or {seconds, nanoseconds}
if (typeof str === "object") {
const secs = str._seconds ?? str.seconds;
if (typeof secs === "number") return new Date(secs * 1000);
return null;
}
const cleaned = str.replace(" at ", " ").replace("UTC+0000", "UTC").replace(/UTC\+(\d{4})/, "UTC");
const d = new Date(cleaned);
return isNaN(d.getTime()) ? null : d;
@@ -1711,6 +1744,7 @@ export default function DeviceDetail() {
const [editingBacklight, setEditingBacklight] = useState(false);
const [editingSubscription, setEditingSubscription] = useState(false);
const [editingWarranty, setEditingWarranty] = useState(false);
const [qrTarget, setQrTarget] = useState(null);
useEffect(() => {
loadData();
@@ -2201,7 +2235,23 @@ export default function DeviceDetail() {
<div className="db-row">
<div className="db-info-field">
<span className="db-info-label">SERIAL NUMBER</span>
<span className="db-info-value">{device.serial_number || device.device_id || "-"}</span>
<span className="db-info-value" style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
{device.serial_number || device.device_id || "-"}
{(device.serial_number || device.device_id) && (
<button
type="button"
title="Show QR Code"
onClick={() => setQrTarget(device.serial_number || device.device_id)}
className="cursor-pointer hover:opacity-70 transition-opacity"
style={{ color: "var(--text-muted)", background: "none", border: "none", padding: 0, lineHeight: 1 }}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
<path d="M14 14h.01M14 18h.01M18 14h.01M18 18h.01M21 14h.01M21 21h.01M14 21h.01"/>
</svg>
</button>
)}
</span>
</div>
</div>
<div className="db-row">
@@ -3893,6 +3943,7 @@ export default function DeviceDetail() {
stats={stats}
id={id}
/>
<QrModal value={qrTarget} onClose={() => setQrTarget(null)} />
</div>
);
}