passepartout-apple/Passepartout/App/Views/DiagnosticsView+OpenVPN.swift
2022-05-01 19:48:24 +02:00

194 lines
6.4 KiB
Swift

//
// DiagnosticsView+OpenVPN.swift
// Passepartout
//
// Created by Davide De Rosa on 3/11/22.
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import SwiftUI
import PassepartoutCore
import TunnelKitOpenVPN
extension DiagnosticsView {
struct OpenVPNView: View {
enum AlertType: Int, Identifiable {
case emailNotConfigured
var id: Int {
return rawValue
}
}
@ObservedObject private var appManager: AppManager
@ObservedObject private var providerManager: ProviderManager
@ObservedObject private var vpnManager: VPNManager
@ObservedObject private var currentVPNState: VPNManager.ObservableState
@ObservedObject private var productManager: ProductManager
private let providerName: ProviderName?
private var isEligibleForFeedback: Bool {
productManager.isEligibleForFeedback()
}
@State private var isReportingIssue = false
@State private var alertType: AlertType?
private let vpnProtocol: VPNProtocolType = .openVPN
private let logUpdateInterval = Constants.Log.tunnelLogRefreshInterval
init(providerName: ProviderName?) {
appManager = .shared
providerManager = .shared
vpnManager = .shared
currentVPNState = .shared
productManager = .shared
self.providerName = providerName
}
var body: some View {
List {
serverConfigurationSection
debugLogSection
// eligibility: to report a connectivity issue
if isEligibleForFeedback {
issueReporterSection
}
}.sheet(isPresented: $isReportingIssue, content: reportIssueView)
.alert(item: $alertType, content: presentedAlert)
}
private func presentedAlert(_ alertType: AlertType) -> Alert {
switch alertType {
case .emailNotConfigured:
return Alert(
title: Text(L10n.ReportIssue.Alert.title),
message: Text(L10n.Global.Messages.emailNotConfigured),
dismissButton: .cancel(Text(L10n.Global.Strings.ok))
)
}
}
private var serverConfigurationSection: some View {
Section {
let cfg = currentServerConfiguration
NavigationLink(L10n.Diagnostics.Items.ServerConfiguration.caption) {
cfg.map {
EndpointAdvancedView.OpenVPNView(
builder: .constant($0),
isReadonly: true,
isServerPushed: true
).navigationTitle(L10n.Diagnostics.Items.ServerConfiguration.caption)
}
}.disabled(cfg == nil)
}
}
private var debugLogSection: some View {
Section {
let url = debugLogURL
NavigationLink(L10n.DebugLog.title) {
url.map {
DebugLogView(
url: $0,
updateInterval: logUpdateInterval
)
}
}.disabled(url == nil)
Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $appManager.masksPrivateData)
} footer: {
Text(L10n.Diagnostics.Sections.DebugLog.footer)
}
}
private var issueReporterSection: some View {
Section {
Button(L10n.Diagnostics.Items.ReportIssue.caption, action: presentReportIssue)
}
}
private func reportIssueView() -> some View {
let logURL = vpnManager.debugLogURL(forProtocol: vpnProtocol)
var metadata: ProviderMetadata?
var lastUpdate: Date?
if let name = providerName {
metadata = providerManager.provider(withName: name)
lastUpdate = providerManager.lastUpdate(name, vpnProtocol: vpnProtocol)
}
return ReportIssueView(
isPresented: $isReportingIssue,
vpnProtocol: vpnProtocol,
logURL: logURL,
providerMetadata: metadata,
lastUpdate: lastUpdate
)
}
}
}
extension DiagnosticsView.OpenVPNView {
private var currentServerConfiguration: OpenVPN.ConfigurationBuilder? {
guard currentVPNState.vpnStatus == .connected else {
return nil
}
guard let cfg = vpnManager.serverConfiguration(forProtocol: vpnProtocol) as? OpenVPN.Configuration else {
return nil
}
// "withFallbacks: false" for view to hide nil options
return cfg.builder(withFallbacks: false)
}
private var debugLogURL: URL? {
return vpnManager.debugLogURL(forProtocol: vpnProtocol)
}
}
extension DiagnosticsView.OpenVPNView {
private func presentReportIssue() {
guard MailComposerView.canSendMail() else {
openReportIssueMailTo()
return
}
isReportingIssue = true
}
private func openReportIssueMailTo() {
let V = Unlocalized.Issues.self
let body = V.body(V.template, DebugLog(content: "--").decoratedString())
guard let url = URL.mailto(to: V.recipient, subject: V.subject, body: body) else {
return
}
guard URL.openURL(url) else {
alertType = .emailNotConfigured
return
}
}
}