updates
This commit is contained in:
BIN
Binary file not shown.
+31
-7
@@ -15,7 +15,7 @@
|
||||
"key": "ALL SERVERS: %lld OK · %lld WARN · %lld FAIL",
|
||||
"location": {
|
||||
"startingColumn": 18,
|
||||
"startingLine": 76
|
||||
"startingLine": 97
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -42,12 +42,36 @@
|
||||
"startingLine": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "SERVICES",
|
||||
"location": {
|
||||
"startingColumn": 38,
|
||||
"startingLine": 59
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "HEALTH GROUPS",
|
||||
"location": {
|
||||
"startingColumn": 38,
|
||||
"startingLine": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "REQUIRED",
|
||||
"location": {
|
||||
"startingColumn": 50,
|
||||
"startingLine": 68
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Refresh All",
|
||||
"location": {
|
||||
"startingColumn": 24,
|
||||
"startingLine": 84
|
||||
"startingLine": 105
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55,7 +79,7 @@
|
||||
"key": "Diagnostics",
|
||||
"location": {
|
||||
"startingColumn": 24,
|
||||
"startingLine": 86
|
||||
"startingLine": 107
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -63,7 +87,7 @@
|
||||
"key": "Settings",
|
||||
"location": {
|
||||
"startingColumn": 24,
|
||||
"startingLine": 87
|
||||
"startingLine": 108
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -71,7 +95,7 @@
|
||||
"key": "Quit",
|
||||
"location": {
|
||||
"startingColumn": 24,
|
||||
"startingLine": 88
|
||||
"startingLine": 109
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -79,7 +103,7 @@
|
||||
"key": "%@ — Diagnostics",
|
||||
"location": {
|
||||
"startingColumn": 18,
|
||||
"startingLine": 98
|
||||
"startingLine": 119
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -87,7 +111,7 @@
|
||||
"key": "Select a server in the menu bar to change this report.",
|
||||
"location": {
|
||||
"startingColumn": 18,
|
||||
"startingLine": 99
|
||||
"startingLine": 120
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+104
-16
@@ -7,7 +7,7 @@
|
||||
"key": "Servers",
|
||||
"location": {
|
||||
"startingColumn": 22,
|
||||
"startingLine": 14
|
||||
"startingLine": 16
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -15,7 +15,7 @@
|
||||
"key": "Use the Pegasus adapter for Pegasus, or install server/remote-health-json on other Linux servers. SSH must work without interactive prompts. SSH known_hosts is enforced.",
|
||||
"location": {
|
||||
"startingColumn": 22,
|
||||
"startingLine": 48
|
||||
"startingLine": 52
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -23,7 +23,7 @@
|
||||
"key": "Add a server",
|
||||
"location": {
|
||||
"startingColumn": 41,
|
||||
"startingLine": 37
|
||||
"startingLine": 39
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -31,7 +31,7 @@
|
||||
"key": "Connection details",
|
||||
"location": {
|
||||
"startingColumn": 58,
|
||||
"startingLine": 37
|
||||
"startingLine": 39
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -39,7 +39,7 @@
|
||||
"key": "Display name",
|
||||
"location": {
|
||||
"startingColumn": 31,
|
||||
"startingLine": 40
|
||||
"startingLine": 42
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -47,7 +47,7 @@
|
||||
"key": "Host / SSH alias",
|
||||
"location": {
|
||||
"startingColumn": 31,
|
||||
"startingLine": 41
|
||||
"startingLine": 43
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55,7 +55,7 @@
|
||||
"key": "SSH username",
|
||||
"location": {
|
||||
"startingColumn": 31,
|
||||
"startingLine": 42
|
||||
"startingLine": 44
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -63,7 +63,7 @@
|
||||
"key": "SSH port",
|
||||
"location": {
|
||||
"startingColumn": 31,
|
||||
"startingLine": 43
|
||||
"startingLine": 45
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -71,7 +71,7 @@
|
||||
"key": "SSH key path (optional)",
|
||||
"location": {
|
||||
"startingColumn": 31,
|
||||
"startingLine": 44
|
||||
"startingLine": 46
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -79,7 +79,7 @@
|
||||
"key": "Remote health command",
|
||||
"location": {
|
||||
"startingColumn": 31,
|
||||
"startingLine": 45
|
||||
"startingLine": 47
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -87,7 +87,15 @@
|
||||
"key": "Monitoring enabled",
|
||||
"location": {
|
||||
"startingColumn": 28,
|
||||
"startingLine": 46
|
||||
"startingLine": 48
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Automatically discover services",
|
||||
"location": {
|
||||
"startingColumn": 28,
|
||||
"startingLine": 49
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -95,7 +103,7 @@
|
||||
"key": "New Server",
|
||||
"location": {
|
||||
"startingColumn": 28,
|
||||
"startingLine": 52
|
||||
"startingLine": 56
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -103,7 +111,7 @@
|
||||
"key": "Save and Refresh",
|
||||
"location": {
|
||||
"startingColumn": 28,
|
||||
"startingLine": 54
|
||||
"startingLine": 58
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -111,7 +119,7 @@
|
||||
"key": "Refresh every",
|
||||
"location": {
|
||||
"startingColumn": 28,
|
||||
"startingLine": 59
|
||||
"startingLine": 63
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -119,7 +127,7 @@
|
||||
"key": "Notifications",
|
||||
"location": {
|
||||
"startingColumn": 28,
|
||||
"startingLine": 64
|
||||
"startingLine": 68
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -127,7 +135,87 @@
|
||||
"key": "%lld min",
|
||||
"location": {
|
||||
"startingColumn": 34,
|
||||
"startingLine": 61
|
||||
"startingLine": 65
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Mark critical services as Required. Missing or non-running required services count as failures. Optional services not installed do not affect health.",
|
||||
"location": {
|
||||
"startingColumn": 18,
|
||||
"startingLine": 98
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "No service discovery data yet. Refresh the server or add a required service below.",
|
||||
"location": {
|
||||
"startingColumn": 22,
|
||||
"startingLine": 105
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Required: %@",
|
||||
"location": {
|
||||
"startingColumn": 22,
|
||||
"startingLine": 132
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Show not installed services",
|
||||
"location": {
|
||||
"startingColumn": 20,
|
||||
"startingLine": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Service monitoring",
|
||||
"location": {
|
||||
"startingColumn": 22,
|
||||
"startingLine": 92
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Refresh services",
|
||||
"location": {
|
||||
"startingColumn": 24,
|
||||
"startingLine": 94
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Required",
|
||||
"location": {
|
||||
"startingColumn": 40,
|
||||
"startingLine": 115
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Require %@ to be running",
|
||||
"location": {
|
||||
"startingColumn": 56,
|
||||
"startingLine": 118
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Add required service (e.g. mysql)",
|
||||
"location": {
|
||||
"startingColumn": 27,
|
||||
"startingLine": 126
|
||||
}
|
||||
},
|
||||
{
|
||||
"comment": "",
|
||||
"key": "Add Required",
|
||||
"location": {
|
||||
"startingColumn": 24,
|
||||
"startingLine": 128
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-49
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"" : {
|
||||
"diagnostics" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitor-primary.dia",
|
||||
"emit-module-dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitor-primary-emit-module.d",
|
||||
"emit-module-diagnostics" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitor-primary-emit-module.dia",
|
||||
"pch" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitor-primary-Bridging-header.pch",
|
||||
"swift-dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitor-primary.swiftdeps"
|
||||
},
|
||||
"/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/MonitorMenuView.swift" : {
|
||||
"const-values" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.swiftconstvalues",
|
||||
"dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.d",
|
||||
"diagnostics" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.dia",
|
||||
"index-unit-output-path" : "/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.o",
|
||||
"llvm-bc" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.bc",
|
||||
"object" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.o",
|
||||
"swift-dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.swiftdeps",
|
||||
"swiftmodule" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView~partial.swiftmodule"
|
||||
},
|
||||
"/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/PegasusMonitorApp.swift" : {
|
||||
"const-values" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.swiftconstvalues",
|
||||
"dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.d",
|
||||
"diagnostics" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.dia",
|
||||
"index-unit-output-path" : "/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.o",
|
||||
"llvm-bc" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.bc",
|
||||
"object" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.o",
|
||||
"swift-dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.swiftdeps",
|
||||
"swiftmodule" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp~partial.swiftmodule"
|
||||
},
|
||||
"/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/ServerMonitor.swift" : {
|
||||
"const-values" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.swiftconstvalues",
|
||||
"dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.d",
|
||||
"diagnostics" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.dia",
|
||||
"index-unit-output-path" : "/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.o",
|
||||
"llvm-bc" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.bc",
|
||||
"object" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.o",
|
||||
"swift-dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.swiftdeps",
|
||||
"swiftmodule" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor~partial.swiftmodule"
|
||||
},
|
||||
"/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/SettingsView.swift" : {
|
||||
"const-values" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.swiftconstvalues",
|
||||
"dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.d",
|
||||
"diagnostics" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.dia",
|
||||
"index-unit-output-path" : "/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.o",
|
||||
"llvm-bc" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.bc",
|
||||
"object" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.o",
|
||||
"swift-dependencies" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.swiftdeps",
|
||||
"swiftmodule" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView~partial.swiftmodule"
|
||||
}
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/PegasusMonitorApp.swift
|
||||
/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/ServerMonitor.swift
|
||||
/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/MonitorMenuView.swift
|
||||
/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor/SettingsView.swift
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.o
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.o
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.o
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.o
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"case-sensitive":"false","roots":[],"version":0}
|
||||
-1
@@ -1 +0,0 @@
|
||||
["AnyResolverProviding","AppEntity","AppEnum","AppExtension","AppIntent","AppIntentsPackage","AppShortcutProviding","AppShortcutsProvider","AppUnionValue","AppUnionValueCasesProviding","DynamicOptionsProvider","EntityQuery","ExtensionPointDefining","IntentValueQuery","Resolver","TransientEntity","_AssistantIntentsProvider","_GenerativeFunctionExtractable","_IntentValueRepresentable"]
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/PegasusMonitorApp.swiftconstvalues
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/ServerMonitor.swiftconstvalues
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/MonitorMenuView.swiftconstvalues
|
||||
/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PegasusMonitor.build/Release/PegasusMonitor.build/Objects-normal/arm64/SettingsView.swiftconstvalues
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-68
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"buildCommand" : {
|
||||
"command" : "build",
|
||||
"skipDependencies" : false,
|
||||
"style" : "buildOnly"
|
||||
},
|
||||
"configuredTargets" : [
|
||||
{
|
||||
"guid" : "651e0ee2bbff102a3d1ac16211f573867d28c6b0d11cbd2d9a7f0a38de01f763"
|
||||
}
|
||||
],
|
||||
"containerPath" : "/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor.xcodeproj",
|
||||
"continueBuildingAfterErrors" : false,
|
||||
"dependencyScope" : "workspace",
|
||||
"enableIndexBuildArena" : false,
|
||||
"hideShellScriptEnvironment" : false,
|
||||
"parameters" : {
|
||||
"action" : "build",
|
||||
"activeArchitecture" : "arm64",
|
||||
"activeRunDestination" : {
|
||||
"disableOnlyActiveArch" : false,
|
||||
"hostTargetedPlatform" : null,
|
||||
"platform" : "macosx",
|
||||
"sdk" : "macosx27.0",
|
||||
"sdkVariant" : "macos",
|
||||
"supportedArchitectures" : [
|
||||
"arm64e",
|
||||
"arm64",
|
||||
"x86_64"
|
||||
],
|
||||
"targetArchitecture" : "arm64"
|
||||
},
|
||||
"arenaInfo" : {
|
||||
"buildIntermediatesPath" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex",
|
||||
"buildProductsPath" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Products",
|
||||
"derivedDataPath" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData",
|
||||
"indexDataStoreFolderPath" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Index.noindex/DataStore",
|
||||
"indexEnableDataStore" : true,
|
||||
"indexPCHPath" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Index.noindex/PrecompiledHeaders",
|
||||
"pchPath" : "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/Build/Intermediates.noindex/PrecompiledHeaders"
|
||||
},
|
||||
"configurationName" : "Release",
|
||||
"overrides" : {
|
||||
"commandLine" : {
|
||||
"table" : {
|
||||
"CODE_SIGNING_ALLOWED" : "NO"
|
||||
}
|
||||
},
|
||||
"synthesized" : {
|
||||
"table" : {
|
||||
"ACTION" : "build",
|
||||
"COLOR_DIAGNOSTICS" : "YES",
|
||||
"diagnostic_message_length" : "162",
|
||||
"ENABLE_PREVIEWS" : "NO",
|
||||
"ENABLE_XOJIT_PREVIEWS" : "YES",
|
||||
"ONLY_ACTIVE_ARCH" : "YES",
|
||||
"TOOLCHAINS" : "com.apple.dt.toolchain.Metal.32023.921.5 $(inherited)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemeCommand" : "launch",
|
||||
"showNonLoggedProgress" : true,
|
||||
"useDryRun" : false,
|
||||
"useImplicitDependencies" : true,
|
||||
"useLegacyBuildLocations" : false,
|
||||
"useParallelTargets" : true
|
||||
}
|
||||
BIN
Binary file not shown.
-1
File diff suppressed because one or more lines are too long
-2
@@ -1,2 +0,0 @@
|
||||
Target dependency graph (1 target)
|
||||
Target 'PegasusMonitor' in project 'PegasusMonitor' (no dependencies)
|
||||
BIN
Binary file not shown.
+1
@@ -5,3 +5,4 @@ f917cf82bd848fc97291bd609bd414d6
|
||||
ae0397526b5e3c6e8848cdfd6a483288
|
||||
4366908824f51bff885c44198535c243
|
||||
4f303a1f6ed436c288335f2c2511f2f6
|
||||
7e38a5d8e664e612ef350f00745dc212
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -6,6 +6,48 @@
|
||||
<integer>13</integer>
|
||||
<key>logs</key>
|
||||
<dict>
|
||||
<key>0775894F-A03B-46F0-8D09-D39D1E301BA4</key>
|
||||
<dict>
|
||||
<key>className</key>
|
||||
<string>IDECommandLineBuildLog</string>
|
||||
<key>documentTypeString</key>
|
||||
<string><nil></string>
|
||||
<key>domainType</key>
|
||||
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
|
||||
<key>fileName</key>
|
||||
<string>0775894F-A03B-46F0-8D09-D39D1E301BA4.xcactivitylog</string>
|
||||
<key>hasPrimaryLog</key>
|
||||
<true/>
|
||||
<key>primaryObservable</key>
|
||||
<dict>
|
||||
<key>highLevelStatus</key>
|
||||
<string>W</string>
|
||||
<key>totalNumberOfAnalyzerIssues</key>
|
||||
<integer>0</integer>
|
||||
<key>totalNumberOfErrors</key>
|
||||
<integer>0</integer>
|
||||
<key>totalNumberOfTestFailures</key>
|
||||
<integer>0</integer>
|
||||
<key>totalNumberOfWarnings</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>schemeIdentifier-containerName</key>
|
||||
<string>PegasusMonitor project</string>
|
||||
<key>schemeIdentifier-schemeName</key>
|
||||
<string>PegasusMonitor</string>
|
||||
<key>schemeIdentifier-sharedScheme</key>
|
||||
<integer>1</integer>
|
||||
<key>signature</key>
|
||||
<string>Building project PegasusMonitor with scheme PegasusMonitor and configuration Release</string>
|
||||
<key>timeStartedRecording</key>
|
||||
<real>814283389.12080395</real>
|
||||
<key>timeStoppedRecording</key>
|
||||
<real>814283396.01673496</real>
|
||||
<key>title</key>
|
||||
<string>Building project PegasusMonitor with scheme PegasusMonitor and configuration Release</string>
|
||||
<key>uniqueIdentifier</key>
|
||||
<string>0775894F-A03B-46F0-8D09-D39D1E301BA4</string>
|
||||
</dict>
|
||||
<key>CC8DC4F3-DD53-4B42-8573-B118D88EBAB2</key>
|
||||
<dict>
|
||||
<key>className</key>
|
||||
@@ -48,48 +90,6 @@
|
||||
<key>uniqueIdentifier</key>
|
||||
<string>CC8DC4F3-DD53-4B42-8573-B118D88EBAB2</string>
|
||||
</dict>
|
||||
<key>E1DE6BCB-D5CB-4252-BF49-D92793CE0E02</key>
|
||||
<dict>
|
||||
<key>className</key>
|
||||
<string>IDECommandLineBuildLog</string>
|
||||
<key>documentTypeString</key>
|
||||
<string><nil></string>
|
||||
<key>domainType</key>
|
||||
<string>Xcode.IDEActivityLogDomainType.BuildLog</string>
|
||||
<key>fileName</key>
|
||||
<string>E1DE6BCB-D5CB-4252-BF49-D92793CE0E02.xcactivitylog</string>
|
||||
<key>hasPrimaryLog</key>
|
||||
<true/>
|
||||
<key>primaryObservable</key>
|
||||
<dict>
|
||||
<key>highLevelStatus</key>
|
||||
<string>S</string>
|
||||
<key>totalNumberOfAnalyzerIssues</key>
|
||||
<integer>0</integer>
|
||||
<key>totalNumberOfErrors</key>
|
||||
<integer>0</integer>
|
||||
<key>totalNumberOfTestFailures</key>
|
||||
<integer>0</integer>
|
||||
<key>totalNumberOfWarnings</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>schemeIdentifier-containerName</key>
|
||||
<string>PegasusMonitor project</string>
|
||||
<key>schemeIdentifier-schemeName</key>
|
||||
<string>PegasusMonitor</string>
|
||||
<key>schemeIdentifier-sharedScheme</key>
|
||||
<integer>1</integer>
|
||||
<key>signature</key>
|
||||
<string>Building project PegasusMonitor with scheme PegasusMonitor and configuration Release</string>
|
||||
<key>timeStartedRecording</key>
|
||||
<real>814281866.56225002</real>
|
||||
<key>timeStoppedRecording</key>
|
||||
<real>814281867.39492798</real>
|
||||
<key>title</key>
|
||||
<string>Building project PegasusMonitor with scheme PegasusMonitor and configuration Release</string>
|
||||
<key>uniqueIdentifier</key>
|
||||
<string>E1DE6BCB-D5CB-4252-BF49-D92793CE0E02</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1 +1 @@
|
||||
1792589935.5735621: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/ModuleCache.noindex")
|
||||
1792590590.976388: Module build session file for module cache at Path(str: "/Volumes/Developer/Applications/PegasusMonitor/.build/ReleaseDerivedData/ModuleCache.noindex")
|
||||
|
||||
BIN
Binary file not shown.
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LastAccessedDate</key>
|
||||
<date>2026-10-21T13:38:54Z</date>
|
||||
<date>2026-10-21T13:49:49Z</date>
|
||||
<key>WorkspacePath</key>
|
||||
<string>/Volumes/Developer/Applications/PegasusMonitor/PegasusMonitor.xcodeproj</string>
|
||||
</dict>
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 39R6U3437D;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -174,7 +174,7 @@
|
||||
INFOPLIST_KEY_LSUIElement = YES;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PegasusMonitor;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
@@ -189,7 +189,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = 39R6U3437D;
|
||||
ENABLE_APP_SANDBOX = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -197,7 +197,7 @@
|
||||
INFOPLIST_KEY_LSUIElement = YES;
|
||||
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.1.0;
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PegasusMonitor;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
|
||||
BIN
Binary file not shown.
@@ -55,6 +55,27 @@ struct MonitorMenuView: View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 0) {
|
||||
if let report = monitor.report {
|
||||
if !monitor.selectedServices.isEmpty {
|
||||
Text("SERVICES")
|
||||
.font(.caption.bold()).foregroundStyle(.secondary)
|
||||
.padding(.horizontal, 14).padding(.vertical, 8)
|
||||
ForEach(monitor.selectedServices) { service in
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: service.symbol).foregroundStyle(service.color)
|
||||
Text(service.name).font(.system(size: 12))
|
||||
Spacer()
|
||||
if selected.requiredServices.contains(service.name) {
|
||||
Text("REQUIRED").font(.system(size: 9, weight: .bold)).foregroundStyle(.orange)
|
||||
}
|
||||
Text(service.title).font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.horizontal, 14).padding(.vertical, 5)
|
||||
}
|
||||
Divider()
|
||||
Text("HEALTH GROUPS")
|
||||
.font(.caption.bold()).foregroundStyle(.secondary)
|
||||
.padding(.horizontal, 14).padding(.vertical, 8)
|
||||
}
|
||||
ForEach(report.groups) { group in
|
||||
HStack {
|
||||
Text(group.name).font(.system(size: 12, weight: .medium))
|
||||
|
||||
@@ -18,9 +18,9 @@ struct PegasusMonitorApp: App {
|
||||
// Real independent windows: editing cannot be dismissed by the menu-bar popover.
|
||||
Window("Pegasus Monitor Settings", id: "monitor-settings") {
|
||||
SettingsView(monitor: monitor)
|
||||
.frame(minWidth: 630, minHeight: 470)
|
||||
.frame(minWidth: 780, minHeight: 640)
|
||||
}
|
||||
.defaultSize(width: 750, height: 570)
|
||||
.defaultSize(width: 880, height: 750)
|
||||
|
||||
Window("Server Diagnostics", id: "monitor-diagnostics") {
|
||||
DiagnosticsView(monitor: monitor)
|
||||
|
||||
@@ -20,6 +20,47 @@ struct HealthReport: Codable {
|
||||
let groups: [HealthGroup]
|
||||
let checks: [HealthCheck]
|
||||
let diagnostics: String?
|
||||
let services: [DiscoveredService]?
|
||||
}
|
||||
|
||||
|
||||
// Service discovery is optional so legacy Pegasus health JSON stays compatible.
|
||||
struct DiscoveredService: Codable, Identifiable, Equatable {
|
||||
let name: String
|
||||
let state: String
|
||||
let detail: String
|
||||
var id: String { name }
|
||||
var isInstalled: Bool { state != "not_installed" }
|
||||
var isHealthy: Bool { state == "running" }
|
||||
var symbol: String {
|
||||
switch state {
|
||||
case "running": "checkmark.circle.fill"
|
||||
case "failed", "required_missing": "xmark.circle.fill"
|
||||
case "stopped": "exclamationmark.triangle.fill"
|
||||
case "disabled": "pause.circle"
|
||||
case "not_installed": "minus.circle"
|
||||
default: "questionmark.circle"
|
||||
}
|
||||
}
|
||||
var color: Color {
|
||||
switch state {
|
||||
case "running": .green
|
||||
case "failed", "required_missing": .red
|
||||
case "stopped": .orange
|
||||
default: .secondary
|
||||
}
|
||||
}
|
||||
var title: String {
|
||||
switch state {
|
||||
case "running": "Running"
|
||||
case "failed": "Failed"
|
||||
case "stopped": "Stopped"
|
||||
case "disabled": "Disabled"
|
||||
case "not_installed": "Not installed"
|
||||
case "required_missing": "Required — missing"
|
||||
default: "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerProfile: Identifiable, Codable, Equatable {
|
||||
@@ -31,6 +72,33 @@ struct ServerProfile: Identifiable, Codable, Equatable {
|
||||
var keyFile: String = ""
|
||||
var remoteCommand: String = "/usr/local/bin/pegasus-monitor-health-json"
|
||||
var enabled: Bool = true
|
||||
var requiredServices: [String] = []
|
||||
var discoverServices: Bool = true
|
||||
|
||||
// Preserve profiles saved by 1.1, which did not include service preferences.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, host, user, port, keyFile, remoteCommand, enabled, requiredServices, discoverServices
|
||||
}
|
||||
init(id: UUID = UUID(), name: String = "New Server", host: String = "", user: String = "", port: String = "22",
|
||||
keyFile: String = "", remoteCommand: String = "/usr/local/bin/pegasus-monitor-health-json",
|
||||
enabled: Bool = true, requiredServices: [String] = [], discoverServices: Bool = true) {
|
||||
self.id = id; self.name = name; self.host = host; self.user = user; self.port = port
|
||||
self.keyFile = keyFile; self.remoteCommand = remoteCommand; self.enabled = enabled
|
||||
self.requiredServices = requiredServices; self.discoverServices = discoverServices
|
||||
}
|
||||
init(from decoder: Decoder) throws {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try c.decode(UUID.self, forKey: .id)
|
||||
name = try c.decode(String.self, forKey: .name)
|
||||
host = try c.decode(String.self, forKey: .host)
|
||||
user = try c.decode(String.self, forKey: .user)
|
||||
port = try c.decode(String.self, forKey: .port)
|
||||
keyFile = try c.decode(String.self, forKey: .keyFile)
|
||||
remoteCommand = try c.decode(String.self, forKey: .remoteCommand)
|
||||
enabled = try c.decode(Bool.self, forKey: .enabled)
|
||||
requiredServices = try c.decodeIfPresent([String].self, forKey: .requiredServices) ?? []
|
||||
discoverServices = try c.decodeIfPresent(Bool.self, forKey: .discoverServices) ?? true
|
||||
}
|
||||
|
||||
static func legacy() -> ServerProfile {
|
||||
let defaults = UserDefaults.standard
|
||||
@@ -84,6 +152,47 @@ final class ServerMonitor: ObservableObject {
|
||||
var selected: ServerProfile? { servers.first(where: { $0.id == selectedID }) }
|
||||
var selectedResult: ServerResult? { selectedID.flatMap { results[$0] } }
|
||||
var report: HealthReport? { selectedResult?.report }
|
||||
var selectedServices: [DiscoveredService] {
|
||||
guard let profile = selected else { return [] }
|
||||
return discoveredServices(for: profile.id).filter { profile.requiredServices.contains($0.name) || (profile.discoverServices && $0.state != "not_installed") }
|
||||
}
|
||||
func discoveredServices(for id: UUID) -> [DiscoveredService] {
|
||||
guard let profile = servers.first(where: { $0.id == id }) else { return [] }
|
||||
let discovered = results[id]?.report?.services ?? []
|
||||
var byName = Dictionary(uniqueKeysWithValues: discovered.map { ($0.name, $0) })
|
||||
for name in profile.requiredServices where byName[name] == nil {
|
||||
byName[name] = DiscoveredService(name: name, state: "not_installed", detail: "Not reported by agent")
|
||||
}
|
||||
return byName.values.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
|
||||
}
|
||||
func serviceStatus(_ item: DiscoveredService, required: Bool) -> HealthStatus? {
|
||||
if required && !item.isHealthy { return .fail }
|
||||
if item.state == "failed" { return .fail }
|
||||
if item.state == "stopped" { return .warn }
|
||||
return nil
|
||||
}
|
||||
func applyServicePolicy(_ report: HealthReport, for profile: ServerProfile) -> HealthReport {
|
||||
let services = report.services ?? []
|
||||
// If an older agent has no service discovery, do not invent a healthy state.
|
||||
// Explicit required services with no reported data are a failure.
|
||||
let known = Dictionary(uniqueKeysWithValues: services.map { ($0.name, $0) })
|
||||
var extra: [HealthCheck] = []
|
||||
for required in Set(profile.requiredServices) {
|
||||
let item = known[required]
|
||||
if item?.isHealthy != true {
|
||||
let description = item?.title ?? "Not reported by monitoring agent"
|
||||
extra.append(HealthCheck(name: "Required: \(required)", status: .fail, detail: description, section: "Required services"))
|
||||
}
|
||||
}
|
||||
let existing = Set(report.checks.map { $0.id })
|
||||
extra.removeAll { existing.contains($0.id) }
|
||||
guard !extra.isEmpty else { return report }
|
||||
let checks = report.checks + extra
|
||||
let counts = HealthCounts(ok: report.counts.ok, warn: report.counts.warn, fail: report.counts.fail + extra.count)
|
||||
let groups = report.groups + [HealthGroup(name: "Required services", status: .fail, missing: extra.map(\.name))]
|
||||
return HealthReport(schema: report.schema, host: report.host, generatedAt: report.generatedAt, overall: .fail,
|
||||
counts: counts, groups: groups, checks: checks, diagnostics: report.diagnostics, services: report.services)
|
||||
}
|
||||
var status: HealthStatus {
|
||||
let statuses = servers.filter(\.enabled).map { results[$0.id]?.status ?? .warn }
|
||||
if statuses.contains(.fail) { return .fail }
|
||||
@@ -106,6 +215,7 @@ final class ServerMonitor: ObservableObject {
|
||||
func save(_ profile: ServerProfile) {
|
||||
if let index = servers.firstIndex(where: { $0.id == profile.id }) { servers[index] = profile }
|
||||
else { servers.append(profile) }
|
||||
results.removeValue(forKey: profile.id)
|
||||
persist()
|
||||
select(profile.id)
|
||||
Task { await refresh(id: profile.id) }
|
||||
@@ -151,7 +261,8 @@ final class ServerMonitor: ObservableObject {
|
||||
do {
|
||||
let data = try await SSHRunner.run(host: server.host, user: server.user, port: server.port,
|
||||
keyFile: server.keyFile, remoteCommand: server.remoteCommand)
|
||||
let report = try JSONDecoder().decode(HealthReport.self, from: data)
|
||||
let decoded = try JSONDecoder().decode(HealthReport.self, from: data)
|
||||
let report = applyServicePolicy(decoded, for: server)
|
||||
guard report.schema == 1 else { throw SSHError.invalidResponse }
|
||||
state.report = report
|
||||
state.error = nil
|
||||
|
||||
@@ -7,6 +7,8 @@ struct SettingsView: View {
|
||||
@State private var validation = ""
|
||||
@State private var interval = 5
|
||||
@State private var notifications = true
|
||||
@State private var newRequiredService = ""
|
||||
@State private var showUninstalled = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
@@ -44,7 +46,9 @@ struct SettingsView: View {
|
||||
TextField("SSH key path (optional)", text: $draft.keyFile)
|
||||
TextField("Remote health command", text: $draft.remoteCommand)
|
||||
Toggle("Monitoring enabled", isOn: $draft.enabled)
|
||||
Toggle("Automatically discover services", isOn: $draft.discoverServices)
|
||||
}
|
||||
serviceConfiguration
|
||||
Text("Use the Pegasus adapter for Pegasus, or install server/remote-health-json on other Linux servers. SSH must work without interactive prompts. SSH known_hosts is enforced.")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
if !validation.isEmpty { Text(validation).foregroundStyle(.red).font(.caption) }
|
||||
@@ -65,6 +69,7 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
.frame(minWidth: 460)
|
||||
}
|
||||
.onAppear {
|
||||
interval = monitor.intervalMinutes
|
||||
@@ -75,15 +80,80 @@ struct SettingsView: View {
|
||||
.onChange(of: editingID) { _, newValue in
|
||||
if let id = newValue, let server = monitor.servers.first(where: { $0.id == id }) { draft = server }
|
||||
validation = ""
|
||||
newRequiredService = ""
|
||||
}
|
||||
.onChange(of: interval) { _, value in monitor.intervalMinutes = value }
|
||||
.onChange(of: notifications) { _, value in monitor.notifications = value }
|
||||
}
|
||||
|
||||
private var serviceConfiguration: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
Text("Service monitoring").font(.headline)
|
||||
Spacer()
|
||||
Button("Refresh services") {
|
||||
if let id = editingID { Task { await monitor.refresh(id: id) } }
|
||||
}.disabled(editingID == nil)
|
||||
}
|
||||
Text("Mark critical services as Required. Missing or non-running required services count as failures. Optional services not installed do not affect health.")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
Toggle("Show not installed services", isOn: $showUninstalled)
|
||||
.font(.caption)
|
||||
let discovered = editingID.map { monitor.discoveredServices(for: $0) } ?? []
|
||||
let display = discovered.filter { showUninstalled || $0.isInstalled || draft.requiredServices.contains($0.name) }
|
||||
if display.isEmpty {
|
||||
Text("No service discovery data yet. Refresh the server or add a required service below.")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
} else {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 6) {
|
||||
ForEach(display) { service in
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: service.symbol).foregroundStyle(service.color).frame(width: 20)
|
||||
Text(service.name).frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text(service.title).font(.caption).foregroundStyle(.secondary)
|
||||
Toggle("Required", isOn: Binding(
|
||||
get: { draft.requiredServices.contains(service.name) },
|
||||
set: { required in setRequired(service.name, required: required) }
|
||||
)).labelsHidden().help("Require \(service.name) to be running")
|
||||
}
|
||||
.font(.system(size: 12))
|
||||
}
|
||||
}
|
||||
}.frame(maxHeight: 190)
|
||||
}
|
||||
HStack {
|
||||
TextField("Add required service (e.g. mysql)", text: $newRequiredService)
|
||||
.onSubmit { addRequiredService() }
|
||||
Button("Add Required") { addRequiredService() }
|
||||
.disabled(newRequiredService.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
||||
}
|
||||
if !draft.requiredServices.isEmpty {
|
||||
Text("Required: \(draft.requiredServices.sorted().joined(separator: ", "))")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setRequired(_ name: String, required: Bool) {
|
||||
if required && !draft.requiredServices.contains(name) { draft.requiredServices.append(name) }
|
||||
if !required { draft.requiredServices.removeAll { $0 == name } }
|
||||
}
|
||||
private func addRequiredService() {
|
||||
let name = newRequiredService.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !name.isEmpty, name.range(of: #"^[a-zA-Z0-9@_.-]+$"#, options: .regularExpression) != nil else {
|
||||
validation = "Use a valid systemd service name (letters, digits, @, _, -, or .)."
|
||||
return
|
||||
}
|
||||
setRequired(name, required: true)
|
||||
newRequiredService = ""
|
||||
validation = ""
|
||||
}
|
||||
private func newProfile() {
|
||||
editingID = nil
|
||||
draft = ServerProfile()
|
||||
validation = ""
|
||||
newRequiredService = ""
|
||||
}
|
||||
private func save() {
|
||||
guard !draft.name.trimmingCharacters(in: .whitespaces).isEmpty,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Pegasus Monitor 1.2.0 (build 3)
|
||||
|
||||
## Changes
|
||||
- Generic Linux agent reports discovered systemd service states: running, stopped, failed, disabled, not installed, and unknown.
|
||||
- Optional services not installed do not count as failures.
|
||||
- Per-server **Required** service toggles; any required service not reported as running is a failure.
|
||||
- Service list in the menu bar, and discovery controls in the independent Settings window.
|
||||
- Required services persist in existing server profiles; profiles from 1.1 migrate without losing connections.
|
||||
- Manually add required units if no agent discovery has been performed.
|
||||
- Existing Pegasus specialised agent and its health checks remain supported (service discovery requires updated generic agent).
|
||||
|
||||
## Deploy updated Linux agent
|
||||
|
||||
From the macOS project folder, replace `mag@REMOTE_HOST` with the SSH login for the target server:
|
||||
|
||||
```sh
|
||||
scp server/remote-health-json mag@REMOTE_HOST:/tmp/pegasus-monitor-health-json
|
||||
ssh mag@REMOTE_HOST 'sudo install -m 755 /tmp/pegasus-monitor-health-json /usr/local/bin/pegasus-monitor-health-json'
|
||||
ssh mag@REMOTE_HOST '/usr/local/bin/pegasus-monitor-health-json'
|
||||
```
|
||||
|
||||
In the Settings window, point the server's remote command to `/usr/local/bin/pegasus-monitor-health-json`, refresh services and save required services. Keep Pegasus's original `sudo -n /usr/local/bin/pegasus-health-json` for its 72 specialised checks; it does not report the new per-service inventory.
|
||||
|
||||
## Safety and expected behaviour
|
||||
|
||||
The Linux health agent is read-only; it does not install, start or stop services. Service discovery uses `systemctl show` on an allowlisted collection of commonly-used units. `unknown` indicates discovery could not establish a state and is **not** treated as installed or running. Required services that are missing, disabled, stopped, failed, unknown or unreported count as failures.
|
||||
|
||||
Apple notarization is only attempted by the release script on macOS with a valid configured `orion-notary` profile. No notarized DMG is included here.
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generic read-only Linux health adapter for Pegasus Monitor 1.1.
|
||||
"""Generic read-only Linux health adapter for Pegasus Monitor 1.2.
|
||||
Install as /usr/local/bin/pegasus-monitor-health-json and run as an ordinary user.
|
||||
No third-party Python packages required.
|
||||
"""
|
||||
@@ -64,13 +64,48 @@ if shutil.which('docker'):
|
||||
else:
|
||||
check('Docker', 'Docker access', 'warn', 'Docker is installed but unavailable to this SSH user')
|
||||
|
||||
for service in ('nginx', 'mysql', 'postgresql', 'redis-server', 'gitea'):
|
||||
result = run(['systemctl', 'is-enabled', service])
|
||||
if not result or result.returncode != 0:
|
||||
continue
|
||||
active = run(['systemctl', 'is-active', service])
|
||||
working = active is not None and active.stdout.strip() == 'active'
|
||||
check('Services', service, 'ok' if working else 'fail', 'active' if working else 'inactive')
|
||||
# Discover common services without assuming they are installed or enabled.
|
||||
# Each state is explicit so optional missing software never counts as a failure.
|
||||
SERVICES = (
|
||||
('nginx', 'nginx'), ('mysql', 'mysql'), ('mariadb', 'mariadb'),
|
||||
('postgresql', 'postgresql'), ('redis-server', 'redis-server'),
|
||||
('gitea', 'gitea'), ('docker', 'docker'), ('containerd', 'containerd'),
|
||||
('php8.3-fpm', 'php8.3-fpm'), ('php8.4-fpm', 'php8.4-fpm'),
|
||||
('php8.5-fpm', 'php8.5-fpm'), ('ssh', 'ssh'), ('cloudflared', 'cloudflared'),
|
||||
('cockpit.socket', 'cockpit.socket'), ('libvirtd', 'libvirtd'),
|
||||
)
|
||||
services = []
|
||||
for display, unit in SERVICES:
|
||||
full_unit = unit if unit.endswith(('.service', '.socket')) else unit + '.service'
|
||||
result = run(['systemctl', 'show', full_unit, '--no-pager',
|
||||
'--property=LoadState', '--property=ActiveState',
|
||||
'--property=UnitFileState'], timeout=5)
|
||||
if result is None or not result.stdout.strip():
|
||||
state, detail = 'unknown', 'systemctl unavailable'
|
||||
else:
|
||||
properties = dict(line.split('=', 1) for line in result.stdout.splitlines() if '=' in line)
|
||||
load = properties.get('LoadState', 'not-found')
|
||||
active = properties.get('ActiveState', 'unknown')
|
||||
unit_state = properties.get('UnitFileState', 'unknown')
|
||||
detail = f'Load: {load}; active: {active}; enablement: {unit_state}'
|
||||
if load == 'not-found':
|
||||
state = 'not_installed'
|
||||
elif active == 'active':
|
||||
state = 'running'
|
||||
elif active == 'failed':
|
||||
state = 'failed'
|
||||
elif unit_state in ('disabled', 'masked'):
|
||||
state = 'disabled'
|
||||
elif active in ('inactive', 'deactivating'):
|
||||
state = 'stopped'
|
||||
else:
|
||||
state = 'unknown'
|
||||
services.append(dict(name=display, state=state, detail=detail))
|
||||
# Uninstalled/disabled services are informational, not warnings or failures.
|
||||
if state == 'failed':
|
||||
check('Services', display, 'fail', detail)
|
||||
elif state == 'stopped':
|
||||
check('Services', display, 'warn', detail)
|
||||
|
||||
groups = []
|
||||
for section in dict.fromkeys(c['section'] for c in checks):
|
||||
@@ -80,4 +115,4 @@ for section in dict.fromkeys(c['section'] for c in checks):
|
||||
counts = {s: sum(c['status'] == s for c in checks) for s in ('ok', 'warn', 'fail')}
|
||||
overall = 'fail' if counts['fail'] else 'warn' if counts['warn'] else 'ok'
|
||||
print(json.dumps(dict(schema=1, host=host, generatedAt=datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
overall=overall, counts=counts, groups=groups, checks=checks, diagnostics=None)))
|
||||
overall=overall, counts=counts, groups=groups, checks=checks, diagnostics=None, services=services)))
|
||||
|
||||
Reference in New Issue
Block a user