mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-16 11:39:57 +00:00
Port VNC settings and connection-approval prompt to the Wails UI
This commit is contained in:
@@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client";
|
||||
import "./globals.css";
|
||||
import { HashRouter, Navigate, Route, Routes } from "react-router-dom";
|
||||
import SessionExpirationDialog from "@/modules/session/SessionExpirationDialog.tsx";
|
||||
import ApprovalDialog from "@/modules/approval/ApprovalDialog.tsx";
|
||||
import UpdateInProgressDialog from "@/modules/auto-update/UpdateInProgressDialog.tsx";
|
||||
import WelcomeDialog from "@/modules/welcome/WelcomeDialog.tsx";
|
||||
import ErrorDialog from "@/modules/error/ErrorDialog.tsx";
|
||||
@@ -48,6 +49,7 @@ Promise.all([
|
||||
path={"session-expiration"}
|
||||
element={<SessionExpirationDialog />}
|
||||
/>
|
||||
<Route path={"approval"} element={<ApprovalDialog />} />
|
||||
<Route path={"welcome"} element={<WelcomeDialog />} />
|
||||
<Route path={"error"} element={<ErrorDialog />} />
|
||||
</Route>
|
||||
|
||||
170
client/ui/frontend/src/modules/approval/ApprovalDialog.tsx
Normal file
170
client/ui/frontend/src/modules/approval/ApprovalDialog.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { MonitorIcon } from "lucide-react";
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { ConfirmDialog } from "@/components/dialog/ConfirmDialog";
|
||||
import { DialogActions } from "@/components/dialog/DialogActions";
|
||||
import { DialogHeading } from "@/components/dialog/DialogHeading";
|
||||
import { SquareIcon } from "@/components/SquareIcon";
|
||||
import { Approval, WindowManager } from "@bindings/services";
|
||||
import { useAutoSizeWindow } from "@/hooks/useAutoSizeWindow";
|
||||
|
||||
const WINDOW_WIDTH = 360;
|
||||
// Fallback window so a missing/unparseable expires_at can't leave the prompt open forever.
|
||||
const FALLBACK_SECONDS = 13;
|
||||
|
||||
// shortFingerprint groups a hex key as XXXX-XXXX-XXXX-XXXX (16 chars). Mirrors the
|
||||
// daemon's approval.ShortKeyFingerprint so the value matches an out-of-band reference.
|
||||
function shortFingerprint(hexKey: string): string {
|
||||
if (hexKey.length < 8) return "";
|
||||
const src = hexKey.slice(0, 16);
|
||||
return src.match(/.{1,4}/g)?.join("-") ?? src;
|
||||
}
|
||||
|
||||
type Row = { label: string; value: string; mono?: boolean };
|
||||
|
||||
export default function ApprovalDialog() {
|
||||
const { t } = useTranslation();
|
||||
const contentRef = useAutoSizeWindow<HTMLDivElement>(WINDOW_WIDTH);
|
||||
const [params] = useSearchParams();
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const requestID = params.get("request_id") ?? "";
|
||||
const kind = params.get("kind") ?? "";
|
||||
const initiator = params.get("initiator") ?? "";
|
||||
const peerName = params.get("peer_name") ?? "";
|
||||
const sourceIP = params.get("source_ip") ?? "";
|
||||
const username = params.get("username") ?? "";
|
||||
const peerPubKey = params.get("peer_pubkey") ?? "";
|
||||
const expiresAt = params.get("expires_at") ?? "";
|
||||
|
||||
const deadline = useMemo(() => {
|
||||
const parsed = Date.parse(expiresAt);
|
||||
return Number.isFinite(parsed) ? parsed : Date.now() + FALLBACK_SECONDS * 1000;
|
||||
}, [expiresAt]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
switch (kind) {
|
||||
case "vnc":
|
||||
return t("approval.title.vnc");
|
||||
case "ssh":
|
||||
return t("approval.title.ssh");
|
||||
default:
|
||||
return t("approval.title.default");
|
||||
}
|
||||
}, [kind, t]);
|
||||
|
||||
const rows = useMemo<Row[]>(() => {
|
||||
const out: Row[] = [];
|
||||
// The display name is dashboard-supplied and not cryptographically
|
||||
// asserted; the key fingerprint below IS, so show both.
|
||||
if (initiator) out.push({ label: t("approval.field.user"), value: initiator });
|
||||
const fp = shortFingerprint(peerPubKey);
|
||||
if (fp) out.push({ label: t("approval.field.keyFingerprint"), value: fp, mono: true });
|
||||
if (peerName) out.push({ label: t("approval.field.peer"), value: peerName });
|
||||
if (sourceIP && sourceIP !== peerName)
|
||||
out.push({ label: t("approval.field.sourceIp"), value: sourceIP, mono: true });
|
||||
if (username) out.push({ label: t("approval.field.osUser"), value: username });
|
||||
return out;
|
||||
}, [initiator, peerPubKey, peerName, sourceIP, username, t]);
|
||||
|
||||
const respond = useCallback(
|
||||
async (accept: boolean, viewOnly: boolean) => {
|
||||
if (busy) return;
|
||||
setBusy(true);
|
||||
try {
|
||||
if (requestID) {
|
||||
await Approval.Respond(requestID, accept, viewOnly);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("respond approval failed", e);
|
||||
} finally {
|
||||
WindowManager.CloseApproval().catch(console.error);
|
||||
}
|
||||
},
|
||||
[busy, requestID],
|
||||
);
|
||||
|
||||
const secondsLeft = () => Math.max(0, Math.ceil((deadline - Date.now()) / 1000));
|
||||
const [remaining, setRemaining] = useState(secondsLeft);
|
||||
const closedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
const id = globalThis.setInterval(() => {
|
||||
const left = secondsLeft();
|
||||
setRemaining(left);
|
||||
// On the deadline the daemon auto-denies; just close the prompt.
|
||||
if (left <= 0 && !closedRef.current) {
|
||||
closedRef.current = true;
|
||||
WindowManager.CloseApproval().catch(console.error);
|
||||
}
|
||||
}, 1000);
|
||||
return () => globalThis.clearInterval(id);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [deadline]);
|
||||
|
||||
const showViewOnly = kind === "vnc";
|
||||
|
||||
return (
|
||||
<ConfirmDialog ref={contentRef} aria-labelledby={"nb-approval-title"}>
|
||||
<SquareIcon icon={MonitorIcon} />
|
||||
|
||||
<DialogHeading id={"nb-approval-title"}>{title}</DialogHeading>
|
||||
|
||||
{rows.length > 0 && (
|
||||
<dl className={"w-full space-y-1 text-left text-sm"}>
|
||||
{rows.map((row) => (
|
||||
<div key={row.label} className={"flex justify-between gap-4"}>
|
||||
<dt className={"shrink-0 text-nb-gray-400"}>{row.label}</dt>
|
||||
<dd
|
||||
className={`min-w-0 truncate text-nb-gray-100 ${
|
||||
row.mono ? "font-mono" : ""
|
||||
}`}
|
||||
title={row.value}
|
||||
>
|
||||
{row.value}
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
)}
|
||||
|
||||
<div className={"text-sm tabular-nums text-nb-gray-400"} aria-live={"polite"}>
|
||||
{t("approval.countdown", { seconds: remaining })}
|
||||
</div>
|
||||
|
||||
<DialogActions>
|
||||
<Button
|
||||
autoFocus
|
||||
variant={"primary"}
|
||||
size={"md"}
|
||||
className={"w-full"}
|
||||
onClick={() => respond(true, false)}
|
||||
disabled={busy}
|
||||
>
|
||||
{t("approval.action.allow")}
|
||||
</Button>
|
||||
{showViewOnly && (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
size={"md"}
|
||||
className={"w-full"}
|
||||
onClick={() => respond(true, true)}
|
||||
disabled={busy}
|
||||
>
|
||||
{t("approval.action.allowViewOnly")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant={"danger"}
|
||||
size={"md"}
|
||||
className={"w-full"}
|
||||
onClick={() => respond(false, false)}
|
||||
disabled={busy}
|
||||
>
|
||||
{t("approval.action.deny")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</ConfirmDialog>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
BoltIcon,
|
||||
InfoIcon,
|
||||
LifeBuoyIcon,
|
||||
MonitorIcon,
|
||||
NetworkIcon,
|
||||
ShieldIcon,
|
||||
SlidersHorizontalIcon,
|
||||
@@ -63,6 +64,13 @@ export const SettingsNavigation = () => {
|
||||
title={t("settings.tabs.ssh")}
|
||||
/>
|
||||
)}
|
||||
{!features.disableUpdateSettings && (
|
||||
<VerticalTabs.Trigger
|
||||
value={"vnc"}
|
||||
icon={MonitorIcon}
|
||||
title={t("settings.tabs.vnc")}
|
||||
/>
|
||||
)}
|
||||
{!features.disableUpdateSettings && (
|
||||
<VerticalTabs.Trigger
|
||||
value={"advanced"}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { SettingsNetwork } from "@/modules/settings/SettingsNetwork.tsx";
|
||||
import { SettingsSecurity } from "@/modules/settings/SettingsSecurity.tsx";
|
||||
import { ProfilesTab } from "@/modules/profiles/ProfilesTab.tsx";
|
||||
import { SettingsSSH } from "@/modules/settings/SettingsSSH.tsx";
|
||||
import { SettingsVNC } from "@/modules/settings/SettingsVNC.tsx";
|
||||
import { SettingsAdvanced } from "@/modules/settings/SettingsAdvanced.tsx";
|
||||
import { SettingsTroubleshooting } from "@/modules/settings/SettingsTroubleshooting.tsx";
|
||||
import { SettingsAbout } from "@/modules/settings/SettingsAbout.tsx";
|
||||
@@ -26,6 +27,7 @@ const enum Tab {
|
||||
Security = "security",
|
||||
Profiles = "profiles",
|
||||
SSH = "ssh",
|
||||
VNC = "vnc",
|
||||
Advanced = "advanced",
|
||||
Troubleshooting = "troubleshooting",
|
||||
About = "about",
|
||||
@@ -37,6 +39,7 @@ const TAB_CONTENT: Record<Tab, ReactNode> = {
|
||||
[Tab.Security]: <SettingsSecurity />,
|
||||
[Tab.Profiles]: <ProfilesTab />,
|
||||
[Tab.SSH]: <SettingsSSH />,
|
||||
[Tab.VNC]: <SettingsVNC />,
|
||||
[Tab.Advanced]: <SettingsAdvanced />,
|
||||
[Tab.Troubleshooting]: <SettingsTroubleshooting />,
|
||||
[Tab.About]: <SettingsAbout />,
|
||||
@@ -55,6 +58,7 @@ export const SettingsPage = () => {
|
||||
[Tab.Security]: editable,
|
||||
[Tab.Profiles]: !features.disableProfiles,
|
||||
[Tab.SSH]: mdm.allowServerSSH ?? editable,
|
||||
[Tab.VNC]: editable,
|
||||
[Tab.Advanced]: editable,
|
||||
[Tab.Troubleshooting]: true,
|
||||
[Tab.About]: true,
|
||||
|
||||
32
client/ui/frontend/src/modules/settings/SettingsVNC.tsx
Normal file
32
client/ui/frontend/src/modules/settings/SettingsVNC.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FancyToggleSwitch from "@/components/switches/FancyToggleSwitch";
|
||||
import { SectionGroup } from "@/modules/settings/SettingsSection.tsx";
|
||||
import { useSettings } from "@/contexts/SettingsContext.tsx";
|
||||
|
||||
export function SettingsVNC() {
|
||||
const { t } = useTranslation();
|
||||
const { config, setField } = useSettings();
|
||||
const isVNCServerEnabled = config.serverVncAllowed;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionGroup title={t("settings.vnc.section.server")}>
|
||||
<FancyToggleSwitch
|
||||
value={config.serverVncAllowed}
|
||||
onChange={(v) => setField("serverVncAllowed", v)}
|
||||
label={t("settings.vnc.server.label")}
|
||||
helpText={t("settings.vnc.server.help")}
|
||||
/>
|
||||
</SectionGroup>
|
||||
|
||||
<SectionGroup title={t("settings.vnc.section.approval")} disabled={!isVNCServerEnabled}>
|
||||
<FancyToggleSwitch
|
||||
value={!config.disableVncApproval}
|
||||
onChange={(v) => setField("disableVncApproval", !v)}
|
||||
label={t("settings.vnc.approval.label")}
|
||||
helpText={t("settings.vnc.approval.help")}
|
||||
/>
|
||||
</SectionGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "Vorgang fehlgeschlagen."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Server"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Genehmigung"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "VNC-Server aktivieren"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "Den NetBird-VNC-Server auf diesem Host ausführen, damit autorisierte Peers den Bildschirm ansehen oder steuern können."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Verbindungsgenehmigung erforderlich"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Auf diesem Host eine Aufforderung anzeigen, die bestätigt werden muss, bevor eine eingehende VNC-Verbindung zugelassen wird."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Verbindungsanfrage"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "VNC-Verbindung zulassen?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "SSH-Verbindung zulassen?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "Eingehende Verbindung zulassen?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "Von Benutzer"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Schlüssel-Fingerabdruck"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "Über Peer"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "Quell-IP"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "Betriebssystem-Benutzer"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Automatische Ablehnung in {seconds}s"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Zulassen"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Zulassen (nur ansehen)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Ablehnen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,6 +683,10 @@
|
||||
"message": "SSH",
|
||||
"description": "Settings tab label: SSH. Acronym — keep as-is."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC",
|
||||
"description": "Settings tab label: VNC. Acronym — keep as-is."
|
||||
},
|
||||
"settings.tabs.advanced": {
|
||||
"message": "Advanced",
|
||||
"description": "Settings tab label: Advanced. Keep short."
|
||||
@@ -951,6 +955,30 @@
|
||||
"message": "Second(s)",
|
||||
"description": "Unit suffix shown after the JWT TTL number field. The '(s)' marks an optional plural."
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Server",
|
||||
"description": "Section heading: Server (VNC settings)."
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Approval",
|
||||
"description": "Section heading: Approval (VNC connection approval settings)."
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "Enable VNC Server",
|
||||
"description": "Toggle label: enable the embedded VNC server."
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "Run the NetBird VNC server on this host so authorized peers can view or control its screen.",
|
||||
"description": "Helper text for the VNC server toggle."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Require Connection Approval",
|
||||
"description": "Toggle label: prompt for approval before each inbound VNC connection."
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Show a prompt on this host that must be accepted before an incoming VNC connection is allowed.",
|
||||
"description": "Helper text for the VNC connection-approval toggle."
|
||||
},
|
||||
"settings.advanced.section.interface": {
|
||||
"message": "Interface",
|
||||
"description": "Section heading: Interface (network-interface settings)."
|
||||
@@ -1363,6 +1391,58 @@
|
||||
"message": "Session Expiring",
|
||||
"description": "OS window-chrome title for the session-expiration window."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Connection Request",
|
||||
"description": "OS window-chrome title for the inbound-connection approval window."
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "Allow VNC Connection?",
|
||||
"description": "Approval dialog heading for an inbound VNC connection."
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "Allow SSH Connection?",
|
||||
"description": "Approval dialog heading for an inbound SSH connection."
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "Allow Incoming Connection?",
|
||||
"description": "Approval dialog heading for an inbound connection of unknown kind."
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "From user",
|
||||
"description": "Approval dialog row label: the initiating user's display name."
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Key fingerprint",
|
||||
"description": "Approval dialog row label: the connecting peer's cryptographic key fingerprint."
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "Via peer",
|
||||
"description": "Approval dialog row label: the peer the connection arrives through."
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "Source IP",
|
||||
"description": "Approval dialog row label: the source IP address of the connection."
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "OS user",
|
||||
"description": "Approval dialog row label: the target operating-system user."
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Auto-deny in {seconds}s",
|
||||
"description": "Approval dialog countdown; {seconds} is the remaining whole seconds before the daemon auto-denies."
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Allow",
|
||||
"description": "Approval dialog button: allow the connection."
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Allow (view only)",
|
||||
"description": "Approval dialog button: allow the connection in view-only mode."
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Deny",
|
||||
"description": "Approval dialog button: deny the connection."
|
||||
},
|
||||
"window.title.updating": {
|
||||
"message": "Updating",
|
||||
"description": "OS window-chrome title for the update / install window."
|
||||
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "La operación falló."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Servidor"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Aprobación"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "Habilitar el servidor VNC"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "Ejecuta el servidor VNC de NetBird en este host para que los peers autorizados puedan ver o controlar su pantalla."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Requerir aprobación de conexión"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Mostrar en este host una solicitud que debe aceptarse antes de permitir una conexión VNC entrante."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Solicitud de conexión"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "¿Permitir la conexión VNC?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "¿Permitir la conexión SSH?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "¿Permitir la conexión entrante?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "Del usuario"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Huella de la clave"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "A través del peer"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "IP de origen"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "Usuario del SO"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Rechazo automático en {seconds}s"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Permitir"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Permitir (solo ver)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Denegar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "L’opération a échoué."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Serveur"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Approbation"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "Activer le serveur VNC"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "Exécuter le serveur VNC de NetBird sur cet hôte afin que les pairs autorisés puissent voir ou contrôler son écran."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Exiger l'approbation des connexions"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Afficher sur cet hôte une invite qui doit être acceptée avant d'autoriser une connexion VNC entrante."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Demande de connexion"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "Autoriser la connexion VNC ?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "Autoriser la connexion SSH ?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "Autoriser la connexion entrante ?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "De l'utilisateur"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Empreinte de clé"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "Via le pair"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "IP source"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "Utilisateur du système"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Refus automatique dans {seconds}s"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Autoriser"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Autoriser (lecture seule)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Refuser"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "A művelet meghiúsult."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Szerver"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Jóváhagyás"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "VNC szerver engedélyezése"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "A NetBird VNC szerver futtatása ezen a gépen, hogy az arra jogosult partnerek megtekinthessék vagy vezérelhessék a képernyőjét."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Kapcsolat jóváhagyásának megkövetelése"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Megerősítést kérő ablak megjelenítése ezen a gépen, amelyet el kell fogadni a bejövő VNC-kapcsolat engedélyezése előtt."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Kapcsolódási kérés"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "Engedélyezi a VNC-kapcsolatot?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "Engedélyezi az SSH-kapcsolatot?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "Engedélyezi a bejövő kapcsolatot?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "Felhasználótól"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Kulcs ujjlenyomata"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "Partneren keresztül"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "Forrás IP"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "OS-felhasználó"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Automatikus elutasítás {seconds} mp múlva"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Engedélyezés"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Engedélyezés (csak megtekintés)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Elutasítás"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "Operazione non riuscita."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Server"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Approvazione"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "Abilita server VNC"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "Esegui il server VNC di NetBird su questo host in modo che i peer autorizzati possano visualizzarne o controllarne lo schermo."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Richiedi l'approvazione della connessione"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Mostra su questo host una richiesta che deve essere accettata prima di consentire una connessione VNC in entrata."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Richiesta di connessione"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "Consentire la connessione VNC?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "Consentire la connessione SSH?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "Consentire la connessione in entrata?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "Dall'utente"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Impronta della chiave"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "Tramite peer"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "IP di origine"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "Utente del sistema"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Rifiuto automatico tra {seconds}s"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Consenti"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Consenti (sola visualizzazione)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Rifiuta"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "A operação falhou."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Servidor"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Aprovação"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "Ativar servidor VNC"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "Execute o servidor VNC do NetBird neste host para que os peers autorizados possam ver ou controlar a sua tela."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Exigir aprovação de conexão"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Mostrar neste host um aviso que precisa ser aceito antes de permitir uma conexão VNC de entrada."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Solicitação de conexão"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "Permitir a conexão VNC?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "Permitir a conexão SSH?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "Permitir a conexão de entrada?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "Do usuário"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Impressão digital da chave"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "Via peer"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "IP de origem"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "Usuário do SO"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Negação automática em {seconds}s"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Permitir"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Permitir (somente visualização)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Negar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "Не удалось выполнить операцию."
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "Сервер"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "Подтверждение"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "Включить VNC-сервер"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "Запустить VNC-сервер NetBird на этом хосте, чтобы авторизованные пиры могли просматривать его экран или управлять им."
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "Требовать подтверждение подключения"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "Показывать на этом хосте запрос, который нужно принять перед разрешением входящего VNC-подключения."
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "Запрос на подключение"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "Разрешить VNC-подключение?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "Разрешить SSH-подключение?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "Разрешить входящее подключение?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "От пользователя"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "Отпечаток ключа"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "Через пир"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "IP-адрес источника"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "Пользователь ОС"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "Автоотклонение через {seconds} с"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "Разрешить"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "Разрешить (только просмотр)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "Отклонить"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,5 +1321,65 @@
|
||||
},
|
||||
"error.unknown": {
|
||||
"message": "操作失败。"
|
||||
},
|
||||
"settings.tabs.vnc": {
|
||||
"message": "VNC"
|
||||
},
|
||||
"settings.vnc.section.server": {
|
||||
"message": "服务器"
|
||||
},
|
||||
"settings.vnc.section.approval": {
|
||||
"message": "批准"
|
||||
},
|
||||
"settings.vnc.server.label": {
|
||||
"message": "启用 VNC 服务器"
|
||||
},
|
||||
"settings.vnc.server.help": {
|
||||
"message": "在此主机上运行 NetBird VNC 服务器,以便授权的对端可以查看或控制其屏幕。"
|
||||
},
|
||||
"settings.vnc.approval.label": {
|
||||
"message": "要求连接批准"
|
||||
},
|
||||
"settings.vnc.approval.help": {
|
||||
"message": "在此主机上显示一个提示,必须先接受该提示才能允许传入的 VNC 连接。"
|
||||
},
|
||||
"window.title.approval": {
|
||||
"message": "连接请求"
|
||||
},
|
||||
"approval.title.vnc": {
|
||||
"message": "允许 VNC 连接?"
|
||||
},
|
||||
"approval.title.ssh": {
|
||||
"message": "允许 SSH 连接?"
|
||||
},
|
||||
"approval.title.default": {
|
||||
"message": "允许传入连接?"
|
||||
},
|
||||
"approval.field.user": {
|
||||
"message": "来自用户"
|
||||
},
|
||||
"approval.field.keyFingerprint": {
|
||||
"message": "密钥指纹"
|
||||
},
|
||||
"approval.field.peer": {
|
||||
"message": "经由对端"
|
||||
},
|
||||
"approval.field.sourceIp": {
|
||||
"message": "源 IP"
|
||||
},
|
||||
"approval.field.osUser": {
|
||||
"message": "操作系统用户"
|
||||
},
|
||||
"approval.countdown": {
|
||||
"message": "{seconds} 秒后自动拒绝"
|
||||
},
|
||||
"approval.action.allow": {
|
||||
"message": "允许"
|
||||
},
|
||||
"approval.action.allowViewOnly": {
|
||||
"message": "允许(仅查看)"
|
||||
},
|
||||
"approval.action.deny": {
|
||||
"message": "拒绝"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +318,7 @@ func registerServices(app *application.App, conn *Conn, s registeredServices) {
|
||||
app.RegisterService(application.NewService(s.settings))
|
||||
app.RegisterService(application.NewService(s.networks))
|
||||
app.RegisterService(application.NewService(services.NewForwarding(conn)))
|
||||
app.RegisterService(application.NewService(services.NewApproval(conn)))
|
||||
app.RegisterService(application.NewService(s.profiles))
|
||||
app.RegisterService(application.NewService(services.NewDebug(conn)))
|
||||
app.RegisterService(application.NewService(s.update))
|
||||
|
||||
36
client/ui/services/approval.go
Normal file
36
client/ui/services/approval.go
Normal 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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -42,6 +42,14 @@ func (t *Tray) onSystemEvent(ev *application.CustomEvent) {
|
||||
}
|
||||
return
|
||||
}
|
||||
// Inbound-connection approval prompts open a dedicated dialog instead of a
|
||||
// toast. Handle before the message gate: the daemon auto-denies on its
|
||||
// deadline, so a missing WindowManager fails closed.
|
||||
if se.Category == "approval" {
|
||||
t.openApproval(se)
|
||||
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"
|
||||
@@ -93,6 +101,30 @@ func (t *Tray) onSystemEvent(ev *application.CustomEvent) {
|
||||
t.notify(eventTitle(se), body, notifyIDEvent+se.ID)
|
||||
}
|
||||
|
||||
// openApproval opens the inbound-connection approval dialog from an APPROVAL
|
||||
// SystemEvent. request_id is daemon-issued; without it the prompt can't be
|
||||
// answered, so it's dropped and the daemon auto-denies on its deadline.
|
||||
func (t *Tray) openApproval(se services.SystemEvent) {
|
||||
if t.svc.WindowManager == nil {
|
||||
return
|
||||
}
|
||||
requestID := se.Metadata["request_id"]
|
||||
if requestID == "" {
|
||||
log.Warnf("approval event missing request_id: %v", se.Metadata)
|
||||
return
|
||||
}
|
||||
t.svc.WindowManager.OpenApproval(services.ApprovalRequest{
|
||||
RequestID: requestID,
|
||||
Kind: se.Metadata["kind"],
|
||||
Initiator: se.Metadata["initiator"],
|
||||
PeerName: se.Metadata["peer_name"],
|
||||
SourceIP: se.Metadata["source_ip"],
|
||||
Username: se.Metadata["username"],
|
||||
PeerPubKey: se.Metadata["peer_pubkey"],
|
||||
ExpiresAt: se.Metadata["expires_at"],
|
||||
})
|
||||
}
|
||||
|
||||
// eventTitle composes a notification title, e.g. "Critical: DNS", "Warning: Authentication".
|
||||
func eventTitle(e services.SystemEvent) string {
|
||||
prefix := titleCase(e.Severity)
|
||||
|
||||
Reference in New Issue
Block a user