mirror of https://github.com/jetkvm/kvm.git
Compare commits
23 Commits
cfd9d3f62b
...
fe91742c2b
| Author | SHA1 | Date |
|---|---|---|
|
|
fe91742c2b | |
|
|
c61ba677e2 | |
|
|
90621a2fec | |
|
|
5b0c27b696 | |
|
|
7290171c35 | |
|
|
e161339dcf | |
|
|
ad9f079b2b | |
|
|
ec626f9d3c | |
|
|
b144d9926f | |
|
|
e755a6e1b1 | |
|
|
99a8c2711c | |
|
|
317218a682 | |
|
|
62b8dee170 | |
|
|
bdd6f4247b | |
|
|
63aa940f42 | |
|
|
043ef9ddfc | |
|
|
437f0b854a | |
|
|
a45d55123c | |
|
|
213e750e04 | |
|
|
6dcb0286e3 | |
|
|
74ccca0b1a | |
|
|
0ad435475b | |
|
|
23bf3978fa |
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "JetKVM",
|
"name": "JetKVM docker devcontainer",
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
|
@ -33,3 +33,4 @@ wget https://github.com/jetkvm/rv1106-system/releases/download/${BUILDKIT_VERSIO
|
||||||
sudo tar --use-compress-program="unzstd --long=31" -xvf buildkit.tar.zst -C /opt/jetkvm-native-buildkit && \
|
sudo tar --use-compress-program="unzstd --long=31" -xvf buildkit.tar.zst -C /opt/jetkvm-native-buildkit && \
|
||||||
rm buildkit.tar.zst
|
rm buildkit.tar.zst
|
||||||
popd
|
popd
|
||||||
|
rm -rf "${BUILDKIT_TMPDIR}"
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "JetKVM podman devcontainer",
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
// Should match what is defined in ui/package.json
|
||||||
|
"version": "22.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runArgs": [
|
||||||
|
"--userns=keep-id",
|
||||||
|
"--security-opt=label=disable",
|
||||||
|
"--security-opt=label=nested"
|
||||||
|
],
|
||||||
|
"containerUser": "vscode",
|
||||||
|
"containerEnv": {
|
||||||
|
"HOME": "/home/vscode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ jobs:
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
cache-dependency-path: "**/package-lock.json"
|
cache-dependency-path: "**/package-lock.json"
|
||||||
- name: Set up Golang
|
- name: Set up Golang
|
||||||
uses: actions/setup-go@v6.0.0
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "^1.25.1"
|
go-version: "^1.25.1"
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: oldstable
|
go-version: oldstable
|
||||||
- name: Create empty resource directory
|
- name: Create empty resource directory
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ jobs:
|
||||||
EOF
|
EOF
|
||||||
ssh jkci "cat /tmp/device-tests.json" > device-tests.json
|
ssh jkci "cat /tmp/device-tests.json" > device-tests.json
|
||||||
- name: Set up Golang
|
- name: Set up Golang
|
||||||
uses: actions/setup-go@v5.5.0
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.4"
|
go-version: "1.24.4"
|
||||||
- name: Golang Test Report
|
- name: Golang Test Report
|
||||||
|
|
|
||||||
|
|
@ -97,21 +97,38 @@ tail -f /var/log/jetkvm.log
|
||||||
|
|
||||||
```
|
```
|
||||||
/kvm/
|
/kvm/
|
||||||
├── main.go # App entry point
|
├── main.go # App entry point
|
||||||
├── config.go # Settings & configuration
|
├── config.go # Settings & configuration
|
||||||
├── web.go # API endpoints
|
├── display.go # Device UI control
|
||||||
├── ui/ # React frontend
|
├── web.go # API endpoints
|
||||||
│ ├── src/routes/ # Pages (login, settings, etc.)
|
├── cmd/ # Command line main
|
||||||
│ └── src/components/ # UI components
|
├── internal/ # Internal Go packages
|
||||||
├── internal/ # Internal Go packages
|
│ ├── confparser/ # Configuration file implementation
|
||||||
│ ├── native/ # CGO / Native code glue layer
|
│ ├── hidrpc/ # HIDRPC implementation for HID devices (keyboard, mouse, etc.)
|
||||||
│ ├── native/cgo/ # C files for the native library (HDMI, Touchscreen, etc.)
|
│ ├── logging/ # Logging implementation
|
||||||
│ ├── native/eez/ # EEZ Studio Project files (for Touchscreen)
|
│ ├── mdns/ # mDNS implementation
|
||||||
│ ├── hidrpc/ # HIDRPC implementation for HID devices (keyboard, mouse, etc.)
|
│ ├── native/ # CGO / Native code glue layer (on-device hardware)
|
||||||
│ ├── logging/ # Logging implementation
|
│ │ ├── cgo/ # C files for the native library (HDMI, Touchscreen, etc.)
|
||||||
│ ├── usbgadget/ # USB gadget
|
│ │ └── eez/ # EEZ Studio Project files (for Touchscreen)
|
||||||
│ └── websecurity/ # TLS certificate management
|
│ ├── network/ # Network implementation
|
||||||
└── resource # netboot iso and other resources
|
│ ├── timesync/ # Time sync/NTP implementation
|
||||||
|
│ ├── tzdata/ # Timezone data and generation
|
||||||
|
│ ├── udhcpc/ # DHCP implementation
|
||||||
|
│ ├── usbgadget/ # USB gadget
|
||||||
|
│ ├── utils/ # SSH handling
|
||||||
|
│ └── websecure/ # TLS certificate management
|
||||||
|
├── resource/ # netboot iso and other resources
|
||||||
|
├── scripts/ # Bash shell scripts for building and deploying
|
||||||
|
└── static/ # (react client build output)
|
||||||
|
└── ui/ # React frontend
|
||||||
|
├── public/ # UI website static images and fonts
|
||||||
|
└── src/ # Client React UI
|
||||||
|
├── assets/ # UI in-page images
|
||||||
|
├── components/ # UI components
|
||||||
|
├── hooks/ # Hooks (stores, RPC handling, virtual devices)
|
||||||
|
├── keyboardLayouts/ # Keyboard layout definitions
|
||||||
|
├── providers/ # Feature flags
|
||||||
|
└── routes/ # Pages (login, settings, etc.)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Key files for beginners:**
|
**Key files for beginners:**
|
||||||
|
|
@ -252,6 +269,47 @@ rm -rf node_modules
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### "Device UI Fails to Build"
|
||||||
|
|
||||||
|
If while trying to build you run into an error message similar to :
|
||||||
|
```plaintext
|
||||||
|
In file included from /workspaces/kvm/internal/native/cgo/ctrl.c:15:
|
||||||
|
/workspaces/kvm/internal/native/cgo/ui_index.h:4:10: fatal error: ui/ui.h: No such file or directory
|
||||||
|
#include "ui/ui.h"
|
||||||
|
^~~~~~~~~
|
||||||
|
compilation terminated.
|
||||||
|
```
|
||||||
|
This means that your system didn't create the directory-link to from _./internal/native/cgo/ui_ to ./internal/native/eez/src/ui when the repository was checked out. You can verify this is the case if _./internal/native/cgo/ui_ appears as a plain text file with only the textual contents:
|
||||||
|
```plaintext
|
||||||
|
../eez/src/ui
|
||||||
|
```
|
||||||
|
|
||||||
|
If this happens to you need to [enable git creation of symbolic links](https://stackoverflow.com/a/59761201/2076) either globally or for the KVM repository:
|
||||||
|
```bash
|
||||||
|
# Globally enable git to create symlinks
|
||||||
|
git config --global core.symlinks true
|
||||||
|
git restore internal/native/cgo/ui
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
# Enable git to create symlinks only in this project
|
||||||
|
git config core.symlinks true
|
||||||
|
git restore internal/native/cgo/ui
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you want to manually create the symlink use:
|
||||||
|
```bash
|
||||||
|
# linux
|
||||||
|
cd internal/native/cgo
|
||||||
|
rm ui
|
||||||
|
ln -s ../eez/src/ui ui
|
||||||
|
```
|
||||||
|
```dos
|
||||||
|
rem Windows
|
||||||
|
cd internal/native/cgo
|
||||||
|
del ui
|
||||||
|
mklink /d ui ..\eez\src\ui
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
|
||||||
39
go.mod
39
go.mod
|
|
@ -5,13 +5,14 @@ go 1.24.4
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.4.0
|
github.com/Masterminds/semver/v3 v3.4.0
|
||||||
github.com/beevik/ntp v1.4.3
|
github.com/beevik/ntp v1.4.3
|
||||||
github.com/coder/websocket v1.8.13
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/coreos/go-oidc/v3 v3.15.0
|
github.com/coreos/go-oidc/v3 v3.15.0
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
|
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/gin-contrib/logger v1.2.6
|
github.com/gin-contrib/logger v1.2.6
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/go-co-op/gocron/v2 v2.16.5
|
github.com/go-co-op/gocron/v2 v2.16.6
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/guregu/null/v6 v6.0.0
|
github.com/guregu/null/v6 v6.0.0
|
||||||
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f
|
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f
|
||||||
|
|
@ -19,8 +20,8 @@ require (
|
||||||
github.com/pion/mdns/v2 v2.0.7
|
github.com/pion/mdns/v2 v2.0.7
|
||||||
github.com/pion/webrtc/v4 v4.1.4
|
github.com/pion/webrtc/v4 v4.1.4
|
||||||
github.com/pojntfx/go-nbd v0.3.2
|
github.com/pojntfx/go-nbd v0.3.2
|
||||||
github.com/prometheus/client_golang v1.23.0
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/prometheus/common v0.66.0
|
github.com/prometheus/common v0.66.1
|
||||||
github.com/prometheus/procfs v0.17.0
|
github.com/prometheus/procfs v0.17.0
|
||||||
github.com/psanford/httpreadat v0.1.0
|
github.com/psanford/httpreadat v0.1.0
|
||||||
github.com/rs/xid v1.6.0
|
github.com/rs/xid v1.6.0
|
||||||
|
|
@ -30,37 +31,31 @@ require (
|
||||||
github.com/vearutop/statigz v1.5.0
|
github.com/vearutop/statigz v1.5.0
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
go.bug.st/serial v1.6.4
|
go.bug.st/serial v1.6.4
|
||||||
golang.org/x/crypto v0.41.0
|
golang.org/x/crypto v0.42.0
|
||||||
golang.org/x/net v0.43.0
|
golang.org/x/net v0.44.0
|
||||||
golang.org/x/sys v0.35.0
|
golang.org/x/sys v0.36.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b
|
replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bytedance/sonic v1.13.3 // indirect
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/creack/goselect v0.1.2 // indirect
|
github.com/creack/goselect v0.1.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
|
|
||||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||||
github.com/jpillora/overseer v1.1.6 // indirect
|
|
||||||
github.com/jpillora/s3 v1.1.4 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
|
@ -90,10 +85,10 @@ require (
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
golang.org/x/arch v0.18.0 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
golang.org/x/oauth2 v0.30.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.8 // indirect
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
86
go.sum
86
go.sum
|
|
@ -1,7 +1,5 @@
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||||
|
|
@ -10,20 +8,18 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE=
|
github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE=
|
||||||
github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
|
github.com/bool64/dev v0.2.39/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
|
||||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b h1:dSbDgy72Y1sjLPWLv7vs0fMFuhMBMViiT9PJZiZWZNs=
|
github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b h1:dSbDgy72Y1sjLPWLv7vs0fMFuhMBMViiT9PJZiZWZNs=
|
||||||
github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b/go.mod h1:SehHnbi2e8NiSAKby42Itm8SIoS7b+wAprsfPH3qgYk=
|
github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b/go.mod h1:SehHnbi2e8NiSAKby42Itm8SIoS7b+wAprsfPH3qgYk=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
|
||||||
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
|
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
|
||||||
github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
|
@ -46,20 +42,18 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-co-op/gocron/v2 v2.16.5 h1:j228Jxk7bb9CF8LKR3gS+bK3rcjRUINjlVI+ZMp26Ss=
|
github.com/go-co-op/gocron/v2 v2.16.6 h1:zI2Ya9sqvuLcgqJgV79LwoJXM8h20Z/drtB7ATbpRWo=
|
||||||
github.com/go-co-op/gocron/v2 v2.16.5/go.mod h1:zAfC/GFQ668qHxOVl/D68Jh5Ce7sDqX6TJnSQyRkRBc=
|
github.com/go-co-op/gocron/v2 v2.16.6/go.mod h1:zAfC/GFQ668qHxOVl/D68Jh5Ce7sDqX6TJnSQyRkRBc=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
|
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
|
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
|
@ -68,26 +62,18 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
|
|
||||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
|
|
||||||
github.com/guregu/null/v6 v6.0.0 h1:N14VRS+4di81i1PXRiprbQJ9EM9gqBa0+KVMeS/QSjQ=
|
github.com/guregu/null/v6 v6.0.0 h1:N14VRS+4di81i1PXRiprbQJ9EM9gqBa0+KVMeS/QSjQ=
|
||||||
github.com/guregu/null/v6 v6.0.0/go.mod h1:hrMIhIfrOZeLPZhROSn149tpw2gHkidAqxoXNyeX3iQ=
|
github.com/guregu/null/v6 v6.0.0/go.mod h1:hrMIhIfrOZeLPZhROSn149tpw2gHkidAqxoXNyeX3iQ=
|
||||||
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f h1:08t2PbrkDgW2+mwCQ3jhKUBrCM9Bc9SeH5j2Dst3B+0=
|
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f h1:08t2PbrkDgW2+mwCQ3jhKUBrCM9Bc9SeH5j2Dst3B+0=
|
||||||
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
|
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
|
||||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
github.com/jpillora/overseer v1.1.6 h1:3ygYfNcR3FfOr22miu3vR1iQcXKMHbmULBh98rbkIyo=
|
|
||||||
github.com/jpillora/overseer v1.1.6/go.mod h1:aPXQtxuVb9PVWRWTXpo+LdnC/YXQ0IBLNXqKMJmgk88=
|
|
||||||
github.com/jpillora/s3 v1.1.4 h1:YCCKDWzb/Ye9EBNd83ATRF/8wPEy0xd43Rezb6u6fzc=
|
|
||||||
github.com/jpillora/s3 v1.1.4/go.mod h1:yedE603V+crlFi1Kl/5vZJaBu9pUzE9wvKegU/lF2zs=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
|
@ -152,12 +138,12 @@ github.com/pion/webrtc/v4 v4.1.4/go.mod h1:Oab9npu1iZtQRMic3K3toYq5zFPvToe/QBw7d
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4=
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||||
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
|
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
|
||||||
|
|
@ -170,15 +156,12 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
|
||||||
github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
|
|
||||||
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f h1:VgoRCP1efSCEZIcF2THLQ46+pIBzzgNiaUBe9wEDwYU=
|
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f h1:VgoRCP1efSCEZIcF2THLQ46+pIBzzgNiaUBe9wEDwYU=
|
||||||
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f/go.mod h1:pzro7BGorij2WgrjEammtrkbo3+xldxo+KaGLGUiD+Q=
|
github.com/sourcegraph/tf-dag v0.2.2-0.20250131204052-3e8ff1477b4f/go.mod h1:pzro7BGorij2WgrjEammtrkbo3+xldxo+KaGLGUiD+Q=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
|
@ -200,12 +183,14 @@ go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
|
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||||
|
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
@ -213,20 +198,17 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,6 @@ func uiInit(rotation uint16) {
|
||||||
defer cgoLock.Unlock()
|
defer cgoLock.Unlock()
|
||||||
|
|
||||||
cRotation := C.u_int16_t(rotation)
|
cRotation := C.u_int16_t(rotation)
|
||||||
defer C.free(unsafe.Pointer(&cRotation))
|
|
||||||
|
|
||||||
C.jetkvm_ui_init(cRotation)
|
C.jetkvm_ui_init(cRotation)
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +349,6 @@ func uiDispSetRotation(rotation uint16) (bool, error) {
|
||||||
nativeLogger.Info().Uint16("rotation", rotation).Msg("setting rotation")
|
nativeLogger.Info().Uint16("rotation", rotation).Msg("setting rotation")
|
||||||
|
|
||||||
cRotation := C.u_int16_t(rotation)
|
cRotation := C.u_int16_t(rotation)
|
||||||
defer C.free(unsafe.Pointer(&cRotation))
|
|
||||||
|
|
||||||
C.jetkvm_ui_set_rotation(cRotation)
|
C.jetkvm_ui_set_rotation(cRotation)
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
|
||||||
27
main.go
27
main.go
|
|
@ -14,6 +14,7 @@ import (
|
||||||
var appCtx context.Context
|
var appCtx context.Context
|
||||||
|
|
||||||
func Main() {
|
func Main() {
|
||||||
|
logger.Log().Msg("JetKVM Starting Up")
|
||||||
LoadConfig()
|
LoadConfig()
|
||||||
|
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
|
|
@ -78,16 +79,16 @@ func Main() {
|
||||||
initDisplay()
|
initDisplay()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
// wait for 15 minutes before starting auto-update checks
|
||||||
|
// this is to avoid interfering with initial setup processes
|
||||||
|
// and to ensure the system is stable before checking for updates
|
||||||
time.Sleep(15 * time.Minute)
|
time.Sleep(15 * time.Minute)
|
||||||
for {
|
|
||||||
logger.Debug().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("UPDATING")
|
|
||||||
if !config.AutoUpdateEnabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
|
for {
|
||||||
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
|
logger.Info().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("auto-update check")
|
||||||
time.Sleep(30 * time.Second)
|
if !config.AutoUpdateEnabled {
|
||||||
|
logger.Debug().Msg("auto-update disabled")
|
||||||
|
time.Sleep(5 * time.Minute) // we'll check if auto-updates are enabled in five minutes
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,6 +98,12 @@ func Main() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
|
||||||
|
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
includePreRelease := config.IncludePreRelease
|
includePreRelease := config.IncludePreRelease
|
||||||
err = TryUpdate(context.Background(), GetDeviceID(), includePreRelease)
|
err = TryUpdate(context.Background(), GetDeviceID(), includePreRelease)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -106,6 +113,7 @@ func Main() {
|
||||||
time.Sleep(1 * time.Hour)
|
time.Sleep(1 * time.Hour)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
//go RunFuseServer()
|
//go RunFuseServer()
|
||||||
go RunWebServer()
|
go RunWebServer()
|
||||||
|
|
||||||
|
|
@ -122,7 +130,8 @@ func Main() {
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-sigs
|
<-sigs
|
||||||
logger.Info().Msg("JetKVM Shutting Down")
|
|
||||||
|
logger.Log().Msg("JetKVM Shutting Down")
|
||||||
//if fuseServer != nil {
|
//if fuseServer != nil {
|
||||||
// err := setMassStorageImage(" ")
|
// err := setMassStorageImage(" ")
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
|
|
||||||
47
ota.go
47
ota.go
|
|
@ -176,7 +176,7 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress
|
||||||
if nr > 0 {
|
if nr > 0 {
|
||||||
nw, ew := file.Write(buf[0:nr])
|
nw, ew := file.Write(buf[0:nr])
|
||||||
if nw < nr {
|
if nw < nr {
|
||||||
return fmt.Errorf("short write: %d < %d", nw, nr)
|
return fmt.Errorf("short file write: %d < %d", nw, nr)
|
||||||
}
|
}
|
||||||
written += int64(nw)
|
written += int64(nw)
|
||||||
if ew != nil {
|
if ew != nil {
|
||||||
|
|
@ -240,7 +240,7 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
|
||||||
if nr > 0 {
|
if nr > 0 {
|
||||||
nw, ew := hash.Write(buf[0:nr])
|
nw, ew := hash.Write(buf[0:nr])
|
||||||
if nw < nr {
|
if nw < nr {
|
||||||
return fmt.Errorf("short write: %d < %d", nw, nr)
|
return fmt.Errorf("short hash write: %d < %d", nw, nr)
|
||||||
}
|
}
|
||||||
verified += int64(nw)
|
verified += int64(nw)
|
||||||
if ew != nil {
|
if ew != nil {
|
||||||
|
|
@ -260,11 +260,16 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hashSum := hash.Sum(nil)
|
// close the file so we can rename below
|
||||||
scopedLogger.Info().Str("path", path).Str("hash", hex.EncodeToString(hashSum)).Msg("SHA256 hash of")
|
if err := fileToHash.Close(); err != nil {
|
||||||
|
return fmt.Errorf("error closing file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if hex.EncodeToString(hashSum) != expectedHash {
|
hashSum := hex.EncodeToString(hash.Sum(nil))
|
||||||
return fmt.Errorf("hash mismatch: %x != %s", hashSum, expectedHash)
|
scopedLogger.Info().Str("path", path).Str("hash", hashSum).Msg("SHA256 hash of")
|
||||||
|
|
||||||
|
if hashSum != expectedHash {
|
||||||
|
return fmt.Errorf("hash mismatch: %s != %s", hashSum, expectedHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(unverifiedPath, path); err != nil {
|
if err := os.Rename(unverifiedPath, path); err != nil {
|
||||||
|
|
@ -296,6 +301,8 @@ type OTAState struct {
|
||||||
AppUpdatedAt *time.Time `json:"appUpdatedAt,omitempty"`
|
AppUpdatedAt *time.Time `json:"appUpdatedAt,omitempty"`
|
||||||
SystemUpdateProgress float32 `json:"systemUpdateProgress,omitempty"` //TODO: port rk_ota, then implement
|
SystemUpdateProgress float32 `json:"systemUpdateProgress,omitempty"` //TODO: port rk_ota, then implement
|
||||||
SystemUpdatedAt *time.Time `json:"systemUpdatedAt,omitempty"`
|
SystemUpdatedAt *time.Time `json:"systemUpdatedAt,omitempty"`
|
||||||
|
RebootNeeded bool `json:"rebootNeeded,omitempty"`
|
||||||
|
Rebooting bool `json:"rebooting,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var otaState = OTAState{}
|
var otaState = OTAState{}
|
||||||
|
|
@ -313,7 +320,7 @@ func triggerOTAStateUpdate() {
|
||||||
func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) error {
|
func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) error {
|
||||||
scopedLogger := otaLogger.With().
|
scopedLogger := otaLogger.With().
|
||||||
Str("deviceId", deviceId).
|
Str("deviceId", deviceId).
|
||||||
Str("includePreRelease", fmt.Sprintf("%v", includePreRelease)).
|
Bool("includePreRelease", includePreRelease).
|
||||||
Logger()
|
Logger()
|
||||||
|
|
||||||
scopedLogger.Info().Msg("Trying to update...")
|
scopedLogger.Info().Msg("Trying to update...")
|
||||||
|
|
@ -322,7 +329,8 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
}
|
}
|
||||||
|
|
||||||
otaState = OTAState{
|
otaState = OTAState{
|
||||||
Updating: true,
|
Updating: true,
|
||||||
|
RebootNeeded: false,
|
||||||
}
|
}
|
||||||
triggerOTAStateUpdate()
|
triggerOTAStateUpdate()
|
||||||
|
|
||||||
|
|
@ -335,7 +343,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
otaState.Error = fmt.Sprintf("Error checking for updates: %v", err)
|
otaState.Error = fmt.Sprintf("Error checking for updates: %v", err)
|
||||||
scopedLogger.Error().Err(err).Msg("Error checking for updates")
|
scopedLogger.Error().Err(err).Msg("Error checking for updates")
|
||||||
return fmt.Errorf("error checking for updates: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
@ -349,8 +357,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
appUpdateAvailable := updateStatus.AppUpdateAvailable
|
appUpdateAvailable := updateStatus.AppUpdateAvailable
|
||||||
systemUpdateAvailable := updateStatus.SystemUpdateAvailable
|
systemUpdateAvailable := updateStatus.SystemUpdateAvailable
|
||||||
|
|
||||||
rebootNeeded := false
|
|
||||||
|
|
||||||
if appUpdateAvailable {
|
if appUpdateAvailable {
|
||||||
scopedLogger.Info().
|
scopedLogger.Info().
|
||||||
Str("local", local.AppVersion).
|
Str("local", local.AppVersion).
|
||||||
|
|
@ -361,7 +367,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
otaState.Error = fmt.Sprintf("Error downloading app update: %v", err)
|
otaState.Error = fmt.Sprintf("Error downloading app update: %v", err)
|
||||||
scopedLogger.Error().Err(err).Msg("Error downloading app update")
|
scopedLogger.Error().Err(err).Msg("Error downloading app update")
|
||||||
triggerOTAStateUpdate()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
downloadFinished := time.Now()
|
downloadFinished := time.Now()
|
||||||
|
|
@ -378,18 +383,20 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
otaState.Error = fmt.Sprintf("Error verifying app update hash: %v", err)
|
otaState.Error = fmt.Sprintf("Error verifying app update hash: %v", err)
|
||||||
scopedLogger.Error().Err(err).Msg("Error verifying app update hash")
|
scopedLogger.Error().Err(err).Msg("Error verifying app update hash")
|
||||||
triggerOTAStateUpdate()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
verifyFinished := time.Now()
|
verifyFinished := time.Now()
|
||||||
otaState.AppVerifiedAt = &verifyFinished
|
otaState.AppVerifiedAt = &verifyFinished
|
||||||
otaState.AppVerificationProgress = 1
|
otaState.AppVerificationProgress = 1
|
||||||
|
triggerOTAStateUpdate()
|
||||||
|
|
||||||
otaState.AppUpdatedAt = &verifyFinished
|
otaState.AppUpdatedAt = &verifyFinished
|
||||||
otaState.AppUpdateProgress = 1
|
otaState.AppUpdateProgress = 1
|
||||||
triggerOTAStateUpdate()
|
triggerOTAStateUpdate()
|
||||||
|
|
||||||
scopedLogger.Info().Msg("App update downloaded")
|
scopedLogger.Info().Msg("App update downloaded")
|
||||||
rebootNeeded = true
|
otaState.RebootNeeded = true
|
||||||
|
triggerOTAStateUpdate()
|
||||||
} else {
|
} else {
|
||||||
scopedLogger.Info().Msg("App is up to date")
|
scopedLogger.Info().Msg("App is up to date")
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +411,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
otaState.Error = fmt.Sprintf("Error downloading system update: %v", err)
|
otaState.Error = fmt.Sprintf("Error downloading system update: %v", err)
|
||||||
scopedLogger.Error().Err(err).Msg("Error downloading system update")
|
scopedLogger.Error().Err(err).Msg("Error downloading system update")
|
||||||
triggerOTAStateUpdate()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
downloadFinished := time.Now()
|
downloadFinished := time.Now()
|
||||||
|
|
@ -421,7 +427,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
otaState.Error = fmt.Sprintf("Error verifying system update hash: %v", err)
|
otaState.Error = fmt.Sprintf("Error verifying system update hash: %v", err)
|
||||||
scopedLogger.Error().Err(err).Msg("Error verifying system update hash")
|
scopedLogger.Error().Err(err).Msg("Error verifying system update hash")
|
||||||
triggerOTAStateUpdate()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
scopedLogger.Info().Msg("System update downloaded")
|
scopedLogger.Info().Msg("System update downloaded")
|
||||||
|
|
@ -441,6 +446,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
scopedLogger.Error().Err(err).Msg("Error starting rk_ota command")
|
scopedLogger.Error().Err(err).Msg("Error starting rk_ota command")
|
||||||
return fmt.Errorf("error starting rk_ota command: %w", err)
|
return fmt.Errorf("error starting rk_ota command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -481,14 +487,19 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
||||||
otaState.SystemUpdateProgress = 1
|
otaState.SystemUpdateProgress = 1
|
||||||
otaState.SystemUpdatedAt = &verifyFinished
|
otaState.SystemUpdatedAt = &verifyFinished
|
||||||
triggerOTAStateUpdate()
|
triggerOTAStateUpdate()
|
||||||
rebootNeeded = true
|
|
||||||
|
otaState.RebootNeeded = true
|
||||||
|
triggerOTAStateUpdate()
|
||||||
} else {
|
} else {
|
||||||
scopedLogger.Info().Msg("System is up to date")
|
scopedLogger.Info().Msg("System is up to date")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rebootNeeded {
|
if otaState.RebootNeeded {
|
||||||
scopedLogger.Info().Msg("System Rebooting in 10s")
|
scopedLogger.Info().Msg("System Rebooting in 10s")
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
|
otaState.Rebooting = true
|
||||||
|
triggerOTAStateUpdate()
|
||||||
|
|
||||||
cmd := exec.Command("reboot")
|
cmd := exec.Command("reboot")
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "kvm-ui",
|
"name": "kvm-ui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2025.09.26.01300",
|
"version": "2025.10.01.1900",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^22.15.0"
|
"node": "^22.15.0"
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-router": "^7.9.3",
|
"react-router": "^7.9.3",
|
||||||
"react-simple-keyboard": "^3.8.122",
|
"react-simple-keyboard": "^3.8.125",
|
||||||
"react-use-websocket": "^4.13.0",
|
"react-use-websocket": "^4.13.0",
|
||||||
"react-xtermjs": "^1.0.10",
|
"react-xtermjs": "^1.0.10",
|
||||||
"recharts": "^3.2.1",
|
"recharts": "^3.2.1",
|
||||||
|
|
@ -56,15 +56,15 @@
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/postcss": "^4.1.13",
|
"@tailwindcss/postcss": "^4.1.14",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"@types/react": "^19.1.14",
|
"@types/react": "^19.1.17",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.1.10",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"@types/validator": "^13.15.3",
|
"@types/validator": "^13.15.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
||||||
"@typescript-eslint/parser": "^8.44.1",
|
"@typescript-eslint/parser": "^8.45.0",
|
||||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"eslint": "^9.36.0",
|
"eslint": "^9.36.0",
|
||||||
|
|
@ -77,8 +77,8 @@
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.14",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.7",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
|
||||||
Button.displayName = "Button";
|
Button.displayName = "Button";
|
||||||
|
|
||||||
type LinkPropsType = Pick<LinkProps, "to"> &
|
type LinkPropsType = Pick<LinkProps, "to"> &
|
||||||
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean };
|
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean, reloadDocument?: boolean };
|
||||||
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
||||||
const classes = cx(
|
const classes = cx(
|
||||||
"group outline-hidden",
|
"group outline-hidden",
|
||||||
|
|
@ -231,7 +231,7 @@ export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Link to={to} className={classes}>
|
<Link to={to} reloadDocument={props.reloadDocument} className={classes}>
|
||||||
<ButtonContent {...props} />
|
<ButtonContent {...props} />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -518,6 +518,7 @@ export type UpdateModalViews =
|
||||||
| "upToDate"
|
| "upToDate"
|
||||||
| "updateAvailable"
|
| "updateAvailable"
|
||||||
| "updateCompleted"
|
| "updateCompleted"
|
||||||
|
| "rebooting"
|
||||||
| "error";
|
| "error";
|
||||||
|
|
||||||
export interface OtaState {
|
export interface OtaState {
|
||||||
|
|
@ -549,19 +550,26 @@ export interface OtaState {
|
||||||
|
|
||||||
systemUpdateProgress: number;
|
systemUpdateProgress: number;
|
||||||
systemUpdatedAt: string | null;
|
systemUpdatedAt: string | null;
|
||||||
|
|
||||||
|
rebootNeeded: boolean;
|
||||||
|
rebooting: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UpdateState {
|
export interface UpdateState {
|
||||||
isUpdatePending: boolean;
|
isUpdatePending: boolean;
|
||||||
setIsUpdatePending: (isPending: boolean) => void;
|
setIsUpdatePending: (isPending: boolean) => void;
|
||||||
|
|
||||||
updateDialogHasBeenMinimized: boolean;
|
updateDialogHasBeenMinimized: boolean;
|
||||||
|
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
|
||||||
|
|
||||||
otaState: OtaState;
|
otaState: OtaState;
|
||||||
setOtaState: (state: OtaState) => void;
|
setOtaState: (state: OtaState) => void;
|
||||||
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
|
|
||||||
modalView: UpdateModalViews
|
modalView: UpdateModalViews
|
||||||
setModalView: (view: UpdateModalViews) => void;
|
setModalView: (view: UpdateModalViews) => void;
|
||||||
setUpdateErrorMessage: (errorMessage: string) => void;
|
|
||||||
updateErrorMessage: string | null;
|
updateErrorMessage: string | null;
|
||||||
|
setUpdateErrorMessage: (errorMessage: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUpdateStore = create<UpdateState>(set => ({
|
export const useUpdateStore = create<UpdateState>(set => ({
|
||||||
|
|
@ -587,13 +595,17 @@ export const useUpdateStore = create<UpdateState>(set => ({
|
||||||
appUpdatedAt: null,
|
appUpdatedAt: null,
|
||||||
systemUpdateProgress: 0,
|
systemUpdateProgress: 0,
|
||||||
systemUpdatedAt: null,
|
systemUpdatedAt: null,
|
||||||
|
rebootNeeded: false,
|
||||||
|
rebooting: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDialogHasBeenMinimized: false,
|
updateDialogHasBeenMinimized: false,
|
||||||
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) =>
|
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) =>
|
||||||
set({ updateDialogHasBeenMinimized: hasBeenMinimized }),
|
set({ updateDialogHasBeenMinimized: hasBeenMinimized }),
|
||||||
|
|
||||||
modalView: "loading",
|
modalView: "loading",
|
||||||
setModalView: (view: UpdateModalViews) => set({ modalView: view }),
|
setModalView: (view: UpdateModalViews) => set({ modalView: view }),
|
||||||
|
|
||||||
updateErrorMessage: null,
|
updateErrorMessage: null,
|
||||||
setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
|
setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -153,13 +153,13 @@ body {
|
||||||
|
|
||||||
@property --grid-color-start {
|
@property --grid-color-start {
|
||||||
syntax: "<color>";
|
syntax: "<color>";
|
||||||
initial-value: var(--color-blue-50/10);
|
initial-value: oklch(97% 0.014 254.604 / 10); /* var(--color-blue-50/10) */
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property --grid-color-end {
|
@property --grid-color-end {
|
||||||
syntax: "<color>";
|
syntax: "<color>";
|
||||||
initial-value: var(--color-blue-50/100);
|
initial-value: oklch(97% 0.014 254.604 / 100); /* var(--color-blue-50/100) */
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,8 +175,8 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.group:hover .grid-card {
|
.group:hover .grid-card {
|
||||||
--grid-color-start: var(--color-blue-100/50);
|
--grid-color-start: oklch(from var(--color-blue-100) l c h / 50);
|
||||||
--grid-color-end: var(--color-blue-50/50);
|
--grid-color-end: oklch(from var(--color-blue-50) l c h / 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
video::-webkit-media-controls {
|
video::-webkit-media-controls {
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ export async function checkDeviceAuth() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkAuth() {
|
export async function checkAuth() {
|
||||||
return import.meta.env.MODE === "device" ? checkDeviceAuth() : checkCloudAuth();
|
return isOnDevice ? checkDeviceAuth() : checkCloudAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
let router;
|
let router;
|
||||||
|
|
|
||||||
|
|
@ -374,8 +374,8 @@ function UrlView({
|
||||||
icon: FedoraIcon,
|
icon: FedoraIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "openSUSE Leap 15.6",
|
name: "openSUSE Leap 16.0",
|
||||||
url: "https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso",
|
url: "https://download.opensuse.org/distribution/leap/16.0/offline/Leap-16.0-online-installer-x86_64.install.iso",
|
||||||
icon: OpenSUSEIcon,
|
icon: OpenSUSEIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ export default function SettingsAccessIndexRoute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
getCloudState();
|
getCloudState();
|
||||||
// In cloud mode, we need to navigate to the device overview page, as we don't a connection anymore
|
// In cloud mode, we need to navigate to the device overview page, as we don't have a connection anymore
|
||||||
if (!isOnDevice) navigate("/");
|
if (!isOnDevice) navigate("/");
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@ export default function SettingsGeneralRebootRoute() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
|
|
||||||
|
const onClose = useCallback(() => {
|
||||||
|
navigate(".."); // back to the devices.$id.settings page
|
||||||
|
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
|
||||||
const onConfirmUpdate = useCallback(() => {
|
const onConfirmUpdate = useCallback(() => {
|
||||||
// This is where we send the RPC to the golang binary
|
// This is where we send the RPC to the golang binary
|
||||||
send("reboot", {force: true});
|
send("reboot", {force: true});
|
||||||
|
|
@ -16,7 +22,7 @@ export default function SettingsGeneralRebootRoute() {
|
||||||
{
|
{
|
||||||
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
||||||
}
|
}
|
||||||
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
|
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Dialog({
|
export function Dialog({
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { useLocation, useNavigate } from "react-router";
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { useLocation, useNavigate } from "react-router";
|
||||||
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
||||||
|
import { MdConnectWithoutContact, MdRestartAlt } from "react-icons/md";
|
||||||
|
|
||||||
import Card from "@/components/Card";
|
import Card from "@/components/Card";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { Button } from "@components/Button";
|
import { Button, LinkButton } from "@components/Button";
|
||||||
import { UpdateState, useUpdateStore } from "@/hooks/stores";
|
import { UpdateState, useUpdateStore } from "@/hooks/stores";
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||||
|
|
@ -18,6 +19,11 @@ export default function SettingsGeneralUpdateRoute() {
|
||||||
const { setModalView, otaState } = useUpdateStore();
|
const { setModalView, otaState } = useUpdateStore();
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
|
|
||||||
|
const onClose = useCallback(() => {
|
||||||
|
navigate(".."); // back to the devices.$id.settings page
|
||||||
|
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
const onConfirmUpdate = useCallback(() => {
|
const onConfirmUpdate = useCallback(() => {
|
||||||
send("tryUpdate", {});
|
send("tryUpdate", {});
|
||||||
setModalView("updating");
|
setModalView("updating");
|
||||||
|
|
@ -33,16 +39,14 @@ export default function SettingsGeneralUpdateRoute() {
|
||||||
} else {
|
} else {
|
||||||
setModalView("loading");
|
setModalView("loading");
|
||||||
}
|
}
|
||||||
}, [otaState.updating, otaState.error, setModalView, updateSuccess]);
|
}, [otaState.error, otaState.updating, setModalView, updateSuccess]);
|
||||||
|
|
||||||
{
|
{
|
||||||
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
||||||
}
|
}
|
||||||
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
|
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function Dialog({
|
export function Dialog({
|
||||||
onClose,
|
onClose,
|
||||||
onConfirmUpdate,
|
onConfirmUpdate,
|
||||||
|
|
@ -224,7 +228,7 @@ function UpdatingDeviceState({
|
||||||
100,
|
100,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// System: 10% download, 90% update
|
// System: 10% download, 10% verification, 80% update
|
||||||
return Math.min(
|
return Math.min(
|
||||||
downloadProgress * 0.1 + verificationProgress * 0.1 + updateProgress * 0.8,
|
downloadProgress * 0.1 + verificationProgress * 0.1 + updateProgress * 0.8,
|
||||||
100,
|
100,
|
||||||
|
|
@ -239,14 +243,18 @@ function UpdatingDeviceState({
|
||||||
|
|
||||||
if (!otaState.metadataFetchedAt) {
|
if (!otaState.metadataFetchedAt) {
|
||||||
return "Fetching update information...";
|
return "Fetching update information...";
|
||||||
|
} else if (otaState.rebooting) {
|
||||||
|
return "Rebooting...";
|
||||||
} else if (!downloadFinishedAt) {
|
} else if (!downloadFinishedAt) {
|
||||||
return `Downloading ${type} update...`;
|
return `Downloading ${type} update...`;
|
||||||
} else if (!verfiedAt) {
|
} else if (!verfiedAt) {
|
||||||
return `Verifying ${type} update...`;
|
return `Verifying ${type} update...`;
|
||||||
} else if (!updatedAt) {
|
} else if (!updatedAt) {
|
||||||
return `Installing ${type} update...`;
|
return `Installing ${type} update...`;
|
||||||
|
} else if (otaState.rebootNeeded) {
|
||||||
|
return "Reboot needed";
|
||||||
} else {
|
} else {
|
||||||
return `Awaiting reboot`;
|
return "Awaiting reboot";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -278,12 +286,51 @@ function UpdatingDeviceState({
|
||||||
<Card className="space-y-4 p-4">
|
<Card className="space-y-4 p-4">
|
||||||
{areAllUpdatesComplete() ? (
|
{areAllUpdatesComplete() ? (
|
||||||
<div className="my-2 flex flex-col items-center space-y-2 text-center">
|
<div className="my-2 flex flex-col items-center space-y-2 text-center">
|
||||||
<LoadingSpinner className="h-6 w-6 text-blue-700 dark:text-blue-500" />
|
<CheckCircleIcon className="h-6 w-6 text-blue-700 dark:text-blue-500" />
|
||||||
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-300">
|
{otaState.rebooting ? (
|
||||||
<span className="font-medium text-black dark:text-white">
|
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-300">
|
||||||
Rebooting to complete the update...
|
<span className="font-medium text-black dark:text-white">
|
||||||
</span>
|
Rebooting the device to complete the update...
|
||||||
</div>
|
</span>
|
||||||
|
<p className="flex-col text-black dark:text-white">
|
||||||
|
This may take a few minutes. The device will automatically
|
||||||
|
reconnect once it is back online.<br/>
|
||||||
|
If it doesn{"'"}t reconnect automatically, you can manually
|
||||||
|
reconnect by clicking here:
|
||||||
|
<LinkButton
|
||||||
|
size="XS"
|
||||||
|
theme="light"
|
||||||
|
text="Reconnect to KVM"
|
||||||
|
LeadingIcon={MdConnectWithoutContact}
|
||||||
|
textAlign="center"
|
||||||
|
reloadDocument={true}
|
||||||
|
to={".."}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
otaState.rebootNeeded && (
|
||||||
|
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-300">
|
||||||
|
<span className="font-medium text-black dark:text-white">
|
||||||
|
Device reboot is pending...
|
||||||
|
</span>
|
||||||
|
<p className="flex-col text-black dark:text-white">
|
||||||
|
The JetKVM is preparing to reboot. This may take a while.<br/>
|
||||||
|
If it doesn{"'"}t automatically reboot after a few minutes, you
|
||||||
|
can manually request a reboot by clicking here:
|
||||||
|
<LinkButton
|
||||||
|
size="XS"
|
||||||
|
theme="light"
|
||||||
|
text="Reboot the KVM"
|
||||||
|
LeadingIcon={MdRestartAlt}
|
||||||
|
textAlign="center"
|
||||||
|
reloadDocument={true}
|
||||||
|
to={"../reboot"}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { lazy, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { lazy, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Outlet,
|
Outlet,
|
||||||
redirect,
|
|
||||||
useLoaderData,
|
useLoaderData,
|
||||||
useLocation,
|
useLocation,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
|
|
@ -15,7 +14,7 @@ import { FocusTrap } from "focus-trap-react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import useWebSocket from "react-use-websocket";
|
import useWebSocket from "react-use-websocket";
|
||||||
|
|
||||||
import { CLOUD_API, DEVICE_API } from "@/ui.config";
|
import { CLOUD_API } from "@/ui.config";
|
||||||
import api from "@/api";
|
import api from "@/api";
|
||||||
import { checkAuth, isInCloud, isOnDevice } from "@/main";
|
import { checkAuth, isInCloud, isOnDevice } from "@/main";
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
|
|
@ -48,7 +47,6 @@ import {
|
||||||
} from "@/components/VideoOverlay";
|
} from "@/components/VideoOverlay";
|
||||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||||
import { FeatureFlagProvider } from "@/providers/FeatureFlagProvider";
|
import { FeatureFlagProvider } from "@/providers/FeatureFlagProvider";
|
||||||
import { DeviceStatus } from "@routes/welcome-local";
|
|
||||||
import { useVersion } from "@/hooks/useVersion";
|
import { useVersion } from "@/hooks/useVersion";
|
||||||
|
|
||||||
interface LocalLoaderResp {
|
interface LocalLoaderResp {
|
||||||
|
|
@ -70,20 +68,8 @@ export interface LocalDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceLoader = async () => {
|
const deviceLoader = async () => {
|
||||||
const res = await api
|
const device = await checkAuth();
|
||||||
.GET(`${DEVICE_API}/device/status`)
|
return { authMode: device.authMode } as LocalLoaderResp;
|
||||||
.then(res => res.json() as Promise<DeviceStatus>);
|
|
||||||
|
|
||||||
if (!res.isSetup) return redirect("/welcome");
|
|
||||||
|
|
||||||
const deviceRes = await api.GET(`${DEVICE_API}/device`);
|
|
||||||
if (deviceRes.status === 401) return redirect("/login-local");
|
|
||||||
if (deviceRes.ok) {
|
|
||||||
const device = (await deviceRes.json()) as LocalDevice;
|
|
||||||
return { authMode: device.authMode };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Error fetching device");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> => {
|
const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> => {
|
||||||
|
|
@ -106,11 +92,11 @@ const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> =>
|
||||||
device: { id: string; name: string; user: { googleId: string } };
|
device: { id: string; name: string; user: { googleId: string } };
|
||||||
};
|
};
|
||||||
|
|
||||||
return { user, iceConfig, deviceName: device.name || device.id };
|
return { user, iceConfig, deviceName: device.name || device.id } as CloudLoaderResp;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loader: LoaderFunction = ({ params }: LoaderFunctionArgs) => {
|
const loader: LoaderFunction = ({ params }: LoaderFunctionArgs) => {
|
||||||
return import.meta.env.MODE === "device" ? deviceLoader() : cloudLoader(params);
|
return isOnDevice ? deviceLoader() : cloudLoader(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function KvmIdRoute() {
|
export default function KvmIdRoute() {
|
||||||
|
|
@ -146,6 +132,7 @@ export default function KvmIdRoute() {
|
||||||
const { otaState, setOtaState, setModalView } = useUpdateStore();
|
const { otaState, setOtaState, setModalView } = useUpdateStore();
|
||||||
|
|
||||||
const [loadingMessage, setLoadingMessage] = useState("Connecting to device...");
|
const [loadingMessage, setLoadingMessage] = useState("Connecting to device...");
|
||||||
|
|
||||||
const cleanupAndStopReconnecting = useCallback(
|
const cleanupAndStopReconnecting = useCallback(
|
||||||
function cleanupAndStopReconnecting() {
|
function cleanupAndStopReconnecting() {
|
||||||
console.log("Closing peer connection");
|
console.log("Closing peer connection");
|
||||||
|
|
@ -182,11 +169,11 @@ export default function KvmIdRoute() {
|
||||||
pc: RTCPeerConnection,
|
pc: RTCPeerConnection,
|
||||||
remoteDescription: RTCSessionDescriptionInit,
|
remoteDescription: RTCSessionDescriptionInit,
|
||||||
) {
|
) {
|
||||||
setLoadingMessage("Setting remote description");
|
setLoadingMessage("Setting remote description type:" + remoteDescription.type);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pc.setRemoteDescription(new RTCSessionDescription(remoteDescription));
|
await pc.setRemoteDescription(new RTCSessionDescription(remoteDescription));
|
||||||
console.log("[setRemoteSessionDescription] Remote description set successfully");
|
console.log("[setRemoteSessionDescription] Remote description set successfully to: " + remoteDescription.sdp);
|
||||||
setLoadingMessage("Establishing secure connection...");
|
setLoadingMessage("Establishing secure connection...");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|
@ -231,9 +218,15 @@ export default function KvmIdRoute() {
|
||||||
const ignoreOffer = useRef(false);
|
const ignoreOffer = useRef(false);
|
||||||
const isSettingRemoteAnswerPending = useRef(false);
|
const isSettingRemoteAnswerPending = useRef(false);
|
||||||
const makingOffer = useRef(false);
|
const makingOffer = useRef(false);
|
||||||
|
const reconnectAttemptsRef = useRef(2000);
|
||||||
|
|
||||||
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
|
||||||
|
const reconnectInterval = (attempt: number) => {
|
||||||
|
// Exponential backoff with a max of 10 seconds between attempts
|
||||||
|
return Math.min(500 * 2 ** attempt, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
const { sendMessage, getWebSocket } = useWebSocket(
|
const { sendMessage, getWebSocket } = useWebSocket(
|
||||||
isOnDevice
|
isOnDevice
|
||||||
? `${wsProtocol}//${window.location.host}/webrtc/signaling/client`
|
? `${wsProtocol}//${window.location.host}/webrtc/signaling/client`
|
||||||
|
|
@ -241,17 +234,16 @@ export default function KvmIdRoute() {
|
||||||
{
|
{
|
||||||
heartbeat: true,
|
heartbeat: true,
|
||||||
retryOnError: true,
|
retryOnError: true,
|
||||||
reconnectAttempts: 15,
|
reconnectAttempts: reconnectAttemptsRef.current,
|
||||||
reconnectInterval: 1000,
|
reconnectInterval: reconnectInterval,
|
||||||
onReconnectStop: () => {
|
onReconnectStop: (attempt: number) => {
|
||||||
console.debug("Reconnect stopped");
|
console.debug("Reconnect stopped after ", attempt, "attempts");
|
||||||
cleanupAndStopReconnecting();
|
cleanupAndStopReconnecting();
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldReconnect(event) {
|
shouldReconnect(event) {
|
||||||
console.debug("[Websocket] shouldReconnect", event);
|
console.debug("[Websocket] shouldReconnect", event);
|
||||||
// TODO: Why true?
|
return !connectionFailed; // we always want to try to reconnect unless we're explicitly stopped
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onClose(event) {
|
onClose(event) {
|
||||||
|
|
@ -284,6 +276,7 @@ export default function KvmIdRoute() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const parsedMessage = JSON.parse(message.data);
|
const parsedMessage = JSON.parse(message.data);
|
||||||
|
|
||||||
if (parsedMessage.type === "device-metadata") {
|
if (parsedMessage.type === "device-metadata") {
|
||||||
const { deviceVersion } = parsedMessage.data;
|
const { deviceVersion } = parsedMessage.data;
|
||||||
console.debug("[Websocket] Received device-metadata message");
|
console.debug("[Websocket] Received device-metadata message");
|
||||||
|
|
@ -300,10 +293,12 @@ export default function KvmIdRoute() {
|
||||||
console.log("[Websocket] Device is using new signaling");
|
console.log("[Websocket] Device is using new signaling");
|
||||||
isLegacySignalingEnabled.current = false;
|
isLegacySignalingEnabled.current = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPeerConnection();
|
setupPeerConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!peerConnection) return;
|
if (!peerConnection) return;
|
||||||
|
|
||||||
if (parsedMessage.type === "answer") {
|
if (parsedMessage.type === "answer") {
|
||||||
console.debug("[Websocket] Received answer");
|
console.debug("[Websocket] Received answer");
|
||||||
const readyForOffer =
|
const readyForOffer =
|
||||||
|
|
@ -594,7 +589,9 @@ export default function KvmIdRoute() {
|
||||||
api.POST(`${CLOUD_API}/webrtc/turn_activity`, {
|
api.POST(`${CLOUD_API}/webrtc/turn_activity`, {
|
||||||
bytesReceived: bytesReceivedDelta,
|
bytesReceived: bytesReceivedDelta,
|
||||||
bytesSent: bytesSentDelta,
|
bytesSent: bytesSentDelta,
|
||||||
});
|
}).catch(() => {
|
||||||
|
// we don't care about errors here, but we don't want unhandled promise rejections
|
||||||
|
});
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
const { setNetworkState} = useNetworkStateStore();
|
const { setNetworkState} = useNetworkStateStore();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue