1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 03:50:08 +02:00

New Features

* Differentiate between services running as root and current user
* Support for custom services (via config.json)
* Renamed "Restart All Services" to "Restart Valet Services"
* Use SwiftUI for Stats, Services and Header view
* Added Color extension for debugging (PAINT_PHPMON_SWIFTUI_VIEWS)
This commit is contained in:
2022-06-11 23:18:27 +02:00
parent 090440abc8
commit 30059353fe
19 changed files with 252 additions and 167 deletions

View File

@ -152,8 +152,6 @@
C484437B2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; };
C484437C2804BB560041A78A /* ValetProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C484437A2804BB560041A78A /* ValetProxyScanner.swift */; };
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9525CC80B100CC7490 /* HeaderView.swift */; };
C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C48D0C9925CC888B00CC7490 /* HeaderView.xib */; };
C48D6C70279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */; };
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */; };
@ -180,7 +178,7 @@
C4B585422770FE3900DA4FBE /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853C2770FE3900DA4FBE /* Shell.swift */; };
C4B585442770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
C4B585452770FE3900DA4FBE /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B5853D2770FE3900DA4FBE /* Command.swift */; };
C4B6091A2853AAD300C95265 /* MiniHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* MiniHeaderView.swift */; };
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B609192853AAD300C95265 /* SectionHeaderView.swift */; };
C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B6091C2853AB9700C95265 /* ServicesView.swift */; };
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; };
C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */; };
@ -229,6 +227,8 @@
C4E0F7EE27BEBDA9007475F2 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */; };
C4E4404627C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; };
C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E4404527C56F4700D225E1 /* ValetSite.swift */; };
C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E428551F9B006F9937 /* HeaderView.swift */; };
C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB53E628553117006F9937 /* ArrayExtension.swift */; };
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EC1E72279DFCF40010F296 /* Events.swift */; };
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EED88827A48778006D7272 /* InterAppHandler.swift */; };
@ -255,7 +255,6 @@
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EE188322D3386B00E126E5 /* Constants.swift */; };
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9525CC80B100CC7490 /* HeaderView.swift */; };
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; };
C4F780C525D80B75000DBC97 /* MenuBarImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */; };
C4F780C625D80B75000DBC97 /* XibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C48D0C9225CC804200CC7490 /* XibLoadable.swift */; };
@ -378,8 +377,6 @@
C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
C484437A2804BB560041A78A /* ValetProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetProxyScanner.swift; sourceTree = "<group>"; };
C48D0C9225CC804200CC7490 /* XibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XibLoadable.swift; sourceTree = "<group>"; };
C48D0C9525CC80B100CC7490 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
C48D0C9925CC888B00CC7490 /* HeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HeaderView.xib; sourceTree = "<group>"; };
C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpVersionNumber.swift; sourceTree = "<group>"; };
C48D6C73279CD3E400F26D7E /* PhpVersionNumberTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpVersionNumberTest.swift; sourceTree = "<group>"; };
C4927F0A27B2DFC200C55AFD /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
@ -396,7 +393,7 @@
C4B5853B2770FE3900DA4FBE /* Paths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Paths.swift; sourceTree = "<group>"; };
C4B5853C2770FE3900DA4FBE /* Shell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
C4B5853D2770FE3900DA4FBE /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
C4B609192853AAD300C95265 /* MiniHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniHeaderView.swift; sourceTree = "<group>"; };
C4B609192853AAD300C95265 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = "<group>"; };
C4B6091C2853AB9700C95265 /* ServicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesView.swift; sourceTree = "<group>"; };
C4B97B74275CF08C003F3378 /* AppDelegate+MenuOutlets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+MenuOutlets.swift"; sourceTree = "<group>"; };
C4B97B77275CF1B5003F3378 /* App+ActivationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "App+ActivationPolicy.swift"; sourceTree = "<group>"; };
@ -425,6 +422,8 @@
C4E4404527C56F4700D225E1 /* ValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetSite.swift; sourceTree = "<group>"; };
C4E713562570150F00007428 /* SECURITY.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = SECURITY.md; sourceTree = "<group>"; };
C4E713572570151400007428 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
C4EB53E428551F9B006F9937 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
C4EB53E628553117006F9937 /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = "<group>"; };
C4EC1E72279DFCF40010F296 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = "<group>"; };
C4EE188322D3386B00E126E5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
C4EED88827A48778006D7272 /* InterAppHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterAppHandler.swift; sourceTree = "<group>"; };
@ -767,8 +766,6 @@
C4F361602836BFD9003598CC /* MainMenu+Actions.swift */,
C47331A1247093B7009A0597 /* StatusMenu.swift */,
C42800A928452AA10099C999 /* StatusMenu+Items.swift */,
C48D0C9525CC80B100CC7490 /* HeaderView.swift */,
C48D0C9925CC888B00CC7490 /* HeaderView.xib */,
);
path = Menu;
sourceTree = "<group>";
@ -873,7 +870,8 @@
children = (
C4B6091C2853AB9700C95265 /* ServicesView.swift */,
C4709CA128524B3400088BB8 /* StatsView.swift */,
C4B609192853AAD300C95265 /* MiniHeaderView.swift */,
C4B609192853AAD300C95265 /* SectionHeaderView.swift */,
C4EB53E428551F9B006F9937 /* HeaderView.swift */,
);
path = Menu;
sourceTree = "<group>";
@ -1049,6 +1047,7 @@
C48D0C9225CC804200CC7490 /* XibLoadable.swift */,
C42759662627662800093CAE /* NSMenuExtension.swift */,
C4E0F7EC27BEBDA9007475F2 /* NSWindowExtension.swift */,
C4EB53E628553117006F9937 /* ArrayExtension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -1152,7 +1151,6 @@
C4068CA427B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */,
54FCFD2D276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */,
C48D0C9A25CC888B00CC7490 /* HeaderView.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1215,6 +1213,7 @@
C4F8C0A422D4F12C002EFE61 /* DateExtension.swift in Sources */,
C4AF9F7A2754499000D44ED0 /* Valet.swift in Sources */,
C4C0E8EA27F88B80002D32A9 /* ValetProxy+Fake.swift in Sources */,
C4EB53E728553117006F9937 /* ArrayExtension.swift in Sources */,
5420395926135DC100FB00FA /* PrefsVC.swift in Sources */,
C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */,
@ -1234,13 +1233,14 @@
C4C0E8DF27F88AEB002D32A9 /* FakeSiteScanner.swift in Sources */,
C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */,
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */,
C4EB53E528551F9B006F9937 /* HeaderView.swift in Sources */,
C40FE737282ABA4F00A302C2 /* AppVersion.swift in Sources */,
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */,
C4B585442770FE3900DA4FBE /* Command.swift in Sources */,
C44067F527E2582B0045BD4E /* DomainListNameCell.swift in Sources */,
C40C5C9C2846A40600E28255 /* Preset.swift in Sources */,
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
C4B6091A2853AAD300C95265 /* MiniHeaderView.swift in Sources */,
C4B6091A2853AAD300C95265 /* SectionHeaderView.swift in Sources */,
C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */,
C42800AA28452AA10099C999 /* StatusMenu+Items.swift in Sources */,
C415D3B72770F294005EF286 /* Actions.swift in Sources */,
@ -1297,7 +1297,6 @@
C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */,
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */,
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
@ -1410,7 +1409,6 @@
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
C4F2E43B27530F750020E974 /* PhpInstallation.swift in Sources */,
C4F780BD25D80B65000DBC97 /* Constants.swift in Sources */,
C4F780C325D80B75000DBC97 /* HeaderView.swift in Sources */,
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */,
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
@ -1599,7 +1597,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 900;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
@ -1609,7 +1607,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = "5.4-dev";
MARKETING_VERSION = "5.5-dev";
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1626,7 +1624,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 900;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
ENABLE_HARDENED_RUNTIME = YES;
@ -1636,7 +1634,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = "5.4-dev";
MARKETING_VERSION = "5.5-dev";
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -69,8 +69,8 @@
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "PHPMON_MARKETING_MODE"
value = "YES"
key = "PAINT_PHPMON_SWIFTUI_VIEWS"
value = ""
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>

