feat: add DNS management to Network settings

- Add DNS configuration APIs for manual and DHCP modes, including persistence and udhcpc hook support.
- Add a Network settings tab with DNS controls, move Wi-Fi and HTTPS settings into it, and update translations.
- Update locales
This commit is contained in:
wenjie
2026-05-07 17:03:25 +08:00
parent 1c43542919
commit 36cf9c30d8
36 changed files with 2310 additions and 232 deletions

View File

@@ -28,3 +28,25 @@ type ConnectWifiReq struct {
Ssid string `validate:"required"`
Password string `valid:"required"`
}
type GetDNSRsp struct {
Mode string `json:"mode"`
Servers []string `json:"servers"`
Effective []string `json:"effective"`
DHCP []string `json:"dhcp"`
Info DNSInfo `json:"info"`
}
type SetDNSReq struct {
Mode string `json:"mode" validate:"required,oneof=manual dhcp"`
Servers []string `json:"servers"`
}
type DNSInfo struct {
Interface string `json:"interface"`
Type string `json:"type"`
Address string `json:"address"`
SubnetMask string `json:"subnetMask"`
Gateway string `json:"gateway"`
SearchDomains []string `json:"searchDomains"`
}

View File

@@ -10,8 +10,8 @@ import (
func networkRouter(r *gin.Engine) {
service := network.NewService()
r.POST("/api/network/wifi", service.ConnectWifiNoAuth) // connect Wi-Fi without auth (only available in ap mode)
r.POST("/api/network/wifi/verify", service.VerifyApLogin) // verify ap login
r.POST("/api/network/wifi", service.ConnectWifiNoAuth) // connect Wi-Fi without auth (only available in ap mode)
r.POST("/api/network/wifi/verify", service.VerifyApLogin) // verify ap login
api := r.Group("/api").Use(middleware.CheckToken())
@@ -23,4 +23,7 @@ func networkRouter(r *gin.Engine) {
api.GET("/network/wifi", service.GetWifi) // get Wi-Fi information
api.POST("/network/wifi/connect", service.ConnectWifi) // connect Wi-Fi
api.POST("/network/wifi/disconnect", service.DisconnectWifi) // disconnect Wi-Fi
api.GET("/network/dns", service.GetDNS) // get DNS configuration
api.POST("/network/dns", service.SetDNS) // set DNS configuration
}

View File

@@ -0,0 +1,614 @@
package network
import (
"bufio"
_ "embed"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"NanoKVM-Server/proto"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
const (
dnsModeManual = "manual"
dnsModeDHCP = "dhcp"
dnsConfigDir = "/etc/kvm/network"
dnsModeFile = "/etc/kvm/network/dns.mode"
dnsServersFile = "/etc/kvm/network/dns.servers"
bootResolvFile = "/boot/resolv.conf"
bootResolvBackup = "/boot/resolv.conf.manual.bak"
etcResolvFile = "/etc/resolv.conf"
dhcpResolvFile = "/etc/resolv.conf.dhcp"
udhcpcHookFile = "/usr/share/udhcpc/default.script.d/99-nanokvm-dns"
maxDNSServers = 6
)
//go:embed scripts/99-nanokvm-dns
var udhcpcDNSHook string
type resolvConfig struct {
Servers []string
SearchDomains []string
}
func (s *Service) GetDNS(c *gin.Context) {
var rsp proto.Response
mode := currentDNSMode()
servers := readManualDNSServers()
if len(servers) == 0 {
servers, _ = parseResolvConf(bootResolvFile)
}
if len(servers) == 0 {
servers, _ = parseResolvConf(bootResolvBackup)
}
effective, _ := parseResolvConf(etcResolvFile)
dhcpConfig, _ := readDHCPResolvConfig(mode == dnsModeDHCP)
dhcp := dhcpConfig.Servers
info := getDNSInfo()
rsp.OkRspWithData(c, &proto.GetDNSRsp{
Mode: mode,
Servers: servers,
Effective: effective,
DHCP: dhcp,
Info: info,
})
log.Debugf("get dns config: mode=%s servers=%v effective=%v dhcp=%v info=%+v", mode, servers, effective, dhcp, info)
}
func (s *Service) SetDNS(c *gin.Context) {
var req proto.SetDNSReq
var rsp proto.Response
if err := proto.ParseFormRequest(c, &req); err != nil {
rsp.ErrRsp(c, -1, "invalid arguments")
return
}
switch req.Mode {
case dnsModeManual:
if err := setManualDNS(req.Servers); err != nil {
log.Errorf("failed to set manual dns: %s", err)
rsp.ErrRsp(c, -2, err.Error())
return
}
case dnsModeDHCP:
if err := setDHCPDNS(); err != nil {
log.Errorf("failed to set dhcp dns: %s", err)
rsp.ErrRsp(c, -3, err.Error())
return
}
default:
rsp.ErrRsp(c, -1, "invalid dns mode")
return
}
_ = exec.Command("sync").Run()
rsp.OkRsp(c)
log.Debugf("set dns config: mode=%s servers=%v", req.Mode, req.Servers)
}
func setManualDNS(servers []string) error {
normalized, err := validateDNSServers(servers)
if err != nil {
return err
}
if len(normalized) == 0 {
return fmt.Errorf("dns servers are required")
}
if currentDNSMode() == dnsModeDHCP {
if err := refreshDHCPResolvCacheFromEffective(); err != nil {
log.Warnf("failed to refresh dhcp dns cache before switching to manual: %s", err)
}
}
if err := os.MkdirAll(dnsConfigDir, 0o755); err != nil {
return fmt.Errorf("failed to create dns config directory: %w", err)
}
if err := writeDNSMode(dnsModeManual); err != nil {
return err
}
if err := os.WriteFile(dnsServersFile, []byte(strings.Join(normalized, "\n")+"\n"), 0o644); err != nil {
return fmt.Errorf("failed to write dns servers: %w", err)
}
if err := renderResolvConf(bootResolvFile, normalized); err != nil {
return err
}
if err := renderResolvConf(etcResolvFile, normalized); err != nil {
return err
}
return installUDHCPCDNSHook()
}
func setDHCPDNS() error {
dhcpConfig, err := readDHCPResolvConfig(currentDNSMode() == dnsModeDHCP)
if err != nil {
return fmt.Errorf("failed to read dhcp dns: %w", err)
}
if len(dhcpConfig.Servers) == 0 {
return fmt.Errorf("no dhcp dns is currently available")
}
if err := os.MkdirAll(dnsConfigDir, 0o755); err != nil {
return fmt.Errorf("failed to create dns config directory: %w", err)
}
if err := renderResolvConfig(dhcpResolvFile, dhcpConfig); err != nil {
return fmt.Errorf("failed to write dhcp dns cache: %w", err)
}
if err := writeDNSMode(dnsModeDHCP); err != nil {
return err
}
if err := preserveManualDNSServers(); err != nil {
return err
}
if err := backupAndRemoveBootResolv(); err != nil {
return err
}
if err := renderResolvConfig(etcResolvFile, dhcpConfig); err != nil {
return err
}
return installUDHCPCDNSHook()
}
func currentDNSMode() string {
mode := readDNSMode()
if mode == "" {
mode = defaultDNSMode()
}
return mode
}
func readDNSMode() string {
data, err := os.ReadFile(dnsModeFile)
if err != nil {
return ""
}
mode := strings.TrimSpace(string(data))
if mode != dnsModeManual && mode != dnsModeDHCP {
return ""
}
return mode
}
func defaultDNSMode() string {
if _, err := os.Stat(bootResolvFile); err == nil {
return dnsModeManual
}
return dnsModeDHCP
}
func writeDNSMode(mode string) error {
if err := os.WriteFile(dnsModeFile, []byte(mode+"\n"), 0o644); err != nil {
return fmt.Errorf("failed to write dns mode: %w", err)
}
return nil
}
func readManualDNSServers() []string {
servers, err := parsePlainDNSServers(dnsServersFile)
if err != nil {
return nil
}
return servers
}
func preserveManualDNSServers() error {
if len(readManualDNSServers()) > 0 {
return nil
}
servers, _ := parseResolvConf(bootResolvFile)
if len(servers) == 0 {
servers, _ = parseResolvConf(bootResolvBackup)
}
if len(servers) == 0 {
return nil
}
if err := os.WriteFile(dnsServersFile, []byte(strings.Join(servers, "\n")+"\n"), 0o644); err != nil {
return fmt.Errorf("failed to preserve manual dns servers: %w", err)
}
return nil
}
func parsePlainDNSServers(path string) ([]string, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
lines := strings.Split(string(data), "\n")
return normalizeDNSServers(lines)
}
func readDHCPResolvConfig(allowEffectiveFallback bool) (resolvConfig, error) {
config, err := parseResolvConfig(dhcpResolvFile)
if err == nil && len(config.Servers) > 0 {
return config, nil
}
if allowEffectiveFallback {
effective, effectiveErr := parseResolvConfig(etcResolvFile)
if effectiveErr == nil && len(effective.Servers) > 0 {
return effective, nil
}
}
if err != nil && !os.IsNotExist(err) {
return resolvConfig{}, err
}
return resolvConfig{}, nil
}
func refreshDHCPResolvCacheFromEffective() error {
config, err := parseResolvConfig(etcResolvFile)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
if len(config.Servers) == 0 {
return nil
}
if len(config.SearchDomains) == 0 {
cached, _ := parseResolvConfig(dhcpResolvFile)
config.SearchDomains = cached.SearchDomains
}
return renderResolvConfig(dhcpResolvFile, config)
}
func parseResolvConf(path string) ([]string, error) {
config, err := parseResolvConfig(path)
if err != nil {
return nil, err
}
return config.Servers, nil
}
func parseResolvConfig(path string) (resolvConfig, error) {
file, err := os.Open(path)
if err != nil {
return resolvConfig{}, err
}
defer func() {
_ = file.Close()
}()
var servers []string
var searchDomains []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := stripInlineComment(scanner.Text())
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
switch fields[0] {
case "nameserver":
servers = append(servers, fields[1])
case "search":
searchDomains = append(searchDomains, fields[1:]...)
case "domain":
searchDomains = append(searchDomains, fields[1])
}
}
if err := scanner.Err(); err != nil {
return resolvConfig{}, err
}
normalizedServers, err := normalizeDNSServers(servers)
if err != nil {
return resolvConfig{}, err
}
return resolvConfig{
Servers: normalizedServers,
SearchDomains: normalizeSearchDomains(searchDomains),
}, nil
}
func getDNSInfo() proto.DNSInfo {
ifaceName, gateway := getDefaultIPv4Route()
if ifaceName == "" {
ifaceName = getFallbackIPv4Interface()
}
info := proto.DNSInfo{
Interface: ifaceName,
Gateway: gateway,
}
dhcpConfig, _ := readDHCPResolvConfig(currentDNSMode() == dnsModeDHCP)
info.SearchDomains = dhcpConfig.SearchDomains
if ifaceName == "" {
return info
}
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return info
}
info.Type = getDNSInterfaceType(iface.Name)
info.Address, info.SubnetMask = getIPv4AddressInfo(*iface)
return info
}
func getDefaultIPv4Route() (string, string) {
file, err := os.Open("/proc/net/route")
if err != nil {
return "", ""
}
defer func() {
_ = file.Close()
}()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 8 || fields[1] != "00000000" {
continue
}
return fields[0], parseRouteGateway(fields[2])
}
return "", ""
}
func parseRouteGateway(value string) string {
gateway, err := strconv.ParseUint(value, 16, 32)
if err != nil || gateway == 0 {
return ""
}
return net.IPv4(byte(gateway), byte(gateway>>8), byte(gateway>>16), byte(gateway>>24)).String()
}
func getFallbackIPv4Interface() string {
interfaces, err := net.Interfaces()
if err != nil {
return ""
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 {
continue
}
if getDNSInterfaceType(iface.Name) == "" {
continue
}
address, _ := getIPv4AddressInfo(iface)
if address != "" {
return iface.Name
}
}
return ""
}
func getDNSInterfaceType(name string) string {
if strings.HasPrefix(name, "eth") || strings.HasPrefix(name, "en") {
return "Wired"
}
if strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wl") {
return "Wireless"
}
return ""
}
func getIPv4AddressInfo(iface net.Interface) (string, string) {
addrs, err := iface.Addrs()
if err != nil {
return "", ""
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP.To4()
if ip == nil {
continue
}
ones, _ := ipNet.Mask.Size()
return fmt.Sprintf("%s/%d", ip.String(), ones), net.IP(ipNet.Mask).String()
}
return "", ""
}
func renderResolvConf(path string, servers []string) error {
return renderResolvConfig(path, resolvConfig{Servers: servers})
}
func renderResolvConfig(path string, config resolvConfig) error {
normalized, err := normalizeDNSServers(config.Servers)
if err != nil {
return err
}
if len(normalized) == 0 {
return fmt.Errorf("dns servers are required")
}
var builder strings.Builder
searchDomains := normalizeSearchDomains(config.SearchDomains)
if len(searchDomains) > 0 {
builder.WriteString("search ")
builder.WriteString(strings.Join(searchDomains, " "))
builder.WriteString("\n")
}
for _, server := range normalized {
builder.WriteString("nameserver ")
builder.WriteString(server)
builder.WriteString("\n")
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return fmt.Errorf("failed to create dns file directory: %w", err)
}
if err := os.WriteFile(path, []byte(builder.String()), 0o644); err != nil {
return fmt.Errorf("failed to write %s: %w", path, err)
}
return nil
}
func validateDNSServers(servers []string) ([]string, error) {
normalized, err := normalizeDNSServers(servers)
if err != nil {
return nil, err
}
if len(normalized) > maxDNSServers {
return nil, fmt.Errorf("too many dns servers")
}
return normalized, nil
}
func normalizeDNSServers(servers []string) ([]string, error) {
seen := make(map[string]struct{})
var normalized []string
for _, server := range servers {
server = normalizeDNSServer(server)
if server == "" {
continue
}
ip := net.ParseIP(server)
if ip == nil {
return nil, fmt.Errorf("invalid dns server: %s", server)
}
server = ip.String()
if _, exists := seen[server]; exists {
continue
}
seen[server] = struct{}{}
normalized = append(normalized, server)
}
return normalized, nil
}
func normalizeSearchDomains(domains []string) []string {
seen := make(map[string]struct{})
var normalized []string
for _, domain := range domains {
domain = strings.TrimSpace(domain)
if domain == "" {
continue
}
if _, exists := seen[domain]; exists {
continue
}
seen[domain] = struct{}{}
normalized = append(normalized, domain)
}
return normalized
}
func normalizeDNSServer(server string) string {
server = stripInlineComment(server)
server = strings.TrimSpace(server)
fields := strings.Fields(server)
if len(fields) == 0 {
return ""
}
return fields[0]
}
func stripInlineComment(value string) string {
if index := strings.Index(value, "#"); index >= 0 {
return value[:index]
}
return value
}
func backupAndRemoveBootResolv() error {
data, err := os.ReadFile(bootResolvFile)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("failed to read boot resolv: %w", err)
}
if err := os.WriteFile(bootResolvBackup, data, 0o644); err != nil {
return fmt.Errorf("failed to backup boot resolv: %w", err)
}
if err := os.Remove(bootResolvFile); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove boot resolv: %w", err)
}
return nil
}
func installUDHCPCDNSHook() error {
if err := os.MkdirAll(filepath.Dir(udhcpcHookFile), 0o755); err != nil {
return fmt.Errorf("failed to create udhcpc hook directory: %w", err)
}
if err := os.WriteFile(udhcpcHookFile, []byte(udhcpcDNSHook), 0o755); err != nil {
return fmt.Errorf("failed to install udhcpc dns hook: %w", err)
}
return nil
}

