iOS: Ability to view the log
This commit is contained in:
parent
16b9f191d9
commit
f36f7e456c
|
@ -160,6 +160,7 @@
|
||||||
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
|
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
|
||||||
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */; };
|
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */; };
|
||||||
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */; };
|
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */; };
|
||||||
|
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */; };
|
||||||
6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; };
|
6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; };
|
||||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
|
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
|
||||||
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
|
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
|
||||||
|
@ -364,6 +365,7 @@
|
||||||
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
||||||
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
|
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
|
||||||
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewCell.swift; sourceTree = "<group>"; };
|
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
|
||||||
6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
|
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
|
||||||
6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; };
|
6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; };
|
||||||
|
@ -461,6 +463,7 @@
|
||||||
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
|
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
|
||||||
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */,
|
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */,
|
||||||
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */,
|
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */,
|
||||||
|
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */,
|
||||||
);
|
);
|
||||||
path = ViewController;
|
path = ViewController;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1343,6 +1346,7 @@
|
||||||
6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
||||||
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
|
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
|
||||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
||||||
|
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
|
||||||
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||||
5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
|
5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
|
||||||
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
||||||
|
|
|
@ -209,10 +209,14 @@
|
||||||
"settingsSectionTitleExportConfigurations" = "Export configurations";
|
"settingsSectionTitleExportConfigurations" = "Export configurations";
|
||||||
"settingsExportZipButtonTitle" = "Export zip archive";
|
"settingsExportZipButtonTitle" = "Export zip archive";
|
||||||
|
|
||||||
"settingsSectionTitleTunnelLog" = "Tunnel log";
|
"settingsSectionTitleTunnelLog" = "Log";
|
||||||
"settingsExportLogFileButtonTitle" = "Export log file";
|
"settingsViewLogButtonTitle" = "View log";
|
||||||
|
|
||||||
// Settings alerts
|
// Log view
|
||||||
|
|
||||||
|
"logViewTitle" = "Log";
|
||||||
|
|
||||||
|
// Log alerts
|
||||||
|
|
||||||
"alertUnableToRemovePreviousLogTitle" = "Log export failed";
|
"alertUnableToRemovePreviousLogTitle" = "Log export failed";
|
||||||
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
|
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class LogViewController: UIViewController {
|
||||||
|
|
||||||
|
let textView: UITextView = {
|
||||||
|
let textView = UITextView()
|
||||||
|
textView.isEditable = false
|
||||||
|
textView.isSelectable = false
|
||||||
|
textView.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
|
||||||
|
textView.adjustsFontForContentSizeCategory = true
|
||||||
|
return textView
|
||||||
|
}()
|
||||||
|
|
||||||
|
let busyIndicator: UIActivityIndicatorView = {
|
||||||
|
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||||
|
busyIndicator.hidesWhenStopped = true
|
||||||
|
return busyIndicator
|
||||||
|
}()
|
||||||
|
|
||||||
|
var logViewHelper: LogViewHelper?
|
||||||
|
var isFetchingLogEntries = false
|
||||||
|
private var updateLogEntriesTimer: Timer?
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
|
||||||
|
view.addSubview(textView)
|
||||||
|
textView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
textView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
view.addSubview(busyIndicator)
|
||||||
|
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
|
busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
busyIndicator.startAnimating()
|
||||||
|
|
||||||
|
logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
|
||||||
|
startUpdatingLogEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
title = tr("logViewTitle")
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped(sender:)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLogEntries() {
|
||||||
|
guard !isFetchingLogEntries else { return }
|
||||||
|
isFetchingLogEntries = true
|
||||||
|
logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
|
||||||
|
guard let self = self else { return }
|
||||||
|
defer {
|
||||||
|
self.isFetchingLogEntries = false
|
||||||
|
}
|
||||||
|
if self.busyIndicator.isAnimating {
|
||||||
|
self.busyIndicator.stopAnimating()
|
||||||
|
}
|
||||||
|
guard !fetchedLogEntries.isEmpty else { return }
|
||||||
|
let isScrolledToEnd = self.textView.contentSize.height - self.textView.bounds.height - self.textView.contentOffset.y < 1
|
||||||
|
let text = fetchedLogEntries.reduce("") { $0 + $1.text() + "\n" }
|
||||||
|
self.textView.insertText(text)
|
||||||
|
if isScrolledToEnd {
|
||||||
|
let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)
|
||||||
|
self.textView.scrollRangeToVisible(endOfCurrentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startUpdatingLogEntries() {
|
||||||
|
updateLogEntries()
|
||||||
|
updateLogEntriesTimer?.invalidate()
|
||||||
|
let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateLogEntries()
|
||||||
|
}
|
||||||
|
updateLogEntriesTimer = timer
|
||||||
|
RunLoop.main.add(timer, forMode: .common)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func saveTapped(sender: AnyObject) {
|
||||||
|
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
||||||
|
|
||||||
|
let dateFormatter = ISO8601DateFormatter()
|
||||||
|
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
|
||||||
|
let timeStampString = dateFormatter.string(from: Date())
|
||||||
|
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||||
|
let isDeleted = FileManager.deleteFile(at: destinationURL)
|
||||||
|
if !isDeleted {
|
||||||
|
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard isWritten else {
|
||||||
|
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
|
||||||
|
if let sender = sender as? UIBarButtonItem {
|
||||||
|
activityVC.popoverPresentationController?.barButtonItem = sender
|
||||||
|
}
|
||||||
|
activityVC.completionWithItemsHandler = { _, _, _, _ in
|
||||||
|
// Remove the exported log file after the activity has completed
|
||||||
|
_ = FileManager.deleteFile(at: destinationURL)
|
||||||
|
}
|
||||||
|
self.present(activityVC, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,14 +10,14 @@ class SettingsTableViewController: UITableViewController {
|
||||||
case iosAppVersion
|
case iosAppVersion
|
||||||
case goBackendVersion
|
case goBackendVersion
|
||||||
case exportZipArchive
|
case exportZipArchive
|
||||||
case exportLogFile
|
case viewLog
|
||||||
|
|
||||||
var localizedUIString: String {
|
var localizedUIString: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
|
case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
|
||||||
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
|
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
|
||||||
case .exportZipArchive: return tr("settingsExportZipButtonTitle")
|
case .exportZipArchive: return tr("settingsExportZipButtonTitle")
|
||||||
case .exportLogFile: return tr("settingsExportLogFileButtonTitle")
|
case .viewLog: return tr("settingsViewLogButtonTitle")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ class SettingsTableViewController: UITableViewController {
|
||||||
let settingsFieldsBySection: [[SettingsFields]] = [
|
let settingsFieldsBySection: [[SettingsFields]] = [
|
||||||
[.iosAppVersion, .goBackendVersion],
|
[.iosAppVersion, .goBackendVersion],
|
||||||
[.exportZipArchive],
|
[.exportZipArchive],
|
||||||
[.exportLogFile]
|
[.viewLog]
|
||||||
]
|
]
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager?
|
let tunnelsManager: TunnelsManager?
|
||||||
|
@ -108,41 +108,10 @@ class SettingsTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportLogForLastActivatedTunnel(sourceView: UIView) {
|
func presentLogView() {
|
||||||
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
let logVC = LogViewController()
|
||||||
|
navigationController?.pushViewController(logVC, animated: true)
|
||||||
|
|
||||||
let dateFormatter = ISO8601DateFormatter()
|
|
||||||
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
|
|
||||||
let timeStampString = dateFormatter.string(from: Date())
|
|
||||||
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
|
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
|
||||||
let isDeleted = FileManager.deleteFile(at: destinationURL)
|
|
||||||
if !isDeleted {
|
|
||||||
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
guard isWritten else {
|
|
||||||
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
|
|
||||||
activityVC.popoverPresentationController?.sourceView = sourceView
|
|
||||||
activityVC.popoverPresentationController?.sourceRect = sourceView.bounds
|
|
||||||
activityVC.completionWithItemsHandler = { _, _, _, _ in
|
|
||||||
// Remove the exported log file after the activity has completed
|
|
||||||
_ = FileManager.deleteFile(at: destinationURL)
|
|
||||||
}
|
|
||||||
self.present(activityVC, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,11 +161,11 @@ extension SettingsTableViewController {
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
} else {
|
} else {
|
||||||
assert(field == .exportLogFile)
|
assert(field == .viewLog)
|
||||||
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = field.localizedUIString
|
cell.buttonText = field.localizedUIString
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
|
self?.presentLogView()
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue