|
| 1 | +// |
| 2 | +// TeamSettings.swift |
| 3 | +// TeamSettings |
| 4 | +// |
| 5 | +// Created by Janez Troha on 10/09/2021. |
| 6 | +// |
| 7 | + |
| 8 | +import Defaults |
| 9 | +import SwiftUI |
| 10 | + |
| 11 | +struct TeamSettingsView: View { |
| 12 | + @StateObject var teamSettings: TeamSettingsUpdater |
| 13 | + |
| 14 | + @Default(.teamID) var teamID |
| 15 | + @Default(.machineUUID) var machineUUID |
| 16 | + @Default(.sendHWInfo) var sendHWInfo |
| 17 | + @Default(.showBeta) var showBeta |
| 18 | + |
| 19 | + @State private var debugLinkURL: String = "" |
| 20 | + |
| 21 | + // SwiftUI-native alert handling |
| 22 | + @State private var alertData: InlineAlert? |
| 23 | + |
| 24 | + @Environment(\.openURL) private var openURL |
| 25 | + |
| 26 | + private struct InlineAlert: Identifiable { |
| 27 | + let id = UUID() |
| 28 | + let title: String |
| 29 | + let message: String? |
| 30 | + } |
| 31 | + |
| 32 | + private func copyIDsToPasteboard() { |
| 33 | + NSPasteboard.general.clearContents() |
| 34 | + NSPasteboard.general.setString("Team ID: \(teamID)\nMachine UUID: \(machineUUID)", forType: .string) |
| 35 | + alertData = InlineAlert(title: "Copied", message: "Team ID and Machine UUID have been copied to the clipboard.") |
| 36 | + } |
| 37 | + |
| 38 | + private func copyApps() { |
| 39 | + var logs = [String]() |
| 40 | + logs.append("Name, Bundle, Version") |
| 41 | + for app in PublicApp.all { |
| 42 | + logs.append("\(app.name), \(app.bundle), \(app.version)") |
| 43 | + } |
| 44 | + |
| 45 | + NSPasteboard.general.clearContents() |
| 46 | + NSPasteboard.general.setString(logs.joined(separator: "\n"), forType: .string) |
| 47 | + alertData = InlineAlert(title: "Copied", message: "List of installed apps has been copied to the clipboard.") |
| 48 | + } |
| 49 | + |
| 50 | + private func help() { |
| 51 | + if let url = URL(string: "https://support.apple.com/en-ie/guide/mac-help/mchlp2322/mac#mchl8c79215b") { |
| 52 | + openURL(url) |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + private func processDebugLinkURL() { |
| 57 | + let trimmed = debugLinkURL.trimmingCharacters(in: .whitespacesAndNewlines) |
| 58 | + guard let url = URL(string: trimmed), !trimmed.isEmpty else { |
| 59 | + alertData = InlineAlert(title: "Invalid URL", message: "Please enter a valid paretosecurity:// URL.") |
| 60 | + return |
| 61 | + } |
| 62 | + |
| 63 | + AppHandlers().processAction(url) |
| 64 | + debugLinkURL = "" |
| 65 | + } |
| 66 | + |
| 67 | + var body: some View { |
| 68 | + Group { |
| 69 | + if !teamID.isEmpty { |
| 70 | + Form { |
| 71 | + Section(footer: Text("Device Name").font(.caption)) { |
| 72 | + Text(AppInfo.machineName) |
| 73 | + .textSelection(.enabled) |
| 74 | + .contextMenu { |
| 75 | + Button("How to change", action: help) |
| 76 | + } |
| 77 | + } |
| 78 | + Section(footer: Text("Device ID").font(.caption)) { |
| 79 | + Text(machineUUID) |
| 80 | + .font(.system(.body, design: .monospaced)) |
| 81 | + .textSelection(.enabled) |
| 82 | + .contextMenu { |
| 83 | + Button("Copy", action: copyIDsToPasteboard) |
| 84 | + } |
| 85 | + } |
| 86 | + Section(footer: Text("When enabled, send model name and serial number.").font(.footnote)) { |
| 87 | + if teamSettings.forceSerialPush { |
| 88 | + Toggle("Send inventory info on update", isOn: $sendHWInfo) |
| 89 | + Text("Sending is requested by the team policy.") |
| 90 | + .font(.footnote) |
| 91 | + .foregroundStyle(.secondary) |
| 92 | + } else { |
| 93 | + Toggle("Send inventory info on update", isOn: $sendHWInfo) |
| 94 | + } |
| 95 | + Button("Copy App list") { copyApps() } |
| 96 | + } |
| 97 | + |
| 98 | + if showBeta { |
| 99 | + Section(footer: Text("Cloud API Endpoint").font(.caption)) { |
| 100 | + Text(Defaults[.teamAPI]) |
| 101 | + .font(.system(.body, design: .monospaced)) |
| 102 | + .textSelection(.enabled) |
| 103 | + .contextMenu { |
| 104 | + Button("Copy") { |
| 105 | + NSPasteboard.general.clearContents() |
| 106 | + NSPasteboard.general.setString(Defaults[.teamAPI], forType: .string) |
| 107 | + alertData = InlineAlert(title: "Copied", message: "Cloud API Endpoint copied to the clipboard.") |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + Section { |
| 114 | + HStack { |
| 115 | + Button("Unlink this device") { Defaults.toOpenSource() } |
| 116 | + Spacer() |
| 117 | + Link("Cloud Dashboard »", destination: AppInfo.teamsURL()) |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + .padding(20) |
| 122 | + .task { |
| 123 | + // Refresh team settings when the view appears |
| 124 | + teamSettings.update {} |
| 125 | + } |
| 126 | + } else { |
| 127 | + Form { |
| 128 | + Section { |
| 129 | + VStack(alignment: .leading, spacing: 6) { |
| 130 | + Text("Pareto Cloud provides a web dashboard for an overview of your company's devices. [Learn more »](https://paretosecurity.com/product/device-monitoring)") |
| 131 | + .font(.body) |
| 132 | + .foregroundStyle(.secondary) |
| 133 | + .fixedSize(horizontal: false, vertical: true) |
| 134 | + .lineLimit(nil) |
| 135 | + } |
| 136 | + } |
| 137 | + if showBeta { |
| 138 | + Section { |
| 139 | + // Use inline placeholder to avoid wide left-side form label |
| 140 | + TextField("Debug URL", text: $debugLinkURL, prompt: Text("paretosecurity://linkDevice/?invite_id=...")) |
| 141 | + .textFieldStyle(RoundedBorderTextFieldStyle()) |
| 142 | + .onSubmit { processDebugLinkURL() } |
| 143 | + |
| 144 | + HStack { |
| 145 | + Button("Process URL", action: processDebugLinkURL) |
| 146 | + .keyboardShortcut(.defaultAction) |
| 147 | + .disabled(debugLinkURL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) |
| 148 | + Spacer() |
| 149 | + } |
| 150 | + } footer: { |
| 151 | + VStack(alignment: .leading, spacing: 4) { |
| 152 | + Text("DEBUG: Enter a paretosecurity:// URL to trigger device linking").font(.footnote) |
| 153 | + Text("Host parameter options:").font(.footnote).fontWeight(.medium) |
| 154 | + Text("• Default (cloud): paretosecurity://linkDevice/?invite_id=123").font(.footnote) |
| 155 | + Text("• Complete URL: paretosecurity://linkDevice/?invite_id=123&host=https://api.example.com").font(.footnote) |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + .padding(20) |
| 161 | + } |
| 162 | + } |
| 163 | + .alert(item: $alertData) { data in |
| 164 | + Alert(title: Text(data.title), message: data.message.map(Text.init), dismissButton: .default(Text("OK"))) |
| 165 | + } |
| 166 | + } |
| 167 | +} |
| 168 | + |
0 commit comments