View File

@@ -0,0 +1,56 @@
#!/bin/sh
ACTION="$1"
CACHE="/etc/resolv.conf.dhcp"
ACTIVE="/etc/resolv.conf"
MODE="/etc/kvm/network/dns.mode"
BOOT="/boot/resolv.conf"
[ "$ACTION" = "bound" ] || [ "$ACTION" = "renew" ] || exit 0
[ -n "$dns" ] || exit 0
TMPFILE="$(mktemp)" || exit 1
write_search_domain() {
if [ -n "$search" ]; then
printf "search"
for domain_name in $search; do
printf " %s" "$domain_name"
done
printf "\n"
return
fi
if [ -n "$domain" ]; then
printf "domain %s\n" "$domain"
return
fi
for source in "$CACHE" "$ACTIVE"; do
[ -f "$source" ] || continue
while IFS= read -r line; do
line="${line%%#*}"
set -- $line
[ "$1" = "search" ] || [ "$1" = "domain" ] || continue
[ -n "$2" ] || continue
printf "%s\n" "$line"
return
done < "$source"
done
}
{
write_search_domain
for server in $dns; do
printf "nameserver %s\n" "$server"
done
} > "$TMPFILE"
cat "$TMPFILE" > "$CACHE"
MODE_VALUE="$(cat "$MODE" 2>/dev/null)"
if [ "$MODE_VALUE" = "dhcp" ] || { [ -z "$MODE_VALUE" ] && [ ! -f "$BOOT" ]; }; then
cat "$TMPFILE" > "$ACTIVE"
fi
rm -f "$TMPFILE"
exit 0

View File

