Editor: Passwort unter Benutzername, Live-Validierung, Fehlerbericht kopieren, Doku
This commit is contained in:
@@ -43,6 +43,9 @@ struct EmbeddedTerminalView: View {
|
||||
.lineLimit(3)
|
||||
}
|
||||
Spacer()
|
||||
Button("Bericht kopieren") { copyReport() }
|
||||
.buttonStyle(.link).font(.caption)
|
||||
.help("Kopiert Profil (ohne Passwort), Status und letzte Ausgabe — hierher schicken zur Fehlersuche")
|
||||
Button("Diagnose") { diagnose() }.buttonStyle(.link).font(.caption)
|
||||
Button("Erneut") { connect() }.buttonStyle(.link).font(.caption)
|
||||
}
|
||||
@@ -129,6 +132,20 @@ struct EmbeddedTerminalView: View {
|
||||
input = ""
|
||||
}
|
||||
|
||||
/// Fehlerbericht ohne Secrets (Passwort steht weder im Profil noch im Befehl).
|
||||
/// Hinweis: Wer das Passwort sichtbar eingetippt hat (nicht Auge-Modus),
|
||||
/// löscht die Ausgabe vorher per „Leeren".
|
||||
func copyReport() {
|
||||
var r = "RemminaMac Fehlerbericht\n"
|
||||
r += "Profil: \(profile.name) | \(profile.proto.rawValue) | \(profile.username)@\(profile.host):\(profile.port)\n"
|
||||
r += "Auto-Login: \(term.autoLogin ? "ja" : "nein") | sshpass: \(SSHService.findSshpass() ?? "fehlt")\n"
|
||||
r += "Status: \(statusText)\n"
|
||||
let tail = term.lines.suffix(20).map { $0.map(\.text).joined() }.joined(separator: "\n")
|
||||
r += "--- letzte Ausgabe ---\n\(tail)\n"
|
||||
NSPasteboard.general.clearContents()
|
||||
NSPasteboard.general.setString(r, forType: .string)
|
||||
}
|
||||
|
||||
var failed: Bool {
|
||||
if case .done(let code, _) = term.state {
|
||||
return (code ?? 1) != 0
|
||||
|
||||
@@ -16,6 +16,8 @@ struct ProfileEditorView: View {
|
||||
self.onSave = onSave
|
||||
}
|
||||
|
||||
var liveWarnings: [String] { ProfileValidation.warnings(for: profile) }
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Text(isNew ? "Neues Profil" : "Profil bearbeiten").font(.headline).padding()
|
||||
@@ -37,26 +39,37 @@ struct ProfileEditorView: View {
|
||||
}
|
||||
Section("Verbindung") {
|
||||
TextField("Host", text: $profile.host)
|
||||
.autocorrectionDisabled()
|
||||
TextField("Port", value: $profile.port, format: .number)
|
||||
TextField("Benutzername", text: $profile.username)
|
||||
TextField("Auflösung (RDP, z. B. 1920x1080 oder Automatisch)", text: $profile.resolution)
|
||||
TextField("Extra-Argumente", text: $profile.extraArgs)
|
||||
}
|
||||
Section("Authentifizierung") {
|
||||
Picker("SSH-Key", selection: $profile.sshKeyID) {
|
||||
Text("Kein Key").tag(UUID?.none)
|
||||
ForEach(store.sshKeys) { k in Text(k.name).tag(UUID?.some(k.id)) }
|
||||
}
|
||||
.autocorrectionDisabled()
|
||||
// Passwort direkt unter dem Benutzernamen (Keychain-gespeichert)
|
||||
if storedPasswordPresent && password.isEmpty {
|
||||
HStack {
|
||||
Image(systemName: "checkmark.circle.fill").foregroundStyle(.green)
|
||||
Text("Passwort in Keychain gespeichert")
|
||||
Text("Passwort gespeichert")
|
||||
Spacer()
|
||||
Button("Löschen", role: .destructive) { deleteStoredPassword() }
|
||||
}
|
||||
}
|
||||
SecureField(storedPasswordPresent ? "Neues Passwort …" : "Passwort (optional, Keychain)", text: $password)
|
||||
SecureField(storedPasswordPresent ? "Neues Passwort …" : "Passwort", text: $password)
|
||||
Toggle("Passwort in Keychain speichern", isOn: $savePassword)
|
||||
TextField("Auflösung (RDP, z. B. 1920x1080 oder Automatisch)", text: $profile.resolution)
|
||||
TextField("Extra-Argumente", text: $profile.extraArgs)
|
||||
if !liveWarnings.isEmpty {
|
||||
ForEach(liveWarnings, id: \.self) { w in
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "exclamationmark.triangle").foregroundStyle(.orange)
|
||||
Text(w).font(.caption).foregroundStyle(.orange)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section("SSH-Key (optional)") {
|
||||
Picker("SSH-Key", selection: $profile.sshKeyID) {
|
||||
Text("Kein Key (Passwort-Login)").tag(UUID?.none)
|
||||
ForEach(store.sshKeys) { k in Text(k.name).tag(UUID?.some(k.id)) }
|
||||
}
|
||||
Text(sshpassHint).font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +79,7 @@ struct ProfileEditorView: View {
|
||||
Button("Abbrechen", role: .cancel) { dismiss() }
|
||||
Spacer()
|
||||
Button("Speichern") {
|
||||
var toSave = profile
|
||||
var toSave = ProfileValidation.cleaned(profile)
|
||||
if toSave.port == 0 { toSave.port = toSave.proto.defaultPort }
|
||||
if toSave.passwordAccount == nil { toSave.passwordAccount = "profile-\(toSave.id.uuidString)" }
|
||||
if savePassword, !password.isEmpty, let acct = toSave.passwordAccount {
|
||||
@@ -76,11 +89,12 @@ struct ProfileEditorView: View {
|
||||
dismiss()
|
||||
}
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.disabled(profile.host.isEmpty || profile.name.isEmpty)
|
||||
.disabled(profile.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
|| profile.host.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.frame(width: 520, height: 640)
|
||||
.frame(width: 520, height: 680)
|
||||
.onAppear {
|
||||
if let acct = profile.passwordAccount,
|
||||
let stored = KeychainHelper.load(account: acct), !stored.isEmpty {
|
||||
|
||||
Reference in New Issue
Block a user