mirror of https://github.com/jetkvm/kvm.git
Compare commits
1 Commits
05a3613fc6
...
09dba289d7
| Author | SHA1 | Date |
|---|---|---|
|
|
09dba289d7 |
|
|
@ -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
|
uses: actions/setup-go@v6.0.0
|
||||||
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@v6
|
uses: actions/setup-go@v5
|
||||||
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@v6
|
uses: actions/setup-go@v5.5.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.4"
|
go-version: "1.24.4"
|
||||||
- name: Golang Test Report
|
- name: Golang Test Report
|
||||||
|
|
|
||||||
48
go.mod
48
go.mod
|
|
@ -5,14 +5,13 @@ 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.14
|
github.com/coder/websocket v1.8.13
|
||||||
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.11.0
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/go-co-op/gocron/v2 v2.16.6
|
github.com/go-co-op/gocron/v2 v2.16.5
|
||||||
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
|
||||||
|
|
@ -20,8 +19,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.2
|
github.com/prometheus/client_golang v1.23.0
|
||||||
github.com/prometheus/common v0.66.1
|
github.com/prometheus/common v0.66.0
|
||||||
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
|
||||||
|
|
@ -31,32 +30,37 @@ 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.42.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/net v0.44.0
|
golang.org/x/net v0.43.0
|
||||||
golang.org/x/sys v0.36.0
|
golang.org/x/sys v0.35.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.14.0 // indirect
|
github.com/bytedance/sonic v1.13.3 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // 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.6 // indirect
|
github.com/cloudwego/base64x v0.1.5 // 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.27.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.18.0 // 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.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // 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
|
||||||
|
|
@ -80,22 +84,16 @@ require (
|
||||||
github.com/pion/turn/v4 v4.1.1 // indirect
|
github.com/pion/turn/v4 v4.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
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
|
||||||
go.uber.org/mock v0.5.0 // 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/mod v0.27.0 // indirect
|
|
||||||
golang.org/x/oauth2 v0.30.0 // indirect
|
golang.org/x/oauth2 v0.30.0 // indirect
|
||||||
golang.org/x/sync v0.17.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
|
||||||
golang.org/x/tools v0.36.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
104
go.sum
104
go.sum
|
|
@ -1,5 +1,7 @@
|
||||||
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=
|
||||||
|
|
@ -8,18 +10,20 @@ 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.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
||||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
|
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.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||||
|
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=
|
||||||
|
|
@ -40,42 +44,50 @@ github.com/gin-contrib/logger v1.2.6 h1:EPolruKUTzNXMVBD9LuAFQmRjTs7AH7yKGuXgYqr
|
||||||
github.com/gin-contrib/logger v1.2.6/go.mod h1:7niPrd7F0Nscw/zvgz8RiGJxSdbKM2yfQNy8xCHcm64=
|
github.com/gin-contrib/logger v1.2.6/go.mod h1:7niPrd7F0Nscw/zvgz8RiGJxSdbKM2yfQNy8xCHcm64=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
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.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-co-op/gocron/v2 v2.16.6 h1:zI2Ya9sqvuLcgqJgV79LwoJXM8h20Z/drtB7ATbpRWo=
|
github.com/go-co-op/gocron/v2 v2.16.5 h1:j228Jxk7bb9CF8LKR3gS+bK3rcjRUINjlVI+ZMp26Ss=
|
||||||
github.com/go-co-op/gocron/v2 v2.16.6/go.mod h1:zAfC/GFQ668qHxOVl/D68Jh5Ce7sDqX6TJnSQyRkRBc=
|
github.com/go-co-op/gocron/v2 v2.16.5/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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.26.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/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
|
||||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
|
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=
|
||||||
|
|
@ -140,20 +152,16 @@ 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.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
||||||
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.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
|
||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4=
|
||||||
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=
|
||||||
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
|
||||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
|
||||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
|
@ -162,12 +170,15 @@ 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=
|
||||||
|
|
@ -189,40 +200,33 @@ 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=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
|
||||||
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/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
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=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
|
||||||
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=
|
||||||
|
|
|
||||||
62
jsonrpc.go
62
jsonrpc.go
|
|
@ -820,9 +820,9 @@ func rpcGetATXState() (ATXState, error) {
|
||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcSendCustomCommand(command string, terminator string) error {
|
func rpcSendCustomCommand(command string) error {
|
||||||
logger.Debug().Str("Command", command).Msg("JSONRPC: Sending custom serial command")
|
logger.Debug().Str("Command", command).Msg("JSONRPC: Sending custom serial command")
|
||||||
err := sendCustomCommand(command, terminator)
|
err := sendCustomCommand(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send custom command in jsonrpc: %w", err)
|
return fmt.Errorf("failed to send custom command in jsonrpc: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -920,49 +920,59 @@ func rpcSetSerialSettings(settings SerialSettings) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcGetSerialButtonConfig() (CustomButtonSettings, error) {
|
type QuickButton struct {
|
||||||
return getSerialSettings()
|
Id string `json:"id"` // uuid-ish
|
||||||
|
Label string `json:"label"` // shown on the button
|
||||||
|
Command string `json:"command"` // raw command to send (without auto-terminator)
|
||||||
|
Sort int `json:"sort"` // for stable ordering
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcSetSerialButtonConfig(config CustomButtonSettings) error {
|
type SerialButtonConfig struct {
|
||||||
return setSerialSettings(config)
|
Buttons []QuickButton `json:"buttons"` // slice of QuickButton
|
||||||
|
Terminator string `json:"terminator"` // CR/CRLF/None
|
||||||
|
HideSerialSettings bool `json:"hideSerialSettings"` // lowercase `bool`
|
||||||
|
HideSerialResponse bool `json:"hideSerialResponse"` // lowercase `bool`
|
||||||
}
|
}
|
||||||
|
|
||||||
const SerialCommandHistoryPath = "/userdata/serialCommandHistory.json"
|
func rpcGetSerialButtonConfig() (SerialButtonConfig, error) {
|
||||||
|
config := SerialButtonConfig{
|
||||||
|
Buttons: []QuickButton{},
|
||||||
|
Terminator: "\r",
|
||||||
|
HideSerialSettings: false,
|
||||||
|
HideSerialResponse: true,
|
||||||
|
}
|
||||||
|
|
||||||
func rpcGetSerialCommandHistory() ([]string, error) {
|
file, err := os.Open("/userdata/serialButtons_config.json")
|
||||||
items := []string{}
|
|
||||||
|
|
||||||
file, err := os.Open(SerialCommandHistoryPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug().Msg("SerialCommandHistory file doesn't exist, using default")
|
logger.Debug().Msg("SerialButtons config file doesn't exist, using default")
|
||||||
return items, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// load and merge the default config with the user config
|
// load and merge the default config with the user config
|
||||||
var loadedItems []string
|
var loadedConfig SerialButtonConfig
|
||||||
if err := json.NewDecoder(file).Decode(&loadedItems); err != nil {
|
if err := json.NewDecoder(file).Decode(&loadedConfig); err != nil {
|
||||||
logger.Warn().Err(err).Msg("SerialCommandHistory file JSON parsing failed")
|
logger.Warn().Err(err).Msg("SerialButtons config file JSON parsing failed")
|
||||||
return items, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadedItems, nil
|
return loadedConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcSetSerialCommandHistory(commandHistory []string) error {
|
func rpcSetSerialButtonConfig(config SerialButtonConfig) error {
|
||||||
logger.Trace().Str("path", SerialCommandHistoryPath).Msg("Saving serial command history")
|
|
||||||
|
|
||||||
file, err := os.Create(SerialCommandHistoryPath)
|
logger.Trace().Str("path", "/userdata/serialButtons_config.json").Msg("Saving config")
|
||||||
|
|
||||||
|
file, err := os.Create("/userdata/serialButtons_config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create SerialCommandHistory file: %w", err)
|
return fmt.Errorf("failed to create SerialButtons config file: %w", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
encoder := json.NewEncoder(file)
|
encoder := json.NewEncoder(file)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
if err := encoder.Encode(commandHistory); err != nil {
|
if err := encoder.Encode(config); err != nil {
|
||||||
return fmt.Errorf("failed to encode SerialCommandHistory: %w", err)
|
return fmt.Errorf("failed to encode SerialButtons config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -1316,13 +1326,11 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
||||||
"getATXState": {Func: rpcGetATXState},
|
"getATXState": {Func: rpcGetATXState},
|
||||||
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
|
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
|
||||||
"sendCustomCommand": {Func: rpcSendCustomCommand, Params: []string{"command", "terminator"}},
|
"sendCustomCommand": {Func: rpcSendCustomCommand, Params: []string{"command"}},
|
||||||
"getSerialSettings": {Func: rpcGetSerialSettings},
|
"getSerialSettings": {Func: rpcGetSerialSettings},
|
||||||
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
|
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
|
||||||
"getSerialButtonConfig": {Func: rpcGetSerialButtonConfig},
|
"getSerialButtonConfig": {Func: rpcGetSerialButtonConfig},
|
||||||
"setSerialButtonConfig": {Func: rpcSetSerialButtonConfig, Params: []string{"config"}},
|
"setSerialButtonConfig": {Func: rpcSetSerialButtonConfig, Params: []string{"config"}},
|
||||||
"getSerialCommandHistory": {Func: rpcGetSerialCommandHistory},
|
|
||||||
"setSerialCommandHistory": {Func: rpcSetSerialCommandHistory, Params: []string{"commandHistory"}},
|
|
||||||
"getUsbDevices": {Func: rpcGetUsbDevices},
|
"getUsbDevices": {Func: rpcGetUsbDevices},
|
||||||
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
|
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
|
||||||
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
|
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
|
||||||
|
|
|
||||||
164
serial.go
164
serial.go
|
|
@ -3,10 +3,7 @@ package kvm
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -275,7 +272,7 @@ func startSerialButtonsRxLoop(session *Session) {
|
||||||
scopedLogger.Debug().Msg("Attempting to start RX reader.")
|
scopedLogger.Debug().Msg("Attempting to start RX reader.")
|
||||||
// Stop previous loop if running
|
// Stop previous loop if running
|
||||||
if serialButtonsRXStopCh != nil {
|
if serialButtonsRXStopCh != nil {
|
||||||
stopSerialButtonsRxLoop()
|
close(serialButtonsRXStopCh)
|
||||||
}
|
}
|
||||||
serialButtonsRXStopCh = make(chan struct{})
|
serialButtonsRXStopCh = make(chan struct{})
|
||||||
|
|
||||||
|
|
@ -288,10 +285,6 @@ func startSerialButtonsRxLoop(session *Session) {
|
||||||
case <-serialButtonsRXStopCh:
|
case <-serialButtonsRXStopCh:
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
if currentSession == nil {
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := port.Read(buf)
|
n, err := port.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
|
|
@ -300,7 +293,7 @@ func startSerialButtonsRxLoop(session *Session) {
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 || currentSession == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Safe for any bytes: wrap in Base64
|
// Safe for any bytes: wrap in Base64
|
||||||
|
|
@ -314,25 +307,23 @@ func startSerialButtonsRxLoop(session *Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopSerialButtonsRxLoop() {
|
func stopSerialButtonsRxLoop() {
|
||||||
scopedLogger := serialLogger.With().Str("service", "custom_buttons_rx").Logger()
|
|
||||||
scopedLogger.Debug().Msg("Stopping RX reader.")
|
|
||||||
if serialButtonsRXStopCh != nil {
|
if serialButtonsRXStopCh != nil {
|
||||||
close(serialButtonsRXStopCh)
|
close(serialButtonsRXStopCh)
|
||||||
serialButtonsRXStopCh = nil
|
serialButtonsRXStopCh = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendCustomCommand(command string, terminator string) error {
|
func sendCustomCommand(command string) error {
|
||||||
scopedLogger := serialLogger.With().Str("service", "custom_buttons_tx").Logger()
|
scopedLogger := serialLogger.With().Str("service", "custom_buttons_tx").Logger()
|
||||||
scopedLogger.Info().Str("Command", command).Msg("Sending custom command.")
|
scopedLogger.Info().Str("Command", command).Msg("Sending custom command.")
|
||||||
_, err := port.Write([]byte(terminator))
|
_, err := port.Write([]byte("\n"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scopedLogger.Warn().Err(err).Msg("Failed to send terminator")
|
scopedLogger.Warn().Err(err).Msg("Failed to send serial output \\n")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = port.Write([]byte(command))
|
_, err = port.Write([]byte(command))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scopedLogger.Warn().Err(err).Str("Command", command).Msg("Failed to send serial command")
|
scopedLogger.Warn().Err(err).Str("line", command).Msg("Failed to send serial output")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -345,149 +336,6 @@ var defaultMode = &serial.Mode{
|
||||||
StopBits: serial.OneStopBit,
|
StopBits: serial.OneStopBit,
|
||||||
}
|
}
|
||||||
|
|
||||||
const serialSettingsPath = "/userdata/serialSettings.json"
|
|
||||||
|
|
||||||
type Terminator struct {
|
|
||||||
Label string `json:"label"` // Terminator label
|
|
||||||
Value string `json:"value"` // Terminator value
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuickButton struct {
|
|
||||||
Id string `json:"id"` // Unique identifier
|
|
||||||
Label string `json:"label"` // Button label
|
|
||||||
Command string `json:"command"` // Command to send, raw command to send (without auto-terminator)
|
|
||||||
Terminator Terminator `json:"terminator"` // Terminator to use: None/CR/LF/CRLF/LFCR
|
|
||||||
Sort int `json:"sort"` // Sort order
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode describes a serial port configuration.
|
|
||||||
type CustomButtonSettings struct {
|
|
||||||
BaudRate string `json:"baudRate"` // The serial port bitrate (aka Baudrate)
|
|
||||||
DataBits string `json:"dataBits"` // Size of the character (must be 5, 6, 7 or 8)
|
|
||||||
Parity string `json:"parity"` // Parity (see Parity type for more info)
|
|
||||||
StopBits string `json:"stopBits"` // Stop bits (see StopBits type for more info)
|
|
||||||
Terminator Terminator `json:"terminator"` // Terminator to send after each command
|
|
||||||
LineMode bool `json:"lineMode"` // Whether to send each line when Enter is pressed, or each character immediately
|
|
||||||
HideSerialSettings bool `json:"hideSerialSettings"` // Whether to hide the serial settings in the UI
|
|
||||||
EnableEcho bool `json:"enableEcho"` // Whether to echo received characters back to the sender
|
|
||||||
Buttons []QuickButton `json:"buttons"` // Custom quick buttons
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSerialSettings() (CustomButtonSettings, error) {
|
|
||||||
config := CustomButtonSettings{
|
|
||||||
BaudRate: strconv.Itoa(defaultMode.BaudRate),
|
|
||||||
DataBits: strconv.Itoa(defaultMode.DataBits),
|
|
||||||
Parity: "none",
|
|
||||||
StopBits: "1",
|
|
||||||
Terminator: Terminator{Label: "CR (\\r)", Value: "\r"},
|
|
||||||
LineMode: true,
|
|
||||||
HideSerialSettings: false,
|
|
||||||
EnableEcho: false,
|
|
||||||
Buttons: []QuickButton{},
|
|
||||||
}
|
|
||||||
|
|
||||||
switch defaultMode.StopBits {
|
|
||||||
case serial.OneStopBit:
|
|
||||||
config.StopBits = "1"
|
|
||||||
case serial.OnePointFiveStopBits:
|
|
||||||
config.StopBits = "1.5"
|
|
||||||
case serial.TwoStopBits:
|
|
||||||
config.StopBits = "2"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch defaultMode.Parity {
|
|
||||||
case serial.NoParity:
|
|
||||||
config.Parity = "none"
|
|
||||||
case serial.OddParity:
|
|
||||||
config.Parity = "odd"
|
|
||||||
case serial.EvenParity:
|
|
||||||
config.Parity = "even"
|
|
||||||
case serial.MarkParity:
|
|
||||||
config.Parity = "mark"
|
|
||||||
case serial.SpaceParity:
|
|
||||||
config.Parity = "space"
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(serialSettingsPath)
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug().Msg("SerialButtons config file doesn't exist, using default")
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// load and merge the default config with the user config
|
|
||||||
var loadedConfig CustomButtonSettings
|
|
||||||
if err := json.NewDecoder(file).Decode(&loadedConfig); err != nil {
|
|
||||||
logger.Warn().Err(err).Msg("SerialButtons config file JSON parsing failed")
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return loadedConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSerialSettings(newSettings CustomButtonSettings) error {
|
|
||||||
logger.Trace().Str("path", serialSettingsPath).Msg("Saving config")
|
|
||||||
|
|
||||||
file, err := os.Create(serialSettingsPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create SerialButtons config file: %w", err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(file)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
if err := encoder.Encode(newSettings); err != nil {
|
|
||||||
return fmt.Errorf("failed to encode SerialButtons config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
baudRate, err := strconv.Atoi(newSettings.BaudRate)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid baud rate: %v", err)
|
|
||||||
}
|
|
||||||
dataBits, err := strconv.Atoi(newSettings.DataBits)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid data bits: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var stopBits serial.StopBits
|
|
||||||
switch newSettings.StopBits {
|
|
||||||
case "1":
|
|
||||||
stopBits = serial.OneStopBit
|
|
||||||
case "1.5":
|
|
||||||
stopBits = serial.OnePointFiveStopBits
|
|
||||||
case "2":
|
|
||||||
stopBits = serial.TwoStopBits
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid stop bits: %s", newSettings.StopBits)
|
|
||||||
}
|
|
||||||
|
|
||||||
var parity serial.Parity
|
|
||||||
switch newSettings.Parity {
|
|
||||||
case "none":
|
|
||||||
parity = serial.NoParity
|
|
||||||
case "odd":
|
|
||||||
parity = serial.OddParity
|
|
||||||
case "even":
|
|
||||||
parity = serial.EvenParity
|
|
||||||
case "mark":
|
|
||||||
parity = serial.MarkParity
|
|
||||||
case "space":
|
|
||||||
parity = serial.SpaceParity
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid parity: %s", newSettings.Parity)
|
|
||||||
}
|
|
||||||
serialPortMode = &serial.Mode{
|
|
||||||
BaudRate: baudRate,
|
|
||||||
DataBits: dataBits,
|
|
||||||
StopBits: stopBits,
|
|
||||||
Parity: parity,
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = port.SetMode(serialPortMode)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initSerialPort() {
|
func initSerialPort() {
|
||||||
_ = reopenSerialPort()
|
_ = reopenSerialPort()
|
||||||
switch config.ActiveExtension {
|
switch config.ActiveExtension {
|
||||||
|
|
|
||||||
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.10.01.1900",
|
"version": "2025.09.26.01300",
|
||||||
"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.125",
|
"react-simple-keyboard": "^3.8.122",
|
||||||
"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.14",
|
"@tailwindcss/postcss": "^4.1.13",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@types/react": "^19.1.17",
|
"@types/react": "^19.1.14",
|
||||||
"@types/react-dom": "^19.1.10",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@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.45.0",
|
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
||||||
"@typescript-eslint/parser": "^8.45.0",
|
"@typescript-eslint/parser": "^8.44.1",
|
||||||
"@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.14",
|
"tailwindcss": "^4.1.13",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.7",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,290 +0,0 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import InputField from "@/components/InputField"; // your existing input component
|
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
|
||||||
import notifications from "@/notifications";
|
|
||||||
|
|
||||||
interface Hit { value: string; index: number }
|
|
||||||
|
|
||||||
// ---------- history hook ----------
|
|
||||||
function useCommandHistory(max = 300) {
|
|
||||||
const { send } = useJsonRpc();
|
|
||||||
const [items, setItems] = useState<string[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
send("getSerialCommandHistory", {}, (resp: JsonRpcResponse) => {
|
|
||||||
if ("error" in resp) {
|
|
||||||
notifications.error(
|
|
||||||
`Failed to get command history: ${resp.error.data || "Unknown error"}`,
|
|
||||||
);
|
|
||||||
} else if ("result" in resp) {
|
|
||||||
setItems(resp.result as string[]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [send]);
|
|
||||||
|
|
||||||
const [pointer, setPointer] = useState<number>(-1); // -1 = fresh line
|
|
||||||
const [anchorPrefix, setAnchorPrefix] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (items.length > 1) {
|
|
||||||
send("setSerialCommandHistory", { commandHistory: items }, (resp: JsonRpcResponse) => {
|
|
||||||
if ("error" in resp) {
|
|
||||||
notifications.error(`Failed to update command history: ${resp.error.data || "Unknown error"}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [items, send]);
|
|
||||||
|
|
||||||
const push = useCallback((cmd: string) => {
|
|
||||||
if (!cmd.trim()) return;
|
|
||||||
setItems((prev) => {
|
|
||||||
const next = prev[prev.length - 1] === cmd ? prev : [...prev, cmd];
|
|
||||||
return next.slice(-max);
|
|
||||||
});
|
|
||||||
setPointer(-1);
|
|
||||||
setAnchorPrefix(null);
|
|
||||||
}, [max]);
|
|
||||||
|
|
||||||
const resetTraversal = useCallback(() => {
|
|
||||||
setPointer(-1);
|
|
||||||
setAnchorPrefix(null);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const up = useCallback((draft: string) => {
|
|
||||||
const pref = anchorPrefix ?? draft;
|
|
||||||
if (anchorPrefix == null) setAnchorPrefix(pref);
|
|
||||||
let i = pointer < 0 ? items.length - 1 : pointer - 1;
|
|
||||||
for (; i >= 0; i--) {
|
|
||||||
if (items[i].startsWith(pref)) {
|
|
||||||
setPointer(i);
|
|
||||||
return items[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
}, [items, pointer, anchorPrefix]);
|
|
||||||
|
|
||||||
const down = useCallback((draft: string) => {
|
|
||||||
const pref = anchorPrefix ?? draft;
|
|
||||||
if (anchorPrefix == null) setAnchorPrefix(pref);
|
|
||||||
let i = pointer < 0 ? 0 : pointer + 1;
|
|
||||||
for (; i < items.length; i++) {
|
|
||||||
if (items[i].startsWith(pref)) {
|
|
||||||
setPointer(i);
|
|
||||||
return items[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setPointer(-1);
|
|
||||||
return draft;
|
|
||||||
}, [items, pointer, anchorPrefix]);
|
|
||||||
|
|
||||||
const search = useCallback((query: string): Hit[] => {
|
|
||||||
if (!query) return [];
|
|
||||||
const q = query.toLowerCase();
|
|
||||||
return [...items]
|
|
||||||
.map((value, index) => ({ value, index }))
|
|
||||||
.filter((x) => x.value.toLowerCase().includes(q))
|
|
||||||
.reverse(); // newest first
|
|
||||||
}, [items]);
|
|
||||||
|
|
||||||
return { push, up, down, resetTraversal, search };
|
|
||||||
}
|
|
||||||
|
|
||||||
function Portal({ children }: { children: React.ReactNode }) {
|
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
useEffect(() => setMounted(true), []);
|
|
||||||
if (!mounted) return null;
|
|
||||||
return createPortal(children, document.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- reverse search popup ----------
|
|
||||||
function ReverseSearch({
|
|
||||||
open, results, sel, setSel, onPick, onClose,
|
|
||||||
}: {
|
|
||||||
open: boolean;
|
|
||||||
results: Hit[];
|
|
||||||
sel: number;
|
|
||||||
setSel: (i: number) => void;
|
|
||||||
onPick: (val: string) => void;
|
|
||||||
onClose: () => void;
|
|
||||||
}) {
|
|
||||||
const listRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// keep selected item in view when sel changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!listRef.current) return;
|
|
||||||
const el = listRef.current.querySelector<HTMLDivElement>(`[data-idx="${sel}"]`);
|
|
||||||
el?.scrollIntoView({ block: "nearest" });
|
|
||||||
}, [sel, results]);
|
|
||||||
|
|
||||||
if (!open) return null;
|
|
||||||
return (
|
|
||||||
<Portal>
|
|
||||||
<div
|
|
||||||
className="absolute bottom-12 left-0 right-0 ml-17 mr-8 mb-5 rounded-md border border-slate-600 bg-slate-900/95 p-2 shadow-lg"
|
|
||||||
role="listbox"
|
|
||||||
aria-activedescendant={`rev-opt-${sel}`}
|
|
||||||
>
|
|
||||||
<div ref={listRef} className="max-h-48 overflow-auto">
|
|
||||||
{results.length === 0 ? (
|
|
||||||
<div className="px-2 py-1 text-sm text-slate-400">No matches</div>
|
|
||||||
) : results.map((r, i) => (
|
|
||||||
<div
|
|
||||||
id={`rev-opt-${i}`}
|
|
||||||
data-idx={i}
|
|
||||||
key={`${r.index}-${i}`}
|
|
||||||
role="option"
|
|
||||||
aria-selected={i === sel}
|
|
||||||
className={clsx(
|
|
||||||
"px-2 py-1 font-mono text-sm cursor-pointer",
|
|
||||||
i === sel ? "bg-slate-700 text-white rounded" : "text-slate-200",
|
|
||||||
)}
|
|
||||||
onMouseEnter={() => setSel(i)}
|
|
||||||
onClick={() => onPick(r.value)}
|
|
||||||
>
|
|
||||||
{r.value}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 flex justify-between text-s text-slate-400">
|
|
||||||
<span>↑/↓ select • Enter accept • Esc close</span>
|
|
||||||
<button className="underline" onClick={onClose}>Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Portal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- main component ----------
|
|
||||||
interface CommandInputProps {
|
|
||||||
onSend: (line: string) => void; // called on Enter
|
|
||||||
storageKey?: string; // localStorage key for history
|
|
||||||
placeholder?: string; // input placeholder
|
|
||||||
className?: string; // container className
|
|
||||||
disabled?: boolean; // disable input (optional)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CommandInput({
|
|
||||||
onSend,
|
|
||||||
placeholder = "Type serial command… (Enter to send • ↑/↓ history • Ctrl+R search)",
|
|
||||||
className,
|
|
||||||
disabled,
|
|
||||||
}: CommandInputProps) {
|
|
||||||
const [cmd, setCmd] = useState("");
|
|
||||||
const [revOpen, setRevOpen] = useState(false);
|
|
||||||
const [revQuery, setRevQuery] = useState("");
|
|
||||||
const [sel, setSel] = useState(0);
|
|
||||||
const { push, up, down, resetTraversal, search } = useCommandHistory();
|
|
||||||
|
|
||||||
const results = useMemo(() => search(revQuery), [revQuery, search]);
|
|
||||||
|
|
||||||
useEffect(() => { setSel(0); }, [results]);
|
|
||||||
|
|
||||||
const cmdInputRef = React.useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
const isMeta = e.ctrlKey || e.metaKey;
|
|
||||||
|
|
||||||
if (e.key === "Enter" && !e.shiftKey && !isMeta) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!cmd) return;
|
|
||||||
onSend(cmd);
|
|
||||||
push(cmd);
|
|
||||||
setCmd("");
|
|
||||||
resetTraversal();
|
|
||||||
setRevOpen(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.key === "ArrowUp") {
|
|
||||||
e.preventDefault();
|
|
||||||
setCmd((prev) => up(prev));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.key === "ArrowDown") {
|
|
||||||
e.preventDefault();
|
|
||||||
setCmd((prev) => down(prev));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isMeta && e.key.toLowerCase() === "r") {
|
|
||||||
e.preventDefault();
|
|
||||||
setRevOpen(true);
|
|
||||||
setRevQuery(cmd);
|
|
||||||
setSel(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.key === "Escape" && revOpen) {
|
|
||||||
e.preventDefault();
|
|
||||||
setRevOpen(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx("relative", className)}>
|
|
||||||
<div className="flex items-center gap-2" style={{visibility: revOpen ? "hidden" : "unset"} }>
|
|
||||||
<span className="text-xs text-slate-400 select-none">CMD</span>
|
|
||||||
<InputField
|
|
||||||
ref={cmdInputRef}
|
|
||||||
size="MD"
|
|
||||||
disabled={disabled}
|
|
||||||
value={cmd}
|
|
||||||
onChange={(e) => { setCmd(e.target.value); resetTraversal(); }}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
placeholder={placeholder}
|
|
||||||
className="font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Reverse search controls */}
|
|
||||||
{revOpen && (
|
|
||||||
<div className="mt-[-40px]">
|
|
||||||
<div className="flex items-center gap-2 bg-[#0f172a]">
|
|
||||||
<span className="text-s text-slate-400 select-none">Search</span>
|
|
||||||
<InputField
|
|
||||||
size="MD"
|
|
||||||
autoFocus
|
|
||||||
value={revQuery}
|
|
||||||
onChange={(e) => setRevQuery(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "ArrowDown") {
|
|
||||||
e.preventDefault();
|
|
||||||
setSel((i) => (i + 1) % Math.max(1, results.length));
|
|
||||||
} else if (e.key === "ArrowUp") {
|
|
||||||
e.preventDefault();
|
|
||||||
setSel((i) => (i - 1 + results.length) % Math.max(1, results.length));
|
|
||||||
} else if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
const pick = results[sel]?.value ?? results[0]?.value;
|
|
||||||
if (pick) {
|
|
||||||
setCmd(pick);
|
|
||||||
setRevOpen(false);
|
|
||||||
requestAnimationFrame(() => cmdInputRef.current?.focus());
|
|
||||||
}
|
|
||||||
} else if (e.key === "Escape") {
|
|
||||||
e.preventDefault();
|
|
||||||
setRevOpen(false);
|
|
||||||
requestAnimationFrame(() => cmdInputRef.current?.focus());
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder="Type to filter history…"
|
|
||||||
className="font-mono"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ReverseSearch
|
|
||||||
open={revOpen}
|
|
||||||
results={results}
|
|
||||||
sel={sel}
|
|
||||||
setSel={setSel}
|
|
||||||
onPick={(v) => { setCmd(v); setRevOpen(false); requestAnimationFrame(() => cmdInputRef.current?.focus()); }}
|
|
||||||
onClose={() => {setRevOpen(false); requestAnimationFrame(() => cmdInputRef.current?.focus());}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CommandInput;
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import "react-simple-keyboard/build/css/index.css";
|
import "react-simple-keyboard/build/css/index.css";
|
||||||
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
||||||
import { useEffect, useMemo, useCallback } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useXTerm } from "react-xtermjs";
|
import { useXTerm } from "react-xtermjs";
|
||||||
import { FitAddon } from "@xterm/addon-fit";
|
import { FitAddon } from "@xterm/addon-fit";
|
||||||
import { WebLinksAddon } from "@xterm/addon-web-links";
|
import { WebLinksAddon } from "@xterm/addon-web-links";
|
||||||
|
|
@ -10,11 +10,9 @@ import { ClipboardAddon } from "@xterm/addon-clipboard";
|
||||||
|
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
import { AvailableTerminalTypes, useUiStore } from "@/hooks/stores";
|
import { AvailableTerminalTypes, useUiStore } from "@/hooks/stores";
|
||||||
import { CommandInput } from "@/components/CommandInput";
|
|
||||||
|
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
|
|
||||||
const isWebGl2Supported = !!document.createElement("canvas").getContext("webgl2");
|
const isWebGl2Supported = !!document.createElement("canvas").getContext("webgl2");
|
||||||
|
|
||||||
// Terminal theme configuration
|
// Terminal theme configuration
|
||||||
|
|
@ -67,20 +65,13 @@ function Terminal({
|
||||||
readonly dataChannel: RTCDataChannel;
|
readonly dataChannel: RTCDataChannel;
|
||||||
readonly type: AvailableTerminalTypes;
|
readonly type: AvailableTerminalTypes;
|
||||||
}) {
|
}) {
|
||||||
const { terminalLineMode, terminalType, setTerminalType, setDisableVideoFocusTrap } = useUiStore();
|
const { terminalType, setTerminalType, setDisableVideoFocusTrap } = useUiStore();
|
||||||
const { instance, ref } = useXTerm({ options: TERMINAL_CONFIG });
|
const { instance, ref } = useXTerm({ options: TERMINAL_CONFIG });
|
||||||
|
|
||||||
const isTerminalTypeEnabled = useMemo(() => {
|
const isTerminalTypeEnabled = useMemo(() => {
|
||||||
console.log("Terminal type:", terminalType, "Checking against:", type);
|
|
||||||
return terminalType == type;
|
return terminalType == type;
|
||||||
}, [terminalType, type]);
|
}, [terminalType, type]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!instance) return;
|
|
||||||
instance.options.disableStdin = !terminalLineMode;
|
|
||||||
instance.options.cursorStyle = terminalLineMode ? "bar" : "block";
|
|
||||||
}, [instance, terminalLineMode]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setDisableVideoFocusTrap(isTerminalTypeEnabled);
|
setDisableVideoFocusTrap(isTerminalTypeEnabled);
|
||||||
|
|
@ -170,11 +161,6 @@ function Terminal({
|
||||||
};
|
};
|
||||||
}, [instance]);
|
}, [instance]);
|
||||||
|
|
||||||
const sendLine = useCallback((line: string) => {
|
|
||||||
// Just send; echo/normalization handled elsewhere as you planned
|
|
||||||
dataChannel.send(line + "\r\n"); // adjust CR/LF to taste
|
|
||||||
}, [dataChannel]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onKeyDown={e => e.stopPropagation()}
|
onKeyDown={e => e.stopPropagation()}
|
||||||
|
|
@ -213,14 +199,7 @@ function Terminal({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-[calc(100%-36px)] p-3">
|
<div className="h-[calc(100%-36px)] p-3">
|
||||||
<div key="serial" ref={ref} style={{height: (terminalType === "serial" && terminalLineMode) ? "90%" : "100%", width: "100%" }} />
|
<div ref={ref} style={{ height: "100%", width: "100%" }} />
|
||||||
{terminalType == "serial" && terminalLineMode && (
|
|
||||||
<CommandInput
|
|
||||||
placeholder="Type serial command… (Enter to send • ↑/↓ history • Ctrl+R search)"
|
|
||||||
onSend={sendLine}
|
|
||||||
className="mt-2"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { LuPlus, LuTrash2, LuPencil, LuSettings2, LuEye, LuEyeOff, LuSave, LuArrowBigUp, LuArrowBigDown, LuCircleX, LuTerminal } from "react-icons/lu";
|
import { LuPlus, LuTrash2, LuPencil, LuSettings2, LuEye, LuEyeOff, LuSave, LuArrowBigUp, LuArrowBigDown, LuCirclePause, LuCirclePlay } from "react-icons/lu";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "@components/Button";
|
import { Button } from "@components/Button";
|
||||||
import Card from "@components/Card";
|
import Card from "@components/Card";
|
||||||
|
|
@ -8,43 +8,38 @@ import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||||
import { InputFieldWithLabel } from "@components/InputField";
|
import { InputFieldWithLabel } from "@components/InputField";
|
||||||
import { useUiStore } from "@/hooks/stores";
|
import { TextAreaWithLabel } from "@components/TextArea";
|
||||||
|
|
||||||
import Checkbox from "../../components/Checkbox";
|
|
||||||
import { SettingsItem } from "../../routes/devices.$id.settings";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** ============== Types ============== */
|
/** ============== Types ============== */
|
||||||
interface QuickButton {
|
|
||||||
id: string; // uuid-ish
|
|
||||||
label: string; // shown on the button
|
|
||||||
command: string; // raw command to send (without auto-terminator)
|
|
||||||
terminator: {label: string, value: string}; // None/CR/LF/CRLF/LFCR
|
|
||||||
sort: number; // for stable ordering
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CustomButtonSettings {
|
interface SerialSettings {
|
||||||
baudRate: string;
|
baudRate: string;
|
||||||
dataBits: string;
|
dataBits: string;
|
||||||
stopBits: string;
|
stopBits: string;
|
||||||
parity: string;
|
parity: string;
|
||||||
terminator: {label: string, value: string}; // None/CR/LF/CRLF/LFCR
|
}
|
||||||
lineMode: boolean;
|
|
||||||
hideSerialSettings: boolean;
|
interface QuickButton {
|
||||||
enableEcho: boolean; // future use
|
id: string; // uuid-ish
|
||||||
|
label: string; // shown on the button
|
||||||
|
command: string; // raw command to send (without auto-terminator)
|
||||||
|
sort: number; // for stable ordering
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ButtonConfig {
|
||||||
buttons: QuickButton[];
|
buttons: QuickButton[];
|
||||||
|
terminator: string; // CR/CRLF/None
|
||||||
|
hideSerialSettings: boolean;
|
||||||
|
hideSerialResponse: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ============== Component ============== */
|
/** ============== Component ============== */
|
||||||
|
|
||||||
export function SerialButtons() {
|
export function SerialButtons() {
|
||||||
const { setTerminalType, setTerminalLineMode } = useUiStore();
|
|
||||||
|
|
||||||
// This will receive all JSON-RPC notifications (method + no id)
|
// This will receive all JSON-RPC notifications (method + no id)
|
||||||
const { send } = useJsonRpc((payload) => {
|
const { send } = useJsonRpc((payload) => {
|
||||||
if (payload.method !== "serial.rx") return;
|
if (payload.method !== "serial.rx") return;
|
||||||
// if (paused) return;
|
if (paused) return;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const p = payload.params as any;
|
const p = payload.params as any;
|
||||||
|
|
@ -66,30 +61,48 @@ export function SerialButtons() {
|
||||||
// Normalize CRLF for display
|
// Normalize CRLF for display
|
||||||
chunk = chunk.replace(/\r\n/g, "\n");
|
chunk = chunk.replace(/\r\n/g, "\n");
|
||||||
|
|
||||||
// setSerialResponse(prev => (prev + chunk).slice(-MAX_CHARS));
|
setSerialResponse(prev => (prev + chunk).slice(-MAX_CHARS));
|
||||||
});
|
});
|
||||||
|
|
||||||
// extension config (buttons + prefs)
|
|
||||||
const [buttonConfig, setButtonConfig] = useState<CustomButtonSettings>({
|
const MAX_CHARS = 50_000;
|
||||||
|
|
||||||
|
// serial settings (same as SerialConsole)
|
||||||
|
const [serialSettings, setSerialSettings] = useState<SerialSettings>({
|
||||||
baudRate: "9600",
|
baudRate: "9600",
|
||||||
dataBits: "8",
|
dataBits: "8",
|
||||||
stopBits: "1",
|
stopBits: "1",
|
||||||
parity: "none",
|
parity: "none",
|
||||||
terminator: {label: "CR (\\r)", value: "\r"},
|
});
|
||||||
lineMode: true,
|
|
||||||
hideSerialSettings: false,
|
// extension config (buttons + prefs)
|
||||||
enableEcho: false,
|
const [buttonConfig, setButtonConfig] = useState<ButtonConfig>({
|
||||||
buttons: [],
|
buttons: [],
|
||||||
|
terminator: "",
|
||||||
|
hideSerialSettings: false,
|
||||||
|
hideSerialResponse: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// editor modal state
|
// editor modal state
|
||||||
const [editorOpen, setEditorOpen] = useState<null | { id?: string }>(null);
|
const [editorOpen, setEditorOpen] = useState<null | { id?: string }>(null);
|
||||||
const [draftLabel, setDraftLabel] = useState("");
|
const [draftLabel, setDraftLabel] = useState("");
|
||||||
const [draftCmd, setDraftCmd] = useState("");
|
const [draftCmd, setDraftCmd] = useState("");
|
||||||
const [draftTerminator, setDraftTerminator] = useState({label: "CR (\\r)", value: "\r"});
|
const [serialResponse, setSerialResponse] = useState("");
|
||||||
|
const [paused, setPaused] = useState(false);
|
||||||
|
const taRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
// load serial settings like SerialConsole
|
// load serial settings like SerialConsole
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
send("getSerialSettings", {}, (resp: JsonRpcResponse) => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
notifications.error(
|
||||||
|
`Failed to get serial settings: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSerialSettings(resp.result as SerialSettings);
|
||||||
|
});
|
||||||
|
|
||||||
send("getSerialButtonConfig", {}, (resp: JsonRpcResponse) => {
|
send("getSerialButtonConfig", {}, (resp: JsonRpcResponse) => {
|
||||||
if ("error" in resp) {
|
if ("error" in resp) {
|
||||||
notifications.error(
|
notifications.error(
|
||||||
|
|
@ -98,35 +111,57 @@ export function SerialButtons() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setButtonConfig(resp.result as CustomButtonSettings);
|
setButtonConfig(resp.result as ButtonConfig);
|
||||||
setTerminalLineMode((resp.result as CustomButtonSettings).lineMode);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}, [send, setTerminalLineMode]);
|
});
|
||||||
|
|
||||||
const handleSerialButtonConfigChange = (config: keyof CustomButtonSettings, value: unknown) => {
|
const handleSerialSettingChange = (setting: keyof SerialSettings, value: string) => {
|
||||||
|
const newSettings = { ...serialSettings, [setting]: value };
|
||||||
|
send("setSerialSettings", { settings: newSettings }, (resp: JsonRpcResponse) => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
notifications.error(`Failed to update serial settings: ${resp.error.data || "Unknown error"}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSerialSettings(newSettings);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSerialButtonConfigChange = (config: keyof ButtonConfig, value: unknown) => {
|
||||||
const newButtonConfig = { ...buttonConfig, [config]: value };
|
const newButtonConfig = { ...buttonConfig, [config]: value };
|
||||||
send("setSerialButtonConfig", { config: newButtonConfig }, (resp: JsonRpcResponse) => {
|
send("setSerialButtonConfig", { config: newButtonConfig }, (resp: JsonRpcResponse) => {
|
||||||
if ("error" in resp) {
|
if ("error" in resp) {
|
||||||
notifications.error(`Failed to update button config: ${resp.error.data || "Unknown error"}`);
|
notifications.error(`Failed to update button config: ${resp.error.data || "Unknown error"}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setButtonConfig(newButtonConfig);
|
||||||
});
|
});
|
||||||
setButtonConfig(newButtonConfig);
|
setButtonConfig(newButtonConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (buttonConfig.hideSerialResponse) return;
|
||||||
|
const el = taRef.current;
|
||||||
|
if (el) el.scrollTop = el.scrollHeight;
|
||||||
|
}, [serialResponse, buttonConfig.hideSerialResponse]);
|
||||||
|
|
||||||
const onClickButton = (btn: QuickButton) => {
|
const onClickButton = (btn: QuickButton) => {
|
||||||
|
|
||||||
const command = btn.command + btn.terminator.value;
|
/** build final string to send:
|
||||||
const terminator = btn.terminator.value;
|
* if the user's button command already contains a terminator, we don't append the selected terminator safely
|
||||||
|
*/
|
||||||
|
const raw = btn.command;
|
||||||
|
const t = buttonConfig.terminator ?? "";
|
||||||
|
const command = raw.endsWith("\r") || raw.endsWith("\n") ? raw : raw + t;
|
||||||
|
|
||||||
send("sendCustomCommand", { command, terminator }, (resp: JsonRpcResponse) => {
|
send("sendCustomCommand", { command }, (resp: JsonRpcResponse) => {
|
||||||
if ("error" in resp) {
|
if ("error" in resp) {
|
||||||
notifications.error(
|
notifications.error(
|
||||||
`Failed to send custom command: ${resp.error.data || "Unknown error"}`,
|
`Failed to send ATX power action: ${resp.error.data || "Unknown error"}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** CRUD helpers */
|
/** CRUD helpers */
|
||||||
|
|
@ -134,14 +169,12 @@ export function SerialButtons() {
|
||||||
setEditorOpen({ id: undefined });
|
setEditorOpen({ id: undefined });
|
||||||
setDraftLabel("");
|
setDraftLabel("");
|
||||||
setDraftCmd("");
|
setDraftCmd("");
|
||||||
setDraftTerminator({label: "CR (\\r)", value: "\r"});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const editBtn = (btn: QuickButton) => {
|
const editBtn = (btn: QuickButton) => {
|
||||||
setEditorOpen({ id: btn.id });
|
setEditorOpen({ id: btn.id });
|
||||||
setDraftLabel(btn.label);
|
setDraftLabel(btn.label);
|
||||||
setDraftCmd(btn.command);
|
setDraftCmd(btn.command);
|
||||||
setDraftTerminator(btn.terminator);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeBtn = (id: string) => {
|
const removeBtn = (id: string) => {
|
||||||
|
|
@ -194,29 +227,23 @@ export function SerialButtons() {
|
||||||
|
|
||||||
const saveDraft = () => {
|
const saveDraft = () => {
|
||||||
const label = draftLabel.trim() || "Unnamed";
|
const label = draftLabel.trim() || "Unnamed";
|
||||||
const command = draftCmd;
|
const command = draftCmd.trim();
|
||||||
if (!command) {
|
if (!command) {
|
||||||
notifications.error("Command cannot be empty.");
|
notifications.error("Command cannot be empty.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const terminator = draftTerminator;
|
|
||||||
|
|
||||||
// if editing, get current id, otherwise undefined => new button
|
const isEdit = editorOpen?.id;
|
||||||
const currentID = editorOpen?.id;
|
const nextButtons = isEdit
|
||||||
|
? buttonConfig.buttons.map(b => (b.id === isEdit ? { ...b, label, command } : b))
|
||||||
// either update existing or add new
|
: [...buttonConfig.buttons, { id: genId(), label, command, sort: buttonConfig.buttons.length }];
|
||||||
// if new, assign next sort index
|
|
||||||
// if existing, keep sort index
|
|
||||||
const nextButtons = currentID
|
|
||||||
? buttonConfig.buttons.map(b => (b.id === currentID ? { ...b, label, command } : b))
|
|
||||||
: [...buttonConfig.buttons, { id: genId(), label, command, terminator, sort: buttonConfig.buttons.length }];
|
|
||||||
|
|
||||||
handleSerialButtonConfigChange("buttons", stableSort(nextButtons) );
|
handleSerialButtonConfigChange("buttons", stableSort(nextButtons) );
|
||||||
setEditorOpen(null);
|
setEditorOpen(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** simple reordering: alphabetical by sort, then label */
|
/** simple reordering: alphabetical by sort, then label */
|
||||||
const sortedButtons = useMemo(() => buttonConfig.buttons, [buttonConfig.buttons]);
|
const sortedButtons = useMemo(() => stableSort(buttonConfig.buttons), [buttonConfig.buttons]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
@ -230,28 +257,25 @@ export function SerialButtons() {
|
||||||
{/* Top actions */}
|
{/* Top actions */}
|
||||||
<div className="flex flex-wrap justify-around items-center gap-3">
|
<div className="flex flex-wrap justify-around items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="SM"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
LeadingIcon={buttonConfig.hideSerialSettings ? LuEye : LuEyeOff}
|
LeadingIcon={buttonConfig.hideSerialSettings ? LuEye : LuEyeOff}
|
||||||
text={buttonConfig.hideSerialSettings ? "Show Settings" : "Hide Settings"}
|
text={buttonConfig.hideSerialSettings ? "Show Settings" : "Hide Settings"}
|
||||||
onClick={() => handleSerialButtonConfigChange("hideSerialSettings", !buttonConfig.hideSerialSettings )}
|
onClick={() => handleSerialButtonConfigChange("hideSerialSettings", !buttonConfig.hideSerialSettings )}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="SM"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
LeadingIcon={LuPlus}
|
LeadingIcon={LuPlus}
|
||||||
text="Add Button"
|
text="Add Button"
|
||||||
onClick={addNew}
|
onClick={addNew}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="SM"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
LeadingIcon={LuTerminal}
|
LeadingIcon={buttonConfig.hideSerialResponse ? LuEye : LuEyeOff}
|
||||||
text="Open Console"
|
text={buttonConfig.hideSerialResponse ? "View RX" : "Hide RX"}
|
||||||
onClick={() => {
|
onClick={() => handleSerialButtonConfigChange("hideSerialResponse", !buttonConfig.hideSerialResponse )}
|
||||||
setTerminalType("serial");
|
|
||||||
console.log("Opening serial console with settings: ", buttonConfig);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
||||||
|
|
@ -272,8 +296,8 @@ export function SerialButtons() {
|
||||||
{ label: "57600", value: "57600" },
|
{ label: "57600", value: "57600" },
|
||||||
{ label: "115200", value: "115200" },
|
{ label: "115200", value: "115200" },
|
||||||
]}
|
]}
|
||||||
value={buttonConfig.baudRate}
|
value={serialSettings.baudRate}
|
||||||
onChange={(e) => handleSerialButtonConfigChange("baudRate", e.target.value)}
|
onChange={(e) => handleSerialSettingChange("baudRate", e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectMenuBasic
|
<SelectMenuBasic
|
||||||
|
|
@ -282,8 +306,8 @@ export function SerialButtons() {
|
||||||
{ label: "8", value: "8" },
|
{ label: "8", value: "8" },
|
||||||
{ label: "7", value: "7" },
|
{ label: "7", value: "7" },
|
||||||
]}
|
]}
|
||||||
value={buttonConfig.dataBits}
|
value={serialSettings.dataBits}
|
||||||
onChange={(e) => handleSerialButtonConfigChange("dataBits", e.target.value)}
|
onChange={(e) => handleSerialSettingChange("dataBits", e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectMenuBasic
|
<SelectMenuBasic
|
||||||
|
|
@ -293,8 +317,8 @@ export function SerialButtons() {
|
||||||
{ label: "1.5", value: "1.5" },
|
{ label: "1.5", value: "1.5" },
|
||||||
{ label: "2", value: "2" },
|
{ label: "2", value: "2" },
|
||||||
]}
|
]}
|
||||||
value={buttonConfig.stopBits}
|
value={serialSettings.stopBits}
|
||||||
onChange={(e) => handleSerialButtonConfigChange("stopBits", e.target.value)}
|
onChange={(e) => handleSerialSettingChange("stopBits", e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectMenuBasic
|
<SelectMenuBasic
|
||||||
|
|
@ -304,61 +328,20 @@ export function SerialButtons() {
|
||||||
{ label: "Even", value: "even" },
|
{ label: "Even", value: "even" },
|
||||||
{ label: "Odd", value: "odd" },
|
{ label: "Odd", value: "odd" },
|
||||||
]}
|
]}
|
||||||
value={buttonConfig.parity}
|
value={serialSettings.parity}
|
||||||
onChange={(e) => handleSerialButtonConfigChange("parity", e.target.value)}
|
onChange={(e) => handleSerialSettingChange("parity", e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<SelectMenuBasic
|
<SelectMenuBasic
|
||||||
className="mb-1"
|
|
||||||
label="Line ending"
|
label="Line ending"
|
||||||
options={[
|
options={[
|
||||||
{ label: "None", value: "" },
|
{ label: "None", value: "" },
|
||||||
{ label: "CR (\\r)", value: "\r" },
|
{ label: "CR (\\r)", value: "\r" },
|
||||||
{ label: "LF (\\n)", value: "\n" },
|
|
||||||
{ label: "CRLF (\\r\\n)", value: "\r\n" },
|
{ label: "CRLF (\\r\\n)", value: "\r\n" },
|
||||||
{ label: "LFCR (\\n\\r)", value: "\n\r" },
|
|
||||||
]}
|
]}
|
||||||
value={buttonConfig.terminator.value}
|
value={buttonConfig.terminator}
|
||||||
onChange={(e) => handleSerialButtonConfigChange("terminator", {label: e.target.selectedOptions[0].text, value: e.target.value})}
|
onChange={(e) => handleSerialButtonConfigChange("terminator", e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="text-xs text-white opacity-70 mt-0 ml-2">
|
|
||||||
When sent, the selected line ending ({buttonConfig.terminator.label}) will be appended.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<SelectMenuBasic
|
|
||||||
className="mb-1"
|
|
||||||
label="Terminal Mode"
|
|
||||||
options={[
|
|
||||||
{ label: "Raw Mode", value: "raw" },
|
|
||||||
{ label: "Line Mode", value: "line" },
|
|
||||||
]}
|
|
||||||
value={buttonConfig.lineMode ? "line" : "raw"}
|
|
||||||
onChange={(e) => {
|
|
||||||
handleSerialButtonConfigChange("lineMode", e.target.value === "line")
|
|
||||||
setTerminalLineMode(e.target.value === "line");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="text-xs text-white opacity-70 mt-0 ml-2">
|
|
||||||
{buttonConfig.lineMode
|
|
||||||
? "In Line Mode, input is sent when you press Enter in the input field."
|
|
||||||
: "In Raw Mode, input is sent immediately as you type in the console."}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4 m-2">
|
|
||||||
<SettingsItem
|
|
||||||
title="Local Echo"
|
|
||||||
description="Whether to echo received characters back to the sender"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={buttonConfig.enableEcho}
|
|
||||||
onChange={e => {
|
|
||||||
handleSerialButtonConfigChange("enableEcho", e.target.checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SettingsItem>
|
|
||||||
</div>
|
|
||||||
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
@ -399,7 +382,7 @@ export function SerialButtons() {
|
||||||
<LuSettings2 className="h-3.5 text-white shrink-0 justify-start" />
|
<LuSettings2 className="h-3.5 text-white shrink-0 justify-start" />
|
||||||
<div className="font-medium text-black dark:text-white">{editorOpen.id ? "Edit Button" : "New Button"}</div>
|
<div className="font-medium text-black dark:text-white">{editorOpen.id ? "Edit Button" : "New Button"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 h-23">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<InputFieldWithLabel
|
<InputFieldWithLabel
|
||||||
size="SM"
|
size="SM"
|
||||||
|
|
@ -423,34 +406,16 @@ export function SerialButtons() {
|
||||||
setDraftCmd(e.target.value);
|
setDraftCmd(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{draftTerminator.value != "" && (
|
{buttonConfig.terminator != "" && (
|
||||||
<div className="text-xs text-white opacity-70 mt-1">
|
<div className="text-xs text-white opacity-70 mt-1">
|
||||||
When sent, the selected line ending ({draftTerminator.label}) will be appended.
|
The selected line ending ({pretty(buttonConfig.terminator)}) will be appended when sent.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-around items-end">
|
<div className="flex gap-2 mt-3">
|
||||||
<SelectMenuBasic
|
|
||||||
label="Line ending"
|
|
||||||
options={[
|
|
||||||
{ label: "None", value: "" },
|
|
||||||
{ label: "CR (\\r)", value: "\r" },
|
|
||||||
{ label: "LF (\\n)", value: "\n" },
|
|
||||||
{ label: "CRLF (\\r\\n)", value: "\r\n" },
|
|
||||||
{ label: "LFCR (\\n\\r)", value: "\n\r" },
|
|
||||||
]}
|
|
||||||
value={draftTerminator.value}
|
|
||||||
onChange={(e) => setDraftTerminator({label: e.target.selectedOptions[0].text, value: e.target.value})}
|
|
||||||
/>
|
|
||||||
<div className="pb-[3px]">
|
|
||||||
<Button size="SM" theme="primary" LeadingIcon={LuSave} text="Save" onClick={saveDraft} />
|
<Button size="SM" theme="primary" LeadingIcon={LuSave} text="Save" onClick={saveDraft} />
|
||||||
</div>
|
<Button size="SM" theme="primary" text="Cancel" onClick={() => setEditorOpen(null)} />
|
||||||
<div className="pb-[3px]">
|
|
||||||
<Button size="SM" theme="primary" LeadingIcon={LuCircleX} text="Cancel" onClick={() => setEditorOpen(null)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-around mt-3">
|
|
||||||
{editorOpen.id && (
|
{editorOpen.id && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -465,18 +430,12 @@ export function SerialButtons() {
|
||||||
size="SM"
|
size="SM"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
LeadingIcon={LuArrowBigUp}
|
LeadingIcon={LuArrowBigUp}
|
||||||
text="Move Up"
|
|
||||||
aria-label={`Move ${draftLabel} up`}
|
|
||||||
disabled={sortedButtons.findIndex(b => b.id === editorOpen.id) === 0}
|
|
||||||
onClick={() => moveUpBtn(editorOpen.id!)}
|
onClick={() => moveUpBtn(editorOpen.id!)}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size="SM"
|
size="SM"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
LeadingIcon={LuArrowBigDown}
|
LeadingIcon={LuArrowBigDown}
|
||||||
text="Move Down"
|
|
||||||
aria-label={`Move ${draftLabel} down`}
|
|
||||||
disabled={sortedButtons.findIndex(b => b.id === editorOpen.id)+1 === sortedButtons.length}
|
|
||||||
onClick={() => moveDownBtn(editorOpen.id!)}
|
onClick={() => moveDownBtn(editorOpen.id!)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
@ -484,6 +443,37 @@ export function SerialButtons() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* Serial response (collapsible) */}
|
||||||
|
{!buttonConfig.hideSerialResponse && (
|
||||||
|
<>
|
||||||
|
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
||||||
|
<TextAreaWithLabel
|
||||||
|
ref={taRef}
|
||||||
|
readOnly
|
||||||
|
label="RX response from serial connection"
|
||||||
|
value={serialResponse|| ""}
|
||||||
|
rows={3}
|
||||||
|
onChange={e => setSerialResponse(e.target.value)}
|
||||||
|
placeholder="Will show the response recieved from the serial port."
|
||||||
|
/>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
size="XS"
|
||||||
|
theme="primary"
|
||||||
|
text={paused ? "Resume" : "Pause"}
|
||||||
|
LeadingIcon={paused ? LuCirclePlay : LuCirclePause}
|
||||||
|
onClick={() => setPaused(p => !p)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="XS"
|
||||||
|
theme="primary"
|
||||||
|
text="Clear"
|
||||||
|
LeadingIcon={LuTrash2}
|
||||||
|
onClick={() => setSerialResponse("")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -491,6 +481,10 @@ export function SerialButtons() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ============== helpers ============== */
|
/** ============== helpers ============== */
|
||||||
|
|
||||||
|
function pretty(s: string) {
|
||||||
|
return s.replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
||||||
|
}
|
||||||
function genId() {
|
function genId() {
|
||||||
return "b_" + Math.random().toString(36).slice(2, 10);
|
return "b_" + Math.random().toString(36).slice(2, 10);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,6 @@ export interface UIState {
|
||||||
|
|
||||||
terminalType: AvailableTerminalTypes;
|
terminalType: AvailableTerminalTypes;
|
||||||
setTerminalType: (type: UIState["terminalType"]) => void;
|
setTerminalType: (type: UIState["terminalType"]) => void;
|
||||||
|
|
||||||
terminalLineMode: boolean;
|
|
||||||
setTerminalLineMode: (enabled: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUiStore = create<UIState>(set => ({
|
export const useUiStore = create<UIState>(set => ({
|
||||||
|
|
@ -99,9 +96,6 @@ export const useUiStore = create<UIState>(set => ({
|
||||||
isAttachedVirtualKeyboardVisible: true,
|
isAttachedVirtualKeyboardVisible: true,
|
||||||
setAttachedVirtualKeyboardVisibility: (enabled: boolean) =>
|
setAttachedVirtualKeyboardVisibility: (enabled: boolean) =>
|
||||||
set({ isAttachedVirtualKeyboardVisible: enabled }),
|
set({ isAttachedVirtualKeyboardVisible: enabled }),
|
||||||
|
|
||||||
terminalLineMode: true,
|
|
||||||
setTerminalLineMode: (enabled: boolean) => set({ terminalLineMode: enabled }),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export interface RTCState {
|
export interface RTCState {
|
||||||
|
|
|
||||||
|
|
@ -153,13 +153,13 @@ body {
|
||||||
|
|
||||||
@property --grid-color-start {
|
@property --grid-color-start {
|
||||||
syntax: "<color>";
|
syntax: "<color>";
|
||||||
initial-value: oklch(97% 0.014 254.604 / 10); /* var(--color-blue-50/10) */
|
initial-value: var(--color-blue-50/10);
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property --grid-color-end {
|
@property --grid-color-end {
|
||||||
syntax: "<color>";
|
syntax: "<color>";
|
||||||
initial-value: oklch(97% 0.014 254.604 / 100); /* var(--color-blue-50/100) */
|
initial-value: 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: oklch(from var(--color-blue-100) l c h / 50);
|
--grid-color-start: var(--color-blue-100/50);
|
||||||
--grid-color-end: oklch(from var(--color-blue-50) l c h / 50);
|
--grid-color-end: var(--color-blue-50/50);
|
||||||
}
|
}
|
||||||
|
|
||||||
video::-webkit-media-controls {
|
video::-webkit-media-controls {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue