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:
@ -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 = "";
|
||||
|
@ -69,8 +69,8 @@
|
||||
</CommandLineArguments>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "PHPMON_MARKETING_MODE"
|
||||
value = "YES"
|
||||
key = "PAINT_PHPMON_SWIFTUI_VIEWS"
|
||||
value = ""
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
|
@ -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)
|
||||
|
24
phpmon/Common/Extensions/ArrayExtension.swift
Normal file
24
phpmon/Common/Extensions/ArrayExtension.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
@ -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(
|
||||
|
@ -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]
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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.")
|
||||
}
|
||||
|
@ -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
|
||||
}()
|
||||
}
|
||||
|
47
phpmon/Domain/SwiftUI/Menu/HeaderView.swift
Normal file
47
phpmon/Domain/SwiftUI/Menu/HeaderView.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 Monitor’s automatic fixes that’ll 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!";
|
||||
|
Reference in New Issue
Block a user