feat(magic): Hub vereinigt Magic-Bindings + alte ProtectedDevices
- GET /api/magic/devices fetcht jetzt parallel listMagicDevices() + listProtectedDevices() und merged beide Quellen in eine Response. Items haben neues 'source' Feld (magic|protected). - ProtectedDevice (alter Native-DNS-Flow) wird auf gleiche Shape gemappt: label->hostname, platform->model. - Mac-App MagicDevice: source-Feld optional + resolvedSource Fallback fuer Backwards-Compat. id mit source-Prefix gegen Collisions zwischen Tabellen. - DeviceHubView Row: protected-Geraete bekommen graues 'Native-App' Badge und Hinweis 'Verwaltung in der ReBreak-App' statt Trash-Button (Release laeuft dort).
This commit is contained in:
parent
dbc62b98ca
commit
ac72fabc34
@ -8,7 +8,13 @@ struct MagicRegistration: Codable {
|
||||
let existing: Bool
|
||||
}
|
||||
|
||||
enum MagicDeviceSource: String, Codable {
|
||||
case magic
|
||||
case protected
|
||||
}
|
||||
|
||||
struct MagicDevice: Codable, Identifiable {
|
||||
let source: MagicDeviceSource?
|
||||
let deviceId: String
|
||||
let hostname: String
|
||||
let model: String?
|
||||
@ -17,7 +23,10 @@ struct MagicDevice: Codable, Identifiable {
|
||||
let releaseRequestedAt: String?
|
||||
let releaseAvailableAt: String?
|
||||
|
||||
var id: String { deviceId }
|
||||
var id: String { "\(source?.rawValue ?? "magic"):\(deviceId)" }
|
||||
|
||||
/// Default zu `.magic` falls Backend (alte Version) das Feld nicht setzt.
|
||||
var resolvedSource: MagicDeviceSource { source ?? .magic }
|
||||
|
||||
var enrolledDate: Date? {
|
||||
ISO8601DateFormatter().date(from: magicEnrolledAt)
|
||||
|
||||
@ -279,8 +279,19 @@ private struct HubDeviceRow: View {
|
||||
.frame(width: 28)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(spacing: 6) {
|
||||
Text(device.hostname)
|
||||
.font(.callout.bold())
|
||||
if device.resolvedSource == .protected {
|
||||
Text("Native-App")
|
||||
.font(.caption2.bold())
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.gray.opacity(0.15))
|
||||
.foregroundStyle(.secondary)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
HStack(spacing: 8) {
|
||||
if let model = device.model {
|
||||
Text(model).font(.caption2).foregroundStyle(.secondary)
|
||||
@ -294,7 +305,11 @@ private struct HubDeviceRow: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
if device.isReleasing {
|
||||
if device.resolvedSource == .protected {
|
||||
Text("Verwaltung in der ReBreak-App")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
} else if device.isReleasing {
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Label("Freigabe läuft", systemImage: "hourglass")
|
||||
.font(.caption2.bold())
|
||||
|
||||
@ -221,7 +221,7 @@ struct MacRegistrationView: View {
|
||||
|
||||
await MainActor.run {
|
||||
isInstallingProfile = false
|
||||
successMessage = "System Settings → Profile geöffnet. Bitte dort „Installieren" klicken und Admin-Passwort eingeben."
|
||||
successMessage = "System Settings → Profile geöffnet. Bitte dort „Installieren“ klicken und Admin-Passwort eingeben."
|
||||
}
|
||||
|
||||
// Re-check profile status nach kurzer Wartezeit (User muss in UI bestätigen)
|
||||
|
||||
@ -36,7 +36,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: MAIN_BUNDLE,
|
||||
buildNumber: "67",
|
||||
buildNumber: "68",
|
||||
// Apple Sign-In Entitlement — Pflicht für expo-apple-authentication nativen
|
||||
// signInAsync()-Flow. Ohne flag generiert Expo's prebuild den
|
||||
// com.apple.developer.applesignin-Entitlement nicht in die .entitlements.
|
||||
@ -59,7 +59,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
||||
|
||||
android: {
|
||||
package: "org.rebreak.app",
|
||||
versionCode: 50,
|
||||
versionCode: 51,
|
||||
adaptiveIcon: {
|
||||
// Foreground muss in der ~66%-Safe-Zone bleiben (Launcher-Mask clippt den
|
||||
// Außenring) → adaptive-foreground.png ist das Logo auf transparentem
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.13</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>67</string>
|
||||
<string>68</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.13</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>67</string>
|
||||
<string>68</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.13</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>67</string>
|
||||
<string>68</string>
|
||||
<key>EXAppExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>EXExtensionPointIdentifier</key>
|
||||
|
||||
@ -1,18 +1,27 @@
|
||||
import { listMagicDevices } from "../../db/devices";
|
||||
import { listProtectedDevices } from "../../db/protectedDevices";
|
||||
import { requireUser } from "../../utils/auth";
|
||||
|
||||
/**
|
||||
* GET /api/magic/devices
|
||||
*
|
||||
* Listet alle aktiven Magic-Bindings des Users für UI.
|
||||
* Response: [{ deviceId, hostname, model, osVersion, magicEnrolledAt, releaseRequestedAt, releaseAvailableAt }]
|
||||
* Listet alle gesch\u00fctzten Ger\u00e4te des Users f\u00fcr den Magic-Hub. Vereinigt:
|
||||
* - Magic-Bindings (UserDevice.magicEnrolledAt) \u2014 via Magic-App registriert
|
||||
* - ProtectedDevices \u2014 alter Native-App-DNS-Schutz-Flow (Multi-Device)
|
||||
*
|
||||
* Response-Items haben ein `source`-Flag:
|
||||
* "magic" \u2192 voll verwaltet, unterst\u00fctzt request-release
|
||||
* "protected" \u2192 alter Flow, nur Anzeige + revoke (TODO: own action)
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = await requireUser(event);
|
||||
const devices = await listMagicDevices(user.id);
|
||||
|
||||
// Berechne releaseAvailableAt (releaseRequestedAt + 24h)
|
||||
const enriched = devices.map((d) => {
|
||||
const [magic, protectedDevices] = await Promise.all([
|
||||
listMagicDevices(user.id),
|
||||
listProtectedDevices(user.id),
|
||||
]);
|
||||
|
||||
const magicItems = magic.map((d) => {
|
||||
let releaseAvailableAt: string | null = null;
|
||||
if (d.releaseRequestedAt) {
|
||||
const availableAt = new Date(
|
||||
@ -22,8 +31,9 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
return {
|
||||
source: "magic" as const,
|
||||
deviceId: d.deviceId,
|
||||
hostname: d.hostname,
|
||||
hostname: d.hostname ?? "Unbenanntes Ger\u00e4t",
|
||||
model: d.model,
|
||||
osVersion: d.osVersion,
|
||||
magicEnrolledAt: d.magicEnrolledAt.toISOString(),
|
||||
@ -32,8 +42,20 @@ export default defineEventHandler(async (event) => {
|
||||
};
|
||||
});
|
||||
|
||||
const protectedItems = protectedDevices.map((d) => ({
|
||||
source: "protected" as const,
|
||||
deviceId: d.id,
|
||||
hostname: d.label,
|
||||
model: d.platform,
|
||||
osVersion: null as string | null,
|
||||
magicEnrolledAt: (d.installedAt ?? d.createdAt).toISOString(),
|
||||
releaseRequestedAt: null as string | null,
|
||||
releaseAvailableAt: null as string | null,
|
||||
}));
|
||||
|
||||
// Magic-Bindings zuerst (neuste), dann alte ProtectedDevices
|
||||
return {
|
||||
success: true,
|
||||
data: enriched,
|
||||
data: [...magicItems, ...protectedItems],
|
||||
};
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user