chahinebrini 2919ce45b8 feat(magic): sync current ReBreak Magic app state
Include recent Magic app work: Tauri native shell, iOS device detection
via supervise-magic sidecar, MDM client, local HTTP server, new pages
(detect, enroll, supervise, sideload, pair, preflight, configure, done),
and updated device section/status UI.
2026-06-18 05:23:26 +02:00

116 lines
3.9 KiB
Vue

<template>
<div>
<!-- Toggle button (small, bottom-right) -->
<UButton icon="i-heroicons-bug-ant" color="neutral" variant="ghost" size="xs"
class="fixed bottom-3 right-3 z-50 opacity-60 hover:opacity-100" @click="open = true">
Logs
</UButton>
<UDrawer v-model:open="open" direction="bottom" :handle="true" :dismissible="true">
<template #body>
<div class="flex flex-col h-full">
<div class="flex items-center justify-end gap-2 pb-3 border-b border-gray-200">
<UButton size="xs" color="neutral" variant="ghost" icon="i-heroicons-clipboard-document" @click="copyLogs">
Kopieren
</UButton>
<UButton size="xs" color="error" variant="ghost" icon="i-heroicons-trash" @click="clear">
Löschen
</UButton>
</div>
<div class="flex-1 overflow-auto pt-3 space-y-2 bg-gray-950 -mx-4 px-4">
<div v-for="entry in logs" :key="entry.id" class="text-xs font-mono p-2 rounded border"
:class="entryClass(entry.level)">
<div class="flex items-center gap-2 opacity-70">
<span>{{ formatTime(entry.timestamp) }}</span>
<UBadge :color="badgeColor(entry.level)" size="xs" variant="solid">
{{ entry.level.toUpperCase() }}
</UBadge>
</div>
<div class="mt-1 whitespace-pre-wrap break-all">{{ entry.message }}</div>
<details v-if="entry.details" class="mt-2 group">
<summary class="cursor-pointer hover:underline flex items-center gap-2">
<span>Details</span>
<UButton size="2xs" color="neutral" variant="ghost" icon="i-heroicons-clipboard-document"
class="opacity-0 group-hover:opacity-100 transition-opacity"
@click.stop="copyDetails(entry.details)">
Kopieren
</UButton>
</summary>
<div class="mt-1 p-2 bg-black/30 rounded whitespace-pre-wrap break-all">{{ entry.details }}</div>
</details>
</div>
<div v-if="logs.length === 0" class="text-gray-500 text-center py-8">
Noch keine Logs vorhanden.
</div>
</div>
</div>
</template>
</UDrawer>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { useLogger, useLoggerState, type LogEntry } from "~/composables/useLogger";
const open = ref(false);
const logs = useLoggerState();
const { clear, exportLogs } = useLogger();
function entryClass(level: LogEntry["level"]) {
switch (level) {
case "error": return "bg-red-950/50 border-red-800 text-red-100";
case "warn": return "bg-yellow-950/50 border-yellow-800 text-yellow-100";
case "info": return "bg-blue-950/50 border-blue-800 text-blue-100";
default: return "bg-gray-900 border-gray-800 text-gray-200";
}
}
function badgeColor(level: LogEntry["level"]) {
switch (level) {
case "error": return "red";
case "warn": return "yellow";
case "info": return "blue";
default: return "neutral";
}
}
function formatTime(date: Date) {
return date.toLocaleTimeString("de-DE", { hour12: false }) + "." + String(date.getMilliseconds()).padStart(3, "0");
}
async function copyLogs() {
try {
await navigator.clipboard.writeText(exportLogs());
} catch {
// ignore
}
}
async function copyDetails(details: string) {
try {
await navigator.clipboard.writeText(details);
} catch {
// ignore
}
}
// Keyboard shortcut: Cmd/Ctrl + Shift + L
function onKeyDown(e: KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "l") {
e.preventDefault();
open.value = !open.value;
}
}
onMounted(() => {
window.addEventListener("keydown", onKeyDown);
});
onUnmounted(() => {
window.removeEventListener("keydown", onKeyDown);
});
</script>