diff --git a/kvmapp/system/init.d/S30wifi b/kvmapp/system/init.d/S30wifi index 93bea60..6694eca 100755 --- a/kvmapp/system/init.d/S30wifi +++ b/kvmapp/system/init.d/S30wifi @@ -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 diff --git a/server/proto/auth.go b/server/proto/auth.go index 6c4f28c..dcf92cf 100644 --- a/server/proto/auth.go +++ b/server/proto/auth.go @@ -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"` -} diff --git a/server/proto/network.go b/server/proto/network.go index 7393c22..e6cf9ff 100644 --- a/server/proto/network.go +++ b/server/proto/network.go @@ -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"` } diff --git a/server/proto/tailscale.go b/server/proto/tailscale.go new file mode 100644 index 0000000..21186d9 --- /dev/null +++ b/server/proto/tailscale.go @@ -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"` +} diff --git a/server/router/network.go b/server/router/network.go index cbd96e1..af4e722 100644 --- a/server/router/network.go +++ b/server/router/network.go @@ -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 } diff --git a/server/service/network/wifi.go b/server/service/network/wifi.go index b2b2eb1..f3eb973 100644 --- a/server/service/network/wifi.go +++ b/server/service/network/wifi.go @@ -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", "") } diff --git a/web/package.json b/web/package.json index ca97c7e..1d9f1b1 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 56aa94e..4bb2adc 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -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 diff --git a/web/src/api/network.ts b/web/src/api/network.ts index f91048c..52bac3e 100644 --- a/web/src/api/network.ts +++ b/web/src/api/network.ts @@ -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'); +} diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 6665886..7edf8cf 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -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: { diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index 076360c..420bef4 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -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 远程访问', diff --git a/web/src/pages/desktop/menu/settings/device/wifi.tsx b/web/src/pages/desktop/menu/settings/device/wifi.tsx index 5503f9c..876f0d9 100644 --- a/web/src/pages/desktop/menu/settings/device/wifi.tsx +++ b/web/src/pages/desktop/menu/settings/device/wifi.tsx @@ -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 ( +