Compare commits
2 Commits
5404f6676b
...
a5581cf077
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5581cf077 | ||
|
|
92ab26605f |
3
.gitignore
vendored
3
.gitignore
vendored
@ -31,6 +31,9 @@ Thumbs.db
|
||||
# Claude Code agent state (lokale Definitionen, nicht versioniert)
|
||||
.claude/
|
||||
|
||||
# Kimi Code / Sixth plugin state
|
||||
.sixth/
|
||||
|
||||
# xgit binary (generated)
|
||||
xgit
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ steps:
|
||||
- mkdir -p ~/.ssh
|
||||
- cp /root/ssh-keys/rebreak-deploy ~/.ssh/id_ed25519
|
||||
- chmod 600 ~/.ssh/id_ed25519
|
||||
- ssh-keyscan -H admin.staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
|
||||
- ssh-keyscan -H staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
|
||||
- tar czf admin-output.tar.gz -C apps/admin/.output .
|
||||
- scp -i ~/.ssh/id_ed25519 admin-output.tar.gz root@staging.rebreak.org:/srv/rebreak/apps/admin/.output-incoming.tar.gz
|
||||
- ssh -i ~/.ssh/id_ed25519 root@staging.rebreak.org 'bash /srv/rebreak/scripts/deploy-admin-from-artifact.sh'
|
||||
|
||||
@ -195,18 +195,60 @@
|
||||
<UIcon
|
||||
name="i-heroicons-lock-closed"
|
||||
class="w-5 h-5 text-purple-600 dark:text-purple-400"
|
||||
:class="{ 'animate-spin': lockPhase === 'loading' }"
|
||||
:class="{ 'animate-spin': lockPhase === 'loading' || lockPhase === 'checking' }"
|
||||
/>
|
||||
<p class="text-sm font-bold text-purple-900 dark:text-purple-200">
|
||||
Lock-Profil
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Progress steps -->
|
||||
<div class="flex items-center gap-2 text-xs mb-4">
|
||||
<span
|
||||
class="px-2 py-1 rounded-full"
|
||||
:class="lockPhase === 'loading' ? 'bg-purple-200 text-purple-800 dark:bg-purple-700 dark:text-purple-100' : 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'"
|
||||
>
|
||||
1. Server starten
|
||||
</span>
|
||||
<span class="text-gray-400">→</span>
|
||||
<span
|
||||
class="px-2 py-1 rounded-full"
|
||||
:class="lockPhase === 'waiting' ? 'bg-purple-200 text-purple-800 dark:bg-purple-700 dark:text-purple-100' : (lockPhase === 'checking' || lockPhase === 'success' || lockPhase === 'error') ? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'"
|
||||
>
|
||||
2. QR-Code scannen
|
||||
</span>
|
||||
<span class="text-gray-400">→</span>
|
||||
<span
|
||||
class="px-2 py-1 rounded-full"
|
||||
:class="lockPhase === 'checking' ? 'bg-purple-200 text-purple-800 dark:bg-purple-700 dark:text-purple-100' : lockPhase === 'success' ? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'"
|
||||
>
|
||||
3. Prüfen
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- QR code -->
|
||||
<div v-if="lockPhase === 'waiting' && lockQrUrl" class="text-center space-y-3">
|
||||
<div class="bg-white p-3 rounded-xl inline-block">
|
||||
<img :src="lockQrUrl" alt="Lock-Profil QR-Code" class="w-40 h-40">
|
||||
</div>
|
||||
<p class="text-xs text-purple-700 dark:text-purple-300">
|
||||
Scanne den Code mit der iPhone-Kamera und installiere das Lock-Profil.
|
||||
</p>
|
||||
<UButton
|
||||
size="sm"
|
||||
color="primary"
|
||||
:loading="lockPhase === 'checking'"
|
||||
@click="checkInlineLockProfile"
|
||||
>
|
||||
Installation prüfen
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div v-if="lockPhase === 'loading'" class="text-sm text-purple-700 dark:text-purple-300">
|
||||
Lock-Profil wird per MDM auf das iPhone gepusht …
|
||||
Lock-Profil-Server wird gestartet …
|
||||
</div>
|
||||
<div v-if="lockPhase === 'success'" class="text-sm text-green-700 dark:text-green-300">
|
||||
✓ Lock-Profil-Installation initiiert. Das Gerät aktualisiert den Schutz in Kürze.
|
||||
✓ Lock-Profil installiert. Das Gerät aktualisiert den Schutz in Kürze.
|
||||
</div>
|
||||
<div v-if="lockPhase === 'error'" class="text-sm text-red-700 dark:text-red-300">
|
||||
✗ {{ lockError || "Lock-Profil-Installation fehlgeschlagen" }}
|
||||
@ -283,7 +325,6 @@ const {
|
||||
stopLocalProfileServer,
|
||||
getInstalledProfiles,
|
||||
mdmPush,
|
||||
mdmInstallLockProfile,
|
||||
} = useTauri();
|
||||
|
||||
const enrollmentPhase = ref<"idle" | "loading" | "waiting" | "checking" | "success" | "error">("idle");
|
||||
@ -292,9 +333,10 @@ const enrollmentQrUrl = ref<string>("");
|
||||
const enrollmentError = ref<string | null>(null);
|
||||
const enrollmentLogs = ref<string[]>([]);
|
||||
|
||||
const lockPhase = ref<"idle" | "loading" | "success" | "error">("idle");
|
||||
const lockPhase = ref<"idle" | "loading" | "waiting" | "checking" | "success" | "error">("idle");
|
||||
const lockError = ref<string | null>(null);
|
||||
const lockLogs = ref<string[]>([]);
|
||||
const lockQrUrl = ref<string>("");
|
||||
|
||||
const LOCK_PROFILE_PATH = "/Users/chahinebrini/mono/rebreak-monorepo/ops/mdm/profiles/rebreak-content-filter-sideload.mobileconfig";
|
||||
|
||||
@ -780,6 +822,15 @@ async function checkInlineEnrollment() {
|
||||
}
|
||||
|
||||
await refreshMdmStatus();
|
||||
|
||||
// After successful enrollment, automatically continue to lock profile if needed.
|
||||
if (!localLock.value) {
|
||||
enrollmentLogs.value.push("→ Enrollment abgeschlossen. Starte Lock-Profil …");
|
||||
closeInlineEnrollment();
|
||||
await startInlineLockProfile();
|
||||
return;
|
||||
}
|
||||
|
||||
enrollmentPhase.value = "success";
|
||||
} catch (e: any) {
|
||||
enrollmentError.value = e?.message ?? "Prüfung fehlgeschlagen";
|
||||
@ -797,29 +848,60 @@ function closeInlineEnrollment() {
|
||||
}
|
||||
|
||||
async function startInlineLockProfile() {
|
||||
if (!props.iphone?.udid) return;
|
||||
|
||||
lockPhase.value = "loading";
|
||||
lockError.value = null;
|
||||
lockLogs.value = [];
|
||||
lockQrUrl.value = "";
|
||||
|
||||
try {
|
||||
lockLogs.value.push("→ Installiere Lock-Profil per MDM …");
|
||||
const result = await mdmInstallLockProfile(props.iphone.udid, LOCK_PROFILE_PATH);
|
||||
lockLogs.value.push(`✓ Command UUID: ${result.command_uuid}`);
|
||||
lockLogs.value.push("→ Starte lokalen Server für Lock-Profil …");
|
||||
const serverInfo = await startLocalProfileServer(LOCK_PROFILE_PATH);
|
||||
lockLogs.value.push(`✓ Server gestartet: ${serverInfo.url}`);
|
||||
|
||||
lockQrUrl.value = await QRCode.toDataURL(serverInfo.qr_payload, {
|
||||
width: 192,
|
||||
margin: 2,
|
||||
});
|
||||
|
||||
lockPhase.value = "waiting";
|
||||
} catch (e: any) {
|
||||
lockError.value = e?.message ?? "Lock-Profil-Server konnte nicht gestartet werden";
|
||||
lockLogs.value.push(`✗ ${lockError.value}`);
|
||||
lockPhase.value = "error";
|
||||
}
|
||||
}
|
||||
|
||||
async function checkInlineLockProfile() {
|
||||
if (!props.iphone) return;
|
||||
|
||||
lockPhase.value = "checking";
|
||||
lockError.value = null;
|
||||
|
||||
try {
|
||||
const ids = await getInstalledProfiles();
|
||||
props.iphone.installedProfileIDs = ids;
|
||||
|
||||
if (!ids.includes(LOCK_PROFILE_ID)) {
|
||||
lockError.value = "Lock-Profil noch nicht installiert. Bitte QR-Code scannen und Profil installieren.";
|
||||
lockPhase.value = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
lockLogs.value.push("✓ Lock-Profil erkannt");
|
||||
await refreshMdmStatus();
|
||||
lockPhase.value = "success";
|
||||
} catch (e: any) {
|
||||
lockError.value = e?.message ?? "Lock-Profil-Installation fehlgeschlagen";
|
||||
lockError.value = e?.message ?? "Prüfung fehlgeschlagen";
|
||||
lockLogs.value.push(`✗ ${lockError.value}`);
|
||||
lockPhase.value = "error";
|
||||
}
|
||||
}
|
||||
|
||||
function closeInlineLockProfile() {
|
||||
stopLocalProfileServer();
|
||||
lockPhase.value = "idle";
|
||||
lockError.value = null;
|
||||
lockLogs.value = [];
|
||||
lockQrUrl.value = "";
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -3,12 +3,9 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
use tiny_http::{Response, Server};
|
||||
|
||||
static SERVER_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LocalServerInfo {
|
||||
pub url: String,
|
||||
@ -17,10 +14,6 @@ pub struct LocalServerInfo {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServerInfo> {
|
||||
if SERVER_RUNNING.load(Ordering::SeqCst) {
|
||||
return Err(AppError::new("Local server is already running"));
|
||||
}
|
||||
|
||||
let path = PathBuf::from(profile_path);
|
||||
if !path.exists() {
|
||||
return Err(AppError::new(format!(
|
||||
@ -48,8 +41,6 @@ pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServer
|
||||
let qr_payload = url.clone();
|
||||
let profile_bytes = fs::read(&path)?;
|
||||
|
||||
SERVER_RUNNING.store(true, Ordering::SeqCst);
|
||||
|
||||
thread::spawn(move || {
|
||||
for request in server.incoming_requests() {
|
||||
let response = Response::from_data(profile_bytes.clone())
|
||||
@ -62,7 +53,6 @@ pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServer
|
||||
);
|
||||
let _ = request.respond(response);
|
||||
}
|
||||
SERVER_RUNNING.store(false, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
Ok(LocalServerInfo { url, qr_payload })
|
||||
@ -71,8 +61,7 @@ pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServer
|
||||
#[tauri::command]
|
||||
pub fn stop_local_profile_server() -> AppResult<()> {
|
||||
// tiny_http does not support graceful shutdown out of the box.
|
||||
// In a real implementation, store the server handle and close it.
|
||||
SERVER_RUNNING.store(false, Ordering::SeqCst);
|
||||
// Old server threads keep running, but each new profile gets a fresh port.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user