1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2026-03-25 21:50:08 +01:00

Add basic command state tracking

This commit is contained in:
2026-02-24 13:14:54 +01:00
parent 86fa865291
commit b445c2aa4f
10 changed files with 307 additions and 3 deletions

View File

@@ -14,6 +14,10 @@
0317C17E2ED87CAB005479D2 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = 0317C17D2ED87CAB005479D2 /* NVAppUpdater */; };
0317C1812ED87CE1005479D2 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = 0317C1802ED87CE1005479D2 /* NVAppUpdater */; };
0317C1832ED87CEA005479D2 /* NVAppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = 0317C1822ED87CEA005479D2 /* NVAppUpdater */; };
031A80DC2F4CF1690016F7DD /* CommandTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031A80DB2F4CF1690016F7DD /* CommandTracker.swift */; };
031A80DD2F4CF1690016F7DD /* CommandTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031A80DB2F4CF1690016F7DD /* CommandTracker.swift */; };
031A80DE2F4CF1690016F7DD /* CommandTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031A80DB2F4CF1690016F7DD /* CommandTracker.swift */; };
031A80DF2F4CF1690016F7DD /* CommandTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031A80DB2F4CF1690016F7DD /* CommandTracker.swift */; };
031D747C2F46225600D4FF48 /* SimpleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031D747B2F46225600D4FF48 /* SimpleButton.swift */; };
031D747D2F46225600D4FF48 /* SimpleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031D747B2F46225600D4FF48 /* SimpleButton.swift */; };
031D747E2F46225600D4FF48 /* SimpleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031D747B2F46225600D4FF48 /* SimpleButton.swift */; };
@@ -317,6 +321,7 @@
C42106682AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */; };
C42106692AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */; };
C422DDAA28A2C49900CEAC97 /* PhpDoctorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* PhpDoctorView.swift */; };
719E6363E4F41C8A599FCC99 /* ActiveCommandsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31503E15DADA980998F0F5A2 /* ActiveCommandsView.swift */; };
C4232EE52612526500158FC6 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; };
C42337A3281F19F000459A48 /* Xdebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42337A2281F19F000459A48 /* Xdebug.swift */; };
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
@@ -632,6 +637,7 @@
C471E86428F9BB650021E251 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; };
C471E86528F9BB650021E251 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; };
C471E86628F9BB650021E251 /* PhpDoctorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* PhpDoctorWindowController.swift */; };
DE164E569298309E4224C5D6 /* ActiveCommandsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51118AFDD02AA1B7D8C9278 /* ActiveCommandsWindowController.swift */; };
C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; };
C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; };
C471E86928F9BB650021E251 /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; };
@@ -652,6 +658,7 @@
C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; };
C471E87F28F9BB650021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
C471E88028F9BB650021E251 /* PhpDoctorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* PhpDoctorView.swift */; };
25250C95E34D20ED4C91C320 /* ActiveCommandsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31503E15DADA980998F0F5A2 /* ActiveCommandsView.swift */; };
C471E88128F9BB650021E251 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; };
C471E88228F9BB650021E251 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
C471E88328F9BB650021E251 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
@@ -713,6 +720,7 @@
C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; };
C471E8C828F9BB8F0021E251 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; };
C471E8C928F9BB8F0021E251 /* PhpDoctorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* PhpDoctorWindowController.swift */; };
BB3AF45BED2C82AE27E86107 /* ActiveCommandsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51118AFDD02AA1B7D8C9278 /* ActiveCommandsWindowController.swift */; };
C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; };
C471E8CB28F9BB8F0021E251 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PreferencesWindowController.swift */; };
C471E8CC28F9BB8F0021E251 /* PreferencesWindowController+Hotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE7F288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift */; };
@@ -733,6 +741,7 @@
C471E8E128F9BB8F0021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; };
C471E8E228F9BB8F0021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
C471E8E328F9BB8F0021E251 /* PhpDoctorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* PhpDoctorView.swift */; };
0A1A6208D3DD2495FBD8569B /* ActiveCommandsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31503E15DADA980998F0F5A2 /* ActiveCommandsView.swift */; };
C471E8E428F9BB8F0021E251 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; };
C471E8E528F9BB8F0021E251 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
C471E8E628F9BB8F0021E251 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
@@ -766,7 +775,9 @@
C485706D28BF450900539B36 /* NSMenuItemExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40508B028ADAB44008FAC1F /* NSMenuItemExtension.swift */; };
C485706E28BF451C00539B36 /* OnboardingWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FACE82288F1F9700FC478F /* OnboardingWindowController.swift */; };
C485706F28BF452300539B36 /* PhpDoctorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* PhpDoctorWindowController.swift */; };
54D6418DBC6AF23FED489018 /* ActiveCommandsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51118AFDD02AA1B7D8C9278 /* ActiveCommandsWindowController.swift */; };
C485707028BF452300539B36 /* PhpDoctorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDAC28A2DAC600CEAC97 /* PhpDoctorWindowController.swift */; };
5FD883E3DBC963F53E12A17F /* ActiveCommandsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51118AFDD02AA1B7D8C9278 /* ActiveCommandsWindowController.swift */; };
C485707128BF452E00539B36 /* WarningManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699EE28A2F2A30060FEB8 /* WarningManager.swift */; };
C485707228BF453800539B36 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; };
C485707328BF454300539B36 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E9D2BF2878B336008FFDAD /* OnboardingView.swift */; };
@@ -777,6 +788,7 @@
C485707828BF456300539B36 /* Warning.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47699F028A2F3150060FEB8 /* Warning.swift */; };
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; };
C485707A28BF457800539B36 /* PhpDoctorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C422DDA928A2C49900CEAC97 /* PhpDoctorView.swift */; };
4181B8F1C0ED930B2C5E5532 /* ActiveCommandsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31503E15DADA980998F0F5A2 /* ActiveCommandsView.swift */; };
C485707B28BF458900539B36 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
C485707C28BF459500539B36 /* NoWarningsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C708C28AA7F7900E8D498 /* NoWarningsView.swift */; };
C485707D28BF45A200539B36 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
@@ -1053,6 +1065,7 @@
/* Begin PBXFileReference section */
0309E6662B0D4B2F002AC007 /* BrewExtensionsObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewExtensionsObservable.swift; sourceTree = "<group>"; };
031A80DB2F4CF1690016F7DD /* CommandTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandTracker.swift; sourceTree = "<group>"; };
031D747B2F46225600D4FF48 /* SimpleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleButton.swift; sourceTree = "<group>"; };
031D74802F46225C00D4FF48 /* SelectDomainTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectDomainTypeView.swift; sourceTree = "<group>"; };
031D74852F46306F00D4FF48 /* AddSiteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteView.swift; sourceTree = "<group>"; };
@@ -1185,6 +1198,8 @@
C42106652AFA9FF400DF3732 /* PhpVersionManagerView+Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PhpVersionManagerView+Actions.swift"; sourceTree = "<group>"; };
C422DDA928A2C49900CEAC97 /* PhpDoctorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpDoctorView.swift; sourceTree = "<group>"; };
C422DDAC28A2DAC600CEAC97 /* PhpDoctorWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpDoctorWindowController.swift; sourceTree = "<group>"; };
E51118AFDD02AA1B7D8C9278 /* ActiveCommandsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCommandsWindowController.swift; sourceTree = "<group>"; };
31503E15DADA980998F0F5A2 /* ActiveCommandsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveCommandsView.swift; sourceTree = "<group>"; };
C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
C42337A2281F19F000459A48 /* Xdebug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xdebug.swift; sourceTree = "<group>"; };
C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = "<group>"; };
@@ -1710,6 +1725,7 @@
C40C7F2F27722E8D00DDDCDC /* Logger.swift */,
C417DC73277614690015E6EE /* Helpers.swift */,
C4CB6E64292C362C002E9027 /* Homebrew.swift */,
031A80DB2F4CF1690016F7DD /* CommandTracker.swift */,
);
path = Core;
sourceTree = "<group>";
@@ -1959,6 +1975,23 @@
path = Data;
sourceTree = "<group>";
};
F0F42C1C96504D46C75AD6F8 /* UI */ = {
isa = PBXGroup;
children = (
E51118AFDD02AA1B7D8C9278 /* ActiveCommandsWindowController.swift */,
31503E15DADA980998F0F5A2 /* ActiveCommandsView.swift */,
);
path = UI;
sourceTree = "<group>";
};
E1E4FBD76E71C469A65AC4F8 /* Active Commands */ = {
isa = PBXGroup;
children = (
F0F42C1C96504D46C75AD6F8 /* UI */,
);
path = "Active Commands";
sourceTree = "<group>";
};
C44DFA832A6706A200B98ED5 /* Modules */ = {
isa = PBXGroup;
children = (
@@ -1966,6 +1999,7 @@
C464ADAA275A7A25003FCD53 /* Domain List */,
C44DFA7A2A6703FD00B98ED5 /* PHP Config Editor */,
C4297F7828970D4E004C4630 /* PHP Doctor */,
E1E4FBD76E71C469A65AC4F8 /* Active Commands */,
C43931C329C4BD510069165B /* PHP Version Manager */,
C4292D512B023F37004F0D2A /* PHP Extension Manager */,
);
@@ -2990,6 +3024,8 @@
03D846352EB64E39006EFE3C /* CrashReporter.swift in Sources */,
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */,
C422DDAA28A2C49900CEAC97 /* PhpDoctorView.swift in Sources */,
719E6363E4F41C8A599FCC99 /* ActiveCommandsView.swift in Sources */,
031A80DE2F4CF1690016F7DD /* CommandTracker.swift in Sources */,
C469E6FE294CF7B200A82AB2 /* FakeValetProxy.swift in Sources */,
C464ADAF275A7A69003FCD53 /* DomainListVC.swift in Sources */,
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */,
@@ -3027,6 +3063,7 @@
031D74842F46225C00D4FF48 /* SelectDomainTypeView.swift in Sources */,
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C485707028BF452300539B36 /* PhpDoctorWindowController.swift in Sources */,
5FD883E3DBC963F53E12A17F /* ActiveCommandsWindowController.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
C40D725A2A018ACC0054A067 /* BusyStatus.swift in Sources */,
031F24802EA1071A00CFB8D9 /* Container+Fake.swift in Sources */,
@@ -3166,6 +3203,7 @@
C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */,
C471E86528F9BB650021E251 /* WarningManager.swift in Sources */,
C471E86628F9BB650021E251 /* PhpDoctorWindowController.swift in Sources */,
DE164E569298309E4224C5D6 /* ActiveCommandsWindowController.swift in Sources */,
C471E86728F9BB650021E251 /* OnboardingWindowController.swift in Sources */,
C471E86828F9BB650021E251 /* PreferencesWindowController.swift in Sources */,
C471E86928F9BB650021E251 /* PreferencesWindowController+Hotkey.swift in Sources */,
@@ -3198,6 +3236,7 @@
C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */,
C471E87F28F9BB650021E251 /* WarningView.swift in Sources */,
C471E88028F9BB650021E251 /* PhpDoctorView.swift in Sources */,
25250C95E34D20ED4C91C320 /* ActiveCommandsView.swift in Sources */,
C471E88128F9BB650021E251 /* NoWarningsView.swift in Sources */,
C471E88228F9BB650021E251 /* OnboardingView.swift in Sources */,
C471E88328F9BB650021E251 /* VersionPopoverView.swift in Sources */,
@@ -3220,6 +3259,7 @@
C471E7E928F9BAC20021E251 /* Paths.swift in Sources */,
C43B8FD72BA9C689000C02BE /* UnavailableContentView.swift in Sources */,
C45B91552956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
031A80DF2F4CF1690016F7DD /* CommandTracker.swift in Sources */,
C471E7FE28F9BACE0021E251 /* HomebrewDecodable.swift in Sources */,
C4415E8F2B0287E90035F520 /* BrewFormulaeObservable.swift in Sources */,
C471E7D828F9BA8F0021E251 /* FileSystemProtocol.swift in Sources */,
@@ -3394,11 +3434,13 @@
C45B91562956123A00F4EC78 /* FakeServicesManager.swift in Sources */,
C471E8C628F9BB8F0021E251 /* PMTableView.swift in Sources */,
C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */,
031A80DC2F4CF1690016F7DD /* CommandTracker.swift in Sources */,
033E9DFC2F44D93000685F62 /* Startup+Alert.swift in Sources */,
031D74822F46225C00D4FF48 /* SelectDomainTypeView.swift in Sources */,
C471E8C828F9BB8F0021E251 /* WarningManager.swift in Sources */,
C46DC7A72C7B5BCA00F19D17 /* Favorites.swift in Sources */,
C471E8C928F9BB8F0021E251 /* PhpDoctorWindowController.swift in Sources */,
BB3AF45BED2C82AE27E86107 /* ActiveCommandsWindowController.swift in Sources */,
C41ADCEB2970CCC700120423 /* FSNotifier.swift in Sources */,
C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */,
C456A0C92AA614BD0080144F /* PhpPreference.swift in Sources */,
@@ -3437,6 +3479,7 @@
031D747D2F46225600D4FF48 /* SimpleButton.swift in Sources */,
C471E8E228F9BB8F0021E251 /* WarningView.swift in Sources */,
C471E8E328F9BB8F0021E251 /* PhpDoctorView.swift in Sources */,
0A1A6208D3DD2495FBD8569B /* ActiveCommandsView.swift in Sources */,
C471E8E428F9BB8F0021E251 /* NoWarningsView.swift in Sources */,
C471E8E528F9BB8F0021E251 /* OnboardingView.swift in Sources */,
C4B79EBF29CA38DB00A483EE /* BrewCommand.swift in Sources */,
@@ -3572,6 +3615,7 @@
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
039C291A2E8AA314007F5FAB /* TestableWebApi.swift in Sources */,
031A80DD2F4CF1690016F7DD /* CommandTracker.swift in Sources */,
038A2B802EDDB24C00173ACF /* App+UUID.swift in Sources */,
03C099472EA15C8E00B76D43 /* Container+Real.swift in Sources */,
C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */,
@@ -3597,6 +3641,7 @@
C41ADCE92970CCC700120423 /* FSNotifier.swift in Sources */,
C40C7F2927721FF600DDDCDC /* Valet+Alerts.swift in Sources */,
C485707A28BF457800539B36 /* PhpDoctorView.swift in Sources */,
4181B8F1C0ED930B2C5E5532 /* ActiveCommandsView.swift in Sources */,
C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */,
C449B4F027EE7FB800C47E8A /* DomainListTLSCell.swift in Sources */,
037F44162EDB0AAA002EBF75 /* FSNotifierTest.swift in Sources */,
@@ -3781,6 +3826,7 @@
C449B4F327EE7FC600C47E8A /* DomainListTypeCell.swift in Sources */,
C48D6C71279CD2AC00F26D7E /* VersionNumber.swift in Sources */,
C485706F28BF452300539B36 /* PhpDoctorWindowController.swift in Sources */,
54D6418DBC6AF23FED489018 /* ActiveCommandsWindowController.swift in Sources */,
C46FA9892822EFDC00D78807 /* PhpConfigurationFile.swift in Sources */,
C43931CB29C4C03F0069165B /* Brew.swift in Sources */,
0392CDEC2EB25371009176DA /* SecurePopoverView.swift in Sources */,

View File

@@ -8,12 +8,28 @@
import Cocoa
public class RealCommand: CommandProtocol {
private let commandTracker: CommandTracker
init(commandTracker: CommandTracker) {
self.commandTracker = commandTracker
}
public func execute(
path: String,
arguments: [String],
trimNewlines: Bool,
withStandardError: Bool
) -> String {
let tracker = self.commandTracker
let commandDescription = "\(path) \(arguments.joined(separator: " "))"
var trackingId: UUID?
DispatchQueue.main.async { trackingId = tracker.track(commandDescription) }
defer {
DispatchQueue.main.async {
if let id = trackingId { tracker.complete(id) }
}
}
let task = Process()
var output = ""

View File

@@ -0,0 +1,76 @@
//
// CommandTracker.swift
// PHP Monitor
//
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Foundation
struct TrackedCommand: Identifiable {
let id: UUID
let command: String
let startedAt: Date
var completedAt: Date?
var isCompleted: Bool {
completedAt != nil
}
var durationText: String {
let end = completedAt ?? Date()
let ms = Int(end.timeIntervalSince(startedAt) * 1000)
if isCompleted {
return "Completed in \(ms) ms"
} else {
return "Running for \(ms) ms"
}
}
}
@MainActor
class CommandTracker: ObservableObject {
nonisolated init() {}
@Published private(set) var commands: [TrackedCommand] = []
var activeCommands: [TrackedCommand] {
commands.filter { !$0.isCompleted }
}
var isActive: Bool {
!activeCommands.isEmpty
}
var loggingEnabled: Bool = true
@discardableResult
func track(_ command: String) -> UUID {
let tracked = TrackedCommand(id: UUID(), command: command, startedAt: Date())
commands.append(tracked)
if loggingEnabled {
logActiveCommands("TRACK")
}
return tracked.id
}
func complete(_ id: UUID) {
if let index = commands.firstIndex(where: { $0.id == id }) {
commands[index].completedAt = Date()
}
if loggingEnabled {
logActiveCommands("COMPLETE")
}
}
private func logActiveCommands(_ label: String) {
if activeCommands.isEmpty {
Log.info("[CommandTracker] [\(label)] No active commands.")
} else {
let list = activeCommands
.map { " - \($0.command) (started: \($0.startedAt))" }
.joined(separator: "\n")
Log.info("[CommandTracker] [\(label)] Active commands:\n\(list)")
}
}
}

View File

@@ -10,12 +10,14 @@ import Foundation
@preconcurrency import Dispatch
class RealShell: ShellProtocol, @unchecked Sendable {
init(binPath: String) {
init(binPath: String, commandTracker: CommandTracker) {
self.binPath = binPath
self.commandTracker = commandTracker
self._PATH = RealShell.getPath()
self._exports = [:]
}
private let commandTracker: CommandTracker
private(set) var binPath: String
/**
@@ -156,6 +158,15 @@ class RealShell: ShellProtocol, @unchecked Sendable {
// MARK: - Shellable Protocol
func sync(_ command: String) -> ShellOutput {
let tracker = self.commandTracker
var trackingId: UUID?
DispatchQueue.main.async { trackingId = tracker.track(command) }
defer {
DispatchQueue.main.async {
if let id = trackingId { tracker.complete(id) }
}
}
let process = getShellProcess(for: command)
let outputPipe = Pipe()
@@ -187,6 +198,12 @@ class RealShell: ShellProtocol, @unchecked Sendable {
@discardableResult
func pipe(_ command: String) async -> ShellOutput {
let trackingId = await commandTracker.track(command)
defer {
let tracker = self.commandTracker
DispatchQueue.main.async { tracker.complete(trackingId) }
}
let process = getShellProcess(for: command)
let outputPipe = Pipe()
@@ -223,6 +240,12 @@ class RealShell: ShellProtocol, @unchecked Sendable {
@discardableResult
func pipe(_ command: String, timeout: TimeInterval) async -> ShellOutput {
let trackingId = await commandTracker.track(command)
defer {
let tracker = self.commandTracker
DispatchQueue.main.async { tracker.complete(trackingId) }
}
let process = getShellProcess(for: command)
let outputPipe = Pipe()
@@ -293,6 +316,12 @@ class RealShell: ShellProtocol, @unchecked Sendable {
didReceiveOutput: @escaping (String, ShellStream) -> Void,
withTimeout timeout: TimeInterval = 5.0
) async throws -> (Process, ShellOutput) {
let trackingId = await commandTracker.track(command)
defer {
let tracker = self.commandTracker
DispatchQueue.main.async { tracker.complete(trackingId) }
}
let process = getShellProcess(for: command)
let outputPipe = Pipe(), errorPipe = Pipe()

View File

@@ -17,6 +17,7 @@ class Container: @unchecked Sendable {
private(set) var shell: ShellProtocol!
private(set) var command: CommandProtocol!
private(set) var webApi: WebApiProtocol!
private(set) var commandTracker: CommandTracker!
// Secondary (uses primary instances above)
private(set) var preferences: Preferences!
@@ -67,8 +68,9 @@ class Container: @unchecked Sendable {
// any of the other classes can be initialized!
self.filesystem = RealFileSystem(container: self)
self.paths = Paths(container: self)
self.shell = RealShell(binPath: paths.binPath)
self.command = RealCommand()
self.commandTracker = CommandTracker()
self.shell = RealShell(binPath: paths.binPath, commandTracker: commandTracker)
self.command = RealCommand(commandTracker: commandTracker)
self.webApi = RealWebApi(container: self)
if coreOnly {
@@ -97,6 +99,7 @@ class Container: @unchecked Sendable {
webApiGetResponses: [URL: FakeWebApiResponse] = [:],
webApiPostResponses: [URL: FakeWebApiResponse] = [:]
) {
self.commandTracker = CommandTracker()
self.shell = TestableShell(expectations: shellExpectations)
self.filesystem = TestableFileSystem(files: fileSystemFiles)
self.command = TestableCommand(commands: commands)

View File

@@ -119,6 +119,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
// Start with the regular busy icon
MainMenu.shared.setStatusBar(image: NSImage.statusBarIcon)
// Show the active commands debug window
ActiveCommandsWindowController.show()
Task { // Make sure the menu performs its initial checks
await Startup.check(App.shared.container)
}

View File

@@ -15,6 +15,7 @@ typealias PhpConfigManagerWC = PhpConfigManagerWindowController
typealias PhpDoctorWC = PhpDoctorWindowController
typealias PhpVersionManagerWC = PhpVersionManagerWindowController
typealias PhpExtensionManagerWC = PhpExtensionManagerWindowController
typealias ActiveCommandsWC = ActiveCommandsWindowController
let WindowManager = WindowCoordinator.shared

View File

@@ -47,6 +47,11 @@ extension GenericPreferenceVC {
name: windowName,
frame: WindowManager.window(for: PhpExtensionManagerWC.self)?.frame
)
case "ActiveCommands":
return WindowSnapshot(
name: windowName,
frame: WindowManager.window(for: ActiveCommandsWC.self)?.frame
)
default:
return nil
}
@@ -74,6 +79,9 @@ extension GenericPreferenceVC {
case "PhpExtensionManager":
PhpExtensionManagerWindowController.show()
applyFrame(snapshot.frame, for: PhpExtensionManagerWC.self)
case "ActiveCommands":
ActiveCommandsWindowController.show()
applyFrame(snapshot.frame, for: ActiveCommandsWC.self)
default:
continue
}

View File

@@ -0,0 +1,75 @@
//
// ActiveCommandsView.swift
// PHP Monitor
//
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct ActiveCommandsView: View {
@ObservedObject var commandTracker: CommandTracker
@State private var tick = false
let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
init(commandTracker: CommandTracker? = nil) {
self.commandTracker = commandTracker ?? App.shared.container.commandTracker
}
var body: some View {
ScrollViewReader { proxy in
List {
if commandTracker.commands.isEmpty {
HStack {
Spacer()
Text("No commands have been tracked yet.")
.font(.system(size: 13))
.foregroundColor(.secondary)
.padding(30)
Spacer()
}
} else {
ForEach(commandTracker.commands) { command in
HStack(spacing: 10) {
if command.isCompleted {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
.frame(width: 16)
} else {
ProgressView()
.controlSize(.small)
.frame(width: 16)
}
VStack(alignment: .leading, spacing: 4) {
Text(command.command)
.font(.system(size: 12, design: .monospaced))
.lineLimit(2)
// tick forces re-evaluation every 200ms
let _ = tick
Text(command.durationText)
.font(.system(size: 11))
.foregroundColor(.secondary)
}
Spacer()
}
.padding(.vertical, 2)
.id(command.id)
}
}
}
.listStyle(.plain)
.onChange(of: commandTracker.commands.count) { _ in
if let last = commandTracker.commands.last {
proxy.scrollTo(last.id, anchor: .bottom)
}
}
}
.frame(minWidth: 400, minHeight: 200)
.onReceive(timer) { _ in
if commandTracker.commands.contains(where: { !$0.isCompleted }) {
tick.toggle()
}
}
}
}

View File

@@ -0,0 +1,47 @@
//
// ActiveCommandsWindowController.swift
// PHP Monitor
//
// Copyright © 2025 Nico Verbruggen. All rights reserved.
//
import Cocoa
import SwiftUI
class ActiveCommandsWindowController: PMWindowController {
// MARK: - Window Identifier
override var windowName: String {
return "ActiveCommands"
}
public static func create(delegate: NSWindowDelegate?) {
let windowController = Self()
let panel = NSPanel()
panel.styleMask = [.titled, .closable, .miniaturizable, .resizable, .utilityWindow]
panel.title = "Active Commands"
panel.titlebarAppearsTransparent = true
panel.isFloatingPanel = true
panel.hidesOnDeactivate = false
panel.delegate = delegate ?? windowController
panel.contentView = NSHostingView(rootView: ActiveCommandsView())
panel.setContentSize(NSSize(width: 500, height: 300))
windowController.window = panel
WindowManager.setController(windowController)
}
public static func show(delegate: NSWindowDelegate? = nil) {
if !WindowManager.hasController(for: ActiveCommandsWC.self) {
Self.create(delegate: delegate)
}
WindowManager.show(ActiveCommandsWC.self)
WindowManager.withWindow(for: ActiveCommandsWC.self) { window in
window.setCenterPosition(offsetY: 70)
}
}
}