Browse Source

Completely removed telemetry

maziggy 4 months ago
parent
commit
bb7e8398bc

+ 0 - 1
backend/app/api/routes/settings.py

@@ -73,7 +73,6 @@ async def get_settings(db: AsyncSession = Depends(get_db)):
                 "capture_finish_photo",
                 "spoolman_enabled",
                 "check_updates",
-                "telemetry_enabled",
                 "virtual_printer_enabled",
                 "ftp_retry_enabled",
                 "mqtt_enabled",

+ 0 - 4
backend/app/main.py

@@ -100,7 +100,6 @@ from backend.app.services.printer_manager import (
 from backend.app.services.smart_plug_manager import smart_plug_manager
 from backend.app.services.spoolman import close_spoolman_client, get_spoolman_client, init_spoolman_client
 from backend.app.services.tasmota import tasmota_service
-from backend.app.services.telemetry import start_telemetry_loop
 
 # Track active prints: {(printer_id, filename): archive_id}
 _active_prints: dict[tuple[int, str], int] = {}
@@ -2071,9 +2070,6 @@ async def lifespan(app: FastAPI):
     # Start printer runtime tracking
     start_runtime_tracking()
 
-    # Start anonymous telemetry (opt-out via settings)
-    asyncio.create_task(start_telemetry_loop(async_session))
-
     # Initialize virtual printer manager
     from backend.app.services.virtual_printer import virtual_printer_manager
 

+ 0 - 4
backend/app/schemas/settings.py

@@ -53,9 +53,6 @@ class AppSettings(BaseModel):
     # Default printer for operations
     default_printer_id: int | None = Field(default=None, description="Default printer ID for uploads, reprints, etc.")
 
-    # Telemetry
-    telemetry_enabled: bool = Field(default=True, description="Send anonymous usage data to help improve BamBuddy")
-
     # Virtual Printer
     virtual_printer_enabled: bool = Field(default=False, description="Enable virtual printer for slicer uploads")
     virtual_printer_access_code: str = Field(default="", description="Access code for virtual printer authentication")
@@ -141,7 +138,6 @@ class AppSettingsUpdate(BaseModel):
     date_format: str | None = None
     time_format: str | None = None
     default_printer_id: int | None = None
-    telemetry_enabled: bool | None = None
     virtual_printer_enabled: bool | None = None
     virtual_printer_access_code: str | None = None
     virtual_printer_mode: str | None = None

+ 0 - 133
backend/app/services/telemetry.py

@@ -1,133 +0,0 @@
-"""Anonymous telemetry service for BamBuddy."""
-
-import asyncio
-import logging
-import uuid
-from datetime import datetime, timedelta
-
-import httpx
-from sqlalchemy import func, select
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from backend.app.core.config import APP_VERSION
-from backend.app.models.printer import Printer
-from backend.app.models.settings import Settings
-
-logger = logging.getLogger(__name__)
-
-# Default telemetry server URL (can be overridden via settings)
-DEFAULT_TELEMETRY_URL = "https://telemetry.bambuddy.cool"
-
-# How often to send heartbeats (once per day)
-HEARTBEAT_INTERVAL = timedelta(hours=24)
-
-_last_heartbeat: datetime | None = None
-
-
-async def get_or_create_installation_id(db: AsyncSession) -> str:
-    """Get existing installation ID or create a new one."""
-    result = await db.execute(select(Settings).where(Settings.key == "installation_id"))
-    setting = result.scalar_one_or_none()
-
-    if setting:
-        return setting.value
-
-    # Generate new UUID
-    installation_id = str(uuid.uuid4())
-
-    # Save to database
-    new_setting = Settings(key="installation_id", value=installation_id)
-    db.add(new_setting)
-    await db.commit()
-
-    logger.info(f"Generated new installation ID: {installation_id[:8]}...")
-    return installation_id
-
-
-async def is_telemetry_enabled(db: AsyncSession) -> bool:
-    """Check if telemetry is enabled (opt-out model)."""
-    result = await db.execute(select(Settings).where(Settings.key == "telemetry_enabled"))
-    setting = result.scalar_one_or_none()
-
-    # Default to enabled (opt-out model)
-    if not setting:
-        return True
-
-    return setting.value.lower() == "true"
-
-
-async def get_telemetry_url(db: AsyncSession) -> str:
-    """Get telemetry server URL from settings."""
-    result = await db.execute(select(Settings).where(Settings.key == "telemetry_url"))
-    setting = result.scalar_one_or_none()
-
-    return setting.value if setting else DEFAULT_TELEMETRY_URL
-
-
-async def get_printer_model_counts(db: AsyncSession) -> dict[str, int]:
-    """Get count of each printer model configured in BamBuddy."""
-    result = await db.execute(select(Printer.model, func.count(Printer.id)).group_by(Printer.model))
-    counts = {}
-    for model, count in result.all():
-        # Normalize model name (handle None/empty)
-        model_name = model if model else "Unknown"
-        counts[model_name] = count
-    return counts
-
-
-async def send_heartbeat(db: AsyncSession) -> bool:
-    """Send anonymous heartbeat to telemetry server."""
-    global _last_heartbeat
-
-    try:
-        # Check if telemetry is enabled
-        if not await is_telemetry_enabled(db):
-            logger.debug("Telemetry disabled, skipping heartbeat")
-            return False
-
-        # Rate limit: only send once per day
-        if _last_heartbeat and datetime.now() - _last_heartbeat < HEARTBEAT_INTERVAL:
-            logger.debug("Heartbeat already sent recently, skipping")
-            return True
-
-        installation_id = await get_or_create_installation_id(db)
-        telemetry_url = await get_telemetry_url(db)
-        printer_models = await get_printer_model_counts(db)
-
-        async with httpx.AsyncClient(timeout=10.0) as client:
-            response = await client.post(
-                f"{telemetry_url}/heartbeat",
-                json={
-                    "installation_id": installation_id,
-                    "version": APP_VERSION,
-                    "printer_models": printer_models,
-                },
-            )
-            response.raise_for_status()
-
-        _last_heartbeat = datetime.now()
-        logger.info(f"Telemetry heartbeat sent to {telemetry_url}")
-        return True
-
-    except httpx.HTTPError as e:
-        logger.debug(f"Telemetry heartbeat failed (network): {e}")
-        return False
-    except Exception as e:
-        logger.debug(f"Telemetry heartbeat failed: {e}")
-        return False
-
-
-async def start_telemetry_loop(get_session):
-    """Background task to send periodic heartbeats."""
-    # Wait a bit before first heartbeat to let app initialize
-    await asyncio.sleep(30)
-
-    while True:
-        try:
-            async with get_session() as db:
-                await send_heartbeat(db)
-        except Exception as e:
-            logger.debug(f"Telemetry loop error: {e}")
-
-        # Check daily
-        await asyncio.sleep(HEARTBEAT_INTERVAL.total_seconds())

+ 0 - 229
backend/tests/unit/services/test_telemetry.py

@@ -1,229 +0,0 @@
-"""Unit tests for Telemetry service.
-
-Tests the anonymous telemetry/stats collection functionality.
-"""
-
-from datetime import datetime, timedelta
-from unittest.mock import AsyncMock, MagicMock, patch
-
-import pytest
-
-from backend.app.models.settings import Settings
-from backend.app.services.telemetry import (
-    DEFAULT_TELEMETRY_URL,
-    HEARTBEAT_INTERVAL,
-    _last_heartbeat,
-    get_or_create_installation_id,
-    get_telemetry_url,
-    is_telemetry_enabled,
-    send_heartbeat,
-)
-
-
-class TestTelemetryService:
-    """Tests for telemetry service functions."""
-
-    # ========================================================================
-    # Installation ID Tests
-    # ========================================================================
-
-    @pytest.mark.asyncio
-    async def test_get_or_create_installation_id_creates_new(self, db_session):
-        """Verify new installation ID is created when none exists."""
-        installation_id = await get_or_create_installation_id(db_session)
-
-        assert installation_id is not None
-        assert len(installation_id) == 36  # UUID format
-        assert "-" in installation_id
-
-    @pytest.mark.asyncio
-    async def test_get_or_create_installation_id_returns_existing(self, db_session):
-        """Verify existing installation ID is returned."""
-        # Create an existing installation ID
-        existing_id = "test-uuid-1234-5678-abcd"
-        setting = Settings(key="installation_id", value=existing_id)
-        db_session.add(setting)
-        await db_session.commit()
-
-        result = await get_or_create_installation_id(db_session)
-
-        assert result == existing_id
-
-    @pytest.mark.asyncio
-    async def test_get_or_create_installation_id_persists(self, db_session):
-        """Verify created installation ID persists in database."""
-        first_id = await get_or_create_installation_id(db_session)
-        second_id = await get_or_create_installation_id(db_session)
-
-        assert first_id == second_id
-
-    # ========================================================================
-    # Telemetry Enabled Tests
-    # ========================================================================
-
-    @pytest.mark.asyncio
-    async def test_is_telemetry_enabled_default_true(self, db_session):
-        """Verify telemetry is enabled by default (opt-out model)."""
-        result = await is_telemetry_enabled(db_session)
-
-        assert result is True
-
-    @pytest.mark.asyncio
-    async def test_is_telemetry_enabled_explicit_true(self, db_session):
-        """Verify telemetry enabled when explicitly set to true."""
-        setting = Settings(key="telemetry_enabled", value="true")
-        db_session.add(setting)
-        await db_session.commit()
-
-        result = await is_telemetry_enabled(db_session)
-
-        assert result is True
-
-    @pytest.mark.asyncio
-    async def test_is_telemetry_enabled_explicit_false(self, db_session):
-        """Verify telemetry disabled when set to false."""
-        setting = Settings(key="telemetry_enabled", value="false")
-        db_session.add(setting)
-        await db_session.commit()
-
-        result = await is_telemetry_enabled(db_session)
-
-        assert result is False
-
-    @pytest.mark.asyncio
-    async def test_is_telemetry_enabled_case_insensitive(self, db_session):
-        """Verify telemetry enabled check is case insensitive."""
-        setting = Settings(key="telemetry_enabled", value="TRUE")
-        db_session.add(setting)
-        await db_session.commit()
-
-        result = await is_telemetry_enabled(db_session)
-
-        assert result is True
-
-    # ========================================================================
-    # Telemetry URL Tests
-    # ========================================================================
-
-    @pytest.mark.asyncio
-    async def test_get_telemetry_url_default(self, db_session):
-        """Verify default telemetry URL is returned when not configured."""
-        result = await get_telemetry_url(db_session)
-
-        assert result == DEFAULT_TELEMETRY_URL
-
-    @pytest.mark.asyncio
-    async def test_get_telemetry_url_custom(self, db_session):
-        """Verify custom telemetry URL is returned when configured."""
-        custom_url = "https://custom.telemetry.example.com"
-        setting = Settings(key="telemetry_url", value=custom_url)
-        db_session.add(setting)
-        await db_session.commit()
-
-        result = await get_telemetry_url(db_session)
-
-        assert result == custom_url
-
-    # ========================================================================
-    # Send Heartbeat Tests
-    # ========================================================================
-
-    @pytest.mark.asyncio
-    async def test_send_heartbeat_when_disabled(self, db_session):
-        """Verify heartbeat is not sent when telemetry is disabled."""
-        setting = Settings(key="telemetry_enabled", value="false")
-        db_session.add(setting)
-        await db_session.commit()
-
-        with patch("httpx.AsyncClient") as mock_client:
-            result = await send_heartbeat(db_session)
-
-        assert result is False
-        mock_client.assert_not_called()
-
-    @pytest.mark.asyncio
-    async def test_send_heartbeat_success(self, db_session, mock_httpx_client):
-        """Verify heartbeat is sent successfully when enabled."""
-        # Reset the last heartbeat to allow sending
-        import backend.app.services.telemetry as telemetry_module
-
-        telemetry_module._last_heartbeat = None
-
-        result = await send_heartbeat(db_session)
-
-        assert result is True
-
-    @pytest.mark.asyncio
-    async def test_send_heartbeat_rate_limited(self, db_session):
-        """Verify heartbeat is rate limited to once per day."""
-        import backend.app.services.telemetry as telemetry_module
-
-        # Set last heartbeat to recent time
-        telemetry_module._last_heartbeat = datetime.now()
-
-        with patch("httpx.AsyncClient") as mock_client:
-            result = await send_heartbeat(db_session)
-
-        # Should return True (already sent) without making HTTP request
-        assert result is True
-        mock_client.assert_not_called()
-
-    @pytest.mark.asyncio
-    async def test_send_heartbeat_handles_exceptions(self, db_session):
-        """Verify heartbeat returns False on general exceptions."""
-        import backend.app.services.telemetry as telemetry_module
-
-        telemetry_module._last_heartbeat = None
-
-        # Test that the function handles exceptions gracefully by checking
-        # the code path - the actual telemetry URL may or may not be reachable
-        # The function should not raise exceptions to the caller
-        try:
-            result = await send_heartbeat(db_session)
-            # Result can be True (success) or False (failure) but should not raise
-            assert isinstance(result, bool)
-        except Exception as e:
-            pytest.fail(f"send_heartbeat should not raise exceptions: {e}")
-
-    @pytest.mark.asyncio
-    async def test_send_heartbeat_sends_correct_data(self, db_session):
-        """Verify heartbeat sends correct payload."""
-        import backend.app.services.telemetry as telemetry_module
-        from backend.app.core.config import APP_VERSION
-
-        telemetry_module._last_heartbeat = None
-
-        captured_data = {}
-
-        with patch("httpx.AsyncClient") as mock_class:
-            mock_instance = AsyncMock()
-            mock_response = MagicMock()
-            mock_response.raise_for_status = MagicMock()
-
-            async def capture_post(url, json=None):
-                captured_data["url"] = url
-                captured_data["json"] = json
-                return mock_response
-
-            mock_instance.post = capture_post
-            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
-            mock_instance.__aexit__ = AsyncMock()
-            mock_class.return_value = mock_instance
-
-            await send_heartbeat(db_session)
-
-        assert "heartbeat" in captured_data["url"]
-        assert "installation_id" in captured_data["json"]
-        assert captured_data["json"]["version"] == APP_VERSION
-
-
-class TestHeartbeatInterval:
-    """Tests for heartbeat interval configuration."""
-
-    def test_heartbeat_interval_is_24_hours(self):
-        """Verify heartbeat interval is set to 24 hours."""
-        assert timedelta(hours=24) == HEARTBEAT_INTERVAL
-
-    def test_default_telemetry_url(self):
-        """Verify default telemetry URL is correct."""
-        assert DEFAULT_TELEMETRY_URL == "https://telemetry.bambuddy.cool"

+ 0 - 2
frontend/src/api/client.ts

@@ -648,8 +648,6 @@ export interface AppSettings {
   time_format: 'system' | '12h' | '24h';
   // Default printer
   default_printer_id: number | null;
-  // Telemetry
-  telemetry_enabled: boolean;
   // Dark mode theme settings
   dark_style: 'classic' | 'glow' | 'vibrant';
   dark_background: 'neutral' | 'warm' | 'cool' | 'oled' | 'slate' | 'forest';

+ 0 - 15
frontend/src/i18n/locales/de.ts

@@ -244,21 +244,6 @@ export default {
     latestVersion: 'Neueste Version',
     upToDate: 'Sie sind auf dem neuesten Stand',
     updateAvailable: 'Update verfügbar',
-    telemetry: 'Anonyme Telemetrie',
-    telemetryDescription: 'Helfen Sie BamBuddy zu verbessern, indem Sie anonyme Nutzungsdaten senden',
-    telemetryLearnMore: 'Mehr erfahren',
-    telemetryInfoTitle: 'Welche Daten werden gesammelt?',
-    telemetryInfoIntro: 'BamBuddy sammelt minimale anonyme Daten, um zu verstehen, wie viele Personen die App nutzen und welche Versionen verwendet werden. Dies hilft bei der Priorisierung von Fehlerbehebungen und neuen Funktionen.',
-    telemetryInfoCollected: 'Was wir sammeln:',
-    telemetryInfoItem1: 'Eine zufällige Installations-ID (nicht mit Ihnen oder Ihrer Hardware verknüpft)',
-    telemetryInfoItem2: 'Die App-Version, die Sie verwenden',
-    telemetryInfoItem3: 'Ein Zeitstempel (um tägliche/wöchentliche aktive Nutzer zu zählen)',
-    telemetryInfoNotCollected: 'Was wir NICHT sammeln:',
-    telemetryInfoNotItem1: 'IP-Adressen oder Standortdaten',
-    telemetryInfoNotItem2: 'Druckernamen, Seriennummern oder Druckerdaten',
-    telemetryInfoNotItem3: 'Druckverlauf, Dateinamen oder persönliche Inhalte',
-    telemetryInfoNotItem4: 'Informationen, die Sie identifizieren könnten',
-    telemetryInfoFooter: 'Sie können die Telemetrie jederzeit deaktivieren. Die Installations-ID wird zufällig generiert und kann nicht zu Ihnen zurückverfolgt werden.',
     // Notifications
     notificationLanguage: 'Benachrichtigungssprache',
     notificationLanguageDescription: 'Sprache für Push-Benachrichtigungen',

+ 0 - 16
frontend/src/i18n/locales/en.ts

@@ -244,22 +244,6 @@ export default {
     latestVersion: 'Latest Version',
     upToDate: 'You are up to date',
     updateAvailable: 'Update available',
-    telemetry: 'Anonymous telemetry',
-    telemetryDescription: 'Help improve BamBuddy by sending anonymous usage data',
-    telemetryLearnMore: 'Learn more',
-    telemetryInfoTitle: 'What data is collected?',
-    telemetryInfoIntro: 'BamBuddy collects minimal anonymous data to help understand how many people use the app, which versions and printer models are in use. This helps prioritize bug fixes and new features.',
-    telemetryInfoCollected: 'What we collect:',
-    telemetryInfoItem1: 'A random installation ID (not linked to you or your hardware)',
-    telemetryInfoItem2: 'The app version you\'re running',
-    telemetryInfoItem3: 'Printer model types (e.g., X1C, P1S) - not names or serial numbers',
-    telemetryInfoItem4: 'A timestamp (to count daily/weekly active users)',
-    telemetryInfoNotCollected: 'What we do NOT collect:',
-    telemetryInfoNotItem1: 'Your IP address is hashed and cannot be reversed',
-    telemetryInfoNotItem2: 'Printer names, serial numbers, or access codes',
-    telemetryInfoNotItem3: 'Print history, filenames, or any personal content',
-    telemetryInfoNotItem4: 'Any information that could identify you',
-    telemetryInfoFooter: 'You can disable telemetry at any time. The installation ID is randomly generated and cannot be traced back to you.',
     // Notifications
     notificationLanguage: 'Notification Language',
     notificationLanguageDescription: 'Language for push notifications',

+ 1 - 114
frontend/src/pages/SettingsPage.tsx

@@ -1,5 +1,5 @@
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { Loader2, Plus, Plug, AlertTriangle, RotateCcw, Bell, Download, RefreshCw, ExternalLink, Globe, Droplets, Thermometer, FileText, Edit2, Send, CheckCircle, XCircle, History, Trash2, Upload, Zap, TrendingUp, Calendar, DollarSign, Power, PowerOff, Key, Copy, Database, Info, X, Shield, Printer, Cylinder, Wifi, Home, Video, Users, Lock, Unlock } from 'lucide-react';
+import { Loader2, Plus, Plug, AlertTriangle, RotateCcw, Bell, Download, RefreshCw, ExternalLink, Globe, Droplets, Thermometer, FileText, Edit2, Send, CheckCircle, XCircle, History, Trash2, Upload, Zap, TrendingUp, Calendar, DollarSign, Power, PowerOff, Key, Copy, Database, X, Shield, Printer, Cylinder, Wifi, Home, Video, Users, Lock, Unlock } from 'lucide-react';
 import { useTranslation } from 'react-i18next';
 import { useNavigate, useSearchParams } from 'react-router-dom';
 import { api } from '../api/client';
@@ -87,7 +87,6 @@ export function SettingsPage() {
   const [showBulkPlugConfirm, setShowBulkPlugConfirm] = useState<'on' | 'off' | null>(null);
   const [showBackupModal, setShowBackupModal] = useState(false);
   const [showRestoreModal, setShowRestoreModal] = useState(false);
-  const [showTelemetryInfo, setShowTelemetryInfo] = useState(false);
   const [showReleaseNotes, setShowReleaseNotes] = useState(false);
   const [showDisableAuthConfirm, setShowDisableAuthConfirm] = useState(false);
 
@@ -400,7 +399,6 @@ export function SettingsPage() {
       settings.energy_tracking_mode !== localSettings.energy_tracking_mode ||
       settings.check_updates !== localSettings.check_updates ||
       settings.notification_language !== localSettings.notification_language ||
-      settings.telemetry_enabled !== localSettings.telemetry_enabled ||
       settings.ams_humidity_good !== localSettings.ams_humidity_good ||
       settings.ams_humidity_fair !== localSettings.ams_humidity_fair ||
       settings.ams_temp_good !== localSettings.ams_temp_good ||
@@ -461,7 +459,6 @@ export function SettingsPage() {
         energy_tracking_mode: localSettings.energy_tracking_mode,
         check_updates: localSettings.check_updates,
         notification_language: localSettings.notification_language,
-        telemetry_enabled: localSettings.telemetry_enabled,
         ams_humidity_good: localSettings.ams_humidity_good,
         ams_humidity_fair: localSettings.ams_humidity_fair,
         ams_temp_good: localSettings.ams_temp_good,
@@ -1237,33 +1234,6 @@ export function SettingsPage() {
                   <div className="w-11 h-6 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-bambu-green"></div>
                 </label>
               </div>
-              <div className="flex items-center justify-between">
-                <div>
-                  <div className="flex items-center gap-2">
-                    <p className="text-white">{t('settings.telemetry')}</p>
-                    <button
-                      onClick={() => setShowTelemetryInfo(true)}
-                      className="inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bambu-dark rounded-full text-bambu-gray hover:text-white hover:bg-bambu-dark-tertiary transition-colors"
-                    >
-                      <Info className="w-3 h-3" />
-                      {t('settings.telemetryLearnMore')}
-                    </button>
-                  </div>
-                  <p className="text-sm text-bambu-gray">
-                    {t('settings.telemetryDescription')}
-                  </p>
-                </div>
-                <label className="relative inline-flex items-center cursor-pointer">
-                  <input
-                    type="checkbox"
-                    checked={localSettings.telemetry_enabled}
-                    onChange={(e) => updateSetting('telemetry_enabled', e.target.checked)}
-                    className="sr-only peer"
-                  />
-                  <div className="w-11 h-6 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-bambu-green"></div>
-                </label>
-              </div>
-
               <div className="border-t border-bambu-dark-tertiary pt-4">
                 <div className="flex items-center justify-between mb-2">
                   <div>
@@ -2937,89 +2907,6 @@ export function SettingsPage() {
         />
       )}
 
-      {/* Telemetry Info Modal */}
-      {showTelemetryInfo && (
-        <div
-          className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
-          onClick={() => setShowTelemetryInfo(false)}
-        >
-          <Card className="w-full max-w-lg" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
-            <CardHeader className="flex flex-row items-center justify-between">
-              <div className="flex items-center gap-2">
-                <Shield className="w-5 h-5 text-bambu-green" />
-                <h2 className="text-lg font-semibold text-white">{t('settings.telemetryInfoTitle')}</h2>
-              </div>
-              <button
-                onClick={() => setShowTelemetryInfo(false)}
-                className="p-1 rounded hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white"
-              >
-                <X className="w-5 h-5" />
-              </button>
-            </CardHeader>
-            <CardContent className="space-y-4">
-              <p className="text-bambu-gray text-sm">
-                {t('settings.telemetryInfoIntro')}
-              </p>
-
-              <div className="space-y-3">
-                <h3 className="text-white font-medium">{t('settings.telemetryInfoCollected')}</h3>
-                <ul className="space-y-2 text-sm">
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <CheckCircle className="w-4 h-4 text-bambu-green mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoItem1')}</span>
-                  </li>
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <CheckCircle className="w-4 h-4 text-bambu-green mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoItem2')}</span>
-                  </li>
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <CheckCircle className="w-4 h-4 text-bambu-green mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoItem3')}</span>
-                  </li>
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <CheckCircle className="w-4 h-4 text-bambu-green mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoItem4')}</span>
-                  </li>
-                </ul>
-              </div>
-
-              <div className="space-y-3">
-                <h3 className="text-white font-medium">{t('settings.telemetryInfoNotCollected')}</h3>
-                <ul className="space-y-2 text-sm">
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <XCircle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoNotItem1')}</span>
-                  </li>
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <XCircle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoNotItem2')}</span>
-                  </li>
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <XCircle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoNotItem3')}</span>
-                  </li>
-                  <li className="flex items-start gap-2 text-bambu-gray">
-                    <XCircle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" />
-                    <span>{t('settings.telemetryInfoNotItem4')}</span>
-                  </li>
-                </ul>
-              </div>
-
-              <p className="text-bambu-gray text-xs border-t border-bambu-dark-tertiary pt-4">
-                {t('settings.telemetryInfoFooter')}
-              </p>
-
-              <Button
-                onClick={() => setShowTelemetryInfo(false)}
-                className="w-full"
-              >
-                {t('common.close')}
-              </Button>
-            </CardContent>
-          </Card>
-        </div>
-      )}
-
       {/* Release Notes Modal */}
       {showReleaseNotes && updateCheck?.release_notes && (
         <div

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BO_iYk_v.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BuZVMtR5.js"></script>
+    <script type="module" crossorigin src="/assets/index-BO_iYk_v.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BKSrBx0A.css">
   </head>
   <body>

+ 4 - 6
update_website_wiki.sh

@@ -1,10 +1,5 @@
 #!/bin/bash
 
-cd ../bambuddy-telemetry
-git add .
-git commit -m "Updated telemetry"
-git push
-
 cd ../bambuddy-website
 git add .
 git commit -m "Updated website"
@@ -20,4 +15,7 @@ git add .
 git commit -m "Updated website"
 git push
 
-cd ../bambuddy
+cd ../spoolbuddy-wiki
+git add .
+git commit -m "Updated Wiki"
+git push

Some files were not shown because too many files changed in this diff