From af726d29f927d5ef28afa2dd48693a6038e96a68 Mon Sep 17 00:00:00 2001 From: YipKo <17290550+YipKo@users.noreply.github.com> Date: Tue, 26 May 2026 16:12:12 +0800 Subject: [PATCH 1/5] feat: Add WPA Enterprise Wi-Fi support --- kvmapp/system/init.d/S30wifi | 90 ++++++++++- kvmapp/system/init.d/S95nanokvm | 8 +- server/proto/network.go | 11 +- server/service/network/wifi.go | 146 ++++++++++++++++-- web/src/api/network.ts | 26 +++- .../desktop/menu/settings/network/wifi.tsx | 82 +++++++++- web/src/pages/wifi/index.tsx | 61 +++++++- 7 files changed, 390 insertions(+), 34 deletions(-) diff --git a/kvmapp/system/init.d/S30wifi b/kvmapp/system/init.d/S30wifi index 6694eca2..d6c532b5 100755 --- a/kvmapp/system/init.d/S30wifi +++ b/kvmapp/system/init.d/S30wifi @@ -4,6 +4,50 @@ APFLAG_FILE=/tmp/wifiap +escape_wpa_value() { + printf "%s" "$1" | tr -d '\r\n' | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +sanitize_wpa_token() { + printf "%s" "$1" | tr -cd 'A-Za-z0-9_-' +} + +append_wpa_field() { + key="${1}" + value="${2}" + if [ -n "$value" ]; then + printf ' %s="%s"\n' "$key" "$(escape_wpa_value "$value")" + fi +} + +gen_wpa_enterprise_conf() { + ssid="${1}" + pass="${2}" + identity="${3}" + eap="${4:-PEAP}" + phase2="${5:-auth=MSCHAPV2}" + anonymous_identity="${6}" + ca_cert="${7}" + domain_suffix_match="${8}" + eap=$(sanitize_wpa_token "$eap") + + echo "ctrl_interface=/var/run/wpa_supplicant" + echo "ap_scan=1" + echo "fast_reauth=1" + echo "" + echo "network={" + append_wpa_field "ssid" "$ssid" + echo " key_mgmt=WPA-EAP" + echo " eap=${eap}" + append_wpa_field "identity" "$identity" + append_wpa_field "password" "$pass" + append_wpa_field "phase2" "$phase2" + append_wpa_field "anonymous_identity" "$anonymous_identity" + append_wpa_field "ca_cert" "$ca_cert" + append_wpa_field "domain_suffix_match" "$domain_suffix_match" + echo "}" +} + gen_hostapd_conf() { ssid="${1}" pass="${2}" @@ -52,16 +96,24 @@ start() { echo "wifi mode: sta" ssid="" pass="" + mode="psk" + identity="" + eap="PEAP" + phase2="auth=MSCHAPV2" + anonymous_identity="" + ca_cert="" + domain_suffix_match="" if [ -e /boot/wifi.ssid ] && [ -e /boot/wifi.pass ]; then echo "Updating WiFi credentials from /boot to /etc/kvm/" - rm -f /etc/kvm/wifi.ssid /etc/kvm/wifi.pass + rm -f /etc/kvm/wifi.ssid /etc/kvm/wifi.pass /etc/kvm/wifi.mode mv /boot/wifi.ssid /etc/kvm/wifi.ssid mv /boot/wifi.pass /etc/kvm/wifi.pass chown root:root /etc/kvm/wifi.ssid /etc/kvm/wifi.pass - chmod 644 /etc/kvm/wifi.ssid /etc/kvm/wifi.pass + chmod 644 /etc/kvm/wifi.ssid + chmod 600 /etc/kvm/wifi.pass fi if [ -e /etc/kvm/wifi.ssid ]; then ssid=$(cat /etc/kvm/wifi.ssid) @@ -69,8 +121,38 @@ start() { if [ -e /etc/kvm/wifi.pass ]; then pass=$(cat /etc/kvm/wifi.pass) fi - echo "ctrl_interface=/var/run/wpa_supplicant" >/etc/wpa_supplicant.conf - wpa_passphrase "$ssid" "$pass" >>/etc/wpa_supplicant.conf + if [ -e /etc/kvm/wifi.mode ]; then + mode=$(cat /etc/kvm/wifi.mode) + fi + if [ -e /etc/kvm/wifi.identity ]; then + identity=$(cat /etc/kvm/wifi.identity) + fi + if [ -e /etc/kvm/wifi.eap ]; then + eap=$(cat /etc/kvm/wifi.eap) + fi + if [ -e /etc/kvm/wifi.phase2 ]; then + phase2=$(cat /etc/kvm/wifi.phase2) + fi + if [ -e /etc/kvm/wifi.anonymous_identity ]; then + anonymous_identity=$(cat /etc/kvm/wifi.anonymous_identity) + fi + if [ -e /etc/kvm/wifi.ca_cert ]; then + ca_cert=$(cat /etc/kvm/wifi.ca_cert) + fi + if [ -e /etc/kvm/wifi.domain_suffix_match ]; then + domain_suffix_match=$(cat /etc/kvm/wifi.domain_suffix_match) + fi + + case "$mode" in + enterprise | 8021x | wpa-eap) + gen_wpa_enterprise_conf "$ssid" "$pass" "$identity" "$eap" "$phase2" "$anonymous_identity" "$ca_cert" "$domain_suffix_match" >/etc/wpa_supplicant.conf + ;; + *) + echo "ctrl_interface=/var/run/wpa_supplicant" >/etc/wpa_supplicant.conf + wpa_passphrase "$ssid" "$pass" >>/etc/wpa_supplicant.conf + ;; + esac + chmod 600 /etc/wpa_supplicant.conf wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf if [ ! -e /boot/wifi.nodhcp ]; then (udhcpc -i wlan0 -t 10 -T 1 -A 5 -b -p /run/udhcpc.wlan0.pid) & diff --git a/kvmapp/system/init.d/S95nanokvm b/kvmapp/system/init.d/S95nanokvm index f3142618..e93f6645 100755 --- a/kvmapp/system/init.d/S95nanokvm +++ b/kvmapp/system/init.d/S95nanokvm @@ -49,7 +49,8 @@ case "$1" in /tmp/kvm_system/kvm_system & cp -r /kvmapp/server /tmp/ - /tmp/server/NanoKVM-Server & + chmod -R a+rX /tmp/server/web + (cd /tmp/server && LD_LIBRARY_PATH=/tmp/server/dl_lib ./NanoKVM-Server >/tmp/nanokvm-server.log 2>&1) & ;; stop) @@ -68,7 +69,8 @@ case "$1" in /tmp/kvm_system/kvm_system & cp -r /kvmapp/server /tmp/ - /tmp/server/NanoKVM-Server & + chmod -R a+rX /tmp/server/web + (cd /tmp/server && LD_LIBRARY_PATH=/tmp/server/dl_lib ./NanoKVM-Server >/tmp/nanokvm-server.log 2>&1) & sync echo "OK" @@ -78,4 +80,4 @@ case "$1" in echo "Usage: $0 {start|stop|restart}" exit 1 ;; -esac \ No newline at end of file +esac diff --git a/server/proto/network.go b/server/proto/network.go index 966e6b23..0ab4a469 100644 --- a/server/proto/network.go +++ b/server/proto/network.go @@ -25,8 +25,15 @@ type GetWifiRsp struct { } type ConnectWifiReq struct { - Ssid string `validate:"required"` - Password string `validate:"required"` + Ssid string `json:"ssid" form:"ssid"` + Password string `json:"password" form:"password"` + Mode string `json:"mode" form:"mode"` + Identity string `json:"identity" form:"identity"` + EAP string `json:"eap" form:"eap"` + Phase2 string `json:"phase2" form:"phase2"` + AnonymousIdentity string `json:"anonymousIdentity" form:"anonymousIdentity"` + CACert string `json:"caCert" form:"caCert"` + DomainSuffixMatch string `json:"domainSuffixMatch" form:"domainSuffixMatch"` } type GetDNSRsp struct { diff --git a/server/service/network/wifi.go b/server/service/network/wifi.go index d163d343..a5f4d0c1 100644 --- a/server/service/network/wifi.go +++ b/server/service/network/wifi.go @@ -15,14 +15,21 @@ import ( ) const ( - WiFiExistFile = "/etc/kvm/wifi_exist" - WiFiApModeFile = "/tmp/wifiap" - WiFiSSID = "/etc/kvm/wifi.ssid" - WiFiPasswd = "/etc/kvm/wifi.pass" - WiFiConnect = "/kvmapp/kvm/wifi_try_connect" - WiFiStateFile = "/kvmapp/kvm/wifi_state" - WiFiScript = "/etc/init.d/S30wifi" - WiFiApPassFile = "/kvmapp/kvm/ap.pass" + WiFiExistFile = "/etc/kvm/wifi_exist" + WiFiApModeFile = "/tmp/wifiap" + WiFiSSID = "/etc/kvm/wifi.ssid" + WiFiPasswd = "/etc/kvm/wifi.pass" + WiFiMode = "/etc/kvm/wifi.mode" + WiFiIdentity = "/etc/kvm/wifi.identity" + WiFiEAP = "/etc/kvm/wifi.eap" + WiFiPhase2 = "/etc/kvm/wifi.phase2" + WiFiAnonymousIdentity = "/etc/kvm/wifi.anonymous_identity" + WiFiCACert = "/etc/kvm/wifi.ca_cert" + WiFiDomainSuffixMatch = "/etc/kvm/wifi.domain_suffix_match" + WiFiConnect = "/kvmapp/kvm/wifi_try_connect" + WiFiStateFile = "/kvmapp/kvm/wifi_state" + WiFiScript = "/etc/init.d/S30wifi" + WiFiApPassFile = "/kvmapp/kvm/ap.pass" ) func (s *Service) GetWifi(c *gin.Context) { @@ -73,13 +80,13 @@ func (s *Service) ConnectWifiNoAuth(c *gin.Context) { return } - if err := proto.ParseFormRequest(c, &req); err != nil { + if err := parseConnectWifiRequest(c, &req); err != nil { time.Sleep(1 * time.Second) rsp.ErrRsp(c, -2, "invalid parameters") return } - if err := connect(req.Ssid, req.Password); err != nil { + if err := connect(req); err != nil { rsp.ErrRsp(c, -3, "failed to connect wifi") return } @@ -114,12 +121,12 @@ func (s *Service) ConnectWifi(c *gin.Context) { var req proto.ConnectWifiReq var rsp proto.Response - if err := proto.ParseFormRequest(c, &req); err != nil { + if err := parseConnectWifiRequest(c, &req); err != nil { rsp.ErrRsp(c, -1, "invalid parameters") return } - if err := connect(req.Ssid, req.Password); err != nil { + if err := connect(req); err != nil { rsp.ErrRsp(c, -2, "failed to connect wifi") return } @@ -160,22 +167,93 @@ func (s *Service) DisconnectWifi(c *gin.Context) { _ = os.Remove(WiFiSSID) _ = os.Remove(WiFiPasswd) + removeEnterpriseWiFiFiles() rsp.OkRsp(c) log.Debugf("stop wifi successfully") } -func connect(ssid string, password string) error { - if err := os.WriteFile(WiFiSSID, []byte(ssid), 0o644); err != nil { +func parseConnectWifiRequest(c *gin.Context, req *proto.ConnectWifiReq) error { + if err := c.ShouldBind(req); err != nil { + log.Errorf("parse wifi request failed, err: %s", err) + return err + } + + req.Ssid = strings.TrimSpace(req.Ssid) + req.Mode = normalizeWiFiMode(req.Mode) + req.EAP = strings.TrimSpace(req.EAP) + req.Phase2 = strings.TrimSpace(req.Phase2) + req.Identity = strings.TrimSpace(req.Identity) + req.AnonymousIdentity = strings.TrimSpace(req.AnonymousIdentity) + req.CACert = strings.TrimSpace(req.CACert) + req.DomainSuffixMatch = strings.TrimSpace(req.DomainSuffixMatch) + + if req.Ssid == "" || req.Password == "" { + return fmt.Errorf("ssid and password are required") + } + if isEnterpriseWiFiMode(req.Mode) && req.Identity == "" { + return fmt.Errorf("identity is required for enterprise wifi") + } + if hasLineBreak(req.Ssid, req.Password, req.Identity, req.EAP, req.Phase2, req.AnonymousIdentity, req.CACert, req.DomainSuffixMatch) { + return fmt.Errorf("wifi parameters must not contain line breaks") + } + + if req.EAP == "" { + req.EAP = "PEAP" + } + if req.Phase2 == "" { + req.Phase2 = "auth=MSCHAPV2" + } + + log.Debugf("wifi connect request: ssid=%q mode=%q identity_set=%t", req.Ssid, req.Mode, req.Identity != "") + return nil +} + +func normalizeWiFiMode(mode string) string { + mode = strings.ToLower(strings.TrimSpace(mode)) + if mode == "" { + return "psk" + } + return mode +} + +func isEnterpriseWiFiMode(mode string) bool { + return mode == "enterprise" || mode == "8021x" || mode == "wpa-eap" +} + +func hasLineBreak(values ...string) bool { + for _, value := range values { + if strings.ContainsAny(value, "\r\n") { + return true + } + } + return false +} + +func connect(req proto.ConnectWifiReq) error { + if err := os.WriteFile(WiFiSSID, []byte(req.Ssid), 0o644); err != nil { log.Errorf("failed to save wifi ssid: %s", err) return err } - if err := os.WriteFile(WiFiPasswd, []byte(password), 0o644); err != nil { + if err := os.WriteFile(WiFiPasswd, []byte(req.Password), 0o600); err != nil { log.Errorf("failed to save wifi password: %s", err) return err } + if err := os.WriteFile(WiFiMode, []byte(req.Mode), 0o644); err != nil { + log.Errorf("failed to save wifi mode: %s", err) + return err + } + + if isEnterpriseWiFiMode(req.Mode) { + if err := writeEnterpriseWiFiFiles(req); err != nil { + return err + } + } else { + removeEnterpriseWiFiFiles() + } + if err := os.WriteFile(WiFiConnect, nil, 0o644); err != nil { log.Errorf("failed to connect wifi: %s", err) return err @@ -184,6 +262,44 @@ func connect(ssid string, password string) error { return nil } +func writeEnterpriseWiFiFiles(req proto.ConnectWifiReq) error { + files := map[string]string{ + WiFiIdentity: req.Identity, + WiFiEAP: req.EAP, + WiFiPhase2: req.Phase2, + WiFiAnonymousIdentity: req.AnonymousIdentity, + WiFiCACert: req.CACert, + WiFiDomainSuffixMatch: req.DomainSuffixMatch, + } + + for path, value := range files { + if strings.TrimSpace(value) == "" { + _ = os.Remove(path) + continue + } + if err := os.WriteFile(path, []byte(value), 0o600); err != nil { + log.Errorf("failed to save enterprise wifi file %s: %s", path, err) + return err + } + } + + return nil +} + +func removeEnterpriseWiFiFiles() { + for _, path := range []string{ + WiFiMode, + WiFiIdentity, + WiFiEAP, + WiFiPhase2, + WiFiAnonymousIdentity, + WiFiCACert, + WiFiDomainSuffixMatch, + } { + _ = os.Remove(path) + } +} + func isSupported() bool { _, err := os.Stat(WiFiExistFile) return err == nil diff --git a/web/src/api/network.ts b/web/src/api/network.ts index 00a4c381..c27e0d30 100644 --- a/web/src/api/network.ts +++ b/web/src/api/network.ts @@ -1,6 +1,17 @@ import { http } from '@/lib/http.ts'; export type DNSMode = 'manual' | 'dhcp'; +export type WiFiSecurityMode = 'psk' | 'enterprise'; + +export type ConnectWifiOptions = { + mode?: WiFiSecurityMode; + identity?: string; + eap?: string; + phase2?: string; + anonymousIdentity?: string; + caCert?: string; + domainSuffixMatch?: string; +}; // wake on lan export function wol(mac: string) { @@ -33,10 +44,16 @@ export function getWiFi() { } // connect wifi without auth (only available in wifi configuration mode) -export function connectWifiNoAuth(ssid: string, password: string, apPassword?: string) { +export function connectWifiNoAuth( + ssid: string, + password: string, + apPassword?: string, + options: ConnectWifiOptions = {} +) { const data = { ssid, - password + password, + ...options }; return http.post('/api/network/wifi', data, { headers: { @@ -59,10 +76,11 @@ export function verifyApLogin(apPassword: string) { } // connect wifi -export function connectWifi(ssid: string, password: string) { +export function connectWifi(ssid: string, password: string, options: ConnectWifiOptions = {}) { const data = { ssid, - password + password, + ...options }; return http.post('/api/network/wifi/connect', data); } diff --git a/web/src/pages/desktop/menu/settings/network/wifi.tsx b/web/src/pages/desktop/menu/settings/network/wifi.tsx index d6114a65..c835f7f8 100644 --- a/web/src/pages/desktop/menu/settings/network/wifi.tsx +++ b/web/src/pages/desktop/menu/settings/network/wifi.tsx @@ -1,10 +1,11 @@ import { useEffect, useState } from 'react'; import { LockOutlined, WifiOutlined } from '@ant-design/icons'; -import { Button, Input, Modal, Switch } from 'antd'; +import { Button, Input, Modal, Select, Switch } from 'antd'; import { WifiIcon, WifiPenIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import * as api from '@/api/network.ts'; +import type { WiFiSecurityMode } from '@/api/network.ts'; export const Wifi = () => { const { t } = useTranslation(); @@ -14,8 +15,15 @@ export const Wifi = () => { const [connectedWiFi, setConnectedWifi] = useState(''); const [isModalOpen, setIsModalOpen] = useState(false); + const [mode, setMode] = useState('psk'); const [ssid, setSsid] = useState(''); const [password, setPassword] = useState(''); + const [identity, setIdentity] = useState(''); + const [eap, setEap] = useState('PEAP'); + const [phase2, setPhase2] = useState('auth=MSCHAPV2'); + const [anonymousIdentity, setAnonymousIdentity] = useState(''); + const [caCert, setCaCert] = useState(''); + const [domainSuffixMatch, setDomainSuffixMatch] = useState(''); const [status, setStatus] = useState<'' | 'connecting' | 'disconnecting'>(''); const [message, setMessage] = useState(''); @@ -47,13 +55,21 @@ export const Wifi = () => { async function connect() { setMessage(''); - if (!ssid || !password) return; + if (!ssid || !password || (mode === 'enterprise' && !identity)) return; if (status !== '') return; setStatus('connecting'); try { - const rsp = await api.connectWifi(ssid, password); + const rsp = await api.connectWifi(ssid, password, { + mode, + identity, + eap, + phase2, + anonymousIdentity, + caCert, + domainSuffixMatch + }); if (rsp.code !== 0) { console.log(rsp.msg); setMessage(t('settings.network.wifi.failed')); @@ -91,8 +107,15 @@ export const Wifi = () => { } function openModal() { + setMode('psk'); setSsid(''); setPassword(''); + setIdentity(''); + setEap('PEAP'); + setPhase2('auth=MSCHAPV2'); + setAnonymousIdentity(''); + setCaCert(''); + setDomainSuffixMatch(''); setMessage(''); setIsModalOpen(true); } @@ -186,6 +209,15 @@ export const Wifi = () => { {/* form */}
+ { placeholder={t('settings.network.wifi.password')} onChange={(e) => setPassword(e.target.value)} /> + {mode === 'enterprise' && ( + <> + setIdentity(e.target.value)} + /> + setPhase2(e.target.value)} + /> + setAnonymousIdentity(e.target.value)} + /> + setCaCert(e.target.value)} + /> + setDomainSuffixMatch(e.target.value)} + /> + + )} {!!message && {message}}
diff --git a/web/src/pages/wifi/index.tsx b/web/src/pages/wifi/index.tsx index f1319928..c66df0a7 100644 --- a/web/src/pages/wifi/index.tsx +++ b/web/src/pages/wifi/index.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { CheckOutlined, KeyOutlined, LockOutlined, WifiOutlined } from '@ant-design/icons'; -import { Button, Form, Input } from 'antd'; +import { Button, Form, Input, Select } from 'antd'; import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; @@ -19,6 +19,8 @@ export const Wifi = () => { const [isAuthenticated, setIsAuthenticated] = useState(false); const [verifying, setVerifying] = useState(false); const [verifyState, setVerifyState] = useState(''); + const [form] = Form.useForm(); + const mode = Form.useWatch('mode', form); useEffect(() => { const pass = searchParams.get('p') || searchParams.get('P'); @@ -54,12 +56,21 @@ export const Wifi = () => { async function connect(values: any) { if (!values.ssid || !values.password) return; + if (values.mode === 'enterprise' && !values.identity) return; if (state === 'loading') return; setState('loading'); try { - const rsp = await api.connectWifiNoAuth(values.ssid, values.password, apPassword); + const rsp = await api.connectWifiNoAuth(values.ssid, values.password, apPassword, { + mode: values.mode || 'psk', + identity: values.identity, + eap: values.eap, + phase2: values.phase2, + anonymousIdentity: values.anonymousIdentity, + caCert: values.caCert, + domainSuffixMatch: values.domainSuffixMatch + }); switch (rsp?.code) { case 0: @@ -129,8 +140,9 @@ export const Wifi = () => {
@@ -140,6 +152,15 @@ export const Wifi = () => { {t('wifi.description')}
+ + } placeholder="SSID" /> @@ -148,6 +169,40 @@ export const Wifi = () => { } placeholder="Password" /> + {mode === 'enterprise' && ( + <> + + + + + + + + + + + + + + + + + + + + + )} + {state === 'success' ? (
-
- - - - - - - - - {mode === 'manual' ? ( -
- {servers.length === 0 ? ( -
- {t('settings.network.dns.none')} -
- ) : ( - servers.map((server, index) => ( - updateServer(index, val)} - onRemove={() => removeServer(index)} + + {mode === 'manual' ? ( +
+ {servers.length === 0 ? ( +
+ {t('settings.network.dns.none')} +
+ ) : ( + servers.map((server, index) => ( + updateServer(index, val)} + onRemove={() => removeServer(index)} + /> + )) + )} + + {canAdd && ( +
+
+ + -
- )} - - {/* Validation hints */} - {(hasInvalidServer || isExceedMax) && ( -
- {hasInvalidServer && ( -
{t('settings.network.dns.invalid')}
- )} - {isExceedMax && ( -
- {t('settings.network.dns.maxServers', { count: maxServers })} -
- )} -
- )} -
- ) : ( - - )} -
-
+
+ )} + + {(hasInvalidServer || isExceedMax) && ( +
+ {hasInvalidServer && ( +
{t('settings.network.dns.invalid')}
+ )} + {isExceedMax && ( +
+ {t('settings.network.dns.maxServers', { count: maxServers })} +
+ )} +
+ )} + + ) : ( + + )} + {/* Footer: status + save button */} {(hasChanges || statusText) && ( diff --git a/web/src/pages/desktop/menu/settings/network/ethernet.tsx b/web/src/pages/desktop/menu/settings/network/ethernet.tsx new file mode 100644 index 00000000..6ea36e72 --- /dev/null +++ b/web/src/pages/desktop/menu/settings/network/ethernet.tsx @@ -0,0 +1,413 @@ +import { useEffect, useState } from 'react'; +import { ApiOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; +import { Button, Input, Modal, Segmented, Select, Switch } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import * as api from '@/api/network.ts'; +import type { EthernetIPMode, EthernetSecurityMode } from '@/api/network.ts'; + +type EAPMethod = 'PEAP' | 'TTLS' | 'TLS' | 'PWD' | 'LEAP'; + +const getDefaultPhase2 = (eap: EAPMethod) => { + if (eap === 'PEAP' || eap === 'TTLS') return 'auth=MSCHAPV2'; + return ''; +}; + +const getEapOptions = (t: (key: string) => string) => [ + { value: 'PEAP', label: t('settings.network.wifi.eapPeap') }, + { value: 'TTLS', label: t('settings.network.wifi.eapTtls') }, + { value: 'TLS', label: t('settings.network.wifi.eapTls') }, + { value: 'PWD', label: t('settings.network.wifi.eapPwd') }, + { value: 'LEAP', label: t('settings.network.wifi.eapLeap') } +]; + +const getInnerAuthOptions = (eap: EAPMethod, t: (key: string) => string) => { + const peapInnerAuthOptions = [ + { value: 'auth=MSCHAPV2', label: t('settings.network.wifi.authMschapv2') }, + { value: 'auth=GTC', label: t('settings.network.wifi.authGtc') }, + { value: 'auth=MD5', label: t('settings.network.wifi.authMd5') } + ]; + const ttlsInnerAuthOptions = [ + { value: 'auth=MSCHAPV2', label: t('settings.network.wifi.authMschapv2') }, + { value: 'auth=MSCHAP', label: t('settings.network.wifi.authMschap') }, + { value: 'auth=CHAP', label: t('settings.network.wifi.authChap') }, + { value: 'auth=PAP', label: t('settings.network.wifi.authPap') } + ]; + if (eap === 'PEAP') return peapInnerAuthOptions; + if (eap === 'TTLS') return ttlsInnerAuthOptions; + return []; +}; + +const usesPassword = (eap: EAPMethod) => eap !== 'TLS'; +const usesInnerAuth = (eap: EAPMethod) => eap === 'PEAP' || eap === 'TTLS'; + +function isValidIPv4(value: string) { + const parts = value.split('.'); + if (parts.length !== 4) return false; + + return parts.every((part) => { + if (!/^\d+$/.test(part)) return false; + if (part.length > 1 && part.startsWith('0')) return false; + + const number = Number(part); + return number >= 0 && number <= 255; + }); +} + +function isValidSubnetMask(value: string) { + const parts = value.split('.'); + if (parts.length !== 4) return false; + + const bytes = parts.map((part) => { + if (!/^\d+$/.test(part)) return NaN; + const number = Number(part); + if (number < 0 || number > 255) return NaN; + return number; + }); + if (bytes.some(Number.isNaN)) return false; + + const mask = bytes + .map((byte) => byte.toString(2).padStart(8, '0')) + .join(''); + if (!/^1*0*$/.test(mask)) return false; + + const ones = mask.indexOf('0') === -1 ? 32 : mask.indexOf('0'); + return ones >= 1 && ones <= 32; +} + +export const Ethernet = () => { + const { t } = useTranslation(); + + const [configured, setConfigured] = useState(false); + const [connected, setConnected] = useState(false); + const [iface, setIface] = useState('eth0'); + + const [isModalOpen, setIsModalOpen] = useState(false); + const [mode, setMode] = useState('off'); + const [ipMode, setIPMode] = useState('dhcp'); + const [address, setAddress] = useState(''); + const [subnetMask, setSubnetMask] = useState(''); + const [gateway, setGateway] = useState(''); + const [password, setPassword] = useState(''); + const [passwordSet, setPasswordSet] = useState(false); + const [identity, setIdentity] = useState(''); + const [eap, setEap] = useState('PEAP'); + const [phase2, setPhase2] = useState('auth=MSCHAPV2'); + const [anonymousIdentity, setAnonymousIdentity] = useState(''); + const [caCert, setCaCert] = useState(''); + const [clientCert, setClientCert] = useState(''); + const [privateKey, setPrivateKey] = useState(''); + const [privateKeyPasswd, setPrivateKeyPasswd] = useState(''); + const [privateKeyPasswdSet, setPrivateKeyPasswdSet] = useState(false); + const [domainSuffixMatch, setDomainSuffixMatch] = useState(''); + const [status, setStatus] = useState<'' | 'saving'>(''); + const [message, setMessage] = useState(''); + + useEffect(() => { + getEthernet(); + }, []); + + async function getEthernet() { + try { + const rsp = await api.getEthernet(); + if (rsp.code !== 0) return; + + setConfigured(!!rsp.data?.configured); + setConnected(!!rsp.data?.connected); + setIface(rsp.data?.interface || 'eth0'); + setMode(rsp.data?.mode === 'enterprise' ? 'enterprise' : 'off'); + setIPMode(rsp.data?.ipMode === 'manual' ? 'manual' : 'dhcp'); + setAddress(rsp.data?.address || ''); + setSubnetMask(rsp.data?.subnetMask || ''); + setGateway(rsp.data?.gateway || ''); + setPassword(''); + setPasswordSet(!!rsp.data?.passwordSet); + setIdentity(rsp.data?.identity || ''); + setEap((rsp.data?.eap as EAPMethod) || 'PEAP'); + setPhase2(rsp.data?.phase2 || 'auth=MSCHAPV2'); + setAnonymousIdentity(rsp.data?.anonymousIdentity || ''); + setCaCert(rsp.data?.caCert || ''); + setClientCert(rsp.data?.clientCert || ''); + setPrivateKey(rsp.data?.privateKey || ''); + setPrivateKeyPasswd(''); + setPrivateKeyPasswdSet(!!rsp.data?.privateKeyPasswdSet); + setDomainSuffixMatch(rsp.data?.domainSuffixMatch || ''); + } catch { + /* empty */ + } + } + + async function save() { + setMessage(''); + + if (mode === 'enterprise' && !identity) return; + if (mode === 'enterprise' && usesPassword(eap) && !password && !passwordSet) return; + if (mode === 'enterprise' && eap === 'TLS' && (!clientCert || !privateKey)) return; + if (ipMode === 'manual' && !isValidIPv4(address)) return; + if (ipMode === 'manual' && !subnetMask) return; + if (ipMode === 'manual' && !isValidSubnetMask(subnetMask)) return; + if (ipMode === 'manual' && gateway && !isValidIPv4(gateway)) return; + if (status !== '') return; + + setStatus('saving'); + + try { + const rsp = await api.setEthernet({ + mode, + ipMode, + address, + subnetMask, + gateway, + password, + identity, + eap, + phase2, + anonymousIdentity, + caCert, + clientCert, + privateKey, + privateKeyPasswd, + domainSuffixMatch + }); + if (rsp.code !== 0) { + setMessage(t('settings.network.ethernet.failed')); + await getEthernet(); + return; + } + + setConfigured(mode === 'enterprise'); + setIsModalOpen(false); + await getEthernet(); + } finally { + setStatus(''); + } + } + + function openModal() { + setPassword(''); + setPrivateKeyPasswd(''); + setMessage(''); + setIsModalOpen(true); + } + + function closeModal() { + if (status !== '') return; + setIsModalOpen(false); + } + + function changeEap(value: EAPMethod) { + setEap(value); + setPhase2(getDefaultPhase2(value)); + if (value === 'TLS') { + setPassword(''); + } + } + + const statusText = configured + ? connected + ? t('settings.network.ethernet.active') + : t('settings.network.ethernet.inactive') + : t('settings.network.ethernet.description'); + const enterpriseEnabled = mode === 'enterprise'; + + return ( + <> +
+
+ {t('settings.network.ethernet.title')} + {statusText} +
+ + +
+ + +
+
+ +
+ +
+ {t('settings.network.ethernet.connect')} + + {t('settings.network.ethernet.connectDesc', { iface })} + +
+
+ +
+
+
+ {t('settings.network.ethernet.ipConfig')} + + {t('settings.network.ethernet.ipConfigDescription')} + +
+ setIPMode(value as EthernetIPMode)} + /> + + {ipMode === 'manual' && ( +
+ setAddress(e.target.value)} + status={address !== '' && !isValidIPv4(address) ? 'error' : undefined} + /> + setSubnetMask(e.target.value)} + status={subnetMask !== '' && !isValidSubnetMask(subnetMask) ? 'error' : undefined} + /> + setGateway(e.target.value)} + status={gateway !== '' && !isValidIPv4(gateway) ? 'error' : undefined} + /> +
+ )} +
+ +
+ +
+
+ {t('settings.network.ethernet.authTitle')} + + {t('settings.network.ethernet.authDescription')} + +
+ { + if (!enabled) { + setMode('off'); + return; + } + setMode('enterprise'); + if (!identity) setIdentity(''); + if (!eap) setEap('PEAP'); + if (!phase2) setPhase2('auth=MSCHAPV2'); + }} + /> +
+ + {enterpriseEnabled && ( + <> + } + placeholder={t('settings.network.wifi.identity')} + onChange={(e) => setIdentity(e.target.value)} + /> + {usesPassword(eap) && ( + } + placeholder={ + passwordSet + ? t('settings.network.ethernet.passwordUnchanged') + : t('settings.network.wifi.password') + } + onChange={(e) => setPassword(e.target.value)} + /> + )} + + )} + setAnonymousIdentity(e.target.value)} + /> + {eap === 'TLS' && ( + <> + setClientCert(e.target.value)} + /> + setPrivateKey(e.target.value)} + /> + setPrivateKeyPasswd(e.target.value)} + /> + + )} + setCaCert(e.target.value)} + /> + setDomainSuffixMatch(e.target.value)} + /> + + )} + + {!!message && {message}} +
+ + + ); +}; diff --git a/web/src/pages/desktop/menu/settings/network/index.tsx b/web/src/pages/desktop/menu/settings/network/index.tsx index a85fd1cc..07d25f81 100644 --- a/web/src/pages/desktop/menu/settings/network/index.tsx +++ b/web/src/pages/desktop/menu/settings/network/index.tsx @@ -2,6 +2,8 @@ import { Divider } from 'antd'; import { useTranslation } from 'react-i18next'; import { DNS } from './dns.tsx'; +import { Ethernet } from './ethernet.tsx'; +import { NetworkDetails } from './network-details.tsx'; import { Tls } from './tls.tsx'; import { Wifi } from './wifi.tsx'; @@ -15,7 +17,9 @@ export const Network = () => {
+ +
diff --git a/web/src/pages/desktop/menu/settings/network/network-details.tsx b/web/src/pages/desktop/menu/settings/network/network-details.tsx new file mode 100644 index 00000000..44eec050 --- /dev/null +++ b/web/src/pages/desktop/menu/settings/network/network-details.tsx @@ -0,0 +1,153 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import * as api from '@/api/network.ts'; + +type NetworkInfo = { + interface?: string; + type?: string; + address?: string; + subnetMask?: string; + gateway?: string; + signal?: string; + rxRate?: string; + txRate?: string; +}; + +type NetworkDetailsData = { + info?: NetworkInfo; + infos?: NetworkInfo[]; +}; + +function formatInterfaceType(type: string | undefined, t: (key: string) => string) { + const translate = t as unknown as (key: string, options?: { defaultValue?: string }) => string; + switch ((type || '').toLowerCase()) { + case 'wired': + return translate('settings.network.dns.wired', { defaultValue: 'Wired' }); + case 'wireless': + return translate('settings.network.dns.wireless', { defaultValue: 'Wireless' }); + default: + return type || ''; + } +} + +function isWireless(info: NetworkInfo) { + return (info.type || '').toLowerCase() === 'wireless'; +} + +function formatInterface(info: NetworkInfo, t: (key: string) => string) { + if (!info.interface) return ''; + const type = formatInterfaceType(info.type, t); + if (!type) return info.interface; + + return `${type} (${info.interface})`; +} + +const InfoRow = ({ + label, + value, + isLast = false +}: { + label: string; + value?: string; + isLast?: boolean; +}) => { + return ( +
+ {label} + + {value || '-'} + +
+ ); +}; + +const WirelessRow = ({ label, value }: { label: string; value?: string }) => { + if (!value) return null; + + return ; +}; + +const NetworkCard = ({ info, isLast = false }: { info: NetworkInfo; isLast?: boolean }) => { + const { t } = useTranslation(); + + return ( +
+
+
{formatInterface(info, t) || '-'}
+
+
+ + + + {isWireless(info) && ( + <> + + + + + )} +
+
+ ); +}; + +export const NetworkDetails = () => { + const { t } = useTranslation(); + const [details, setDetails] = useState([]); + + useEffect(() => { + getNetworkDetails(); + }, []); + + async function getNetworkDetails() { + try { + const rsp = await api.getDNS(); + if (rsp.code !== 0) return; + + const data = rsp.data as NetworkDetailsData | undefined; + const infos = data?.infos?.length ? data.infos : data?.info ? [data.info] : []; + setDetails(infos); + } catch { + /* empty */ + } + } + + return ( +
+
+
+ {t('settings.network.dns.networkDetails')} +
+
+
+ {details.length > 0 ? ( + details.map((info, index) => ( + + )) + ) : ( +
+ {t('settings.network.dns.none')} +
+ )} +
+
+ ); +}; diff --git a/web/src/pages/desktop/menu/settings/network/wifi.tsx b/web/src/pages/desktop/menu/settings/network/wifi.tsx index c835f7f8..00740571 100644 --- a/web/src/pages/desktop/menu/settings/network/wifi.tsx +++ b/web/src/pages/desktop/menu/settings/network/wifi.tsx @@ -1,11 +1,78 @@ import { useEffect, useState } from 'react'; -import { LockOutlined, WifiOutlined } from '@ant-design/icons'; -import { Button, Input, Modal, Select, Switch } from 'antd'; +import { LockOutlined, UserOutlined, WifiOutlined } from '@ant-design/icons'; +import { Button, Input, Modal, Select, Segmented, Switch } from 'antd'; import { WifiIcon, WifiPenIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import * as api from '@/api/network.ts'; -import type { WiFiSecurityMode } from '@/api/network.ts'; +import type { WiFiIPMode, WiFiSecurityMode } from '@/api/network.ts'; + +type EAPMethod = 'PEAP' | 'TTLS' | 'TLS' | 'PWD' | 'LEAP'; + +const getDefaultPhase2 = (eap: EAPMethod) => { + if (eap === 'PEAP' || eap === 'TTLS') return 'auth=MSCHAPV2'; + return ''; +}; + +const getEapOptions = (t: (key: string) => string) => [ + { value: 'PEAP', label: t('settings.network.wifi.eapPeap') }, + { value: 'TTLS', label: t('settings.network.wifi.eapTtls') }, + { value: 'TLS', label: t('settings.network.wifi.eapTls') }, + { value: 'PWD', label: t('settings.network.wifi.eapPwd') }, + { value: 'LEAP', label: t('settings.network.wifi.eapLeap') } +]; + +const getInnerAuthOptions = (eap: EAPMethod, t: (key: string) => string) => { + const peapInnerAuthOptions = [ + { value: 'auth=MSCHAPV2', label: t('settings.network.wifi.authMschapv2') }, + { value: 'auth=GTC', label: t('settings.network.wifi.authGtc') }, + { value: 'auth=MD5', label: t('settings.network.wifi.authMd5') } + ]; + const ttlsInnerAuthOptions = [ + { value: 'auth=MSCHAPV2', label: t('settings.network.wifi.authMschapv2') }, + { value: 'auth=MSCHAP', label: t('settings.network.wifi.authMschap') }, + { value: 'auth=CHAP', label: t('settings.network.wifi.authChap') }, + { value: 'auth=PAP', label: t('settings.network.wifi.authPap') } + ]; + if (eap === 'PEAP') return peapInnerAuthOptions; + if (eap === 'TTLS') return ttlsInnerAuthOptions; + return []; +}; + +const usesPassword = (eap: EAPMethod) => eap !== 'TLS'; +const usesInnerAuth = (eap: EAPMethod) => eap === 'PEAP' || eap === 'TTLS'; + +function isValidIPv4(value: string) { + const parts = value.split('.'); + if (parts.length !== 4) return false; + + return parts.every((part) => { + if (!/^\d+$/.test(part)) return false; + if (part.length > 1 && part.startsWith('0')) return false; + + const number = Number(part); + return number >= 0 && number <= 255; + }); +} + +function isValidSubnetMask(value: string) { + const parts = value.split('.'); + if (parts.length !== 4) return false; + + const bytes = parts.map((part) => { + if (!/^\d+$/.test(part)) return NaN; + const number = Number(part); + if (number < 0 || number > 255) return NaN; + return number; + }); + if (bytes.some(Number.isNaN)) return false; + + const mask = bytes.map((byte) => byte.toString(2).padStart(8, '0')).join(''); + if (!/^1*0*$/.test(mask)) return false; + + const ones = mask.indexOf('0') === -1 ? 32 : mask.indexOf('0'); + return ones >= 1 && ones <= 32; +} export const Wifi = () => { const { t } = useTranslation(); @@ -16,13 +83,22 @@ export const Wifi = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [mode, setMode] = useState('psk'); + const [ipMode, setIPMode] = useState('dhcp'); + const [address, setAddress] = useState(''); + const [subnetMask, setSubnetMask] = useState(''); + const [gateway, setGateway] = useState(''); const [ssid, setSsid] = useState(''); const [password, setPassword] = useState(''); + const [passwordSet, setPasswordSet] = useState(false); const [identity, setIdentity] = useState(''); - const [eap, setEap] = useState('PEAP'); + const [eap, setEap] = useState('PEAP'); const [phase2, setPhase2] = useState('auth=MSCHAPV2'); const [anonymousIdentity, setAnonymousIdentity] = useState(''); const [caCert, setCaCert] = useState(''); + const [clientCert, setClientCert] = useState(''); + const [privateKey, setPrivateKey] = useState(''); + const [privateKeyPasswd, setPrivateKeyPasswd] = useState(''); + const [privateKeyPasswdSet, setPrivateKeyPasswdSet] = useState(false); const [domainSuffixMatch, setDomainSuffixMatch] = useState(''); const [status, setStatus] = useState<'' | 'connecting' | 'disconnecting'>(''); const [message, setMessage] = useState(''); @@ -41,6 +117,24 @@ export const Wifi = () => { setIsSupported(!!rsp.data?.supported); setIsAPMode(!!rsp.data?.apMode); + setMode(rsp.data?.mode === 'enterprise' ? 'enterprise' : 'psk'); + setIPMode(rsp.data?.ipMode === 'manual' ? 'manual' : 'dhcp'); + setAddress(rsp.data?.address || ''); + setSubnetMask(rsp.data?.subnetMask || ''); + setGateway(rsp.data?.gateway || ''); + setSsid(rsp.data?.ssid || ''); + setPassword(''); + setPasswordSet(!!rsp.data?.passwordSet); + setIdentity(rsp.data?.identity || ''); + setEap((rsp.data?.eap as EAPMethod) || 'PEAP'); + setPhase2(rsp.data?.phase2 || 'auth=MSCHAPV2'); + setAnonymousIdentity(rsp.data?.anonymousIdentity || ''); + setCaCert(rsp.data?.caCert || ''); + setClientCert(rsp.data?.clientCert || ''); + setPrivateKey(rsp.data?.privateKey || ''); + setPrivateKeyPasswd(''); + setPrivateKeyPasswdSet(!!rsp.data?.privateKeyPasswdSet); + setDomainSuffixMatch(rsp.data?.domainSuffixMatch || ''); if (rsp.data?.connected && rsp.data?.ssid) { setConnectedWifi(rsp.data.ssid); @@ -55,7 +149,14 @@ export const Wifi = () => { async function connect() { setMessage(''); - if (!ssid || !password || (mode === 'enterprise' && !identity)) return; + if (!ssid || (mode === 'psk' && !password && !passwordSet)) return; + if (mode === 'enterprise' && (!identity || (usesPassword(eap) && !password && !passwordSet))) + return; + if (mode === 'enterprise' && eap === 'TLS' && (!clientCert || !privateKey)) return; + if (ipMode === 'manual' && !isValidIPv4(address)) return; + if (ipMode === 'manual' && !subnetMask) return; + if (ipMode === 'manual' && !isValidSubnetMask(subnetMask)) return; + if (ipMode === 'manual' && gateway && !isValidIPv4(gateway)) return; if (status !== '') return; setStatus('connecting'); @@ -63,11 +164,18 @@ export const Wifi = () => { try { const rsp = await api.connectWifi(ssid, password, { mode, + ipMode, + address, + subnetMask, + gateway, identity, eap, phase2, anonymousIdentity, caCert, + clientCert, + privateKey, + privateKeyPasswd, domainSuffixMatch }); if (rsp.code !== 0) { @@ -106,16 +214,10 @@ export const Wifi = () => { } } - function openModal() { - setMode('psk'); - setSsid(''); + async function openModal() { + await getWiFi(); setPassword(''); - setIdentity(''); - setEap('PEAP'); - setPhase2('auth=MSCHAPV2'); - setAnonymousIdentity(''); - setCaCert(''); - setDomainSuffixMatch(''); + setPrivateKeyPasswd(''); setMessage(''); setIsModalOpen(true); } @@ -125,6 +227,14 @@ export const Wifi = () => { setIsModalOpen(false); } + function changeEap(value: EAPMethod) { + setEap(value); + setPhase2(getDefaultPhase2(value)); + if (value === 'TLS') { + setPassword(''); + } + } + if (!isSupported) { return <>; } @@ -157,12 +267,16 @@ export const Wifi = () => {
@@ -209,12 +323,61 @@ export const Wifi = () => { {/* form */}
+
+
+ {t('settings.network.wifi.ipConfig')} +
+ setIPMode(value as WiFiIPMode)} + /> + + {ipMode === 'manual' && ( +
+
+ {t('settings.network.dns.ipAddress')} + setAddress(e.target.value)} + status={address !== '' && !isValidIPv4(address) ? 'error' : undefined} + /> +
+
+ {t('settings.network.dns.subnetMask')} + setSubnetMask(e.target.value)} + status={subnetMask !== '' && !isValidSubnetMask(subnetMask) ? 'error' : undefined} + /> +
+
+ {t('settings.network.dns.router')} + setGateway(e.target.value)} + status={gateway !== '' && !isValidIPv4(gateway) ? 'error' : undefined} + /> +
+
+ )} +
+ +
+ } + placeholder={t('settings.network.wifi.identity')} onChange={(e) => setIdentity(e.target.value)} /> + {usesPassword(eap) && ( + } + placeholder={ + passwordSet + ? t('settings.network.ethernet.passwordUnchanged') + : t('settings.network.wifi.password') + } + onChange={(e) => setPassword(e.target.value)} + /> + )} setPhase2(e.target.value)} + options={getEapOptions(t)} + placeholder={t('settings.network.wifi.authentication')} + onChange={changeEap} /> + {usesInnerAuth(eap) && ( + setAnonymousIdentity(e.target.value)} /> + {eap === 'TLS' && ( + <> + setClientCert(e.target.value)} + /> + setPrivateKey(e.target.value)} + /> + setPrivateKeyPasswd(e.target.value)} + /> + + )} setCaCert(e.target.value)} /> setDomainSuffixMatch(e.target.value)} /> diff --git a/web/src/pages/wifi/index.tsx b/web/src/pages/wifi/index.tsx index c66df0a7..4cf5cab6 100644 --- a/web/src/pages/wifi/index.tsx +++ b/web/src/pages/wifi/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { CheckOutlined, KeyOutlined, LockOutlined, WifiOutlined } from '@ant-design/icons'; +import { CheckOutlined, KeyOutlined, LockOutlined, UserOutlined, WifiOutlined } from '@ant-design/icons'; import { Button, Form, Input, Select } from 'antd'; import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom'; @@ -9,6 +9,40 @@ import { Head } from '@/components/head.tsx'; type State = '' | 'loading' | 'success' | 'failed' | 'denied'; type VerifyState = '' | 'failed' | 'denied'; +type EAPMethod = 'PEAP' | 'TTLS' | 'TLS' | 'PWD' | 'LEAP'; + +const getDefaultPhase2 = (eap: EAPMethod) => { + if (eap === 'PEAP' || eap === 'TTLS') return 'auth=MSCHAPV2'; + return ''; +}; + +const getEapOptions = (t: (key: string) => string) => [ + { value: 'PEAP', label: t('settings.network.wifi.eapPeap') }, + { value: 'TTLS', label: t('settings.network.wifi.eapTtls') }, + { value: 'TLS', label: t('settings.network.wifi.eapTls') }, + { value: 'PWD', label: t('settings.network.wifi.eapPwd') }, + { value: 'LEAP', label: t('settings.network.wifi.eapLeap') } +]; + +const getInnerAuthOptions = (eap: EAPMethod, t: (key: string) => string) => { + const peapInnerAuthOptions = [ + { value: 'auth=MSCHAPV2', label: t('settings.network.wifi.authMschapv2') }, + { value: 'auth=GTC', label: t('settings.network.wifi.authGtc') }, + { value: 'auth=MD5', label: t('settings.network.wifi.authMd5') } + ]; + const ttlsInnerAuthOptions = [ + { value: 'auth=MSCHAPV2', label: t('settings.network.wifi.authMschapv2') }, + { value: 'auth=MSCHAP', label: t('settings.network.wifi.authMschap') }, + { value: 'auth=CHAP', label: t('settings.network.wifi.authChap') }, + { value: 'auth=PAP', label: t('settings.network.wifi.authPap') } + ]; + if (eap === 'PEAP') return peapInnerAuthOptions; + if (eap === 'TTLS') return ttlsInnerAuthOptions; + return []; +}; + +const usesPassword = (eap: EAPMethod) => eap !== 'TLS'; +const usesInnerAuth = (eap: EAPMethod) => eap === 'PEAP' || eap === 'TTLS'; export const Wifi = () => { const { t } = useTranslation(); @@ -21,6 +55,7 @@ export const Wifi = () => { const [verifyState, setVerifyState] = useState(''); const [form] = Form.useForm(); const mode = Form.useWatch('mode', form); + const eap = (Form.useWatch('eap', form) || 'PEAP') as EAPMethod; useEffect(() => { const pass = searchParams.get('p') || searchParams.get('P'); @@ -55,8 +90,17 @@ export const Wifi = () => { } async function connect(values: any) { - if (!values.ssid || !values.password) return; + if (!values.ssid) return; + if ((values.mode || 'psk') === 'psk' && !values.password) return; if (values.mode === 'enterprise' && !values.identity) return; + if (values.mode === 'enterprise' && usesPassword(values.eap || 'PEAP') && !values.password) + return; + if ( + values.mode === 'enterprise' && + values.eap === 'TLS' && + (!values.clientCert || !values.privateKey) + ) + return; if (state === 'loading') return; setState('loading'); @@ -69,6 +113,9 @@ export const Wifi = () => { phase2: values.phase2, anonymousIdentity: values.anonymousIdentity, caCert: values.caCert, + clientCert: values.clientCert, + privateKey: values.privateKey, + privateKeyPasswd: values.privateKeyPasswd, domainSuffixMatch: values.domainSuffixMatch }); @@ -155,8 +202,8 @@ export const Wifi = () => { } placeholder="SSID" /> - - } placeholder="Password" /> - + {mode !== 'enterprise' && ( + + } + placeholder={t('settings.network.wifi.password')} + /> + + )} {mode === 'enterprise' && ( <> - + } placeholder={t('settings.network.wifi.identity')} /> + {usesPassword(eap) && ( + + } + placeholder={t('settings.network.wifi.password')} + /> + + )} + - + {usesInnerAuth(eap) && ( + + + - + + {eap === 'TLS' && ( + <> + + + + + + + + + + + + + )} + - + )} From 984d0f60731442e3f747d65854273853ebbe0390 Mon Sep 17 00:00:00 2001 From: YipKo <17290550+YipKo@users.noreply.github.com> Date: Wed, 27 May 2026 03:59:51 +0800 Subject: [PATCH 5/5] chore: add translations for network settings updates --- web/src/i18n/locales/ca.ts | 63 +++++++++++++++++++++++++++++++---- web/src/i18n/locales/cz.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/da.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/de.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/es.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/fr.ts | 55 ++++++++++++++++++++++++++++-- web/src/i18n/locales/hu.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/id.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/it.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/ja.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/ko.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/nb.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/nl.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/pl.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/pt_br.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/ru.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/se.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/th.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/tr.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/uk.ts | 57 +++++++++++++++++++++++++++++-- web/src/i18n/locales/vi.ts | 53 ++++++++++++++++++++++++++++- web/src/i18n/locales/zh.ts | 2 +- web/src/i18n/locales/zh_tw.ts | 2 +- 23 files changed, 1102 insertions(+), 31 deletions(-) diff --git a/web/src/i18n/locales/ca.ts b/web/src/i18n/locales/ca.ts index 57b1a072..78c236aa 100644 --- a/web/src/i18n/locales/ca.ts +++ b/web/src/i18n/locales/ca.ts @@ -9,21 +9,21 @@ const ca = { }, auth: { login: 'Inici de sessió', - placeholderUsername: "Nom d'usuari", + placeholderUsername: "Nom d\'usuari", placeholderPassword: 'Contrasenya', placeholderPassword2: 'Torna a introduir la contrasenya', - noEmptyUsername: "Cal introduir el nom d'usuari", + noEmptyUsername: "Cal introduir el nom d\'usuari", noEmptyPassword: 'Cal introduir la contrasenya', noAccount: - "No s'ha pogut obtenir la informació de l'usuari, actualitza la pàgina web o restableix la contrasenya", - invalidUser: "Nom d'usuari o contrasenya invàlids", + "No s'ha pogut obtenir la informació de l\'usuari, actualitza la pàgina web o restableix la contrasenya", + invalidUser: "Nom d\'usuari o contrasenya invàlids", locked: 'Massa inicis de sessió, si us plau, torna-ho a provar més tard', globalLocked: 'Sistema sota protecció, torneu-ho a provar més tard', error: 'Error inesperat', changePassword: 'Canviar la contrasenya', changePasswordDesc: 'Per a la seguretat del dispositiu, canvia la contrasenya!', differentPassword: 'Les contrasenyes no coincideixen', - illegalUsername: "El nom d'usuari conté caràcters no permesos", + illegalUsername: "El nom d\'usuari conté caràcters no permesos", illegalPassword: 'La contrasenya conté caràcters no permesos', forgetPassword: 'Has oblidat la contrasenya', ok: "D'acord", @@ -381,8 +381,54 @@ const ca = { password: 'Contrasenya', joinBtn: 'Connecta', confirmBtn: "D'acord", - cancelBtn: 'Cancel·la' + cancelBtn: 'Cancel·la', + ipConfig: 'Configuració IP', + ipConfigDescription: 'Trieu DHCP o establiu una adreça estàtica per a aquesta connexió Wi-Fi', + security: 'Seguretat', + personal: 'Personal / WPA-PSK', + enterprise: 'Empresa / 802.1X', + identity: 'Identitat / Nom d\'usuari', + authentication: 'Autenticació', + innerAuthentication: 'Autenticació interna', + anonymousIdentity: 'Identitat anònima (opcional)', + caCert: 'Ruta del certificat CA (opcional)', + domainSuffixMatch: 'Coincidència de sufix de domini (opcional)', + clientCert: 'Ruta del certificat client', + privateKey: 'Ruta de la clau privada', + privateKeyPasswd: 'Contrasenya de la clau privada (opcional)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certificat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Xarxa amb cable', + description: 'Configurar IP i autenticació 802.1X opcional', + connect: 'Configurar xarxa amb cable', + connectDesc: 'Configurar IP per a {{iface}} i activar 802.1X si cal', + ipConfig: 'Configuració IP', + ipConfigDescription: 'Trieu DHCP o establiu una adreça estàtica per a aquesta interfície amb cable', + authTitle: 'Autenticació 802.1X', + authDescription: 'Activeu només si la vostra xarxa amb cable requereix autenticació per nom d\'usuari, contrasenya o certificat', + driverRequired: '802.1X amb cable necessita un wpa_supplicant del sistema amb suport de controlador wired', + addressPlaceholder: 'Adreça IP estàtica (ex: 192.168.1.10)', + gatewayPlaceholder: 'Passarel·la (opcional)', + passwordUnchanged: 'Sense canvis', + privateKeyPasswdUnchanged: 'Sense canvis', + off: 'Desactivat', + enterprise: 'Empresa / 802.1X', + active: '802.1X activat', + inactive: '802.1X configurat, esperant autenticació', + failed: 'Error en configurar la xarxa amb cable 802.1X. Torneu-ho a provar.' + }, + tls: { description: 'Activa el protocol HTTPS', tip: 'Atenció: Usar HTTPS pot augmentar la latència, sobretot amb vídeo MJPEG.' @@ -409,6 +455,11 @@ const ca = { ipAddress: 'Adreça IP', subnetMask: 'Màscara de subxarxa', router: 'Encaminador', + wired: 'Amb cable', + wireless: 'Sense fil', + signalStrength: 'Intensitat del senyal', + rxRate: 'Velocitat de recepció', + txRate: 'Velocitat de transmissió', none: 'Cap' } }, diff --git a/web/src/i18n/locales/cz.ts b/web/src/i18n/locales/cz.ts index 269ffcaa..f71fab9b 100644 --- a/web/src/i18n/locales/cz.ts +++ b/web/src/i18n/locales/cz.ts @@ -384,8 +384,54 @@ const cz = { password: 'Heslo', joinBtn: 'Připojit', confirmBtn: 'OK', - cancelBtn: 'Zrušit' + cancelBtn: 'Zrušit', + ipConfig: 'Konfigurace IP', + ipConfigDescription: 'Vyberte DHCP nebo nastavte statickou adresu pro toto připojení Wi-Fi', + security: 'Zabezpečení', + personal: 'Osobní / WPA-PSK', + enterprise: 'Firemní / 802.1X', + identity: 'Identita / Uživatelské jméno', + authentication: 'Ověření', + innerAuthentication: 'Vnitřní ověření', + anonymousIdentity: 'Anonymní identita (volitelné)', + caCert: 'Cesta k certifikátu CA (volitelné)', + domainSuffixMatch: 'Shoda přípony domény (volitelné)', + clientCert: 'Cesta klientského certifikátu', + privateKey: 'Cesta soukromého klíče', + privateKeyPasswd: 'Heslo soukromého klíče (volitelné)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certifikát)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Drátová síť', + description: 'Konfigurace IP a volitelného ověření 802.1X', + connect: 'Nakonfigurovat drátovou síť', + connectDesc: 'Nakonfigurujte IP pro {{iface}} a povolte 802.1X v případě potřeby', + ipConfig: 'Konfigurace IP', + ipConfigDescription: 'Vyberte DHCP nebo nastavte statickou adresu pro tento drátový interface', + authTitle: 'Ověření 802.1X', + authDescription: 'Povolte pouze pokud vaše drátová síť vyžaduje ověření pomocí uživatelského jména, hesla nebo certifikátu', + driverRequired: 'Drátové 802.1X vyžaduje systémový wpa_supplicant s podporou ovladače wired', + addressPlaceholder: 'Statická IP adresa (např. 192.168.1.10)', + gatewayPlaceholder: 'Brána (volitelné)', + passwordUnchanged: 'Beze změny', + privateKeyPasswdUnchanged: 'Beze změny', + off: 'Zakázáno', + enterprise: 'Firemní / 802.1X', + active: '802.1X povoleno', + inactive: '802.1X nakonfigurováno, čeká na ověření', + failed: 'Nepodařilo se nakonfigurovat drátovou síť 802.1X. Zkuste to prosím znovu.' + }, + tls: { description: 'Povolit protokol HTTPS', tip: 'Upozornění: Použití HTTPS může zvýšit latenci, zejména v režimu videa MJPEG.' @@ -412,6 +458,11 @@ const cz = { ipAddress: 'IP adresa', subnetMask: 'Maska podsítě', router: 'Router', + wired: 'Drátová', + wireless: 'Bezdrátová', + signalStrength: 'Síla signálu', + rxRate: 'Rychlost příjmu', + txRate: 'Rychlost odesílání', none: 'Žádné' } }, diff --git a/web/src/i18n/locales/da.ts b/web/src/i18n/locales/da.ts index fb2a52b5..71489962 100644 --- a/web/src/i18n/locales/da.ts +++ b/web/src/i18n/locales/da.ts @@ -383,8 +383,54 @@ const da = { password: 'Adgangskode', joinBtn: 'Tilslut', confirmBtn: 'OK', - cancelBtn: 'Annuller' + cancelBtn: 'Annuller', + ipConfig: 'IP-konfiguration', + ipConfigDescription: 'Vælg DHCP eller angiv en statisk adresse for denne Wi-Fi-forbindelse', + security: 'Sikkerhed', + personal: 'Personlig / WPA-PSK', + enterprise: 'Erhverv / 802.1X', + identity: 'Identitet / Brugernavn', + authentication: 'Godkendelse', + innerAuthentication: 'Intern godkendelse', + anonymousIdentity: 'Anonym identitet (valgfrit)', + caCert: 'CA-certifikatsti (valgfrit)', + domainSuffixMatch: 'Domænesuffiks-matchning (valgfrit)', + clientCert: 'Klientcertifikatsti', + privateKey: 'Privat nøglesti', + privateKeyPasswd: 'Adgangskode for privat nøgle (valgfrit)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certifikat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Kablet netværk', + description: 'Konfigurer IP-indstillinger og valgfri 802.1X-godkendelse', + connect: 'Konfigurer kablet netværk', + connectDesc: 'Konfigurer IP for {{iface}} og aktiver 802.1X ved behov', + ipConfig: 'IP-konfiguration', + ipConfigDescription: 'Vælg DHCP eller angiv en statisk adresse for dette kablede interface', + authTitle: '802.1X-godkendelse', + authDescription: 'Aktiver kun hvis dit kablede netværk kræver brugernavn, adgangskode eller certifikatgodkendelse', + driverRequired: 'Kablet 802.1X kræver en system-wpa_supplicant med understøttelse af wired-driver', + addressPlaceholder: 'Statisk IP-adresse (f.eks. 192.168.1.10)', + gatewayPlaceholder: 'Gateway (valgfrit)', + passwordUnchanged: 'Ingen ændring', + privateKeyPasswdUnchanged: 'Ingen ændring', + off: 'Deaktiveret', + enterprise: 'Erhverv / 802.1X', + active: '802.1X aktiveret', + inactive: '802.1X konfigureret, venter på godkendelse', + failed: 'Konfiguration af kablet netværk 802.1X mislykkedes. Prøv venligst igen.' + }, + tls: { description: 'Aktiver HTTPS-protokol', tip: 'Bemærk: Brug af HTTPS kan øge forsinkelsen, især med MJPEG-videotilstand.' @@ -411,6 +457,11 @@ const da = { ipAddress: 'IP-adresse', subnetMask: 'Undernetmaske', router: 'Router', + wired: 'Kablet', + wireless: 'Trådløst', + signalStrength: 'Signalstyrke', + rxRate: 'Modtagelseshastighed', + txRate: 'Sendehastighed', none: 'Ingen' } }, diff --git a/web/src/i18n/locales/de.ts b/web/src/i18n/locales/de.ts index 8cbaf2b2..135ec7e8 100644 --- a/web/src/i18n/locales/de.ts +++ b/web/src/i18n/locales/de.ts @@ -388,8 +388,54 @@ const de = { password: 'Passwort', joinBtn: 'Verbinden', confirmBtn: 'OK', - cancelBtn: 'Abbrechen' + cancelBtn: 'Abbrechen', + ipConfig: 'IP-Konfiguration', + ipConfigDescription: 'DHCP oder statische Adresse für diese Wi-Fi-Verbindung wählen', + security: 'Sicherheit', + personal: 'Persönlich / WPA-PSK', + enterprise: 'Unternehmen / 802.1X', + identity: 'Identität / Benutzername', + authentication: 'Authentifizierung', + innerAuthentication: 'Innere Authentifizierung', + anonymousIdentity: 'Anonyme Identität (optional)', + caCert: 'CA-Zertifikatpfad (optional)', + domainSuffixMatch: 'Domänensuffix-Abgleich (optional)', + clientCert: 'Client-Zertifikatpfad', + privateKey: 'Privater Schlüsselpfad', + privateKeyPasswd: 'Passwort für privaten Schlüssel (optional)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Zertifikat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Kabelgebundenes Netzwerk', + description: 'IP-Einstellungen und optionale 802.1X-Authentifizierung konfigurieren', + connect: 'Kabelgebundenes Netzwerk konfigurieren', + connectDesc: 'IP-Konfiguration für {{iface}} festlegen und bei Bedarf 802.1X aktivieren', + ipConfig: 'IP-Konfiguration', + ipConfigDescription: 'DHCP oder statische Adresse für diese kabelgebundene Schnittstelle wählen', + authTitle: '802.1X-Authentifizierung', + authDescription: 'Nur aktivieren, wenn Ihr kabelgebundenes Netzwerk Benutzername, Passwort oder Zertifikat-Authentifizierung erfordert', + driverRequired: 'Kabelgebundenes 802.1X erfordert ein wpa_supplicant-System mit Wired-Treiber-Unterstützung', + addressPlaceholder: 'Statische IP-Adresse (z.B. 192.168.1.10)', + gatewayPlaceholder: 'Gateway (optional)', + passwordUnchanged: 'Keine Änderung', + privateKeyPasswdUnchanged: 'Keine Änderung', + off: 'Deaktiviert', + enterprise: 'Unternehmen / 802.1X', + active: '802.1X aktiviert', + inactive: '802.1X konfiguriert, warte auf Authentifizierung', + failed: 'Konfiguration von Kabelgebundenes Netzwerk 802.1X fehlgeschlagen. Bitte erneut versuchen.' + }, + tls: { description: 'HTTPS-Protokoll aktivieren', tip: 'Hinweis: Die Verwendung von HTTPS kann die Latenz erhöhen, besonders im MJPEG-Videomodus.' @@ -416,6 +462,11 @@ const de = { ipAddress: 'IP-Adresse', subnetMask: 'Subnetzmaske', router: 'Router', + wired: 'Kabelgebunden', + wireless: 'Drahtlos', + signalStrength: 'Signalstärke', + rxRate: 'Empfangsrate', + txRate: 'Sendrate', none: 'Keine' } }, diff --git a/web/src/i18n/locales/es.ts b/web/src/i18n/locales/es.ts index 8f789dd6..a71cc108 100644 --- a/web/src/i18n/locales/es.ts +++ b/web/src/i18n/locales/es.ts @@ -385,8 +385,54 @@ const es = { password: 'Contraseña', joinBtn: 'Unirse', confirmBtn: 'Aceptar', - cancelBtn: 'Cancelar' + cancelBtn: 'Cancelar', + ipConfig: 'Configuración IP', + ipConfigDescription: 'Elegir DHCP o establecer una dirección estática para esta conexión Wi-Fi', + security: 'Seguridad', + personal: 'Personal / WPA-PSK', + enterprise: 'Empresa / 802.1X', + identity: 'Identidad / Nombre de usuario', + authentication: 'Autenticación', + innerAuthentication: 'Autenticación interna', + anonymousIdentity: 'Identidad anónima (opcional)', + caCert: 'Ruta del certificado CA (opcional)', + domainSuffixMatch: 'Coincidencia de sufijo de dominio (opcional)', + clientCert: 'Ruta del certificado cliente', + privateKey: 'Ruta de la clave privada', + privateKeyPasswd: 'Contraseña de la clave privada (opcional)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certificado)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Red cableada', + description: 'Configurar IP y autenticación 802.1X opcional', + connect: 'Configurar red cableada', + connectDesc: 'Configurar IP para {{iface}} y habilitar 802.1X si es necesario', + ipConfig: 'Configuración IP', + ipConfigDescription: 'Elegir DHCP o establecer una dirección estática para esta interfaz cableada', + authTitle: 'Autenticación 802.1X', + authDescription: 'Active solo si su red cableada requiere autenticación por usuario, contraseña o certificado', + driverRequired: '802.1X cableado necesita un wpa_supplicant del sistema con soporte de controlador wired', + addressPlaceholder: 'Dirección IP estática (ej: 192.168.1.10)', + gatewayPlaceholder: 'Puerta de enlace (opcional)', + passwordUnchanged: 'Sin cambios', + privateKeyPasswdUnchanged: 'Sin cambios', + off: 'Deshabilitado', + enterprise: 'Empresa / 802.1X', + active: '802.1X habilitado', + inactive: '802.1X configurado, esperando autenticación', + failed: 'Error al configurar la red cableada 802.1X. Por favor, inténtelo de nuevo.' + }, + tls: { description: 'Habilitar protocolo HTTPS', tip: 'Aviso: Usar HTTPS puede aumentar la latencia, especialmente en modo de vídeo MJPEG.' @@ -413,6 +459,11 @@ const es = { ipAddress: 'Dirección IP', subnetMask: 'Máscara de subred', router: 'Router', + wired: 'Cableada', + wireless: 'Inalámbrica', + signalStrength: 'Intensidad de señal', + rxRate: 'Velocidad de recepción', + txRate: 'Velocidad de transmisión', none: 'Ninguno' } }, diff --git a/web/src/i18n/locales/fr.ts b/web/src/i18n/locales/fr.ts index d46989ea..72615dbb 100644 --- a/web/src/i18n/locales/fr.ts +++ b/web/src/i18n/locales/fr.ts @@ -15,7 +15,7 @@ const fr = { noEmptyUsername: "Le nom d'utilisateur ne peut pas être vide", noEmptyPassword: 'Le mot de passe ne peut pas être vide', noAccount: - "Impossible de récupérer les informations de l'utilisateur, veuillez rafraîchir la page ou réinitialiser le mot de passe", + "Impossible de récupérer les informations de l\'utilisateur, veuillez rafraîchir la page ou réinitialiser le mot de passe", invalidUser: "Nom d'utilisateur ou mot de passe invalide", locked: 'Trop de connexions, veuillez réessayer plus tard', globalLocked: 'Système sous protection, veuillez réessayer plus tard', @@ -387,8 +387,54 @@ const fr = { password: 'Mot de passe', joinBtn: 'Rejoindre', confirmBtn: 'OK', - cancelBtn: 'Annuler' + cancelBtn: 'Annuler', + ipConfig: 'Configuration IP', + ipConfigDescription: 'Choisir DHCP ou définir une adresse statique pour cette connexion Wi-Fi', + security: 'Sécurité', + personal: 'Personnel / WPA-PSK', + enterprise: 'Entreprise / 802.1X', + identity: 'Identifiant / Nom d\'utilisateur', + authentication: 'Authentification', + innerAuthentication: 'Authentification interne', + anonymousIdentity: 'Identité anonyme (optionnel)', + caCert: 'Chemin du certificat CA (optionnel)', + domainSuffixMatch: 'Correspondance du suffixe de domaine (optionnel)', + clientCert: 'Chemin du certificat client', + privateKey: 'Chemin de la clé privée', + privateKeyPasswd: 'Mot de passe de la clé privée (optionnel)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certificat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Réseau filaire', + description: 'Configurer les paramètres IP et l\'authentification 802.1X optionnelle', + connect: 'Configurer le réseau filaire', + connectDesc: 'Configurer les paramètres IP pour {{iface}} et activer 802.1X si nécessaire', + ipConfig: 'Configuration IP', + ipConfigDescription: 'Choisir DHCP ou définir une adresse statique pour cette interface filaire', + authTitle: 'Authentification 802.1X', + authDescription: 'Activez uniquement si votre réseau filaire nécessite une authentification par nom d\'utilisateur, mot de passe ou certificat', + driverRequired: 'Le réseau filaire 802.1X nécessite un système wpa_supplicant avec prise en charge du pilote wired', + addressPlaceholder: 'Adresse IP statique (ex: 192.168.1.10)', + gatewayPlaceholder: 'Passerelle (optionnel)', + passwordUnchanged: 'Aucun changement', + privateKeyPasswdUnchanged: 'Aucun changement', + off: 'Désactivé', + enterprise: 'Entreprise / 802.1X', + active: '802.1X activé', + inactive: '802.1X configuré, en attente d\'authentification', + failed: 'Échec de la configuration du réseau filaire 802.1X. Veuillez réessayer.' + }, + tls: { description: 'Activer le protocole HTTPS', tip: "Attention : l'utilisation de HTTPS peut augmenter la latence, surtout en mode vidéo MJPEG." @@ -415,6 +461,11 @@ const fr = { ipAddress: 'Adresse IP', subnetMask: 'Masque de sous-réseau', router: 'Routeur', + wired: 'Filaire', + wireless: 'Sans fil', + signalStrength: 'Force du signal', + rxRate: 'Débit réception', + txRate: 'Débit émission', none: 'Aucun' } }, diff --git a/web/src/i18n/locales/hu.ts b/web/src/i18n/locales/hu.ts index 2d20380a..85d1e9bb 100644 --- a/web/src/i18n/locales/hu.ts +++ b/web/src/i18n/locales/hu.ts @@ -386,8 +386,54 @@ const hu = { password: 'Jelszó', joinBtn: 'Csatlakozás', confirmBtn: 'OK', - cancelBtn: 'Mégse' + cancelBtn: 'Mégse', + ipConfig: 'IP konfiguráció', + ipConfigDescription: 'Válassza a DHCP-t vagy állítson be statikus címet ehhez a Wi-Fi kapcsolathoz', + security: 'Biztonság', + personal: 'Személyes / WPA-PSK', + enterprise: 'Vállalati / 802.1X', + identity: 'Azonosító / Felhasználónév', + authentication: 'Hitelesítés', + innerAuthentication: 'Belső hitelesítés', + anonymousIdentity: 'Névtelen azonosító (opcionális)', + caCert: 'CA tanúsítvány elérési útja (opcionális)', + domainSuffixMatch: 'Tartomány utótag egyezés (opcionális)', + clientCert: 'Kliens tanúsítvány elérési útja', + privateKey: 'Privát kulcs elérési útja', + privateKeyPasswd: 'Privát kulcs jelszava (opcionális)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Tanúsítvány)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Vezetékes hálózat', + description: 'IP beállítások és opcionális 802.1X hitelesítés konfigurálása', + connect: 'Vezetékes hálózat konfigurálása', + connectDesc: 'IP konfiguráció beállítása a(z) {{iface}} számára és 802.1X engedélyezése szükség esetén', + ipConfig: 'IP konfiguráció', + ipConfigDescription: 'Válassza a DHCP-t vagy állítson be statikus címet ehhez a vezetékes interfészhez', + authTitle: '802.1X hitelesítés', + authDescription: 'Csak akkor engedélyezze, ha a vezetékes hálózat felhasználónévvel, jelszóval vagy tanúsítvánnyal történő hitelesítést igényel', + driverRequired: 'A vezetékes 802.1X egy wired meghajtó támogatással rendelkező rendszer wpa_supplicant-t igényel', + addressPlaceholder: 'Statikus IP-cím (pl. 192.168.1.10)', + gatewayPlaceholder: 'Átjáró (opcionális)', + passwordUnchanged: 'Nincs változás', + privateKeyPasswdUnchanged: 'Nincs változás', + off: 'Letiltva', + enterprise: 'Vállalati / 802.1X', + active: '802.1X engedélyezve', + inactive: '802.1X konfigurálva, hitelesítésre vár', + failed: 'A vezetékes hálózat 802.1X konfigurálása sikertelen. Kérjük, próbálja újra.' + }, + tls: { description: 'HTTPS protokoll engedélyezése', tip: 'Figyelem: A HTTPS használata növelheti a késleltetést, különösen MJPEG videó módban.' @@ -414,6 +460,11 @@ const hu = { ipAddress: 'IP-cím', subnetMask: 'Alhálózati maszk', router: 'Router', + wired: 'Vezetékes', + wireless: 'Vezeték nélküli', + signalStrength: 'Jelerősség', + rxRate: 'Fogadási sebesség', + txRate: 'Küldési sebesség', none: 'Nincs' } }, diff --git a/web/src/i18n/locales/id.ts b/web/src/i18n/locales/id.ts index 7c37c4a1..105f0d3a 100644 --- a/web/src/i18n/locales/id.ts +++ b/web/src/i18n/locales/id.ts @@ -383,8 +383,54 @@ const id = { password: 'Kata sandi', joinBtn: 'Gabung', confirmBtn: 'OK', - cancelBtn: 'Batal' + cancelBtn: 'Batal', + ipConfig: 'Konfigurasi IP', + ipConfigDescription: 'Pilih DHCP atau atur alamat statis untuk koneksi Wi-Fi ini', + security: 'Keamanan', + personal: 'Personal / WPA-PSK', + enterprise: 'Enterprise / 802.1X', + identity: 'Identitas / Nama pengguna', + authentication: 'Autentikasi', + innerAuthentication: 'Autentikasi internal', + anonymousIdentity: 'Identitas anonim (opsional)', + caCert: 'Jalur sertifikat CA (opsional)', + domainSuffixMatch: 'Pencocokan akhiran domain (opsional)', + clientCert: 'Jalur sertifikat klien', + privateKey: 'Jalur kunci privat', + privateKeyPasswd: 'Kata sandi kunci privat (opsional)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Sertifikat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Jaringan berkabel', + description: 'Konfigurasi IP dan autentikasi 802.1X opsional', + connect: 'Konfigurasi jaringan berkabel', + connectDesc: 'Atur konfigurasi IP untuk {{iface}} dan aktifkan 802.1X jika diperlukan', + ipConfig: 'Konfigurasi IP', + ipConfigDescription: 'Pilih DHCP atau atur alamat statis untuk antarmuka berkabel ini', + authTitle: 'Autentikasi 802.1X', + authDescription: 'Aktifkan hanya jika jaringan berkabel Anda memerlukan autentikasi nama pengguna, kata sandi, atau sertifikat', + driverRequired: '802.1X berkabel memerlukan build wpa_supplicant sistem dengan dukungan driver wired', + addressPlaceholder: 'Alamat IP statis (mis: 192.168.1.10)', + gatewayPlaceholder: 'Gateway (opsional)', + passwordUnchanged: 'Tidak ada perubahan', + privateKeyPasswdUnchanged: 'Tidak ada perubahan', + off: 'Nonaktif', + enterprise: 'Enterprise / 802.1X', + active: '802.1X aktif', + inactive: '802.1X dikonfigurasi, menunggu autentikasi', + failed: 'Gagal mengkonfigurasi jaringan berkabel 802.1X. Silakan coba lagi.' + }, + tls: { description: 'Aktifkan protokol HTTPS', tip: 'Perhatian: Menggunakan HTTPS dapat meningkatkan latensi, terutama pada mode video MJPEG.' @@ -411,6 +457,11 @@ const id = { ipAddress: 'Alamat IP', subnetMask: 'Subnet mask', router: 'Router', + wired: 'Berkabel', + wireless: 'Nirkabel', + signalStrength: 'Kekuatan sinyal', + rxRate: 'Kecepatan terima', + txRate: 'Kecepatan kirim', none: 'Tidak ada' } }, diff --git a/web/src/i18n/locales/it.ts b/web/src/i18n/locales/it.ts index 8df8546f..8c066c0c 100644 --- a/web/src/i18n/locales/it.ts +++ b/web/src/i18n/locales/it.ts @@ -387,8 +387,54 @@ const it = { password: 'Password', joinBtn: 'Connetti', confirmBtn: 'OK', - cancelBtn: 'Annulla' + cancelBtn: 'Annulla', + ipConfig: 'Configurazione IP', + ipConfigDescription: 'Scegli DHCP o imposta un indirizzo statico per questa connessione Wi-Fi', + security: 'Sicurezza', + personal: 'Personale / WPA-PSK', + enterprise: 'Aziendale / 802.1X', + identity: 'Identità / Nome utente', + authentication: 'Autenticazione', + innerAuthentication: 'Autenticazione interna', + anonymousIdentity: 'Identità anonima (opzionale)', + caCert: 'Percorso certificato CA (opzionale)', + domainSuffixMatch: 'Corrispondenza suffisso dominio (opzionale)', + clientCert: 'Percorso certificato client', + privateKey: 'Percorso chiave privata', + privateKeyPasswd: 'Password chiave privata (opzionale)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certificato)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Rete cablata', + description: 'Configura IP e autenticazione 802.1X opzionale', + connect: 'Configura rete cablata', + connectDesc: 'Configura IP per {{iface}} e abilita 802.1X se necessario', + ipConfig: 'Configurazione IP', + ipConfigDescription: 'Scegli DHCP o imposta un indirizzo statico per questa interfaccia cablata', + authTitle: 'Autenticazione 802.1X', + authDescription: 'Abilita solo se la rete cablata richiede autenticazione tramite nome utente, password o certificato', + driverRequired: '802.1X cablato richiede un wpa_supplicant di sistema con supporto driver wired', + addressPlaceholder: 'Indirizzo IP statico (es: 192.168.1.10)', + gatewayPlaceholder: 'Gateway (opzionale)', + passwordUnchanged: 'Nessuna modifica', + privateKeyPasswdUnchanged: 'Nessuna modifica', + off: 'Disabilitato', + enterprise: 'Aziendale / 802.1X', + active: '802.1X abilitato', + inactive: '802.1X configurato, in attesa di autenticazione', + failed: 'Configurazione della rete cablata 802.1X non riuscita. Riprova.' + }, + tls: { description: 'Abilita protocollo HTTPS', tip: "Attenzione: l'uso di HTTPS può aumentare la latenza, soprattutto in modalità video MJPEG." @@ -415,6 +461,11 @@ const it = { ipAddress: 'Indirizzo IP', subnetMask: 'Subnet mask', router: 'Router', + wired: 'Cablata', + wireless: 'Wireless', + signalStrength: 'Intensità segnale', + rxRate: 'Velocità ricezione', + txRate: 'Velocità trasmissione', none: 'Nessuno' } }, diff --git a/web/src/i18n/locales/ja.ts b/web/src/i18n/locales/ja.ts index 91b642aa..79354048 100644 --- a/web/src/i18n/locales/ja.ts +++ b/web/src/i18n/locales/ja.ts @@ -385,8 +385,54 @@ const ja = { password: 'パスワード', joinBtn: '接続', confirmBtn: 'OK', - cancelBtn: 'キャンセル' + cancelBtn: 'キャンセル', + ipConfig: 'IP 設定', + ipConfigDescription: 'この Wi-Fi 接続に DHCP または静的アドレスを設定', + security: 'セキュリティ', + personal: 'パーソナル / WPA-PSK', + enterprise: 'エンタープライズ / 802.1X', + identity: 'ID / ユーザー名', + authentication: '認証方式', + innerAuthentication: '内部認証', + anonymousIdentity: '匿名 ID(任意)', + caCert: 'CA 証明書パス(任意)', + domainSuffixMatch: 'ドメインサフィックスマッチ(任意)', + clientCert: 'クライアント証明書パス', + privateKey: '秘密鍵パス', + privateKeyPasswd: '秘密鍵パスワード(任意)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS(証明書)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: '有線ネットワーク', + description: 'IP 設定とオプションの 802.1X 認証を設定', + connect: '有線ネットワークを設定', + connectDesc: '{{iface}} の IP 設定を行い、必要に応じて 802.1X 認証を有効にする', + ipConfig: 'IP 設定', + ipConfigDescription: 'この有線インターフェースに DHCP または静的アドレスを設定', + authTitle: '802.1X 認証', + authDescription: '有線ネットワークでユーザー名、パスワード、または証明書認証が必要な場合のみ有効にしてください', + driverRequired: '有線 802.1X には、wired ドライバーをサポートする wpa_supplicant のシステムビルドが必要です', + addressPlaceholder: '静的 IP アドレス(例: 192.168.1.10)', + gatewayPlaceholder: 'ゲートウェイ(任意)', + passwordUnchanged: '変更なし', + privateKeyPasswdUnchanged: '変更なし', + off: '無効', + enterprise: 'エンタープライズ / 802.1X', + active: '802.1X 有効', + inactive: '802.1X 設定済み、認証待ち', + failed: '有線ネットワーク 802.1X の設定に失敗しました。もう一度お試しください。' + }, + tls: { description: 'HTTPS プロトコルを有効にする', tip: '注意:HTTPS を使用すると、特に MJPEG ビデオモードで遅延が増加する可能性があります。' @@ -413,6 +459,11 @@ const ja = { ipAddress: 'IP アドレス', subnetMask: 'サブネットマスク', router: 'ルーター', + wired: '有線', + wireless: '無線', + signalStrength: '信号強度', + rxRate: '受信速度', + txRate: '送信速度', none: 'なし' } }, diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index 035272c1..c21dd8b7 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -379,8 +379,54 @@ const ko = { password: '비밀번호', joinBtn: '연결', confirmBtn: '확인', - cancelBtn: '취소' + cancelBtn: '취소', + ipConfig: 'IP 설정', + ipConfigDescription: '이 Wi-Fi 연결에 DHCP 또는 고정 주소 설정', + security: '보안', + personal: '개인 / WPA-PSK', + enterprise: '기업 / 802.1X', + identity: 'ID / 사용자 이름', + authentication: '인증 방식', + innerAuthentication: '내부 인증', + anonymousIdentity: '익명 ID (선택)', + caCert: 'CA 인증서 경로 (선택)', + domainSuffixMatch: '도메인 접미사 일치 (선택)', + clientCert: '클라이언트 인증서 경로', + privateKey: '개인키 경로', + privateKeyPasswd: '개인키 비밀번호 (선택)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (인증서)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: '유선 네트워크', + description: 'IP 설정 및 선택적 802.1X 인증 구성', + connect: '유선 네트워크 설정', + connectDesc: '{{iface}}의 IP 설정을 구성하고 필요시 802.1X 인증 활성화', + ipConfig: 'IP 설정', + ipConfigDescription: '이 유선 인터페이스에 DHCP 또는 고정 주소 설정', + authTitle: '802.1X 인증', + authDescription: '유선 네트워크에서 사용자 이름, 비밀번호 또는 인증서 인증이 필요한 경우에만 활성화하세요', + driverRequired: '유선 802.1X에는 wired 드라이버를 지원하는 wpa_supplicant 시스템 빌드가 필요합니다', + addressPlaceholder: '고정 IP 주소 (예: 192.168.1.10)', + gatewayPlaceholder: '게이트웨이 (선택)', + passwordUnchanged: '변경 없음', + privateKeyPasswdUnchanged: '변경 없음', + off: '비활성화', + enterprise: '기업 / 802.1X', + active: '802.1X 활성화됨', + inactive: '802.1X 설정됨, 인증 대기 중', + failed: '유선 네트워크 802.1X 설정에 실패했습니다. 다시 시도해 주세요.' + }, + tls: { description: 'HTTPS 프로토콜 활성화', tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.' @@ -407,6 +453,11 @@ const ko = { ipAddress: 'IP 주소', subnetMask: '서브넷 마스크', router: '라우터', + wired: '유선', + wireless: '무선', + signalStrength: '신호 강도', + rxRate: '수신 속도', + txRate: '송신 속도', none: '없음' } }, diff --git a/web/src/i18n/locales/nb.ts b/web/src/i18n/locales/nb.ts index 5178a7b7..8f6c88b4 100644 --- a/web/src/i18n/locales/nb.ts +++ b/web/src/i18n/locales/nb.ts @@ -383,8 +383,54 @@ const nb = { password: 'Passord', joinBtn: 'Koble til', confirmBtn: 'OK', - cancelBtn: 'Avbryt' + cancelBtn: 'Avbryt', + ipConfig: 'IP-konfigurasjon', + ipConfigDescription: 'Velg DHCP eller angi en statisk adresse for denne Wi-Fi-tilkoblingen', + security: 'Sikkerhet', + personal: 'Personlig / WPA-PSK', + enterprise: 'Bedrift / 802.1X', + identity: 'Identitet / Brukernavn', + authentication: 'Autentisering', + innerAuthentication: 'Indre autentisering', + anonymousIdentity: 'Anonym identitet (valgfritt)', + caCert: 'CA-sertifikatsti (valgfritt)', + domainSuffixMatch: 'Domene suffiks-samsvar (valgfritt)', + clientCert: 'Klientsertifikatsti', + privateKey: 'Privat nøkkelsti', + privateKeyPasswd: 'Passord for privat nøkkel (valgfritt)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Sertifikat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Kablet nettverk', + description: 'Konfigurer IP-innstillinger og valgfri 802.1X-autentisering', + connect: 'Konfigurer kablet nettverk', + connectDesc: 'Konfigurer IP for {{iface}} og aktiver 802.1X ved behov', + ipConfig: 'IP-konfigurasjon', + ipConfigDescription: 'Velg DHCP eller angi en statisk adresse for dette kablede grensesnittet', + authTitle: '802.1X-autentisering', + authDescription: 'Aktiver bare hvis ditt kablede nettverk krever brukernavn, passord eller sertifikat-autentisering', + driverRequired: 'Kablet 802.1X krever en system-wpa_supplicant med støtte for wired-driver', + addressPlaceholder: 'Statisk IP-adresse (f.eks. 192.168.1.10)', + gatewayPlaceholder: 'Gateway (valgfritt)', + passwordUnchanged: 'Ingen endring', + privateKeyPasswdUnchanged: 'Ingen endring', + off: 'Deaktivert', + enterprise: 'Bedrift / 802.1X', + active: '802.1X aktivert', + inactive: '802.1X konfigurert, venter på autentisering', + failed: 'Konfigurasjon av kablet nettverk 802.1X mislyktes. Vennligst prøv igjen.' + }, + tls: { description: 'Aktiver HTTPS-protokoll', tip: 'Merk: Bruk av HTTPS kan øke forsinkelsen, spesielt i MJPEG-videomodus.' @@ -411,6 +457,11 @@ const nb = { ipAddress: 'IP-adresse', subnetMask: 'Subnettmaske', router: 'Ruter', + wired: 'Kablet', + wireless: 'Trådløst', + signalStrength: 'Signalstyrke', + rxRate: 'Mottakshastighet', + txRate: 'Sendingshastighet', none: 'Ingen' } }, diff --git a/web/src/i18n/locales/nl.ts b/web/src/i18n/locales/nl.ts index b7d0e1f9..bba48c6f 100644 --- a/web/src/i18n/locales/nl.ts +++ b/web/src/i18n/locales/nl.ts @@ -387,8 +387,54 @@ const nl = { password: 'Wachtwoord', joinBtn: 'Verbinden', confirmBtn: 'OK', - cancelBtn: 'Annuleren' + cancelBtn: 'Annuleren', + ipConfig: 'IP-configuratie', + ipConfigDescription: 'Kies DHCP of stel een statisch adres in voor deze Wi-Fi verbinding', + security: 'Beveiliging', + personal: 'Persoonlijk / WPA-PSK', + enterprise: 'Zakelijk / 802.1X', + identity: 'Identiteit / Gebruikersnaam', + authentication: 'Authenticatie', + innerAuthentication: 'Interne authenticatie', + anonymousIdentity: 'Anonieme identiteit (optioneel)', + caCert: 'CA-certificaatpad (optioneel)', + domainSuffixMatch: 'Domeinsuffix-overeenkomst (optioneel)', + clientCert: 'Clientcertificaatpad', + privateKey: 'Privésleutelpad', + privateKeyPasswd: 'Privésleutelwachtwoord (optioneel)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certificaat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Bekabeld netwerk', + description: 'IP-instellingen en optionele 802.1X-authenticatie configureren', + connect: 'Bekabeld netwerk configureren', + connectDesc: 'IP-configuratie voor {{iface}} instellen en 802.1X inschakelen indien nodig', + ipConfig: 'IP-configuratie', + ipConfigDescription: 'Kies DHCP of stel een statisch adres in voor deze bekabelde interface', + authTitle: '802.1X-authenticatie', + authDescription: 'Alleen inschakelen als uw bekabelde netwerk gebruikersnaam, wachtwoord of certificaat-authenticatie vereist', + driverRequired: 'Bekabeld 802.1X vereist een systeem wpa_supplicant build met wired driver ondersteuning', + addressPlaceholder: 'Statisch IP-adres (bijv. 192.168.1.10)', + gatewayPlaceholder: 'Gateway (optioneel)', + passwordUnchanged: 'Geen wijziging', + privateKeyPasswdUnchanged: 'Geen wijziging', + off: 'Uitgeschakeld', + enterprise: 'Zakelijk / 802.1X', + active: '802.1X ingeschakeld', + inactive: '802.1X geconfigureerd, wacht op authenticatie', + failed: 'Configuratie van bekabeld netwerk 802.1X mislukt. Probeer het opnieuw.' + }, + tls: { description: 'HTTPS-protocol inschakelen', tip: 'Let op: HTTPS gebruiken kan de latentie verhogen, vooral in MJPEG-videomodus.' @@ -415,6 +461,11 @@ const nl = { ipAddress: 'IP-adres', subnetMask: 'Subnetmasker', router: 'Router', + wired: 'Bekabeld', + wireless: 'Draadloos', + signalStrength: 'Signaalsterkte', + rxRate: 'Ontvangstsnelheid', + txRate: 'Verzendsnelheid', none: 'Geen' } }, diff --git a/web/src/i18n/locales/pl.ts b/web/src/i18n/locales/pl.ts index be69332b..a8aca3cb 100644 --- a/web/src/i18n/locales/pl.ts +++ b/web/src/i18n/locales/pl.ts @@ -386,8 +386,54 @@ const pl = { password: 'Hasło', joinBtn: 'Połącz', confirmBtn: 'OK', - cancelBtn: 'Anuluj' + cancelBtn: 'Anuluj', + ipConfig: 'Konfiguracja IP', + ipConfigDescription: 'Wybierz DHCP lub ustaw statyczny adres dla tego połączenia Wi-Fi', + security: 'Bezpieczeństwo', + personal: 'Osobiste / WPA-PSK', + enterprise: 'Firmowe / 802.1X', + identity: 'Tożsamość / Nazwa użytkownika', + authentication: 'Uwierzytelnianie', + innerAuthentication: 'Uwierzytelnianie wewnętrzne', + anonymousIdentity: 'Anonimowa tożsamość (opcjonalnie)', + caCert: 'Ścieżka certyfikatu CA (opcjonalnie)', + domainSuffixMatch: 'Dopasowanie sufiksu domeny (opcjonalnie)', + clientCert: 'Ścieżka certyfikatu klienta', + privateKey: 'Ścieżka klucza prywatnego', + privateKeyPasswd: 'Hasło klucza prywatnego (opcjonalnie)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certyfikat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Sieć przewodowa', + description: 'Konfiguracja IP i opcjonalnego uwierzytelniania 802.1X', + connect: 'Skonfiguruj sieć przewodową', + connectDesc: 'Skonfiguruj IP dla {{iface}} i włącz 802.1X jeśli potrzeba', + ipConfig: 'Konfiguracja IP', + ipConfigDescription: 'Wybierz DHCP lub ustaw statyczny adres dla tego interfejsu przewodowego', + authTitle: 'Uwierzytelnianie 802.1X', + authDescription: 'Włącz tylko jeśli sieć przewodowa wymaga uwierzytelnienia nazwą użytkownika, hasłem lub certyfikatem', + driverRequired: 'Przewodowe 802.1X wymaga systemowego wpa_supplicant z obsługą sterownika wired', + addressPlaceholder: 'Statyczny adres IP (np. 192.168.1.10)', + gatewayPlaceholder: 'Brama (opcjonalnie)', + passwordUnchanged: 'Bez zmian', + privateKeyPasswdUnchanged: 'Bez zmian', + off: 'Wyłączone', + enterprise: 'Firmowe / 802.1X', + active: '802.1X włączone', + inactive: '802.1X skonfigurowane, oczekiwanie na uwierzytelnienie', + failed: 'Nie udało się skonfigurować sieci przewodowej 802.1X. Spróbuj ponownie.' + }, + tls: { description: 'Włącz protokół HTTPS', tip: 'Uwaga: użycie HTTPS może zwiększyć opóźnienie, szczególnie w trybie wideo MJPEG.' @@ -414,6 +460,11 @@ const pl = { ipAddress: 'Adres IP', subnetMask: 'Maska podsieci', router: 'Router', + wired: 'Przewodowa', + wireless: 'Bezprzewodowa', + signalStrength: 'Siła sygnału', + rxRate: 'Prędkość odbierania', + txRate: 'Prędkość wysyłania', none: 'Brak' } }, diff --git a/web/src/i18n/locales/pt_br.ts b/web/src/i18n/locales/pt_br.ts index e6fd78b3..16aeac0a 100644 --- a/web/src/i18n/locales/pt_br.ts +++ b/web/src/i18n/locales/pt_br.ts @@ -384,8 +384,54 @@ const pt_br = { password: 'Senha', joinBtn: 'Entrar', confirmBtn: 'OK', - cancelBtn: 'Cancelar' + cancelBtn: 'Cancelar', + ipConfig: 'Configuração de IP', + ipConfigDescription: 'Escolher DHCP ou definir um endereço estático para esta conexão Wi-Fi', + security: 'Segurança', + personal: 'Pessoal / WPA-PSK', + enterprise: 'Empresarial / 802.1X', + identity: 'Identidade / Nome de usuário', + authentication: 'Autenticação', + innerAuthentication: 'Autenticação interna', + anonymousIdentity: 'Identidade anônima (opcional)', + caCert: 'Caminho do certificado CA (opcional)', + domainSuffixMatch: 'Correspondência de sufixo de domínio (opcional)', + clientCert: 'Caminho do certificado cliente', + privateKey: 'Caminho da chave privada', + privateKeyPasswd: 'Senha da chave privada (opcional)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certificado)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Rede cabeada', + description: 'Configurar IP e autenticação 802.1X opcional', + connect: 'Configurar rede cabeada', + connectDesc: 'Configurar IP para {{iface}} e habilitar 802.1X se necessário', + ipConfig: 'Configuração de IP', + ipConfigDescription: 'Escolher DHCP ou definir um endereço estático para esta interface cabeada', + authTitle: 'Autenticação 802.1X', + authDescription: 'Ative apenas se sua rede cabeada exigir autenticação por usuário, senha ou certificado', + driverRequired: '802.1X cabeado requer um wpa_supplicant do sistema com suporte ao driver wired', + addressPlaceholder: 'Endereço IP estático (ex: 192.168.1.10)', + gatewayPlaceholder: 'Gateway (opcional)', + passwordUnchanged: 'Sem alteração', + privateKeyPasswdUnchanged: 'Sem alteração', + off: 'Desabilitado', + enterprise: 'Empresarial / 802.1X', + active: '802.1X habilitado', + inactive: '802.1X configurado, aguardando autenticação', + failed: 'Falha ao configurar a rede cabeada 802.1X. Por favor, tente novamente.' + }, + tls: { description: 'Habilitar protocolo HTTPS', tip: 'Atenção: O uso de HTTPS pode aumentar a latência, especialmente com o modo de vídeo MJPEG.' @@ -412,6 +458,11 @@ const pt_br = { ipAddress: 'Endereço IP', subnetMask: 'Máscara de sub-rede', router: 'Roteador', + wired: 'Cabeada', + wireless: 'Sem fio', + signalStrength: 'Intensidade do sinal', + rxRate: 'Taxa de recepção', + txRate: 'Taxa de transmissão', none: 'Nenhum' } }, diff --git a/web/src/i18n/locales/ru.ts b/web/src/i18n/locales/ru.ts index 9b79faa3..3cfa94fa 100644 --- a/web/src/i18n/locales/ru.ts +++ b/web/src/i18n/locales/ru.ts @@ -386,8 +386,54 @@ const ru = { password: 'Пароль', joinBtn: 'Подключить', confirmBtn: 'OK', - cancelBtn: 'Отмена' + cancelBtn: 'Отмена', + ipConfig: 'Настройка IP', + ipConfigDescription: 'Выберите DHCP или установите статический адрес для этого Wi-Fi подключения', + security: 'Безопасность', + personal: 'Личная / WPA-PSK', + enterprise: 'Корпоративная / 802.1X', + identity: 'Идентификатор / Имя пользователя', + authentication: 'Аутентификация', + innerAuthentication: 'Внутренняя аутентификация', + anonymousIdentity: 'Анонимный идентификатор (необязательно)', + caCert: 'Путь к сертификату CA (необязательно)', + domainSuffixMatch: 'Совпадение суффикса домена (необязательно)', + clientCert: 'Путь к клиентскому сертификату', + privateKey: 'Путь к закрытому ключу', + privateKeyPasswd: 'Пароль закрытого ключа (необязательно)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Сертификат)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Проводная сеть', + description: 'Настройка IP и опциональной аутентификации 802.1X', + connect: 'Настроить проводную сеть', + connectDesc: 'Настроить IP для {{iface}} и включить 802.1X при необходимости', + ipConfig: 'Настройка IP', + ipConfigDescription: 'Выберите DHCP или установите статический адрес для этого проводного интерфейса', + authTitle: 'Аутентификация 802.1X', + authDescription: 'Включите только если ваша проводная сеть требует аутентификацию по имени пользователя, паролю или сертификату', + driverRequired: 'Проводной 802.1X требует системный wpa_supplicant с поддержкой драйвера wired', + addressPlaceholder: 'Статический IP-адрес (например: 192.168.1.10)', + gatewayPlaceholder: 'Шлюз (необязательно)', + passwordUnchanged: 'Без изменений', + privateKeyPasswdUnchanged: 'Без изменений', + off: 'Отключено', + enterprise: 'Корпоративная / 802.1X', + active: '802.1X включён', + inactive: '802.1X настроен, ожидание аутентификации', + failed: 'Ошибка настройки проводной сети 802.1X. Пожалуйста, попробуйте снова.' + }, + tls: { description: 'Включить протокол HTTPS', tip: 'Имейте в виду: использование HTTPS может увеличить задержку, особенно в режиме видео MJPEG.' @@ -414,6 +460,11 @@ const ru = { ipAddress: 'IP-адрес', subnetMask: 'Маска подсети', router: 'Маршрутизатор', + wired: 'Проводная', + wireless: 'Беспроводная', + signalStrength: 'Уровень сигнала', + rxRate: 'Скорость приёма', + txRate: 'Скорость передачи', none: 'Нет' } }, diff --git a/web/src/i18n/locales/se.ts b/web/src/i18n/locales/se.ts index 7662d627..2fa3c8c0 100644 --- a/web/src/i18n/locales/se.ts +++ b/web/src/i18n/locales/se.ts @@ -380,8 +380,54 @@ const se = { password: 'Lösenord', joinBtn: 'Anslut', confirmBtn: 'OK', - cancelBtn: 'Avbryt' + cancelBtn: 'Avbryt', + ipConfig: 'IP-konfiguration', + ipConfigDescription: 'Välj DHCP eller ange en statisk adress för detta Wi-Fi-anslutning', + security: 'Säkerhet', + personal: 'Personlig / WPA-PSK', + enterprise: 'Företag / 802.1X', + identity: 'Identitet / Användarnamn', + authentication: 'Autentisering', + innerAuthentication: 'Inre autentisering', + anonymousIdentity: 'Anonym identitet (valfritt)', + caCert: 'CA-certifikatsökväg (valfritt)', + domainSuffixMatch: 'Domänuffix-matchning (valfritt)', + clientCert: 'Klientcertifikatsökväg', + privateKey: 'Privat nyckelsökväg', + privateKeyPasswd: 'Lösenord för privat nyckel (valfritt)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Certifikat)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Trådbundet nätverk', + description: 'Konfigurera IP-inställningar och valfri 802.1X-autentisering', + connect: 'Konfigurera trådbundet nätverk', + connectDesc: 'Konfigurera IP för {{iface}} och aktivera 802.1X vid behov', + ipConfig: 'IP-konfiguration', + ipConfigDescription: 'Välj DHCP eller ange en statisk adress för detta trådbundna gränssnitt', + authTitle: '802.1X-autentisering', + authDescription: 'Aktivera endast om ditt trådbundna nätverk kräver användarnamn, lösenord eller certifikat-autentisering', + driverRequired: 'Trådbundet 802.1X kräver en system-wpa_supplicant med stöd för wired-drivrutin', + addressPlaceholder: 'Statisk IP-adress (t.ex. 192.168.1.10)', + gatewayPlaceholder: 'Gateway (valfritt)', + passwordUnchanged: 'Ingen ändring', + privateKeyPasswdUnchanged: 'Ingen ändring', + off: 'Inaktiverad', + enterprise: 'Företag / 802.1X', + active: '802.1X aktiverad', + inactive: '802.1X konfigurerad, väntar på autentisering', + failed: 'Misslyckades med att konfigurera trådbundet nätverk 802.1X. Försök igen.' + }, + tls: { description: 'Aktivera HTTPS-protokoll', tip: 'Observera: Användning av HTTPS kan öka fördröjningen, särskilt med MJPEG-läge.' @@ -408,6 +454,11 @@ const se = { ipAddress: 'IP-adress', subnetMask: 'Subnätmask', router: 'Router', + wired: 'Trådbundet', + wireless: 'Trådlöst', + signalStrength: 'Signalstyrka', + rxRate: 'Mottagningshastighet', + txRate: 'Sändningshastighet', none: 'Ingen' } }, diff --git a/web/src/i18n/locales/th.ts b/web/src/i18n/locales/th.ts index bf2d5a35..c26a8c2d 100644 --- a/web/src/i18n/locales/th.ts +++ b/web/src/i18n/locales/th.ts @@ -377,8 +377,54 @@ const th = { password: 'รหัสผ่าน', joinBtn: 'เข้าร่วม', confirmBtn: 'ตกลง', - cancelBtn: 'ยกเลิก' + cancelBtn: 'ยกเลิก', + ipConfig: 'กำหนดค่า IP', + ipConfigDescription: 'เลือก DHCP หรือตั้งค่าที่อยู่แบบคงที่สำหรับการเชื่อมต่อ Wi-Fi นี้', + security: 'ความปลอดภัย', + personal: 'ส่วนบุคคล / WPA-PSK', + enterprise: 'องค์กร / 802.1X', + identity: 'ID / ชื่อผู้ใช้', + authentication: 'การยืนยันตัวตน', + innerAuthentication: 'การยืนยันตัวตนภายใน', + anonymousIdentity: 'ID นิรนาม (ไม่บังคับ)', + caCert: 'เส้นทางใบรับรอง CA (ไม่บังคับ)', + domainSuffixMatch: 'การจับคู่นามสกุลโดเมน (ไม่บังคับ)', + clientCert: 'เส้นทางใบรับรองไคลเอนต์', + privateKey: 'เส้นทางกุญแจส่วนตัว', + privateKeyPasswd: 'รหัสผ่านกุญแจส่วนตัว (ไม่บังคับ)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (ใบรับรอง)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'เครือข่ายแบบมีสาย', + description: 'กำหนดค่า IP และการยืนยันตัวตน 802.1X แบบเลือกได้', + connect: 'กำหนดค่าเครือข่ายแบบมีสาย', + connectDesc: 'กำหนดค่า IP สำหรับ {{iface}} และเปิดใช้งาน 802.1X หากจำเป็น', + ipConfig: 'กำหนดค่า IP', + ipConfigDescription: 'เลือก DHCP หรือตั้งค่าที่อยู่แบบคงที่สำหรับอินเทอร์เฟซแบบมีสายนี้', + authTitle: 'การยืนยันตัวตน 802.1X', + authDescription: 'เปิดใช้งานเฉพาะเมื่อเครือข่ายแบบมีสายของคุณต้องการการยืนยันตัวตนด้วยชื่อผู้ใช้ รหัสผ่าน หรือใบรับรอง', + driverRequired: 'เครือข่ายแบบมีสาย 802.1X ต้องการ wpa_supplicant ที่รองรับไดรเวอร์ wired', + addressPlaceholder: 'ที่อยู่ IP แบบคงที่ (เช่น 192.168.1.10)', + gatewayPlaceholder: 'เกตเวย์ (ไม่บังคับ)', + passwordUnchanged: 'ไม่เปลี่ยนแปลง', + privateKeyPasswdUnchanged: 'ไม่เปลี่ยนแปลง', + off: 'ปิดใช้งาน', + enterprise: 'องค์กร / 802.1X', + active: '802.1X เปิดใช้งานแล้ว', + inactive: '802.1X กำหนดค่าแล้ว รอการยืนยันตัวตน', + failed: 'กำหนดค่าเครือข่ายแบบมีสาย 802.1X ล้มเหลว กรุณาลองใหม่' + }, + tls: { description: 'เปิดใช้งานโปรโตคอล HTTPS', tip: 'โปรดทราบ: การใช้ HTTPS อาจเพิ่มความหน่วง โดยเฉพาะในโหมดวิดีโอ MJPEG' @@ -405,6 +451,11 @@ const th = { ipAddress: 'ที่อยู่ IP', subnetMask: 'ซับเน็ตมาสก์', router: 'เราเตอร์', + wired: 'แบบมีสาย', + wireless: 'แบบไร้สาย', + signalStrength: 'ความแรงของสัญญาณ', + rxRate: 'อัตราการรับ', + txRate: 'อัตราการส่ง', none: 'ไม่มี' } }, diff --git a/web/src/i18n/locales/tr.ts b/web/src/i18n/locales/tr.ts index fcdf3ea3..2960f0c2 100644 --- a/web/src/i18n/locales/tr.ts +++ b/web/src/i18n/locales/tr.ts @@ -384,8 +384,54 @@ const tr = { password: 'Parola', joinBtn: 'Katıl', confirmBtn: 'Tamam', - cancelBtn: 'İptal' + cancelBtn: 'İptal', + ipConfig: 'IP yapılandırması', + ipConfigDescription: 'Bu Wi-Fi bağlantısı için DHCP seçin veya statik adres ayarlayın', + security: 'Güvenlik', + personal: 'Kişisel / WPA-PSK', + enterprise: 'Kurumsal / 802.1X', + identity: 'Kimlik / Kullanıcı adı', + authentication: 'Kimlik doğrulama', + innerAuthentication: 'Dahili kimlik doğrulama', + anonymousIdentity: 'Anonim kimlik (isteğe bağlı)', + caCert: 'CA sertifika yolu (isteğe bağlı)', + domainSuffixMatch: 'Alan adı son ek eşleşmesi (isteğe bağlı)', + clientCert: 'İstemci sertifika yolu', + privateKey: 'Özel anahtar yolu', + privateKeyPasswd: 'Özel anahtar şifresi (isteğe bağlı)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Sertifika)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Kablolu ağ', + description: 'IP ayarları ve isteğe bağlı 802.1X kimlik doğrulaması yapılandırması', + connect: 'Kablolu ağı yapılandır', + connectDesc: '{{iface}} için IP yapılandırmasını ayarlayın ve gerekirse 802.1X kimlik doğrulamasını etkinleştirin', + ipConfig: 'IP yapılandırması', + ipConfigDescription: 'Bu kablolu arayüz için DHCP seçin veya statik adres ayarlayın', + authTitle: '802.1X kimlik doğrulama', + authDescription: 'Yalnızca kablolu ağınız kullanıcı adı, şifre veya sertifika kimlik doğrulaması gerektiriyorsa etkinleştirin', + driverRequired: 'Kablolu 802.1X, wired sürücü desteğine sahip bir sistem wpa_supplicant derlemesi gerektirir', + addressPlaceholder: 'Statik IP adresi (örn: 192.168.1.10)', + gatewayPlaceholder: 'Ağ geçidi (isteğe bağlı)', + passwordUnchanged: 'Değişiklik yok', + privateKeyPasswdUnchanged: 'Değişiklik yok', + off: 'Devre dışı', + enterprise: 'Kurumsal / 802.1X', + active: '802.1X etkin', + inactive: '802.1X yapılandırıldı, kimlik doğrulama bekleniyor', + failed: 'Kablolu ağ 802.1X yapılandırması başarısız oldu. Lütfen tekrar deneyin.' + }, + tls: { description: 'HTTPS protokolünü etkinleştir', tip: 'HTTPS protokolü bağlantıda gecikmeye sebep olabilir, özellikle MJPEG görüntü modu ile.' @@ -412,6 +458,11 @@ const tr = { ipAddress: 'IP Adresi', subnetMask: 'Alt Ağ Maskesi', router: 'Yönlendirici', + wired: 'Kablolu', + wireless: 'Kablosuz', + signalStrength: 'Sinyal gücü', + rxRate: 'Alış hızı', + txRate: 'Gönderim hızı', none: 'Yok' } }, diff --git a/web/src/i18n/locales/uk.ts b/web/src/i18n/locales/uk.ts index 1187a4d4..260f4afe 100644 --- a/web/src/i18n/locales/uk.ts +++ b/web/src/i18n/locales/uk.ts @@ -9,14 +9,14 @@ const uk = { }, auth: { login: 'Вхід', - placeholderUsername: "Введіть ім'я користувача", + placeholderUsername: "Введіть ім\'я користувача", placeholderPassword: 'Введіть пароль', placeholderPassword2: 'Введіть пароль ще раз', noEmptyUsername: "Ім'я користувача не може бути порожнім", noEmptyPassword: 'Пароль не може бути порожнім', noAccount: 'Не вдалося отримати інформацію про користувача, оновіть веб-сторінку або скиньте пароль', - invalidUser: "Недійсне ім'я користувача або пароль", + invalidUser: "Недійсне ім\'я користувача або пароль", locked: 'Забагато входів, спробуйте пізніше', globalLocked: 'Система під захистом, спробуйте пізніше', error: 'Якась халепа! Непередбачена помилка :(', @@ -384,8 +384,54 @@ const uk = { password: 'Пароль', joinBtn: 'Підключити', confirmBtn: 'OK', - cancelBtn: 'Скасувати' + cancelBtn: 'Скасувати', + ipConfig: 'Налаштування IP', + ipConfigDescription: 'Оберіть DHCP або встановіть статичну адресу для цього Wi-Fi з\'єднання', + security: 'Безпека', + personal: 'Особиста / WPA-PSK', + enterprise: 'Корпоративна / 802.1X', + identity: 'Ідентифікатор / Ім\'я користувача', + authentication: 'Аутентифікація', + innerAuthentication: 'Внутрішня аутентифікація', + anonymousIdentity: 'Анонімний ідентифікатор (необов\'язково)', + caCert: 'Шлях до сертифіката CA (необов\'язково)', + domainSuffixMatch: 'Збіг суфіксу домену (необов\'язково)', + clientCert: 'Шлях до сертифіката клієнта', + privateKey: 'Шлях до приватного ключа', + privateKeyPasswd: 'Пароль приватного ключа (необов\'язково)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Сертифікат)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Дротова мережа', + description: 'Налаштування IP та опціональної аутентифікації 802.1X', + connect: 'Налаштувати дротову мережу', + connectDesc: 'Налаштувати IP для {{iface}} та увімкнути 802.1X за потреби', + ipConfig: 'Налаштування IP', + ipConfigDescription: 'Оберіть DHCP або встановіть статичну адресу для цього дротового інтерфейсу', + authTitle: 'Аутентифікація 802.1X', + authDescription: 'Увімкніть лише якщо ваша дротова мережа вимагає аутентифікацію за ім\'ям користувача, паролем або сертифікатом', + driverRequired: 'Дротовий 802.1X потребує системний wpa_supplicant з підтримкою драйвера wired', + addressPlaceholder: 'Статична IP-адреса (наприклад: 192.168.1.10)', + gatewayPlaceholder: 'Шлюз (необов\'язково)', + passwordUnchanged: 'Без змін', + privateKeyPasswdUnchanged: 'Без змін', + off: 'Вимкнено', + enterprise: 'Корпоративна / 802.1X', + active: '802.1X увімкнено', + inactive: '802.1X налаштовано, очікування аутентифікації', + failed: 'Помилка налаштування дротової мережі 802.1X. Спробуйте ще раз.' + }, + tls: { description: 'Увімкнути протокол HTTPS', tip: 'Будьте в курсі: Використання HTTPS може збільшити затримку, особливо в режимі відео MJPEG.' @@ -412,6 +458,11 @@ const uk = { ipAddress: 'IP-адреса', subnetMask: 'Маска підмережі', router: 'Маршрутизатор', + wired: 'Дротова', + wireless: 'Бездротова', + signalStrength: 'Рівень сигналу', + rxRate: 'Швидкість прийому', + txRate: 'Швидкість передачі', none: 'Немає' } }, diff --git a/web/src/i18n/locales/vi.ts b/web/src/i18n/locales/vi.ts index 873531d2..ef62cc75 100644 --- a/web/src/i18n/locales/vi.ts +++ b/web/src/i18n/locales/vi.ts @@ -381,8 +381,54 @@ const vi = { password: 'Mật khẩu', joinBtn: 'Tham gia', confirmBtn: 'OK', - cancelBtn: 'Hủy' + cancelBtn: 'Hủy', + ipConfig: 'Cấu hình IP', + ipConfigDescription: 'Chọn DHCP hoặc đặt địa chỉ tĩnh cho kết nối Wi-Fi này', + security: 'Bảo mật', + personal: 'Cá nhân / WPA-PSK', + enterprise: 'Doanh nghiệp / 802.1X', + identity: 'ID / Tên người dùng', + authentication: 'Xác thực', + innerAuthentication: 'Xác thực nội bộ', + anonymousIdentity: 'ID ẩn danh (tùy chọn)', + caCert: 'Đường dẫn chứng chỉ CA (tùy chọn)', + domainSuffixMatch: 'Khớp hậu tố tên miền (tùy chọn)', + clientCert: 'Đường dẫn chứng chỉ máy khách', + privateKey: 'Đường dẫn khóa riêng', + privateKeyPasswd: 'Mật khẩu khóa riêng (tùy chọn)', + eapPeap: 'Protected EAP (PEAP)', + eapTtls: 'Tunneled TLS (TTLS)', + eapTls: 'TLS (Chứng chỉ)', + eapPwd: 'PWD', + eapLeap: 'LEAP', + authMschapv2: 'MSCHAPv2', + authMschap: 'MSCHAP', + authChap: 'CHAP', + authPap: 'PAP', + authGtc: 'GTC', + authMd5: 'MD5', }, + ethernet: { + title: 'Mạng có dây', + description: 'Cấu hình IP và xác thực 802.1X tùy chọn', + connect: 'Cấu hình mạng có dây', + connectDesc: 'Cấu hình IP cho {{iface}} và bật 802.1X nếu cần', + ipConfig: 'Cấu hình IP', + ipConfigDescription: 'Chọn DHCP hoặc đặt địa chỉ tĩnh cho giao diện có dây này', + authTitle: 'Xác thực 802.1X', + authDescription: 'Chỉ bật nếu mạng có dây của bạn yêu cầu xác thực bằng tên người dùng, mật khẩu hoặc chứng chỉ', + driverRequired: 'Mạng có dây 802.1X cần wpa_supplicant với hỗ trợ trình điều khiển wired', + addressPlaceholder: 'Địa chỉ IP tĩnh (vd: 192.168.1.10)', + gatewayPlaceholder: 'Cổng mạng (tùy chọn)', + passwordUnchanged: 'Không thay đổi', + privateKeyPasswdUnchanged: 'Không thay đổi', + off: 'Tắt', + enterprise: 'Doanh nghiệp / 802.1X', + active: '802.1X đã bật', + inactive: '802.1X đã cấu hình, đang chờ xác thực', + failed: 'Cấu hình mạng có dây 802.1X thất bại. Vui lòng thử lại.' + }, + tls: { description: 'Bật giao thức HTTPS', tip: 'Lưu ý: Sử dụng HTTPS có thể tăng độ trễ, đặc biệt trong chế độ video MJPEG.' @@ -409,6 +455,11 @@ const vi = { ipAddress: 'Địa chỉ IP', subnetMask: 'Mặt nạ mạng con', router: 'Bộ định tuyến', + wired: 'Có dây', + wireless: 'Không dây', + signalStrength: 'Cường độ tín hiệu', + rxRate: 'Tốc độ nhận', + txRate: 'Tốc độ gửi', none: 'Không có' } }, diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index e6ec1a62..b6314d97 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -446,7 +446,7 @@ const zh = { wireless: '无线', ipAddress: 'IP 地址', subnetMask: '子网掩码', - router: '路由器', + router: '路由', signalStrength: '信号强度', rxRate: '接收速率', txRate: '发送速率', diff --git a/web/src/i18n/locales/zh_tw.ts b/web/src/i18n/locales/zh_tw.ts index 5faf8081..e0825410 100644 --- a/web/src/i18n/locales/zh_tw.ts +++ b/web/src/i18n/locales/zh_tw.ts @@ -446,7 +446,7 @@ const zh_tw = { wireless: '無線', ipAddress: 'IP 位址', subnetMask: '子網路遮罩', - router: '路由器', + router: '路由', signalStrength: '訊號強度', rxRate: '接收速率', txRate: '傳送速率',