diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 48eb530..5071de3 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -737,6 +737,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = "4.1-beta2"; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -761,6 +762,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = "4.1-beta2"; PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/phpmon/Assets.xcassets/GreenLock.imageset/GreenLock.svg b/phpmon/Assets.xcassets/GreenLock.imageset/GreenLock.svg deleted file mode 100644 index d81604f..0000000 --- a/phpmon/Assets.xcassets/GreenLock.imageset/GreenLock.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/phpmon/Assets.xcassets/GreenLock.imageset/Contents.json b/phpmon/Assets.xcassets/IconLinked.imageset/Contents.json similarity index 73% rename from phpmon/Assets.xcassets/GreenLock.imageset/Contents.json rename to phpmon/Assets.xcassets/IconLinked.imageset/Contents.json index 6501997..6cb3d9a 100644 --- a/phpmon/Assets.xcassets/GreenLock.imageset/Contents.json +++ b/phpmon/Assets.xcassets/IconLinked.imageset/Contents.json @@ -5,7 +5,7 @@ "scale" : "1x" }, { - "filename" : "GreenLock.svg", + "filename" : "link.svg", "idiom" : "universal", "scale" : "2x" }, @@ -17,5 +17,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/phpmon/Assets.xcassets/IconLinked.imageset/link.svg b/phpmon/Assets.xcassets/IconLinked.imageset/link.svg new file mode 100644 index 0000000..f0fdbf4 --- /dev/null +++ b/phpmon/Assets.xcassets/IconLinked.imageset/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpmon/Assets.xcassets/RedLock.imageset/Contents.json b/phpmon/Assets.xcassets/IconParked.imageset/Contents.json similarity index 72% rename from phpmon/Assets.xcassets/RedLock.imageset/Contents.json rename to phpmon/Assets.xcassets/IconParked.imageset/Contents.json index 116378f..24b6de7 100644 --- a/phpmon/Assets.xcassets/RedLock.imageset/Contents.json +++ b/phpmon/Assets.xcassets/IconParked.imageset/Contents.json @@ -5,7 +5,7 @@ "scale" : "1x" }, { - "filename" : "RedLock.svg", + "filename" : "car-alt.svg", "idiom" : "universal", "scale" : "2x" }, @@ -17,5 +17,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/phpmon/Assets.xcassets/IconParked.imageset/car-alt.svg b/phpmon/Assets.xcassets/IconParked.imageset/car-alt.svg new file mode 100644 index 0000000..90a786e --- /dev/null +++ b/phpmon/Assets.xcassets/IconParked.imageset/car-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpmon/Assets.xcassets/Search.imageset/Contents.json b/phpmon/Assets.xcassets/Lock.imageset/Contents.json similarity index 73% rename from phpmon/Assets.xcassets/Search.imageset/Contents.json rename to phpmon/Assets.xcassets/Lock.imageset/Contents.json index 44e7f88..9069859 100644 --- a/phpmon/Assets.xcassets/Search.imageset/Contents.json +++ b/phpmon/Assets.xcassets/Lock.imageset/Contents.json @@ -5,7 +5,7 @@ "scale" : "1x" }, { - "filename" : "search.svg", + "filename" : "lock.svg", "idiom" : "universal", "scale" : "2x" }, @@ -17,5 +17,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/phpmon/Assets.xcassets/Lock.imageset/lock.svg b/phpmon/Assets.xcassets/Lock.imageset/lock.svg new file mode 100644 index 0000000..86a172a --- /dev/null +++ b/phpmon/Assets.xcassets/Lock.imageset/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/phpmon/Assets.xcassets/RedLock.imageset/RedLock.svg b/phpmon/Assets.xcassets/RedLock.imageset/RedLock.svg deleted file mode 100644 index cedd503..0000000 --- a/phpmon/Assets.xcassets/RedLock.imageset/RedLock.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/phpmon/Assets.xcassets/Search.imageset/search.svg b/phpmon/Assets.xcassets/Search.imageset/search.svg deleted file mode 100644 index 7e46970..0000000 --- a/phpmon/Assets.xcassets/Search.imageset/search.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/phpmon/Domain/Core/Base.lproj/Main.storyboard b/phpmon/Domain/Core/Base.lproj/Main.storyboard index 9ce73da..4c6a3e8 100644 --- a/phpmon/Domain/Core/Base.lproj/Main.storyboard +++ b/phpmon/Domain/Core/Base.lproj/Main.storyboard @@ -3,6 +3,7 @@ + @@ -518,7 +519,7 @@ Gw - + @@ -528,34 +529,54 @@ Gw + + + + + + + + + + + + + + + + + + + + - + - + - + - + - - + + @@ -599,46 +620,73 @@ Gw - + - - - - - - - - - + - + - + + - + + - + + + + + - @@ -656,7 +704,7 @@ Gw - - - - - - - - - - - - - - - - - - - - + - - - - - - - + - - + + diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 48cfe8f..615e345 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -66,35 +66,48 @@ class Valet { // MARK: - Structs class Site { - var name: String + var name: String! - var absolutePath: String + var absolutePath: String! var aliasPath: String? - var secured: Bool - init(absolutePath: String, tld: String) { + var secured: Bool! + var driver: String = "???" + + init() {} + + convenience init(absolutePath: String, tld: String) { + self.init() self.absolutePath = absolutePath - self.aliasPath = nil self.name = URL(string: absolutePath)!.lastPathComponent - self.secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name).\(tld).key") + self.aliasPath = nil + determineSecured(tld) + determineDriver() } convenience init(aliasPath: String, tld: String) { - // Resolve the symlink - let absolutePath = try! FileManager.default - .destinationOfSymbolicLink(atPath: aliasPath) - self.init(absolutePath: absolutePath, tld: tld) - - // TODO: Make sure the destination is a valid directory! - - // The name should be identical to the alias' name + self.init() + self.absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath) self.name = URL(string: aliasPath)!.lastPathComponent - - // Update the alias' path self.aliasPath = aliasPath - - // Make sure we check again, this time for the aliased file - self.secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name).\(tld).key") + determineSecured(tld) + determineDriver() + } + + public func determineSecured(_ tld: String) { + self.secured = Shell.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key") + } + + public func determineDriver() { + let driver = Shell.pipe("cd \(absolutePath!) && valet which", requiresPath: true) + if driver.contains("This site is served by") { + self.driver = driver + // TODO: Use a regular expression to retrieve the driver instead? + .replacingOccurrences(of: "This site is served by [", with: "") + .replacingOccurrences(of: "ValetDriver].\n", with: "") + } else { + self.driver = "???" + } } } diff --git a/phpmon/Domain/SiteList/SiteListCell.swift b/phpmon/Domain/SiteList/SiteListCell.swift index 1cd7c5d..c514b80 100644 --- a/phpmon/Domain/SiteList/SiteListCell.swift +++ b/phpmon/Domain/SiteList/SiteListCell.swift @@ -15,9 +15,10 @@ class SiteListCell: NSTableCellView @IBOutlet weak var labelPathName: NSTextField! @IBOutlet weak var imageViewLock: NSImageView! + @IBOutlet weak var imageViewType: NSImageView! + @IBOutlet weak var labelDriver: NSTextField! @IBOutlet weak var labelPhpVersion: NSTextField! - @IBOutlet weak var labelSiteType: NSTextField! override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) diff --git a/phpmon/Domain/SiteList/SiteListVC.swift b/phpmon/Domain/SiteList/SiteListVC.swift index 5bc4109..c44261a 100644 --- a/phpmon/Domain/SiteList/SiteListVC.swift +++ b/phpmon/Domain/SiteList/SiteListVC.swift @@ -10,12 +10,10 @@ import Cocoa import HotKey import Carbon -class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate { +class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { // MARK: - Outlets - @IBOutlet weak var textFieldSearch: NSTextField! - @IBOutlet weak var tableView: NSTableView! public var editorAvailability: [String] = [] @@ -26,15 +24,15 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource, public static func show(delegate: NSWindowDelegate? = nil) { if (App.shared.siteListWindowController == nil) { - let vc = NSStoryboard(name: "Main", bundle: nil) - .instantiateController(withIdentifier: "siteList") as! SiteListVC - let window = NSWindow(contentViewController: vc) + let storyboard = NSStoryboard(name: "Main" , bundle : nil) + let windowController = (storyboard.instantiateController(withIdentifier: "siteListWindow")) as! SiteListWC - window.title = "site_list.title".localized - window.delegate = delegate - window.styleMask = [.titled, .closable, .resizable] + windowController.window!.title = "site_list.title".localized + windowController.window!.delegate = delegate + windowController.window!.styleMask = [.titled, .closable, .resizable] + windowController.window!.maxSize = NSSize(width: 800, height: 10000) - App.shared.siteListWindowController = SiteListWC(window: window) + App.shared.siteListWindowController = windowController } App.shared.siteListWindowController!.showWindow(self) @@ -70,19 +68,26 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource, let item = self.sites[row] /// Make sure to show the TLD - userCell.labelSiteName.stringValue = "\(item.name).\(Valet.shared.config.tld)" + userCell.labelSiteName.stringValue = "\(item.name!).\(Valet.shared.config.tld)" /// Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability userCell.labelPathName.stringValue = item.absolutePath .replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~") /// If the `aliasPath` is nil, we're dealing with a parked site. Otherwise, it's a link that was explicitly created. - userCell.labelSiteType.stringValue = item.aliasPath == nil - ? "Parked Site" - : "Linked Site" + userCell.imageViewType.image = NSImage( + named: item.aliasPath == nil + ? "IconParked" + : "IconLinked" + ) + userCell.imageViewType.contentTintColor = NSColor.tertiaryLabelColor /// Show the green or red lock based on whether the site was secured - userCell.imageViewLock.image = NSImage(named: item.secured ? "GreenLock" : "RedLock") + userCell.imageViewLock.contentTintColor = item.secured ? NSColor.systemGreen + : NSColor.red + + /// Show the current driver + userCell.labelDriver.stringValue = item.driver /// TODO: Load the correct PHP version (not determined as of yet) userCell.labelPhpVersion.stringValue = "PHP 8.0" @@ -104,7 +109,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource, withTitle: site.secured ? "site_list.unsecure".localized : "site_list.secure".localized, - action: nil, + action: #selector(toggleSecure), keyEquivalent: "L" ) @@ -146,19 +151,43 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource, // MARK: Secure / unsecure @objc public func toggleSecure() { - // TODO + let rowToReload = self.tableView.selectedRow + let site = self.sites[self.tableView.selectedRow] + let previous = site.secured + let action = site.secured ? "unsecure" : "secure" + + let command = "cd \(site.absolutePath!) && sudo \(Paths.valet) \(action) && exit;" + let _ = Shell.pipe(command, requiresPath: true) + + site.determineSecured(Valet.shared.config.tld) + + if site.secured == previous { + Alert.notify( + message: "SSL status not changed", + info: "Something went wrong. Try running the command in your terminal manually: `\(command)`") + } else { + let newState = site.secured ? "secured" : "unsecured" + LocalNotification.send( + title: "SSL status changed", + subtitle: "The domain '\(site.name!).\(Valet.shared.config.tld)' is now \(newState)." + ) + } + + tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0]) + tableView.deselectRow(rowToReload) + tableView.selectRowIndexes([rowToReload], byExtendingSelection: true) } // MARK: Open with IDE / Editor @objc public func openWithPhpStorm() { let site = self.sites[self.tableView.selectedRow] - Shell.run("open -a /Applications/PhpStorm.app \(site.absolutePath)") + Shell.run("open -a /Applications/PhpStorm.app \(site.absolutePath!)") } @objc public func openWithVSCode() { let site = self.sites[self.tableView.selectedRow] - Shell.run("/usr/local/bin/code \(site.absolutePath)") + Shell.run("/usr/local/bin/code \(site.absolutePath!)") } // MARK: Open in Browser & Finder @@ -166,19 +195,19 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource, @objc public func openInBrowser() { let site = self.sites[self.tableView.selectedRow] let prefix = site.secured ? "https://" : "http://" - let url = "\(prefix)\(site.name).\(Valet.shared.config.tld)" + let url = "\(prefix)\(site.name!).\(Valet.shared.config.tld)" NSWorkspace.shared.open(URL(string: url)!) } @objc public func openInFinder() { let site = self.sites[self.tableView.selectedRow] - Shell.run("open \(site.absolutePath)") + Shell.run("open \(site.absolutePath!)") } // MARK: - (Search) Text Field Delegate - func controlTextDidChange(_ obj: Notification) { - let searchString = self.textFieldSearch.stringValue.lowercased() + func searchedFor(text: String) { + let searchString = text.lowercased() if searchString.isEmpty { self.sites = Valet.shared.sites diff --git a/phpmon/Domain/SiteList/SiteListWC.swift b/phpmon/Domain/SiteList/SiteListWC.swift index 2a3122a..0e3ac47 100644 --- a/phpmon/Domain/SiteList/SiteListWC.swift +++ b/phpmon/Domain/SiteList/SiteListWC.swift @@ -8,10 +8,24 @@ import Cocoa -class SiteListWC: NSWindowController { +class SiteListWC: NSWindowController, NSSearchFieldDelegate { + + @IBOutlet weak var searchToolbarItem: NSSearchToolbarItem! override func windowDidLoad() { super.windowDidLoad() + self.searchToolbarItem.searchField.delegate = self + self.searchToolbarItem.searchField.becomeFirstResponder() + } + + func controlTextDidChange(_ notification: Notification) { + guard let searchField = notification.object as? NSSearchField else { + print("Unexpected control in update notification") + return + } + + let window = self.contentViewController as! SiteListVC + window.searchedFor(text: searchField.stringValue) } } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index fd91fcf..cbfd71d 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -50,7 +50,7 @@ // SITE LIST -"site_list.title" = "Linked & Parked Domains"; +"site_list.title" = "Domains"; // SITE LIST ACTIONS