Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
acb18474c8 | |||
ed61490398 | |||
abb76273c9 | |||
2f15af4ff8 | |||
e29e8416d5 | |||
5d423210dd | |||
340c36fdf8 | |||
3085158b80 | |||
c2585f9bf4 | |||
d478137742 | |||
827bd182b1 | |||
f7500637fe | |||
7c884610b1 | |||
d3d219751e | |||
16d2e7d06f | |||
47b86ff9fa | |||
6e574b9154 | |||
485001403d | |||
694c5e7f7d | |||
d9ff26385a | |||
0cfb7c65bb | |||
3cbc2a0367 | |||
7733c90206 | |||
0ad6e5cb1c |
@@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
|
||||
5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
|
||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; };
|
||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; };
|
||||
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; };
|
||||
@@ -18,6 +20,7 @@
|
||||
C41C1B4B22B019FF00E7CF16 /* PhpInstallation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */; };
|
||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; };
|
||||
C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; };
|
||||
C4232EE52612526500158FC6 /* Credits.html in Resources */ = {isa = PBXBuildFile; fileRef = C4232EE42612526500158FC6 /* Credits.html */; };
|
||||
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
|
||||
C43A8A2025D9D1D700591B77 /* brew.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew.json */; };
|
||||
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */; };
|
||||
@@ -28,6 +31,8 @@
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; };
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
|
||||
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; };
|
||||
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
|
||||
C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C8F25CC7FD000CC7490 /* StatsView.xib */; };
|
||||
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
|
||||
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9525CC80B100CC7490 /* HeaderView.swift */; };
|
||||
@@ -62,6 +67,7 @@
|
||||
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; };
|
||||
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C474B00524C0E98C00066A22 /* LocalNotification.swift */; };
|
||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8C0A322D4F12C002EFE61 /* DateExtension.swift */; };
|
||||
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -75,6 +81,8 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = "<group>"; };
|
||||
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = "<group>"; };
|
||||
@@ -89,6 +97,7 @@
|
||||
C41C1B4A22B019FF00E7CF16 /* PhpInstallation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpInstallation.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>"; };
|
||||
C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
|
||||
C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
|
||||
C43A8A1F25D9D1D700591B77 /* brew.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = brew.json; sourceTree = "<group>"; };
|
||||
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewJsonParserTest.swift; sourceTree = "<group>"; };
|
||||
@@ -118,6 +127,7 @@
|
||||
C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionParserTest.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>"; };
|
||||
C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionDetectionTest.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -138,6 +148,15 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
5420395726135DB800FB00FA /* Preferences */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5420395826135DC100FB00FA /* PrefsVC.swift */,
|
||||
5420395E2613607600FB00FA /* Preferences.swift */,
|
||||
);
|
||||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C405A4CD24B9B9070062FAFA /* IAP */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -176,6 +195,7 @@
|
||||
C4EE188322D3386B00E126E5 /* Constants.swift */,
|
||||
C41E181722CB61EB0072CF09 /* Domain */,
|
||||
C41C1B3F22B0098000E7CF16 /* Info.plist */,
|
||||
C4232EE42612526500158FC6 /* Credits.html */,
|
||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
|
||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */,
|
||||
C473319E2470923A009A0597 /* Localizable.strings */,
|
||||
@@ -187,6 +207,7 @@
|
||||
C41E181722CB61EB0072CF09 /* Domain */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5420395726135DB800FB00FA /* Preferences */,
|
||||
C4F7808A25D7F918000DBC97 /* Terminal */,
|
||||
C4B13B1D25C4915000548C3A /* Core */,
|
||||
C47331A0247093AC009A0597 /* Menu */,
|
||||
@@ -240,6 +261,7 @@
|
||||
C4F7809B25D80344000DBC97 /* CommandTest.swift */,
|
||||
C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */,
|
||||
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */,
|
||||
C4FBFC512616485F00CDB8E1 /* PhpVersionDetectionTest.swift */,
|
||||
C43A8A1925D9CD1000591B77 /* Utility.swift */,
|
||||
);
|
||||
path = "phpmon-tests";
|
||||
@@ -352,6 +374,7 @@
|
||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */,
|
||||
C48D0C9025CC7FD000CC7490 /* StatsView.xib in Resources */,
|
||||
C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */,
|
||||
C4232EE52612526500158FC6 /* Credits.html in Resources */,
|
||||
C473319F2470923A009A0597 /* Localizable.strings in Resources */,
|
||||
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
|
||||
C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */,
|
||||
@@ -377,12 +400,14 @@
|
||||
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */,
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
|
||||
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
|
||||
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
|
||||
C41C1B4722B009A400E7CF16 /* Shell.swift in Sources */,
|
||||
C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */,
|
||||
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */,
|
||||
C42295DD2358D02000E263B2 /* Command.swift in Sources */,
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
|
||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
||||
5420395F2613607600FB00FA /* Preferences.swift in Sources */,
|
||||
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
|
||||
C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */,
|
||||
@@ -407,12 +432,14 @@
|
||||
C4F780CC25D80B75000DBC97 /* PhpInstallation.swift in Sources */,
|
||||
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
|
||||
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
|
||||
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
||||
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
|
||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
||||
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
|
||||
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
|
||||
C4F780C725D80B75000DBC97 /* StatusMenu.swift in Sources */,
|
||||
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
|
||||
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */,
|
||||
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
|
||||
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
|
||||
C4F780A225D804AA000DBC97 /* Paths.swift in Sources */,
|
||||
@@ -422,6 +449,7 @@
|
||||
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */,
|
||||
C4F780B725D80B5D000DBC97 /* App.swift in Sources */,
|
||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
|
||||
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */,
|
||||
C4F780CB25D80B75000DBC97 /* StatsView.swift in Sources */,
|
||||
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */,
|
||||
C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */,
|
||||
@@ -577,7 +605,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 54;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = phpmon/Info.plist;
|
||||
@@ -585,7 +613,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2;
|
||||
MARKETING_VERSION = 3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -601,7 +629,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 54;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = phpmon/Info.plist;
|
||||
@@ -609,7 +637,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2;
|
||||
MARKETING_VERSION = 3.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
20
README.md
@@ -1,20 +1,21 @@
|
||||
# PHP Monitor
|
||||
|
||||
> ⭐️ If this software has been useful to you, all I ask is that you **please star the repository**, so I know that the software is being used. You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy. Thank you! 😃
|
||||
> If this software has been useful to you, all I ask is that you **please star the repository**, so I know that the software is being used.
|
||||
> You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy.<br>**Thank you!** ⭐️
|
||||
|
||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
||||
|
||||
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.
|
||||
**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. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so you need to have it set up before you can use this.
|
||||
|
||||
<img src="./docs/screenshot.png" width="389px" alt="phpmon screenshot (menu bar app)"/>
|
||||
<img src="./docs/screenshot33.png" width="376px" alt="phpmon screenshot (menu bar app)"/>
|
||||
|
||||
<small><i>Screenshot: A menu showing all of the functionality of PHP Monitor.</i></small>
|
||||
|
||||
It's also super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
||||
It's super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
||||
|
||||
<img src="./docs/notification.png" width="370px" alt="phpmon screenshot (notification)"/>
|
||||
|
||||
It also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
|
||||
PHP Monitor also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
|
||||
|
||||
## 🖥 System requirements
|
||||
|
||||
@@ -160,6 +161,7 @@ For Zend extensions:
|
||||
* `; zend_extension="*.so"`
|
||||
|
||||
The `*` is a wildcard and the name of the extension. If you've commented out the extension, make sure you've commented it out with a semicolon (;) and a single space after the semicolon for PHP Monitor to detect it.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -187,13 +189,17 @@ You will need to manually clean up those folders yourself using `rm -rf` or by m
|
||||
|
||||
</details>
|
||||
|
||||
## 📝 Another issue?
|
||||
## 📝 Having another issue?
|
||||
|
||||
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 usually 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](https://paypal.me/nicoverbruggen).
|
||||
PHP Monitor is available entirely **free of charge**, but if you can afford it a donation helps keep the project alive and the app maintained.
|
||||
|
||||
You can find a [sponsor](https://nicoverbruggen.be/sponsor) link at the top of this repo or you could click the link here to be taken to my sponsorship page.
|
||||
|
||||
Donations really help with the Apple Developer Program cost, and keep me motivated to keep working on PHP Monitor outside of work hours (I do have a day job!).
|
||||
|
||||
## 🚜 How it works
|
||||
|
||||
|
Before Width: | Height: | Size: 127 KiB |
BIN
docs/screenshot33.png
Normal file
After Width: | Height: | Size: 212 KiB |
30
phpmon-tests/PhpVersionDetectionTest.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// PhpVersionDetectionTest.swift
|
||||
// phpmon-tests
|
||||
//
|
||||
// Created by Nico Verbruggen on 01/04/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class PhpVersionDetectionTest: XCTestCase {
|
||||
|
||||
func testCanDetectValidPhpVersions() throws {
|
||||
let outcome = Actions.extractPhpVersions(from: [
|
||||
"", // empty lines should be omitted
|
||||
"php@8.0",
|
||||
"php@8.0", // should only be detected once
|
||||
"meta-php@8.0", // should be omitted, invalid
|
||||
"php@8.0-coolio", // should be omitted, invalid
|
||||
"php@7.0",
|
||||
"",
|
||||
"unrelatedphp@1.0", // should be omitted, invalid
|
||||
"php@5.6",
|
||||
"php@5.4" // should be omitted, not supported
|
||||
], checkBinaries: false)
|
||||
|
||||
XCTAssertEqual(outcome, ["8.0", "7.0", "5.6"])
|
||||
}
|
||||
|
||||
}
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 558 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 148 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 780 B After Width: | Height: | Size: 500 B |
22
phpmon/Assets.xcassets/StatusBarIconStatic.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "phpmon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "phpmon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon.png
vendored
Normal file
After Width: | Height: | Size: 229 B |
BIN
phpmon/Assets.xcassets/StatusBarIconStatic.imageset/phpmon@2x.png
vendored
Normal file
After Width: | Height: | Size: 358 B |
22
phpmon/Credits.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
background-color: #FFF;
|
||||
color: #000;
|
||||
font-family: -apple-system;
|
||||
font-size: 11px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<p><b>Want to spread the love?</b> Leave a <a href="https://github.com/nicoverbruggen/phpmon">star on GitHub</a>!</p>
|
||||
<p><b>Having issues?</b> Consult the <a href="https://github.com/nicoverbruggen/phpmon#%EF%B8%8F-faq--troubleshooting">FAQ & Troubleshooting</a> section.</p>
|
||||
<p><b>Want to support me?</b> You can <a href="https://nicoverbruggen.be/sponsor">financially support</a> the continued development of this app.</p>
|
||||
<br>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -18,6 +18,14 @@ class App {
|
||||
return App.shared.busy
|
||||
}
|
||||
|
||||
/** The list of preferences that are currently active. */
|
||||
var preferences: [PreferenceName: Bool]!
|
||||
|
||||
/**
|
||||
The window controller of the currently active window.
|
||||
*/
|
||||
var windowController: NSWindowController? = nil
|
||||
|
||||
/**
|
||||
Whether the application is busy switching versions.
|
||||
*/
|
||||
@@ -43,7 +51,7 @@ class App {
|
||||
*/
|
||||
var brewPhpPackage: HomebrewPackage? = nil {
|
||||
didSet {
|
||||
self.brewPhpVersion = self.brewPhpPackage!.version
|
||||
brewPhpVersion = brewPhpPackage!.version
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
@@ -54,5 +55,89 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-343" y="-16"/>
|
||||
</scene>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="PQa-AT-b2a">
|
||||
<objects>
|
||||
<windowController storyboardIdentifier="preferencesWindow" showSeguePresentationStyle="single" id="hLJ-Fd-wRr" sceneMemberID="viewController">
|
||||
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="h4c-3b-nko">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="372" y="403" width="480" height="270"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2304" height="1271"/>
|
||||
<view key="contentView" id="2yL-50-11x">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="hLJ-Fd-wRr" id="6HE-8Y-aCO"/>
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="AW2-rV-rbS" kind="relationship" relationship="window.shadowedContentViewController" id="3dX-9V-eA0"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="OF0-qs-3Oh" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-374" y="327"/>
|
||||
</scene>
|
||||
<!--Preferences-->
|
||||
<scene sceneID="iyi-IS-7Ps">
|
||||
<objects>
|
||||
<viewController title="Preferences" storyboardIdentifier="preferences" showSeguePresentationStyle="single" id="AW2-rV-rbS" customClass="PrefsVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="Pf1-A5-3Xz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="462" height="139"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="GSr-K5-3yw">
|
||||
<rect key="frame" x="373" y="13" width="76" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="CLOSE" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ocw-Rx-gyh">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="pressed:" target="AW2-rV-rbS" id="8dA-y4-voq"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MEf-MN-oXt">
|
||||
<rect key="frame" x="18" y="102" width="424" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="DYN_ICON" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="m5s-qp-Iaj">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="toggledDynamicIcon:" target="AW2-rV-rbS" id="cuJ-mt-agf"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JrH-aa-AzL">
|
||||
<rect key="frame" x="18" y="81" width="426" height="14"/>
|
||||
<textFieldCell key="cell" title="DYN_ICON_DESC" id="MHA-Xt-xgF">
|
||||
<font key="font" metaFont="system" size="11"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="JrH-aa-AzL" secondAttribute="trailing" constant="20" symbolic="YES" id="8iM-Xf-ShU"/>
|
||||
<constraint firstAttribute="trailing" secondItem="GSr-K5-3yw" secondAttribute="trailing" constant="20" symbolic="YES" id="AT9-5F-6g1"/>
|
||||
<constraint firstItem="MEf-MN-oXt" firstAttribute="top" secondItem="Pf1-A5-3Xz" secondAttribute="top" constant="20" symbolic="YES" id="FJC-Lx-L8a"/>
|
||||
<constraint firstItem="MEf-MN-oXt" firstAttribute="leading" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="Imd-YJ-Ae7"/>
|
||||
<constraint firstItem="JrH-aa-AzL" firstAttribute="top" secondItem="MEf-MN-oXt" secondAttribute="bottom" constant="8" symbolic="YES" id="Vf8-fx-H50"/>
|
||||
<constraint firstAttribute="bottom" secondItem="GSr-K5-3yw" secondAttribute="bottom" constant="20" symbolic="YES" id="dAS-yW-vua"/>
|
||||
<constraint firstItem="JrH-aa-AzL" firstAttribute="leading" secondItem="MEf-MN-oXt" secondAttribute="leading" id="dzR-S7-M6U"/>
|
||||
<constraint firstItem="GSr-K5-3yw" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Pf1-A5-3Xz" secondAttribute="leading" constant="20" symbolic="YES" id="mTE-WD-54L"/>
|
||||
<constraint firstAttribute="trailing" secondItem="MEf-MN-oXt" secondAttribute="trailing" constant="20" symbolic="YES" id="pJg-zj-cBs"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="buttonClose" destination="GSr-K5-3yw" id="d4I-Cf-gXD"/>
|
||||
<outlet property="buttonDynamicIcon" destination="MEf-MN-oXt" id="qEN-Vg-EZS"/>
|
||||
<outlet property="labelDynamicIcon" destination="JrH-aa-AzL" id="CFc-fF-oPq"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="eQC-8B-FkX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="216" y="319"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
@@ -62,11 +62,11 @@ class PhpExtension {
|
||||
*/
|
||||
func toggle() {
|
||||
Actions.sed(
|
||||
file: self.file,
|
||||
original: self.line,
|
||||
replacement: self.enabled ? "; \(self.line)" : self.line.replacingOccurrences(of: "; ", with: "")
|
||||
file: file,
|
||||
original: line,
|
||||
replacement: enabled ? "; \(line)" : line.replacingOccurrences(of: "; ", with: "")
|
||||
)
|
||||
self.enabled = !self.enabled
|
||||
enabled = !enabled
|
||||
}
|
||||
|
||||
// MARK: - Static Methods
|
||||
|
@@ -16,28 +16,29 @@ class PhpInstallation {
|
||||
// MARK: - Computed
|
||||
|
||||
var formula: String {
|
||||
return (self.version.short == App.shared.brewPhpVersion) ? "php" : "php@\(self.version.short)"
|
||||
return (version.short == App.shared.brewPhpVersion) ? "php" : "php@\(version.short)"
|
||||
}
|
||||
|
||||
// MARK: - Initializer
|
||||
|
||||
init() {
|
||||
// Show information about the current version
|
||||
self.version = Self.getVersion()
|
||||
version = Self.getVersion()
|
||||
|
||||
// If an error occurred, exit early
|
||||
if (self.version.error) {
|
||||
self.configuration = Configuration()
|
||||
self.extensions = []
|
||||
if (version.error) {
|
||||
configuration = Configuration()
|
||||
extensions = []
|
||||
return
|
||||
}
|
||||
|
||||
// Load extension information
|
||||
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(self.version.short)/php.ini")
|
||||
self.extensions = PhpExtension.load(from: path)
|
||||
let path = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(version.short)/php.ini")
|
||||
|
||||
extensions = PhpExtension.load(from: path)
|
||||
|
||||
// Get configuration values
|
||||
self.configuration = Configuration(
|
||||
configuration = Configuration(
|
||||
memory_limit: Self.getByteCount(key: "memory_limit"),
|
||||
upload_max_filesize: Self.getByteCount(key: "upload_max_filesize"),
|
||||
post_max_size: Self.getByteCount(key: "post_max_size")
|
||||
|
@@ -25,11 +25,12 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
*/
|
||||
func startup() {
|
||||
// Start with the icon
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
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() }
|
||||
Startup().checkEnvironment(success: { onEnvironmentPass() },
|
||||
failure: { onEnvironmentFail() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -39,13 +40,14 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
*/
|
||||
private func onEnvironmentPass() {
|
||||
App.shared.availablePhpVersions = Actions.detectPhpVersions()
|
||||
self.updatePhpVersionInStatusBar()
|
||||
updatePhpVersionInStatusBar()
|
||||
|
||||
// Schedule a request to fetch the PHP version every 60 seconds
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async { [self] in
|
||||
App.shared.timer = Timer.scheduledTimer(
|
||||
timeInterval: 60,
|
||||
target: self,
|
||||
selector: #selector(self.updatePhpVersionInStatusBar),
|
||||
selector: #selector(updatePhpVersionInStatusBar),
|
||||
userInfo: nil,
|
||||
repeats: true
|
||||
)
|
||||
@@ -56,7 +58,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
When the environment is not OK, present an alert to inform the user.
|
||||
*/
|
||||
private func onEnvironmentFail() {
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async { [self] in
|
||||
let close = Alert.present(
|
||||
messageText: "alert.cannot_start.title".localized,
|
||||
informativeText: "alert.cannot_start.info".localized,
|
||||
@@ -68,7 +70,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
self.startup()
|
||||
startup()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +79,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
*/
|
||||
func update() {
|
||||
// Update the menu item on the main thread
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async { [self] in
|
||||
// Create a new menu
|
||||
let menu = StatusMenu()
|
||||
|
||||
@@ -94,15 +96,16 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
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"))
|
||||
menu.addItem(NSMenuItem(title: "mi_preferences".localized, action: #selector(openPrefs), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "mi_about".localized, action: #selector(openAbout), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "mi_quit".localized, action: #selector(terminateApp), keyEquivalent: "q"))
|
||||
|
||||
// Make sure every item can be interacted with
|
||||
menu.items.forEach({ (item) in
|
||||
item.target = self
|
||||
})
|
||||
|
||||
self.statusItem.menu = menu
|
||||
statusItem.menu = menu
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +113,7 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
Sets the status bar image based on a version string.
|
||||
*/
|
||||
func setStatusBarImage(version: String) {
|
||||
self.setStatusBar(
|
||||
setStatusBar(
|
||||
image: MenuBarImageGenerator.textToImage(text: version)
|
||||
)
|
||||
}
|
||||
@@ -136,17 +139,18 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
- Parameter execute: 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 = {})
|
||||
private func waitAndExecute(_ execute: @escaping () -> Void, completion: @escaping () -> Void = {})
|
||||
{
|
||||
App.shared.busy = true
|
||||
self.setBusyImage()
|
||||
setBusyImage()
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
self.update()
|
||||
update()
|
||||
execute()
|
||||
App.shared.busy = false
|
||||
DispatchQueue.main.async {
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.update()
|
||||
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePhpVersionInStatusBar()
|
||||
update()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
@@ -156,80 +160,102 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
|
||||
@objc func updatePhpVersionInStatusBar() {
|
||||
App.shared.currentInstall = PhpInstallation()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if (App.shared.busy) {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
refreshIcon()
|
||||
update()
|
||||
}
|
||||
|
||||
func refreshIcon() {
|
||||
DispatchQueue.main.async { [self] in
|
||||
if (App.busy) {
|
||||
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
} else {
|
||||
self.setStatusBarImage(version: App.phpInstall!.version.short)
|
||||
if Preferences.preferences[.shouldDisplayDynamicIcon] == false {
|
||||
// Static icon has been requested
|
||||
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIconStatic"))!)
|
||||
} else {
|
||||
// The dynamic icon has been requested
|
||||
setStatusBarImage(version: App.phpInstall!.version.short)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func reloadPhpMonitorMenu() {
|
||||
waitAndExecute {
|
||||
// This automatically reloads the menu
|
||||
print("Reloading information about the PHP installation...")
|
||||
} completion: {
|
||||
// Add a slight delay to make sure it loads the new menu
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
// Open the menu again
|
||||
MainMenu.shared.statusItem.button?.performClick(nil)
|
||||
}
|
||||
}
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
@objc func setBusyImage() {
|
||||
DispatchQueue.main.async {
|
||||
self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
DispatchQueue.main.async { [self] in
|
||||
setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func restartPhpFpm() {
|
||||
self.waitAndExecute({
|
||||
waitAndExecute {
|
||||
Actions.restartPhpFpm()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartAllServices() {
|
||||
self.waitAndExecute({
|
||||
waitAndExecute {
|
||||
Actions.restartDnsMasq()
|
||||
Actions.restartPhpFpm()
|
||||
Actions.restartNginx()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartNginx() {
|
||||
self.waitAndExecute({
|
||||
waitAndExecute {
|
||||
Actions.restartNginx()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func restartDnsMasq() {
|
||||
self.waitAndExecute({
|
||||
waitAndExecute {
|
||||
Actions.restartDnsMasq()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggleExtension(sender: ExtensionMenuItem) {
|
||||
self.waitAndExecute({
|
||||
// Toggle that extension
|
||||
print("Toggling extension '\(sender.phpExtension!.name)'")
|
||||
waitAndExecute {
|
||||
sender.phpExtension?.toggle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func openPhpInfo() {
|
||||
self.waitAndExecute({
|
||||
waitAndExecute {
|
||||
// Write a file called `phpmon_phpinfo.php` to /tmp
|
||||
try! "<?php phpinfo();".write(toFile: "/tmp/phpmon_phpinfo.php", atomically: true, encoding: .utf8)
|
||||
|
||||
// Tell php-cgi to run the PHP and output as an .html file
|
||||
Shell.run("\(Paths.binPath)/php-cgi -q /tmp/phpmon_phpinfo.php > /tmp/phpmon_phpinfo.html")
|
||||
}, {
|
||||
} completion: {
|
||||
// When this has been completed, open the URL to the file in the browser
|
||||
NSWorkspace.shared.open(URL(string: "file:///private/tmp/phpmon_phpinfo.html")!)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func forceRestartLatestPhp() {
|
||||
// Tell the user the switch is about to occur
|
||||
Alert.notify(message: "alert.force_reload.title".localized, info: "alert.force_reload.info".localized)
|
||||
|
||||
// Start switching
|
||||
self.waitAndExecute(
|
||||
{ Actions.fixMyPhp() },
|
||||
{ Alert.notify(
|
||||
message: "alert.force_reload_done.title".localized,
|
||||
info: "alert.force_reload_done.info".localized
|
||||
) }
|
||||
)
|
||||
waitAndExecute {
|
||||
Actions.fixMyPhp()
|
||||
} completion: {
|
||||
Alert.notify(message: "alert.force_reload_done.title".localized, info: "alert.force_reload_done.info".localized)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func openActiveConfigFolder() {
|
||||
@@ -248,17 +274,17 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
}
|
||||
|
||||
@objc func switchToPhpVersion(sender: PhpMenuItem) {
|
||||
print("Switching to: PHP \(sender.version)")
|
||||
// print("Switching to: PHP \(sender.version)")
|
||||
|
||||
self.setBusyImage()
|
||||
setBusyImage()
|
||||
App.shared.busy = true
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
// Update the PHP version in the status bar
|
||||
self.updatePhpVersionInStatusBar()
|
||||
updatePhpVersionInStatusBar()
|
||||
|
||||
// Update the menu
|
||||
self.update()
|
||||
update()
|
||||
|
||||
// Switch the PHP version
|
||||
Actions.switchToPhpVersion(
|
||||
@@ -270,9 +296,10 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
App.shared.busy = false
|
||||
|
||||
// Perform UI updates on main thread
|
||||
DispatchQueue.main.async {
|
||||
self.updatePhpVersionInStatusBar()
|
||||
self.update()
|
||||
DispatchQueue.main.async { [self] in
|
||||
updatePhpVersionInStatusBar()
|
||||
update()
|
||||
|
||||
// Send a notification that the switch has been completed
|
||||
LocalNotification.send(
|
||||
title: String(format: "notification.version_changed_title".localized, sender.version),
|
||||
@@ -287,6 +314,10 @@ class MainMenu: NSObject, NSWindowDelegate {
|
||||
NSApplication.shared.orderFrontStandardAboutPanel()
|
||||
}
|
||||
|
||||
@objc func openPrefs() {
|
||||
PrefsVC.show()
|
||||
}
|
||||
|
||||
@objc func terminateApp() {
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
|
@@ -15,18 +15,18 @@ class StatusMenu : NSMenu {
|
||||
|
||||
if App.phpInstall!.version.error {
|
||||
for message in ["mi_php_broken_1", "mi_php_broken_2", "mi_php_broken_3", "mi_php_broken_4"] {
|
||||
self.addItem(NSMenuItem(title: message.localized, action: nil, keyEquivalent: ""))
|
||||
addItem(NSMenuItem(title: message.localized, action: nil, keyEquivalent: ""))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let phpVersionText = "\("mi_php_version".localized) \(App.phpInstall!.version.long)"
|
||||
self.addItem(HeaderView.asMenuItem(text: phpVersionText))
|
||||
addItem(HeaderView.asMenuItem(text: phpVersionText))
|
||||
}
|
||||
|
||||
func addPhpActionMenuItems() {
|
||||
if App.busy {
|
||||
self.addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
|
||||
addItem(NSMenuItem(title: "mi_busy".localized, action: nil, keyEquivalent: ""))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,6 +109,10 @@ class StatusMenu : NSMenu {
|
||||
for phpExtension in App.phpInstall!.extensions {
|
||||
self.addExtensionItem(phpExtension)
|
||||
}
|
||||
|
||||
self.addItem(NSMenuItem.separator())
|
||||
|
||||
self.addItem(NSMenuItem(title: "mi_php_refresh".localized, action: #selector(MainMenu.reloadPhpMonitorMenu), keyEquivalent: "r"))
|
||||
}
|
||||
|
||||
private func addExtensionItem(_ phpExtension: PhpExtension) {
|
||||
@@ -118,6 +122,7 @@ class StatusMenu : NSMenu {
|
||||
)
|
||||
menuItem.state = phpExtension.enabled ? .on : .off
|
||||
menuItem.phpExtension = phpExtension
|
||||
|
||||
self.addItem(menuItem)
|
||||
}
|
||||
}
|
||||
|
50
phpmon/Domain/Preferences/Preferences.swift
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Preferences.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 30/03/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum PreferenceName: String {
|
||||
case shouldDisplayDynamicIcon = "use_dynamic_icon"
|
||||
}
|
||||
|
||||
class Preferences {
|
||||
|
||||
static func handleFirstTimeLaunch() {
|
||||
let launchedBefore = UserDefaults.standard.bool(forKey: "launched_before")
|
||||
|
||||
if launchedBefore {
|
||||
return
|
||||
}
|
||||
|
||||
UserDefaults.standard.setValue(true, forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue)
|
||||
UserDefaults.standard.setValue(true, forKey: "launched_before")
|
||||
UserDefaults.standard.synchronize()
|
||||
|
||||
print("Saving first-time preferences!")
|
||||
}
|
||||
|
||||
static func retrieve() -> [PreferenceName: Bool] {
|
||||
Preferences.handleFirstTimeLaunch()
|
||||
|
||||
return [
|
||||
.shouldDisplayDynamicIcon: UserDefaults.standard.bool(
|
||||
forKey: PreferenceName.shouldDisplayDynamicIcon.rawValue
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
static var preferences: [PreferenceName: Bool] {
|
||||
return Preferences.retrieve()
|
||||
}
|
||||
|
||||
static func update(_ preference: PreferenceName, value: Bool) {
|
||||
UserDefaults.standard.setValue(value, forKey: preference.rawValue)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
}
|
51
phpmon/Domain/Preferences/PrefsVC.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// PrefsVC.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 30/03/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class PrefsVC: NSViewController {
|
||||
|
||||
@IBOutlet weak var buttonDynamicIcon: NSButton!
|
||||
@IBOutlet weak var labelDynamicIcon: NSTextField!
|
||||
@IBOutlet weak var buttonClose: NSButton!
|
||||
|
||||
public static func show(delegate: NSWindowDelegate? = nil) {
|
||||
if (App.shared.windowController == nil) {
|
||||
let vc = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "preferences") as! PrefsVC
|
||||
let window = NSWindow(contentViewController: vc)
|
||||
window.title = "prefs.title".localized
|
||||
window.delegate = delegate
|
||||
window.styleMask = [.titled, .closable]
|
||||
App.shared.windowController = NSWindowController(window: window)
|
||||
}
|
||||
App.shared.windowController!.showWindow(self)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@IBAction func toggledDynamicIcon(_ sender: Any) {
|
||||
Preferences.update(.shouldDisplayDynamicIcon, value: buttonDynamicIcon.state == .on)
|
||||
MainMenu.shared.refreshIcon()
|
||||
}
|
||||
|
||||
override func viewWillAppear() {
|
||||
buttonDynamicIcon.title = "prefs.dynamic_icon_title".localized
|
||||
labelDynamicIcon.stringValue = "prefs.dynamic_icon_desc".localized
|
||||
buttonClose.title = "prefs.close".localized
|
||||
|
||||
let prefs = Preferences.preferences
|
||||
self.buttonDynamicIcon.state = (prefs[.shouldDisplayDynamicIcon] == true) ? .on : .off
|
||||
}
|
||||
|
||||
@IBAction func pressed(_ sender: Any) {
|
||||
self.view.window?.windowController?.close()
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("VC deallocated")
|
||||
}
|
||||
}
|
@@ -15,31 +15,52 @@ class Actions {
|
||||
public static func detectPhpVersions() -> [String]
|
||||
{
|
||||
let files = Shell.pipe("ls \(Paths.optPath) | 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])
|
||||
}
|
||||
var versionsOnly = Self.extractPhpVersions(from: files.components(separatedBy: "\n"))
|
||||
|
||||
// Make sure the aliased version is detected
|
||||
// The user may have `php` installed, but not e.g. `php@8.0`
|
||||
// We should also detect that as a version that is installed
|
||||
let phpAlias = App.shared.brewPhpVersion
|
||||
|
||||
// Avoid inserting a duplicate
|
||||
if (!versionsOnly.contains(phpAlias)) {
|
||||
versionsOnly.append(phpAlias);
|
||||
}
|
||||
|
||||
print("The PHP versions that were detected are: \(versionsOnly)")
|
||||
|
||||
return versionsOnly
|
||||
}
|
||||
|
||||
/**
|
||||
Extracts valid PHP versions from an array of strings.
|
||||
This array of strings is usually retrieved from `grep`.
|
||||
*/
|
||||
public static func extractPhpVersions(
|
||||
from versions: [String],
|
||||
checkBinaries: Bool = true
|
||||
) -> [String] {
|
||||
var output : [String] = []
|
||||
|
||||
versions.filter { (version) -> Bool in
|
||||
// Omit everything that doesn't start with php@
|
||||
// (e.g. something-php@8.0 won't be detected)
|
||||
return version.starts(with: "php@")
|
||||
}.forEach { (string) in
|
||||
let version = string.components(separatedBy: "php@")[1]
|
||||
// Only append the version if it doesn't already exist (avoid dupes),
|
||||
// is supported and where the binary exists (avoids broken installs)
|
||||
if !output.contains(version)
|
||||
&& Constants.SupportedPhpVersions.contains(version)
|
||||
&& (checkBinaries ? Shell.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true)
|
||||
{
|
||||
output.append(version)
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// MARK: - Services
|
||||
|
||||
public static func restartPhpFpm()
|
||||
@@ -75,6 +96,7 @@ class Actions {
|
||||
}
|
||||
|
||||
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
|
||||
brew("link \(formula) --overwrite --force")
|
||||
brew("services start \(formula)", sudo: true)
|
||||
}
|
||||
@@ -110,7 +132,7 @@ class Actions {
|
||||
{
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
|
||||
self.detectPhpVersions().forEach { (version) in
|
||||
detectPhpVersions().forEach { (version) in
|
||||
let formula = (version == App.shared.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
brew("unlink php@\(version)")
|
||||
brew("services stop \(formula)")
|
||||
|
@@ -23,41 +23,41 @@ class Paths {
|
||||
|
||||
if (optBrewFound) {
|
||||
// This is usually the case with Homebrew installed on Apple Silicon
|
||||
self.baseDir = .opt
|
||||
baseDir = .opt
|
||||
} else if (usrBrewFound) {
|
||||
// This is usually the case with Homebrew installed on Intel (or Rosetta 2)
|
||||
self.baseDir = .usr
|
||||
baseDir = .usr
|
||||
} else {
|
||||
// Falling back to default "legacy" Homebrew location (for Intel)
|
||||
print("Seems like we couldn't determine the Homebrew directory.")
|
||||
print("This usually means we're in trouble... (no Homebrew?)")
|
||||
self.baseDir = .usr
|
||||
baseDir = .usr
|
||||
}
|
||||
|
||||
print("Homebrew directory: \(self.baseDir)")
|
||||
print("Homebrew directory: \(baseDir)")
|
||||
}
|
||||
|
||||
// - MARK: Binaries
|
||||
|
||||
public static var brew: String {
|
||||
return "\(self.binPath)/brew"
|
||||
return "\(binPath)/brew"
|
||||
}
|
||||
|
||||
public static var php: String {
|
||||
return "\(self.binPath)/php"
|
||||
return "\(binPath)/php"
|
||||
}
|
||||
|
||||
// - MARK: Paths
|
||||
|
||||
public static var binPath: String {
|
||||
return "\(self.shared.baseDir.rawValue)/bin"
|
||||
return "\(shared.baseDir.rawValue)/bin"
|
||||
}
|
||||
|
||||
public static var optPath: String {
|
||||
return "\(self.shared.baseDir.rawValue)/opt"
|
||||
return "\(shared.baseDir.rawValue)/opt"
|
||||
}
|
||||
|
||||
public static var etcPath: String {
|
||||
return "\(self.shared.baseDir.rawValue)/etc"
|
||||
return "\(shared.baseDir.rawValue)/etc"
|
||||
}
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ class Shell {
|
||||
.init(majorVersion: 10, minorVersion: 15, patchVersion: 0))
|
||||
|
||||
// If macOS Mojave is being used, we'll default to /bin/bash
|
||||
self.shell = at_least_10_15 ? "/bin/sh" : "/bin/bash"
|
||||
shell = at_least_10_15 ? "/bin/sh" : "/bin/bash"
|
||||
print(at_least_10_15 ? "Detected recent macOS (> 10.15): defaulting to /bin/sh"
|
||||
: "Detected older macOS (< 10.15): so defaulting to /bin/bash")
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class Shell {
|
||||
*/
|
||||
func run(_ command: String) {
|
||||
// Equivalent of piping to /dev/null; don't do anything with the string
|
||||
_ = self.pipe(command)
|
||||
_ = pipe(command)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -21,56 +21,61 @@ class Startup {
|
||||
*/
|
||||
func checkEnvironment(success: () -> Void, failure: @escaping () -> Void)
|
||||
{
|
||||
self.failureCallback = failure
|
||||
failureCallback = failure
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
performEnvironmentCheck(
|
||||
!Shell.fileExists("\(Paths.binPath)/php"),
|
||||
messageText: "startup.errors.php_binary.title".localized,
|
||||
informativeText: "startup.errors.php_binary_desc".localized,
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
performEnvironmentCheck(
|
||||
!Shell.pipe("ls \(Paths.optPath) | grep php").contains("php"),
|
||||
messageText: "startup.errors.php_opt.title".localized,
|
||||
informativeText: "startup.errors.php_opt.desc".localized,
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
// Older versions of Valet might be located in `/usr/local/bin` regardless of Homebrew prefix
|
||||
!(Shell.fileExists("/usr/local/bin/valet") || Shell.fileExists("/opt/homebrew/bin/valet")),
|
||||
performEnvironmentCheck(
|
||||
// Check for Valet; it can be symlinked or in .composer/vendor/bin
|
||||
!(Shell.fileExists("/usr/local/bin/valet")
|
||||
|| Shell.fileExists("/opt/homebrew/bin/valet")
|
||||
|| Shell.fileExists("~/.composer/vendor/bin/valet")
|
||||
),
|
||||
messageText: "startup.errors.valet_executable.title".localized,
|
||||
informativeText: "startup.errors.valet_executable.desc".localized,
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
performEnvironmentCheck(
|
||||
!Shell.pipe("cat /private/etc/sudoers.d/brew").contains("\(Paths.binPath)/brew"),
|
||||
messageText: "startup.errors.sudoers_brew.title".localized,
|
||||
informativeText: "startup.errors.sudoers_brew.desc".localized,
|
||||
breaking: true
|
||||
)
|
||||
|
||||
self.performEnvironmentCheck(
|
||||
// Older versions of Valet might be located in `/usr/local/bin` regardless of Homebrew prefix
|
||||
performEnvironmentCheck(
|
||||
// Check for Valet; it can be symlinked or in .composer/vendor/bin
|
||||
!(Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/usr/local/bin/valet")
|
||||
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/opt/homebrew/bin/valet")),
|
||||
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains("/opt/homebrew/bin/valet")
|
||||
|| Shell.pipe("cat /private/etc/sudoers.d/valet").contains(".composer/vendor/bin/valet")
|
||||
),
|
||||
messageText: "startup.errors.sudoers_valet.title".localized,
|
||||
informativeText: "startup.errors.sudoers_valet.desc".localized,
|
||||
breaking: true
|
||||
)
|
||||
|
||||
let services = Shell.pipe("\(Paths.brew) services list | grep php")
|
||||
self.performEnvironmentCheck(
|
||||
performEnvironmentCheck(
|
||||
(services.countInstances(of: "started") > 1),
|
||||
messageText: "startup.errors.services.title".localized,
|
||||
informativeText: "startup.errors.services.desc".localized,
|
||||
breaking: false
|
||||
)
|
||||
|
||||
if (!self.failed) {
|
||||
self.determineBrewAliasVersion()
|
||||
if (!failed) {
|
||||
determineBrewAliasVersion()
|
||||
success()
|
||||
}
|
||||
}
|
||||
@@ -110,13 +115,13 @@ class Startup {
|
||||
) {
|
||||
if (!condition) { return }
|
||||
|
||||
self.failed = breaking
|
||||
failed = breaking
|
||||
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async { [self] in
|
||||
// Present the information to the user
|
||||
Alert.notify(message: messageText, info: informativeText)
|
||||
// Only breaking issues will throw the extra retry modal
|
||||
breaking ? self.failureCallback() : ()
|
||||
breaking ? failureCallback() : ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
"mi_restart_specific" = "Restart specific service";
|
||||
"mi_restart_all_services" = "Restart all services";
|
||||
"mi_force_load_latest" = "Force load latest PHP version";
|
||||
"mi_php_refresh" = "Refresh information";
|
||||
|
||||
"mi_configuration" = "Configuration";
|
||||
"mi_limits" = "Limits Configuration";
|
||||
@@ -38,9 +39,17 @@
|
||||
"mi_detected_extensions" = "Detected Extensions";
|
||||
"mi_no_extensions_detected" = "No additional extensions detected.";
|
||||
|
||||
"mi_preferences" = "Preferences...";
|
||||
"mi_quit" = "Quit PHP Monitor";
|
||||
"mi_about" = "About PHP Monitor";
|
||||
|
||||
// PREFERENCES
|
||||
|
||||
"prefs.title" = "Preferences";
|
||||
"prefs.close" = "Close";
|
||||
"prefs.dynamic_icon_title" = "Show a dynamic icon in the menu bar";
|
||||
"prefs.dynamic_icon_desc" = "If you uncheck this box, the truck icon will always be visible.\nIf checked, it will display the major version number of the currently linked PHP version.";
|
||||
|
||||
// NOTIFICATIONS
|
||||
|
||||
"notification.version_changed_title" = "PHP %@ now active";
|
||||
|