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
|
let existing: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MagicDeviceSource: String, Codable {
|
||||||
|
case magic
|
||||||
|
case protected
|
||||||
|
}
|
||||||
|
|
||||||
struct MagicDevice: Codable, Identifiable {
|
struct MagicDevice: Codable, Identifiable {
|
||||||
|
let source: MagicDeviceSource?
|
||||||
let deviceId: String
|
let deviceId: String
|
||||||
let hostname: String
|
let hostname: String
|
||||||
let model: String?
|
let model: String?
|
||||||
@ -16,8 +22,11 @@ struct MagicDevice: Codable, Identifiable {
|
|||||||
let magicEnrolledAt: String
|
let magicEnrolledAt: String
|
||||||
let releaseRequestedAt: String?
|
let releaseRequestedAt: String?
|
||||||
let releaseAvailableAt: 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? {
|
var enrolledDate: Date? {
|
||||||
ISO8601DateFormatter().date(from: magicEnrolledAt)
|
ISO8601DateFormatter().date(from: magicEnrolledAt)
|
||||||
|
|||||||
@ -279,8 +279,19 @@ private struct HubDeviceRow: View {
|
|||||||
.frame(width: 28)
|
.frame(width: 28)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(device.hostname)
|
HStack(spacing: 6) {
|
||||||
.font(.callout.bold())
|
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) {
|
HStack(spacing: 8) {
|
||||||
if let model = device.model {
|
if let model = device.model {
|
||||||
Text(model).font(.caption2).foregroundStyle(.secondary)
|
Text(model).font(.caption2).foregroundStyle(.secondary)
|
||||||
@ -294,7 +305,11 @@ private struct HubDeviceRow: View {
|
|||||||
|
|
||||||
Spacer()
|
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) {
|
VStack(alignment: .trailing, spacing: 2) {
|
||||||
Label("Freigabe läuft", systemImage: "hourglass")
|
Label("Freigabe läuft", systemImage: "hourglass")
|
||||||
.font(.caption2.bold())
|
.font(.caption2.bold())
|
||||||
|
|||||||
@ -221,7 +221,7 @@ struct MacRegistrationView: View {
|
|||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
isInstallingProfile = false
|
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)
|
// Re-check profile status nach kurzer Wartezeit (User muss in UI bestätigen)
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
|||||||
ios: {
|
ios: {
|
||||||
supportsTablet: true,
|
supportsTablet: true,
|
||||||
bundleIdentifier: MAIN_BUNDLE,
|
bundleIdentifier: MAIN_BUNDLE,
|
||||||
buildNumber: "67",
|
buildNumber: "68",
|
||||||
// Apple Sign-In Entitlement — Pflicht für expo-apple-authentication nativen
|
// Apple Sign-In Entitlement — Pflicht für expo-apple-authentication nativen
|
||||||
// signInAsync()-Flow. Ohne flag generiert Expo's prebuild den
|
// signInAsync()-Flow. Ohne flag generiert Expo's prebuild den
|
||||||
// com.apple.developer.applesignin-Entitlement nicht in die .entitlements.
|
// com.apple.developer.applesignin-Entitlement nicht in die .entitlements.
|
||||||
@ -59,7 +59,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
|||||||
|
|
||||||
android: {
|
android: {
|
||||||
package: "org.rebreak.app",
|
package: "org.rebreak.app",
|
||||||
versionCode: 50,
|
versionCode: 51,
|
||||||
adaptiveIcon: {
|
adaptiveIcon: {
|
||||||
// Foreground muss in der ~66%-Safe-Zone bleiben (Launcher-Mask clippt den
|
// Foreground muss in der ~66%-Safe-Zone bleiben (Launcher-Mask clippt den
|
||||||
// Außenring) → adaptive-foreground.png ist das Logo auf transparentem
|
// Außenring) → adaptive-foreground.png ist das Logo auf transparentem
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.3.13</string>
|
<string>0.3.13</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>67</string>
|
<string>68</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.3.13</string>
|
<string>0.3.13</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>67</string>
|
<string>68</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.3.13</string>
|
<string>0.3.13</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>67</string>
|
<string>68</string>
|
||||||
<key>EXAppExtensionAttributes</key>
|
<key>EXAppExtensionAttributes</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>EXExtensionPointIdentifier</key>
|
<key>EXExtensionPointIdentifier</key>
|
||||||
|
|||||||
@ -1,18 +1,27 @@
|
|||||||
import { listMagicDevices } from "../../db/devices";
|
import { listMagicDevices } from "../../db/devices";
|
||||||
|
import { listProtectedDevices } from "../../db/protectedDevices";
|
||||||
import { requireUser } from "../../utils/auth";
|
import { requireUser } from "../../utils/auth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/magic/devices
|
* GET /api/magic/devices
|
||||||
*
|
*
|
||||||
* Listet alle aktiven Magic-Bindings des Users für UI.
|
* Listet alle gesch\u00fctzten Ger\u00e4te des Users f\u00fcr den Magic-Hub. Vereinigt:
|
||||||
* Response: [{ deviceId, hostname, model, osVersion, magicEnrolledAt, releaseRequestedAt, releaseAvailableAt }]
|
* - 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) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const user = await requireUser(event);
|
const user = await requireUser(event);
|
||||||
const devices = await listMagicDevices(user.id);
|
|
||||||
|
|
||||||
// Berechne releaseAvailableAt (releaseRequestedAt + 24h)
|
const [magic, protectedDevices] = await Promise.all([
|
||||||
const enriched = devices.map((d) => {
|
listMagicDevices(user.id),
|
||||||
|
listProtectedDevices(user.id),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const magicItems = magic.map((d) => {
|
||||||
let releaseAvailableAt: string | null = null;
|
let releaseAvailableAt: string | null = null;
|
||||||
if (d.releaseRequestedAt) {
|
if (d.releaseRequestedAt) {
|
||||||
const availableAt = new Date(
|
const availableAt = new Date(
|
||||||
@ -22,8 +31,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
source: "magic" as const,
|
||||||
deviceId: d.deviceId,
|
deviceId: d.deviceId,
|
||||||
hostname: d.hostname,
|
hostname: d.hostname ?? "Unbenanntes Ger\u00e4t",
|
||||||
model: d.model,
|
model: d.model,
|
||||||
osVersion: d.osVersion,
|
osVersion: d.osVersion,
|
||||||
magicEnrolledAt: d.magicEnrolledAt.toISOString(),
|
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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: enriched,
|
data: [...magicItems, ...protectedItems],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user