|
@@ -556,7 +556,10 @@ async def _collect_support_info() -> dict:
|
|
|
except Exception:
|
|
except Exception:
|
|
|
logger.debug("Failed to collect virtual printer info", exc_info=True)
|
|
logger.debug("Failed to collect virtual printer info", exc_info=True)
|
|
|
|
|
|
|
|
- # Non-sensitive settings
|
|
|
|
|
|
|
+ # All settings — sensitive values are redacted rather than dropped so
|
|
|
|
|
+ # new settings automatically show up in support bundles without a code
|
|
|
|
|
+ # change. The value is replaced with "[REDACTED]" but the key is kept
|
|
|
|
|
+ # so we can still see which integrations are configured.
|
|
|
result = await db.execute(select(Settings))
|
|
result = await db.execute(select(Settings))
|
|
|
all_settings = result.scalars().all()
|
|
all_settings = result.scalars().all()
|
|
|
sensitive_keys = {
|
|
sensitive_keys = {
|
|
@@ -578,12 +581,16 @@ async def _collect_support_info() -> dict:
|
|
|
"path", # Filesystem paths may contain usernames
|
|
"path", # Filesystem paths may contain usernames
|
|
|
"config", # URLs may contain IPs, configs may have embedded secrets
|
|
"config", # URLs may contain IPs, configs may have embedded secrets
|
|
|
"_ip", # IP address fields (e.g. virtual_printer_remote_interface_ip)
|
|
"_ip", # IP address fields (e.g. virtual_printer_remote_interface_ip)
|
|
|
|
|
+ "host",
|
|
|
|
|
+ "credential",
|
|
|
}
|
|
}
|
|
|
for s in all_settings:
|
|
for s in all_settings:
|
|
|
- # Skip sensitive settings
|
|
|
|
|
- if any(sensitive in s.key.lower() for sensitive in sensitive_keys):
|
|
|
|
|
- continue
|
|
|
|
|
- info["settings"][s.key] = s.value
|
|
|
|
|
|
|
+ key_lower = s.key.lower()
|
|
|
|
|
+ if any(sensitive in key_lower for sensitive in sensitive_keys):
|
|
|
|
|
+ # Preserve shape: mark presence without leaking the value
|
|
|
|
|
+ info["settings"][s.key] = "[REDACTED]" if s.value else ""
|
|
|
|
|
+ else:
|
|
|
|
|
+ info["settings"][s.key] = s.value
|
|
|
|
|
|
|
|
# Notification providers (anonymized — type/enabled/error status only)
|
|
# Notification providers (anonymized — type/enabled/error status only)
|
|
|
try:
|
|
try:
|
|
@@ -668,6 +675,44 @@ async def _collect_support_info() -> dict:
|
|
|
except Exception:
|
|
except Exception:
|
|
|
logger.debug("Failed to collect MQTT relay info", exc_info=True)
|
|
logger.debug("Failed to collect MQTT relay info", exc_info=True)
|
|
|
|
|
|
|
|
|
|
+ # SpoolBuddy devices (anonymized — no hostnames, IPs or device IDs)
|
|
|
|
|
+ try:
|
|
|
|
|
+ async with async_session() as db:
|
|
|
|
|
+ from backend.app.models.spoolbuddy_device import SpoolBuddyDevice
|
|
|
|
|
+
|
|
|
|
|
+ result = await db.execute(select(SpoolBuddyDevice))
|
|
|
|
|
+ devices = result.scalars().all()
|
|
|
|
|
+ info["integrations"]["spoolbuddy"] = {
|
|
|
|
|
+ "device_count": len(devices),
|
|
|
|
|
+ "online_count": sum(
|
|
|
|
|
+ 1
|
|
|
|
|
+ for d in devices
|
|
|
|
|
+ if d.last_seen
|
|
|
|
|
+ and (datetime.now(tz=timezone.utc) - d.last_seen.replace(tzinfo=timezone.utc)).total_seconds() < 30
|
|
|
|
|
+ ),
|
|
|
|
|
+ "devices": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "index": i + 1,
|
|
|
|
|
+ "firmware_version": d.firmware_version,
|
|
|
|
|
+ "has_nfc": d.has_nfc,
|
|
|
|
|
+ "has_scale": d.has_scale,
|
|
|
|
|
+ "nfc_reader_type": d.nfc_reader_type,
|
|
|
|
|
+ "nfc_connection": d.nfc_connection,
|
|
|
|
|
+ "has_backlight": d.has_backlight,
|
|
|
|
|
+ "nfc_ok": d.nfc_ok,
|
|
|
|
|
+ "scale_ok": d.scale_ok,
|
|
|
|
|
+ "uptime_s": d.uptime_s,
|
|
|
|
|
+ "calibration_factor": d.calibration_factor,
|
|
|
|
|
+ "tare_offset": d.tare_offset,
|
|
|
|
|
+ "last_calibrated_at": d.last_calibrated_at.isoformat() if d.last_calibrated_at else None,
|
|
|
|
|
+ "update_status": d.update_status,
|
|
|
|
|
+ }
|
|
|
|
|
+ for i, d in enumerate(devices)
|
|
|
|
|
+ ],
|
|
|
|
|
+ }
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ logger.debug("Failed to collect SpoolBuddy info", exc_info=True)
|
|
|
|
|
+
|
|
|
# Home Assistant (check ha_enabled setting)
|
|
# Home Assistant (check ha_enabled setting)
|
|
|
try:
|
|
try:
|
|
|
info["integrations"]["homeassistant"] = {
|
|
info["integrations"]["homeassistant"] = {
|