Port VNC settings and connection-approval prompt to the Wails UI

This commit is contained in:
Viktor Liu
2026-07-12 18:46:41 +02:00
parent f0818f328c
commit 6c3dac6fa0
19 changed files with 932 additions and 1 deletions
+36
View File
@@ -0,0 +1,36 @@
//go:build !android && !ios && !freebsd && !js
package services
import (
"context"
"github.com/netbirdio/netbird/client/proto"
)
// Approval forwards the user's decision on a pending inbound-connection
// approval prompt to the daemon. The daemon pushes the prompt as a SystemEvent
// with category APPROVAL; the dialog calls Respond with the same request id to
// unblock whichever subsystem (VNC, SSH, ...) is waiting.
type Approval struct {
conn DaemonConn
}
func NewApproval(conn DaemonConn) *Approval {
return &Approval{conn: conn}
}
// Respond delivers the accept/deny decision for requestID. viewOnly is only
// meaningful when accept is true and the subsystem supports a read-only grant.
func (a *Approval) Respond(ctx context.Context, requestID string, accept, viewOnly bool) error {
cli, err := a.conn.Client()
if err != nil {
return err
}
_, err = cli.RespondApproval(ctx, &proto.RespondApprovalRequest{
RequestId: requestID,
Accept: accept,
ViewOnly: viewOnly,
})
return err
}
+9 -1
View File
@@ -24,7 +24,7 @@ type MDMFields struct {
DisableMetricsCollection bool `json:"disableMetricsCollection"`
SplitTunnelMode bool `json:"splitTunnelMode"`
SplitTunnelApps bool `json:"splitTunnelApps"`
DisableAdvancedView bool `json:"disableAdvancedView"`
DisableAdvancedView bool `json:"disableAdvancedView"`
}
type Features struct {
@@ -54,6 +54,8 @@ type Config struct {
MTU int64 `json:"mtu"`
DisableAutoConnect bool `json:"disableAutoConnect"`
ServerSSHAllowed bool `json:"serverSshAllowed"`
ServerVNCAllowed bool `json:"serverVncAllowed"`
DisableVNCApproval bool `json:"disableVncApproval"`
RosenpassEnabled bool `json:"rosenpassEnabled"`
RosenpassPermissive bool `json:"rosenpassPermissive"`
DisableNotifications bool `json:"disableNotifications"`
@@ -85,6 +87,8 @@ type SetConfigParams struct {
PreSharedKey *string `json:"preSharedKey,omitempty"`
DisableAutoConnect *bool `json:"disableAutoConnect,omitempty"`
ServerSSHAllowed *bool `json:"serverSshAllowed,omitempty"`
ServerVNCAllowed *bool `json:"serverVncAllowed,omitempty"`
DisableVNCApproval *bool `json:"disableVncApproval,omitempty"`
RosenpassEnabled *bool `json:"rosenpassEnabled,omitempty"`
RosenpassPermissive *bool `json:"rosenpassPermissive,omitempty"`
DisableNotifications *bool `json:"disableNotifications,omitempty"`
@@ -135,6 +139,8 @@ func (s *Settings) GetConfig(ctx context.Context, p ConfigParams) (Config, error
MTU: resp.GetMtu(),
DisableAutoConnect: resp.GetDisableAutoConnect(),
ServerSSHAllowed: resp.GetServerSSHAllowed(),
ServerVNCAllowed: resp.GetServerVNCAllowed(),
DisableVNCApproval: resp.GetDisableVNCApproval(),
RosenpassEnabled: resp.GetRosenpassEnabled(),
RosenpassPermissive: resp.GetRosenpassPermissive(),
DisableNotifications: resp.GetDisableNotifications(),
@@ -170,6 +176,8 @@ func (s *Settings) SetConfig(ctx context.Context, p SetConfigParams) error {
OptionalPreSharedKey: p.PreSharedKey,
DisableAutoConnect: p.DisableAutoConnect,
ServerSSHAllowed: p.ServerSSHAllowed,
ServerVNCAllowed: p.ServerVNCAllowed,
DisableVNCApproval: p.DisableVNCApproval,
RosenpassEnabled: p.RosenpassEnabled,
RosenpassPermissive: p.RosenpassPermissive,
DisableNotifications: p.DisableNotifications,
+78
View File
@@ -106,6 +106,7 @@ type WindowManager struct {
settings *application.WebviewWindow
browserLogin *application.WebviewWindow
sessionExpiration *application.WebviewWindow
approval *application.WebviewWindow
installProgress *application.WebviewWindow
welcome *application.WebviewWindow
errorDialog *application.WebviewWindow
@@ -279,6 +280,58 @@ func (s *WindowManager) CloseSessionExpiration() {
}
}
// ApprovalRequest carries the daemon-supplied metadata for an inbound-connection
// approval prompt to the dialog window as query params. Kind, RequestID and
// ExpiresAt are daemon-issued; the rest are remote-influenced and shown so the
// user can vet who is connecting.
type ApprovalRequest struct {
RequestID string
Kind string
Initiator string
PeerName string
SourceIP string
Username string
PeerPubKey string
ExpiresAt string
}
// OpenApproval shows the inbound-connection approval prompt on the cursor's
// display. Singleton, destroyed on close: a second request replaces the window,
// and the superseded request auto-denies on the daemon's deadline.
func (s *WindowManager) OpenApproval(req ApprovalRequest) {
s.mu.Lock()
defer s.mu.Unlock()
startURL := approvalDialogURL(req)
if s.approval == nil {
opts := DialogWindowOptions("approval", s.title("window.title.approval"), startURL, s.linuxIcon)
opts.Height = 380
opts.Screen = s.getScreenBasedOnCursorPosition()
opts.InitialPosition = application.WindowCentered
s.approval = s.app.Window.NewWithOptions(opts)
s.approval.OnWindowEvent(events.Common.WindowClosing, func(_ *application.WindowEvent) {
s.mu.Lock()
s.approval = nil
s.mu.Unlock()
})
s.centerOnCursorScreen(s.approval)
return
}
s.approval.SetURL(startURL)
s.centerOnCursorScreen(s.approval)
s.approval.Show()
s.approval.Focus()
}
func (s *WindowManager) CloseApproval() {
s.mu.Lock()
w := s.approval
s.approval = nil
s.mu.Unlock()
if w != nil {
w.Close()
}
}
// OpenInstallProgress shows the install-progress window and hides the rest for the duration
// (restored on close). It owns its own result polling since the daemon restarts mid-install.
func (s *WindowManager) OpenInstallProgress(version string) {
@@ -572,5 +625,30 @@ func errorDialogURL(title, message string) string {
return startURL
}
// approvalDialogURL builds the approval window's start URL with the request
// metadata as escaped query params. Empty fields are omitted so the dialog
// renders only the rows it has values for.
func approvalDialogURL(req ApprovalRequest) string {
q := url.Values{}
set := func(k, v string) {
if v != "" {
q.Set(k, v)
}
}
set("request_id", req.RequestID)
set("kind", req.Kind)
set("initiator", req.Initiator)
set("peer_name", req.PeerName)
set("source_ip", req.SourceIP)
set("username", req.Username)
set("peer_pubkey", req.PeerPubKey)
set("expires_at", req.ExpiresAt)
startURL := "/#/dialog/approval"
if enc := q.Encode(); enc != "" {
startURL += "?" + enc
}
return startURL
}
// u32ptr returns a pointer to v, for the optional *uint32 Wails theme fields.
func u32ptr(v uint32) *uint32 { return &v }