mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
Merge branch 'main' of https://github.com/sipeed/NanoKVM
This commit is contained in:
@@ -21,7 +21,7 @@ var defaultConfig = &Config{
|
||||
},
|
||||
Stun: "stun.l.google.com:19302",
|
||||
Turn: Turn{
|
||||
TurnAddr: "turn.cloudflare.com:3478",
|
||||
TurnAddr: "",
|
||||
TurnUser: "",
|
||||
TurnCred: "",
|
||||
},
|
||||
|
||||
@@ -83,3 +83,7 @@ type GetOLEDRsp struct {
|
||||
type GetSSHStateRsp struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type GetMdnsStateRsp struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
@@ -39,4 +39,8 @@ func vmRouter(r *gin.Engine) {
|
||||
api.GET("/vm/ssh", service.GetSSHState) // get SSH state
|
||||
api.POST("/vm/ssh/enable", service.EnableSSH) // enable SSH
|
||||
api.POST("/vm/ssh/disable", service.DisableSSH) // disable SSH
|
||||
|
||||
api.GET("/vm/mdns", service.GetMdnsState) // get mDNS state
|
||||
api.POST("/vm/mdns/enable", service.EnableMdns) // enable mDNS
|
||||
api.POST("/vm/mdns/disable", service.DisableMdns) // disable mDNS
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ const (
|
||||
TailscalePath = "/usr/bin/tailscale"
|
||||
TailscaledPath = "/usr/sbin/tailscaled"
|
||||
ConfigPath = "etc/sysctl.d/99-tailscale.conf"
|
||||
|
||||
GoMemLimit int64 = 75
|
||||
)
|
||||
|
||||
var StateMap = map[string]proto.TailscaleState{
|
||||
@@ -69,7 +71,7 @@ func (s *Service) Start(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !utils.IsGoMemLimitExist() {
|
||||
_ = utils.SetGoMemLimit(50)
|
||||
_ = utils.SetGoMemLimit(GoMemLimit)
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
@@ -165,7 +167,7 @@ func (s *Service) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !utils.IsGoMemLimitExist() {
|
||||
_ = utils.SetGoMemLimit(50)
|
||||
_ = utils.SetGoMemLimit(GoMemLimit)
|
||||
}
|
||||
|
||||
rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{
|
||||
|
||||
@@ -33,7 +33,7 @@ func (s *Service) WakeOnLAN(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
command := fmt.Sprintf("ether-wake %s", mac)
|
||||
command := fmt.Sprintf("ether-wake -b %s", mac)
|
||||
cmd := exec.Command("sh", "-c", command)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
@@ -39,7 +39,7 @@ func Connect(c *gin.Context) {
|
||||
|
||||
var iceServers []webrtc.ICEServer
|
||||
|
||||
if conf.Stun != "" {
|
||||
if conf.Stun != "" && conf.Stun != "disable" {
|
||||
iceServers = append(iceServers, webrtc.ICEServer{
|
||||
URLs: []string{"stun:" + conf.Stun},
|
||||
})
|
||||
@@ -53,11 +53,9 @@ func Connect(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
rtc_config := webrtc.Configuration{
|
||||
peerConn, err := webrtc.NewPeerConnection(webrtc.Configuration{
|
||||
ICEServers: iceServers,
|
||||
}
|
||||
|
||||
peerConn, err := webrtc.NewPeerConnection(rtc_config)
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to create PeerConnection: %s", err)
|
||||
return
|
||||
|
||||
@@ -18,7 +18,7 @@ var imageVersionMap = map[string]string{
|
||||
"2024-07-23-20-18-587710.img": "v1.1.0",
|
||||
"2024-08-08-19-44-bef2ca.img": "v1.2.0",
|
||||
"2024-11-13-09-59-9c961a.img": "v1.3.0",
|
||||
"2025-02-17-16-59-3649fe.img": "v1.4.0",
|
||||
"2025-02-17-19-08-3649fe.img": "v1.4.0",
|
||||
}
|
||||
|
||||
func (s *Service) GetInfo(c *gin.Context) {
|
||||
@@ -53,6 +53,10 @@ func getIp() string {
|
||||
}
|
||||
|
||||
func getMdns() string {
|
||||
if pid := getAvahiDaemonPid(); pid == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
content, err := os.ReadFile("/etc/hostname")
|
||||
if err != nil {
|
||||
return ""
|
||||
|
||||
91
server/service/vm/mdns.go
Normal file
91
server/service/vm/mdns.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
AvahiDaemonPid = "/run/avahi-daemon/pid"
|
||||
AvahiDaemonScript = "/etc/init.d/S50avahi-daemon"
|
||||
AvahiDaemonBackupScript = "/kvmapp/system/init.d/S50avahi-daemon"
|
||||
)
|
||||
|
||||
func (s *Service) GetMdnsState(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
pid := getAvahiDaemonPid()
|
||||
|
||||
rsp.OkRspWithData(c, &proto.GetMdnsStateRsp{
|
||||
Enabled: pid != "",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) EnableMdns(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
pid := getAvahiDaemonPid()
|
||||
if pid != "" {
|
||||
rsp.OkRsp(c)
|
||||
return
|
||||
}
|
||||
|
||||
commands := []string{
|
||||
fmt.Sprintf("cp -f %s %s", AvahiDaemonBackupScript, AvahiDaemonScript),
|
||||
fmt.Sprintf("%s start", AvahiDaemonScript),
|
||||
}
|
||||
|
||||
command := strings.Join(commands, " && ")
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
if err != nil {
|
||||
log.Errorf("failed to start avahi-daemon: %s", err)
|
||||
rsp.ErrRsp(c, -1, "failed to enable mdns")
|
||||
return
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("avahi-daemon started")
|
||||
}
|
||||
|
||||
func (s *Service) DisableMdns(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
pid := getAvahiDaemonPid()
|
||||
if pid == "" {
|
||||
rsp.OkRsp(c)
|
||||
return
|
||||
}
|
||||
|
||||
command := fmt.Sprintf("kill -9 %s", pid)
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
if err != nil {
|
||||
log.Errorf("failed to stop avahi-daemon: %s", err)
|
||||
rsp.ErrRsp(c, -1, "failed to disable mdns")
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.Remove(AvahiDaemonPid)
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("avahi-daemon stopped")
|
||||
}
|
||||
|
||||
func getAvahiDaemonPid() string {
|
||||
if _, err := os.Stat(AvahiDaemonPid); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(AvahiDaemonPid)
|
||||
if err != nil {
|
||||
log.Errorf("failed to read mdns pid: %s", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(string(content), "\n", "")
|
||||
}
|
||||
@@ -52,7 +52,7 @@ int get_ip_addr(ip_addr_t ip_type)
|
||||
switch (ip_type){
|
||||
case ETH_IP: // eth_addr
|
||||
if(strcmp(ip_address()["eth0"].c_str(), (char*)kvm_sys_state.eth_addr) != 0){
|
||||
if(*(ip_address()["eth0"].c_str()) == NULL){
|
||||
if(*(ip_address()["eth0"].c_str()) == 0){
|
||||
printf("can`t get ip addr\r\n");
|
||||
kvm_sys_state.eth_addr[0] = 0;
|
||||
return 0;
|
||||
@@ -67,7 +67,7 @@ int get_ip_addr(ip_addr_t ip_type)
|
||||
return 1;
|
||||
case WiFi_IP: // wifi_addr
|
||||
if(strcmp(ip_address()["wlan0"].c_str(), (char*)kvm_sys_state.wifi_addr) != 0){
|
||||
if(*(ip_address()["wlan0"].c_str()) == NULL){
|
||||
if(*(ip_address()["wlan0"].c_str()) == 0){
|
||||
printf("can`t get ip addr\r\n");
|
||||
kvm_sys_state.wifi_addr[0] = 0;
|
||||
return 0;
|
||||
@@ -81,7 +81,7 @@ int get_ip_addr(ip_addr_t ip_type)
|
||||
}
|
||||
return 1;
|
||||
case Tailscale_IP: // tail_addr
|
||||
if(*(ip_address()["tailscale0"].c_str()) == NULL){
|
||||
if(*(ip_address()["tailscale0"].c_str()) == 0){
|
||||
printf("can`t get ip addr\r\n");
|
||||
kvm_sys_state.tail_addr[0] = 0;
|
||||
return 0;
|
||||
@@ -94,7 +94,7 @@ int get_ip_addr(ip_addr_t ip_type)
|
||||
printf("\r\n");
|
||||
return 1;
|
||||
case RNDIS_IP: // rndis_addr
|
||||
if(*(ip_address()["usb0"].c_str()) == NULL){
|
||||
if(*(ip_address()["usb0"].c_str()) == 0){
|
||||
printf("can`t get ip addr\r\n");
|
||||
kvm_sys_state.rndis_addr[0] = 0;
|
||||
return 0;
|
||||
@@ -466,6 +466,8 @@ void kvm_update_tailscale_state(void)
|
||||
uint8_t ion_free_space(void)
|
||||
{
|
||||
//cat /sys/kernel/debug/ion/cvi_carveout_heap_dump/summary | grep "usage rate:" | awk '{print $2}'
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t watchdog_sf_is_open()
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"lucide-react": "^0.469.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-draggable": "^4.4.0",
|
||||
"react-error-boundary": "^4.1.2",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"react-i18next": "^14.1.3",
|
||||
@@ -58,7 +59,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.14",
|
||||
"vite": "^6.2.1",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
},
|
||||
"msw": {
|
||||
@@ -66,4 +67,4 @@
|
||||
"public"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
493
web/pnpm-lock.yaml
generated
493
web/pnpm-lock.yaml
generated
@@ -50,6 +50,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
react-draggable:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-error-boundary:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2(react@18.3.1)
|
||||
@@ -107,7 +110,7 @@ importers:
|
||||
version: 8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(vite@5.4.14(@types/node@22.9.0))
|
||||
version: 4.3.3(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0))
|
||||
autoprefixer:
|
||||
specifier: ^10.4.20
|
||||
version: 10.4.20(postcss@8.4.47)
|
||||
@@ -145,11 +148,11 @@ importers:
|
||||
specifier: ^5.6.3
|
||||
version: 5.6.3
|
||||
vite:
|
||||
specifier: ^5.4.14
|
||||
version: 5.4.14(@types/node@22.9.0)
|
||||
specifier: ^6.2.1
|
||||
version: 6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0)
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^4.3.2
|
||||
version: 4.3.2(typescript@5.6.3)(vite@5.4.14(@types/node@22.9.0))
|
||||
version: 4.3.2(typescript@5.6.3)(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -297,141 +300,153 @@ packages:
|
||||
'@emotion/unitless@0.7.5':
|
||||
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/aix-ppc64@0.25.0':
|
||||
resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/android-arm64@0.25.0':
|
||||
resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/android-arm@0.25.0':
|
||||
resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/android-x64@0.25.0':
|
||||
resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/darwin-arm64@0.25.0':
|
||||
resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/darwin-x64@0.25.0':
|
||||
resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/freebsd-arm64@0.25.0':
|
||||
resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/freebsd-x64@0.25.0':
|
||||
resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-arm64@0.25.0':
|
||||
resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-arm@0.25.0':
|
||||
resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-ia32@0.25.0':
|
||||
resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-loong64@0.25.0':
|
||||
resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-mips64el@0.25.0':
|
||||
resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-ppc64@0.25.0':
|
||||
resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-riscv64@0.25.0':
|
||||
resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-s390x@0.25.0':
|
||||
resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/linux-x64@0.25.0':
|
||||
resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/netbsd-arm64@0.25.0':
|
||||
resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.0':
|
||||
resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/openbsd-arm64@0.25.0':
|
||||
resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.0':
|
||||
resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/sunos-x64@0.25.0':
|
||||
resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/win32-arm64@0.25.0':
|
||||
resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/win32-ia32@0.25.0':
|
||||
resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
|
||||
engines: {node: '>=12'}
|
||||
'@esbuild/win32-x64@0.25.0':
|
||||
resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@@ -790,98 +805,98 @@ packages:
|
||||
resolution: {integrity: sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.34.4':
|
||||
resolution: {integrity: sha512-gGi5adZWvjtJU7Axs//CWaQbQd/vGy8KGcnEaCWiyCqxWYDxwIlAHFuSe6Guoxtd0SRvSfVTDMPd5H+4KE2kKA==}
|
||||
'@rollup/rollup-android-arm-eabi@4.34.9':
|
||||
resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.34.4':
|
||||
resolution: {integrity: sha512-1aRlh1gqtF7vNPMnlf1vJKk72Yshw5zknR/ZAVh7zycRAGF2XBMVDAHmFQz/Zws5k++nux3LOq/Ejj1WrDR6xg==}
|
||||
'@rollup/rollup-android-arm64@4.34.9':
|
||||
resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.34.4':
|
||||
resolution: {integrity: sha512-drHl+4qhFj+PV/jrQ78p9ch6A0MfNVZScl/nBps5a7u01aGf/GuBRrHnRegA9bP222CBDfjYbFdjkIJ/FurvSQ==}
|
||||
'@rollup/rollup-darwin-arm64@4.34.9':
|
||||
resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.34.4':
|
||||
resolution: {integrity: sha512-hQqq/8QALU6t1+fbNmm6dwYsa0PDD4L5r3TpHx9dNl+aSEMnIksHZkSO3AVH+hBMvZhpumIGrTFj8XCOGuIXjw==}
|
||||
'@rollup/rollup-darwin-x64@4.34.9':
|
||||
resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.34.4':
|
||||
resolution: {integrity: sha512-/L0LixBmbefkec1JTeAQJP0ETzGjFtNml2gpQXA8rpLo7Md+iXQzo9kwEgzyat5Q+OG/C//2B9Fx52UxsOXbzw==}
|
||||
'@rollup/rollup-freebsd-arm64@4.34.9':
|
||||
resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.34.4':
|
||||
resolution: {integrity: sha512-6Rk3PLRK+b8L/M6m/x6Mfj60LhAUcLJ34oPaxufA+CfqkUrDoUPQYFdRrhqyOvtOKXLJZJwxlOLbQjNYQcRQfw==}
|
||||
'@rollup/rollup-freebsd-x64@4.34.9':
|
||||
resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.34.4':
|
||||
resolution: {integrity: sha512-kmT3x0IPRuXY/tNoABp2nDvI9EvdiS2JZsd4I9yOcLCCViKsP0gB38mVHOhluzx+SSVnM1KNn9k6osyXZhLoCA==}
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.34.9':
|
||||
resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.34.4':
|
||||
resolution: {integrity: sha512-3iSA9tx+4PZcJH/Wnwsvx/BY4qHpit/u2YoZoXugWVfc36/4mRkgGEoRbRV7nzNBSCOgbWMeuQ27IQWgJ7tRzw==}
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.34.9':
|
||||
resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.34.4':
|
||||
resolution: {integrity: sha512-7CwSJW+sEhM9sESEk+pEREF2JL0BmyCro8UyTq0Kyh0nu1v0QPNY3yfLPFKChzVoUmaKj8zbdgBxUhBRR+xGxg==}
|
||||
'@rollup/rollup-linux-arm64-gnu@4.34.9':
|
||||
resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.34.4':
|
||||
resolution: {integrity: sha512-GZdafB41/4s12j8Ss2izofjeFXRAAM7sHCb+S4JsI9vaONX/zQ8cXd87B9MRU/igGAJkKvmFmJJBeeT9jJ5Cbw==}
|
||||
'@rollup/rollup-linux-arm64-musl@4.34.9':
|
||||
resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.34.4':
|
||||
resolution: {integrity: sha512-uuphLuw1X6ur11675c2twC6YxbzyLSpWggvdawTUamlsoUv81aAXRMPBC1uvQllnBGls0Qt5Siw8reSIBnbdqQ==}
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.34.9':
|
||||
resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.34.4':
|
||||
resolution: {integrity: sha512-KvLEw1os2gSmD6k6QPCQMm2T9P2GYvsMZMRpMz78QpSoEevHbV/KOUbI/46/JRalhtSAYZBYLAnT9YE4i/l4vg==}
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.34.9':
|
||||
resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.34.4':
|
||||
resolution: {integrity: sha512-wcpCLHGM9yv+3Dql/CI4zrY2mpQ4WFergD3c9cpRowltEh5I84pRT/EuHZsG0In4eBPPYthXnuR++HrFkeqwkA==}
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.34.9':
|
||||
resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.34.4':
|
||||
resolution: {integrity: sha512-nLbfQp2lbJYU8obhRQusXKbuiqm4jSJteLwfjnunDT5ugBKdxqw1X9KWwk8xp1OMC6P5d0WbzxzhWoznuVK6XA==}
|
||||
'@rollup/rollup-linux-s390x-gnu@4.34.9':
|
||||
resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.34.4':
|
||||
resolution: {integrity: sha512-JGejzEfVzqc/XNiCKZj14eb6s5w8DdWlnQ5tWUbs99kkdvfq9btxxVX97AaxiUX7xJTKFA0LwoS0KU8C2faZRg==}
|
||||
'@rollup/rollup-linux-x64-gnu@4.34.9':
|
||||
resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.34.4':
|
||||
resolution: {integrity: sha512-/iFIbhzeyZZy49ozAWJ1ZR2KW6ZdYUbQXLT4O5n1cRZRoTpwExnHLjlurDXXPKEGxiAg0ujaR9JDYKljpr2fDg==}
|
||||
'@rollup/rollup-linux-x64-musl@4.34.9':
|
||||
resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.34.4':
|
||||
resolution: {integrity: sha512-qORc3UzoD5UUTneiP2Afg5n5Ti1GAW9Gp5vHPxzvAFFA3FBaum9WqGvYXGf+c7beFdOKNos31/41PRMUwh1tpA==}
|
||||
'@rollup/rollup-win32-arm64-msvc@4.34.9':
|
||||
resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.34.4':
|
||||
resolution: {integrity: sha512-5g7E2PHNK2uvoD5bASBD9aelm44nf1w4I5FEI7MPHLWcCSrR8JragXZWgKPXk5i2FU3JFfa6CGZLw2RrGBHs2Q==}
|
||||
'@rollup/rollup-win32-ia32-msvc@4.34.9':
|
||||
resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.34.4':
|
||||
resolution: {integrity: sha512-p0scwGkR4kZ242xLPBuhSckrJ734frz6v9xZzD+kHVYRAkSUmdSLCIJRfql6H5//aF8Q10K+i7q8DiPfZp0b7A==}
|
||||
'@rollup/rollup-win32-x64-msvc@4.34.9':
|
||||
resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@@ -1166,6 +1181,10 @@ packages:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
clsx@1.2.1:
|
||||
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1337,9 +1356,9 @@ packages:
|
||||
resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
esbuild@0.21.5:
|
||||
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||
engines: {node: '>=12'}
|
||||
esbuild@0.25.0:
|
||||
resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
escalade@3.2.0:
|
||||
@@ -2040,6 +2059,10 @@ packages:
|
||||
resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
postcss@8.5.3:
|
||||
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2356,6 +2379,12 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-draggable@4.4.6:
|
||||
resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==}
|
||||
peerDependencies:
|
||||
react: '>= 16.3.0'
|
||||
react-dom: '>= 16.3.0'
|
||||
|
||||
react-error-boundary@4.1.2:
|
||||
resolution: {integrity: sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==}
|
||||
peerDependencies:
|
||||
@@ -2495,8 +2524,8 @@ packages:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
||||
rollup@4.34.4:
|
||||
resolution: {integrity: sha512-spF66xoyD7rz3o08sHP7wogp1gZ6itSq22SGa/IZTcUDXDlOyrShwMwkVSB+BUxFRZZCUYqdb3KWDEOMVQZxuw==}
|
||||
rollup@4.34.9:
|
||||
resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2775,22 +2804,27 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
vite@5.4.14:
|
||||
resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
vite@6.2.1:
|
||||
resolution: {integrity: sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||
jiti: '>=1.21.0'
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
sass-embedded: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
@@ -2805,6 +2839,10 @@ packages:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
@@ -3073,73 +3111,79 @@ snapshots:
|
||||
|
||||
'@emotion/unitless@0.7.5': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
'@esbuild/aix-ppc64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.21.5':
|
||||
'@esbuild/android-arm64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.21.5':
|
||||
'@esbuild/android-arm@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.21.5':
|
||||
'@esbuild/android-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.21.5':
|
||||
'@esbuild/darwin-arm64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.21.5':
|
||||
'@esbuild/darwin-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.21.5':
|
||||
'@esbuild/freebsd-arm64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.21.5':
|
||||
'@esbuild/freebsd-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.21.5':
|
||||
'@esbuild/linux-arm64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.21.5':
|
||||
'@esbuild/linux-arm@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.21.5':
|
||||
'@esbuild/linux-ia32@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.21.5':
|
||||
'@esbuild/linux-loong64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.21.5':
|
||||
'@esbuild/linux-mips64el@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.21.5':
|
||||
'@esbuild/linux-ppc64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.21.5':
|
||||
'@esbuild/linux-riscv64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.21.5':
|
||||
'@esbuild/linux-s390x@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.21.5':
|
||||
'@esbuild/linux-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.21.5':
|
||||
'@esbuild/netbsd-arm64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.21.5':
|
||||
'@esbuild/netbsd-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.21.5':
|
||||
'@esbuild/openbsd-arm64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.21.5':
|
||||
'@esbuild/openbsd-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.21.5':
|
||||
'@esbuild/sunos-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.21.5':
|
||||
'@esbuild/win32-arm64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.25.0':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@1.21.6))':
|
||||
@@ -3499,61 +3543,61 @@ snapshots:
|
||||
|
||||
'@remix-run/router@1.20.0': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.34.4':
|
||||
'@rollup/rollup-android-arm-eabi@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.34.4':
|
||||
'@rollup/rollup-android-arm64@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.34.4':
|
||||
'@rollup/rollup-darwin-arm64@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.34.4':
|
||||
'@rollup/rollup-darwin-x64@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.34.4':
|
||||
'@rollup/rollup-freebsd-arm64@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.34.4':
|
||||
'@rollup/rollup-freebsd-x64@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.34.4':
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.34.4':
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.34.4':
|
||||
'@rollup/rollup-linux-arm64-gnu@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.34.4':
|
||||
'@rollup/rollup-linux-arm64-musl@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.34.4':
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.34.4':
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.34.4':
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.34.4':
|
||||
'@rollup/rollup-linux-s390x-gnu@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.34.4':
|
||||
'@rollup/rollup-linux-x64-gnu@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.34.4':
|
||||
'@rollup/rollup-linux-x64-musl@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.34.4':
|
||||
'@rollup/rollup-win32-arm64-msvc@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.34.4':
|
||||
'@rollup/rollup-win32-ia32-msvc@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.34.4':
|
||||
'@rollup/rollup-win32-x64-msvc@4.34.9':
|
||||
optional: true
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
@@ -3689,14 +3733,14 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.19.1
|
||||
eslint-visitor-keys: 4.2.0
|
||||
|
||||
'@vitejs/plugin-react@4.3.3(vite@5.4.14(@types/node@22.9.0))':
|
||||
'@vitejs/plugin-react@4.3.3(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0)
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.14.2
|
||||
vite: 5.4.14(@types/node@22.9.0)
|
||||
vite: 6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3961,6 +4005,8 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
clsx@1.2.1: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
@@ -4179,31 +4225,33 @@ snapshots:
|
||||
d: 1.0.2
|
||||
ext: 1.7.0
|
||||
|
||||
esbuild@0.21.5:
|
||||
esbuild@0.25.0:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.21.5
|
||||
'@esbuild/android-arm': 0.21.5
|
||||
'@esbuild/android-arm64': 0.21.5
|
||||
'@esbuild/android-x64': 0.21.5
|
||||
'@esbuild/darwin-arm64': 0.21.5
|
||||
'@esbuild/darwin-x64': 0.21.5
|
||||
'@esbuild/freebsd-arm64': 0.21.5
|
||||
'@esbuild/freebsd-x64': 0.21.5
|
||||
'@esbuild/linux-arm': 0.21.5
|
||||
'@esbuild/linux-arm64': 0.21.5
|
||||
'@esbuild/linux-ia32': 0.21.5
|
||||
'@esbuild/linux-loong64': 0.21.5
|
||||
'@esbuild/linux-mips64el': 0.21.5
|
||||
'@esbuild/linux-ppc64': 0.21.5
|
||||
'@esbuild/linux-riscv64': 0.21.5
|
||||
'@esbuild/linux-s390x': 0.21.5
|
||||
'@esbuild/linux-x64': 0.21.5
|
||||
'@esbuild/netbsd-x64': 0.21.5
|
||||
'@esbuild/openbsd-x64': 0.21.5
|
||||
'@esbuild/sunos-x64': 0.21.5
|
||||
'@esbuild/win32-arm64': 0.21.5
|
||||
'@esbuild/win32-ia32': 0.21.5
|
||||
'@esbuild/win32-x64': 0.21.5
|
||||
'@esbuild/aix-ppc64': 0.25.0
|
||||
'@esbuild/android-arm': 0.25.0
|
||||
'@esbuild/android-arm64': 0.25.0
|
||||
'@esbuild/android-x64': 0.25.0
|
||||
'@esbuild/darwin-arm64': 0.25.0
|
||||
'@esbuild/darwin-x64': 0.25.0
|
||||
'@esbuild/freebsd-arm64': 0.25.0
|
||||
'@esbuild/freebsd-x64': 0.25.0
|
||||
'@esbuild/linux-arm': 0.25.0
|
||||
'@esbuild/linux-arm64': 0.25.0
|
||||
'@esbuild/linux-ia32': 0.25.0
|
||||
'@esbuild/linux-loong64': 0.25.0
|
||||
'@esbuild/linux-mips64el': 0.25.0
|
||||
'@esbuild/linux-ppc64': 0.25.0
|
||||
'@esbuild/linux-riscv64': 0.25.0
|
||||
'@esbuild/linux-s390x': 0.25.0
|
||||
'@esbuild/linux-x64': 0.25.0
|
||||
'@esbuild/netbsd-arm64': 0.25.0
|
||||
'@esbuild/netbsd-x64': 0.25.0
|
||||
'@esbuild/openbsd-arm64': 0.25.0
|
||||
'@esbuild/openbsd-x64': 0.25.0
|
||||
'@esbuild/sunos-x64': 0.25.0
|
||||
'@esbuild/win32-arm64': 0.25.0
|
||||
'@esbuild/win32-ia32': 0.25.0
|
||||
'@esbuild/win32-x64': 0.25.0
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
@@ -4891,6 +4939,12 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.3:
|
||||
dependencies:
|
||||
nanoid: 3.3.8
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
prettier-plugin-tailwindcss@0.6.9(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.4.2))(prettier@3.4.2):
|
||||
@@ -5242,6 +5296,13 @@ snapshots:
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
clsx: 1.2.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react-error-boundary@4.1.2(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.0
|
||||
@@ -5377,29 +5438,29 @@ snapshots:
|
||||
|
||||
reusify@1.0.4: {}
|
||||
|
||||
rollup@4.34.4:
|
||||
rollup@4.34.9:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.34.4
|
||||
'@rollup/rollup-android-arm64': 4.34.4
|
||||
'@rollup/rollup-darwin-arm64': 4.34.4
|
||||
'@rollup/rollup-darwin-x64': 4.34.4
|
||||
'@rollup/rollup-freebsd-arm64': 4.34.4
|
||||
'@rollup/rollup-freebsd-x64': 4.34.4
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.34.4
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.34.4
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.34.4
|
||||
'@rollup/rollup-linux-arm64-musl': 4.34.4
|
||||
'@rollup/rollup-linux-loongarch64-gnu': 4.34.4
|
||||
'@rollup/rollup-linux-powerpc64le-gnu': 4.34.4
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.34.4
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.34.4
|
||||
'@rollup/rollup-linux-x64-gnu': 4.34.4
|
||||
'@rollup/rollup-linux-x64-musl': 4.34.4
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.34.4
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.34.4
|
||||
'@rollup/rollup-win32-x64-msvc': 4.34.4
|
||||
'@rollup/rollup-android-arm-eabi': 4.34.9
|
||||
'@rollup/rollup-android-arm64': 4.34.9
|
||||
'@rollup/rollup-darwin-arm64': 4.34.9
|
||||
'@rollup/rollup-darwin-x64': 4.34.9
|
||||
'@rollup/rollup-freebsd-arm64': 4.34.9
|
||||
'@rollup/rollup-freebsd-x64': 4.34.9
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.34.9
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.34.9
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.34.9
|
||||
'@rollup/rollup-linux-arm64-musl': 4.34.9
|
||||
'@rollup/rollup-linux-loongarch64-gnu': 4.34.9
|
||||
'@rollup/rollup-linux-powerpc64le-gnu': 4.34.9
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.34.9
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.34.9
|
||||
'@rollup/rollup-linux-x64-gnu': 4.34.9
|
||||
'@rollup/rollup-linux-x64-musl': 4.34.9
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.34.9
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.34.9
|
||||
'@rollup/rollup-win32-x64-msvc': 4.34.9
|
||||
fsevents: 2.3.3
|
||||
|
||||
run-parallel@1.2.0:
|
||||
@@ -5719,25 +5780,27 @@ snapshots:
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
|
||||
vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.14(@types/node@22.9.0)):
|
||||
vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0)):
|
||||
dependencies:
|
||||
debug: 4.3.7
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.1.4(typescript@5.6.3)
|
||||
optionalDependencies:
|
||||
vite: 5.4.14(@types/node@22.9.0)
|
||||
vite: 6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite@5.4.14(@types/node@22.9.0):
|
||||
vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0):
|
||||
dependencies:
|
||||
esbuild: 0.21.5
|
||||
postcss: 8.4.47
|
||||
rollup: 4.34.4
|
||||
esbuild: 0.25.0
|
||||
postcss: 8.5.3
|
||||
rollup: 4.34.9
|
||||
optionalDependencies:
|
||||
'@types/node': 22.9.0
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.6
|
||||
yaml: 2.6.0
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
|
||||
@@ -76,3 +76,18 @@ export function enableSSH() {
|
||||
export function disableSSH() {
|
||||
return http.post('/api/vm/ssh/disable');
|
||||
}
|
||||
|
||||
// get mDNS state
|
||||
export function getMdnsState() {
|
||||
return http.get('/api/vm/mdns');
|
||||
}
|
||||
|
||||
// enable mDNS
|
||||
export function enableMdns() {
|
||||
return http.post('/api/vm/mdns/enable');
|
||||
}
|
||||
|
||||
// disable mDNS
|
||||
export function disableMdns() {
|
||||
return http.post('/api/vm/mdns/disable');
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Button } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const MainError = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex h-screen w-screen flex-col items-center justify-center space-y-5"
|
||||
role="alert"
|
||||
>
|
||||
<h2 className="text-lg font-semibold text-red-500">
|
||||
{t('error.title')}
|
||||
</h2>
|
||||
<h2 className="text-lg font-semibold text-red-500">{t('error.title')}</h2>
|
||||
<Button type="primary" danger onClick={() => window.location.assign(window.location.origin)}>
|
||||
{t('error.refresh')}
|
||||
</Button>
|
||||
|
||||
72
web/src/components/menu-item.tsx
Normal file
72
web/src/components/menu-item.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { Popover, Tooltip } from 'antd';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
type MenuItemProps = {
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
content: ReactNode;
|
||||
className?: string;
|
||||
fresh?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const MenuItem = ({
|
||||
title,
|
||||
icon,
|
||||
content,
|
||||
className,
|
||||
fresh,
|
||||
onOpenChange
|
||||
}: MenuItemProps) => {
|
||||
const isBigScreen = useMediaQuery({ minWidth: 640 });
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
||||
|
||||
function togglePopover(open: boolean) {
|
||||
setIsTooltipOpen(false);
|
||||
setIsPopoverOpen(open);
|
||||
|
||||
if (onOpenChange) {
|
||||
onOpenChange(open);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTooltip(open: boolean) {
|
||||
if (isPopoverOpen) {
|
||||
return;
|
||||
}
|
||||
setIsTooltipOpen(open);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
arrow={false}
|
||||
trigger="click"
|
||||
placement={isBigScreen ? 'bottomLeft' : 'bottom'}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={togglePopover}
|
||||
fresh={!!fresh}
|
||||
>
|
||||
<Tooltip
|
||||
title={title}
|
||||
mouseEnterDelay={0.6}
|
||||
placement="bottom"
|
||||
open={isTooltipOpen}
|
||||
onOpenChange={toggleTooltip}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
className
|
||||
? className
|
||||
: 'flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/80 hover:text-white'
|
||||
}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -96,6 +96,7 @@ const en = {
|
||||
mountFailed: 'Mount Failed',
|
||||
mountDesc:
|
||||
"In some systems, it's necessary to eject the virtual disk on the remote host before mounting the image.",
|
||||
refresh: 'Refresh the image list',
|
||||
tips: {
|
||||
title: 'How to upload',
|
||||
usb1: 'Connect the NanoKVM to your computer via USB.',
|
||||
@@ -199,6 +200,10 @@ const en = {
|
||||
description: 'Enable SSH remote access',
|
||||
tip: 'Set a strong password before enabling (Account - Change Password)'
|
||||
},
|
||||
mdns: {
|
||||
description: 'Enable mDNS discovery service',
|
||||
tip: "Turning it off if it's not needed"
|
||||
},
|
||||
disk: 'Virtual Disk',
|
||||
diskDesc: 'Mount virtual U-disk on the remote host',
|
||||
network: 'Virtual Network',
|
||||
@@ -208,7 +213,7 @@ const en = {
|
||||
title: 'Tailscale',
|
||||
memory: {
|
||||
title: 'Memory optimization',
|
||||
tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. It's recommended to set to 50MB if using Tailscale. A Tailscale restart is required for the change to take effect.",
|
||||
tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. It's recommended to set to 75MB if using Tailscale. A Tailscale restart is required for the change to take effect.",
|
||||
disable: 'Disable'
|
||||
},
|
||||
restart: 'Restart Tailscale?',
|
||||
@@ -260,15 +265,15 @@ const en = {
|
||||
}
|
||||
},
|
||||
error: {
|
||||
title: 'We\'ve ran into an issue',
|
||||
refresh: 'Refresh',
|
||||
title: "We've ran into an issue",
|
||||
refresh: 'Refresh'
|
||||
},
|
||||
fullscreen: {
|
||||
toggle: 'Toggle Fullscreen',
|
||||
toggle: 'Toggle Fullscreen'
|
||||
},
|
||||
menu: {
|
||||
collapse: 'Collapse Menu',
|
||||
expand: 'Expand Menu',
|
||||
expand: 'Expand Menu'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,13 +14,11 @@ const fr = {
|
||||
placeholderPassword2: 'Veuillez entrer votre mot de passe à nouveau',
|
||||
noEmptyUsername: "Le nom d'utilisateur ne peut pas être vide",
|
||||
noEmptyPassword: 'Le mot de passe ne peut pas être vide',
|
||||
noAccount:
|
||||
"Impossible de récupérer les informations de l'utilisateur, veuillez rafraîchir la page ou réinitialiser le mot de passe",
|
||||
noAccount: "Impossible de récupérer les informations de l'utilisateur, veuillez rafraîchir la page ou réinitialiser le mot de passe",
|
||||
invalidUser: "Nom d'utilisateur ou mot de passe invalide",
|
||||
error: 'Erreur inattendue',
|
||||
changePassword: 'Changer le mot de passe',
|
||||
changePasswordDesc:
|
||||
'Pour la sécurité de votre appareil, veuillez modifier le mot de passe de connexion Web.',
|
||||
changePasswordDesc: 'Pour la sécurité de votre appareil, veuillez modifier le mot de passe de connexion Web.',
|
||||
differentPassword: 'Les mots de passe ne correspondent pas',
|
||||
illegalUsername: "Le nom d'utilisateur contient des caractères illégaux",
|
||||
illegalPassword: 'Le mot de passe contient des caractères illégaux',
|
||||
@@ -140,6 +138,12 @@ const fr = {
|
||||
input: "Veuillez entrer l'adresse MAC",
|
||||
ok: 'Ok'
|
||||
},
|
||||
download: {
|
||||
title: 'Télécharger l’image',
|
||||
input: 'Veuillez entrer l’URL d’une image distante',
|
||||
ok: 'Ok',
|
||||
disabled: 'La partition /data est en lecture seule, impossible de télécharger l’image'
|
||||
},
|
||||
power: {
|
||||
title: 'Power',
|
||||
reset: 'Réinitialiser',
|
||||
@@ -244,6 +248,10 @@ const fr = {
|
||||
password: 'Mot de passe',
|
||||
updateBtn: 'Mettre à jour',
|
||||
logoutBtn: 'Déconnexion'
|
||||
},
|
||||
error: {
|
||||
title: "Une erreur est survenue",
|
||||
refresh: 'Rafraîchir'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ const ko = {
|
||||
placeholderPassword2: '비밀번호를 다시 입력하세요.',
|
||||
noEmptyUsername: '사용자 이름은 비어있을 수 없습니다.',
|
||||
noEmptyPassword: '비밀번호는 비어있을 수 없습니다.',
|
||||
noAccount:
|
||||
'사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침하거나 비밀번호를 초기화하세요.',
|
||||
noAccount: '사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침하거나 비밀번호를 초기화하세요.',
|
||||
invalidUser: '사용자 이름이나 비밀번호가 틀렸습니다.',
|
||||
error: '알 수 없는 오류',
|
||||
changePassword: '비밀번호 변경',
|
||||
@@ -28,7 +27,8 @@ const ko = {
|
||||
cancel: '취소',
|
||||
loginButtonText: '로그인',
|
||||
tips: {
|
||||
reset1: '비밀번호를 재설정하려면 NanoKVM의 BOOT 버튼을 10초 동안 누르고 계세요.',
|
||||
reset1:
|
||||
'비밀번호를 재설정하려면 NanoKVM의 BOOT 버튼을 10초 동안 누르고 계세요.',
|
||||
reset2: '자세한 절차는 이 문서를 참조하세요:',
|
||||
reset3: '웹 기본 계정:',
|
||||
reset4: 'SSH 기본 계정:',
|
||||
@@ -89,9 +89,11 @@ const ko = {
|
||||
title: '이미지',
|
||||
loading: '불러오는 중...',
|
||||
empty: '아무것도 없습니다.',
|
||||
cdrom: 'CD-ROM 모드로 이미지 마운트',
|
||||
mountFailed: '이미지 마운트 실패',
|
||||
mountDesc:
|
||||
'일부 시스템에서는 이미지를 마운트하기 전에 원격 호스트에서 가상 디스크를 제거해야 합니다.',
|
||||
refresh: '이미지 목록 새로고침',
|
||||
tips: {
|
||||
title: '업로드 방법',
|
||||
usb1: 'USB를 통해 NanoKVM을 컴퓨터에 연결하세요.',
|
||||
@@ -99,7 +101,7 @@ const ko = {
|
||||
usb3: '컴퓨터에서 가상 디스크를 열고 이미지 파일을 가상 디스크의 루트 디렉토리로 복사하세요.',
|
||||
scp1: 'NanoKVM과 컴퓨터가 동일한 로컬 네트워크에 있는지 확인하세요.',
|
||||
scp2: '컴퓨터에서 터미널을 열고 SCP 명령을 사용하여 이미지 파일을 NanoKVM의 /data 디렉터리에 업로드하세요.',
|
||||
scp3: '예: scp [이미지 파일 경로] root@[NanoKVM IP 주소]:/data',
|
||||
scp3: '예시: scp [이미지 파일 경로] root@[NanoKVM IP 주소]:/data',
|
||||
tfCard: 'TF 카드',
|
||||
tf1: '이 방법은 Linux 시스템에서 지원됩니다',
|
||||
tf2: 'NanoKVM에서 TF 카드를 가져옵니다(전체 버전의 경우 먼저 케이스를 분해하세요).',
|
||||
@@ -116,7 +118,7 @@ const ko = {
|
||||
runFailed: '실행 실패',
|
||||
attention: '주의',
|
||||
delDesc: '이 파일을 정말로 삭제합니까?',
|
||||
confirm: '예',
|
||||
confirm: '네',
|
||||
cancel: '아니오',
|
||||
delete: '삭제',
|
||||
close: '닫기'
|
||||
@@ -222,7 +224,8 @@ const ko = {
|
||||
upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/',
|
||||
upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/',
|
||||
refresh: '현재 페이지 새로고침',
|
||||
notLogin: '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.',
|
||||
notLogin:
|
||||
'이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.',
|
||||
urlPeriod: '이 주소는 10분간 유효합니다.',
|
||||
login: '로그인',
|
||||
loginSuccess: '로그인 성공',
|
||||
@@ -233,7 +236,7 @@ const ko = {
|
||||
logout: '로그아웃',
|
||||
logout2: '정말로 로그아웃 합니까?',
|
||||
uninstall: 'Tailscale 제거',
|
||||
okBtn: '예',
|
||||
okBtn: '네',
|
||||
cancelBtn: '아니오'
|
||||
},
|
||||
update: {
|
||||
@@ -253,6 +256,10 @@ const ko = {
|
||||
updateBtn: '업데이트',
|
||||
logoutBtn: '로그아웃'
|
||||
}
|
||||
},
|
||||
error: {
|
||||
title: '문제가 발생했습니다.',
|
||||
refresh: '새로고침'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,6 +90,7 @@ const ru = {
|
||||
title: 'Образы',
|
||||
loading: 'Загрузка...',
|
||||
empty: 'Пусто',
|
||||
cdrom: 'Смонтировать образ как CD-ROM диск',
|
||||
mountFailed: 'Монтирование образа не удалось',
|
||||
mountDesc:
|
||||
'В некоторых системах необходимо отсоединить виртуальный диск на удаленном хосте перед монтированием образа.',
|
||||
@@ -254,6 +255,10 @@ const ru = {
|
||||
updateBtn: 'Обновить',
|
||||
logoutBtn: 'Выйти'
|
||||
}
|
||||
},
|
||||
error: {
|
||||
title: "У нас возникла проблема",
|
||||
refresh: 'Обновить страницу'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -193,6 +193,10 @@ const zh = {
|
||||
description: '启用 SSH 远程访问',
|
||||
tip: '启用前请务必设置强密码(帐号 - 修改密码)'
|
||||
},
|
||||
mdns: {
|
||||
description: '启用 mDNS 发现服务',
|
||||
tip: '如果您未使用此功能,建议将其关闭'
|
||||
},
|
||||
disk: '虚拟U盘',
|
||||
diskDesc: '在远程主机中挂载虚拟U盘',
|
||||
network: '虚拟网卡',
|
||||
|
||||
@@ -46,12 +46,12 @@ const zh_tw = {
|
||||
finishBtn: '完成'
|
||||
},
|
||||
screen: {
|
||||
video: '影片模式',
|
||||
video: '編碼格式',
|
||||
resolution: '解析度',
|
||||
auto: '自動',
|
||||
autoTips:
|
||||
'在特定解析度下可能會出現畫面撕裂或滑鼠偏移的情況。考慮調整遠端主機的解析度或停用自動模式。',
|
||||
fps: '影格速率',
|
||||
fps: '更新頻率',
|
||||
customizeFps: '自定義',
|
||||
quality: '品質',
|
||||
qualityLossless: '無損',
|
||||
@@ -87,6 +87,7 @@ const zh_tw = {
|
||||
title: '映像',
|
||||
loading: '載入中...',
|
||||
empty: '未找到任何內容',
|
||||
cdrom: '以CD-ROM模式掛載',
|
||||
mountFailed: '掛載失敗',
|
||||
mountDesc: '在某些系統中,在掛載映像之前需要中斷遠端主機上的虛擬磁碟。',
|
||||
tips: {
|
||||
@@ -250,6 +251,17 @@ const zh_tw = {
|
||||
updateBtn: '修改',
|
||||
logoutBtn: '登出'
|
||||
}
|
||||
},
|
||||
error: {
|
||||
title: '我們遇到了一些問題',
|
||||
refresh: '重新整理',
|
||||
},
|
||||
fullscreen: {
|
||||
toggle: '進入全螢幕模式',
|
||||
},
|
||||
menu: {
|
||||
collapse: '收起選單',
|
||||
expand: '展開選單',
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Divider, Input, Popover, Tooltip } from 'antd';
|
||||
import { Button, Divider, Input } from 'antd';
|
||||
import type { InputRef } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { useSetAtom } from 'jotai';
|
||||
@@ -8,22 +8,18 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { downloadImage, imageEnabled, statusImage } from '@/api/download.ts';
|
||||
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
export const DownloadImage = () => {
|
||||
const { t } = useTranslation();
|
||||
const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom);
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
const [status, setStatus] = useState('');
|
||||
const [log, setLog] = useState('');
|
||||
const [diskEnabled, setDiskEnabled] = useState(false);
|
||||
const [popoverKey, setPopoverKey] = useState(0);
|
||||
|
||||
const tooltip = t('download.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
|
||||
const intervalId = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
@@ -41,6 +37,7 @@ export const DownloadImage = () => {
|
||||
setDiskEnabled(false);
|
||||
});
|
||||
}
|
||||
|
||||
function handleOpenChange(open: boolean) {
|
||||
if (open) {
|
||||
clearInterval(intervalId.current);
|
||||
@@ -60,8 +57,6 @@ export const DownloadImage = () => {
|
||||
clearInterval(intervalId.current);
|
||||
intervalId.current = undefined;
|
||||
}
|
||||
|
||||
setIsPopoverOpen(open);
|
||||
}
|
||||
|
||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
@@ -116,7 +111,7 @@ export const DownloadImage = () => {
|
||||
}
|
||||
|
||||
const content = (
|
||||
<div className="min-w-[300px]">
|
||||
<div key={popoverKey} className="min-w-[300px]">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<span className="text-base font-bold text-neutral-300">{t('download.title')}</span>
|
||||
</div>
|
||||
@@ -161,23 +156,11 @@ export const DownloadImage = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
key={popoverKey}
|
||||
<MenuItem
|
||||
title={t('download.title')}
|
||||
icon={<DownloadIcon size={18} />}
|
||||
content={content}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
handleOpenChange(visible);
|
||||
setTooltipValue(visible ? '' : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
|
||||
<DownloadIcon size={18} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
onOpenChange={handleOpenChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Tooltip } from 'antd';
|
||||
import { MaximizeIcon, MinimizeIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip } from 'antd';
|
||||
|
||||
export const Fullscreen = () => {
|
||||
const { t } = useTranslation();
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [tooltipValue, setTooltipValue] = useState(t('fullscreen.toggle'));
|
||||
|
||||
useEffect(() => {
|
||||
function onFullscreenChange() {
|
||||
@@ -24,16 +23,16 @@ export const Fullscreen = () => {
|
||||
function handleFullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
const element = document.documentElement;
|
||||
element.requestFullscreen().then();
|
||||
element.requestFullscreen();
|
||||
} else {
|
||||
document.exitFullscreen().then();
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<Tooltip title={t('fullscreen.toggle')} placement="bottom" mouseEnterDelay={0.6}>
|
||||
<div
|
||||
className="hidden h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white sm:flex"
|
||||
className="hidden h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/80 hover:text-white sm:flex"
|
||||
onClick={handleFullscreen}
|
||||
>
|
||||
{isFullscreen ? <MinimizeIcon size={18} /> : <MaximizeIcon size={18} />}
|
||||
|
||||
@@ -14,11 +14,12 @@ import * as api from '@/api/storage.ts';
|
||||
import { client } from '@/lib/websocket.ts';
|
||||
|
||||
type ImagesProps = {
|
||||
isOpen: boolean;
|
||||
cdrom: boolean;
|
||||
setIsMounted: (isMounted: boolean) => void;
|
||||
};
|
||||
|
||||
export const Images = ({ cdrom, setIsMounted }: ImagesProps) => {
|
||||
export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [notify, contextHolder] = notification.useNotification();
|
||||
|
||||
@@ -28,8 +29,10 @@ export const Images = ({ cdrom, setIsMounted }: ImagesProps) => {
|
||||
const [mountedImage, setMountedImage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
getImages();
|
||||
}, []);
|
||||
if (isOpen) {
|
||||
getImages();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
function getImages() {
|
||||
if (isLoading) return;
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Divider, Popover, Switch, Tooltip } from 'antd';
|
||||
import { Divider, Switch, Tooltip } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { DiscIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import { getCdRom, getMountedImage } from '@/api/storage.ts';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
import { Images } from './images.tsx';
|
||||
import { Tips } from './tips.tsx';
|
||||
|
||||
export const Image = () => {
|
||||
const { t } = useTranslation();
|
||||
const isBigScreen = useMediaQuery({ minWidth: 640 });
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const [cdrom, setCdrom] = useState(false);
|
||||
|
||||
const tooltip = t('image.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
useEffect(() => {
|
||||
getMountedImage().then((rsp) => {
|
||||
if (rsp.code !== 0) return;
|
||||
setIsMounted(!!rsp.data?.file);
|
||||
if (rsp.code === 0) {
|
||||
setIsMounted(!!rsp.data?.file);
|
||||
}
|
||||
});
|
||||
|
||||
getCdRom().then((rsp) => {
|
||||
if (rsp.code !== 0) return;
|
||||
setCdrom(rsp.data?.cdrom === 1);
|
||||
if (rsp.code === 0) {
|
||||
setCdrom(rsp.data?.cdrom === 1);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -41,43 +39,38 @@ export const Image = () => {
|
||||
<Tips />
|
||||
</div>
|
||||
|
||||
<Tooltip title={t('image.cdrom')} placement="bottom">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs text-neutral-400">CD-ROM</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Tooltip title={t('image.cdrom')} placement="bottom">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs text-neutral-400">CD-ROM</span>
|
||||
|
||||
<Switch size="small" checked={cdrom} onChange={(checked) => setCdrom(checked)}></Switch>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={cdrom}
|
||||
onChange={(checked) => setCdrom(checked)}
|
||||
></Switch>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '10px 0 15px 0' }} />
|
||||
|
||||
{isPopoverOpen && <Images cdrom={cdrom} setIsMounted={setIsMounted} />}
|
||||
<Images isOpen={isPopoverOpen} cdrom={cdrom} setIsMounted={setIsMounted} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
<MenuItem
|
||||
title={t('image.title')}
|
||||
icon={<DiscIcon size={17} />}
|
||||
content={content}
|
||||
placement={isBigScreen ? 'bottomLeft' : 'bottom'}
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
setIsPopoverOpen(visible);
|
||||
setTooltipValue(visible ? '' : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<div
|
||||
className={clsx(
|
||||
'flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700',
|
||||
isMounted ? 'text-blue-500' : 'text-neutral-300 hover:text-white'
|
||||
)}
|
||||
>
|
||||
<DiscIcon size={18} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
className={clsx(
|
||||
'flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700',
|
||||
isMounted ? 'text-blue-500' : 'text-neutral-300 hover:text-white'
|
||||
)}
|
||||
fresh={true}
|
||||
onOpenChange={setIsPopoverOpen}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Divider, Tooltip } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom } from 'jotai';
|
||||
import { MenuIcon, XIcon } from 'lucide-react';
|
||||
import Draggable from 'react-draggable';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { getMenuDisabledItems } from '@/lib/localstorage.ts';
|
||||
import { menuDisabledItemsAtom } from '@/jotai/settings.ts';
|
||||
@@ -18,77 +20,83 @@ import { Script } from './script';
|
||||
import { Settings } from './settings';
|
||||
import { Terminal } from './terminal';
|
||||
import { Wol } from './wol';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const Menu = () => {
|
||||
const { t } = useTranslation();
|
||||
const [menuDisabledItems, setMenuDisabledItems] = useAtom(menuDisabledItemsAtom);
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||
|
||||
const nodeRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const items = getMenuDisabledItems();
|
||||
setMenuDisabledItems(items);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="fixed left-1/2 top-[10px] z-[1000] -translate-x-1/2">
|
||||
<div className="sticky top-[10px]">
|
||||
<div
|
||||
className={clsx(
|
||||
'h-[36px] items-center rounded bg-neutral-800/80',
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
)}
|
||||
>
|
||||
<div className="hidden h-[30px] select-none items-center px-3 sm:flex">
|
||||
<img src="/sipeed.ico" width={18} height={18} alt="sipeed" />
|
||||
<Draggable nodeRef={nodeRef} handle="strong" positionOffset={{ x: '-50%', y: '0%' }}>
|
||||
<div ref={nodeRef} className="fixed left-1/2 top-[10px] z-[1000] -translate-x-1/2">
|
||||
<div className="sticky top-[10px] flex w-full justify-center">
|
||||
<div
|
||||
className={clsx(
|
||||
'h-[36px] items-center rounded bg-neutral-800/80',
|
||||
isMenuOpen ? 'flex' : 'hidden'
|
||||
)}
|
||||
>
|
||||
<strong>
|
||||
<div className="hidden h-[30px] cursor-move select-none items-center px-3 sm:flex">
|
||||
<img src="/sipeed.ico" width={18} height={18} draggable={false} alt="sipeed" />
|
||||
</div>
|
||||
</strong>
|
||||
|
||||
<Screen />
|
||||
<Keyboard />
|
||||
<Mouse />
|
||||
<Divider type="vertical" />
|
||||
|
||||
{!menuDisabledItems.includes('image') && <Image />}
|
||||
{!menuDisabledItems.includes('download') && <DownloadImage />}
|
||||
{!menuDisabledItems.includes('script') && <Script />}
|
||||
{!menuDisabledItems.includes('terminal') && <Terminal />}
|
||||
{!menuDisabledItems.includes('wol') && <Wol />}
|
||||
|
||||
{['image', 'script', 'terminal', 'wol', 'download'].some(
|
||||
(key) => !menuDisabledItems.includes(key)
|
||||
) && <Divider type="vertical" />}
|
||||
|
||||
{!menuDisabledItems.includes('power') && (
|
||||
<>
|
||||
<Power />
|
||||
<Divider type="vertical" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Settings />
|
||||
<Fullscreen />
|
||||
|
||||
<Tooltip title={t('menu.collapse')} placement="bottom" mouseEnterDelay={0.6}>
|
||||
<div
|
||||
className="mr-1 flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/80 hover:text-white"
|
||||
onClick={() => setIsMenuOpen((o) => !o)}
|
||||
>
|
||||
<XIcon size={20} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Screen />
|
||||
<Keyboard />
|
||||
<Mouse />
|
||||
<Divider type="vertical" />
|
||||
|
||||
{!menuDisabledItems.includes('image') && <Image />}
|
||||
{!menuDisabledItems.includes('download') && <DownloadImage />}
|
||||
{!menuDisabledItems.includes('script') && <Script />}
|
||||
{!menuDisabledItems.includes('terminal') && <Terminal />}
|
||||
{!menuDisabledItems.includes('wol') && <Wol />}
|
||||
|
||||
{['image', 'script', 'terminal', 'wol', 'download'].some(
|
||||
(key) => !menuDisabledItems.includes(key)
|
||||
) && <Divider type="vertical" />}
|
||||
|
||||
{!menuDisabledItems.includes('power') && (
|
||||
<>
|
||||
<Power />
|
||||
<Divider type="vertical" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Settings />
|
||||
<Fullscreen />
|
||||
|
||||
<Tooltip title={t('menu.collapse')} placement="bottom">
|
||||
<div
|
||||
className="mr-1 flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white"
|
||||
onClick={() => setIsMenuOpen((o) => !o)}
|
||||
>
|
||||
<XIcon size={20} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{!isMenuOpen && (
|
||||
<Tooltip title={t('menu.expand')} placement="bottom">
|
||||
<Tooltip title={t('menu.expand')} placement="bottom" mouseEnterDelay={0.6}>
|
||||
<div
|
||||
className="flex h-[30px] w-[50px] items-center justify-center rounded bg-neutral-800/50 text-white/50 hover:bg-neutral-800 hover:text-white"
|
||||
className="flex h-[30px] w-[32px] cursor-pointer items-center justify-center rounded bg-neutral-800/50 text-white/50 hover:bg-neutral-700 hover:text-white"
|
||||
onClick={() => setIsMenuOpen((o) => !o)}
|
||||
>
|
||||
<MenuIcon />
|
||||
<MenuIcon size={20} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,44 +1,26 @@
|
||||
import { useState } from 'react';
|
||||
import { Popover, Tooltip } from 'antd';
|
||||
import { KeyboardIcon } from 'lucide-react';
|
||||
import { t } from 'i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
import { CtrlAltDel } from './ctrl-alt-del.tsx';
|
||||
import { Paste } from './paste.tsx';
|
||||
import { VirtualKeyboard } from './virtual-keyboard.tsx';
|
||||
import { CtrlAltDel } from './ctrl-alt-del.tsx';
|
||||
|
||||
|
||||
export const Keyboard = () => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const tooltip = t('keyboard.title')
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<Paste setIsPopoverOpen={setIsPopoverOpen} />
|
||||
<VirtualKeyboard />
|
||||
<CtrlAltDel />
|
||||
</>
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
setIsPopoverOpen(visible);
|
||||
setTooltipValue(visible ? "" : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
|
||||
<KeyboardIcon size={18} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
<MenuItem
|
||||
title={t('keyboard.title')}
|
||||
icon={<KeyboardIcon size={18} />}
|
||||
content={
|
||||
<>
|
||||
<Paste />
|
||||
<VirtualKeyboard />
|
||||
<CtrlAltDel />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,11 +10,7 @@ import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
type PasteProps = {
|
||||
setIsPopoverOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const Paste = ({ setIsPopoverOpen }: PasteProps) => {
|
||||
export const Paste = () => {
|
||||
const { t } = useTranslation();
|
||||
const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom);
|
||||
|
||||
@@ -26,12 +22,6 @@ export const Paste = ({ setIsPopoverOpen }: PasteProps) => {
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
|
||||
function openModal() {
|
||||
setIsPopoverOpen(false);
|
||||
|
||||
setIsModalOpen(true);
|
||||
}
|
||||
|
||||
function onChange(e: ChangeEvent<HTMLTextAreaElement>) {
|
||||
const value = e.target.value;
|
||||
setStatus(isASCII(value) ? '' : 'error');
|
||||
@@ -82,7 +72,7 @@ export const Paste = ({ setIsPopoverOpen }: PasteProps) => {
|
||||
className={clsx(
|
||||
'flex cursor-pointer select-none items-center space-x-2 rounded py-1 pl-2 pr-5 hover:bg-neutral-700/70'
|
||||
)}
|
||||
onClick={openModal}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
<ClipboardIcon size={18} />
|
||||
<span>{t('keyboard.paste')}</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Divider, Popover, Tooltip } from 'antd';
|
||||
import { Divider, Popover } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom } from 'jotai';
|
||||
import {
|
||||
@@ -19,17 +19,14 @@ import * as api from '@/api/hid';
|
||||
import * as ls from '@/lib/localstorage';
|
||||
import { client } from '@/lib/websocket';
|
||||
import { mouseModeAtom, mouseStyleAtom } from '@/jotai/mouse';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
export const Mouse = () => {
|
||||
const { t } = useTranslation();
|
||||
const [mouseStyle, setMouseStyle] = useAtom(mouseStyleAtom);
|
||||
const [mouseMode, setMouseMode] = useAtom(mouseModeAtom);
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [isResetting, setIsResetting] = useState(false);
|
||||
|
||||
const tooltip = t('mouse.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
const mouseStyles = [
|
||||
{ name: t('mouse.default'), icon: <MousePointerIcon size={14} />, value: 'cursor-default' },
|
||||
{ name: t('mouse.grab'), icon: <HandIcon size={14} />, value: 'cursor-grab' },
|
||||
@@ -81,7 +78,6 @@ export const Mouse = () => {
|
||||
api.reset().finally(() => {
|
||||
client.connect();
|
||||
setIsResetting(false);
|
||||
setIsPopoverOpen(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,23 +146,5 @@ export const Mouse = () => {
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
setIsPopoverOpen(visible);
|
||||
setTooltipValue(visible ? "" : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue}>
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
|
||||
<MouseIcon size={18} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
);
|
||||
return <MenuItem title={t('mouse.title')} icon={<MouseIcon size={18} />} content={content} />;
|
||||
};
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Popover, Slider, Tooltip } from 'antd';
|
||||
import { Slider } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { CirclePowerIcon, LoaderCircleIcon, PowerIcon, RotateCcwIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/vm';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
export const Power = () => {
|
||||
const { t } = useTranslation();
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [isPowerOn, setIsPowerOn] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [powerDuration, setPowerDuration] = useState(8);
|
||||
|
||||
const tooltip = t('power.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
useEffect(() => {
|
||||
getLed();
|
||||
const interval = setInterval(getLed, 5000);
|
||||
@@ -40,8 +37,6 @@ export const Power = () => {
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
|
||||
setIsPopoverOpen(false);
|
||||
|
||||
const millisecond = Math.floor((duration ? duration : powerDuration) * 1000);
|
||||
|
||||
api.setGpio(button, millisecond).finally(() => {
|
||||
@@ -53,6 +48,16 @@ export const Power = () => {
|
||||
setPowerDuration(value);
|
||||
}
|
||||
|
||||
const icon = (
|
||||
<div className={clsx('h-[18px] w-[18px]', isPowerOn ? 'text-green-600' : 'text-neutral-500')}>
|
||||
{isLoading ? (
|
||||
<LoaderCircleIcon className="animate-spin" size={18} />
|
||||
) : (
|
||||
<PowerIcon size={18} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const content = (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div
|
||||
@@ -91,31 +96,5 @@ export const Power = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
setIsPopoverOpen(visible);
|
||||
setTooltipValue(visible ? '' : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700 hover:text-white">
|
||||
<div
|
||||
className={clsx('h-[18px] w-[18px]', isPowerOn ? 'text-green-600' : 'text-neutral-500')}
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoaderCircleIcon className="animate-spin" size={18} />
|
||||
) : (
|
||||
<PowerIcon size={18} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
);
|
||||
return <MenuItem title={t('power.title')} icon={icon} content={content} />;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Popover, Tooltip } from 'antd';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { MonitorIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { updateScreen } from '@/api/vm';
|
||||
import * as ls from '@/lib/localstorage';
|
||||
import { resolutionAtom, videoModeAtom } from '@/jotai/screen.ts';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
import { BitRateMap, QualityMap } from './constants.ts';
|
||||
import { Fps } from './fps';
|
||||
@@ -14,17 +15,16 @@ import { Quality } from './quality';
|
||||
import { Reset } from './reset.tsx';
|
||||
import { Resolution } from './resolution';
|
||||
import { VideoMode } from './video-mode.tsx';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const Screen = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const videoMode = useAtomValue(videoModeAtom);
|
||||
const resolution = useAtomValue(resolutionAtom);
|
||||
|
||||
const [fps, setFps] = useState(30);
|
||||
const [quality, setQuality] = useState(2);
|
||||
|
||||
const tooltip = t('screen.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
useEffect(() => {
|
||||
updateScreen('type', videoMode === 'mjpeg' ? 0 : 1);
|
||||
updateScreen('resolution', resolution!.height);
|
||||
@@ -57,28 +57,16 @@ export const Screen = () => {
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<div className="flex flex-col space-y-1">
|
||||
<VideoMode />
|
||||
<Resolution />
|
||||
<Quality quality={quality} setQuality={setQuality} />
|
||||
<Fps fps={fps} setFps={setFps} />
|
||||
{videoMode === 'mjpeg' && <FrameDetect />}
|
||||
<Reset />
|
||||
</div>
|
||||
}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
onOpenChange={(visible) => setTooltipValue(visible ? '' : tooltip)}
|
||||
>
|
||||
<Tooltip title={tooltipValue}>
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
|
||||
<MonitorIcon size={18} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
const content = (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<VideoMode />
|
||||
<Resolution />
|
||||
<Quality quality={quality} setQuality={setQuality} />
|
||||
<Fps fps={fps} setFps={setFps} />
|
||||
{videoMode === 'mjpeg' && <FrameDetect />}
|
||||
<Reset />
|
||||
</div>
|
||||
);
|
||||
|
||||
return <MenuItem title={t('screen.title')} icon={<MonitorIcon size={18} />} content={content} />;
|
||||
};
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Popconfirm, Popover, Tooltip } from 'antd';
|
||||
import { Button, Divider, Popconfirm } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { ChevronRightIcon, FileJsonIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import * as api from '@/api/script.ts';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
import { Run } from './run';
|
||||
|
||||
export const Script = () => {
|
||||
const { t } = useTranslation();
|
||||
const isBigScreen = useMediaQuery({ minWidth: 640 });
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [scripts, setScripts] = useState<string[]>([]);
|
||||
const [currentScript, setCurrentScript] = useState('');
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
@@ -22,17 +20,12 @@ export const Script = () => {
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const inputRef = useRef<any>(null);
|
||||
|
||||
const tooltip = t('script.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
function handleOpenChange(open: boolean) {
|
||||
if (open) {
|
||||
getScripts();
|
||||
} else {
|
||||
setCurrentScript('');
|
||||
}
|
||||
|
||||
setIsPopoverOpen(open);
|
||||
}
|
||||
|
||||
function selectFile() {
|
||||
@@ -83,8 +76,6 @@ export const Script = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setIsPopoverOpen(false);
|
||||
}
|
||||
|
||||
function getScripts() {
|
||||
@@ -191,23 +182,12 @@ export const Script = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover
|
||||
<MenuItem
|
||||
title={t('script.title')}
|
||||
icon={<FileJsonIcon size={18} />}
|
||||
content={content}
|
||||
placement={isBigScreen ? 'bottomLeft' : 'bottom'}
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
handleOpenChange(visible);
|
||||
setTooltipValue(visible ? '' : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
|
||||
<FileJsonIcon size={18} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
onOpenChange={handleOpenChange}
|
||||
/>
|
||||
|
||||
{isRunning && <Run script={currentScript} setIsRunning={setIsRunning} />}
|
||||
</>
|
||||
|
||||
@@ -39,10 +39,12 @@ export const Information = () => {
|
||||
<span>{information ? information.ip : '-'}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<span>{t('settings.about.mdns')}</span>
|
||||
<span>{information ? information.mdns : '-'}</span>
|
||||
</div>
|
||||
{!!information?.mdns && (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<span>{t('settings.about.mdns')}</span>
|
||||
<span>{information.mdns}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Divider } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Mdns } from './mdns.tsx';
|
||||
import { Oled } from './oled.tsx';
|
||||
import { Ssh } from './ssh.tsx';
|
||||
import { VirtualDevices } from './virtual-devices.tsx';
|
||||
@@ -18,6 +19,7 @@ export const Device = () => {
|
||||
<Oled />
|
||||
<Wifi />
|
||||
<Ssh />
|
||||
<Mdns />
|
||||
<VirtualDevices />
|
||||
</div>
|
||||
</>
|
||||
|
||||
64
web/src/pages/desktop/menu/settings/device/mdns.tsx
Normal file
64
web/src/pages/desktop/menu/settings/device/mdns.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Switch, Tooltip } from 'antd';
|
||||
import { CircleAlertIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/vm.ts';
|
||||
|
||||
export const Mdns = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isEnabled, setIsEnabled] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
|
||||
api
|
||||
.getMdnsState()
|
||||
.then((rsp) => {
|
||||
if (rsp.data?.enabled) {
|
||||
setIsEnabled(true);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
async function update() {
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
|
||||
const rsp = isEnabled ? await api.disableMdns() : await api.enableMdns();
|
||||
setIsLoading(false);
|
||||
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsEnabled(!isEnabled);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span>mDNS</span>
|
||||
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.mdns.description')}</span>
|
||||
<Tooltip
|
||||
title={t('settings.device.mdns.tip')}
|
||||
className="cursor-pointer text-neutral-500"
|
||||
placement="bottom"
|
||||
>
|
||||
<CircleAlertIcon size={16} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Switch checked={isEnabled} loading={isLoading} onChange={update} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -32,9 +32,6 @@ export const Settings = () => {
|
||||
|
||||
const [isUpdateAvailable, setIsUpdateAvailable] = useState(false);
|
||||
|
||||
const tooltip = t('settings.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
const tabs = [
|
||||
{ id: 'about', icon: <BadgeInfoIcon size={16} />, component: <About /> },
|
||||
{ id: 'appearance', icon: <PaletteIcon size={16} />, component: <Appearance /> },
|
||||
@@ -95,9 +92,9 @@ export const Settings = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<Tooltip title={t('settings.title')} placement="bottom" mouseEnterDelay={0.6}>
|
||||
<div
|
||||
className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-white hover:bg-neutral-700"
|
||||
className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-white hover:bg-neutral-700/80"
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
<Badge dot={isUpdateAvailable} color="blue" offset={[0, 2]}>
|
||||
@@ -115,7 +112,6 @@ export const Settings = () => {
|
||||
destroyOnClose={true}
|
||||
styles={{ content: { padding: 0 } }}
|
||||
onCancel={closeModal}
|
||||
afterOpenChange={(visible) => setTooltipValue(visible ? '' : tooltip)}
|
||||
>
|
||||
<div className="flex min-h-[500px] rounded-lg outline outline-1 outline-neutral-700">
|
||||
<div className="flex flex-col space-y-0.5 rounded-l-lg bg-neutral-800 py-5 sm:w-1/5 sm:px-2">
|
||||
|
||||
@@ -115,16 +115,18 @@ export const Update = ({ setIsLocked }: UpdateProps) => {
|
||||
|
||||
{status === 'failed' && <Result subTitle={errMsg} />}
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
href="https://github.com/sipeed/NanoKVM/blob/main/CHANGELOG.md"
|
||||
target="_blank"
|
||||
>
|
||||
CHANGELOG
|
||||
</Button>
|
||||
</div>
|
||||
{status !== 'loading' && (
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
href="https://github.com/sipeed/NanoKVM/blob/main/CHANGELOG.md"
|
||||
target="_blank"
|
||||
>
|
||||
CHANGELOG
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { useState } from 'react';
|
||||
import { Divider, Popover, Tooltip } from 'antd';
|
||||
import { Divider } from 'antd';
|
||||
import { SquareTerminalIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
import { Nanokvm } from './nanokvm';
|
||||
import { SerialPort } from './serial-port';
|
||||
|
||||
export const Terminal = () => {
|
||||
const { t } = useTranslation();
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const tooltip = t('terminal.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
const content = (
|
||||
<div className="min-w-[200px]">
|
||||
@@ -21,28 +18,16 @@ export const Terminal = () => {
|
||||
|
||||
<Divider style={{ margin: '10px 0 15px 0' }} />
|
||||
|
||||
<Nanokvm setIsPopoverOpen={setIsPopoverOpen} />
|
||||
<SerialPort setIsPopoverOpen={setIsPopoverOpen} />
|
||||
<Nanokvm />
|
||||
<SerialPort />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
<MenuItem
|
||||
title={t('terminal.title')}
|
||||
icon={<SquareTerminalIcon size={18} />}
|
||||
content={content}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
setIsPopoverOpen(visible);
|
||||
setTooltipValue(visible ? '' : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
|
||||
<SquareTerminalIcon size={18} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { SquareTerminalIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type NanokvmProps = {
|
||||
setIsPopoverOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const Nanokvm = ({ setIsPopoverOpen }: NanokvmProps) => {
|
||||
export const Nanokvm = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
function openTerminal() {
|
||||
setIsPopoverOpen(false);
|
||||
window.open('/#terminal', '_blank');
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
|
||||
|
||||
type SerialPortProps = {
|
||||
setIsPopoverOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const SerialPort = ({ setIsPopoverOpen }: SerialPortProps) => {
|
||||
export const SerialPort = () => {
|
||||
const { t } = useTranslation();
|
||||
const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom);
|
||||
|
||||
@@ -22,7 +18,6 @@ export const SerialPort = ({ setIsPopoverOpen }: SerialPortProps) => {
|
||||
setIsKeyboardEnable(false);
|
||||
|
||||
setIsModalOpen(true);
|
||||
setIsPopoverOpen(false);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import { Button, Divider, Input, List, Popover, Tooltip } from 'antd';
|
||||
import { Button, Divider, Input, List } from 'antd';
|
||||
import type { InputRef } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { NetworkIcon, SendIcon, Trash2Icon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import { deleteWolMac, getWolMacs, wol } from '@/api/network.ts';
|
||||
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
|
||||
import { MenuItem } from '@/components/menu-item.tsx';
|
||||
|
||||
export const Wol = () => {
|
||||
const { t } = useTranslation();
|
||||
const isBigScreen = useMediaQuery({ minWidth: 640 });
|
||||
|
||||
const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom);
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
const [status, setStatus] = useState('');
|
||||
const [log, setLog] = useState('');
|
||||
|
||||
const tooltip = t('wol.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
const [macList, setMacList] = useState<string[]>([]);
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
@@ -41,8 +35,6 @@ export const Wol = () => {
|
||||
|
||||
setIsKeyboardEnable(true);
|
||||
}
|
||||
|
||||
setIsPopoverOpen(open);
|
||||
}
|
||||
|
||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
@@ -150,22 +142,11 @@ export const Wol = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
<MenuItem
|
||||
title={t('wol.title')}
|
||||
icon={<NetworkIcon size={16} />}
|
||||
content={content}
|
||||
placement={isBigScreen ? 'bottomLeft' : 'bottom'}
|
||||
trigger="click"
|
||||
arrow={false}
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={(visible) => {
|
||||
handleOpenChange(visible);
|
||||
setTooltipValue(visible ? '' : tooltip);
|
||||
}}
|
||||
>
|
||||
<Tooltip title={tooltipValue} placement="bottom">
|
||||
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
|
||||
<NetworkIcon size={16} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
onOpenChange={handleOpenChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,10 +18,13 @@ export const H264 = () => {
|
||||
let heartbeatTimer: any;
|
||||
const videoElement = document.getElementById('screen') as HTMLVideoElement;
|
||||
|
||||
const url = `${getBaseUrl('ws')}/api/stream/h264`;
|
||||
const ws = new W3cWebSocket(url);
|
||||
|
||||
const pc = new RTCPeerConnection({
|
||||
iceServers: [
|
||||
{
|
||||
urls: ['stun:stun.l.google.com:19302', 'stun:turn.cloudflare.com:3478']
|
||||
urls: ['stun:stun.l.google.com:19302']
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -34,9 +37,6 @@ export const H264 = () => {
|
||||
videoElement.srcObject = event.streams[0];
|
||||
};
|
||||
|
||||
const url = `${getBaseUrl('ws')}/api/stream/h264`;
|
||||
const ws = new W3cWebSocket(url);
|
||||
|
||||
ws.onopen = () => {
|
||||
pc.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
|
||||
Reference in New Issue
Block a user