refactor: refactor the Wi-Fi module

This commit is contained in:
wj-xiao
2025-12-24 16:48:24 +08:00
parent 79cc1cc2bf
commit aaad552633
13 changed files with 511 additions and 187 deletions

View File

@@ -2,24 +2,26 @@
. /etc/profile
APFLAG_FILE=/tmp/wifiap
gen_hostapd_conf() {
ssid="${1}"
pass="${2}"
echo "ctrl_interface=/var/run/hostapd"
echo "ctrl_interface_group=0"
echo "ssid=${ssid}"
echo "hw_mode=g"
echo "channel=1"
echo "beacon_int=100"
echo "dtim_period=2"
echo "max_num_sta=255"
echo "rts_threshold=-1"
echo "fragm_threshold=-1"
echo "macaddr_acl=0"
echo "auth_algs=3"
echo "wpa=2"
echo "wpa_passphrase=${pass}"
echo "ieee80211n=1"
ssid="${1}"
pass="${2}"
echo "ctrl_interface=/var/run/hostapd"
echo "ctrl_interface_group=0"
echo "ssid=${ssid}"
echo "hw_mode=g"
echo "channel=1"
echo "beacon_int=100"
echo "dtim_period=2"
echo "max_num_sta=255"
echo "rts_threshold=-1"
echo "fragm_threshold=-1"
echo "macaddr_acl=0"
echo "auth_algs=3"
echo "wpa=2"
echo "wpa_passphrase=${pass}"
echo "ieee80211n=1"
}
gen_udhcpd_conf() {
@@ -61,19 +63,16 @@ start() {
chown root:root /etc/kvm/wifi.ssid /etc/kvm/wifi.pass
chmod 644 /etc/kvm/wifi.ssid /etc/kvm/wifi.pass
fi
if [ -e /etc/kvm/wifi.ssid ]
then
ssid=`cat /etc/kvm/wifi.ssid`
if [ -e /etc/kvm/wifi.ssid ]; then
ssid=$(cat /etc/kvm/wifi.ssid)
fi
if [ -e /etc/kvm/wifi.pass ]
then
pass=`cat /etc/kvm/wifi.pass`
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
echo "ctrl_interface=/var/run/wpa_supplicant" >/etc/wpa_supplicant.conf
wpa_passphrase "$ssid" "$pass" >>/etc/wpa_supplicant.conf
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
if [ ! -e /boot/wifi.nodhcp ]
then
if [ ! -e /boot/wifi.nodhcp ]; then
(udhcpc -i wlan0 -t 10 -T 1 -A 5 -b -p /run/udhcpc.wlan0.pid) &
fi
}
@@ -82,33 +81,27 @@ ap_start() {
echo "wifi mode: ap"
id2=$(printf "%d" 0x$(sha512sum /sys/class/cvi-base/base_uid | head -c 2))
id3=$(printf "%d" 0x$(sha512sum /sys/class/cvi-base/base_uid | head -c 4 | tail -c 2))
if [ "$id2" = "$id3" ]
then
if [ "$id2" = "$id3" ]; then
id2=$((id2 + 1))
fi
if [ "$id2" -ge 255 ]
then
if [ "$id2" -ge 255 ]; then
id2=253
fi
if [ "$id3" -ge 255 ]
then
if [ "$id3" -ge 255 ]; then
id3=254
fi
ssid=""
pass=""
if [ -e /kvmapp/kvm/ap.ssid ]
then
ssid=`cat /kvmapp/kvm/ap.ssid`
if [ -e /kvmapp/kvm/ap.ssid ]; then
ssid=$(cat /kvmapp/kvm/ap.ssid)
fi
if [ -e /kvmapp/kvm/ap.pass ]
then
pass=`cat /kvmapp/kvm/ap.pass`
if [ -e /kvmapp/kvm/ap.pass ]; then
pass=$(cat /kvmapp/kvm/ap.pass)
fi
gen_hostapd_conf "$ssid" "$pass" > /etc/hostapd.conf
gen_hostapd_conf "$ssid" "$pass" >/etc/hostapd.conf
ipv4_prefix=10.$id3.$id2
if [ ! -e /etc/udhcpd.wlan0.conf ]
then
gen_udhcpd_conf wlan0 "${ipv4_prefix}" > /etc/udhcpd.wlan0.conf
if [ ! -e /etc/udhcpd.wlan0.conf ]; then
gen_udhcpd_conf wlan0 "${ipv4_prefix}" >/etc/udhcpd.wlan0.conf
fi
# gen_dnsmasq_conf "${ipv4_prefix}" > /etc/dnsmasq.conf
@@ -126,50 +119,52 @@ ap_start() {
# dnsmasq --pid-file=/tmp/dnsmasq.run -C /etc/dnsmasq.conf
hostapd -B -i wlan0 /etc/hostapd.conf
udhcpd -S /etc/udhcpd.wlan0.conf
if [ ! -f "$APFLAG_FILE" ]; then
touch "$APFLAG_FILE"
fi
}
stop() {
ps -ef|grep hostapd|grep -v grep|awk '{print $1}'|xargs kill -2 || true
ps -ef|grep "udhcpd -S /etc/udhcpd.wlan0.conf" |grep -v grep|awk '{print $1}'|xargs kill -2 || true
killall wpa_supplicant || true
if [ -e /run/udhcpc.wlan0.pid ]
then
kill `cat /run/udhcpc.wlan0.pid` || true
rm -f /run/udhcpc.wlan0.pid
fi
if [ -e /var/run/udhcpd.wlan0.pid ]
then
kill `cat /var/run/udhcpd.wlan0.pid` || true
rm -f /var/run/udhcpd.wlan0.pid
fi
if [ -e /tmp/dnsmasq.run ]
then
kill `cat /tmp/dnsmasq.run` || true
rm -f /tmp/dnsmasq.run
fi
# if [ -e /etc/wpa_supplicant.conf]
# then
# echo > /etc/wpa_supplicant.conf
# fi
airmon-ng stop wlan0mon || true
ps -ef | grep hostapd | grep -v grep | awk '{print $1}' | xargs kill -2 || true
ps -ef | grep "udhcpd -S /etc/udhcpd.wlan0.conf" | grep -v grep | awk '{print $1}' | xargs kill -2 || true
killall wpa_supplicant || true
if [ -e /run/udhcpc.wlan0.pid ]; then
kill $(cat /run/udhcpc.wlan0.pid) || true
rm -f /run/udhcpc.wlan0.pid
fi
if [ -e /var/run/udhcpd.wlan0.pid ]; then
kill $(cat /var/run/udhcpd.wlan0.pid) || true
rm -f /var/run/udhcpd.wlan0.pid
fi
if [ -e /tmp/dnsmasq.run ]; then
kill $(cat /tmp/dnsmasq.run) || true
rm -f /tmp/dnsmasq.run
fi
# if [ -e /etc/wpa_supplicant.conf]
# then
# echo > /etc/wpa_supplicant.conf
# fi
airmon-ng stop wlan0mon || true
if [ -f "$APFLAG_FILE" ]; then
rm "$APFLAG_FILE"
fi
}
restart() {
stop
start
stop
start
}
if [ "${1}" = "start" ]
then
start
elif [ "${1}" = "stop" ]
then
stop
elif [ "${1}" = "ap" ]
then
if [ "${1}" = "start" ]; then
start
elif [ "${1}" = "stop" ]; then
stop
ap_start
elif [ "${1}" = "restart" ]
then
restart
elif [ "${1}" = "ap" ]; then
stop
ap_start
elif [ "${1}" = "restart" ]; then
restart
fi

View File

@@ -21,8 +21,3 @@ type ChangePasswordReq struct {
type IsPasswordUpdatedRsp struct {
IsUpdated bool `json:"isUpdated"`
}
type ConnectWifiReq struct {
Ssid string `validate:"required"`
Password string `valid:"required"`
}

View File

@@ -17,28 +17,14 @@ type SetMacNameReq struct {
Name string `form:"name" validate:"required"`
}
type TailscaleState string
const (
TailscaleNotInstall TailscaleState = "notInstall"
TailscaleNotRunning TailscaleState = "notRunning"
TailscaleNotLogin TailscaleState = "notLogin"
TailscaleStopped TailscaleState = "stopped"
TailscaleRunning TailscaleState = "running"
)
type GetTailscaleStatusRsp struct {
State TailscaleState `json:"state"`
Name string `json:"name"`
IP string `json:"ip"`
Account string `json:"account"`
}
type LoginTailscaleRsp struct {
Url string `json:"url"`
}
type GetWifiRsp struct {
Supported bool `json:"supported"`
Connected bool `json:"connected"`
Supported bool `json:"supported"`
ApMode bool `json:"apMode"`
Connected bool `json:"connected"`
Ssid string `json:"ssid"`
}
type ConnectWifiReq struct {
Ssid string `validate:"required"`
Password string `valid:"required"`
}

22
server/proto/tailscale.go Normal file
View File

@@ -0,0 +1,22 @@
package proto
type TailscaleState string
const (
TailscaleNotInstall TailscaleState = "notInstall"
TailscaleNotRunning TailscaleState = "notRunning"
TailscaleNotLogin TailscaleState = "notLogin"
TailscaleStopped TailscaleState = "stopped"
TailscaleRunning TailscaleState = "running"
)
type GetTailscaleStatusRsp struct {
State TailscaleState `json:"state"`
Name string `json:"name"`
IP string `json:"ip"`
Account string `json:"account"`
}
type LoginTailscaleRsp struct {
Url string `json:"url"`
}

View File

@@ -10,7 +10,7 @@ import (
func networkRouter(r *gin.Engine) {
service := network.NewService()
r.POST("/api/network/wifi", service.ConnectWifi) // connect Wi-Fi
r.POST("/api/network/wifi", service.ConnectWifiNoAuth) // connect Wi-Fi without auth (only available in ap mode)
api := r.Group("/api").Use(middleware.CheckToken())
@@ -18,5 +18,8 @@ func networkRouter(r *gin.Engine) {
api.GET("/network/wol/mac", service.GetMac) // get mac list
api.DELETE("/network/wol/mac", service.DeleteMac) // delete mac
api.POST("/network/wol/mac/name", service.SetMacName) // set mac name
api.GET("/network/wifi", service.GetWifi) // get Wi-Fi information
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
}

View File

@@ -1,20 +1,26 @@
package network
import (
"NanoKVM-Server/proto"
"fmt"
"os"
"os/exec"
"strings"
"time"
"NanoKVM-Server/proto"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
const (
WiFiExistFile = "/etc/kvm/wifi_exist"
WiFiSSID = "/etc/kvm/wifi.ssid"
WiFiPasswd = "/etc/kvm/wifi.pass"
WiFiConnect = "/kvmapp/kvm/wifi_try_connect"
WiFiStateFile = "/kvmapp/kvm/wifi_state"
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"
)
func (s *Service) GetWifi(c *gin.Context) {
@@ -22,25 +28,55 @@ func (s *Service) GetWifi(c *gin.Context) {
data := &proto.GetWifiRsp{}
_, err := os.Stat(WiFiExistFile)
if err != nil {
data.Supported = isSupported()
if !data.Supported {
rsp.OkRspWithData(c, data)
return
}
data.Supported = true
data.ApMode = isAPMode()
content, err := os.ReadFile(WiFiStateFile)
if err != nil {
data.Connected = isConnected()
if !data.Connected {
rsp.OkRspWithData(c, data)
return
}
state := strings.ReplaceAll(string(content), "\n", "")
data.Connected = state == "1"
data.Ssid = getWiFiSsid()
if data.Ssid == "" {
data.Ssid = "Wi-Fi"
}
rsp.OkRspWithData(c, data)
log.Debugf("get wifi state: %s", state)
log.Debugf("get wifi state: %+v", data)
}
func (s *Service) ConnectWifiNoAuth(c *gin.Context) {
var req proto.ConnectWifiReq
var rsp proto.Response
// Check Wi-Fi configuration mode
if !isSupported() || !isAPMode() {
time.Sleep(2 * time.Second)
rsp.ErrRsp(c, -1, "invalid mode")
return
}
if err := proto.ParseFormRequest(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 {
rsp.ErrRsp(c, -3, "failed to connect wifi")
return
}
time.Sleep(5 * time.Second)
rsp.OkRsp(c)
log.Debugf("set wifi ap mode successfully")
}
func (s *Service) ConnectWifi(c *gin.Context) {
@@ -52,24 +88,97 @@ func (s *Service) ConnectWifi(c *gin.Context) {
return
}
if err := os.WriteFile(WiFiSSID, []byte(req.Ssid), 0o644); err != nil {
log.Errorf("failed to save wifi ssid: %s", err)
rsp.ErrRsp(c, -2, "failed to save wifi")
if err := connect(req.Ssid, req.Password); err != nil {
rsp.ErrRsp(c, -2, "failed to connect wifi")
return
}
if err := os.WriteFile(WiFiPasswd, []byte(req.Password), 0o644); err != nil {
log.Errorf("failed to save wifi password: %s", err)
rsp.ErrRsp(c, -3, "failed to save wifi")
// Waiting for Wi-Fi connection
maxTries := 7
if isConnected() {
maxTries = 3
time.Sleep(12 * time.Second)
}
for range maxTries {
time.Sleep(2 * time.Second)
if isConnected() && getWiFiSsid() != "" {
rsp.OkRsp(c)
log.Debugf("set wifi successfully: %s", req.Ssid)
return
}
}
rsp.ErrRsp(c, -3, "failed to get wifi status")
return
}
func (s *Service) DisconnectWifi(c *gin.Context) {
var rsp proto.Response
command := fmt.Sprintf("%s stop", WiFiScript)
cmd := exec.Command("sh", "-c", command)
output, err := cmd.CombinedOutput()
if err != nil {
log.Errorf("failed to disconnect wifi: %s", output)
rsp.ErrRsp(c, -1, "failed to stop wifi")
return
}
time.Sleep(5 * time.Second)
_ = os.Remove(WiFiSSID)
_ = os.Remove(WiFiPasswd)
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 {
log.Errorf("failed to save wifi ssid: %s", err)
return err
}
if err := os.WriteFile(WiFiPasswd, []byte(password), 0o644); err != nil {
log.Errorf("failed to save wifi password: %s", err)
return err
}
if err := os.WriteFile(WiFiConnect, nil, 0o644); err != nil {
log.Errorf("failed to connect wifi: %s", err)
rsp.ErrRsp(c, -4, "failed to connect wifi")
return
return err
}
rsp.OkRsp(c)
log.Debugf("set wifi successfully: %s", req.Ssid)
return nil
}
func isSupported() bool {
_, err := os.Stat(WiFiExistFile)
return err == nil
}
func isAPMode() bool {
_, err := os.Stat(WiFiApModeFile)
return err == nil
}
func isConnected() bool {
content, err := os.ReadFile(WiFiStateFile)
if err != nil {
return false
}
state := strings.ReplaceAll(string(content), "\n", "")
return state == "1"
}
func getWiFiSsid() string {
ssidByte, err := os.ReadFile(WiFiSSID)
if err != nil {
return ""
}
return strings.ReplaceAll(string(ssidByte), "\n", "")
}

View File

@@ -22,7 +22,7 @@
"i18next": "^23.16.4",
"jotai": "^2.10.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.469.0",
"lucide-react": "^0.562.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-draggable": "^4.4.6",

10
web/pnpm-lock.yaml generated
View File

@@ -42,8 +42,8 @@ importers:
specifier: ^3.0.5
version: 3.0.5
lucide-react:
specifier: ^0.469.0
version: 0.469.0(react@18.3.1)
specifier: ^0.562.0
version: 0.562.0(react@18.3.1)
react:
specifier: ^18.3.1
version: 18.3.1
@@ -1982,8 +1982,8 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react@0.469.0:
resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==}
lucide-react@0.562.0:
resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -5026,7 +5026,7 @@ snapshots:
dependencies:
yallist: 3.1.1
lucide-react@0.469.0(react@18.3.1):
lucide-react@0.562.0(react@18.3.1):
dependencies:
react: 18.3.1

View File

@@ -31,11 +31,25 @@ export function getWiFi() {
return http.get('/api/network/wifi');
}
// connect wifi
export function connectWifi(ssid: string, password: string) {
// connect wifi without auth (only available in wifi configuration mode)
export function connectWifiNoAuth(ssid: string, password: string) {
const data = {
ssid,
password
};
return http.post('/api/network/wifi', data);
}
// connect wifi
export function connectWifi(ssid: string, password: string) {
const data = {
ssid,
password
};
return http.post('/api/network/wifi/connect', data);
}
// disconnect wifi
export function disconnectWifi() {
return http.post('/api/network/wifi/disconnect');
}

View File

@@ -41,8 +41,10 @@ const en = {
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi for NanoKVM',
success: 'Please check the network status of NanoKVM and visit the new IP address.',
success: 'Please go to the device to check the network status of NanoKVM.',
failed: 'Operation failed, please try again.',
invalidMode:
'The current mode does not support network setup. Please go to your device and enable Wi-Fi configuration mode.',
confirmBtn: 'Ok',
finishBtn: 'Finished'
},
@@ -186,9 +188,9 @@ const en = {
input: 'Please enter a remote image URL',
ok: 'Ok',
disabled: '/data partition is RO, so we cannot download the image',
uploadbox: "Drop file here or click to select",
inputfile: "Please enter the image File",
NoISO: "No ISO"
uploadbox: 'Drop file here or click to select',
inputfile: 'Please enter the image File',
NoISO: 'No ISO'
},
power: {
title: 'Power',
@@ -251,7 +253,17 @@ const en = {
wifi: {
title: 'Wi-Fi',
description: 'Configure Wi-Fi',
setBtn: 'Config'
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',
@@ -296,7 +308,7 @@ const en = {
hidOnly: 'HID-Only Mode',
disk: 'Virtual Disk',
diskDesc: 'Mount SD card on the remote host',
media: "Virtual Image",
media: 'Virtual Image',
mediaDesc: 'Attach virtual image device to the remote host',
network: 'Virtual Network',
networkDesc: 'Mount virtual network card on the remote host',
@@ -371,9 +383,9 @@ const en = {
updating: 'Update started. Please wait...',
confirm: 'Confirm',
cancel: 'Cancel',
inputfile: "Please enter the Update File",
noupdatefile: "No update file",
uploadbox: "Drop file here or click to select",
inputfile: 'Please enter the Update File',
noupdatefile: 'No update file',
uploadbox: 'Drop file here or click to select',
ok: 'Ok'
},
account: {

View File

@@ -40,8 +40,9 @@ const zh = {
wifi: {
title: 'Wi-Fi',
description: '配置 NanoKVM Wi-Fi 信息',
success: '请检查 NanoKVM 的网络状态,并访问新的 IP 地址。',
success: '请前往设备检查 NanoKVM 的网络状态。',
failed: '操作失败,请重试。',
invalidMode: '当前模式不支持配置网络。请先前往设备启用 Wi-Fi 配置模式。',
confirmBtn: '确定',
finishBtn: '完成'
},
@@ -222,7 +223,17 @@ const zh = {
wifi: {
title: 'Wi-Fi',
description: '配置 Wi-Fi 信息',
setBtn: '设置'
apMode: 'AP 模式已启用,该模式下仅支持扫描二维码配网',
connect: '连接 Wi-Fi',
connectDesc1: '请输入网络名称和密码',
connectDesc2: '请输入密码以连接此网络',
disconnect: '是否要断开该网络连接?',
failed: '连接失败,请重试',
ssid: '名称',
password: '密码',
joinBtn: '加入',
confirmBtn: '确定',
cancelBtn: '取消'
},
ssh: {
description: '启用 SSH 远程访问',

View File

@@ -1,5 +1,7 @@
import { useEffect, useState } from 'react';
import { Button } from 'antd';
import { LockOutlined, WifiOutlined } from '@ant-design/icons';
import { Button, Input, Modal, Switch } from 'antd';
import { WifiIcon, WifiPenIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import * as api from '@/api/network.ts';
@@ -8,38 +10,198 @@ export const Wifi = () => {
const { t } = useTranslation();
const [isSupported, setIsSupported] = useState(false);
const [isAPMode, setIsAPMode] = useState(false);
const [connectedWiFi, setConnectedWifi] = useState('');
const [isModalOpen, setIsModalOpen] = useState(false);
const [ssid, setSsid] = useState('');
const [password, setPassword] = useState('');
const [status, setStatus] = useState<'' | 'connecting' | 'disconnecting'>('');
const [message, setMessage] = useState('');
useEffect(() => {
api.getWiFi().then((rsp) => {
getWiFi();
}, []);
async function getWiFi() {
try {
const rsp = await api.getWiFi();
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
setIsSupported(rsp.data?.supported);
});
}, []);
setIsSupported(!!rsp.data?.supported);
setIsAPMode(!!rsp.data?.apMode);
function open() {
window.open('/#/wifi', '_blank');
if (rsp.data?.connected && rsp.data?.ssid) {
setConnectedWifi(rsp.data.ssid);
} else {
setConnectedWifi('');
}
} catch {
/* empty */
}
}
async function connect() {
setMessage('');
if (!ssid || !password) return;
if (status !== '') return;
setStatus('connecting');
try {
const rsp = await api.connectWifi(ssid, password);
if (rsp.code !== 0) {
console.log(rsp.msg);
setMessage(t('settings.device.wifi.failed'));
getWiFi();
return;
}
setConnectedWifi(ssid);
setIsModalOpen(false);
} catch (err) {
console.log(err);
} finally {
setStatus('');
}
}
async function disconnect(enable: boolean) {
if (enable || status !== '') return;
setStatus('disconnecting');
try {
const rsp = await api.disconnectWifi();
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
setConnectedWifi('');
} catch (err) {
console.log(err);
} finally {
setStatus('');
}
}
function openModal() {
setSsid('');
setPassword('');
setMessage('');
setIsModalOpen(true);
}
function closeModal() {
if (status !== '') return;
setIsModalOpen(false);
}
if (!isSupported) {
return <></>;
}
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>
<Button shape="round" size="small" disabled>
<div className="flex items-center justify-center px-1.5">
<WifiIcon size={16} />
</div>
</Button>
</div>
);
}
return (
<>
{isSupported && (
<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.description')}
</span>
<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.description')}</span>
</div>
<Button
type={connectedWiFi ? 'primary' : 'default'}
shape="round"
size="small"
onClick={openModal}
>
<div className="flex items-center justify-center px-1.5">
<WifiIcon size={16} />
</div>
</Button>
</div>
<Modal
closable={false}
open={isModalOpen}
centered={true}
onOk={connect}
onCancel={closeModal}
okText={t('settings.device.wifi.joinBtn')}
cancelText={t('settings.device.wifi.cancelBtn')}
confirmLoading={status === 'connecting'}
>
{/* title */}
<div className="flex items-center space-x-5">
<div className="h-[64px] w-[64px]">
<WifiPenIcon size={64} className="text-blue-400" />
</div>
<Button type="primary" size="small" onClick={open}>
{t('settings.device.wifi.setBtn')}
</Button>
{!connectedWiFi ? (
<div className="flex flex-col">
<span className="text-lg font-bold">{t('settings.device.wifi.connect')}</span>
<span className="text-xs text-neutral-400">
{t('settings.device.wifi.connectDesc1')}
</span>
</div>
) : (
<div className="flex justify-center">
<div className="flex w-[300px] justify-between rounded-lg bg-neutral-800">
<div className="flex w-full justify-between p-3">
<span>{connectedWiFi}</span>
<Switch
value={!!connectedWiFi}
loading={status === 'disconnecting'}
onChange={disconnect}
/>
</div>
</div>
</div>
)}
</div>
)}
{/* form */}
<div className="flex flex-col items-center space-y-3 py-6">
<Input
value={ssid}
style={{ width: '300px' }}
prefix={<WifiOutlined />}
placeholder={t('settings.device.wifi.ssid')}
onChange={(e) => setSsid(e.target.value)}
/>
<Input.Password
value={password}
style={{ width: '300px' }}
prefix={<LockOutlined />}
placeholder={t('settings.device.wifi.password')}
onChange={(e) => setPassword(e.target.value)}
/>
{!!message && <span className="text-sm text-red-500">{message}</span>}
</div>
</Modal>
</>
);
};

View File

@@ -3,32 +3,42 @@ import { CheckOutlined, LockOutlined, WifiOutlined } from '@ant-design/icons';
import { Button, Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import { connectWifi } from '@/api/network.ts';
import * as api from '@/api/network.ts';
import { Head } from '@/components/head.tsx';
type State = '' | 'loading' | 'success' | 'failed';
type State = '' | 'loading' | 'success' | 'failed' | 'denied';
export const Wifi = () => {
const { t } = useTranslation();
const [state, setState] = useState<State>('');
function connect(values: any) {
if (state === 'loading' || state === 'success') return;
const ssid = values.ssid;
const password = values.password;
if (!ssid || !password) return;
async function connect(values: any) {
if (!values.ssid || !values.password) return;
if (state === 'loading') return;
setState('loading');
connectWifi(ssid, password)
.then((rsp) => {
setState(rsp.code === 0 ? 'success' : 'failed');
})
.catch(() => {
setState('failed');
});
try {
const rsp = await api.connectWifiNoAuth(values.ssid, values.password);
switch (rsp?.code) {
case 0:
setState('success');
return;
case -1:
setState('denied');
return;
case -2:
case -3:
setState('failed');
return;
}
} catch (err) {
console.log(err);
}
setState('success');
}
return (
@@ -74,12 +84,17 @@ export const Wifi = () => {
</Form.Item>
</Form>
<div className="flex h-6 justify-center px-10">
<div className="flex max-w-[500px] justify-center px-5 pt-3 md:px-10">
{state === 'success' && (
<span className="text-sm text-green-500">{t('wifi.success')}</span>
)}
{state === 'failed' && <span className="text-sm text-red-500">{t('wifi.failed')} </span>}
{state === 'denied' && (
<div className="flex flex-col items-center space-y-5">
<span className="text-sm text-red-500">{t('wifi.invalidMode')} </span>
</div>
)}
</div>
</div>
</>