diff --git a/apps/rebreak-magic/app/components/IosDeviceCard.vue b/apps/rebreak-magic/app/components/IosDeviceCard.vue
index 3064e09..cac9abb 100644
--- a/apps/rebreak-magic/app/components/IosDeviceCard.vue
+++ b/apps/rebreak-magic/app/components/IosDeviceCard.vue
@@ -25,66 +25,150 @@
- {{ statusLabel }}
+ {{ topBadge.label }}
+
-
-
Zum Live-Status iPhone per USB verbinden und aktualisieren
+
+
+
+
+ Schutz wird geprüft…
+
+
+ Backend- und USB-Status werden abgeglichen
+
+
+
+
+
+
+
+ Backend-MDM
+
+
+ Lädt…
+
+
+ Enrolled
+
+
+ Nicht enrolled
+
+
+
+
+ -
+ {{ row.label }}
+ {{ row.value }}
+
+
+
+
+
+
+
+
+ Lokales USB-Gerät
+
+
+ Verbunden
+
+
+
+
+ -
+ {{ row.label }}
+ {{ row.value }}
+
+
+
+
+
+
+
@@ -104,7 +188,8 @@
:variant="action.variant"
size="sm"
:icon="action.icon"
- :loading="syncing"
+ :loading="manualSyncing || autoSyncing"
+ :disabled="autoSyncing"
@click="onActionClick"
>
{{ action.label }}
@@ -126,8 +211,9 @@
diff --git a/apps/rebreak-magic/app/composables/useMdmStatus.ts b/apps/rebreak-magic/app/composables/useMdmStatus.ts
new file mode 100644
index 0000000..b588fc3
--- /dev/null
+++ b/apps/rebreak-magic/app/composables/useMdmStatus.ts
@@ -0,0 +1,65 @@
+import { ref, watch, type Ref } from "vue";
+import { useTauri, type MdmStatusData } from "./useTauri";
+
+export interface MdmStatusState {
+ data: MdmStatusData | null;
+ loading: boolean;
+ error: string | null;
+}
+
+export function useMdmStatus(deviceId: Ref
) {
+ const { getMdmStatus, linkMdmDevice } = useTauri();
+
+ const state = ref({
+ data: null,
+ loading: false,
+ error: null,
+ });
+
+ async function refresh() {
+ const id = deviceId.value;
+ if (!id) {
+ state.value.data = null;
+ return;
+ }
+
+ state.value.loading = true;
+ state.value.error = null;
+ try {
+ state.value.data = await getMdmStatus(id);
+ } catch (e: any) {
+ state.value.error = e?.message ?? "MDM-Status konnte nicht geladen werden";
+ state.value.data = null;
+ } finally {
+ state.value.loading = false;
+ }
+ }
+
+ async function link(mdmId: string) {
+ const id = deviceId.value;
+ if (!id) return;
+
+ state.value.loading = true;
+ state.value.error = null;
+ try {
+ await linkMdmDevice(id, mdmId);
+ await refresh();
+ } catch (e: any) {
+ state.value.error = e?.message ?? "MDM-Verknüpfung fehlgeschlagen";
+ } finally {
+ state.value.loading = false;
+ }
+ }
+
+ watch(
+ () => deviceId.value,
+ () => refresh(),
+ { immediate: true },
+ );
+
+ return {
+ state,
+ refresh,
+ link,
+ };
+}
diff --git a/apps/rebreak-magic/app/composables/useTauri.ts b/apps/rebreak-magic/app/composables/useTauri.ts
index 6e103db..a99473a 100644
--- a/apps/rebreak-magic/app/composables/useTauri.ts
+++ b/apps/rebreak-magic/app/composables/useTauri.ts
@@ -82,6 +82,14 @@ export interface DesktopProtectionState {
activatedAt: string;
}
+export interface MdmStatusData {
+ enrolled: boolean;
+ company: string | null;
+ supervised: boolean;
+ lockProfileInstalled: boolean;
+ lastAppPushAt: string | null;
+}
+
export interface SuperviseStatus {
isSupervised: boolean;
organizationName?: string;
@@ -262,6 +270,14 @@ export function useTauri() {
return await invokeLogged("get_hostname");
}
+ async function getMdmStatus(deviceId: string): Promise {
+ return await invokeLogged("get_mdm_status", { deviceId });
+ }
+
+ async function linkMdmDevice(deviceId: string, mdmId: string): Promise {
+ await invokeLogged("link_mdm_device", { deviceId, mdmId });
+ }
+
return {
getPlatform,
redeemPairingCode,
@@ -297,5 +313,7 @@ export function useTauri() {
getHostname,
getHardwareId,
getDeviceId,
+ getMdmStatus,
+ linkMdmDevice,
};
}
diff --git a/apps/rebreak-magic/src-tauri/src/backend/api.rs b/apps/rebreak-magic/src-tauri/src/backend/api.rs
index 3c9c250..a8289a1 100644
--- a/apps/rebreak-magic/src-tauri/src/backend/api.rs
+++ b/apps/rebreak-magic/src-tauri/src/backend/api.rs
@@ -97,6 +97,23 @@ pub struct UserProfile {
pub plan: Option,
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct MdmStatusData {
+ pub enrolled: bool,
+ pub company: Option,
+ pub supervised: bool,
+ #[serde(rename = "lockProfileInstalled")]
+ pub lock_profile_installed: bool,
+ #[serde(rename = "lastAppPushAt")]
+ pub last_app_push_at: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct MdmLinkRequest {
+ #[serde(rename = "mdmId")]
+ pub mdm_id: String,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiEnvelope {
pub success: bool,
@@ -345,6 +362,49 @@ impl MagicApiClient {
.map_err(|e| AppError::new(format!("Failed to read profile: {}", e)))
}
+ pub async fn get_mdm_status(&self, token: &str, device_id: &str) -> AppResult {
+ let url = format!("{}/api/magic/devices/{}/mdm", self.base_url, device_id);
+
+ let response = self
+ .client
+ .get(&url)
+ .header("Authorization", format!("Bearer {}", token))
+ .send()
+ .await
+ .map_err(|e| AppError::new(format!("Network error: {}", e)))?;
+
+ Self::handle_response::>(response)
+ .await
+ .map(|envelope| envelope.data)
+ }
+
+ pub async fn link_mdm_device(
+ &self,
+ token: &str,
+ device_id: &str,
+ mdm_id: &str,
+ ) -> AppResult<()> {
+ let url = format!(
+ "{}/api/magic/devices/{}/mdm-link",
+ self.base_url, device_id
+ );
+
+ let response = self
+ .client
+ .post(&url)
+ .header("Authorization", format!("Bearer {}", token))
+ .json(&MdmLinkRequest {
+ mdm_id: mdm_id.to_string(),
+ })
+ .send()
+ .await
+ .map_err(|e| AppError::new(format!("Network error: {}", e)))?;
+
+ Self::handle_response::>(response)
+ .await
+ .map(|_| ())
+ }
+
async fn handle_response(
response: reqwest::Response,
) -> AppResult {
diff --git a/apps/rebreak-magic/src-tauri/src/lib.rs b/apps/rebreak-magic/src-tauri/src/lib.rs
index 1c09a4e..89f0929 100644
--- a/apps/rebreak-magic/src-tauri/src/lib.rs
+++ b/apps/rebreak-magic/src-tauri/src/lib.rs
@@ -8,8 +8,8 @@ mod server;
mod sidecar;
use backend::api::{
- MagicApiClient, MagicDeviceInfo, RedeemPairingResponse, RegisterDeviceResponse, ReleaseResponse,
- UserProfile,
+ MagicApiClient, MagicDeviceInfo, MdmStatusData, RedeemPairingResponse, RegisterDeviceResponse,
+ ReleaseResponse, UserProfile,
};
use config::{AppConfig, DesktopProtectionState, MagicSession};
use error::AppResult;
@@ -51,6 +51,8 @@ pub fn run() {
download_profile,
activate_protection,
fetch_me,
+ get_mdm_status,
+ link_mdm_device,
get_desktop_protection_status,
set_desktop_protection_status,
get_hostname,
@@ -217,6 +219,22 @@ async fn fetch_me() -> AppResult {
client.fetch_me(&session.access_token).await
}
+#[tauri::command]
+async fn get_mdm_status(device_id: String) -> AppResult {
+ let session = require_session()?;
+ let config = AppConfig::load();
+ let client = MagicApiClient::new(&config);
+ client.get_mdm_status(&session.access_token, &device_id).await
+}
+
+#[tauri::command]
+async fn link_mdm_device(device_id: String, mdm_id: String) -> AppResult<()> {
+ let session = require_session()?;
+ let config = AppConfig::load();
+ let client = MagicApiClient::new(&config);
+ client.link_mdm_device(&session.access_token, &device_id, &mdm_id).await
+}
+
#[tauri::command]
async fn download_profile(profile_url: String) -> AppResult {
let session = require_session()?;