mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-05 13:09:07 +02:00
[client] UI refactor (#6069)
Refactor UI --------- Co-authored-by: Eduard Gert <kontakt@eduardgert.de> Co-authored-by: braginini <bangvalo@gmail.com> Co-authored-by: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: riccardom <riccardomanfrin@gmail.com>
This commit is contained in:
co-authored by
Eduard Gert
braginini
Pascal Fischer
Claude
riccardom
parent
679c7182a4
commit
8b7ce337d8
@@ -0,0 +1,137 @@
|
||||
//go:build !android && !ios && !freebsd && !js
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/ui/authsession"
|
||||
"github.com/netbirdio/netbird/client/ui/services"
|
||||
)
|
||||
|
||||
// onSystemEvent fires an OS notification for daemon SystemEvents that carry a
|
||||
// user-facing message. Gated by the "Notifications" toggle; critical events bypass it.
|
||||
func (t *Tray) onSystemEvent(ev *application.CustomEvent) {
|
||||
se, ok := ev.Data.(services.SystemEvent)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// config_changed carries no UserMessage, so handle it before the message gate below.
|
||||
if se.Category == "system" && se.Metadata[proto.MetadataTypeKey] == proto.MetadataTypeConfigChanged {
|
||||
log.Infof("config_changed event received (source=%s); refreshing tray restrictions", se.Metadata[proto.MetadataSourceKey])
|
||||
go t.refreshRestrictions()
|
||||
go t.loadConfig()
|
||||
// MDM gets a localised toast here; the daemon's English "policy_applied"
|
||||
// event is suppressed in shouldSkipSystemEvent. Other sources stay silent.
|
||||
if se.Metadata[proto.MetadataSourceKey] == proto.MetadataSourceMDM {
|
||||
t.profileMu.Lock()
|
||||
enabled := t.notificationsEnabled
|
||||
t.profileMu.Unlock()
|
||||
if enabled {
|
||||
t.notify(
|
||||
t.loc.T("notify.mdm.policyApplied.title"),
|
||||
t.loc.T("notify.mdm.policyApplied.body"),
|
||||
notifyIDMDMPolicy,
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
// Session-warning and deadline-rejected events build their body locally from
|
||||
// metadata; every other event needs a UserMessage.
|
||||
isSessionWarning := se.Metadata[authsession.MetaWarning] == "true"
|
||||
isDeadlineRejected := se.Metadata[authsession.MetaDeadlineRejected] != ""
|
||||
if !isSessionWarning && !isDeadlineRejected && se.UserMessage == "" {
|
||||
return
|
||||
}
|
||||
if shouldSkipSystemEvent(se) {
|
||||
return
|
||||
}
|
||||
|
||||
critical := strings.EqualFold(se.Severity, services.SeverityCritical)
|
||||
t.profileMu.Lock()
|
||||
enabled := t.notificationsEnabled
|
||||
t.profileMu.Unlock()
|
||||
if !enabled && !critical {
|
||||
return
|
||||
}
|
||||
|
||||
// Session-warning events route via stable metadata flags rather than
|
||||
// category/severity so a daemon-side reword still lands here. Final warning
|
||||
// auto-opens the SessionExpiration dialog with no notification (the dialog is
|
||||
// the last-chance reminder; doubling up would be noise).
|
||||
if isDeadlineRejected {
|
||||
t.notify(
|
||||
t.loc.T("notify.sessionDeadlineRejected.title"),
|
||||
t.loc.T("notify.sessionDeadlineRejected.body"),
|
||||
notifyIDSessionExpired,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if se.Metadata != nil && se.Metadata[authsession.MetaWarning] == "true" {
|
||||
if se.Metadata[authsession.MetaFinal] == "true" {
|
||||
t.openSessionExpiration()
|
||||
return
|
||||
}
|
||||
t.notifySessionWarning(
|
||||
t.loc.T("notify.sessionWarning.title"),
|
||||
t.buildSessionWarningBody(se.Metadata),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
body := se.UserMessage
|
||||
if id := se.Metadata["id"]; id != "" {
|
||||
body += fmt.Sprintf(" ID: %s", id)
|
||||
}
|
||||
t.notify(eventTitle(se), body, notifyIDEvent+se.ID)
|
||||
}
|
||||
|
||||
// eventTitle composes a notification title, e.g. "Critical: DNS", "Warning: Authentication".
|
||||
func eventTitle(e services.SystemEvent) string {
|
||||
prefix := titleCase(e.Severity)
|
||||
if prefix == "" {
|
||||
prefix = "Info"
|
||||
}
|
||||
category := titleCase(e.Category)
|
||||
if category == "" {
|
||||
category = "System"
|
||||
}
|
||||
return prefix + ": " + category
|
||||
}
|
||||
|
||||
func titleCase(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
}
|
||||
|
||||
// shouldSkipSystemEvent reports whether a daemon SystemEvent must not surface as
|
||||
// a tray notification:
|
||||
// - update-available announcements (trayUpdater emits its own)
|
||||
// - install-progress signals (consumed by the install-progress window)
|
||||
// - the ::/0 partner of an exit-node default route (0.0.0.0/0 already toasted)
|
||||
func shouldSkipSystemEvent(se services.SystemEvent) bool {
|
||||
// "policy_applied" carries a hardcoded English message; the localised toast
|
||||
// fires on the paired config_changed (source=mdm) event instead.
|
||||
if se.Metadata[proto.MetadataTypeKey] == proto.MetadataTypePolicyApplied {
|
||||
return true
|
||||
}
|
||||
if _, isUpdate := se.Metadata["new_version_available"]; isUpdate {
|
||||
return true
|
||||
}
|
||||
if _, isProgress := se.Metadata["progress_window"]; isProgress {
|
||||
return true
|
||||
}
|
||||
if se.Category == "network" && se.Metadata["network"] == "::/0" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user