@@ -1,5 +1,7 @@
import { http } from '@/lib/http.ts';
export type DNSMode = 'manual' | 'dhcp';
// wake on lan
export function wol(mac: string) {
const data = {
@@ -69,3 +71,11 @@ export function connectWifi(ssid: string, password: string) {
export function disconnectWifi() {
return http.post('/api/network/wifi/disconnect');
}
export function getDNS() {
return http.get('/api/network/dns');
}
export function setDNS(mode: DNSMode, servers: string[]) {
return http.post('/api/network/dns', { mode, servers });
}

View File

@@ -4,9 +4,12 @@ import clsx from 'clsx';
function ScrollArea({
className,
viewportRef,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root> & {
viewportRef?: React.Ref<HTMLDivElement>;
}) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
@@ -14,6 +17,7 @@ function ScrollArea({
{...props}
>
<ScrollAreaPrimitive.Viewport
ref={viewportRef}
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px]"
>

View File

@@ -226,19 +226,10 @@ const ca = {
1800: "30 min",
3600: "1 h"
},
wifi: {
title: "Xarxa sense fils",
description: "Configura la xarxa sense fils",
setBtn: "Configura"
},
ssh: {
description: "Activa accés remot per SSH",
tip: "Configura una contrasenya segura abans (Compte - Canvia contrasenya)"
},
tls: {
description: "Activa el protocol HTTPS",
tip: "Atenció: Usar HTTPS pot augmentar la latència, sobretot amb vídeo MJPEG."
},
advanced: "Configuració avançada",
swap: {
title: "Swap",
@@ -270,6 +261,52 @@ const ca = {
okBtn: "Sí",
cancelBtn: "No"
},
network: {
title: 'Xarxa',
wifi: {
title: 'Wi-Fi',
description: 'Configura la xarxa Wi-Fi',
apMode: 'El mode AP està activat; connecta\'t al Wi-Fi escanejant el codi QR',
connect: 'Connecta\'t a Wi-Fi',
connectDesc1: 'Introdueix el SSID i la contrasenya de la xarxa',
connectDesc2: 'Introdueix la contrasenya per connectar-te a aquesta xarxa',
disconnect: 'Segur que vols desconnectar la xarxa?',
failed: 'La connexió ha fallat, torna-ho a provar.',
ssid: 'Nom',
password: 'Contrasenya',
joinBtn: 'Connecta',
confirmBtn: 'D\'acord',
cancelBtn: 'Cancel·la'
},
tls: {
description: 'Activa el protocol HTTPS',
tip: 'Atenció: Usar HTTPS pot augmentar la latència, sobretot amb vídeo MJPEG.'
},
dns: {
title: 'DNS',
description: 'Configura els servidors DNS per a NanoKVM',
mode: 'Mode',
dhcp: 'DHCP',
manual: 'Manual',
add: 'Afegeix DNS',
save: 'Desa',
invalid: 'Introdueix una adreça IP vàlida',
noDhcp: 'No hi ha cap DNS DHCP disponible actualment',
saved: 'Configuració DNS desada',
saveFailed: 'No s\'ha pogut desar la configuració DNS',
unsaved: 'Canvis no desats',
maxServers: 'Es permeten com a màxim {{count}} servidors DNS',
dnsServers: 'Servidors DNS',
dhcpServersDescription: 'Els servidors DNS s\'obtenen automàticament via DHCP.',
manualServersDescription: 'Els servidors DNS es poden editar manualment.',
networkDetails: 'Detalls de xarxa',
interface: 'Interfície',
ipAddress: 'Adreça IP',
subnetMask: 'Màscara de subxarxa',
router: 'Encaminador',
none: 'Cap'
}
},
tailscale: {
title: "Tailscale",
memory: {

View File

@@ -183,16 +183,57 @@ const cz = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Síť',
wifi: {
title: 'Wi-Fi',
description: 'Nastavit Wi-Fi',
apMode: 'Režim AP je povolen, připojte se k Wi-Fi naskenováním QR kódu',
connect: 'Připojit Wi-Fi',
connectDesc1: 'Zadejte SSID sítě a heslo',
connectDesc2: 'Zadejte heslo pro připojení k této síti',
disconnect: 'Opravdu chcete síť odpojit?',
failed: 'Připojení se nezdařilo, zkuste to znovu.',
ssid: 'Název',
password: 'Heslo',
joinBtn: 'Připojit',
confirmBtn: 'OK',
cancelBtn: 'Zrušit'
},
tls: {
description: 'Povolit protokol HTTPS',
tip: 'Upozornění: Použití HTTPS může zvýšit latenci, zejména v režimu videa MJPEG.'
},
dns: {
title: 'DNS',
description: 'Nastavit DNS servery pro NanoKVM',
mode: 'Režim',
dhcp: 'DHCP',
manual: 'Ručně',
add: 'Přidat DNS',
save: 'Uložit',
invalid: 'Zadejte platnou IP adresu',
noDhcp: 'Momentálně není k dispozici žádné DHCP DNS',
saved: 'Nastavení DNS uloženo',
saveFailed: 'Nastavení DNS se nepodařilo uložit',
unsaved: 'Neuložené změny',
maxServers: 'Je povoleno maximálně {{count}} DNS serverů',
dnsServers: 'DNS servery',
dhcpServersDescription: 'DNS servery jsou automaticky získávány z DHCP.',
manualServersDescription: 'DNS servery lze upravit ručně.',
networkDetails: 'Podrobnosti sítě',
interface: 'Rozhraní',
ipAddress: 'IP adresa',
subnetMask: 'Maska podsítě',
router: 'Router',
none: 'Žádné'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -181,16 +181,57 @@ const da = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Netværk',
wifi: {
title: 'Wi-Fi',
description: 'Konfigurer Wi-Fi',
apMode: 'AP-tilstand er aktiveret, opret forbindelse til Wi-Fi ved at scanne QR-koden',
connect: 'Tilslut Wi-Fi',
connectDesc1: 'Indtast netværkets SSID og adgangskode',
connectDesc2: 'Indtast adgangskoden for at tilslutte dette netværk',
disconnect: 'Er du sikker på, at du vil afbryde netværket?',
failed: 'Forbindelsen mislykkedes, prøv igen.',
ssid: 'Navn',
password: 'Adgangskode',
joinBtn: 'Tilslut',
confirmBtn: 'OK',
cancelBtn: 'Annuller'
},
tls: {
description: 'Aktiver HTTPS-protokol',
tip: 'Bemærk: Brug af HTTPS kan øge forsinkelsen, især med MJPEG-videotilstand.'
},
dns: {
title: 'DNS',
description: 'Konfigurer DNS-servere til NanoKVM',
mode: 'Tilstand',
dhcp: 'DHCP',
manual: 'Manuel',
add: 'Tilføj DNS',
save: 'Gem',
invalid: 'Indtast en gyldig IP-adresse',
noDhcp: 'Ingen DHCP-DNS er tilgængelig i øjeblikket',
saved: 'DNS-indstillinger gemt',
saveFailed: 'DNS-indstillinger kunne ikke gemmes',
unsaved: 'Ikke-gemte ændringer',
maxServers: 'Maksimalt {{count}} DNS-servere er tilladt',
dnsServers: 'DNS-servere',
dhcpServersDescription: 'DNS-servere hentes automatisk fra DHCP.',
manualServersDescription: 'DNS-servere kan redigeres manuelt.',
networkDetails: 'Netværksdetaljer',
interface: 'Grænseflade',
ipAddress: 'IP-adresse',
subnetMask: 'Undernetmaske',
router: 'Router',
none: 'Ingen'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -238,11 +238,6 @@ const de = {
1800: '30 Min',
3600: '1 Stunde'
},
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi konfigurieren',
setBtn: 'Konfigurieren'
},
ssh: {
description: 'Aktiviere entfernten SSH-Zugang',
tip: 'Setzten Sie ein starkes Passwort vor dem aktivieren (Konto - Passwort ändern)'
@@ -275,6 +270,52 @@ const de = {
okBtn: 'Ja',
cancelBtn: 'Nein'
},
network: {
title: 'Netzwerk',
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi konfigurieren',
apMode: 'AP-Modus ist aktiviert, verbinden Sie sich per QR-Code mit dem Wi-Fi',
connect: 'Wi-Fi verbinden',
connectDesc1: 'Bitte geben Sie die Netzwerk-SSID und das Passwort ein',
connectDesc2: 'Bitte geben Sie das Passwort ein, um diesem Netzwerk beizutreten',
disconnect: 'Möchten Sie die Netzwerkverbindung wirklich trennen?',
failed: 'Verbindung fehlgeschlagen, bitte erneut versuchen.',
ssid: 'Name',
password: 'Passwort',
joinBtn: 'Verbinden',
confirmBtn: 'OK',
cancelBtn: 'Abbrechen'
},
tls: {
description: 'HTTPS-Protokoll aktivieren',
tip: 'Hinweis: Die Verwendung von HTTPS kann die Latenz erhöhen, besonders im MJPEG-Videomodus.'
},
dns: {
title: 'DNS',
description: 'DNS-Server für NanoKVM konfigurieren',
mode: 'Modus',
dhcp: 'DHCP',
manual: 'Manuell',
add: 'DNS hinzufügen',
save: 'Speichern',
invalid: 'Bitte geben Sie eine gültige IP-Adresse ein',
noDhcp: 'Derzeit ist kein DHCP-DNS verfügbar',
saved: 'DNS-Einstellungen gespeichert',
saveFailed: 'DNS-Einstellungen konnten nicht gespeichert werden',
unsaved: 'Ungespeicherte Änderungen',
maxServers: 'Maximal {{count}} DNS-Server erlaubt',
dnsServers: 'DNS-Server',
dhcpServersDescription: 'DNS-Server werden automatisch per DHCP bezogen.',
manualServersDescription: 'DNS-Server können manuell bearbeitet werden.',
networkDetails: 'Netzwerkdetails',
interface: 'Schnittstelle',
ipAddress: 'IP-Adresse',
subnetMask: 'Subnetzmaske',
router: 'Router',
none: 'Keine'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -309,29 +309,10 @@ const en = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
apMode: 'AP mode is enabled, connect to Wi-Fi by scanning QR code',
connect: 'Join Wi-Fi',
connectDesc1: 'Please enter the network ssid and password',
connectDesc2: 'Please enter the password to join this network',
disconnect: 'Are you sure to disconnect the network?',
failed: 'Connection failed, please try again.',
ssid: 'Name',
password: 'Password',
joinBtn: 'Join',
confirmBtn: 'Ok',
cancelBtn: 'Cancel'
},
ssh: {
description: 'Enable SSH remote access',
tip: 'Set a strong password before enabling (Account - Change Password)'
},
tls: {
description: 'Enable HTTPS protocol',
tip: 'Be aware: Using HTTPS can increase latency, especially with MJPEG video mode.'
},
advanced: 'Advanced Settings',
swap: {
title: 'Swap',
@@ -375,6 +356,52 @@ const en = {
okBtn: 'Yes',
cancelBtn: 'No'
},
network: {
title: 'Network',
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
apMode: 'AP mode is enabled, connect to Wi-Fi by scanning QR code',
connect: 'Join Wi-Fi',
connectDesc1: 'Please enter the network ssid and password',
connectDesc2: 'Please enter the password to join this network',
disconnect: 'Are you sure to disconnect the network?',
failed: 'Connection failed, please try again.',
ssid: 'Name',
password: 'Password',
joinBtn: 'Join',
confirmBtn: 'Ok',
cancelBtn: 'Cancel'
},
tls: {
description: 'Enable HTTPS protocol',
tip: 'Be aware: Using HTTPS can increase latency, especially with MJPEG video mode.'
},
dns: {
title: 'DNS',
description: 'Configure DNS servers for NanoKVM',
mode: 'Mode',
dhcp: 'DHCP',
manual: 'Manual',
add: 'Add DNS',
save: 'Save',
invalid: 'Please enter a valid IP address',
noDhcp: 'No DHCP DNS is currently available',
saved: 'DNS settings saved',
saveFailed: 'Failed to save DNS settings',
unsaved: 'Unsaved changes',
maxServers: 'Maximum {{count}} DNS servers allowed',
dnsServers: 'DNS Servers',
dhcpServersDescription: 'DNS servers are automatically obtained from DHCP.',
manualServersDescription: 'DNS servers can be edited manually.',
networkDetails: 'Network Details',
interface: 'Interface',
ipAddress: 'IP Address',
subnetMask: 'Subnet Mask',
router: 'Router',
none: 'None'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -237,19 +237,10 @@ const es = {
1800: '30 min',
3600: '1 hora'
},
wifi: {
title: 'Wi-Fi',
description: 'Configura el Wi-Fi',
setBtn: 'Configurar'
},
ssh: {
description: 'Habilitar acceso remoto SSH',
tip: 'Establece una contraseña segura antes de habilitar (Cuenta - Cambiar contraseña)'
},
tls: {
description: 'Habilitar protocolo HTTPS',
tip: 'Aviso: Usar HTTPS puede aumentar la latencia, especialmente en modo de vídeo MJPEG.'
},
advanced: 'Ajustes avanzados',
swap: {
title: 'Memoria Swap',
@@ -285,6 +276,52 @@ const es = {
okBtn: 'Sí',
cancelBtn: 'No'
},
network: {
title: 'Red',
wifi: {
title: 'Wi-Fi',
description: 'Configura el Wi-Fi',
apMode: 'El modo AP está activado; conéctate al Wi-Fi escaneando el código QR',
connect: 'Conectar Wi-Fi',
connectDesc1: 'Introduce el SSID de la red y la contraseña',
connectDesc2: 'Introduce la contraseña para unirte a esta red',
disconnect: '¿Seguro que quieres desconectar la red?',
failed: 'Error de conexión, inténtalo de nuevo.',
ssid: 'Nombre',
password: 'Contraseña',
joinBtn: 'Unirse',
confirmBtn: 'Aceptar',
cancelBtn: 'Cancelar'
},
tls: {
description: 'Habilitar protocolo HTTPS',
tip: 'Aviso: Usar HTTPS puede aumentar la latencia, especialmente en modo de vídeo MJPEG.'
},
dns: {
title: 'DNS',
description: 'Configura los servidores DNS para NanoKVM',
mode: 'Modo',
dhcp: 'DHCP',
manual: 'Manual',
add: 'Añadir DNS',
save: 'Guardar',
invalid: 'Introduce una dirección IP válida',
noDhcp: 'No hay DNS DHCP disponible actualmente',
saved: 'Configuración DNS guardada',
saveFailed: 'No se pudo guardar la configuración DNS',
unsaved: 'Cambios sin guardar',
maxServers: 'Se permiten como máximo {{count}} servidores DNS',
dnsServers: 'Servidores DNS',
dhcpServersDescription: 'Los servidores DNS se obtienen automáticamente por DHCP.',
manualServersDescription: 'Los servidores DNS se pueden editar manualmente.',
networkDetails: 'Detalles de red',
interface: 'Interfaz',
ipAddress: 'Dirección IP',
subnetMask: 'Máscara de subred',
router: 'Router',
none: 'Ninguno'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -194,16 +194,57 @@ const fr = {
1800: '30 min',
3600: '1 heure'
},
wifi: {
title: 'Wi-Fi',
description: 'Configurez le Wi-Fi',
setBtn: 'Configurer'
},
disk: 'Disque virtuel',
diskDesc: 'Monter le disque virtuel U sur l\'hôte distant',
network: 'Réseau virtuel',
networkDesc: 'Monter la carte réseau virtuelle sur l\'hôte distant'
},
network: {
title: 'Réseau',
wifi: {
title: 'Wi-Fi',
description: 'Configurez le Wi-Fi',
apMode: 'Le mode AP est activé, connectez-vous au Wi-Fi en scannant le code QR',
connect: 'Connecter le Wi-Fi',
connectDesc1: 'Veuillez saisir le SSID du réseau et le mot de passe',
connectDesc2: 'Veuillez saisir le mot de passe pour rejoindre ce réseau',
disconnect: 'Voulez-vous vraiment déconnecter le réseau ?',
failed: 'Échec de la connexion, veuillez réessayer.',
ssid: 'Nom',
password: 'Mot de passe',
joinBtn: 'Rejoindre',
confirmBtn: 'OK',
cancelBtn: 'Annuler'
},
tls: {
description: 'Activer le protocole HTTPS',
tip: 'Attention : l\'utilisation de HTTPS peut augmenter la latence, surtout en mode vidéo MJPEG.'
},
dns: {
title: 'DNS',
description: 'Configurer les serveurs DNS pour NanoKVM',
mode: 'Mode',
dhcp: 'DHCP',
manual: 'Manuel',
add: 'Ajouter un DNS',
save: 'Enregistrer',
invalid: 'Veuillez saisir une adresse IP valide',
noDhcp: 'Aucun DNS DHCP n\'est actuellement disponible',
saved: 'Paramètres DNS enregistrés',
saveFailed: 'Échec de l\'enregistrement des paramètres DNS',
unsaved: 'Modifications non enregistrées',
maxServers: '{{count}} serveurs DNS maximum autorisés',
dnsServers: 'Serveurs DNS',
dhcpServersDescription: 'Les serveurs DNS sont obtenus automatiquement par DHCP.',
manualServersDescription: 'Les serveurs DNS peuvent être modifiés manuellement.',
networkDetails: 'Détails du réseau',
interface: 'Interface',
ipAddress: 'Adresse IP',
subnetMask: 'Masque de sous-réseau',
router: 'Routeur',
none: 'Aucun'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -183,16 +183,57 @@ const hu = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Hálózat',
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi beállítása',
apMode: 'Az AP mód engedélyezve van, csatlakozzon a Wi-Fihez a QR-kód beolvasásával',
connect: 'Wi-Fi csatlakoztatása',
connectDesc1: 'Adja meg a hálózat SSID-jét és jelszavát',
connectDesc2: 'Adja meg a jelszót a hálózathoz való csatlakozáshoz',
disconnect: 'Biztosan bontja a hálózati kapcsolatot?',
failed: 'A csatlakozás sikertelen, próbálja újra.',
ssid: 'Név',
password: 'Jelszó',
joinBtn: 'Csatlakozás',
confirmBtn: 'OK',
cancelBtn: 'Mégse'
},
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.'
},
dns: {
title: 'DNS',
description: 'DNS-kiszolgálók beállítása a NanoKVM számára',
mode: 'Mód',
dhcp: 'DHCP',
manual: 'Kézi',
add: 'DNS hozzáadása',
save: 'Mentés',
invalid: 'Adjon meg egy érvényes IP-címet',
noDhcp: 'Jelenleg nincs elérhető DHCP DNS',
saved: 'DNS-beállítások mentve',
saveFailed: 'Nem sikerült menteni a DNS-beállításokat',
unsaved: 'Nem mentett módosítások',
maxServers: 'Legfeljebb {{count}} DNS-kiszolgáló engedélyezett',
dnsServers: 'DNS-kiszolgálók',
dhcpServersDescription: 'A DNS-kiszolgálók automatikusan DHCP-n keresztül érkeznek.',
manualServersDescription: 'A DNS-kiszolgálók kézzel szerkeszthetők.',
networkDetails: 'Hálózati részletek',
interface: 'Interfész',
ipAddress: 'IP-cím',
subnetMask: 'Alhálózati maszk',
router: 'Router',
none: 'Nincs'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -182,16 +182,57 @@ const id = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Jaringan',
wifi: {
title: 'Wi-Fi',
description: 'Konfigurasi Wi-Fi',
apMode: 'Mode AP aktif, sambungkan ke Wi-Fi dengan memindai kode QR',
connect: 'Hubungkan Wi-Fi',
connectDesc1: 'Masukkan SSID jaringan dan kata sandi',
connectDesc2: 'Masukkan kata sandi untuk bergabung ke jaringan ini',
disconnect: 'Yakin ingin memutuskan jaringan?',
failed: 'Koneksi gagal, coba lagi.',
ssid: 'Nama',
password: 'Kata sandi',
joinBtn: 'Gabung',
confirmBtn: 'OK',
cancelBtn: 'Batal'
},
tls: {
description: 'Aktifkan protokol HTTPS',
tip: 'Perhatian: Menggunakan HTTPS dapat meningkatkan latensi, terutama pada mode video MJPEG.'
},
dns: {
title: 'DNS',
description: 'Konfigurasi server DNS untuk NanoKVM',
mode: 'Mode',
dhcp: 'DHCP',
manual: 'Manual',
add: 'Tambah DNS',
save: 'Simpan',
invalid: 'Masukkan alamat IP yang valid',
noDhcp: 'DNS DHCP saat ini tidak tersedia',
saved: 'Pengaturan DNS disimpan',
saveFailed: 'Gagal menyimpan pengaturan DNS',
unsaved: 'Perubahan belum disimpan',
maxServers: 'Maksimal {{count}} server DNS diizinkan',
dnsServers: 'Server DNS',
dhcpServersDescription: 'Server DNS diperoleh otomatis dari DHCP.',
manualServersDescription: 'Server DNS dapat diedit secara manual.',
networkDetails: 'Detail Jaringan',
interface: 'Antarmuka',
ipAddress: 'Alamat IP',
subnetMask: 'Subnet Mask',
router: 'Router',
none: 'Tidak ada'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -183,16 +183,57 @@ const it = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Rete',
wifi: {
title: 'Wi-Fi',
description: 'Configura Wi-Fi',
apMode: 'La modalità AP è attiva, connettiti al Wi-Fi scansionando il codice QR',
connect: 'Connetti Wi-Fi',
connectDesc1: 'Inserisci SSID e password della rete',
connectDesc2: 'Inserisci la password per unirti a questa rete',
disconnect: 'Vuoi davvero disconnettere la rete?',
failed: 'Connessione non riuscita, riprova.',
ssid: 'Nome',
password: 'Password',
joinBtn: 'Connetti',
confirmBtn: 'OK',
cancelBtn: 'Annulla'
},
tls: {
description: 'Abilita protocollo HTTPS',
tip: 'Attenzione: l\'uso di HTTPS può aumentare la latenza, soprattutto in modalità video MJPEG.'
},
dns: {
title: 'DNS',
description: 'Configura i server DNS per NanoKVM',
mode: 'Modalità',
dhcp: 'DHCP',
manual: 'Manuale',
add: 'Aggiungi DNS',
save: 'Salva',
invalid: 'Inserisci un indirizzo IP valido',
noDhcp: 'Nessun DNS DHCP è attualmente disponibile',
saved: 'Impostazioni DNS salvate',
saveFailed: 'Impossibile salvare le impostazioni DNS',
unsaved: 'Modifiche non salvate',
maxServers: 'Sono consentiti al massimo {{count}} server DNS',
dnsServers: 'Server DNS',
dhcpServersDescription: 'I server DNS vengono ottenuti automaticamente da DHCP.',
manualServersDescription: 'I server DNS possono essere modificati manualmente.',
networkDetails: 'Dettagli rete',
interface: 'Interfaccia',
ipAddress: 'Indirizzo IP',
subnetMask: 'Subnet mask',
router: 'Router',
none: 'Nessuno'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -267,29 +267,10 @@ const ja = {
1800: '30分',
3600: '1時間'
},
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi 設定',
apMode: 'AP モードが有効になりました。QR コードをスキャンして Wi-Fi に接続してください。',
connect: 'Wi-Fi に接続',
connectDesc1: 'SSID とパスワードを入力してください',
connectDesc2: 'このネットワークに接続するためのパスワードを入力してください',
disconnect: 'このネットワーク接続を切断しますか?',
failed: '接続に失敗しました。もう一度お試しください。',
ssid: 'SSID',
password: 'パスワード',
joinBtn: '接続',
confirmBtn: 'OK',
cancelBtn: 'キャンセル'
},
ssh: {
description: 'SSH リモートアクセスを有効にする',
tip: '使用する前に必ず強力なパスワードを設定してください(アカウント - パスワードの変更)'
},
tls: {
description: 'HTTPS プロトコルを有効にする',
tip: '注意HTTPS を使用すると、特に MJPEG ビデオモードで遅延が増加する可能性があります。'
},
advanced: '詳細設定',
swap: {
disable: '無効',
@@ -330,6 +311,52 @@ const ja = {
okBtn: 'はい',
cancelBtn: 'いいえ'
},
network: {
title: 'ネットワーク',
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi 設定',
apMode: 'AP モードが有効になりました。QR コードをスキャンして Wi-Fi に接続してください。',
connect: 'Wi-Fi に接続',
connectDesc1: 'SSID とパスワードを入力してください',
connectDesc2: 'このネットワークに接続するためのパスワードを入力してください',
disconnect: 'このネットワーク接続を切断しますか?',
failed: '接続に失敗しました。もう一度お試しください。',
ssid: 'SSID',
password: 'パスワード',
joinBtn: '接続',
confirmBtn: 'OK',
cancelBtn: 'キャンセル'
},
tls: {
description: 'HTTPS プロトコルを有効にする',
tip: '注意HTTPS を使用すると、特に MJPEG ビデオモードで遅延が増加する可能性があります。'
},
dns: {
title: 'DNS',
description: 'NanoKVM の DNS サーバーを設定',
mode: 'モード',
dhcp: 'DHCP',
manual: '手動',
add: 'DNS を追加',
save: '保存',
invalid: '有効な IP アドレスを入力してください',
noDhcp: '現在 DHCP DNS は利用できません',
saved: 'DNS 設定を保存しました',
saveFailed: 'DNS 設定の保存に失敗しました',
unsaved: '未保存の変更',
maxServers: 'DNS サーバーは最大 {{count}} 個までです',
dnsServers: 'DNS サーバー',
dhcpServersDescription: 'DNS サーバーは DHCP から自動取得されます。',
manualServersDescription: 'DNS サーバーは手動で編集できます。',
networkDetails: 'ネットワーク詳細',
interface: 'インターフェイス',
ipAddress: 'IP アドレス',
subnetMask: 'サブネットマスク',
router: 'ルーター',
none: 'なし'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -246,19 +246,10 @@ const ko = {
1800: '30분',
3600: '1시간'
},
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi 설정',
setBtn: '설정'
},
ssh: {
description: 'SSH 원격 접속 활성화',
tip: '활성화하기 전에 강력한 비밀번호를 설정하세요. (계정 - 비밀번호 변경)'
},
tls: {
description: 'HTTPS 프로토콜 활성화',
tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.'
},
advanced: '고급 설정',
swap: {
title: '스왑',
@@ -290,6 +281,52 @@ const ko = {
okBtn: '네',
cancelBtn: '아니오'
},
network: {
title: '네트워크',
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi 설정',
apMode: 'AP 모드가 활성화되었습니다. QR 코드를 스캔하여 Wi-Fi에 연결하세요',
connect: 'Wi-Fi 연결',
connectDesc1: '네트워크 SSID와 비밀번호를 입력하세요',
connectDesc2: '이 네트워크에 연결할 비밀번호를 입력하세요',
disconnect: '네트워크 연결을 끊으시겠습니까?',
failed: '연결에 실패했습니다. 다시 시도하세요.',
ssid: '이름',
password: '비밀번호',
joinBtn: '연결',
confirmBtn: '확인',
cancelBtn: '취소'
},
tls: {
description: 'HTTPS 프로토콜 활성화',
tip: '주의: HTTPS 사용 시 특히 MJPEG 비디오 모드에서 지연 시간이 증가할 수 있습니다.'
},
dns: {
title: 'DNS',
description: 'NanoKVM의 DNS 서버 설정',
mode: '모드',
dhcp: 'DHCP',
manual: '수동',
add: 'DNS 추가',
save: '저장',
invalid: '유효한 IP 주소를 입력하세요',
noDhcp: '현재 사용 가능한 DHCP DNS가 없습니다',
saved: 'DNS 설정이 저장되었습니다',
saveFailed: 'DNS 설정 저장 실패',
unsaved: '저장되지 않은 변경사항',
maxServers: '최대 {{count}}개의 DNS 서버가 허용됩니다',
dnsServers: 'DNS 서버',
dhcpServersDescription: 'DNS 서버는 DHCP에서 자동으로 가져옵니다.',
manualServersDescription: 'DNS 서버를 수동으로 편집할 수 있습니다.',
networkDetails: '네트워크 세부 정보',
interface: '인터페이스',
ipAddress: 'IP 주소',
subnetMask: '서브넷 마스크',
router: '라우터',
none: '없음'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -182,16 +182,57 @@ const nb = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Nettverk',
wifi: {
title: 'Wi-Fi',
description: 'Konfigurer Wi-Fi',
apMode: 'AP-modus er aktivert, koble til Wi-Fi ved å skanne QR-koden',
connect: 'Koble til Wi-Fi',
connectDesc1: 'Skriv inn nettverkets SSID og passord',
connectDesc2: 'Skriv inn passordet for å koble til dette nettverket',
disconnect: 'Er du sikker på at du vil koble fra nettverket?',
failed: 'Tilkobling mislyktes, prøv igjen.',
ssid: 'Navn',
password: 'Passord',
joinBtn: 'Koble til',
confirmBtn: 'OK',
cancelBtn: 'Avbryt'
},
tls: {
description: 'Aktiver HTTPS-protokoll',
tip: 'Merk: Bruk av HTTPS kan øke forsinkelsen, spesielt i MJPEG-videomodus.'
},
dns: {
title: 'DNS',
description: 'Konfigurer DNS-servere for NanoKVM',
mode: 'Modus',
dhcp: 'DHCP',
manual: 'Manuell',
add: 'Legg til DNS',
save: 'Lagre',
invalid: 'Skriv inn en gyldig IP-adresse',
noDhcp: 'Ingen DHCP-DNS er tilgjengelig nå',
saved: 'DNS-innstillinger lagret',
saveFailed: 'Kunne ikke lagre DNS-innstillinger',
unsaved: 'Ulagrede endringer',
maxServers: 'Maksimalt {{count}} DNS-servere er tillatt',
dnsServers: 'DNS-servere',
dhcpServersDescription: 'DNS-servere hentes automatisk fra DHCP.',
manualServersDescription: 'DNS-servere kan redigeres manuelt.',
networkDetails: 'Nettverksdetaljer',
interface: 'Grensesnitt',
ipAddress: 'IP-adresse',
subnetMask: 'Subnettmaske',
router: 'Ruter',
none: 'Ingen'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -182,16 +182,57 @@ const nl = {
1800: '30 min',
3600: '1 uur'
},
wifi: {
title: 'Wifi',
description: 'Configureer wifi',
setBtn: 'Config'
},
disk: 'Virtuele schijf',
diskDesc: 'Koppel virtuele U-schijf aan de externe host',
network: 'Virtueel Netwerk',
networkDesc: 'Koppel virtueel netwerk kaart aan de externe host'
},
network: {
title: 'Netwerk',
wifi: {
title: 'Wifi',
description: 'Configureer wifi',
apMode: 'AP-modus is ingeschakeld, maak verbinding met wifi door de QR-code te scannen',
connect: 'Wifi verbinden',
connectDesc1: 'Voer de netwerk-SSID en het wachtwoord in',
connectDesc2: 'Voer het wachtwoord in om met dit netwerk te verbinden',
disconnect: 'Weet je zeker dat je de netwerkverbinding wilt verbreken?',
failed: 'Verbinding mislukt, probeer het opnieuw.',
ssid: 'Naam',
password: 'Wachtwoord',
joinBtn: 'Verbinden',
confirmBtn: 'OK',
cancelBtn: 'Annuleren'
},
tls: {
description: 'HTTPS-protocol inschakelen',
tip: 'Let op: HTTPS gebruiken kan de latentie verhogen, vooral in MJPEG-videomodus.'
},
dns: {
title: 'DNS',
description: 'Configureer DNS-servers voor NanoKVM',
mode: 'Modus',
dhcp: 'DHCP',
manual: 'Handmatig',
add: 'DNS toevoegen',
save: 'Opslaan',
invalid: 'Voer een geldig IP-adres in',
noDhcp: 'Er is momenteel geen DHCP-DNS beschikbaar',
saved: 'DNS-instellingen opgeslagen',
saveFailed: 'DNS-instellingen opslaan mislukt',
unsaved: 'Niet-opgeslagen wijzigingen',
maxServers: 'Maximaal {{count}} DNS-servers toegestaan',
dnsServers: 'DNS-servers',
dhcpServersDescription: 'DNS-servers worden automatisch via DHCP verkregen.',
manualServersDescription: 'DNS-servers kunnen handmatig worden bewerkt.',
networkDetails: 'Netwerkdetails',
interface: 'Interface',
ipAddress: 'IP-adres',
subnetMask: 'Subnetmasker',
router: 'Router',
none: 'Geen'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -184,16 +184,57 @@ const pl = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Sieć',
wifi: {
title: 'Wi-Fi',
description: 'Skonfiguruj Wi-Fi',
apMode: 'Tryb AP jest włączony, połącz z Wi-Fi skanując kod QR',
connect: 'Połącz Wi-Fi',
connectDesc1: 'Wprowadź SSID sieci i hasło',
connectDesc2: 'Wprowadź hasło, aby połączyć się z tą siecią',
disconnect: 'Czy na pewno chcesz rozłączyć sieć?',
failed: 'Połączenie nie powiodło się, spróbuj ponownie.',
ssid: 'Nazwa',
password: 'Hasło',
joinBtn: 'Połącz',
confirmBtn: 'OK',
cancelBtn: 'Anuluj'
},
tls: {
description: 'Włącz protokół HTTPS',
tip: 'Uwaga: użycie HTTPS może zwiększyć opóźnienie, szczególnie w trybie wideo MJPEG.'
},
dns: {
title: 'DNS',
description: 'Skonfiguruj serwery DNS dla NanoKVM',
mode: 'Tryb',
dhcp: 'DHCP',
manual: 'Ręcznie',
add: 'Dodaj DNS',
save: 'Zapisz',
invalid: 'Wprowadź prawidłowy adres IP',
noDhcp: 'Brak obecnie dostępnego DNS z DHCP',
saved: 'Ustawienia DNS zapisane',
saveFailed: 'Nie udało się zapisać ustawień DNS',
unsaved: 'Niezapisane zmiany',
maxServers: 'Dozwolone jest maksymalnie {{count}} serwerów DNS',
dnsServers: 'Serwery DNS',
dhcpServersDescription: 'Serwery DNS są automatycznie pobierane z DHCP.',
manualServersDescription: 'Serwery DNS można edytować ręcznie.',
networkDetails: 'Szczegóły sieci',
interface: 'Interfejs',
ipAddress: 'Adres IP',
subnetMask: 'Maska podsieci',
router: 'Router',
none: 'Brak'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -226,19 +226,10 @@ const pt_br = {
1800: '30 min',
3600: '1 hora'
},
wifi: {
title: 'Wi-Fi',
description: 'Configurar Wi-Fi',
setBtn: 'Configurar'
},
ssh: {
description: 'Habilitar acesso remoto SSH',
tip: 'Defina uma senha forte antes de habilitar (Conta - Mudar Senha)'
},
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.'
},
advanced: 'Configurações Avançadas',
swap: {
title: 'Swap',
@@ -270,6 +261,52 @@ const pt_br = {
okBtn: 'Sim',
cancelBtn: 'Não'
},
network: {
title: 'Rede',
wifi: {
title: 'Wi-Fi',
description: 'Configurar Wi-Fi',
apMode: 'O modo AP está ativado, conecte-se ao Wi-Fi escaneando o QR code',
connect: 'Conectar Wi-Fi',
connectDesc1: 'Digite o SSID da rede e a senha',
connectDesc2: 'Digite a senha para entrar nesta rede',
disconnect: 'Tem certeza de que deseja desconectar a rede?',
failed: 'Falha na conexão, tente novamente.',
ssid: 'Nome',
password: 'Senha',
joinBtn: 'Entrar',
confirmBtn: 'OK',
cancelBtn: 'Cancelar'
},
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.'
},
dns: {
title: 'DNS',
description: 'Configurar servidores DNS para o NanoKVM',
mode: 'Modo',
dhcp: 'DHCP',
manual: 'Manual',
add: 'Adicionar DNS',
save: 'Salvar',
invalid: 'Digite um endereço IP válido',
noDhcp: 'Nenhum DNS DHCP está disponível no momento',
saved: 'Configurações de DNS salvas',
saveFailed: 'Falha ao salvar as configurações de DNS',
unsaved: 'Alterações não salvas',
maxServers: 'Máximo de {{count}} servidores DNS permitido',
dnsServers: 'Servidores DNS',
dhcpServersDescription: 'Os servidores DNS são obtidos automaticamente via DHCP.',
manualServersDescription: 'Os servidores DNS podem ser editados manualmente.',
networkDetails: 'Detalhes da rede',
interface: 'Interface',
ipAddress: 'Endereço IP',
subnetMask: 'Máscara de sub-rede',
router: 'Roteador',
none: 'Nenhum'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -231,19 +231,10 @@ const ru = {
1800: '30 мин',
3600: '1 час'
},
wifi: {
title: 'Wi-Fi',
description: 'Настройка Wi-Fi',
setBtn: 'Конфигурация'
},
ssh: {
description: 'Включить удаленный доступ по SSH',
tip: 'Задайте сильный пароль перед включением (Аккаунт - Пароль - Обновить)'
},
tls: {
description: 'Включить HTTPS Protocol',
tip: 'Имейте в виду: использование HTTPS может увеличить задержку, особенно в режиме видео MJPEG.'
},
advanced: 'Дополнительные настройки',
swap: {
title: 'Файл подкачки',
@@ -272,6 +263,52 @@ const ru = {
okBtn: 'Да',
cancelBtn: 'Нет'
},
network: {
title: 'Сеть',
wifi: {
title: 'Wi-Fi',
description: 'Настройка Wi-Fi',
apMode: 'Режим AP включен, подключитесь к Wi-Fi, отсканировав QR-код',
connect: 'Подключить Wi-Fi',
connectDesc1: 'Введите SSID сети и пароль',
connectDesc2: 'Введите пароль для подключения к этой сети',
disconnect: 'Вы уверены, что хотите отключить сеть?',
failed: 'Не удалось подключиться, попробуйте снова.',
ssid: 'Имя',
password: 'Пароль',
joinBtn: 'Подключить',
confirmBtn: 'OK',
cancelBtn: 'Отмена'
},
tls: {
description: 'Включить протокол HTTPS',
tip: 'Имейте в виду: использование HTTPS может увеличить задержку, особенно в режиме видео MJPEG.'
},
dns: {
title: 'DNS',
description: 'Настройка DNS-серверов для NanoKVM',
mode: 'Режим',
dhcp: 'DHCP',
manual: 'Вручную',
add: 'Добавить DNS',
save: 'Сохранить',
invalid: 'Введите допустимый IP-адрес',
noDhcp: 'DNS от DHCP сейчас недоступен',
saved: 'Настройки DNS сохранены',
saveFailed: 'Не удалось сохранить настройки DNS',
unsaved: 'Несохраненные изменения',
maxServers: 'Разрешено не более {{count}} DNS-серверов',
dnsServers: 'DNS-серверы',
dhcpServersDescription: 'DNS-серверы автоматически получаются от DHCP.',
manualServersDescription: 'DNS-серверы можно редактировать вручную.',
networkDetails: 'Сведения о сети',
interface: 'Интерфейс',
ipAddress: 'IP-адрес',
subnetMask: 'Маска подсети',
router: 'Маршрутизатор',
none: 'Нет'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -242,19 +242,10 @@ const se = {
1800: '30 min',
3600: '1 timme'
},
wifi: {
title: 'Wi-Fi',
description: 'Konfigurera Wi-Fi',
setBtn: 'Konfigurera'
},
ssh: {
description: 'Aktivera SSH-fjärråtkomst',
tip: 'Ställ in ett starkt lösenord innan du aktiverar (Konto - Byt lösenord)'
},
tls: {
description: 'Aktivera HTTPS-protokoll',
tip: 'Observera: Användning av HTTPS kan öka fördröjningen, särskilt med MJPEG-läge.'
},
advanced: 'Avancerade inställningar',
swap: {
title: 'Swap',
@@ -286,6 +277,52 @@ const se = {
okBtn: 'Ja',
cancelBtn: 'Nej'
},
network: {
title: 'Nätverk',
wifi: {
title: 'Wi-Fi',
description: 'Konfigurera Wi-Fi',
apMode: 'AP-läge är aktiverat, anslut till Wi-Fi genom att skanna QR-koden',
connect: 'Anslut Wi-Fi',
connectDesc1: 'Ange nätverkets SSID och lösenord',
connectDesc2: 'Ange lösenordet för att ansluta till detta nätverk',
disconnect: 'Är du säker på att du vill koppla från nätverket?',
failed: 'Anslutningen misslyckades, försök igen.',
ssid: 'Namn',
password: 'Lösenord',
joinBtn: 'Anslut',
confirmBtn: 'OK',
cancelBtn: 'Avbryt'
},
tls: {
description: 'Aktivera HTTPS-protokoll',
tip: 'Observera: Användning av HTTPS kan öka fördröjningen, särskilt med MJPEG-läge.'
},
dns: {
title: 'DNS',
description: 'Konfigurera DNS-servrar för NanoKVM',
mode: 'Läge',
dhcp: 'DHCP',
manual: 'Manuell',
add: 'Lägg till DNS',
save: 'Spara',
invalid: 'Ange en giltig IP-adress',
noDhcp: 'Ingen DHCP-DNS är tillgänglig just nu',
saved: 'DNS-inställningar sparade',
saveFailed: 'Det gick inte att spara DNS-inställningar',
unsaved: 'Osparade ändringar',
maxServers: 'Maximalt {{count}} DNS-servrar tillåtna',
dnsServers: 'DNS-servrar',
dhcpServersDescription: 'DNS-servrar hämtas automatiskt från DHCP.',
manualServersDescription: 'DNS-servrar kan redigeras manuellt.',
networkDetails: 'Nätverksdetaljer',
interface: 'Gränssnitt',
ipAddress: 'IP-adress',
subnetMask: 'Subnätmask',
router: 'Router',
none: 'Ingen'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -205,11 +205,6 @@ const th = {
1800: '30 นาที',
3600: '1 ชั่วโมง'
},
wifi: {
title: 'Wi-Fi',
description: 'ตั้งค่า Wi-Fi',
setBtn: 'ยืนยัน'
},
ssh: {
description: 'เปิดใช้งาน SSH',
tip: 'ตั้งรหัสผ่านทีปลอดถัยก่อนเปิดใช้งาน (บัญชี - เปลี่ยนรหัสผ่าน)'
@@ -219,6 +214,52 @@ const th = {
network: 'เครือข่ายจำลอง',
networkDesc: 'เปิดใช้งานอุปกรณ์เครือข่ายจำลอง'
},
network: {
title: 'เครือข่าย',
wifi: {
title: 'Wi-Fi',
description: 'ตั้งค่า Wi-Fi',
apMode: 'เปิดใช้งานโหมด AP แล้ว ให้เชื่อมต่อ Wi-Fi โดยสแกนรหัส QR',
connect: 'เชื่อมต่อ Wi-Fi',
connectDesc1: 'กรุณาใส่ SSID และรหัสผ่านของเครือข่าย',
connectDesc2: 'กรุณาใส่รหัสผ่านเพื่อเข้าร่วมเครือข่ายนี้',
disconnect: 'คุณแน่ใจหรือไม่ว่าต้องการตัดการเชื่อมต่อเครือข่าย?',
failed: 'เชื่อมต่อไม่สำเร็จ กรุณาลองอีกครั้ง',
ssid: 'ชื่อ',
password: 'รหัสผ่าน',
joinBtn: 'เข้าร่วม',
confirmBtn: 'ตกลง',
cancelBtn: 'ยกเลิก'
},
tls: {
description: 'เปิดใช้งานโปรโตคอล HTTPS',
tip: 'โปรดทราบ: การใช้ HTTPS อาจเพิ่มความหน่วง โดยเฉพาะในโหมดวิดีโอ MJPEG'
},
dns: {
title: 'DNS',
description: 'ตั้งค่าเซิร์ฟเวอร์ DNS สำหรับ NanoKVM',
mode: 'โหมด',
dhcp: 'DHCP',
manual: 'กำหนดเอง',
add: 'เพิ่ม DNS',
save: 'บันทึก',
invalid: 'กรุณาใส่ที่อยู่ IP ที่ถูกต้อง',
noDhcp: 'ขณะนี้ไม่มี DHCP DNS ให้ใช้งาน',
saved: 'บันทึกการตั้งค่า DNS แล้ว',
saveFailed: 'บันทึกการตั้งค่า DNS ไม่สำเร็จ',
unsaved: 'มีการเปลี่ยนแปลงที่ยังไม่ได้บันทึก',
maxServers: 'อนุญาตเซิร์ฟเวอร์ DNS ได้สูงสุด {{count}} รายการ',
dnsServers: 'เซิร์ฟเวอร์ DNS',
dhcpServersDescription: 'เซิร์ฟเวอร์ DNS จะได้รับจาก DHCP โดยอัตโนมัติ',
manualServersDescription: 'สามารถแก้ไขเซิร์ฟเวอร์ DNS ได้ด้วยตนเอง',
networkDetails: 'รายละเอียดเครือข่าย',
interface: 'อินเทอร์เฟซ',
ipAddress: 'ที่อยู่ IP',
subnetMask: 'Subnet Mask',
router: 'เราเตอร์',
none: 'ไม่มี'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -230,19 +230,10 @@ const tr = {
1800: '30 dakika',
3600: '1 saat'
},
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi ayarlayın',
setBtn: 'Ayarla'
},
ssh: {
description: 'Güvenli Kabuk Bağlantısı (SSH) aktif et',
tip: 'Aktifleştirmeden önce güçlü bir şifreye sahip olduğunuzdan emin olun (Hesap - Şifremi Değiştir)'
},
tls: {
description: 'HTTPS protokolünü etkinleştir',
tip: 'HTTPS protokolü bağlantıda gecikmeye sebep olabilir, özellikle MJPEG görüntü modu ile.'
},
advanced: 'Gelişmiş Ayarlar',
swap: {
title: 'Swap',
@@ -274,6 +265,52 @@ const tr = {
okBtn: 'Evet',
cancelBtn: 'Hayır'
},
network: {
title: 'Ağ',
wifi: {
title: 'Wi-Fi',
description: 'Wi-Fi ayarlayın',
apMode: 'AP modu etkin, QR kodu tarayarak Wi-Fi\'ye bağlanın',
connect: 'Wi-Fi\'ye bağlan',
connectDesc1: 'Lütfen ağ SSID\'sini ve parolayı girin',
connectDesc2: 'Bu ağa katılmak için parolayı girin',
disconnect: 'Ağ bağlantısını kesmek istediğinizden emin misiniz?',
failed: 'Bağlantı başarısız, lütfen tekrar deneyin.',
ssid: 'Ad',
password: 'Parola',
joinBtn: 'Katıl',
confirmBtn: 'Tamam',
cancelBtn: 'İptal'
},
tls: {
description: 'HTTPS protokolünü etkinleştir',
tip: 'HTTPS protokolü bağlantıda gecikmeye sebep olabilir, özellikle MJPEG görüntü modu ile.'
},
dns: {
title: 'DNS',
description: 'NanoKVM için DNS sunucularını yapılandır',
mode: 'Mod',
dhcp: 'DHCP',
manual: 'Manuel',
add: 'DNS ekle',
save: 'Kaydet',
invalid: 'Geçerli bir IP adresi girin',
noDhcp: 'Şu anda DHCP DNS mevcut değil',
saved: 'DNS ayarları kaydedildi',
saveFailed: 'DNS ayarları kaydedilemedi',
unsaved: 'Kaydedilmemiş değişiklikler',
maxServers: 'En fazla {{count}} DNS sunucusuna izin verilir',
dnsServers: 'DNS Sunucuları',
dhcpServersDescription: 'DNS sunucuları DHCP\'den otomatik olarak alınır.',
manualServersDescription: 'DNS sunucuları manuel olarak düzenlenebilir.',
networkDetails: 'Ağ Ayrıntıları',
interface: 'Arayüz',
ipAddress: 'IP Adresi',
subnetMask: 'Alt Ağ Maskesi',
router: 'Yönlendirici',
none: 'Yok'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -227,19 +227,10 @@ const uk = {
1800: '30 хв',
3600: '1 година'
},
wifi: {
title: 'Wi-Fi',
description: 'Налаштування Wi-Fi',
setBtn: 'Конфігурація'
},
ssh: {
description: 'Увімкніть віддалений доступ по SSH',
tip: 'Перед ввімкненням встановіть надійний пароль (Обліковий запис - Зміна пароля)'
},
tls: {
description: 'Увімкнути протокол HTTPS',
tip: 'Будьте в курсі: Використання HTTPS може збільшити затримку, особливо в режимі відео MJPEG.'
},
advanced: 'Розширені налаштування',
swap: {
title: 'Swap (файл підкачки)',
@@ -268,6 +259,52 @@ const uk = {
okBtn: 'Так',
cancelBtn: 'Ні'
},
network: {
title: 'Мережа',
wifi: {
title: 'Wi-Fi',
description: 'Налаштування Wi-Fi',
apMode: 'Режим AP увімкнено, підключіться до Wi-Fi, відсканувавши QR-код',
connect: 'Підключити Wi-Fi',
connectDesc1: 'Введіть SSID мережі та пароль',
connectDesc2: 'Введіть пароль, щоб підключитися до цієї мережі',
disconnect: 'Ви впевнені, що хочете відключити мережу?',
failed: 'Не вдалося підключитися, спробуйте ще раз.',
ssid: 'Назва',
password: 'Пароль',
joinBtn: 'Підключити',
confirmBtn: 'OK',
cancelBtn: 'Скасувати'
},
tls: {
description: 'Увімкнути протокол HTTPS',
tip: 'Будьте в курсі: Використання HTTPS може збільшити затримку, особливо в режимі відео MJPEG.'
},
dns: {
title: 'DNS',
description: 'Налаштування DNS-серверів для NanoKVM',
mode: 'Режим',
dhcp: 'DHCP',
manual: 'Вручну',
add: 'Додати DNS',
save: 'Зберегти',
invalid: 'Введіть дійсну IP-адресу',
noDhcp: 'Наразі DHCP DNS недоступний',
saved: 'Налаштування DNS збережено',
saveFailed: 'Не вдалося зберегти налаштування DNS',
unsaved: 'Незбережені зміни',
maxServers: 'Дозволено щонайбільше {{count}} DNS-серверів',
dnsServers: 'DNS-сервери',
dhcpServersDescription: 'DNS-сервери автоматично отримуються з DHCP.',
manualServersDescription: 'DNS-сервери можна редагувати вручну.',
networkDetails: 'Відомості про мережу',
interface: 'Інтерфейс',
ipAddress: 'IP-адреса',
subnetMask: 'Маска підмережі',
router: 'Маршрутизатор',
none: 'Немає'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -181,16 +181,57 @@ const vi = {
1800: '30 min',
3600: '1 hour'
},
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
},
disk: 'Virtual Disk',
diskDesc: 'Mount virtual U-disk on the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host'
},
network: {
title: 'Mạng',
wifi: {
title: 'Wi-Fi',
description: 'Cấu hình Wi-Fi',
apMode: 'Chế độ AP đang bật, hãy kết nối Wi-Fi bằng cách quét mã QR',
connect: 'Kết nối Wi-Fi',
connectDesc1: 'Vui lòng nhập SSID mạng và mật khẩu',
connectDesc2: 'Vui lòng nhập mật khẩu để tham gia mạng này',
disconnect: 'Bạn có chắc muốn ngắt kết nối mạng không?',
failed: 'Kết nối thất bại, vui lòng thử lại.',
ssid: 'Tên',
password: 'Mật khẩu',
joinBtn: 'Tham gia',
confirmBtn: 'OK',
cancelBtn: 'Hủy'
},
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.'
},
dns: {
title: 'DNS',
description: 'Cấu hình máy chủ DNS cho NanoKVM',
mode: 'Chế độ',
dhcp: 'DHCP',
manual: 'Thủ công',
add: 'Thêm DNS',
save: 'Lưu',
invalid: 'Vui lòng nhập địa chỉ IP hợp lệ',
noDhcp: 'Hiện không có DNS DHCP khả dụng',
saved: 'Đã lưu cài đặt DNS',
saveFailed: 'Không thể lưu cài đặt DNS',
unsaved: 'Thay đổi chưa lưu',
maxServers: 'Cho phép tối đa {{count}} máy chủ DNS',
dnsServers: 'Máy chủ DNS',
dhcpServersDescription: 'Máy chủ DNS được tự động lấy từ DHCP.',
manualServersDescription: 'Có thể chỉnh sửa máy chủ DNS thủ công.',
networkDetails: 'Chi tiết mạng',
interface: 'Giao diện',
ipAddress: 'Địa chỉ IP',
subnetMask: 'Mặt nạ mạng con',
router: 'Bộ định tuyến',
none: 'Không có'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -276,29 +276,10 @@ const zh = {
1800: '30分钟',
3600: '1小时'
},
wifi: {
title: 'Wi-Fi',
description: '配置 Wi-Fi 信息',
apMode: 'AP 模式已启用,该模式下仅支持扫描二维码配网',
connect: '连接 Wi-Fi',
connectDesc1: '请输入网络名称和密码',
connectDesc2: '请输入密码以连接此网络',
disconnect: '是否要断开该网络连接?',
failed: '连接失败,请重试',
ssid: '名称',
password: '密码',
joinBtn: '加入',
confirmBtn: '确定',
cancelBtn: '取消'
},
ssh: {
description: '启用 SSH 远程访问',
tip: '启用前请务必设置强密码(帐号 - 修改密码)'
},
tls: {
description: '启用 HTTPS 协议',
tip: '注意:使用 HTTPS 可能导致延迟增加,特别是在 MJPEG 视频模式下。'
},
advanced: '高级设置',
swap: {
disable: '禁用',
@@ -339,6 +320,52 @@ const zh = {
okBtn: '是',
cancelBtn: '否'
},
network: {
title: '网络',
wifi: {
title: 'Wi-Fi',
description: '配置 Wi-Fi 信息',
apMode: 'AP 模式已启用,该模式下仅支持扫描二维码配网',
connect: '连接 Wi-Fi',
connectDesc1: '请输入网络名称和密码',
connectDesc2: '请输入密码以连接此网络',
disconnect: '是否要断开该网络连接?',
failed: '连接失败,请重试',
ssid: '名称',
password: '密码',
joinBtn: '加入',
confirmBtn: '确定',
cancelBtn: '取消'
},
tls: {
description: '启用 HTTPS 协议',
tip: '注意:使用 HTTPS 可能导致延迟增加,特别是在 MJPEG 视频模式下。'
},
dns: {
title: 'DNS',
description: '配置 NanoKVM 使用的 DNS 服务器',
mode: '模式',
dhcp: 'DHCP',
manual: '手动',
add: '添加 DNS',
save: '保存',
invalid: '请输入有效的 IP 地址',
noDhcp: '当前未获取到 DHCP DNS',
saved: 'DNS 设置已保存',
saveFailed: '保存 DNS 设置失败',
unsaved: '有未保存的更改',
maxServers: '最多允许 {{count}} 个 DNS 服务器',
dnsServers: 'DNS 服务器',
dhcpServersDescription: 'DNS 服务器由 DHCP 自动获取。',
manualServersDescription: 'DNS 服务器可以手动编辑。',
networkDetails: '网络详情',
interface: '接口',
ipAddress: 'IP 地址',
subnetMask: '子网掩码',
router: '路由器',
none: '无'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -258,30 +258,10 @@ const zh_tw = {
1800: '30 分鐘',
3600: '1 小時'
},
wifi: {
title: 'Wi-Fi',
description: '設定 Wi-Fi',
setBtn: '設定',
apMode: 'AP 模式已啟用,該模式下僅支援掃描 QRCode 配網',
connect: '連線 Wi-Fi',
connectDesc1: '請輸入網路名稱和密碼',
connectDesc2: '請輸入密碼以連線此網路',
disconnect: '是否要中斷該網路連線?',
failed: '連線失敗,請重試',
ssid: 'SSID 名稱',
password: '密碼',
joinBtn: '加入',
confirmBtn: '確定',
cancelBtn: '取消'
},
ssh: {
description: '啟用 SSH 伺服器',
tip: '啟用前請務必設定強密碼(帳號 - 更改密碼)'
},
tls: {
description: '啟用 HTTPS 協議',
tip: '啟用 HTTPS 可以提高安全性,但可能會增加傳輸延遲,特別是使用 MJPEG 格式傳輸時。'
},
advanced: '進階設定',
swap: {
title: 'Swap',
@@ -325,6 +305,52 @@ const zh_tw = {
okBtn: '確定',
cancelBtn: '取消'
},
network: {
title: '網路',
wifi: {
title: 'Wi-Fi',
description: '設定 Wi-Fi',
apMode: 'AP 模式已啟用,該模式下僅支援掃描 QRCode 配網',
connect: '連線 Wi-Fi',
connectDesc1: '請輸入網路名稱和密碼',
connectDesc2: '請輸入密碼以連線此網路',
disconnect: '是否要中斷該網路連線?',
failed: '連線失敗,請重試',
ssid: 'SSID 名稱',
password: '密碼',
joinBtn: '加入',
confirmBtn: '確定',
cancelBtn: '取消'
},
tls: {
description: '啟用 HTTPS 協議',
tip: '啟用 HTTPS 可以提高安全性,但可能會增加傳輸延遲,特別是使用 MJPEG 格式傳輸時。'
},
dns: {
title: 'DNS',
description: '設定 NanoKVM 使用的 DNS 伺服器',
mode: '模式',
dhcp: 'DHCP',
manual: '手動',
add: '新增 DNS',
save: '儲存',
invalid: '請輸入有效的 IP 位址',
noDhcp: '目前未取得 DHCP DNS',
saved: 'DNS 設定已儲存',
saveFailed: '儲存 DNS 設定失敗',
unsaved: '有未儲存的變更',
maxServers: '最多允許 {{count}} 個 DNS 伺服器',
dnsServers: 'DNS 伺服器',
dhcpServersDescription: 'DNS 伺服器由 DHCP 自動取得。',
manualServersDescription: 'DNS 伺服器可以手動編輯。',
networkDetails: '網路詳細資訊',
interface: '介面',
ipAddress: 'IP 位址',
subnetMask: '子網路遮罩',
router: '路由器',
none: '無'
}
},
tailscale: {
title: 'Tailscale',
memory: {

View File

@@ -8,9 +8,7 @@ import { MouseJiggler } from './mouse-jiggler.tsx';
import { Oled } from './oled.tsx';
import { Reboot } from './reboot.tsx';
import { Ssh } from './ssh.tsx';
import { Tls } from './tls.tsx';
import { VirtualDevices } from './virtual-devices.tsx';
import { Wifi } from './wifi.tsx';
export const Device = () => {
const { t } = useTranslation();
@@ -21,7 +19,6 @@ export const Device = () => {
<Divider className="opacity-50" />
<div className="flex flex-col space-y-8">
<Tls />
<Ssh />
<Mdns />
<Hdmi />
@@ -31,7 +28,6 @@ export const Device = () => {
<Divider className="opacity-50" />
<Oled />
<Wifi />
<MouseJiggler />
<Divider className="opacity-50" />

View File

@@ -1,10 +1,11 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Badge, Modal, Tooltip } from 'antd';
import clsx from 'clsx';
import { useSetAtom } from 'jotai';
import {
BadgeInfoIcon,
CircleArrowUpIcon,
NetworkIcon,
PaletteIcon,
SettingsIcon,
SmartphoneIcon,
@@ -24,6 +25,7 @@ import { About } from './about';
import { Account } from './account';
import { Appearance } from './appearance';
import { Device } from './device';
import { Network } from './network';
import { Tailscale } from './tailscale';
import { Update } from './update';
@@ -33,6 +35,7 @@ export const Settings = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const [currentTab, setCurrentTab] = useState('about');
const scrollViewportRef = useRef<HTMLDivElement>(null);
const [isUpdateAvailable, setIsUpdateAvailable] = useState(false);
const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom);
@@ -42,6 +45,7 @@ export const Settings = () => {
{ id: 'about', icon: <BadgeInfoIcon size={16} />, component: <About /> },
{ id: 'appearance', icon: <PaletteIcon size={16} />, component: <Appearance /> },
{ id: 'device', icon: <SmartphoneIcon size={16} />, component: <Device /> },
{ id: 'network', icon: <NetworkIcon size={16} />, component: <Network /> },
{
id: 'tailscale',
icon: <TailscaleIcon />,
@@ -62,6 +66,10 @@ export const Settings = () => {
}
}, []);
useEffect(() => {
scrollViewportRef.current?.scrollTo({ top: 0, left: 0 });
}, [currentTab]);
function checkForUpdates() {
api.getVersion().then((rsp: any) => {
if (rsp.code !== 0) {
@@ -162,7 +170,10 @@ export const Settings = () => {
))}
</div>
<ScrollArea className="h-full w-full rounded-r-lg bg-neutral-900/50 px-3">
<ScrollArea
viewportRef={scrollViewportRef}
className="h-full w-full rounded-r-lg bg-neutral-900/50 px-3"
>
<div className="flex h-full w-full justify-center">
<div className="w-full max-w-[600px] pb-10 pt-14">
<>{tabs.find((tab) => tab.id === currentTab)?.component}</>

View File

@@ -0,0 +1,439 @@
import { useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { Button, Input, Segmented } from 'antd';
import { CheckIcon, PlusIcon, XIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import * as api from '@/api/network.ts';
import type { DNSMode } from '@/api/network.ts';
type DNSState = {
mode: DNSMode;
servers: string[];
dhcp: string[];
info: DNSInfo;
};
type DNSInfo = {
interface?: string;
type?: string;
address?: string;
subnetMask?: string;
gateway?: string;
};
const maxServers = 6;
function formatInterface(info: DNSInfo) {
if (!info.interface) return '';
if (!info.type) return info.interface;
return `${info.type} (${info.interface})`;
}
function normalizeServers(servers: string[]) {
const seen = new Set<string>();
const normalized: string[] = [];
for (const server of servers) {
const value = normalizeServer(server);
if (!value || seen.has(value)) continue;
seen.add(value);
normalized.push(value);
}
return normalized;
}
function normalizeServer(server: string) {
return server.split('#')[0].trim().split(/\s+/)[0] || '';
}
function isValidIP(value: string) {
return isValidIPv4(value) || isValidIPv6(value);
}
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 isValidIPv6(value: string) {
if (!value.includes(':')) return false;
try {
new URL(`http://[${value}]/`);
return true;
} catch {
return false;
}
}
const Panel = ({
title,
description,
children
}: {
title: string;
description?: string;
children: ReactNode;
}) => {
return (
<div className="overflow-hidden rounded-xl bg-neutral-800/50">
<div className="px-4 pb-1.5 pt-3">
<div className="font-semibold text-neutral-100">{title}</div>
{description && (
<div className="mt-0.5 text-xs leading-snug text-neutral-500">{description}</div>
)}
</div>
<div>{children}</div>
</div>
);
};
const InfoRow = ({
label,
value,
isLast = false
}: {
label: string;
value?: string;
isLast?: boolean;
}) => {
return (
<div className="px-4">
<div
className={`flex min-h-[44px] items-center justify-between ${
isLast ? '' : 'border-b border-neutral-700/50'
}`}
>
<span className="text-sm text-neutral-300">{label}</span>
<span className="max-w-[330px] break-all text-right text-sm text-neutral-500">
{value || '-'}
</span>
</div>
</div>
);
};
const ServerList = ({ servers }: { servers: string[] }) => {
const { t } = useTranslation();
if (!servers.length) {
return (
<div className="px-4 py-3 text-sm text-neutral-500">{t('settings.network.dns.none')}</div>
);
}
return (
<div>
{servers.map((server, index) => (
<div key={`${server}-${index}`} className="px-4">
<div
className={`min-h-[44px] py-2.5 text-sm text-neutral-400 ${
index === servers.length - 1 ? '' : 'border-b border-neutral-700/50'
}`}
>
{server}
</div>
</div>
))}
</div>
);
};
const EditableServerRow = ({
value,
autoFocus,
onChange,
onRemove
}: {
value: string;
autoFocus: boolean;
onChange: (value: string) => void;
onRemove: () => void;
}) => {
const inputRef = useRef<any>(null);
const normalized = normalizeServer(value);
const isInvalid = normalized !== '' && !isValidIP(normalized);
useEffect(() => {
if (autoFocus && inputRef.current) {
inputRef.current.focus();
}
}, [autoFocus]);
return (
<div className="group px-4 py-1.5">
<div className="flex items-center gap-2">
<Input
ref={inputRef}
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="0.0.0.0"
status={isInvalid ? 'error' : undefined}
/>
<Button
size="small"
shape="circle"
icon={<XIcon size={14} />}
onClick={onRemove}
className="opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100"
/>
</div>
</div>
);
};
export const DNS = () => {
const { t } = useTranslation();
const [mode, setMode] = useState<DNSMode>('dhcp');
const [originalMode, setOriginalMode] = useState<DNSMode>('dhcp');
const [servers, setServers] = useState<string[]>([]);
const [originalServers, setOriginalServers] = useState<string[]>([]);
const [dhcp, setDHCP] = useState<string[]>([]);
const [info, setInfo] = useState<DNSInfo>({});
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [message, setMessage] = useState('');
const [error, setError] = useState('');
const [focusNewRow, setFocusNewRow] = useState(false);
useEffect(() => {
getDNS();
}, []);
async function getDNS(showLoading = true) {
if (showLoading) setIsLoading(true);
try {
const rsp = await api.getDNS();
if (rsp.code !== 0) {
setError(rsp.msg);
return;
}
const data = rsp.data as DNSState;
const fetchedMode = data.mode || 'dhcp';
setMode(fetchedMode);
setOriginalMode(fetchedMode);
const fetchedServers = data.servers?.filter(Boolean) || [];
setServers(fetchedServers);
setOriginalServers(fetchedServers);
setDHCP(data.dhcp || []);
setInfo(data.info || {});
} catch (err) {
console.log(err);
} finally {
if (showLoading) setIsLoading(false);
}
}
async function save() {
if (isSaving) return;
setMessage('');
setError('');
const normalized = normalizeServers(servers);
if (mode === 'manual' && normalized.length === 0) {
setError(t('settings.network.dns.invalid'));
return;
}
if (mode === 'manual' && normalized.some((server) => !isValidIP(server))) {
setError(t('settings.network.dns.invalid'));
return;
}
setIsSaving(true);
try {
const rsp = await api.setDNS(mode, mode === 'manual' ? normalized : []);
if (rsp.code !== 0) {
setError(rsp.msg || t('settings.network.dns.saveFailed'));
return;
}
setServers(normalized);
setOriginalServers(normalized);
setOriginalMode(mode);
await getDNS(false);
setMessage(t('settings.network.dns.saved'));
} catch (err) {
console.log(err);
setError(t('settings.network.dns.saveFailed'));
} finally {
setIsSaving(false);
}
}
function addServer() {
if (servers.length >= maxServers) return;
setMessage('');
setError('');
setServers([...servers, '']);
setFocusNewRow(true);
}
function removeServer(index: number) {
setMessage('');
setError('');
setServers(servers.filter((_, i) => i !== index));
}
function updateServer(index: number, value: string) {
setMessage('');
setError('');
const updated = [...servers];
updated[index] = value;
setServers(updated);
}
const normalizedServers = normalizeServers(servers);
const hasInvalidServer =
mode === 'manual' &&
servers.some((server) => {
const val = normalizeServer(server);
return val !== '' && !isValidIP(val);
});
const isExceedMax = mode === 'manual' && normalizedServers.length > maxServers;
const hasChanges =
mode !== originalMode ||
normalizedServers.join(',') !== normalizeServers(originalServers).join(',');
const statusText = error || message || (hasChanges ? t('settings.network.dns.unsaved') : '');
const statusColor = error ? 'text-red-400' : message ? 'text-green-400' : 'text-yellow-400/80';
const serversDescription =
mode === 'dhcp'
? t('settings.network.dns.dhcpServersDescription')
: t('settings.network.dns.manualServersDescription');
const canAdd = !isLoading && !isSaving && servers.length < maxServers;
return (
<div className="flex flex-col space-y-5">
{/* Header row: title + segmented control */}
<div className="flex items-center justify-between">
<div className="flex flex-col space-y-1">
<span>{t('settings.network.dns.title')}</span>
<span className="text-xs text-neutral-500">{t('settings.network.dns.description')}</span>
</div>
<Segmented
disabled={isLoading || isSaving}
value={mode}
onChange={(val) => {
setMode(val as DNSMode);
setMessage('');
setError('');
}}
options={[
{ label: t('settings.network.dns.dhcp'), value: 'dhcp' },
{ label: t('settings.network.dns.manual'), value: 'manual' }
]}
/>
</div>
<div className="space-y-5">
<Panel title={t('settings.network.dns.networkDetails')}>
<InfoRow label={t('settings.network.dns.interface')} value={formatInterface(info)} />
<InfoRow label={t('settings.network.dns.ipAddress')} value={info.address} />
<InfoRow label={t('settings.network.dns.subnetMask')} value={info.subnetMask} />
<InfoRow label={t('settings.network.dns.router')} value={info.gateway} isLast />
</Panel>
<Panel title={t('settings.network.dns.dnsServers')} description={serversDescription}>
{mode === 'manual' ? (
<div>
{servers.length === 0 ? (
<div className="px-4 py-3 text-sm text-neutral-500">
{t('settings.network.dns.none')}
</div>
) : (
servers.map((server, index) => (
<EditableServerRow
key={index}
value={server}
autoFocus={focusNewRow && index === servers.length - 1}
onChange={(val) => updateServer(index, val)}
onRemove={() => removeServer(index)}
/>
))
)}
{/* Add server button */}
{canAdd && (
<div className="px-4 py-1.5 pb-3">
<div className="flex items-center gap-2">
<Button
type="dashed"
className="flex-1"
icon={<PlusIcon size={14} />}
onClick={addServer}
>
{t('settings.network.dns.add')}
</Button>
<Button
type="text"
size="small"
className="invisible shrink-0"
icon={<XIcon size={14} />}
/>
</div>
</div>
)}
{/* Validation hints */}
{(hasInvalidServer || isExceedMax) && (
<div className="space-y-1 px-4 pb-3">
{hasInvalidServer && (
<div className="text-xs text-red-400">{t('settings.network.dns.invalid')}</div>
)}
{isExceedMax && (
<div className="text-xs text-red-400">
{t('settings.network.dns.maxServers', { count: maxServers })}
</div>
)}
</div>
)}
</div>
) : (
<ServerList servers={dhcp} />
)}
</Panel>
</div>
{/* Footer: status + save button */}
{(hasChanges || statusText) && (
<div className="flex items-center justify-between">
<span className={`text-xs ${statusColor}`}>{statusText}</span>
<Button
type={hasChanges ? 'primary' : 'default'}
icon={message ? <CheckIcon size={14} /> : undefined}
loading={isSaving}
disabled={
isLoading || (!hasChanges && !hasInvalidServer) || hasInvalidServer || isExceedMax
}
onClick={save}
>
{t('settings.network.dns.save')}
</Button>
</div>
)}
</div>
);
};

View File

@@ -0,0 +1,26 @@
import { Divider } from 'antd';
import { useTranslation } from 'react-i18next';
import { DNS } from './dns.tsx';
import { Tls } from './tls.tsx';
import { Wifi } from './wifi.tsx';
export const Network = () => {
const { t } = useTranslation();
return (
<>
<div className="text-base">{t('settings.network.title')}</div>
<Divider className="opacity-50" />
<div className="flex flex-col space-y-8">
<Tls />
<Wifi />
</div>
<Divider className="opacity-50" style={{ margin: '32px 0' }} />
<DNS />
</>
);
};

View File

@@ -52,7 +52,7 @@ export const Tls = () => {
<span>HTTPS</span>
<Tooltip
title={t('settings.device.tls.tip')}
title={t('settings.network.tls.tip')}
className="cursor-pointer"
placement="right"
styles={{ root: { maxWidth: '400px' } }}
@@ -60,7 +60,7 @@ export const Tls = () => {
<CircleAlertIcon className="text-neutral-500" size={14} />
</Tooltip>
</div>
<span className="text-xs text-neutral-500">{t('settings.device.tls.description')}</span>
<span className="text-xs text-neutral-500">{t('settings.network.tls.description')}</span>
</div>
<Switch checked={isEnabled} loading={isLoading} onChange={update} />

View File

@@ -56,7 +56,7 @@ export const Wifi = () => {
const rsp = await api.connectWifi(ssid, password);
if (rsp.code !== 0) {
console.log(rsp.msg);
setMessage(t('settings.device.wifi.failed'));
setMessage(t('settings.network.wifi.failed'));
getWiFi();
return;
}
@@ -109,9 +109,9 @@ export const Wifi = () => {
if (isAPMode) {
return (
<div className="flex items-center justify-between">
<div className="flex flex-col">
<span>{t('settings.device.wifi.title')}</span>
<span className="text-xs text-neutral-500">{t('settings.device.wifi.apMode')}</span>
<div className="flex flex-col space-y-1">
<span>{t('settings.network.wifi.title')}</span>
<span className="text-xs text-neutral-500">{t('settings.network.wifi.apMode')}</span>
</div>
<Button shape="round" size="small" disabled>
@@ -127,8 +127,10 @@ export const Wifi = () => {
<>
<div className="flex items-center justify-between">
<div className="flex flex-col space-y-1">
<span>{t('settings.device.wifi.title')}</span>
<span className="text-xs text-neutral-500">{t('settings.device.wifi.description')}</span>
<span>{t('settings.network.wifi.title')}</span>
<span className="text-xs text-neutral-500">
{connectedWiFi ? connectedWiFi : t('settings.network.wifi.description')}
</span>
</div>
<Button
@@ -149,8 +151,8 @@ export const Wifi = () => {
centered={true}
onOk={connect}
onCancel={closeModal}
okText={t('settings.device.wifi.joinBtn')}
cancelText={t('settings.device.wifi.cancelBtn')}
okText={t('settings.network.wifi.joinBtn')}
cancelText={t('settings.network.wifi.cancelBtn')}
confirmLoading={status === 'connecting'}
>
{/* title */}
@@ -161,9 +163,9 @@ export const Wifi = () => {
{!connectedWiFi ? (
<div className="flex flex-col">
<span className="text-lg font-bold">{t('settings.device.wifi.connect')}</span>
<span className="text-lg font-bold">{t('settings.network.wifi.connect')}</span>
<span className="text-xs text-neutral-400">
{t('settings.device.wifi.connectDesc1')}
{t('settings.network.wifi.connectDesc1')}
</span>
</div>
) : (
@@ -188,14 +190,14 @@ export const Wifi = () => {
value={ssid}
style={{ width: '300px' }}
prefix={<WifiOutlined />}
placeholder={t('settings.device.wifi.ssid')}
placeholder={t('settings.network.wifi.ssid')}
onChange={(e) => setSsid(e.target.value)}
/>
<Input.Password
value={password}
style={{ width: '300px' }}
prefix={<LockOutlined />}
placeholder={t('settings.device.wifi.password')}
placeholder={t('settings.network.wifi.password')}
onChange={(e) => setPassword(e.target.value)}
/>