View File

@ -24,7 +24,7 @@ class Actions {
brew("services restart dnsmasq", sudo: true)
}
public static func stopAllServices() {
public static func stopValetServices() {
brew("services stop \(PhpEnv.phpInstall.formula)", sudo: true)
brew("services stop nginx", sudo: true)
brew("services stop dnsmasq", sudo: true)

View File

@ -0,0 +1,24 @@
//
// ArrayExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 11/06/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
extension Array {
/**
Sourced from Stack Overflow
https://stackoverflow.com/a/33540708
*/
func chunked(by distance: Int) -> [[Element]] {
let indicesSequence = stride(from: startIndex, to: endIndex, by: distance)
let array: [[Element]] = indicesSequence.map {
let newIndex = $0.advanced(by: distance) > endIndex ? endIndex : $0.advanced(by: distance)
return Array(self[$0 ..< newIndex])
}
return array
}
}

View File

@ -19,25 +19,6 @@ struct HomebrewService: Decodable, Equatable {
let log_path: String?
let error_log_path: String?
public static func loadAll(
filter: [String] = [PhpEnv.phpInstall.formula, "nginx", "dnsmasq"],
completion: @escaping ([HomebrewService]) -> Void
) {
DispatchQueue.global(qos: .background).async {
let data = Shell
.pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true)
.data(using: .utf8)!
let services = try! JSONDecoder()
.decode([HomebrewService].self, from: data)
.filter({ return filter.contains($0.name) })
DispatchQueue.main.async {
completion(services)
}
}
}
/**
Dummy data for preview purposes.
*/

View File

@ -26,10 +26,10 @@ class InterApp {
DomainListVC.show()
}),
InterApp.Action(command: "services/stop", action: { _ in
MainMenu.shared.stopAllServices()
MainMenu.shared.stopValetServices()
}),
InterApp.Action(command: "services/restart/all", action: { _ in
MainMenu.shared.restartAllServices()
MainMenu.shared.restartValetServices()
}),
InterApp.Action(command: "services/restart/nginx", action: { _ in
MainMenu.shared.restartNginx()

View File

@ -13,14 +13,55 @@ class ServicesManager: ObservableObject {
static var shared = ServicesManager()
@Published var services: [String: HomebrewService] = [:]
@Published var rootServices: [String: HomebrewService] = [:]
@Published var userServices: [String: HomebrewService] = [:]
public static func loadHomebrewServices() {
let rootServiceNames = [
PhpEnv.phpInstall.formula,
"nginx",
"dnsmasq"
]
DispatchQueue.global(qos: .background).async {
let data = Shell
.pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true)
.data(using: .utf8)!
let services = try! JSONDecoder()
.decode([HomebrewService].self, from: data)
.filter({ return rootServiceNames.contains($0.name) })
DispatchQueue.main.async {
ServicesManager.shared.rootServices = Dictionary(
uniqueKeysWithValues: services.map { ($0.name, $0) }
)
}
}
guard let userServiceNames = Preferences.custom.services else {
return
}
DispatchQueue.global(qos: .background).async {
let data = Shell
.pipe("\(Paths.brew) services info --all --json", requiresPath: true)
.data(using: .utf8)!
let services = try! JSONDecoder()
.decode([HomebrewService].self, from: data)
.filter({ return userServiceNames.contains($0.name) })
DispatchQueue.main.async {
ServicesManager.shared.userServices = Dictionary(
uniqueKeysWithValues: services.map { ($0.name, $0) }
)
}
}
}
func loadData() {
HomebrewService.loadAll { services in
self.services = Dictionary(
uniqueKeysWithValues: services.map { ($0.name, $0) }
)
}
Self.loadHomebrewServices()
}
/**
@ -29,7 +70,7 @@ class ServicesManager: ObservableObject {
func withDummyServices(_ services: [String: Bool]) -> Self {
for (service, enabled) in services {
let item = HomebrewService.dummy(named: service, enabled: enabled)
self.services[service] = item
self.rootServices[service] = item
}
return self

View File

@ -1,36 +0,0 @@
//
// HeaderView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 04/02/2021.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
import Cocoa
class HeaderView: NSView, XibLoadable {
@IBOutlet weak var textField: NSTextField!
static func asMenuItem(
text: String,
width: Int? = nil
) -> NSMenuItem {
let view = Self.createFromXib()!
view.autoresizingMask = [.width, .height]
view.textField.stringValue = text.uppercased()
view.textField.sizeToFit()
view.setFrameSize(CGSize(width: view.textField.frame.width + 40, height: view.frame.height))
let item = NSMenuItem()
item.view = view
item.target = self
return item
}
}

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe" customClass="HeaderView" customModule="PHP_Monitor" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="270" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ddg-VQ-cOT">
<rect key="frame" x="12" y="5" width="113" height="15"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="ACTIVE SERVICES" id="NHz-MZ-8FK">
<font key="font" metaFont="systemBold" size="12"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="ddg-VQ-cOT" firstAttribute="centerY" secondItem="c22-O7-iKe" secondAttribute="centerY" id="n4Z-WN-RIh"/>
<constraint firstItem="ddg-VQ-cOT" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="14" id="yuW-pb-GQJ"/>
</constraints>
<connections>
<outlet property="textField" destination="ddg-VQ-cOT" id="aaQ-Xb-o2X"/>
</connections>
<point key="canvasLocation" x="177" y="105"/>
</customView>
</objects>
</document>

View File

@ -47,7 +47,7 @@ extension MainMenu {
}
}
@objc func restartAllServices() {
@objc func restartValetServices() {
asyncExecution {
Actions.restartDnsMasq()
Actions.restartPhpFpm()
@ -63,9 +63,9 @@ extension MainMenu {
}
}
@objc func stopAllServices() {
@objc func stopValetServices() {
asyncExecution {
Actions.stopAllServices()
Actions.stopValetServices()
} success: {
DispatchQueue.main.async {
LocalNotification.send(

View File

@ -65,7 +65,12 @@ extension StatusMenu {
}
func addPresetsMenuItem() {
if Preferences.custom.presets.isEmpty {
guard let presets = Preferences.custom.presets else {
addEmptyPresetHelp()
return
}
if presets.isEmpty {
addEmptyPresetHelp()
return
}
@ -102,7 +107,7 @@ extension StatusMenu {
presetsMenu.addItem(NSMenuItem.separator())
presetsMenu.addItem(HeaderView.asMenuItem(text: "mi_apply_presets_title".localized))
for preset in Preferences.custom.presets {
for preset in Preferences.custom.presets! {
let presetMenuItem = PresetMenuItem(
title: preset.getMenuItemText(),
action: #selector(MainMenu.togglePreset(sender:)),
@ -132,7 +137,7 @@ extension StatusMenu {
presetsMenu.addItem(NSMenuItem.separator())
presetsMenu.addItem(NSMenuItem(
title: "mi_profiles_loaded".localized(
Preferences.custom.presets.count
Preferences.custom.presets!.count
),
action: nil, keyEquivalent: "")
)
@ -219,12 +224,12 @@ extension StatusMenu {
action: #selector(MainMenu.restartNginx), keyEquivalent: "n")
)
servicesMenu.addItem(
NSMenuItem(title: "mi_restart_all_services".localized,
action: #selector(MainMenu.restartAllServices), keyEquivalent: "s")
NSMenuItem(title: "mi_restart_valet_services".localized,
action: #selector(MainMenu.restartValetServices), keyEquivalent: "s")
)
servicesMenu.addItem(
NSMenuItem(title: "mi_stop_all_services".localized,
action: #selector(MainMenu.stopAllServices), keyEquivalent: "s"),
NSMenuItem(title: "mi_stop_valet_services".localized,
action: #selector(MainMenu.stopValetServices), keyEquivalent: "s"),
withKeyModifier: [.command, .shift]
)

View File

@ -10,10 +10,20 @@ import Foundation
struct CustomPrefs: Decodable {
let scanApps: [String]
let presets: [Preset]
let presets: [Preset]?
let services: [String]?
public func hasPresets() -> Bool {
return self.presets != nil && !self.presets!.isEmpty
}
public func hasServices() -> Bool {
return self.services != nil && !self.services!.isEmpty
}
private enum CodingKeys: String, CodingKey {
case scanApps = "scan_apps"
case presets = "presets"
case services = "services"
}
}

View File

@ -65,7 +65,7 @@ class Preferences {
public init() {
Preferences.handleFirstTimeLaunch()
cachedPreferences = Self.cache()
customPreferences = CustomPrefs(scanApps: [], presets: [])
customPreferences = CustomPrefs(scanApps: [], presets: [], services: [])
loadCustomPreferences()
}
@ -228,7 +228,14 @@ class Preferences {
)
Log.info("The ~/.config/phpmon/config.json file was successfully parsed.")
Log.info("There are \(customPreferences.presets.count) custom presets.")
if customPreferences.hasPresets() {
Log.info("There are \(customPreferences.presets!.count) custom presets.")
}
if customPreferences.hasServices() {
Log.info("There are custom services: \(customPreferences.services!)")
}
} catch {
Log.warn("The ~/.config/phpmon/config.json file seems to be missing or malformed.")
}

View File

@ -7,8 +7,18 @@
//
import Foundation
import SwiftUI
var isRunningSwiftUIPreview: Bool {
return ProcessInfo.processInfo
.environment["XCODE_RUNNING_FOR_PREVIEWS"] != nil
}
extension Color {
public static var debug: Color = {
if ProcessInfo.processInfo.environment["PAINT_PHPMON_SWIFTUI_VIEWS"] != nil {
return Color.yellow
}
return Color.clear
}()
}

View File

@ -0,0 +1,47 @@
//
// MiniHeaderView.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 10/06/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import SwiftUI
struct HeaderView: View {
@State var text: String
var body: some View {
Text(text.uppercased())
.font(.system(size: 12))
.fontWeight(.bold)
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
.padding(.leading, 14.0)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.debug)
}
// MARK: - NSMenuItem
static func asMenuItem(
text: String,
width: Int? = nil
) -> NSMenuItem {
let view = NSHostingView(rootView: Self(text: text))
view.autoresizingMask = [.width, .height]
view.setFrameSize(CGSize(width: view.frame.width, height: 24))
let item = NSMenuItem()
item.view = view
return item
}
}
struct HeaderView_Previews: PreviewProvider {
static var previews: some View {
HeaderView(text: "Hello world")
.frame(width: 330.0)
}
}

View File

@ -8,7 +8,8 @@
import SwiftUI
struct MiniHeaderView: View {
struct SectionHeaderView: View {
@State var text: String
var body: some View {
@ -16,5 +17,6 @@ struct MiniHeaderView: View {
.font(.system(size: 11))
.fontWeight(.medium)
.foregroundColor(.secondary)
.background(Color.debug)
}
}

View File

@ -15,31 +15,47 @@ struct ServicesView: View {
static func asMenuItem() -> NSMenuItem {
let item = NSMenuItem()
var services = [
PhpEnv.phpInstall.formula,
"nginx",
"dnsmasq"
]
if Preferences.custom.hasServices() {
services += Preferences.custom.services!
}
let view = NSHostingView(
rootView: Self(
manager: ServicesManager.shared,
servicesToDisplay: [
PhpEnv.phpInstall.formula,
"nginx",
"dnsmasq"
]
servicesToDisplay: services
)
)
view.frame = CGRect(x: 0, y: 0, width: 330, height: 45)
view.autoresizingMask = [.width, .height]
let height = CGFloat(45 * services.chunked(by: 3).count)
view.setFrameSize(CGSize(width: view.frame.width, height: height))
item.view = view
return item
}
var body: some View {
HStack(alignment: .firstTextBaseline, spacing: 10) {
ForEach(servicesToDisplay, id: \.self) { service in
VStack(alignment: .center, spacing: 3) {
MiniHeaderView(text: service.uppercased())
CheckmarkView(serviceName: service)
.environmentObject(manager)
}.frame(minWidth: 0, maxWidth: .infinity)
VStack(alignment: .leading, spacing: 10) {
ForEach(servicesToDisplay.chunked(by: 3), id: \.self) { chunk in
HStack {
ForEach(chunk, id: \.self) { service in
VStack(alignment: .center, spacing: 3) {
SectionHeaderView(text: service.uppercased())
CheckmarkView(serviceName: service)
.environmentObject(manager)
}.frame(minWidth: 0, maxWidth: .infinity)
}
}
}
}.padding(10)
}
.padding(10)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.debug)
}
}
@ -48,28 +64,39 @@ struct CheckmarkView: View {
@EnvironmentObject var manager: ServicesManager
public func hasAnyServices() -> Bool {
return !manager.services.isEmpty
return !manager.rootServices.isEmpty
}
public func active() -> Bool {
guard let service = manager.services[serviceName] else {
return false
public func active() -> Bool? {
if manager.rootServices.keys.contains(serviceName) {
return manager.rootServices[serviceName]!.running
}
return service.running
if manager.userServices.keys.contains(serviceName) {
return manager.userServices[serviceName]!.running
}
return nil
}
var body: some View {
if !hasAnyServices() {
Image(systemName: "questionmark.circle")
Image(systemName: "hourglass.circle")
.resizable()
.frame(width: 16.0, height: 16.0)
.foregroundColor(.secondary)
} else {
Image(systemName: active() ? "checkmark.circle" : "exclamationmark.triangle")
.resizable()
.frame(width: 16.0, height: 16.0)
.foregroundColor(active() ? Color("IconColorGreen") : Color("IconColorRed"))
if active() == nil {
Image(systemName: "questionmark.square.dashed")
.resizable()
.frame(width: 16.0, height: 16.0)
.foregroundColor(Color("IconColorRed"))
} else {
Image(systemName: active()! ? "checkmark.circle" : "xmark.circle")
.resizable()
.frame(width: 16.0, height: 16.0)
.foregroundColor(active()! ? Color.primary : Color("IconColorRed"))
}
}
}
}
@ -104,7 +131,8 @@ struct ServicesView_Previews: PreviewProvider {
"dnsmasq": true,
"mysql": false
]),
servicesToDisplay: ["php", "nginx", "dnsmasq", "mysql"]
servicesToDisplay: ["php", "nginx", "dnsmasq",
"mysql", "redis", "mailhog"]
)
.frame(width: 330.0)
.previewDisplayName("Dark Mode")

View File

@ -19,7 +19,8 @@ struct StatsView: View {
maxUploadSize: upload
)
)
view.frame = CGRect(x: 0, y: 0, width: 330, height: 55)
view.autoresizingMask = [.width, .height]
view.setFrameSize(CGSize(width: view.frame.width, height: 55))
item.view = view
return item
}
@ -31,24 +32,26 @@ struct StatsView: View {
var body: some View {
HStack(alignment: .firstTextBaseline, spacing: 30) {
VStack(alignment: .center, spacing: 3) {
MiniHeaderView(text: "mi_memory_limit".localized.uppercased())
SectionHeaderView(text: "mi_memory_limit".localized.uppercased())
Text(memoryLimit)
.fontWeight(.medium)
.font(.system(size: 16))
}
VStack(alignment: .center, spacing: 3) {
MiniHeaderView(text: "mi_post_max_size".localized.uppercased())
SectionHeaderView(text: "mi_post_max_size".localized.uppercased())
Text(maxPostSize)
.fontWeight(.medium)
.font(.system(size: 16))
}
VStack(alignment: .center, spacing: 3) {
MiniHeaderView(text: "mi_upload_max_filesize".localized.uppercased())
SectionHeaderView(text: "mi_upload_max_filesize".localized.uppercased())
Text(maxUploadSize)
.fontWeight(.medium)
.font(.system(size: 16))
}
}.padding(10)
}
.padding(10)
.background(Color.debug)
}
}

View File

@ -23,8 +23,8 @@
"mi_restart_nginx" = "Restart Service: nginx";
"mi_restart_dnsmasq" = "Restart Service: dnsmasq";
"mi_manage_services" = "Manage Services";
"mi_restart_all_services" = "Restart All Services";
"mi_stop_all_services" = "Stop All Services";
"mi_restart_valet_services" = "Restart Valet Services";
"mi_stop_valet_services" = "Stop Valet Services";
"mi_fix_my_valet" = "Fix My Valet...";
"mi_fix_my_valet_tooltip" = "Something wrong with your Valet installation? Try PHP Monitors automatic fixes thatll get you back up and running in no time!";
@ -347,7 +347,7 @@ problem manually, using your own Terminal app (this just shows you the output)."
"alert.fix_homebrew_permissions_done.title" = "All file and folder permissions for Valet's dependencies have been restored.";
"alert.fix_homebrew_permissions_done.subtitle" = "Because of this, all of Valet's services are currently no longer running. You can now interact with Homebrew, but your Valet sites will be unavailable as all services are disabled.";
"alert.fix_homebrew_permissions_done.desc" = "When you are done with Homebrew (after running `brew upgrade`, for example) you should restart PHP Monitor and select \"Restart All Services\" if you want Valet to work again. It is always recommended to restart PHP Monitor whenever you upgrade PHP versions with `brew upgrade`, or things might break.";
"alert.fix_homebrew_permissions_done.desc" = "When you are done with Homebrew (after running `brew upgrade`, for example) you should restart PHP Monitor and select \"Restart Valet Services\" if you want Valet to work again. It is always recommended to restart PHP Monitor whenever you upgrade PHP versions with `brew upgrade`, or things might break.";
// PHP FPM Broken
"alert.php_fpm_broken.title" = "Your PHP-FPM configuration is not pointing at the Valet socket!";