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:
@@ -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 */,
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
|
||||
76
phpmon/Common/Core/CommandTracker.swift
Normal file
76
phpmon/Common/Core/CommandTracker.swift
Normal 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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ typealias PhpConfigManagerWC = PhpConfigManagerWindowController
|
||||
typealias PhpDoctorWC = PhpDoctorWindowController
|
||||
typealias PhpVersionManagerWC = PhpVersionManagerWindowController
|
||||
typealias PhpExtensionManagerWC = PhpExtensionManagerWindowController
|
||||
typealias ActiveCommandsWC = ActiveCommandsWindowController
|
||||
|
||||
let WindowManager = WindowCoordinator.shared
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
75
phpmon/Modules/Active Commands/UI/ActiveCommandsView.swift
Normal file
75
phpmon/Modules/Active Commands/UI/ActiveCommandsView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user