From d3bc96ee711fc1f950133643fc4c52010fc7818d Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 30 Oct 2025 15:55:39 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20WIP:=20Present=20certificate=20r?= =?UTF-8?q?enewal=20alert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 14 +++++- .../Domain List/UI/DomainListVC+Certs.swift | 50 +++++++++++++++++++ .../Modules/Domain List/UI/DomainListVC.swift | 20 ++++++-- phpmon/en.lproj/Localizable.strings | 7 ++- 4 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 phpmon/Modules/Domain List/UI/DomainListVC+Certs.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index c4a21ff9..7fcade17 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -105,6 +105,10 @@ 03CC1FF52E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; 03CC1FF62E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; 03CC1FF72E3D23130050FC18 /* ZshRunCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */; }; + 03DAD3A62EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DAD3A52EB3B08A003417BD /* DomainListVC+Certs.swift */; }; + 03DAD3A72EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DAD3A52EB3B08A003417BD /* DomainListVC+Certs.swift */; }; + 03DAD3A82EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DAD3A52EB3B08A003417BD /* DomainListVC+Certs.swift */; }; + 03DAD3A92EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03DAD3A52EB3B08A003417BD /* DomainListVC+Certs.swift */; }; 03FE39E72E81682800B7B5AC /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E52E81682800B7B5AC /* AppIcon.icon */; }; 03FE39E82E81682800B7B5AC /* AppIconEAP.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E62E81682800B7B5AC /* AppIconEAP.icon */; }; 03FE39EA2E81694500B7B5AC /* AppIconUD.icon in Resources */ = {isa = PBXBuildFile; fileRef = 03FE39E92E81694500B7B5AC /* AppIconUD.icon */; }; @@ -1018,6 +1022,7 @@ 03C099432EA15C8B00B76D43 /* Container+Real.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Container+Real.swift"; sourceTree = ""; }; 03CC1FE42E3D220F0050FC18 /* InstallHomebrew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallHomebrew.swift; sourceTree = ""; }; 03CC1FF32E3D230B0050FC18 /* ZshRunCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZshRunCommand.swift; sourceTree = ""; }; + 03DAD3A52EB3B08A003417BD /* DomainListVC+Certs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DomainListVC+Certs.swift"; sourceTree = ""; }; 03FE39E52E81682800B7B5AC /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; 03FE39E62E81682800B7B5AC /* AppIconEAP.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconEAP.icon; sourceTree = ""; }; 03FE39E92E81694500B7B5AC /* AppIconUD.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIconUD.icon; sourceTree = ""; }; @@ -1869,6 +1874,7 @@ children = ( C464ADAB275A7A3F003FCD53 /* DomainListWindowController.swift */, C464ADAE275A7A69003FCD53 /* DomainListVC.swift */, + 03DAD3A52EB3B08A003417BD /* DomainListVC+Certs.swift */, C41E87192763D42300161EE0 /* DomainListVC+ContextMenu.swift */, C41CA5EC2774F8EE00A2C80E /* DomainListVC+Actions.swift */, C4FE011028084FC200D1DE6D /* SelectionVC.swift */, @@ -2827,6 +2833,7 @@ C45E76142854A65300B4FE0C /* ServicesManager.swift in Sources */, C4D5857C2A7038DB00DDBB63 /* ByteLimitView.swift in Sources */, C4D4CB3729C109CF00DB9F93 /* InternalSwitcher+Valet.swift in Sources */, + 03DAD3A72EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */, C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */, C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */, C441CC562AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */, @@ -3128,6 +3135,7 @@ C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */, C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */, C40D725C2A018ACC0054A067 /* BusyStatus.swift in Sources */, + 03DAD3A92EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */, C4821C5C2C2DEDE200357A68 /* AppMenu.swift in Sources */, 0392CDED2EB25371009176DA /* SecurePopoverView.swift in Sources */, C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */, @@ -3180,6 +3188,7 @@ C4513F912B13E2FB001AD760 /* PhpExtensionManagerView.swift in Sources */, C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */, C45B914C295607F400F4EC78 /* Service.swift in Sources */, + 03DAD3A82EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */, C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */, C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */, C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */, @@ -3509,6 +3518,7 @@ C489E0BC2A220A4200323F5E /* FakeBrewFormulaeHandler.swift in Sources */, 036C390B2E5C8CC5008DAEDF /* PackagistP2Response.swift in Sources */, C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */, + 03DAD3A62EB3B08F003417BD /* DomainListVC+Certs.swift in Sources */, C4B79ECC29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */, C43B8FD62BA9C689000C02BE /* UnavailableContentView.swift in Sources */, C4FD87AA29AB9ABD0002D701 /* PhpConfigChecker.swift in Sources */, @@ -4534,8 +4544,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nicoverbruggen/NVAlert"; requirement = { - branch = main; - kind = branch; + kind = exactVersion; + version = 1.1.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/phpmon/Modules/Domain List/UI/DomainListVC+Certs.swift b/phpmon/Modules/Domain List/UI/DomainListVC+Certs.swift new file mode 100644 index 00000000..754fd5aa --- /dev/null +++ b/phpmon/Modules/Domain List/UI/DomainListVC+Certs.swift @@ -0,0 +1,50 @@ +// +// DomainListVC+Certs.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 30/10/2025. +// Copyright © 2025 Nico Verbruggen. All rights reserved. +// + +import NVAlert +import Foundation + +extension DomainListVC { + func checkForCertificateRenewal() { + // Check for expired certificates + let expired = domains.filter { item in + if let expiry = item.getListableCertificateExpiryDate() { + return expiry < Date() + } + return false + } + + // An easily accessible list of domains + let domainListing = "- " + expired.map { + $0.getListableName() + "." + $0.getListableTLD() + }.joined(separator: "\n- ") + + // Ensure the window is accessible + guard let window = App.shared.domainListWindowController?.window else { + return + } + + // Present the modal attached to the window + Task { @MainActor in + // Show an alert + return NVAlert().withInformation( + title: "cert_alert.title".localized, + subtitle: "cert_alert.description".localized, + description: "cert_alert.domains".localized(domainListing) + ) + .withPrimary(text: "cert_alert.renew".localized, action: { vc in + // TODO: renewal + vc.close(with: .OK) + }) + .withSecondary(text: "cert_alert.cancel".localized, action: { vc in + vc.close(with: .abort) + }) + .presentAsSheet(attachedTo: window) + } + } +} diff --git a/phpmon/Modules/Domain List/UI/DomainListVC.swift b/phpmon/Modules/Domain List/UI/DomainListVC.swift index 97077c3d..f5da4b47 100644 --- a/phpmon/Modules/Domain List/UI/DomainListVC.swift +++ b/phpmon/Modules/Domain List/UI/DomainListVC.swift @@ -9,7 +9,9 @@ import Cocoa import Carbon import SwiftUI +import NVAlert +// swiftlint:disable type_body_length file_length class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { var container: Container { return App.shared.container @@ -117,13 +119,22 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource if !Valet.shared.sites.isEmpty { // Preloaded list - domains = Valet.getDomainListable() + reloadDomainListables() searchedFor(text: lastSearchedFor) } else { Task { await reloadDomains() } } } + override func viewDidAppear() { + super.viewDidAppear() + checkForCertificateRenewal() + } + + private func reloadDomainListables() { + domains = Valet.getDomainListable() + } + private func addNoResultsView() { let child = NSHostingController( rootView: UnavailableContentView( @@ -205,14 +216,14 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource waitAndExecute { await Valet.shared.reloadSites() } completion: { [self] in - domains = Valet.shared.sites + reloadDomainListables() searchedFor(text: lastSearchedFor) } } func reloadDomainsWithoutUI() async { await Valet.shared.reloadSites() - domains = Valet.shared.sites + reloadDomainListables() searchedFor(text: lastSearchedFor) } @@ -244,7 +255,7 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource } private func find(_ name: String, _ shouldSecure: Bool = false) { - domains = Valet.getDomainListable() + reloadDomainListables() searchedFor(text: "") if let site = domains.enumerated().first(where: { $0.element.getListableName() == name }) { Task { @MainActor in @@ -359,3 +370,4 @@ class DomainListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource Log.perf("deinit: \(String(describing: self)).\(#function)") } } +// swiftlint:enable type_body_length file_length diff --git a/phpmon/en.lproj/Localizable.strings b/phpmon/en.lproj/Localizable.strings index caba431f..2206ea76 100644 --- a/phpmon/en.lproj/Localizable.strings +++ b/phpmon/en.lproj/Localizable.strings @@ -920,7 +920,10 @@ If you want to make edits to this file, please do so before upgrading. When you "cert_popover.secure_domain_expired" = "The certificate expired on %@. You must renew it to continue using HTTPS without errors."; "cert_popover.secure_domain_expiring_later" = "The certificate is valid. It will expire on %@. At that point it will need to be renewed, but you will be notified."; -"cert_alert.title" = "One or more certificates for have expired, and must be renewed."; -"cert_alert.description" = "Certificates signed with the CA from Laravel Valet are usually valid for only one year. Do you want PHP Monitor to unsecure and re-secure any expired domains? The following certificates are affected:"; +"cert_alert.title" = "One or more certificates have expired, and must be renewed."; +"cert_alert.description" = "Certificates used to secure domains are usually valid for one year. Do you want PHP Monitor to unsecure and re-secure any expired domains?"; +"cert_alert.domains" = "The following certificates are affected and will be renewed: + +%@"; "cert_alert.renew" = "Renew Certificate(s)"; "cert_alert.cancel" = "Cancel";