From 298a0089bbc844a09cf79c6c3cb585a549d80b54 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Tue, 16 Jun 2026 20:53:39 +0200 Subject: [PATCH] feat(magic): redesign status dashboard with hero cards and device sheet --- .../app/composables/useProtectionStatus.ts | 203 +++++++++++ apps/rebreak-magic/app/pages/status.vue | 342 ++++++++++++++++++ 2 files changed, 545 insertions(+) create mode 100644 apps/rebreak-magic/app/composables/useProtectionStatus.ts create mode 100644 apps/rebreak-magic/app/pages/status.vue diff --git a/apps/rebreak-magic/app/composables/useProtectionStatus.ts b/apps/rebreak-magic/app/composables/useProtectionStatus.ts new file mode 100644 index 0000000..6e0f1a2 --- /dev/null +++ b/apps/rebreak-magic/app/composables/useProtectionStatus.ts @@ -0,0 +1,203 @@ +import { ref, computed, onMounted, onUnmounted } from "vue"; +import { useTauri, type IphoneDeviceState, type DesktopProtectionState, type MagicDeviceInfo } from "~/composables/useTauri"; +import { useIphoneDevice, useMagicDevices, useMagicSession } from "~/composables/useMagicState"; + +export interface ProtectionStatus { + overall: "protected" | "partial" | "unprotected" | "unknown"; + label: string; + message: string; + iosConnected: boolean; + iosProtected: boolean; + desktopProtected: boolean; + anyBackendDevice: boolean; +} + +const IPHONE_POLL_INTERVAL = 5000; +const DEVICE_REFRESH_INTERVAL = 30000; + +function normalizeHostname(value: string): string { + return (value.toLowerCase().split(".")[0] ?? "").trim(); +} + +export function useProtectionStatus() { + const { + detectIphoneState, + getDesktopProtectionStatus, + getMagicDevices, + getMagicStatus, + getHostname, + } = useTauri(); + + const iphone = useIphoneDevice(); + const devices = useMagicDevices(); + const session = useMagicSession(); + + const desktopProtection = ref(null); + const localHostname = ref(null); + const loading = ref(false); + const lastError = ref(null); + const lastUpdated = ref(null); + + let iphoneTimer: ReturnType | null = null; + let deviceTimer: ReturnType | null = null; + + const iosConnected = computed(() => !!iphone.value); + const iosProtected = computed(() => { + if (!iphone.value) return false; + const hasEnrollment = iphone.value.installedProfileIDs?.includes("org.rebreak.mdm.enrollment"); + const hasLock = iphone.value.installedProfileIDs?.includes("org.rebreak.protection.contentfilter.sideload"); + return iphone.value.isSupervised && hasEnrollment && hasLock; + }); + const currentBackendDevice = computed(() => { + if (!localHostname.value) return null; + const local = normalizeHostname(localHostname.value); + return devices.value.find((d) => normalizeHostname(d.hostname) === local) ?? null; + }); + const desktopProtected = computed(() => !!desktopProtection.value?.active || !!currentBackendDevice.value); + const anyBackendDevice = computed(() => devices.value.length > 0); + + const status = computed(() => { + const ios = iosConnected.value; + const iosOk = iosProtected.value; + const desktopOk = desktopProtected.value; + const backendOk = anyBackendDevice.value; + + if (!ios && !desktopOk && !backendOk) { + return { + overall: "unprotected", + label: "Kein Schutz aktiv", + message: "Verbinde ein iPhone oder aktiviere den Desktop-Schutz.", + iosConnected: false, + iosProtected: false, + desktopProtected: false, + anyBackendDevice: false, + }; + } + + if (ios && !iosOk) { + return { + overall: "partial", + label: "iOS-Schutz unvollständig", + message: "Supervision oder Schutz-Profile fehlen. Führe das Setup fort.", + iosConnected: true, + iosProtected: false, + desktopProtected: desktopOk, + anyBackendDevice: backendOk, + }; + } + + if (iosOk && desktopOk) { + return { + overall: "protected", + label: "Vollständig geschützt", + message: "iPhone und Desktop sind aktiv geschützt.", + iosConnected: true, + iosProtected: true, + desktopProtected: true, + anyBackendDevice: backendOk, + }; + } + + if (iosOk || desktopOk || backendOk) { + return { + overall: "partial", + label: "Teilweise geschützt", + message: iosOk + ? "iPhone geschützt. Desktop-Schutz ist optional." + : desktopOk + ? "Desktop geschützt. iPhone noch nicht verbunden." + : "Ein registriertes Gerät vorhanden, aber keine lokale Schutzkontrolle.", + iosConnected: ios, + iosProtected: iosOk, + desktopProtected: desktopOk, + anyBackendDevice: backendOk, + }; + } + + return { + overall: "unknown", + label: "Status unbekannt", + message: "Schutzstatus konnte nicht ermittelt werden.", + iosConnected: false, + iosProtected: false, + desktopProtected: false, + anyBackendDevice: false, + }; + }); + + async function refreshIphone() { + try { + const state = await detectIphoneState(); + iphone.value = state; + } catch (e: any) { + // Bei Fehler nicht crashen — Gerät ist einfach nicht verbunden + iphone.value = null; + } + } + + async function refreshDesktop() { + try { + desktopProtection.value = await getDesktopProtectionStatus(); + } catch (e: any) { + lastError.value = e?.message ?? "Desktop-Schutzstatus konnte nicht geladen werden"; + } + } + + async function refreshBackendDevices() { + try { + devices.value = await getMagicDevices(); + if (session.value?.dnsToken) { + await getMagicStatus(session.value.dnsToken); + } + } catch (e: any) { + lastError.value = e?.message ?? "Geräte konnten nicht geladen werden"; + } + } + + async function refresh() { + loading.value = true; + lastError.value = null; + await Promise.all([ + refreshIphone(), + refreshDesktop(), + refreshBackendDevices(), + refreshHostname(), + ]); + lastUpdated.value = new Date(); + loading.value = false; + } + + async function refreshHostname() { + try { + localHostname.value = await getHostname(); + } catch (e: any) { + localHostname.value = null; + } + } + + onMounted(() => { + refresh(); + iphoneTimer = setInterval(refreshIphone, IPHONE_POLL_INTERVAL); + deviceTimer = setInterval(refreshBackendDevices, DEVICE_REFRESH_INTERVAL); + }); + + onUnmounted(() => { + if (iphoneTimer) clearInterval(iphoneTimer); + if (deviceTimer) clearInterval(deviceTimer); + }); + + return { + status, + iphone: computed(() => iphone.value), + desktopProtection, + localHostname, + loading, + lastError, + lastUpdated, + iosConnected, + iosProtected, + desktopProtected, + anyBackendDevice, + refresh, + }; +} diff --git a/apps/rebreak-magic/app/pages/status.vue b/apps/rebreak-magic/app/pages/status.vue new file mode 100644 index 0000000..7c6b19a --- /dev/null +++ b/apps/rebreak-magic/app/pages/status.vue @@ -0,0 +1,342 @@ + + +