mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
refactor: refactor the Wi-Fi module
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
22
server/proto/tailscale.go
Normal 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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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", "")
|
||||
}
|
||||
|
||||
@@ -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
10
web/pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 远程访问',
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user