diff --git a/pkg/deviceagent/tray/dialog_darwin.go b/pkg/deviceagent/tray/dialog_darwin.go index 1b4ef3179..5e94654f7 100644 --- a/pkg/deviceagent/tray/dialog_darwin.go +++ b/pkg/deviceagent/tray/dialog_darwin.go @@ -39,3 +39,16 @@ func showAbout(version string) { ) _ = exec.Command(path, "-e", script).Run() } + +func showEnrollmentError(message string) { + path, ok := osascriptPath() + if !ok { + return + } + + script := fmt.Sprintf( + `display alert "Probo Device Posture Agent" message %q buttons {"OK"} default button "OK"`, + message, + ) + _ = exec.Command(path, "-e", script).Run() +} diff --git a/pkg/deviceagent/tray/dialog_windows.go b/pkg/deviceagent/tray/dialog_windows.go index 56ad1410d..55c44ee92 100644 --- a/pkg/deviceagent/tray/dialog_windows.go +++ b/pkg/deviceagent/tray/dialog_windows.go @@ -33,3 +33,7 @@ func showAbout(version string) { ) nativeMessageBox("Probo Device Posture Agent", message, mbOK|mbIconInformation) } + +func showEnrollmentError(message string) { + nativeMessageBox("Probo Device Posture Agent", message, mbOK|mbIconError) +} diff --git a/pkg/deviceagent/tray/enroll.go b/pkg/deviceagent/tray/enroll.go index f6f3b1985..5a31f87ef 100644 --- a/pkg/deviceagent/tray/enroll.go +++ b/pkg/deviceagent/tray/enroll.go @@ -24,7 +24,6 @@ package tray import ( "fmt" - "os" "go.probo.inc/probo/pkg/deviceagent" ) @@ -32,12 +31,12 @@ import ( func openConsoleEnroll(serverURL string) { enrollURL, err := deviceagent.ConsoleEnrollURL(serverURL) if err != nil { - fmt.Fprintf(os.Stderr, "cannot open enrollment page: %v\n", err) + showEnrollmentError(fmt.Sprintf("Cannot open enrollment page: %v", err)) return } if err := openBrowser(enrollURL); err != nil { - fmt.Fprintf(os.Stderr, "cannot open browser: %v\n", err) + showEnrollmentError(fmt.Sprintf("Cannot open browser: %v", err)) } } @@ -49,7 +48,7 @@ func openSelfHostedEnroll() { normalized, err := deviceagent.NormalizeServerURL(hostname) if err != nil { - fmt.Fprintf(os.Stderr, "invalid hostname: %v\n", err) + showEnrollmentError(fmt.Sprintf("Invalid hostname: %v", err)) return } diff --git a/pkg/deviceagent/tray/icon.png b/pkg/deviceagent/tray/icon.png index 3de3217f2..0b0a06656 100644 Binary files a/pkg/deviceagent/tray/icon.png and b/pkg/deviceagent/tray/icon.png differ diff --git a/pkg/deviceagent/tray/iconTemplate.png b/pkg/deviceagent/tray/iconTemplate.png index 551e840e2..0b0a06656 100644 Binary files a/pkg/deviceagent/tray/iconTemplate.png and b/pkg/deviceagent/tray/iconTemplate.png differ diff --git a/pkg/deviceagent/tray/icons.go b/pkg/deviceagent/tray/icons.go index bb2b96a2f..b86c3f858 100644 --- a/pkg/deviceagent/tray/icons.go +++ b/pkg/deviceagent/tray/icons.go @@ -29,3 +29,12 @@ var iconData []byte //go:embed iconTemplate.png var iconTemplateData []byte + +//go:embed status_connected.png +var statusConnectedIconData []byte + +//go:embed status_unavailable.png +var statusUnavailableIconData []byte + +//go:embed status_enrollment.png +var statusEnrollmentIconData []byte diff --git a/pkg/deviceagent/tray/messagebox_windows.go b/pkg/deviceagent/tray/messagebox_windows.go index 8ef49e10b..e520e344f 100644 --- a/pkg/deviceagent/tray/messagebox_windows.go +++ b/pkg/deviceagent/tray/messagebox_windows.go @@ -31,6 +31,7 @@ import ( const ( mbOK = 0x00000000 mbIconInformation = 0x00000040 + mbIconError = 0x00000010 ) var ( diff --git a/pkg/deviceagent/tray/status_connected.png b/pkg/deviceagent/tray/status_connected.png new file mode 100644 index 000000000..063bc7e07 Binary files /dev/null and b/pkg/deviceagent/tray/status_connected.png differ diff --git a/pkg/deviceagent/tray/status_enrollment.png b/pkg/deviceagent/tray/status_enrollment.png new file mode 100644 index 000000000..d53b9d613 Binary files /dev/null and b/pkg/deviceagent/tray/status_enrollment.png differ diff --git a/pkg/deviceagent/tray/status_unavailable.png b/pkg/deviceagent/tray/status_unavailable.png new file mode 100644 index 000000000..a2bd61474 Binary files /dev/null and b/pkg/deviceagent/tray/status_unavailable.png differ diff --git a/pkg/deviceagent/tray/tray_supported.go b/pkg/deviceagent/tray/tray_supported.go index 887978b7f..b64db7d32 100644 --- a/pkg/deviceagent/tray/tray_supported.go +++ b/pkg/deviceagent/tray/tray_supported.go @@ -3,7 +3,7 @@ // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// to use, copy, modify, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // @@ -23,7 +23,6 @@ package tray import ( - "fmt" "os" "os/signal" "sync" @@ -87,25 +86,35 @@ func (m enrollmentMenu) hide() { func onReady(opts Options, done <-chan struct{}) { setTrayIcons() - - systray.SetTitle("Probo") - - enrollMenu := setupEnrollmentMenu(opts, done) + setTrayTitle() connectedItem := systray.AddMenuItem("Connected", "Device is enrolled and reporting") + connectedItem.SetIcon(statusConnectedIconData) connectedItem.Disable() + connectedItem.Hide() + + enrollmentRequiredItem := systray.AddMenuItem( + "Enrollment required", + "Device is not enrolled yet", + ) + enrollmentRequiredItem.SetIcon(statusEnrollmentIconData) + enrollmentRequiredItem.Disable() + enrollmentRequiredItem.Hide() statusUnavailableItem := systray.AddMenuItem( "Status unavailable", "Cannot read enrollment status", ) + statusUnavailableItem.SetIcon(statusUnavailableIconData) statusUnavailableItem.Disable() statusUnavailableItem.Hide() systray.AddSeparator() + enrollMenu := setupEnrollmentMenu(opts, done) + aboutItem := systray.AddMenuItem( - fmt.Sprintf("About probo-agent %s", opts.Version), + "About Probo Device Posture Agent…", "Probo device posture agent", ) @@ -114,6 +123,7 @@ func onReady(opts Options, done <-chan struct{}) { if err != nil { enrollMenu.hide() connectedItem.Hide() + enrollmentRequiredItem.Hide() statusUnavailableItem.Show() systray.SetTooltip("Probo Device Posture Agent — Status unavailable") @@ -124,11 +134,13 @@ func onReady(opts Options, done <-chan struct{}) { if enrolled { enrollMenu.hide() + enrollmentRequiredItem.Hide() connectedItem.Show() systray.SetTooltip("Probo Device Posture Agent — Connected") } else { enrollMenu.show() connectedItem.Hide() + enrollmentRequiredItem.Show() systray.SetTooltip("Probo Device Posture Agent — Enrollment required") } } @@ -173,7 +185,7 @@ func setupEnrollmentMenu(opts Options, done <-chan struct{}) enrollmentMenu { return enrollmentMenu{items: []*systray.MenuItem{enrollItem}} } - enrollRoot := systray.AddMenuItem("Enroll in browser", enrollTooltip) + enrollRoot := systray.AddMenuItem("Enroll via…", enrollTooltip) usItem := enrollRoot.AddSubMenuItem( "United States (us.probo.com)", diff --git a/pkg/deviceagent/tray/tray_title_darwin.go b/pkg/deviceagent/tray/tray_title_darwin.go new file mode 100644 index 000000000..c32022ff7 --- /dev/null +++ b/pkg/deviceagent/tray/tray_title_darwin.go @@ -0,0 +1,29 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//go:build darwin && cgo + +package tray + +import "fyne.io/systray" + +func setTrayTitle() { + systray.SetTitle("") +} diff --git a/pkg/deviceagent/tray/tray_title_windows.go b/pkg/deviceagent/tray/tray_title_windows.go new file mode 100644 index 000000000..db722171f --- /dev/null +++ b/pkg/deviceagent/tray/tray_title_windows.go @@ -0,0 +1,29 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//go:build windows + +package tray + +import "fyne.io/systray" + +func setTrayTitle() { + systray.SetTitle("Probo") +}