Compare commits
65 Commits
Author | SHA1 | Date | |
---|---|---|---|
a53972404c | |||
64a605235a | |||
a194ecdebe | |||
03158a568c | |||
bdc6be7384 | |||
4908dba57e | |||
0f0aa176b6 | |||
485729f9a5 | |||
5eb36a9bdf | |||
86113d2067 | |||
70c04d4dc7 | |||
5d69b423c1 | |||
1617b57b1e | |||
33825e7b66 | |||
464b7106b2 | |||
0cf85f9958 | |||
00cf2bc360 | |||
dbb5329908 | |||
9fd8e7042a | |||
cb62a20f2a | |||
bb1742a390 | |||
6db43beddf | |||
7e2c7cdd59 | |||
e76dd0daeb | |||
0c33e8f8cc | |||
a6a196518a | |||
a287ebf6e4 | |||
2200b395f1 | |||
bc8a572072 | |||
1c455ea05a | |||
f2d01748be | |||
d3e59e560f | |||
55e849c21b | |||
50f6afb3c5 | |||
67e80aac8d | |||
e683b6bc9a | |||
e854ebe114 | |||
b792f55e5f | |||
cbf5526881 | |||
f62297ee3d | |||
2f017f6732 | |||
1c62a100af | |||
e176d6fa2d | |||
ea60626c47 | |||
69c3386088 | |||
3e898d435b | |||
a4dcd0cd3d | |||
bb382c0f74 | |||
c87c532d1c | |||
b26099369f | |||
90a8e7c336 | |||
398dad9d5e | |||
48d0878dfd | |||
4ba3415bf4 | |||
0fc7f2c905 | |||
eebfa823f1 | |||
edc23f97de | |||
7b79cb7fe8 | |||
88d81f343e | |||
8486997f01 | |||
af55e7fabc | |||
2025c951e8 | |||
a71c6afc0d | |||
1bfcdd546b | |||
78702ae325 |
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
phpmon.xcodeproj/project.xcworkspace
|
||||
phpmon.xcodeproj/xcuserdata
|
||||
PHP Monitor.xcodeproj/project.xcworkspace
|
||||
PHP Monitor.xcodeproj/xcuserdata
|
||||
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Nico Verbruggen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -7,32 +7,53 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
|
||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
|
||||
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
|
||||
C41C1B3922B0097F00E7CF16 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3822B0097F00E7CF16 /* ViewController.swift */; };
|
||||
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
|
||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
|
||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4622B009A400E7CF16 /* Shell.swift */; };
|
||||
C41C1B4922B00A9800E7CF16 /* ImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */; };
|
||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
|
||||
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */; };
|
||||
C41C1B4D22B0215A00E7CF16 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Services.swift */; };
|
||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
|
||||
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
|
||||
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
|
||||
C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; };
|
||||
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
|
||||
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; };
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
||||
C4D8016622B1584700C6DA1B /* BootChecks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* BootChecks.swift */; };
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
|
||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
|
||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
C41C1B3322B0097F00E7CF16 /* phpmon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = phpmon.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = "<group>"; };
|
||||
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = "<group>"; };
|
||||
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
C41C1B3822B0097F00E7CF16 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
C41C1B3D22B0098000E7CF16 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
C41C1B3F22B0098000E7CF16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = phpmon.entitlements; sourceTree = "<group>"; };
|
||||
C41C1B4622B009A400E7CF16 /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
|
||||
C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenerator.swift; sourceTree = "<group>"; };
|
||||
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarImageGenerator.swift; sourceTree = "<group>"; };
|
||||
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersion.swift; sourceTree = "<group>"; };
|
||||
C41C1B4C22B0215A00E7CF16 /* Services.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
|
||||
C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = "<group>"; };
|
||||
C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
|
||||
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
|
||||
C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = "<group>"; };
|
||||
C474B00524C0E98C00066A22 /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = "<group>"; };
|
||||
C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
|
||||
C4D8016522B1584700C6DA1B /* BootChecks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BootChecks.swift; sourceTree = "<group>"; };
|
||||
C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
|
||||
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
|
||||
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
|
||||
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = "<group>"; };
|
||||
C4F8C0A522D4FA41002EFE61 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -46,9 +67,19 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
C405A4CD24B9B9070062FAFA /* IAP */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */,
|
||||
C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */,
|
||||
);
|
||||
path = IAP;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41C1B2A22B0097F00E7CF16 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4F8C0A522D4FA41002EFE61 /* README.md */,
|
||||
C41C1B3522B0097F00E7CF16 /* phpmon */,
|
||||
C41C1B3422B0097F00E7CF16 /* Products */,
|
||||
);
|
||||
@ -57,7 +88,7 @@
|
||||
C41C1B3422B0097F00E7CF16 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B3322B0097F00E7CF16 /* phpmon.app */,
|
||||
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -65,49 +96,27 @@
|
||||
C41C1B3522B0097F00E7CF16 /* phpmon */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41E181722CB61EB0072CF09 /* Classes */,
|
||||
C41E181622CB61890072CF09 /* Startup */,
|
||||
C41E181522CB614C0072CF09 /* Terminal */,
|
||||
C41C1B4E22B024F100E7CF16 /* Helpers */,
|
||||
C41E181822CB62200072CF09 /* View Controllers */,
|
||||
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */,
|
||||
C4EE188322D3386B00E126E5 /* Constants.swift */,
|
||||
C4811D2622D70CEF00B5F6B3 /* Singletons */,
|
||||
C41E181722CB61EB0072CF09 /* Classes */,
|
||||
C41E181822CB62200072CF09 /* View Controllers */,
|
||||
C4F8C0A222D4F100002EFE61 /* Extensions */,
|
||||
C41C1B3F22B0098000E7CF16 /* Info.plist */,
|
||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
|
||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
|
||||
C473319E2470923A009A0597 /* Localizable.strings */,
|
||||
C405A4CD24B9B9070062FAFA /* IAP */,
|
||||
);
|
||||
path = phpmon;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41C1B4E22B024F100E7CF16 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B4822B00A9800E7CF16 /* ImageGenerator.swift */,
|
||||
C476FF9722B0DD830098105B /* Alert.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41E181522CB614C0072CF09 /* Terminal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B4622B009A400E7CF16 /* Shell.swift */,
|
||||
C41C1B4C22B0215A00E7CF16 /* Services.swift */,
|
||||
);
|
||||
path = Terminal;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41E181622CB61890072CF09 /* Startup */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4D8016522B1584700C6DA1B /* BootChecks.swift */,
|
||||
);
|
||||
path = Startup;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C41E181722CB61EB0072CF09 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */,
|
||||
C47331A0247093AC009A0597 /* Menu */,
|
||||
C4811D2722D70D8E00B5F6B3 /* Commands */,
|
||||
C4811D2822D70D9C00B5F6B3 /* Helpers */,
|
||||
);
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
@ -116,17 +125,64 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B3C22B0098000E7CF16 /* Main.storyboard */,
|
||||
C41C1B3822B0097F00E7CF16 /* ViewController.swift */,
|
||||
);
|
||||
path = "View Controllers";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C47331A0247093AC009A0597 /* Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C47331A1247093B7009A0597 /* StatusMenu.swift */,
|
||||
);
|
||||
path = Menu;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4811D2622D70CEF00B5F6B3 /* Singletons */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B4622B009A400E7CF16 /* Shell.swift */,
|
||||
C42295DC2358D02000E263B2 /* Command.swift */,
|
||||
C4811D2322D70A4700B5F6B3 /* App.swift */,
|
||||
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */,
|
||||
);
|
||||
path = Singletons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4811D2722D70D8E00B5F6B3 /* Commands */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C41C1B4C22B0215A00E7CF16 /* Actions.swift */,
|
||||
C4D8016522B1584700C6DA1B /* Startup.swift */,
|
||||
);
|
||||
path = Commands;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4811D2822D70D9C00B5F6B3 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C476FF9722B0DD830098105B /* Alert.swift */,
|
||||
C41C1B4A22B019FF00E7CF16 /* PhpVersion.swift */,
|
||||
C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */,
|
||||
C474B00524C0E98C00066A22 /* LocalNotification.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C4F8C0A222D4F100002EFE61 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */,
|
||||
C46FA23E246C358E00944F05 /* StringExtension.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
C41C1B3222B0097F00E7CF16 /* phpmon */ = {
|
||||
C41C1B3222B0097F00E7CF16 /* PHP Monitor */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "phpmon" */;
|
||||
buildConfigurationList = C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "PHP Monitor" */;
|
||||
buildPhases = (
|
||||
C41C1B2F22B0097F00E7CF16 /* Sources */,
|
||||
C41C1B3022B0097F00E7CF16 /* Frameworks */,
|
||||
@ -136,9 +192,9 @@
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = phpmon;
|
||||
name = "PHP Monitor";
|
||||
productName = phpmon;
|
||||
productReference = C41C1B3322B0097F00E7CF16 /* phpmon.app */;
|
||||
productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
@ -148,7 +204,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1020;
|
||||
LastUpgradeCheck = 1020;
|
||||
LastUpgradeCheck = 1220;
|
||||
ORGANIZATIONNAME = "Nico Verbruggen";
|
||||
TargetAttributes = {
|
||||
C41C1B3222B0097F00E7CF16 = {
|
||||
@ -156,7 +212,7 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "phpmon" */;
|
||||
buildConfigurationList = C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "PHP Monitor" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
@ -169,7 +225,7 @@
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
C41C1B3222B0097F00E7CF16 /* phpmon */,
|
||||
C41C1B3222B0097F00E7CF16 /* PHP Monitor */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -181,6 +237,9 @@
|
||||
files = (
|
||||
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */,
|
||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
|
||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */,
|
||||
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
|
||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -191,14 +250,21 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C4D8016622B1584700C6DA1B /* BootChecks.swift in Sources */,
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
|
||||
C41C1B4D22B0215A00E7CF16 /* Services.swift in Sources */,
|
||||
C41C1B4922B00A9800E7CF16 /* ImageGenerator.swift in Sources */,
|
||||
C41C1B3922B0097F00E7CF16 /* ViewController.swift in Sources */,
|
||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
|
||||
C42295DD2358D02000E263B2 /* Command.swift in Sources */,
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
|
||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
|
||||
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
|
||||
C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */,
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
||||
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
|
||||
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
|
||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -242,6 +308,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@ -303,6 +370,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
@ -336,16 +404,21 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = phpmon/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
@ -355,16 +428,21 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = phpmon/phpmon.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = phpmon/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
@ -372,7 +450,7 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "phpmon" */ = {
|
||||
C41C1B2E22B0097F00E7CF16 /* Build configuration list for PBXProject "PHP Monitor" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C41C1B4122B0098000E7CF16 /* Debug */,
|
||||
@ -381,7 +459,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "phpmon" */ = {
|
||||
C41C1B4322B0098000E7CF16 /* Build configuration list for PBXNativeTarget "PHP Monitor" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
C41C1B4422B0098000E7CF16 /* Debug */,
|
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1220"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
|
||||
BuildableName = "PHP Monitor.app"
|
||||
BlueprintName = "PHP Monitor"
|
||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
|
||||
BuildableName = "PHP Monitor.app"
|
||||
BlueprintName = "PHP Monitor"
|
||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C41C1B3222B0097F00E7CF16"
|
||||
BuildableName = "PHP Monitor.app"
|
||||
BlueprintName = "PHP Monitor"
|
||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
91
README.md
@ -1,14 +1,49 @@
|
||||
# phpmon
|
||||
# PHP Monitor
|
||||
|
||||
<img src="./docs/phpmon.png" width="306px" alt="phpmon screenshot"/>
|
||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
||||
|
||||
phpmon is a macOS utility that runs on your Mac and displays the active PHP version in your status bar. Handy if you're running multiple versions of PHP with Homebrew and wish to see which version is currently linked & active with Laravel Valet.
|
||||
PHP Monitor (or phpmon) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar.
|
||||
|
||||
## How it works
|
||||
It also gives you quick access to various useful functionality (like switching PHP versions, restarting services, accessing configuration files, and more).
|
||||
|
||||
<img src="./docs/screenshot.png" width="362px" alt="phpmon screenshot"/>
|
||||
|
||||
For me, it comes in handy when running multiple versions of PHP with Homebrew. If you wish to be able to see at a glance which version is currently linked & active with Laravel Valet, PHP Monitor is your new best friend.
|
||||
|
||||
It's also super convenient to switch between different versions of PHP, or to find your currently active .ini file!
|
||||
|
||||
## 🖥 System requirements
|
||||
|
||||
PHP Monitor is a universal application that runs on Apple Silicon *and* Intel-based Macs.
|
||||
|
||||
* macOS 10.15 Catalina or higher (works on macOS 11 Big Sur)
|
||||
* PHP 7.4 installed with Homebrew 2.x
|
||||
* Laravel Valet 2.x
|
||||
|
||||
_Please note that future versions of PHP will not work automatically, minor changes are required to add support for newer versions of PHP._
|
||||
|
||||
## 🚀 How to install
|
||||
|
||||
You can install via Homebrew, or may download the latest [release][1].
|
||||
|
||||
To install via Homebrew, run:
|
||||
|
||||
brew tap nicoverbruggen/homebrew-cask
|
||||
brew cask install phpmon
|
||||
|
||||
_The app is signed and notarized, meaning all you have to do is approve its first launch._
|
||||
|
||||
## 👨💻 Why I built this
|
||||
|
||||
I wanted to be able to see at a glance which version of PHP was linked, and handle dealing with Laravel Valet in a simple app without having to deal with the terminal every time.
|
||||
|
||||
Initially, I had an Alfred workflow for this. But this does the job as well, while also showing me at all times which version of PHP is linked (which is the main benefit over e.g. an Alfred workflow).
|
||||
|
||||
## 🚜 How it works
|
||||
|
||||
### Version detection
|
||||
|
||||
This utility runs `php -r 'print phpversion();'` in the background periodically and extracts the version number.
|
||||
This utility runs `php -r 'print phpversion()'` in the background periodically (every 60 seconds).
|
||||
|
||||
### Switching PHP versions
|
||||
|
||||
@ -16,15 +51,53 @@ This utility will detect which PHP versions you have installed via Homebrew, and
|
||||
|
||||
This means:
|
||||
|
||||
- You have at least the latest version of PHP installed (`php@7.3`)
|
||||
- You have at least the latest version of PHP installed (`php@7.4`)
|
||||
- You have installed Laravel Valet (`which valet` returns `/usr/local/bin/valet`)
|
||||
- You ran `valet trust`, which means Valet commands can be run without using sudo
|
||||
|
||||
The utility runs the following commands:
|
||||
|
||||
- Unlink all detected PHP versions
|
||||
- Switch to PHP 7.3 (this is done in order to ensure that Valet works, even when attempting to use PHP 5.6)
|
||||
- Tell Valet to switch to a specific PHP version
|
||||
- Switch to PHP 7.4 (this is done to ensure that Valet works, even when attempting to use PHP 5.6)
|
||||
- Stop all php-fpm service instances
|
||||
- Link the desired version of PHP
|
||||
- Start the correct php-fpm service for the desired PHP version
|
||||
|
||||
If you want to know more about how this works, you can find the file Services.swift under the Helpers directory to learn more about how switching PHP versions works. In the end, this just executes some shell commands.
|
||||
### Want to know more?
|
||||
|
||||
If you want to know more about how this works, I recommend you check out the source code.
|
||||
|
||||
This app isn't very complicated after all. In the end, this just (conveniently) executes some shell commands.
|
||||
|
||||
## 🤬 Troubleshooting
|
||||
|
||||
**If you are having issues, the first thing you should be doing is installing the latest version of PHP Monitor. This can resolve a variety of issues.**
|
||||
|
||||
PHP Monitor performs some integrity checks to ensure a good experience when using the app. You'll get a message telling you that PHP Monitor won't work correctly in the following scenarios:
|
||||
|
||||
- The PHP binary is not located in `/usr/local/bin/php`
|
||||
- PHP 7.4 is missing in `/usr/local/opt`
|
||||
- Laravel Valet is missing in `/usr/local/bin/valet`
|
||||
- Brew has not been added to sudoers in `/private/etc/sudoers.d/brew`
|
||||
- Valet has not been added to sudoers in `/private/etc/sudoers.d/valet`
|
||||
- Multiple PHP services are active (see more info below)
|
||||
|
||||
Follow instructions as specified in the alert in order to resolve any issues.
|
||||
|
||||
## 📝 Additional information
|
||||
|
||||
Please consult the [additional information][2] file that contains more information.
|
||||
|
||||
## ⭐️ Star me!
|
||||
|
||||
If this software has been useful to you, I ask that you **please star the repository**, so I know that the software is being used.
|
||||
|
||||
I did not include any tracking or analytics software, so if you encounter issues, let me know [via an issue](https://github.com/nicoverbruggen/phpmon/issues/new).
|
||||
|
||||
## 💵 Support me?
|
||||
|
||||
I develop this application in my spare time, after work. If you find the application useful and you have a bit of money to spare, feel free to send me [a tip via PayPal][3].
|
||||
|
||||
[1]: https://github.com/nicoverbruggen/phpmon/releases
|
||||
[2]: docs/ADDITIONAL.md
|
||||
[3]: https://paypal.me/nicoverbruggen
|
||||
|
14
SECURITY.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The following versions of PHP Monitor are supported:
|
||||
|
||||
| Version | Universal | Supported | Runs on macOS |
|
||||
| ------- | ------------- | ------------------ | ----- |
|
||||
| 2.4 | ✅ | ✅ | Catalina (10.15), Big Sur (11.0) |
|
||||
| < 2.4 | ❌ | ❌ | Catalina (10.15) |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Contact Nico Verbruggen at the email address used for the commits in the repository. Please include "PHP Monitor" in the subject.
|
BIN
assets.sketch
99
docs/ADDITIONAL.md
Normal file
@ -0,0 +1,99 @@
|
||||
### Q&A
|
||||
|
||||
#### Q: This app is doing network requests?
|
||||
|
||||
It's Homebrew. I can't prevent `brew` from doing things via the network when I invoke it.
|
||||
|
||||
PHP Monitor itself doesn't do any network requests. Feel free to check the source code or intercept the traffic, if you don't believe me.
|
||||
|
||||
#### Q: How can I set this up on a fresh Mac?
|
||||
|
||||
If you want to set up your computer for the very first time, here's how I do it:
|
||||
|
||||
Install [Homebrew](https://brew.sh) first.
|
||||
|
||||
Install PHP, composer, add to path:
|
||||
|
||||
brew install php
|
||||
brew install composer
|
||||
nano .zshrc
|
||||
|
||||
Make sure the following line is not in the comments:
|
||||
|
||||
export PATH=$HOME/bin:/usr/local/bin:$PATH
|
||||
|
||||
and add the following to your .zshrc:
|
||||
|
||||
export PATH=$HOME/bin:~/.composer/vendor/bin:$PATH
|
||||
|
||||
Make sure PHP is linked correctly:
|
||||
|
||||
which php
|
||||
|
||||
should return: `/usr/local/bin/php`
|
||||
|
||||
composer global require laravel/valet
|
||||
valet install
|
||||
|
||||
This should install `dnsmasq` and set up Valet. Great, almost there!
|
||||
|
||||
valet trust
|
||||
|
||||
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work.
|
||||
|
||||
#### Q: I want PHP Monitor to start up when I boot my Mac!
|
||||
|
||||
You can do this by dragging *PHP Monitor.app* into the **Login Items** section in **System Preferences > Users & Groups** for your account.
|
||||
|
||||
Super convenient!
|
||||
|
||||
#### Q: PHP Monitor says that the latest version of PHP is not installed, but it is!
|
||||
|
||||
Try installing again using `brew install php@7.4`.
|
||||
|
||||
This should resolve the issue.
|
||||
|
||||
#### Q: PHP Monitor reports another version compared to phpinfo on my local website, what is going on?
|
||||
|
||||
_Beginning with version 2.0 you'll get alerts about this at startup._
|
||||
|
||||
If you're still seeing another version of PHP in your scripts running on your local webserver (nginx) — e.g. when running `phpinfo()` — I recommend you shut down all PHP services that are currently active. You can find out what services are active by running:
|
||||
|
||||
sudo brew services list | grep php
|
||||
|
||||
This will present to you a list of services, like so (depending on the installed versions of PHP):
|
||||
|
||||
```
|
||||
php started root /Library/LaunchDaemons/homebrew.mxcl.php.plist
|
||||
php@5.6 stopped
|
||||
php@7.0 stopped
|
||||
php@7.1 stopped
|
||||
php@7.2 stopped
|
||||
php@7.3 stopped
|
||||
```
|
||||
|
||||
You'll want to make sure that **only one service is running** and that it is running **as `root`**. You can terminate a service by running:
|
||||
|
||||
sudo brew services stop {service_name}
|
||||
|
||||
So in order to disable PHP 7.3, you'd need to run:
|
||||
|
||||
sudo brew services stop php@7.3
|
||||
|
||||
If you notice that PHP FPM is running as your own user account, you can turn off the service by running:
|
||||
|
||||
brew services stop php@7.3
|
||||
|
||||
The easiest way to make sure that PHP Monitor works again is to run the following commands:
|
||||
|
||||
sudo brew services stop php
|
||||
sudo brew services stop php@7.3
|
||||
sudo brew services stop php@7.2
|
||||
sudo brew services stop php@7.1
|
||||
sudo brew services stop php@7.0
|
||||
sudo brew services stop php@5.6
|
||||
sudo brew services stop nginx
|
||||
|
||||
Then, in PHP Monitor, select "Restart php-fpm service", which should start the service.
|
||||
|
||||
Alternatively, you can run `sudo brew services start php@7.4` where `7.4` is your preferred version of PHP (for the latest version of PHP, you may omit `@7.4` like in the example above).
|
13
docs/RELEASE.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Release Procedure
|
||||
|
||||
1. Merge into `main`
|
||||
2. Create tag
|
||||
3. Add changes to changelog
|
||||
4. Archive
|
||||
5. Notarize and prepare for own distribution
|
||||
6. After notarization, export .app
|
||||
7. Create zipped version
|
||||
8. Calculate SHA256: `openssl dgst -sha256 phpmon-2.x.zip`
|
||||
9. Upload to GitHub
|
||||
10. Update Cask
|
||||
11. Check new version can be installed via Cask
|
BIN
docs/phpmon.png
Before Width: | Height: | Size: 317 KiB |
BIN
docs/screenshot.png
Normal file
After Width: | Height: | Size: 295 KiB |
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:phpmon.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
@ -1,152 +1,74 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// phpmon
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import UserNotifications
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate {
|
||||
|
||||
// MARK: - Variables
|
||||
|
||||
let statusItem = NSStatusBar.system.statusItem(withLength: 32)
|
||||
var timer: Timer?
|
||||
var version: PhpVersion? = nil
|
||||
var availablePhpVersions : [String] = []
|
||||
var busy: Bool = false
|
||||
/**
|
||||
The Shell singleton that keeps track of the history of all
|
||||
(invoked by PHP Monitor) shell commands. It is used to
|
||||
invoke all commands in this application.
|
||||
*/
|
||||
let sharedShell : Shell
|
||||
|
||||
/**
|
||||
The App singleton contains information about the state of
|
||||
the application and global variables.
|
||||
*/
|
||||
let state : App
|
||||
|
||||
/**
|
||||
The MainMenu singleton is responsible for rendering the
|
||||
menu bar item and its menu, as well as its actions.
|
||||
*/
|
||||
let menu : MainMenu
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
/**
|
||||
When the application initializes, create all singletons.
|
||||
*/
|
||||
override init() {
|
||||
self.sharedShell = Shell.user
|
||||
self.state = App.shared
|
||||
self.menu = MainMenu.shared
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
|
||||
/**
|
||||
When the application has finished launching, we'll want to set up
|
||||
the user notification center delegate, and kickoff the menu
|
||||
startup procedure.
|
||||
*/
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
// Start with the icon
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
// Perform environment boot checks
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
BootChecks.perform()
|
||||
self.availablePhpVersions = Services.detectPhpVersions()
|
||||
print("The following PHP versions were detected:")
|
||||
print(self.availablePhpVersions)
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Schedule a request to fetch the PHP version every 15 seconds
|
||||
Timer.scheduledTimer(
|
||||
timeInterval: 15,
|
||||
target: self,
|
||||
selector: #selector(self.updatePhpVersionInStatusBar),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
}
|
||||
NSUserNotificationCenter.default.delegate = self
|
||||
self.menu.startup()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
// Insert code here to tear down your application
|
||||
}
|
||||
// MARK: - NSUserNotificationCenterDelegate
|
||||
|
||||
// MARK: - UI related
|
||||
|
||||
func setStatusBarImage(version: String) {
|
||||
self.setStatusBar(image: ImageGenerator.generateImageForStatusBar(width: 32.0, text: version))
|
||||
}
|
||||
|
||||
func setStatusBar(image: NSImage) {
|
||||
if let button = statusItem.button {
|
||||
image.isTemplate = true
|
||||
button.image = image
|
||||
}
|
||||
}
|
||||
|
||||
func updateMenu() {
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
let menu = NSMenu()
|
||||
var string = "We are not sure what version of PHP you are running."
|
||||
if (self.version != nil) {
|
||||
string = "You are running PHP \(self.version!.long)"
|
||||
}
|
||||
menu.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
|
||||
if (self.version != nil) {
|
||||
// Actions
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
menu.addItem(NSMenuItem(title: "Open php.ini in Finder", action: #selector(self.openActiveConfigFolder), keyEquivalent: ""))
|
||||
// menu.addItem(NSMenuItem(title: "Restart PHP \(self.version!.short) service", action: #selector(self.restartPhp), keyEquivalent: ""))
|
||||
}
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
if (self.availablePhpVersions.count > 0 && !self.busy) {
|
||||
var shortcutKey = 1
|
||||
for index in (0..<self.availablePhpVersions.count).reversed() {
|
||||
let version = self.availablePhpVersions[index]
|
||||
let action = #selector(self.switchToPhpVersion(sender:))
|
||||
let menuItem = NSMenuItem(title: "Switch to PHP \(version)", action: (version == self.version?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
|
||||
menuItem.tag = index
|
||||
shortcutKey = shortcutKey + 1
|
||||
menu.addItem(menuItem)
|
||||
}
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
if (self.busy) {
|
||||
menu.addItem(NSMenuItem(title: "Switching PHP versions...", action: nil, keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
menu.addItem(NSMenuItem(title: "About phpmon", action: #selector(self.openAbout), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "Quit phpmon", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
|
||||
DispatchQueue.main.async {
|
||||
self.statusItem.menu = menu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Callable via Obj-C (#selector)
|
||||
|
||||
@objc func updatePhpVersionInStatusBar() {
|
||||
self.version = PhpVersion()
|
||||
if (self.busy) {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBarImage(version: self.version!.short)
|
||||
}
|
||||
}
|
||||
self.updateMenu()
|
||||
}
|
||||
|
||||
@objc public func openAbout() {
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
NSApplication.shared.orderFrontStandardAboutPanel()
|
||||
}
|
||||
|
||||
@objc public func openActiveConfigFolder() {
|
||||
Services.openPhpConfigFolder(version: self.version!.short)
|
||||
}
|
||||
|
||||
@objc public func restartPhp() {
|
||||
Services.restartPhp(version: self.version!.short)
|
||||
}
|
||||
|
||||
@objc public func switchToPhpVersion(sender: AnyObject) {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
let index = sender.tag!
|
||||
let version = self.availablePhpVersions[index]
|
||||
self.busy = true
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
// Update the PHP version in the status bar
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Update the menu
|
||||
self.updateMenu()
|
||||
// Switch the PHP version
|
||||
Services.switchToPhpVersion(version: version, availableVersions: self.availablePhpVersions)
|
||||
// Mark as no longer busy
|
||||
self.busy = false
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async {
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.updateMenu()
|
||||
}
|
||||
}
|
||||
/**
|
||||
When a notification is sent, the delegate of the notification center
|
||||
is asked whether the notification should be presented or not. Since
|
||||
the user can now disable notifications per application since macOS
|
||||
Catalina, any and all notifications should be displayed.
|
||||
*/
|
||||
func userNotificationCenter(
|
||||
_ center: NSUserNotificationCenter,
|
||||
shouldPresent notification: NSUserNotification
|
||||
) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,68 +1,68 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"size" : "16x16",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_16x16@2x.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"size" : "32x32",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_32x32@2x.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_128x128@2x.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_256x256@2x.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "icon_512x512@2x.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 714 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 422 KiB After Width: | Height: | Size: 315 KiB |
132
phpmon/Classes/Commands/Actions.swift
Normal file
@ -0,0 +1,132 @@
|
||||
//
|
||||
// Services.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
class Actions {
|
||||
|
||||
public static func detectPhpVersions() -> [String] {
|
||||
let files = Shell.user.pipe("ls /usr/local/opt | grep php@")
|
||||
var versions = files.components(separatedBy: "\n")
|
||||
// Remove all empty strings
|
||||
versions.removeAll { (string) -> Bool in
|
||||
return (string == "")
|
||||
}
|
||||
// Get a list of versions only
|
||||
var versionsOnly : [String] = []
|
||||
versions.forEach { (string) in
|
||||
versionsOnly.append(string.components(separatedBy: "php@")[1])
|
||||
}
|
||||
return versionsOnly
|
||||
}
|
||||
|
||||
public static func restartPhpFpm() {
|
||||
let version = App.shared.currentVersion!.short
|
||||
if (version == Constants.LatestPhpVersion) {
|
||||
Shell.user.run("sudo brew services restart php")
|
||||
} else {
|
||||
Shell.user.run("sudo brew services restart php@\(version)")
|
||||
}
|
||||
}
|
||||
|
||||
public static func restartNginx()
|
||||
{
|
||||
Shell.user.run("sudo brew services restart nginx")
|
||||
}
|
||||
|
||||
public static func switchToPhpVersion(version: String, availableVersions: [String]) {
|
||||
availableVersions.forEach { (version) in
|
||||
// Unlink the current version
|
||||
Shell.user.run("brew unlink php@\(version)")
|
||||
// Stop the services
|
||||
if (version == Constants.LatestPhpVersion) {
|
||||
Shell.user.run("sudo brew services stop php")
|
||||
} else {
|
||||
Shell.user.run("sudo brew services stop php@\(version)")
|
||||
}
|
||||
}
|
||||
if (availableVersions.contains(Constants.LatestPhpVersion)) {
|
||||
// Use the latest version as a default
|
||||
Shell.user.run("brew link php@\(Constants.LatestPhpVersion) --overwrite --force")
|
||||
if (version == Constants.LatestPhpVersion) {
|
||||
// If said version was also requested, all we need to do is start the service
|
||||
Shell.user.run("sudo brew services start php")
|
||||
} else {
|
||||
// Otherwise, link the correct php version + start the correct service
|
||||
Shell.user.run("brew link php@\(version) --overwrite --force")
|
||||
Shell.user.run("sudo brew services start php@\(version)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func openGenericPhpConfigFolder() {
|
||||
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php")];
|
||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||
}
|
||||
|
||||
public static func openPhpConfigFolder(version: String) {
|
||||
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")];
|
||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||
}
|
||||
|
||||
public static func openValetConfigFolder() {
|
||||
let files = [NSURL(fileURLWithPath: NSString(string: "~/.config/valet").expandingTildeInPath)];
|
||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL])
|
||||
}
|
||||
|
||||
public static func didFindXdebug(_ version: String) -> Bool {
|
||||
let command = """
|
||||
grep -q 'zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
|
||||
"""
|
||||
let output = Shell.user.pipe(command).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return (output == "YES")
|
||||
}
|
||||
|
||||
public static func didEnableXdebug(_ version: String) -> Bool {
|
||||
let command = """
|
||||
grep -q '; zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO"
|
||||
"""
|
||||
let output = Shell.user.pipe(command).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return (output == "NO")
|
||||
}
|
||||
|
||||
public static func toggleXdebug() {
|
||||
let version = App.shared.currentVersion?.short
|
||||
var command = """
|
||||
sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
|
||||
"""
|
||||
if (self.didEnableXdebug(version!)) {
|
||||
command = """
|
||||
sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini
|
||||
"""
|
||||
}
|
||||
Shell.user.run(command)
|
||||
}
|
||||
|
||||
// unlink all the crap and link the latest version
|
||||
// this also restarts all services
|
||||
public static func fixMyPhp() {
|
||||
let versions = self.detectPhpVersions()
|
||||
versions.forEach { (version) in
|
||||
Shell.user.run("brew unlink php@\(version)")
|
||||
if (version == Constants.LatestPhpVersion) {
|
||||
Shell.user.run("brew services stop php")
|
||||
Shell.user.run("sudo brew services stop php")
|
||||
} else {
|
||||
Shell.user.run("brew services stop php@\(version)")
|
||||
Shell.user.run("sudo brew services stop php@\(version)")
|
||||
}
|
||||
}
|
||||
Shell.user.run("brew services stop php")
|
||||
Shell.user.run("brew services stop nginx")
|
||||
Shell.user.run("brew link php")
|
||||
Shell.user.run("sudo brew services restart php")
|
||||
Shell.user.run("sudo brew services restart nginx")
|
||||
}
|
||||
}
|
112
phpmon/Classes/Commands/Startup.swift
Normal file
@ -0,0 +1,112 @@
|
||||
//
|
||||
// Environment.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 12/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Startup {
|
||||
|
||||
public var failed : Bool = false
|
||||
public var failureCallback = {}
|
||||
|
||||
/**
|
||||
Checks the user's environment and checks if PHP Monitor can be used properly.
|
||||
This checks if PHP is installed, Valet is running, the appropriate permissions are set, and more.
|
||||
|
||||
- Parameter success: Callback that is fired if the application can proceed with launch
|
||||
- Parameter failure: Callback that is fired if the application must retry launch
|
||||
*/
|
||||
public func checkEnvironment(success: () -> Void, failure: @escaping () -> Void)
|
||||
{
|
||||
self.failureCallback = failure
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
!Shell.user.pipe("which php").contains("/usr/local/bin/php"),
|
||||
messageText: "PHP is not correctly installed",
|
||||
informativeText: "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php`. The app will not work correctly until you resolve this issue. (Usually `brew link php` resolves this issue.)",
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
!Shell.user.pipe("ls /usr/local/opt | grep php@7.4").contains("php@7.4"),
|
||||
messageText: "PHP 7.4 is not correctly installed",
|
||||
informativeText: "PHP 7.4 alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue. If you already have the `php` formula installed, you may need to run `brew install php@7.4` in order for PHP Monitor to detect this installation.",
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
!Shell.user.pipe("which valet").contains("/usr/local/bin/valet"),
|
||||
messageText: "Laravel Valet is not correctly installed",
|
||||
informativeText: "You must install Valet with composer. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue.",
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
!Shell.user.pipe("cat /private/etc/sudoers.d/brew").contains("/usr/local/bin/brew"),
|
||||
messageText: "Brew has not been added to sudoers.d",
|
||||
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.",
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
!Shell.user.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet"),
|
||||
messageText: "Valet has not been added to sudoers.d",
|
||||
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue.",
|
||||
breaking: true
|
||||
)
|
||||
|
||||
let services = Shell.user.pipe("brew services list | grep php")
|
||||
self.performEnvironmentCheck(
|
||||
(services.countInstances(of: "started") > 1),
|
||||
messageText: "Multiple PHP services are active",
|
||||
informativeText: "This can cause php-fpm to serve a more recent version of PHP than the one you'd like to see active. Please terminate all extra PHP processes." +
|
||||
"\n\nThe easiest solution is to choose the option 'Force load latest PHP version' in the menu bar." +
|
||||
"\n\nAlternatively, you can fix this manually. You can do this by running `brew services list` and running `sudo brew services stop php@7.3` (and use the version that applies)." +
|
||||
"\n\nPHP Monitor usually handles the starting and stopping of these services, so once the correct version is the only PHP version running you should not have any issues. It is recommended to restart PHP Monitor once you have resolved this issue." +
|
||||
"\n\nFor more information about this issue, please see the README.md file in the repository on GitHub.",
|
||||
breaking: false
|
||||
)
|
||||
|
||||
if (!self.failed) {
|
||||
success()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an environment check. Will cause the application to terminate, if `breaking` is set to true.
|
||||
*
|
||||
* - Parameter condition: Condition to check for
|
||||
* - Parameter messageText: Short description of what is wrong
|
||||
* - Parameter informativeText: Expanded description of the environment check that failed
|
||||
* - Parameter breaking: If the application should terminate afterwards
|
||||
*/
|
||||
private func performEnvironmentCheck(
|
||||
_ condition: Bool,
|
||||
messageText: String,
|
||||
informativeText: String,
|
||||
breaking: Bool
|
||||
)
|
||||
{
|
||||
if (condition) {
|
||||
// Only breaking issues will cause the notification
|
||||
if (breaking) {
|
||||
self.failed = true
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
// Present the information to the user
|
||||
_ = Alert.present(
|
||||
messageText: messageText,
|
||||
informativeText: informativeText
|
||||
)
|
||||
// Only breaking issues will throw the extra retry modal
|
||||
if (breaking) {
|
||||
self.failureCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
//
|
||||
// Alert.swift
|
||||
// phpmon
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 12/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
@ -12,12 +12,16 @@ class Alert {
|
||||
public static func present(
|
||||
messageText: String,
|
||||
informativeText: String,
|
||||
buttonTitle: String = "OK"
|
||||
) {
|
||||
buttonTitle: String = "OK",
|
||||
secondButtonTitle: String = ""
|
||||
) -> Bool {
|
||||
let alert = NSAlert.init()
|
||||
alert.messageText = messageText
|
||||
alert.informativeText = informativeText
|
||||
alert.addButton(withTitle: buttonTitle)
|
||||
alert.runModal()
|
||||
if (!secondButtonTitle.isEmpty) {
|
||||
alert.addButton(withTitle: secondButtonTitle)
|
||||
}
|
||||
return alert.runModal() == .alertFirstButtonReturn
|
||||
}
|
||||
}
|
19
phpmon/Classes/Helpers/LocalNotification.swift
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// LocalNotification.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/07/2020.
|
||||
// Copyright © 2020 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class LocalNotification {
|
||||
public static func send(title: String, subtitle: String)
|
||||
{
|
||||
let notification = NSUserNotification()
|
||||
notification.title = title
|
||||
notification.subtitle = subtitle
|
||||
NSUserNotificationCenter.default.deliver(notification)
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
//
|
||||
// ImageGenerator.swift
|
||||
// phpmon
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
@ -8,14 +8,15 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
class ImageGenerator {
|
||||
class MenuBarImageGenerator {
|
||||
|
||||
public static func generateImageForStatusBar(width: CGFloat = 30.0, height: CGFloat = 20.0, text: String) -> NSImage {
|
||||
let image = NSImage(size: NSMakeSize(width, height))
|
||||
let font = NSFont.systemFont(ofSize: 14)
|
||||
/**
|
||||
Takes a string and converts it to an image that can be displayed in the menu bar.
|
||||
The width of the NSImage depends on the length of the text.
|
||||
*/
|
||||
public static func textToImage(text: String) -> NSImage {
|
||||
|
||||
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
|
||||
let textRect = CGRect(x: 5, y: -1, width: image.size.width, height: image.size.height)
|
||||
let font = NSFont.systemFont(ofSize: 14, weight: .medium)
|
||||
|
||||
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
|
||||
let textFontAttributes = [
|
||||
@ -24,8 +25,23 @@ class ImageGenerator {
|
||||
NSAttributedString.Key.paragraphStyle: textStyle
|
||||
]
|
||||
|
||||
let targetImage: NSImage = NSImage(size: image.size)
|
||||
let padding : CGFloat = 2.0;
|
||||
|
||||
// Create an attributed string so we'll know how wide the item will need to be
|
||||
let attributedString = NSAttributedString(string: text, attributes: textFontAttributes)
|
||||
let textSize = attributedString.size()
|
||||
|
||||
// Add padding to the width of the menu bar item
|
||||
let size = NSSize(width: textSize.width + (2 * padding), height: textSize.height)
|
||||
let image = NSImage(size: size)
|
||||
|
||||
// Set the image rect with the appropriate dimensions
|
||||
let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
|
||||
|
||||
// Position the text inside the image rect
|
||||
let textRect = CGRect(x: padding, y: 0.5, width: image.size.width, height: image.size.height)
|
||||
|
||||
let targetImage: NSImage = NSImage(size: image.size)
|
||||
let rep: NSBitmapImageRep = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(image.size.width),
|
48
phpmon/Classes/Helpers/PhpVersion.swift
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// PhpVersionExtractor.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PhpVersion {
|
||||
|
||||
var short : String = "???"
|
||||
var long : String = "???"
|
||||
|
||||
var xdebugFound: Bool = false
|
||||
var xdebugEnabled : Bool = false
|
||||
|
||||
var error : Bool = false
|
||||
|
||||
init() {
|
||||
let version = Command.execute(path: "/usr/local/bin/php", arguments: ["-r", "print phpversion();"])
|
||||
|
||||
if (version == "" || version.contains("Warning")) {
|
||||
self.short = "💩 BROKEN"
|
||||
self.long = "";
|
||||
self.error = true
|
||||
return;
|
||||
}
|
||||
|
||||
// That's the long version
|
||||
self.long = version
|
||||
|
||||
// Next up, let's strip away the minor version number
|
||||
let segments = long.components(separatedBy: ".")
|
||||
|
||||
// Get the first two elements
|
||||
self.short = segments[0...1].joined(separator: ".")
|
||||
|
||||
// Load xdebug support
|
||||
self.xdebugFound = Actions.didFindXdebug(self.short)
|
||||
if (self.xdebugFound) {
|
||||
self.xdebugEnabled = Actions.didEnableXdebug(self.short)
|
||||
}
|
||||
|
||||
self.error = false;
|
||||
}
|
||||
}
|
89
phpmon/Classes/Menu/StatusMenu.swift
Normal file
@ -0,0 +1,89 @@
|
||||
//
|
||||
// MainMenuBuilder.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/05/2020.
|
||||
// Copyright © 2020 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class StatusMenu : NSMenu {
|
||||
|
||||
public func addPhpVersionMenuItems()
|
||||
{
|
||||
var string = "mi_unsure".localized
|
||||
if (App.shared.currentVersion != nil) {
|
||||
if (!App.shared.currentVersion!.error) {
|
||||
// in case the php version loaded without issue
|
||||
string = "\("mi_php_version".localized) \(App.shared.currentVersion!.long)"
|
||||
self.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: ""))
|
||||
} else {
|
||||
// in case of an error show the error message
|
||||
["mi_php_broken_1", "mi_php_broken_2",
|
||||
"mi_php_broken_3", "mi_php_broken_4"].forEach { (message) in
|
||||
self.addItem(NSMenuItem(title: message.localized, action: nil, keyEquivalent: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func addPhpActionMenuItems()
|
||||
{
|
||||
if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) {
|
||||
var shortcutKey = 1
|
||||
for index in (0..<App.shared.availablePhpVersions.count).reversed() {
|
||||
let version = App.shared.availablePhpVersions[index]
|
||||
let action = #selector(MainMenu.switchToPhpVersion(sender:))
|
||||
let menuItem = NSMenuItem(title: "\("mi_php_switch".localized) \(version)", action: (version == App.shared.currentVersion?.short) ? nil : action, keyEquivalent: "\(shortcutKey)")
|
||||
menuItem.tag = index
|
||||
shortcutKey = shortcutKey + 1
|
||||
self.addItem(menuItem)
|
||||
}
|
||||
self.addItem(NSMenuItem.separator())
|
||||
self.addItem(NSMenuItem(title: "mi_active_services".localized, action: nil, keyEquivalent: ""))
|
||||
self.addItem(NSMenuItem(title: "mi_restart_php_fpm".localized, action: #selector(MainMenu.restartPhpFpm), keyEquivalent: "f"))
|
||||
self.addItem(NSMenuItem(title: "mi_restart_nginx".localized, action: #selector(MainMenu.restartNginx), keyEquivalent: "n"))
|
||||
self.addItem(NSMenuItem(title: "mi_force_load_latest".localized, action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: ""))
|
||||
}
|
||||
if (App.shared.busy) {
|
||||
self.addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
|
||||
}
|
||||
}
|
||||
|
||||
public func addPhpConfigurationMenuItems()
|
||||
{
|
||||
if (App.shared.currentVersion != nil) {
|
||||
self.addItem(NSMenuItem(title: "mi_configuration".localized, action: nil, keyEquivalent: ""))
|
||||
self.addItem(NSMenuItem(title: "mi_valet_config".localized, action: #selector(MainMenu.openValetConfigFolder), keyEquivalent: "v"))
|
||||
self.addItem(NSMenuItem(title: "mi_php_config".localized, action: #selector(MainMenu.openActiveConfigFolder), keyEquivalent: "c"))
|
||||
self.addItem(NSMenuItem.separator())
|
||||
self.addItem(NSMenuItem(title: "mi_enabled_extensions".localized, action: nil, keyEquivalent: ""))
|
||||
self.addXdebugMenuItem()
|
||||
}
|
||||
}
|
||||
|
||||
private func addXdebugMenuItem()
|
||||
{
|
||||
let xdebugFound = App.shared.currentVersion!.xdebugFound
|
||||
if (xdebugFound) {
|
||||
let xdebugOn = App.shared.currentVersion!.xdebugEnabled
|
||||
let xdebugToggleMenuItem = NSMenuItem(
|
||||
title: "mi_xdebug".localized,
|
||||
action: #selector(MainMenu.toggleXdebug), keyEquivalent: "x"
|
||||
)
|
||||
if (xdebugOn) {
|
||||
xdebugToggleMenuItem.state = .on
|
||||
}
|
||||
self.addItem(xdebugToggleMenuItem)
|
||||
} else {
|
||||
let disabledItem = NSMenuItem(
|
||||
title: "mi_xdebug_missing".localized,
|
||||
action: nil, keyEquivalent: "x"
|
||||
)
|
||||
disabledItem.isEnabled = false
|
||||
self.addItem(disabledItem)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
//
|
||||
// PhpVersionExtractor.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PhpVersion {
|
||||
|
||||
var short : String = "???"
|
||||
var long : String = "???"
|
||||
|
||||
init() {
|
||||
let version = Shell
|
||||
// Get the version directly from PHP
|
||||
.execute(command: "php -r 'print phpversion();'")
|
||||
// also remove any colors
|
||||
.replacingOccurrences(of: "\u{1b}(B\u{1b}[m", with: "")
|
||||
|
||||
// That's the long version
|
||||
self.long = version
|
||||
|
||||
// Next up, let's strip away the minor version number
|
||||
let segments = long.components(separatedBy: ".")
|
||||
// Get the first two elements
|
||||
self.short = segments[0...1].joined(separator: ".")
|
||||
}
|
||||
}
|
26
phpmon/Constants.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Constants.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 08/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Constants {
|
||||
|
||||
/**
|
||||
* The PHP versions supported by this application.
|
||||
*/
|
||||
static let SupportedPhpVersions = [
|
||||
"5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0"
|
||||
]
|
||||
|
||||
/**
|
||||
Which php version is aliased as `php` to brew?
|
||||
This is usually the latest PHP version.
|
||||
*/
|
||||
static let LatestPhpVersion = "7.4"
|
||||
|
||||
}
|
18
phpmon/Extensions/DateExtension.swift
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Date.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 09/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension Date
|
||||
{
|
||||
func toString() -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
return dateFormatter.string(from: self)
|
||||
}
|
||||
}
|
32
phpmon/Extensions/StringExtension.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// StringExtension.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 13/05/2020.
|
||||
// Copyright © 2020 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
var localized: String {
|
||||
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
|
||||
}
|
||||
|
||||
func countInstances(of stringToFind: String) -> Int {
|
||||
if (stringToFind.isEmpty) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var searchRange: Range<String.Index>?
|
||||
|
||||
while let foundRange = range(of: stringToFind, options: [], range: searchRange) {
|
||||
count += 1
|
||||
searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex))
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
}
|
47
phpmon/IAP/InternetAccessPolicy.plist
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ApplicationDescription</key>
|
||||
<string>PHP Monitor is a tool that shows the active PHP version in your menu bar and gives you easy access to certain PHP service actions and config files.</string>
|
||||
<key>DeveloperName</key>
|
||||
<string>Nico Verbruggen</string>
|
||||
<key>Website</key>
|
||||
<string>https://github.com/nicoverbruggen/phpmon</string>
|
||||
<key>Connections</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>IsIncoming</key>
|
||||
<false/>
|
||||
<key>Host</key>
|
||||
<string>registry.npmjs.org</string>
|
||||
<key>NetworkProtocol</key>
|
||||
<string>TCP</string>
|
||||
<key>Port</key>
|
||||
<string>80, 443</string>
|
||||
<key>Relevance</key>
|
||||
<string>Essential</string>
|
||||
<key>Purpose</key>
|
||||
<string>PHP Monitor directly invokes Homebrew which contacts the NPM Registry.</string>
|
||||
<key>DenyConsequences</key>
|
||||
<string>If you deny these connections, PHP Monitor might not be able to complete its preset set of instructions, causing version switching to fail.</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>IsIncoming</key>
|
||||
<false/>
|
||||
<key>Host</key>
|
||||
<string>github.com, api.github.com</string>
|
||||
<key>NetworkProtocol</key>
|
||||
<string>TCP</string>
|
||||
<key>Port</key>
|
||||
<string>443</string>
|
||||
<key>Relevance</key>
|
||||
<string>Essential</string>
|
||||
<key>Purpose</key>
|
||||
<string>PHP Monitor directly invokes Homebrew which contacts GitHub.</string>
|
||||
<key>DenyConsequences</key>
|
||||
<string>If you deny these connections, PHP Monitor might not be able to complete its preset set of instructions, causing version switching to fail.</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
2
phpmon/IAP/InternetAccessPolicy.strings
Normal file
@ -0,0 +1,2 @@
|
||||
// Top-level, general application description:
|
||||
"ApplicationDescription" = "PHP Monitor is a tool that shows the active PHP version in your menu bar and gives you easy access to certain PHP service actions and config files.";
|
@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>10</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
@ -27,7 +27,7 @@
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2019 Nico Verbruggen. All rights reserved.</string>
|
||||
<string>Copyright © 2020 Nico Verbruggen. All rights reserved.</string>
|
||||
<key>NSMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
55
phpmon/Localizable.strings
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Strings.strings
|
||||
PHP Monitor
|
||||
|
||||
Created by Nico Verbruggen on 16/05/2020.
|
||||
Copyright © 2020 Nico Verbruggen. All rights reserved.
|
||||
*/
|
||||
|
||||
// MENU ITEMS (MI)
|
||||
|
||||
"mi_busy" = "PHP Monitor is busy...";
|
||||
"mi_unsure" = "We are not sure what version of PHP you are running.";
|
||||
"mi_php_version" = "You are running PHP";
|
||||
"mi_php_switch" = "Switch to PHP";
|
||||
"mi_php_broken_1" = "Oof! It appears your PHP installation is broken...";
|
||||
"mi_php_broken_2" = "Try running `php -v` in your terminal.";
|
||||
"mi_php_broken_3" = "You could also try switching to another version.";
|
||||
"mi_php_broken_4" = "Running `brew reinstall php` (or for the equivalent version) might help.";
|
||||
|
||||
"mi_active_services" = "Active Services";
|
||||
"mi_restart_php_fpm" = "Restart php-fpm service";
|
||||
"mi_restart_nginx" = "Restart nginx service";
|
||||
"mi_force_load_latest" = "Force load latest PHP version";
|
||||
|
||||
"mi_configuration" = "Configuration";
|
||||
"mi_valet_config" = "Valet configuration (.config/valet)";
|
||||
"mi_php_config" = "PHP Configuration file (php.ini)";
|
||||
"mi_enabled_extensions" = "Enabled Extensions";
|
||||
|
||||
"mi_xdebug" = "Xdebug";
|
||||
"mi_xdebug_missing" = "xdebug.so missing";
|
||||
|
||||
"mi_quit" = "Quit PHP Monitor";
|
||||
"mi_about" = "About PHP Monitor";
|
||||
|
||||
// NOTIFICATIONS
|
||||
|
||||
"notification.version_changed_title" = "PHP %@ now active";
|
||||
"notification.version_changed_desc" = "PHP Monitor has finished the switch to PHP %@.";
|
||||
|
||||
// ALERTS
|
||||
|
||||
// Force Reload Started
|
||||
"alert.force_reload.title" = "PHP Monitor will force reload the latest version of PHP";
|
||||
"alert.force_reload.info" = "This can take a while. You'll get another alert when the force reload has completed.";
|
||||
|
||||
// Force Reload Done
|
||||
"alert.force_reload_done.title" = "PHP has been force reloaded";
|
||||
"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP.";
|
||||
|
||||
// PHP Monitor Cannot Start
|
||||
"alert.cannot_start.title" = "PHP Monitor cannot start";
|
||||
"alert.cannot_start.info" = "The issue you were just notified about is keeping PHP Monitor from functioning correctly. Please fix the issue and restart PHP Monitor. After clicking on OK, PHP Monitor will close.\n\nIf you have fixed the issue (or don't remember what the exact issue is) you can click on Retry, which will have PHP Monitor retry the startup checks.";
|
||||
"alert.cannot_start.close" = "Close";
|
||||
"alert.cannot_start.retry" = "Retry";
|
35
phpmon/Singletons/App.swift
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// StateManager.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class App {
|
||||
|
||||
static let shared = App()
|
||||
|
||||
/**
|
||||
Whether the application is busy switching versions.
|
||||
*/
|
||||
var busy: Bool = false
|
||||
|
||||
/**
|
||||
The currently active version of PHP.
|
||||
*/
|
||||
var currentVersion: PhpVersion? = nil
|
||||
|
||||
/**
|
||||
All available versions of PHP.
|
||||
*/
|
||||
var availablePhpVersions : [String] = []
|
||||
|
||||
/**
|
||||
The timer that will periodically fetch the PHP version that is currently active.
|
||||
*/
|
||||
var timer: Timer?
|
||||
|
||||
}
|
33
phpmon/Singletons/Command.swift
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// Command.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 17/10/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Command {
|
||||
|
||||
/**
|
||||
Immediately executes a command.
|
||||
|
||||
- Parameter path: The path of the command or program to invoke.
|
||||
- Parameter arguments: A list of arguments that are passed on.
|
||||
*/
|
||||
public static func execute(path: String, arguments: [String]) -> String {
|
||||
let task = Process()
|
||||
task.launchPath = path
|
||||
task.arguments = arguments
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let output: String = String.init(data: data, encoding: String.Encoding.utf8)!
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
270
phpmon/Singletons/MainMenu.swift
Normal file
@ -0,0 +1,270 @@
|
||||
//
|
||||
// MainMenu.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/07/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class MainMenu: NSObject, NSWindowDelegate {
|
||||
|
||||
static let shared = MainMenu()
|
||||
|
||||
/**
|
||||
The status bar item with variable length.
|
||||
*/
|
||||
let statusItem = NSStatusBar.system.statusItem(
|
||||
withLength: NSStatusItem.variableLength
|
||||
)
|
||||
|
||||
// MARK: - UI related
|
||||
|
||||
/**
|
||||
Kick off the startup of the rendering of the main menu.
|
||||
*/
|
||||
public func startup() {
|
||||
// Start with the icon
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
// Perform environment boot checks
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
Startup().checkEnvironment(success: {
|
||||
self.onEnvironmentPass()
|
||||
}, failure: {
|
||||
self.onEnvironmentFail()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
When the environment is all clear and the app can run, let's go.
|
||||
*/
|
||||
private func onEnvironmentPass() {
|
||||
App.shared.availablePhpVersions = Actions.detectPhpVersions()
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Schedule a request to fetch the PHP version every 60 seconds
|
||||
DispatchQueue.main.async {
|
||||
App.shared.timer = Timer.scheduledTimer(
|
||||
timeInterval: 60,
|
||||
target: self,
|
||||
selector: #selector(self.updatePhpVersionInStatusBar),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
When the environment is not OK, present an alert to inform the user.
|
||||
*/
|
||||
private func onEnvironmentFail() {
|
||||
DispatchQueue.main.async {
|
||||
let close = Alert.present(
|
||||
messageText: "alert.cannot_start.title".localized,
|
||||
informativeText: "alert.cannot_start.info".localized,
|
||||
buttonTitle: "alert.cannot_start.close".localized,
|
||||
secondButtonTitle: "alert.cannot_start.retry".localized
|
||||
)
|
||||
if (!close) {
|
||||
self.startup()
|
||||
} else {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Update the menu's contents, based on what's going on.
|
||||
*/
|
||||
public func update() {
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
// Create a new menu
|
||||
let menu = StatusMenu()
|
||||
|
||||
// Add the PHP versions (or error messages)
|
||||
menu.addPhpVersionMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add the possible actions
|
||||
menu.addPhpActionMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add information about services & actions
|
||||
menu.addPhpConfigurationMenuItems()
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
// Add about & quit menu items
|
||||
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(self.openAbout), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(self.terminateApp), keyEquivalent: "q"))
|
||||
|
||||
// Make sure every item can be interacted with
|
||||
menu.items.forEach({ (item) in
|
||||
item.target = self
|
||||
})
|
||||
|
||||
// Update the menu item on the main thread
|
||||
DispatchQueue.main.async {
|
||||
self.statusItem.menu = menu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the status bar image based on a version string.
|
||||
*/
|
||||
func setStatusBarImage(version: String) {
|
||||
self.setStatusBar(
|
||||
image: MenuBarImageGenerator.textToImage(text: version)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the status bar image, based on the provided NSImage.
|
||||
The image will be used as a template image.
|
||||
*/
|
||||
func setStatusBar(image: NSImage) {
|
||||
if let button = statusItem.button {
|
||||
image.isTemplate = true
|
||||
button.image = image
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Nicer callbacks
|
||||
|
||||
/**
|
||||
Executes a specific callback and fires the completion callback,
|
||||
while updating the UI as required. As long as the completion callback
|
||||
does not fire, the app is presumed to be busy and the UI reflects this.
|
||||
|
||||
- Parameter execute: Escaping callback of the work that needs to happen.
|
||||
- Parameter completion: Callback that is fired when the work is done.
|
||||
*/
|
||||
private func waitAndExecute(_ execute: @escaping () -> Void, _ completion: @escaping () -> Void = {})
|
||||
{
|
||||
App.shared.busy = true
|
||||
self.setBusyImage()
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
self.update()
|
||||
execute()
|
||||
App.shared.busy = false
|
||||
DispatchQueue.main.async {
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.update()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - User Interface
|
||||
|
||||
@objc func updatePhpVersionInStatusBar() {
|
||||
App.shared.currentVersion = PhpVersion()
|
||||
if (App.shared.busy) {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBarImage(version: App.shared.currentVersion!.short)
|
||||
}
|
||||
}
|
||||
self.update()
|
||||
}
|
||||
|
||||
@objc func setBusyImage() {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc public func restartPhpFpm() {
|
||||
self.waitAndExecute({
|
||||
Actions.restartPhpFpm()
|
||||
})
|
||||
}
|
||||
|
||||
@objc public func restartNginx() {
|
||||
self.waitAndExecute({
|
||||
Actions.restartNginx()
|
||||
})
|
||||
}
|
||||
|
||||
@objc public func toggleXdebug() {
|
||||
self.waitAndExecute({
|
||||
Actions.toggleXdebug()
|
||||
})
|
||||
}
|
||||
|
||||
@objc public func forceRestartLatestPhp() {
|
||||
// Tell the user the switch is about to occur
|
||||
_ = Alert.present(
|
||||
messageText: "alert.force_reload.title".localized,
|
||||
informativeText: "alert.force_reload.info".localized
|
||||
)
|
||||
// Start switching
|
||||
self.waitAndExecute({ Actions.fixMyPhp() }, {
|
||||
_ = Alert.present(
|
||||
messageText: "alert.force_reload_done.title".localized,
|
||||
informativeText: "alert.force_reload_done.info".localized
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@objc public func openActiveConfigFolder() {
|
||||
if (App.shared.currentVersion!.error) {
|
||||
// php version was not identified
|
||||
Actions.openGenericPhpConfigFolder()
|
||||
} else {
|
||||
// php version was identified
|
||||
Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc public func openValetConfigFolder() {
|
||||
Actions.openValetConfigFolder()
|
||||
}
|
||||
|
||||
@objc public func switchToPhpVersion(sender: AnyObject) {
|
||||
self.setBusyImage()
|
||||
// TODO: A wise man once said: using tags is not good. Fix this.
|
||||
let index = sender.tag!
|
||||
let version = App.shared.availablePhpVersions[index]
|
||||
App.shared.busy = true
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
// Update the PHP version in the status bar
|
||||
self.updatePhpVersionInStatusBar()
|
||||
// Update the menu
|
||||
self.update()
|
||||
// Switch the PHP version
|
||||
Actions.switchToPhpVersion(
|
||||
version: version,
|
||||
availableVersions: App.shared.availablePhpVersions
|
||||
)
|
||||
// Mark as no longer busy
|
||||
App.shared.busy = false
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async {
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.update()
|
||||
// Send a notification that the switch has been completed
|
||||
LocalNotification.send(
|
||||
title: String(format: "notification.version_changed_title".localized, version),
|
||||
subtitle: String(format: "notification.version_changed_desc".localized, version)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func openAbout() {
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
NSApplication.shared.orderFrontStandardAboutPanel()
|
||||
}
|
||||
|
||||
@objc public func terminateApp() {
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
}
|
53
phpmon/Singletons/Shell.swift
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// Shell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Shell {
|
||||
|
||||
/**
|
||||
Singleton to access a user shell (with --login)
|
||||
*/
|
||||
static let user = Shell()
|
||||
|
||||
/**
|
||||
Runs a shell command without using the output.
|
||||
Uses the default shell.
|
||||
|
||||
- Parameter command: The command to run
|
||||
*/
|
||||
public func run(_ command: String) {
|
||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
||||
_ = self.pipe(command)
|
||||
}
|
||||
|
||||
/**
|
||||
Runs a shell command and returns the output.
|
||||
|
||||
- Parameter command: The command to run
|
||||
- Parameter shell: Path to the shell to invoke
|
||||
*/
|
||||
public func pipe(_ command: String, shell: String = "/bin/sh") -> String {
|
||||
let task = Process()
|
||||
task.launchPath = shell
|
||||
task.arguments = ["--login", "-c", command]
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
|
||||
let output: String = NSString(
|
||||
data: data,
|
||||
encoding: String.Encoding.utf8.rawValue
|
||||
)! as String
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
//
|
||||
// Environment.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 12/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class BootChecks {
|
||||
|
||||
public static func perform()
|
||||
{
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.execute(command: "which php").contains("/usr/local/bin/php"),
|
||||
messageText: "PHP is not correctly installed",
|
||||
informativeText: "You must install PHP via brew. Try running `which php` in Terminal, it should return `/usr/local/bin/php`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.execute(command: "ls /usr/local/opt | grep php@7.3").contains("php@7.3"),
|
||||
messageText: "PHP 7.3 is not correctly installed",
|
||||
informativeText: "PHP 7.3 alias was not found in `/usr/local/opt`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.execute(command: "which valet").contains("/usr/local/bin/valet"),
|
||||
messageText: "Laravel Valet is not correctly installed",
|
||||
informativeText: "You must install Valet via brew. Try running `which valet` in Terminal, it should return `/usr/local/bin/valet`. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.execute(command: "cat /private/etc/sudoers.d/brew").contains("/usr/local/bin/brew"),
|
||||
messageText: "Brew has not been added to sudoers.d",
|
||||
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
|
||||
self.presentAlertOnMainThreadIf(
|
||||
!Shell.execute(command: "cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet"),
|
||||
messageText: "Valet has not been added to sudoers.d",
|
||||
informativeText: "You must run `sudo valet trust` to ensure Valet can start and stop services without having to use sudo every time. The app will not work correctly until you resolve this issue."
|
||||
)
|
||||
}
|
||||
|
||||
private static func presentAlertOnMainThreadIf(
|
||||
_ condition: Bool,
|
||||
messageText: String,
|
||||
informativeText: String
|
||||
)
|
||||
{
|
||||
if (condition) {
|
||||
DispatchQueue.main.async {
|
||||
Alert.present(
|
||||
messageText: messageText,
|
||||
informativeText: informativeText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
//
|
||||
// Services.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
class Services {
|
||||
public static func mysqlIsRunning() -> Bool {
|
||||
let running = Shell.execute(command: "launchctl list | grep homebrew.mxcl.mysql")
|
||||
return (running != "")
|
||||
}
|
||||
|
||||
public static func nginxIsRunning() -> Bool {
|
||||
let running = Shell.execute(command: "launchctl list | grep homebrew.mxcl.nginx")
|
||||
return (running != "")
|
||||
}
|
||||
|
||||
public static func detectPhpVersions() -> [String] {
|
||||
let files = Shell.execute(command: "ls /usr/local/opt | grep php@")
|
||||
var versions = files.components(separatedBy: "\n")
|
||||
// Remove all empty strings
|
||||
versions.removeAll { (string) -> Bool in
|
||||
return (string == "")
|
||||
}
|
||||
// Get a list of versions only
|
||||
var versionsOnly : [String] = []
|
||||
versions.forEach { (string) in
|
||||
versionsOnly.append(string.components(separatedBy: "php@")[1])
|
||||
}
|
||||
return versionsOnly
|
||||
}
|
||||
|
||||
public static func switchToPhpVersion(version: String, availableVersions: [String]) {
|
||||
availableVersions.forEach { (version) in
|
||||
_ = Shell.execute(command: "brew unlink php@\(version)")
|
||||
}
|
||||
if (availableVersions.contains("7.3")) {
|
||||
_ = Shell.execute(command: "brew link php@7.3")
|
||||
_ = Shell.execute(command: "valet use php@\(version)")
|
||||
}
|
||||
}
|
||||
|
||||
public static func restartPhp(version: String) {
|
||||
_ = Shell.execute(command: "brew services restart php@\(version)")
|
||||
}
|
||||
|
||||
public static func openPhpConfigFolder(version: String) {
|
||||
let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")];
|
||||
NSWorkspace.shared.activateFileViewerSelecting(files as [URL]);
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
//
|
||||
// Shell.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Shell {
|
||||
|
||||
public static func execute(command: String) -> String {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/bash"
|
||||
task.arguments = ["--login", "-c", command]
|
||||
|
||||
let pipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.launch()
|
||||
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
|
||||
let output: String = NSString(
|
||||
data: data,
|
||||
encoding: String.Encoding.utf8.rawValue
|
||||
)! as String
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -11,18 +12,18 @@
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="phpmon" id="1Xt-HY-uBw">
|
||||
<menuItem title="PHP Monitor" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="phpmon" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<menu key="submenu" title="PHP Monitor" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About phpmon" id="5kV-Vb-QxS">
|
||||
<menuItem title="About PHP Monitor" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Quit phpmon" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<menuItem title="Quit PHP Monitor" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
@ -34,7 +35,7 @@
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="phpmon Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<menuItem title="PHP Monitor Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
@ -48,24 +49,74 @@
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="phpmon" customModuleProvider="target"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="PHP_Monitor" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="0.0"/>
|
||||
<point key="canvasLocation" x="-343" y="-16"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<!--Log View Controller-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
<objects>
|
||||
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="phpmon" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="logWindow" id="XfG-lQ-9wD" customClass="LogViewController" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" identifier="main" id="m2S-Jp-Qdl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="425" height="264"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="662" height="475"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ICa-gx-jgq">
|
||||
<rect key="frame" x="578" y="8" width="75" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Close" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="3md-FI-EWa">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="pressed:" target="XfG-lQ-9wD" id="fIC-Bz-vTK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vZy-5S-021">
|
||||
<rect key="frame" x="15" y="46" width="632" height="414"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="s5L-AU-0fw">
|
||||
<rect key="frame" x="1" y="1" width="630" height="412"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" smartInsertDelete="YES" id="tN6-Y9-1pA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="630" height="412"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="630" height="412"/>
|
||||
<size key="maxSize" width="640" height="10000000"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Kho-JF-NZJ">
|
||||
<rect key="frame" x="-100" y="-100" width="240" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="qp7-7R-gTO">
|
||||
<rect key="frame" x="615" y="1" width="16" height="412"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="vZy-5S-021" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" constant="15" id="K0k-oE-r37"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ICa-gx-jgq" secondAttribute="trailing" constant="15" id="LFS-0E-Ibw"/>
|
||||
<constraint firstItem="vZy-5S-021" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" constant="15" id="Nec-oI-CjE"/>
|
||||
<constraint firstAttribute="trailing" secondItem="vZy-5S-021" secondAttribute="trailing" constant="15" id="kBJ-O5-eYI"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ICa-gx-jgq" secondAttribute="bottom" constant="15" id="kYB-Fn-DSA"/>
|
||||
<constraint firstItem="ICa-gx-jgq" firstAttribute="top" secondItem="vZy-5S-021" secondAttribute="bottom" constant="10" id="xdn-yU-LVb"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="textView" destination="tN6-Y9-1pA" id="z77-me-Od6"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="74.5" y="217"/>
|
||||
<point key="canvasLocation" x="-105" y="377.5"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
@ -1,13 +0,0 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// phpmon
|
||||
//
|
||||
// Created by Nico Verbruggen on 11/06/2019.
|
||||
// Copyright © 2019 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class ViewController: NSViewController {
|
||||
|
||||
}
|