mirror of
https://github.com/nicoverbruggen/phpmon.git
synced 2025-12-21 11:10:08 +01:00
✨ Add popover with certificate information
This commit is contained in:
@@ -64,6 +64,10 @@
|
|||||||
0392CDE72EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
0392CDE72EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
||||||
0392CDE82EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
0392CDE82EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
||||||
0392CDE92EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
0392CDE92EB23B8F009176DA /* CertificateValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDE52EB23B8F009176DA /* CertificateValidator.swift */; };
|
||||||
|
0392CDEB2EB25371009176DA /* SecurePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */; };
|
||||||
|
0392CDEC2EB25371009176DA /* SecurePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */; };
|
||||||
|
0392CDED2EB25371009176DA /* SecurePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */; };
|
||||||
|
0392CDEE2EB25371009176DA /* SecurePopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0392CDEA2EB25371009176DA /* SecurePopoverView.swift */; };
|
||||||
0396160D2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
0396160D2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
||||||
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
0396160E2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
||||||
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
0396160F2E74A61E002DD7F6 /* LoggableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */; };
|
||||||
@@ -1003,6 +1007,7 @@
|
|||||||
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistError.swift; sourceTree = "<group>"; };
|
036C390E2E5C8D3B008DAEDF /* PackagistError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackagistError.swift; sourceTree = "<group>"; };
|
||||||
036C39132E5CB820008DAEDF /* TestBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBundle.swift; sourceTree = "<group>"; };
|
036C39132E5CB820008DAEDF /* TestBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestBundle.swift; sourceTree = "<group>"; };
|
||||||
0392CDE52EB23B8F009176DA /* CertificateValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = "<group>"; };
|
0392CDE52EB23B8F009176DA /* CertificateValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateValidator.swift; sourceTree = "<group>"; };
|
||||||
|
0392CDEA2EB25371009176DA /* SecurePopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurePopoverView.swift; sourceTree = "<group>"; };
|
||||||
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; };
|
0396160C2E74A61B002DD7F6 /* LoggableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggableEvent.swift; sourceTree = "<group>"; };
|
||||||
039C29122E8AA15F007F5FAB /* ActiveApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveApi.swift; sourceTree = "<group>"; };
|
039C29122E8AA15F007F5FAB /* ActiveApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveApi.swift; sourceTree = "<group>"; };
|
||||||
039C29172E8AA311007F5FAB /* TestableApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApi.swift; sourceTree = "<group>"; };
|
039C29172E8AA311007F5FAB /* TestableApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableApi.swift; sourceTree = "<group>"; };
|
||||||
@@ -2177,6 +2182,7 @@
|
|||||||
C4B609182853AAA700C95265 /* Domains */ = {
|
C4B609182853AAA700C95265 /* Domains */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
0392CDEA2EB25371009176DA /* SecurePopoverView.swift */,
|
||||||
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */,
|
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */,
|
||||||
);
|
);
|
||||||
path = Domains;
|
path = Domains;
|
||||||
@@ -2732,6 +2738,7 @@
|
|||||||
032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */,
|
032DAC2A2E8BEB5B0018E01C /* RealApi.swift in Sources */,
|
||||||
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
|
54FCFD30276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
|
||||||
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */,
|
C450C8C628C919EC002A2B4B /* PreferenceName.swift in Sources */,
|
||||||
|
0392CDEB2EB25371009176DA /* SecurePopoverView.swift in Sources */,
|
||||||
C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */,
|
C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */,
|
||||||
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */,
|
C4F2E43A2752F7D00020E974 /* PhpInstallation.swift in Sources */,
|
||||||
C4D9F24B280B69E100DCD39A /* AddProxyVC.swift in Sources */,
|
C4D9F24B280B69E100DCD39A /* AddProxyVC.swift in Sources */,
|
||||||
@@ -3122,6 +3129,7 @@
|
|||||||
C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */,
|
C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */,
|
||||||
C40D725C2A018ACC0054A067 /* BusyStatus.swift in Sources */,
|
C40D725C2A018ACC0054A067 /* BusyStatus.swift in Sources */,
|
||||||
C4821C5C2C2DEDE200357A68 /* AppMenu.swift in Sources */,
|
C4821C5C2C2DEDE200357A68 /* AppMenu.swift in Sources */,
|
||||||
|
0392CDED2EB25371009176DA /* SecurePopoverView.swift in Sources */,
|
||||||
C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */,
|
C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */,
|
||||||
C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */,
|
C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */,
|
||||||
C4B79ECD29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
|
C4B79ECD29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
|
||||||
@@ -3309,6 +3317,7 @@
|
|||||||
C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */,
|
C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */,
|
||||||
C43931C829C4BD610069165B /* PhpVersionManagerView.swift in Sources */,
|
C43931C829C4BD610069165B /* PhpVersionManagerView.swift in Sources */,
|
||||||
C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */,
|
C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */,
|
||||||
|
0392CDEE2EB25371009176DA /* SecurePopoverView.swift in Sources */,
|
||||||
C4463FCF29804BCB007B93D5 /* RCFile.swift in Sources */,
|
C4463FCF29804BCB007B93D5 /* RCFile.swift in Sources */,
|
||||||
C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */,
|
C471E82C28F9BB340021E251 /* ValetListable.swift in Sources */,
|
||||||
C471E82828F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
|
C471E82828F9BB310021E251 /* BrewDiagnostics.swift in Sources */,
|
||||||
@@ -3574,6 +3583,7 @@
|
|||||||
C485706F28BF452300539B36 /* PhpDoctorWindowController.swift in Sources */,
|
C485706F28BF452300539B36 /* PhpDoctorWindowController.swift in Sources */,
|
||||||
C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
|
C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
|
||||||
C43931CB29C4C03F0069165B /* Brew.swift in Sources */,
|
C43931CB29C4C03F0069165B /* Brew.swift in Sources */,
|
||||||
|
0392CDEC2EB25371009176DA /* SecurePopoverView.swift in Sources */,
|
||||||
C41C02AB27E61CB3009F26CB /* FakeValetSite.swift in Sources */,
|
C41C02AB27E61CB3009F26CB /* FakeValetSite.swift in Sources */,
|
||||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
|
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
|
||||||
C4D9F24C280B69E100DCD39A /* AddProxyVC.swift in Sources */,
|
C4D9F24C280B69E100DCD39A /* AddProxyVC.swift in Sources */,
|
||||||
|
|||||||
@@ -705,6 +705,9 @@ Gw
|
|||||||
<constraint firstAttribute="width" constant="30" id="B8B-Bs-ZMG"/>
|
<constraint firstAttribute="width" constant="30" id="B8B-Bs-ZMG"/>
|
||||||
<constraint firstAttribute="height" constant="30" id="Ssm-kd-GFd"/>
|
<constraint firstAttribute="height" constant="30" id="Ssm-kd-GFd"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
|
<connections>
|
||||||
|
<action selector="pressedPhpVersion:" target="hft-M4-nWb" id="7IB-z6-CA4"/>
|
||||||
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -714,6 +717,7 @@ Gw
|
|||||||
<constraint firstItem="3fp-xd-haK" firstAttribute="centerX" secondItem="hft-M4-nWb" secondAttribute="centerX" id="tQh-hX-llf"/>
|
<constraint firstItem="3fp-xd-haK" firstAttribute="centerX" secondItem="hft-M4-nWb" secondAttribute="centerX" id="tQh-hX-llf"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="buttonLockStatus" destination="3fp-xd-haK" id="RFv-pZ-6fX"/>
|
||||||
<outlet property="imageViewLock" destination="Wel-Ho-Kpp" id="iji-uw-8we"/>
|
<outlet property="imageViewLock" destination="Wel-Ho-Kpp" id="iji-uw-8we"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableCellView>
|
</tableCellView>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ protocol ValetListable {
|
|||||||
|
|
||||||
func getListableSecured() -> Bool
|
func getListableSecured() -> Bool
|
||||||
|
|
||||||
|
func getListableCertificateExpiryDate() -> Date?
|
||||||
|
|
||||||
func getListableAbsolutePath() -> String
|
func getListableAbsolutePath() -> String
|
||||||
|
|
||||||
func getListablePhpVersion() -> String
|
func getListablePhpVersion() -> String
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ class ValetProxy: ValetListable {
|
|||||||
return self.secured
|
return self.secured
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getListableCertificateExpiryDate() -> Date? {
|
||||||
|
return self.certificateExpiryDate
|
||||||
|
}
|
||||||
|
|
||||||
func getListableAbsolutePath() -> String {
|
func getListableAbsolutePath() -> String {
|
||||||
return self.domain
|
return self.domain
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,6 +323,10 @@ class ValetSite: ValetListable {
|
|||||||
return self.secured
|
return self.secured
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getListableCertificateExpiryDate() -> Date? {
|
||||||
|
return self.certificateExpiryDate
|
||||||
|
}
|
||||||
|
|
||||||
func getListableAbsolutePath() -> String {
|
func getListableAbsolutePath() -> String {
|
||||||
return self.absolutePath
|
return self.absolutePath
|
||||||
}
|
}
|
||||||
|
|||||||
68
phpmon/Domain/SwiftUI/Domains/SecurePopoverView.swift
Normal file
68
phpmon/Domain/SwiftUI/Domains/SecurePopoverView.swift
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// SecurePopoverView.swift
|
||||||
|
// PHP Monitor
|
||||||
|
//
|
||||||
|
// Created by Nico Verbruggen on 29/10/2025.
|
||||||
|
// Copyright © 2025 Nico Verbruggen. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SecurePopoverView: View {
|
||||||
|
var container: Container {
|
||||||
|
return App.shared.container
|
||||||
|
}
|
||||||
|
|
||||||
|
@State var name: String
|
||||||
|
@State var tld: String
|
||||||
|
@State var expires: Date?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
if expires == nil {
|
||||||
|
Text("The domain \"\(name).\(tld)\" is not secured.")
|
||||||
|
.fontWeight(.bold)
|
||||||
|
DisclaimerView(
|
||||||
|
iconName: "info.circle.fill",
|
||||||
|
message: "Traffic is served by nginx over plain HTTP. Keep in mind that certain web features may not work correctly without a secure connection.",
|
||||||
|
color: Color.statusColorRed
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text("The domain \"\(name).\(tld)\" is secured.")
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
if let expires {
|
||||||
|
Text("Because this domain has been secured with a certificate, traffic to this domain is served by nginx over HTTPS.")
|
||||||
|
.font(.subheadline)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
if expires < Date() {
|
||||||
|
DisclaimerView(
|
||||||
|
iconName: "info.circle.fill",
|
||||||
|
message: "The certificate expired on \(expires.formatted()). You must renew it to continue using HTTPS without errors.",
|
||||||
|
color: Color.statusColorOrange
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DisclaimerView(
|
||||||
|
iconName: "info.circle.fill",
|
||||||
|
message: "The certificate is valid. It will expire on \(expires.formatted()). At that point it will need to be renewed, but you will be notified.",
|
||||||
|
color: Color.statusColorGreen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.frame(width: 400, alignment: .center)
|
||||||
|
.padding(20)
|
||||||
|
.background(
|
||||||
|
Color(NSColor.windowBackgroundColor)
|
||||||
|
.padding(-80)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Example") {
|
||||||
|
SecurePopoverView(
|
||||||
|
name: "hello",
|
||||||
|
tld: "test",
|
||||||
|
expires: nil
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -92,7 +92,7 @@ class DomainListPhpCell: NSTableCellView, DomainListCellProtocol {
|
|||||||
|
|
||||||
let fittingSize = controller.view.fittingSize
|
let fittingSize = controller.view.fittingSize
|
||||||
let finalWidth: CGFloat = min(fittingSize.width, 400)
|
let finalWidth: CGFloat = min(fittingSize.width, 400)
|
||||||
let finalHeight: CGFloat = min(fittingSize.height, 300)
|
let finalHeight: CGFloat = min(fittingSize.height, 700)
|
||||||
|
|
||||||
controller.view.frame = NSRect(x: 0, y: 0, width: finalWidth, height: finalHeight)
|
controller.view.frame = NSRect(x: 0, y: 0, width: finalWidth, height: finalHeight)
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,12 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
class DomainListTLSCell: NSTableCellView, DomainListCellProtocol {
|
class DomainListTLSCell: NSTableCellView, DomainListCellProtocol {
|
||||||
|
var domain: ValetListable?
|
||||||
|
|
||||||
|
@IBOutlet weak var buttonLockStatus: NSButton!
|
||||||
@IBOutlet weak var imageViewLock: NSImageView!
|
@IBOutlet weak var imageViewLock: NSImageView!
|
||||||
|
|
||||||
static func getCellIdentifier(for domain: ValetListable) -> String {
|
static func getCellIdentifier(for domain: ValetListable) -> String {
|
||||||
@@ -17,6 +21,8 @@ class DomainListTLSCell: NSTableCellView, DomainListCellProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func populateCell(with site: ValetSite) {
|
func populateCell(with site: ValetSite) {
|
||||||
|
domain = site
|
||||||
|
|
||||||
imageViewLock.image = NSImage(named: site.secured ? "Lock" : "LockUnlocked")!
|
imageViewLock.image = NSImage(named: site.secured ? "Lock" : "LockUnlocked")!
|
||||||
|
|
||||||
imageViewLock.contentTintColor = site.secured
|
imageViewLock.contentTintColor = site.secured
|
||||||
@@ -38,4 +44,37 @@ class DomainListTLSCell: NSTableCellView, DomainListCellProtocol {
|
|||||||
imageViewLock.contentTintColor = NSColor(named: "StatusColorOrange")
|
imageViewLock.contentTintColor = NSColor(named: "StatusColorOrange")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var container: Container {
|
||||||
|
return App.shared.container
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func pressedPhpVersion(_ sender: Any) {
|
||||||
|
guard let site = self.domain else { return }
|
||||||
|
|
||||||
|
let button = self.buttonLockStatus!
|
||||||
|
let popover = NSPopover()
|
||||||
|
|
||||||
|
let view = SecurePopoverView(
|
||||||
|
name: site.getListableName(),
|
||||||
|
tld: Valet.shared.config.tld,
|
||||||
|
expires: site.getListableCertificateExpiryDate()
|
||||||
|
)
|
||||||
|
|
||||||
|
let controller = NSHostingController(rootView: view)
|
||||||
|
|
||||||
|
// Force a layout pass to get accurate sizing, this resolves positioning issues
|
||||||
|
controller.view.setFrameSize(NSSize(width: 300, height: 1000))
|
||||||
|
controller.view.layoutSubtreeIfNeeded()
|
||||||
|
|
||||||
|
let fittingSize = controller.view.fittingSize
|
||||||
|
let finalWidth: CGFloat = min(fittingSize.width, 300)
|
||||||
|
|
||||||
|
controller.view.frame = NSRect(x: 0, y: 0, width: finalWidth, height: fittingSize.height)
|
||||||
|
|
||||||
|
popover.contentViewController = controller
|
||||||
|
popover.behavior = .transient
|
||||||
|
popover.animates = true
|
||||||
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user