mirror of https://github.com/jetkvm/kvm.git
Compare commits
20 Commits
c73cdb7fa6
...
367f3c0288
Author | SHA1 | Date |
---|---|---|
|
367f3c0288 | |
|
66fbda864a | |
|
a0f6d01465 | |
|
b4dd4961fc | |
|
eeb103adf9 | |
|
8cf6b40dc3 | |
|
c6b05d4abe | |
|
51814dcc5e | |
|
5ba08de566 | |
|
3f320e50f7 | |
|
57fbee1490 | |
|
0e65c0a9a9 | |
|
2dafb5c9c1 | |
|
566305549f | |
|
1505c37e4c | |
|
564eee9b00 | |
|
fab575dbe0 | |
|
97958e7b86 | |
|
2f7042df18 | |
|
2cadda4e00 |
|
@ -35,6 +35,9 @@ jobs:
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
go test ./... -json > testreport.json
|
go test ./... -json > testreport.json
|
||||||
|
- name: Make test cases
|
||||||
|
run: |
|
||||||
|
make build_dev_test
|
||||||
- name: Golang Test Report
|
- name: Golang Test Report
|
||||||
uses: becheran/go-testreport@v0.3.2
|
uses: becheran/go-testreport@v0.3.2
|
||||||
with:
|
with:
|
||||||
|
@ -43,4 +46,6 @@ jobs:
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: jetkvm-app
|
name: jetkvm-app
|
||||||
path: bin/jetkvm_app
|
path: |
|
||||||
|
bin/jetkvm_app
|
||||||
|
device-tests.tar.gz
|
|
@ -0,0 +1,126 @@
|
||||||
|
name: Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-22.04]
|
||||||
|
go: [1.21, 1.23.4]
|
||||||
|
node: [21]
|
||||||
|
goos: [linux]
|
||||||
|
goarch: [arm]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ui
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build UI
|
||||||
|
working-directory: ui
|
||||||
|
run: npm run build:device
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Install Go Dependencies
|
||||||
|
run: |
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
- name: Build Binaries
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
run: |
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w -X kvm.builtAppVersion=dev-${GIT_COMMIT:0:7}" -o bin/jetkvm_app cmd/main.go
|
||||||
|
chmod 755 bin/jetkvm_app
|
||||||
|
|
||||||
|
- name: Upload Debug Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ (github.ref == 'refs/heads/main' || github.event_name == 'pull_request') && matrix.go == '1.21' }}
|
||||||
|
with:
|
||||||
|
name: jetkvm_app_debug
|
||||||
|
path: bin/jetkvm_app
|
||||||
|
|
||||||
|
comment:
|
||||||
|
name: Comment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate Links
|
||||||
|
id: linksa
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[0].id')
|
||||||
|
echo "ARTIFACT_URL=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID" >> $GITHUB_ENV
|
||||||
|
echo "LATEST_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Comment on PR
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
TITLE="${{ github.event.pull_request.title }}"
|
||||||
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||||
|
else
|
||||||
|
TITLE="main branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMENT=$(cat << EOF
|
||||||
|
✅ **Build successfully for $TITLE!**
|
||||||
|
|
||||||
|
| Name | Link |
|
||||||
|
|------------------|----------------------------------------------------------------------|
|
||||||
|
| 🔗 Debug Binary | [Download](${{ env.ARTIFACT_URL }}) |
|
||||||
|
| 🔗 Latest commit | [${{ env.LATEST_COMMIT }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) |
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Post Comment
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
# Look for an existing comment
|
||||||
|
COMMENT_ID=$(gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments \
|
||||||
|
--jq '.[] | select(.body | contains("✅ **Build successfully for")) | .id')
|
||||||
|
|
||||||
|
if [ -z "$COMMENT_ID" ]; then
|
||||||
|
# Create a new comment if none exists
|
||||||
|
gh pr comment $PR_NUMBER --body "$COMMENT"
|
||||||
|
else
|
||||||
|
# Update the existing comment
|
||||||
|
gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID \
|
||||||
|
--method PATCH \
|
||||||
|
-f body="$COMMENT"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Log the comment for main branch
|
||||||
|
echo "$COMMENT"
|
||||||
|
fi
|
|
@ -0,0 +1,91 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 21
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ui
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build UI
|
||||||
|
working-directory: ui
|
||||||
|
run: npm run build:device
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.21
|
||||||
|
|
||||||
|
- name: Build Release Binaries
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w -X kvm.builtAppVersion=${REF:11}" -o bin/jetkvm_app cmd/main.go
|
||||||
|
chmod 755 bin/jetkvm_app
|
||||||
|
|
||||||
|
- name: Create checksum
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
SUM=$(shasum -a 256 bin/jetkvm_app | cut -d ' ' -f 1)
|
||||||
|
echo -e "\n#### SHA256 Checksum\n\`\`\`\n$SUM bin/jetkvm_app\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
||||||
|
echo -e "$SUM bin/jetkvm_app\n" > checksums.txt
|
||||||
|
|
||||||
|
- name: Create Release Branch
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
BRANCH=release/${REF:10}
|
||||||
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git checkout -b ${BRANCH}
|
||||||
|
git push -u origin ${BRANCH}
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||||
|
body_path: ./RELEASE_CHANGELOG
|
||||||
|
|
||||||
|
- name: Upload JetKVM binary
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: bin/jetkvm_app
|
||||||
|
asset_name: jetkvm_app
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
- name: Upload checksum
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./checksums.txt
|
||||||
|
asset_name: checksums.txt
|
||||||
|
asset_content_type: text/plain
|
|
@ -72,18 +72,51 @@ jobs:
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
make build_dev_test
|
|
||||||
|
|
||||||
echo "+ Copying device-tests.tar.gz to remote host"
|
echo "+ Copying device-tests.tar.gz to remote host"
|
||||||
ssh jkci "cat > /userdata/jetkvm/device-tests.tar.gz" < device-tests.tar.gz
|
ssh jkci "cat > /tmp/device-tests.tar.gz" < device-tests.tar.gz
|
||||||
echo "+ Running go tests"
|
echo "+ Running go tests"
|
||||||
ssh jkci "cd /userdata/jetkvm && tar zxvf device-tests.tar.gz && ./run_all_tests -json"
|
ssh jkci ash << 'EOF'
|
||||||
|
set -e
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
cd ${TMP_DIR}
|
||||||
|
tar zxf /tmp/device-tests.tar.gz
|
||||||
|
./gotestsum --format=testdox \
|
||||||
|
--jsonfile=/tmp/device-tests.json \
|
||||||
|
--post-run-command 'sh -c "echo $TESTS_FAILED > /tmp/device-tests.failed"' \
|
||||||
|
--raw-command -- ./run_all_tests -json
|
||||||
|
|
||||||
|
GOTESTSUM_EXIT_CODE=$?
|
||||||
|
if [ $GOTESTSUM_EXIT_CODE -ne 0 ]; then
|
||||||
|
echo "❌ Tests failed (exit code: $GOTESTSUM_EXIT_CODE)"
|
||||||
|
rm -rf ${TMP_DIR} /tmp/device-tests.tar.gz
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TESTS_FAILED=$(cat /tmp/device-tests.failed)
|
||||||
|
if [ "$TESTS_FAILED" -ne 0 ]; then
|
||||||
|
echo "❌ Tests failed $TESTS_FAILED tests failed"
|
||||||
|
rm -rf ${TMP_DIR} /tmp/device-tests.tar.gz
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Tests passed"
|
||||||
|
rm -rf ${TMP_DIR} /tmp/device-tests.tar.gz
|
||||||
|
EOF
|
||||||
|
ssh jkci "cat /tmp/device-tests.json" > device-tests.json
|
||||||
|
- name: Set up Golang
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.24.0"
|
||||||
|
- name: Golang Test Report
|
||||||
|
uses: becheran/go-testreport@v0.3.2
|
||||||
|
with:
|
||||||
|
input: "device-tests.json"
|
||||||
- name: Deploy application
|
- name: Deploy application
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
# Copy the binary to the remote host
|
# Copy the binary to the remote host
|
||||||
echo "+ Copying the application to the remote host"
|
echo "+ Copying the application to the remote host"
|
||||||
cat jetkvm_app | gzip | ssh jkci "cat > /userdata/jetkvm/jetkvm_app.update.gz"
|
cat bin/jetkvm_app | gzip | ssh jkci "cat > /userdata/jetkvm/jetkvm_app.update.gz"
|
||||||
# Deploy and run the application on the remote host
|
# Deploy and run the application on the remote host
|
||||||
echo "+ Deploying the application on the remote host"
|
echo "+ Deploying the application on the remote host"
|
||||||
ssh jkci ash <<EOF
|
ssh jkci ash <<EOF
|
||||||
|
@ -117,15 +150,25 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "+ Checking the status of the device"
|
echo "+ Checking the status of the device"
|
||||||
curl -v http://$CI_HOST/device/status && echo
|
curl -v http://$CI_HOST/device/status && echo
|
||||||
echo "+ Waiting for 10 seconds to allow all services to start"
|
echo "+ Waiting for 15 seconds to allow all services to start"
|
||||||
sleep 10
|
sleep 15
|
||||||
echo "+ Collecting logs"
|
echo "+ Collecting logs"
|
||||||
ssh jkci "cat /userdata/jetkvm/last.log" > last.log
|
local_log_tar=$(mktemp)
|
||||||
cat last.log
|
ssh jkci ash > $local_log_tar <<'EOF'
|
||||||
|
log_path=$(mktemp -d)
|
||||||
|
dmesg > $log_path/dmesg.log
|
||||||
|
cp /userdata/jetkvm/last.log $log_path/last.log
|
||||||
|
tar -czf - -C $log_path .
|
||||||
|
EOF
|
||||||
|
tar -xf $local_log_tar
|
||||||
|
cat dmesg.log last.log
|
||||||
env:
|
env:
|
||||||
CI_HOST: ${{ vars.JETKVM_CI_HOST }}
|
CI_HOST: ${{ vars.JETKVM_CI_HOST }}
|
||||||
- name: Upload logs
|
- name: Upload logs
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: device-logs
|
name: device-logs
|
||||||
path: last.log
|
path: |
|
||||||
|
last.log
|
||||||
|
dmesg.log
|
||||||
|
device-tests.json
|
||||||
|
|
35
Makefile
35
Makefile
|
@ -15,6 +15,9 @@ GO_LDFLAGS := \
|
||||||
-X $(PROMETHEUS_TAG).Revision=$(REVISION) \
|
-X $(PROMETHEUS_TAG).Revision=$(REVISION) \
|
||||||
-X $(KVM_PKG_NAME).builtTimestamp=$(BUILDTS)
|
-X $(KVM_PKG_NAME).builtTimestamp=$(BUILDTS)
|
||||||
|
|
||||||
|
GO_CMD := GOOS=linux GOARCH=arm GOARM=7 go
|
||||||
|
BIN_DIR := $(shell pwd)/bin
|
||||||
|
|
||||||
TEST_DIRS := $(shell find . -name "*_test.go" -type f -exec dirname {} \; | sort -u)
|
TEST_DIRS := $(shell find . -name "*_test.go" -type f -exec dirname {} \; | sort -u)
|
||||||
|
|
||||||
hash_resource:
|
hash_resource:
|
||||||
|
@ -22,31 +25,35 @@ hash_resource:
|
||||||
|
|
||||||
build_dev: hash_resource
|
build_dev: hash_resource
|
||||||
@echo "Building..."
|
@echo "Building..."
|
||||||
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" -o bin/jetkvm_app cmd/main.go
|
$(GO_CMD) build -ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" -o $(BIN_DIR)/jetkvm_app cmd/main.go
|
||||||
|
|
||||||
build_test2json:
|
build_test2json:
|
||||||
GOOS=linux GOARCH=arm GOARM=7 go build -o bin/test2json cmd/test2json
|
$(GO_CMD) build -o $(BIN_DIR)/test2json cmd/test2json
|
||||||
|
|
||||||
build_dev_test: build_test2json
|
build_gotestsum:
|
||||||
|
@echo "Building gotestsum..."
|
||||||
|
$(GO_CMD) install gotest.tools/gotestsum@latest
|
||||||
|
cp $(shell $(GO_CMD) env GOPATH)/bin/linux_arm/gotestsum $(BIN_DIR)/gotestsum
|
||||||
|
|
||||||
|
build_dev_test: build_test2json build_gotestsum
|
||||||
# collect all directories that contain tests
|
# collect all directories that contain tests
|
||||||
@echo "Building tests for devices ..."
|
@echo "Building tests for devices ..."
|
||||||
@rm -rf bin/tests && mkdir -p bin/tests
|
@rm -rf $(BIN_DIR)/tests && mkdir -p $(BIN_DIR)/tests
|
||||||
|
|
||||||
@cat resource/dev_test.sh > bin/tests/run_all_tests
|
@cat resource/dev_test.sh > $(BIN_DIR)/tests/run_all_tests
|
||||||
@for test in $(TEST_DIRS); do \
|
@for test in $(TEST_DIRS); do \
|
||||||
test_pkg_name=$$(echo $$test | sed 's/^.\///g'); \
|
test_pkg_name=$$(echo $$test | sed 's/^.\///g'); \
|
||||||
test_pkg_full_name=$(KVM_PKG_NAME)/$$(echo $$test | sed 's/^.\///g'); \
|
test_pkg_full_name=$(KVM_PKG_NAME)/$$(echo $$test | sed 's/^.\///g'); \
|
||||||
test_filename=$$(echo $$test_pkg_name | sed 's/\//__/g')_test; \
|
test_filename=$$(echo $$test_pkg_name | sed 's/\//__/g')_test; \
|
||||||
GOOS=linux GOARCH=arm GOARM=7 \
|
$(GO_CMD) test -v \
|
||||||
go test -v \
|
|
||||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
||||||
-c -o bin/tests/$$test_filename $$test; \
|
-c -o $(BIN_DIR)/tests/$$test_filename $$test; \
|
||||||
echo "runTest ./$$test_filename $$test_pkg_full_name" >> bin/tests/run_all_tests; \
|
echo "runTest ./$$test_filename $$test_pkg_full_name" >> $(BIN_DIR)/tests/run_all_tests; \
|
||||||
done; \
|
done; \
|
||||||
chmod +x bin/tests/run_all_tests; \
|
chmod +x $(BIN_DIR)/tests/run_all_tests; \
|
||||||
cp bin/test2json bin/tests/; \
|
cp $(BIN_DIR)/test2json $(BIN_DIR)/tests/ && chmod +x $(BIN_DIR)/tests/test2json; \
|
||||||
chmod +x bin/tests/test2json; \
|
cp $(BIN_DIR)/gotestsum $(BIN_DIR)/tests/ && chmod +x $(BIN_DIR)/tests/gotestsum; \
|
||||||
tar czfv device-tests.tar.gz -C bin/tests .
|
tar czfv device-tests.tar.gz -C $(BIN_DIR)/tests .
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
cd ui && npm ci && npm run build:device
|
cd ui && npm ci && npm run build:device
|
||||||
|
@ -59,7 +66,7 @@ dev_release: frontend build_dev
|
||||||
|
|
||||||
build_release: frontend hash_resource
|
build_release: frontend hash_resource
|
||||||
@echo "Building release..."
|
@echo "Building release..."
|
||||||
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" -o bin/jetkvm_app cmd/main.go
|
$(GO_CMD) build -ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" -o bin/jetkvm_app cmd/main.go
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@if rclone lsf r2://jetkvm-update/app/$(VERSION)/ | grep -q "jetkvm_app"; then \
|
@if rclone lsf r2://jetkvm-update/app/$(VERSION)/ | grep -q "jetkvm_app"; then \
|
||||||
|
|
|
@ -27,7 +27,6 @@ show_help() {
|
||||||
echo " -u, --user <remote_user> Remote username (default: root)"
|
echo " -u, --user <remote_user> Remote username (default: root)"
|
||||||
echo " --run-go-tests Run go tests"
|
echo " --run-go-tests Run go tests"
|
||||||
echo " --run-go-tests-only Run go tests and exit"
|
echo " --run-go-tests-only Run go tests and exit"
|
||||||
echo " --run-go-tests-json Run go tests and output JSON"
|
|
||||||
echo " --skip-ui-build Skip frontend/UI build"
|
echo " --skip-ui-build Skip frontend/UI build"
|
||||||
echo " --help Display this help message"
|
echo " --help Display this help message"
|
||||||
echo
|
echo
|
||||||
|
@ -43,7 +42,6 @@ SKIP_UI_BUILD=false
|
||||||
RESET_USB_HID_DEVICE=false
|
RESET_USB_HID_DEVICE=false
|
||||||
LOG_TRACE_SCOPES="${LOG_TRACE_SCOPES:-jetkvm,cloud,websocket,native,jsonrpc}"
|
LOG_TRACE_SCOPES="${LOG_TRACE_SCOPES:-jetkvm,cloud,websocket,native,jsonrpc}"
|
||||||
RUN_GO_TESTS=false
|
RUN_GO_TESTS=false
|
||||||
RUN_GO_TESTS_JSON=false
|
|
||||||
RUN_GO_TESTS_ONLY=false
|
RUN_GO_TESTS_ONLY=false
|
||||||
|
|
||||||
# Parse command line arguments
|
# Parse command line arguments
|
||||||
|
@ -69,11 +67,6 @@ while [[ $# -gt 0 ]]; do
|
||||||
RUN_GO_TESTS=true
|
RUN_GO_TESTS=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--run-go-tests-json)
|
|
||||||
RUN_GO_TESTS_JSON=true
|
|
||||||
RUN_GO_TESTS=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--run-go-tests-only)
|
--run-go-tests-only)
|
||||||
RUN_GO_TESTS_ONLY=true
|
RUN_GO_TESTS_ONLY=true
|
||||||
RUN_GO_TESTS=true
|
RUN_GO_TESTS=true
|
||||||
|
@ -109,18 +102,35 @@ if [ "$RUN_GO_TESTS" = true ]; then
|
||||||
make build_dev_test
|
make build_dev_test
|
||||||
|
|
||||||
msg_info "▶ Copying device-tests.tar.gz to remote host"
|
msg_info "▶ Copying device-tests.tar.gz to remote host"
|
||||||
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > ${REMOTE_PATH}/device-tests.tar.gz" < device-tests.tar.gz
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > /tmp/device-tests.tar.gz" < device-tests.tar.gz
|
||||||
|
|
||||||
msg_info "▶ Running go tests"
|
msg_info "▶ Running go tests"
|
||||||
TEST_ARGS=""
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" ash << 'EOF'
|
||||||
if [ "$RUN_GO_TESTS_JSON" = true ]; then
|
|
||||||
TEST_ARGS="-json"
|
|
||||||
fi
|
|
||||||
ssh "${REMOTE_USER}@${REMOTE_HOST}" ash << EOF
|
|
||||||
set -e
|
set -e
|
||||||
cd ${REMOTE_PATH}
|
TMP_DIR=$(mktemp -d)
|
||||||
tar zxvf device-tests.tar.gz
|
cd ${TMP_DIR}
|
||||||
PION_LOG_TRACE=all ./run_all_tests $TEST_ARGS
|
tar zxf /tmp/device-tests.tar.gz
|
||||||
|
./gotestsum --format=testdox \
|
||||||
|
--jsonfile=/tmp/device-tests.json \
|
||||||
|
--post-run-command 'sh -c "echo $TESTS_FAILED > /tmp/device-tests.failed"' \
|
||||||
|
--raw-command -- ./run_all_tests -json
|
||||||
|
|
||||||
|
GOTESTSUM_EXIT_CODE=$?
|
||||||
|
if [ $GOTESTSUM_EXIT_CODE -ne 0 ]; then
|
||||||
|
echo "❌ Tests failed (exit code: $GOTESTSUM_EXIT_CODE)"
|
||||||
|
rm -rf ${TMP_DIR} /tmp/device-tests.tar.gz
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TESTS_FAILED=$(cat /tmp/device-tests.failed)
|
||||||
|
if [ "$TESTS_FAILED" -ne 0 ]; then
|
||||||
|
echo "❌ Tests failed $TESTS_FAILED tests failed"
|
||||||
|
rm -rf ${TMP_DIR} /tmp/device-tests.tar.gz
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Tests passed"
|
||||||
|
rm -rf ${TMP_DIR} /tmp/device-tests.tar.gz
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
if [ "$RUN_GO_TESTS_ONLY" = true ]; then
|
if [ "$RUN_GO_TESTS_ONLY" = true ]; then
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -30,9 +30,9 @@ require (
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/vishvananda/netlink v1.3.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
go.bug.st/serial v1.6.2
|
go.bug.st/serial v1.6.2
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.38.0
|
||||||
golang.org/x/net v0.38.0
|
golang.org/x/net v0.40.0
|
||||||
golang.org/x/sys v0.32.0
|
golang.org/x/sys v0.33.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
|
||||||
|
@ -52,6 +52,7 @@ require (
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
|
@ -78,13 +79,14 @@ require (
|
||||||
github.com/pion/turn/v4 v4.0.0 // indirect
|
github.com/pion/turn/v4 v4.0.0 // 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.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 // 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.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/wlynxg/anet v0.0.5 // indirect
|
github.com/wlynxg/anet v0.0.5 // indirect
|
||||||
golang.org/x/arch v0.15.0 // indirect
|
golang.org/x/arch v0.15.0 // indirect
|
||||||
golang.org/x/oauth2 v0.24.0 // indirect
|
golang.org/x/oauth2 v0.24.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
24
go.sum
24
go.sum
|
@ -51,8 +51,8 @@ github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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=
|
||||||
|
@ -147,8 +147,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
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=
|
||||||
|
@ -181,10 +181,10 @@ go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
|
||||||
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -194,10 +194,10 @@ 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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
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=
|
||||||
|
|
|
@ -48,7 +48,7 @@ var FileStateString = map[FileState]string{
|
||||||
FileStateFileContentMatch: "FILE_CONTENT_MATCH",
|
FileStateFileContentMatch: "FILE_CONTENT_MATCH",
|
||||||
FileStateFileWrite: "FILE_WRITE",
|
FileStateFileWrite: "FILE_WRITE",
|
||||||
FileStateMounted: "MOUNTED",
|
FileStateMounted: "MOUNTED",
|
||||||
FileStateMountedConfigFS: "CONFIGFS_MOUNT",
|
FileStateMountedConfigFS: "CONFIGFS_MOUNTED",
|
||||||
FileStateSymlink: "SYMLINK",
|
FileStateSymlink: "SYMLINK",
|
||||||
FileStateSymlinkInOrderConfigFS: "SYMLINK_IN_ORDER_CONFIGFS",
|
FileStateSymlinkInOrderConfigFS: "SYMLINK_IN_ORDER_CONFIGFS",
|
||||||
FileStateTouch: "TOUCH",
|
FileStateTouch: "TOUCH",
|
||||||
|
@ -155,6 +155,10 @@ func (f *RequestedFileChange) String() string {
|
||||||
s = fmt.Sprintf("unknown expected state %d for %s", f.ExpectedState, f.Path)
|
s = fmt.Sprintf("unknown expected state %d for %s", f.ExpectedState, f.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(f.Description) > 0 {
|
||||||
|
s += fmt.Sprintf(" (%s)", f.Description)
|
||||||
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,11 @@ func (c *ChangeSetResolver) doResolveChanges(initial bool) error {
|
||||||
|
|
||||||
for _, key := range c.orderedChanges {
|
for _, key := range c.orderedChanges {
|
||||||
change := c.changesMap[key.(string)]
|
change := c.changesMap[key.(string)]
|
||||||
|
if change == nil {
|
||||||
|
c.l.Error().Str("key", key.(string)).Msg("fileChange not found")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if !initial {
|
if !initial {
|
||||||
change.ResetActionResolution()
|
change.ResetActionResolution()
|
||||||
}
|
}
|
||||||
|
@ -123,7 +128,11 @@ func (c *ChangeSetResolver) applyChanges() error {
|
||||||
|
|
||||||
err := c.changeset.applyChange(change)
|
err := c.changeset.applyChange(change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if change.IgnoreErrors {
|
||||||
|
c.l.Warn().Str("change", change.String()).Err(err).Msg("ignoring error")
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ func checkIfSymlinksInOrder(fc *FileChange, logger *zerolog.Logger) (FileState,
|
||||||
}
|
}
|
||||||
l := logger.With().Str("path", fc.Path).Logger()
|
l := logger.With().Str("path", fc.Path).Logger()
|
||||||
|
|
||||||
if fc.ParamSymlinks == nil || len(fc.ParamSymlinks) == 0 {
|
if len(fc.ParamSymlinks) == 0 {
|
||||||
return FileStateUnknown, fmt.Errorf("no symlinks to check")
|
return FileStateUnknown, fmt.Errorf("no symlinks to check")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package usbgadget
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -158,19 +157,9 @@ func (u *UsbGadget) OverrideGadgetConfig(itemKey string, itemAttr string, value
|
||||||
}
|
}
|
||||||
|
|
||||||
func mountConfigFS(path string) error {
|
func mountConfigFS(path string) error {
|
||||||
_, err := os.Stat(path)
|
err := exec.Command("mount", "-t", "configfs", "none", path).Run()
|
||||||
// TODO: check if it's mounted properly
|
if err != nil {
|
||||||
if err == nil {
|
return fmt.Errorf("failed to mount configfs: %w", err)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = exec.Command("mount", "-t", "configfs", "none", path).Run()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to mount configfs: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("unable to access usb gadget path: %w", err)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -188,12 +177,7 @@ func (u *UsbGadget) Init() error {
|
||||||
|
|
||||||
u.udc = udcs[0]
|
u.udc = udcs[0]
|
||||||
|
|
||||||
err := u.WithTransaction(func() error {
|
err := u.configureUsbGadget(false)
|
||||||
u.tx.MountConfigFS()
|
|
||||||
u.tx.CreateConfigPath()
|
|
||||||
u.tx.WriteGadgetConfig()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return u.logError("unable to initialize USB stack", err)
|
return u.logError("unable to initialize USB stack", err)
|
||||||
}
|
}
|
||||||
|
@ -207,13 +191,22 @@ func (u *UsbGadget) UpdateGadgetConfig() error {
|
||||||
|
|
||||||
u.loadGadgetConfig()
|
u.loadGadgetConfig()
|
||||||
|
|
||||||
err := u.WithTransaction(func() error {
|
err := u.configureUsbGadget(true)
|
||||||
u.tx.WriteGadgetConfig()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return u.logError("unable to update gadget config", err)
|
return u.logError("unable to update gadget config", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UsbGadget) configureUsbGadget(resetUsb bool) error {
|
||||||
|
return u.WithTransaction(func() error {
|
||||||
|
u.tx.MountConfigFS()
|
||||||
|
u.tx.CreateConfigPath()
|
||||||
|
u.tx.WriteGadgetConfig()
|
||||||
|
if resetUsb {
|
||||||
|
u.tx.RebindUsb(true)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -81,11 +81,12 @@ func (tx *UsbGadgetTransaction) addFileChange(component string, change Requested
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *UsbGadgetTransaction) mkdirAll(component string, path string, description string) string {
|
func (tx *UsbGadgetTransaction) mkdirAll(component string, path string, description string, deps []string) string {
|
||||||
return tx.addFileChange(component, RequestedFileChange{
|
return tx.addFileChange(component, RequestedFileChange{
|
||||||
Path: path,
|
Path: path,
|
||||||
ExpectedState: FileStateDirectory,
|
ExpectedState: FileStateDirectory,
|
||||||
Description: description,
|
Description: description,
|
||||||
|
DependsOn: deps,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,14 +132,25 @@ func (tx *UsbGadgetTransaction) MountConfigFS() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *UsbGadgetTransaction) CreateConfigPath() {
|
func (tx *UsbGadgetTransaction) CreateConfigPath() {
|
||||||
tx.mkdirAll("gadget", tx.configC1Path, "create config path")
|
tx.mkdirAll(
|
||||||
|
"gadget",
|
||||||
|
tx.configC1Path,
|
||||||
|
"create config path",
|
||||||
|
[]string{configFSPath},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *UsbGadgetTransaction) WriteGadgetConfig() {
|
func (tx *UsbGadgetTransaction) WriteGadgetConfig() {
|
||||||
// create kvm gadget path
|
// create kvm gadget path
|
||||||
tx.mkdirAll("gadget", tx.kvmGadgetPath, "create kvm gadget path")
|
tx.mkdirAll(
|
||||||
|
"gadget",
|
||||||
|
tx.kvmGadgetPath,
|
||||||
|
"create kvm gadget path",
|
||||||
|
[]string{tx.configC1Path},
|
||||||
|
)
|
||||||
|
|
||||||
deps := make([]string, 0)
|
deps := make([]string, 0)
|
||||||
|
deps = append(deps, tx.kvmGadgetPath)
|
||||||
|
|
||||||
for _, val := range tx.orderedConfigItems {
|
for _, val := range tx.orderedConfigItems {
|
||||||
key := val.key
|
key := val.key
|
||||||
|
@ -188,7 +200,10 @@ func (tx *UsbGadgetTransaction) writeGadgetItemConfig(item gadgetConfigItem, dep
|
||||||
files = append(files, deps...)
|
files = append(files, deps...)
|
||||||
|
|
||||||
gadgetItemPath := joinPath(tx.kvmGadgetPath, item.path)
|
gadgetItemPath := joinPath(tx.kvmGadgetPath, item.path)
|
||||||
files = append(files, tx.mkdirAll(component, gadgetItemPath, "create gadget item directory"))
|
if gadgetItemPath != tx.kvmGadgetPath {
|
||||||
|
gadgetItemDir := tx.mkdirAll(component, gadgetItemPath, "create gadget item directory", files)
|
||||||
|
files = append(files, gadgetItemDir)
|
||||||
|
}
|
||||||
|
|
||||||
beforeChange := make([]string, 0)
|
beforeChange := make([]string, 0)
|
||||||
disableGadgetItemKey := fmt.Sprintf("disable-%s", item.device)
|
disableGadgetItemKey := fmt.Sprintf("disable-%s", item.device)
|
||||||
|
@ -231,7 +246,10 @@ func (tx *UsbGadgetTransaction) writeGadgetItemConfig(item gadgetConfigItem, dep
|
||||||
// create config directory if configAttrs are set
|
// create config directory if configAttrs are set
|
||||||
if len(item.configAttrs) > 0 {
|
if len(item.configAttrs) > 0 {
|
||||||
configItemPath := joinPath(tx.configC1Path, item.configPath)
|
configItemPath := joinPath(tx.configC1Path, item.configPath)
|
||||||
tx.mkdirAll(component, configItemPath, "create config item directory")
|
if configItemPath != tx.configC1Path {
|
||||||
|
configItemDir := tx.mkdirAll(component, configItemPath, "create config item directory", files)
|
||||||
|
files = append(files, configItemDir)
|
||||||
|
}
|
||||||
files = append(files, tx.writeGadgetAttrs(
|
files = append(files, tx.writeGadgetAttrs(
|
||||||
configItemPath,
|
configItemPath,
|
||||||
item.configAttrs,
|
item.configAttrs,
|
||||||
|
@ -301,6 +319,7 @@ func (tx *UsbGadgetTransaction) WriteUDC() {
|
||||||
// bound the gadget to a UDC (USB Device Controller)
|
// bound the gadget to a UDC (USB Device Controller)
|
||||||
path := path.Join(tx.kvmGadgetPath, "UDC")
|
path := path.Join(tx.kvmGadgetPath, "UDC")
|
||||||
tx.addFileChange("udc", RequestedFileChange{
|
tx.addFileChange("udc", RequestedFileChange{
|
||||||
|
Key: "udc",
|
||||||
Path: path,
|
Path: path,
|
||||||
ExpectedState: FileStateFileContentMatch,
|
ExpectedState: FileStateFileContentMatch,
|
||||||
ExpectedContent: []byte(tx.udc),
|
ExpectedContent: []byte(tx.udc),
|
||||||
|
@ -316,6 +335,8 @@ func (tx *UsbGadgetTransaction) RebindUsb(ignoreUnbindError bool) {
|
||||||
ExpectedState: FileStateFileWrite,
|
ExpectedState: FileStateFileWrite,
|
||||||
ExpectedContent: []byte(tx.udc),
|
ExpectedContent: []byte(tx.udc),
|
||||||
Description: "unbind UDC",
|
Description: "unbind UDC",
|
||||||
|
DependsOn: []string{"udc"},
|
||||||
|
IgnoreErrors: ignoreUnbindError,
|
||||||
})
|
})
|
||||||
// bind the gadget to the UDC
|
// bind the gadget to the UDC
|
||||||
tx.addFileChange("udc", RequestedFileChange{
|
tx.addFileChange("udc", RequestedFileChange{
|
||||||
|
@ -324,6 +345,5 @@ func (tx *UsbGadgetTransaction) RebindUsb(ignoreUnbindError bool) {
|
||||||
ExpectedContent: []byte(tx.udc),
|
ExpectedContent: []byte(tx.udc),
|
||||||
Description: "bind UDC",
|
Description: "bind UDC",
|
||||||
DependsOn: []string{path.Join(tx.dwc3Path, "unbind")},
|
DependsOn: []string{path.Join(tx.dwc3Path, "unbind")},
|
||||||
IgnoreErrors: ignoreUnbindError,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
13
jsonrpc.go
13
jsonrpc.go
|
@ -877,17 +877,6 @@ func rpcSetCloudUrl(apiUrl string, appUrl string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentScrollSensitivity string = "default"
|
|
||||||
|
|
||||||
func rpcGetScrollSensitivity() (string, error) {
|
|
||||||
return currentScrollSensitivity, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rpcSetScrollSensitivity(sensitivity string) error {
|
|
||||||
currentScrollSensitivity = sensitivity
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKeyboardMacros() (interface{}, error) {
|
func getKeyboardMacros() (interface{}, error) {
|
||||||
macros := make([]KeyboardMacro, len(config.KeyboardMacros))
|
macros := make([]KeyboardMacro, len(config.KeyboardMacros))
|
||||||
copy(macros, config.KeyboardMacros)
|
copy(macros, config.KeyboardMacros)
|
||||||
|
@ -1053,8 +1042,6 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"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"}},
|
||||||
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
|
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
|
||||||
"getScrollSensitivity": {Func: rpcGetScrollSensitivity},
|
|
||||||
"setScrollSensitivity": {Func: rpcSetScrollSensitivity, Params: []string{"sensitivity"}},
|
|
||||||
"getKeyboardMacros": {Func: getKeyboardMacros},
|
"getKeyboardMacros": {Func: getKeyboardMacros},
|
||||||
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,6 @@ runTest() {
|
||||||
function exit_with_code() {
|
function exit_with_code() {
|
||||||
if [ $EXIT_CODE -ne 0 ]; then
|
if [ $EXIT_CODE -ne 0 ]; then
|
||||||
printf "\e[0;31m❌ Test failed\e[0m\n"
|
printf "\e[0;31m❌ Test failed\e[0m\n"
|
||||||
else
|
|
||||||
printf "\e[0;32m✅ All tests passed\e[0m\n"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit $EXIT_CODE
|
exit $EXIT_CODE
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useResizeObserver } from "usehooks-ts";
|
import { useResizeObserver } from "usehooks-ts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useDeviceSettingsStore,
|
|
||||||
useHidStore,
|
useHidStore,
|
||||||
useMouseStore,
|
useMouseStore,
|
||||||
useRTCStore,
|
useRTCStore,
|
||||||
|
@ -61,7 +60,6 @@ export default function WebRTCVideo() {
|
||||||
useHidStore();
|
useHidStore();
|
||||||
|
|
||||||
// Misc states and hooks
|
// Misc states and hooks
|
||||||
const [blockWheelEvent, setBlockWheelEvent] = useState(false);
|
|
||||||
const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap);
|
const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap);
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
|
|
||||||
|
@ -248,17 +246,8 @@ export default function WebRTCVideo() {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const trackpadSensitivity = useDeviceSettingsStore(state => state.trackpadSensitivity);
|
|
||||||
const mouseSensitivity = useDeviceSettingsStore(state => state.mouseSensitivity);
|
|
||||||
const clampMin = useDeviceSettingsStore(state => state.clampMin);
|
|
||||||
const clampMax = useDeviceSettingsStore(state => state.clampMax);
|
|
||||||
const blockDelay = useDeviceSettingsStore(state => state.blockDelay);
|
|
||||||
const trackpadThreshold = useDeviceSettingsStore(state => state.trackpadThreshold);
|
|
||||||
|
|
||||||
const mouseWheelHandler = useCallback(
|
const mouseWheelHandler = useCallback(
|
||||||
(e: WheelEvent) => {
|
(e: WheelEvent) => {
|
||||||
if (blockWheelEvent) return;
|
|
||||||
|
|
||||||
// Determine if the wheel event is an accel scroll value
|
// Determine if the wheel event is an accel scroll value
|
||||||
const isAccel = Math.abs(e.deltaY) >= 100;
|
const isAccel = Math.abs(e.deltaY) >= 100;
|
||||||
|
|
||||||
|
@ -266,7 +255,7 @@ export default function WebRTCVideo() {
|
||||||
const accelScrollValue = e.deltaY / 100;
|
const accelScrollValue = e.deltaY / 100;
|
||||||
|
|
||||||
// Calculate the no accel scroll value
|
// Calculate the no accel scroll value
|
||||||
const noAccelScrollValue = e.deltaY > 0 ? 1 : (e.deltaY < 0 ? -1 : 0);
|
const noAccelScrollValue = e.deltaY > 0 ? 1 : e.deltaY < 0 ? -1 : 0;
|
||||||
|
|
||||||
// Get scroll value
|
// Get scroll value
|
||||||
const scrollValue = isAccel ? accelScrollValue : noAccelScrollValue;
|
const scrollValue = isAccel ? accelScrollValue : noAccelScrollValue;
|
||||||
|
@ -277,22 +266,9 @@ export default function WebRTCVideo() {
|
||||||
// Invert the clamped scroll value to match expected behavior
|
// Invert the clamped scroll value to match expected behavior
|
||||||
const invertedScrollValue = -clampedScrollValue;
|
const invertedScrollValue = -clampedScrollValue;
|
||||||
|
|
||||||
send("wheelReport", { wheelY : invertedScrollValue });
|
send("wheelReport", { wheelY: invertedScrollValue });
|
||||||
|
|
||||||
// Apply blocking delay
|
|
||||||
setBlockWheelEvent(true);
|
|
||||||
setTimeout(() => setBlockWheelEvent(false), blockDelay);
|
|
||||||
},
|
},
|
||||||
[
|
[send],
|
||||||
blockDelay,
|
|
||||||
blockWheelEvent,
|
|
||||||
clampMax,
|
|
||||||
clampMin,
|
|
||||||
mouseSensitivity,
|
|
||||||
send,
|
|
||||||
trackpadSensitivity,
|
|
||||||
trackpadThreshold,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetMousePosition = useCallback(() => {
|
const resetMousePosition = useCallback(() => {
|
||||||
|
@ -351,11 +327,7 @@ export default function WebRTCVideo() {
|
||||||
// which means the Alt Gr key state would then be "stuck". At this
|
// which means the Alt Gr key state would then be "stuck". At this
|
||||||
// point, we would need to rely on the user to press Alt Gr again
|
// point, we would need to rely on the user to press Alt Gr again
|
||||||
// to properly release the state of that modifier.
|
// to properly release the state of that modifier.
|
||||||
.filter(
|
.filter(modifier => altKey || modifier !== modifiers["AltLeft"])
|
||||||
modifier =>
|
|
||||||
altKey ||
|
|
||||||
(modifier !== modifiers["AltLeft"]),
|
|
||||||
)
|
|
||||||
// Meta: Keep if Meta is pressed or if the key isn't a Meta key
|
// Meta: Keep if Meta is pressed or if the key isn't a Meta key
|
||||||
// Example: If metaKey is true, keep all modifiers
|
// Example: If metaKey is true, keep all modifiers
|
||||||
// If metaKey is false, filter out 0x08 (MetaLeft) and 0x80 (MetaRight)
|
// If metaKey is false, filter out 0x08 (MetaLeft) and 0x80 (MetaRight)
|
||||||
|
@ -716,7 +688,7 @@ export default function WebRTCVideo() {
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
controlsList="nofullscreen"
|
controlsList="nofullscreen"
|
||||||
className={cx(
|
className={cx(
|
||||||
"z-30 max-h-full min-h-[384px] min-w-[512px] max-w-full bg-black/50 object-contain transition-all duration-1000",
|
"z-30 max-h-full min-h-[384px] max-w-full min-w-[512px] bg-black/50 object-contain transition-all duration-1000",
|
||||||
{
|
{
|
||||||
"cursor-none": settings.isCursorHidden,
|
"cursor-none": settings.isCursorHidden,
|
||||||
"opacity-0":
|
"opacity-0":
|
||||||
|
@ -732,7 +704,7 @@ export default function WebRTCVideo() {
|
||||||
{peerConnection?.connectionState == "connected" && (
|
{peerConnection?.connectionState == "connected" && (
|
||||||
<div
|
<div
|
||||||
style={{ animationDuration: "500ms" }}
|
style={{ animationDuration: "500ms" }}
|
||||||
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center"
|
className="animate-slideUpFade pointer-events-none absolute inset-0 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div className="relative h-full w-full rounded-md">
|
<div className="relative h-full w-full rounded-md">
|
||||||
<LoadingVideoOverlay show={isVideoLoading} />
|
<LoadingVideoOverlay show={isVideoLoading} />
|
||||||
|
|
|
@ -321,8 +321,7 @@ export const useSettingsStore = create(
|
||||||
setDeveloperMode: enabled => set({ developerMode: enabled }),
|
setDeveloperMode: enabled => set({ developerMode: enabled }),
|
||||||
|
|
||||||
displayRotation: "270",
|
displayRotation: "270",
|
||||||
setDisplayRotation: (rotation: string) =>
|
setDisplayRotation: (rotation: string) => set({ displayRotation: rotation }),
|
||||||
set({ displayRotation: rotation }),
|
|
||||||
|
|
||||||
backlightSettings: {
|
backlightSettings: {
|
||||||
max_brightness: 100,
|
max_brightness: 100,
|
||||||
|
@ -350,67 +349,6 @@ export interface DeviceSettingsState {
|
||||||
setScrollSensitivity: (sensitivity: DeviceSettingsState["scrollSensitivity"]) => void;
|
setScrollSensitivity: (sensitivity: DeviceSettingsState["scrollSensitivity"]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDeviceSettingsStore = create<DeviceSettingsState>(set => ({
|
|
||||||
trackpadSensitivity: 3.0,
|
|
||||||
mouseSensitivity: 5.0,
|
|
||||||
clampMin: -8,
|
|
||||||
clampMax: 8,
|
|
||||||
blockDelay: 25,
|
|
||||||
trackpadThreshold: 10,
|
|
||||||
|
|
||||||
scrollSensitivity: "default",
|
|
||||||
setScrollSensitivity: sensitivity => {
|
|
||||||
const wheelSettings: Record<
|
|
||||||
DeviceSettingsState["scrollSensitivity"],
|
|
||||||
{
|
|
||||||
trackpadSensitivity: DeviceSettingsState["trackpadSensitivity"];
|
|
||||||
mouseSensitivity: DeviceSettingsState["mouseSensitivity"];
|
|
||||||
clampMin: DeviceSettingsState["clampMin"];
|
|
||||||
clampMax: DeviceSettingsState["clampMax"];
|
|
||||||
blockDelay: DeviceSettingsState["blockDelay"];
|
|
||||||
trackpadThreshold: DeviceSettingsState["trackpadThreshold"];
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
low: {
|
|
||||||
trackpadSensitivity: 2.0,
|
|
||||||
mouseSensitivity: 3.0,
|
|
||||||
clampMin: -6,
|
|
||||||
clampMax: 6,
|
|
||||||
blockDelay: 30,
|
|
||||||
trackpadThreshold: 10,
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
trackpadSensitivity: 3.0,
|
|
||||||
mouseSensitivity: 5.0,
|
|
||||||
clampMin: -8,
|
|
||||||
clampMax: 8,
|
|
||||||
blockDelay: 25,
|
|
||||||
trackpadThreshold: 10,
|
|
||||||
},
|
|
||||||
high: {
|
|
||||||
trackpadSensitivity: 4.0,
|
|
||||||
mouseSensitivity: 6.0,
|
|
||||||
clampMin: -9,
|
|
||||||
clampMax: 9,
|
|
||||||
blockDelay: 20,
|
|
||||||
trackpadThreshold: 10,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const settings = wheelSettings[sensitivity];
|
|
||||||
|
|
||||||
return set({
|
|
||||||
trackpadSensitivity: settings.trackpadSensitivity,
|
|
||||||
trackpadThreshold: settings.trackpadThreshold,
|
|
||||||
mouseSensitivity: settings.mouseSensitivity,
|
|
||||||
clampMin: settings.clampMin,
|
|
||||||
clampMax: settings.clampMax,
|
|
||||||
blockDelay: settings.blockDelay,
|
|
||||||
scrollSensitivity: sensitivity,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export interface RemoteVirtualMediaState {
|
export interface RemoteVirtualMediaState {
|
||||||
source: "WebRTC" | "HTTP" | "Storage" | null;
|
source: "WebRTC" | "HTTP" | "Storage" | null;
|
||||||
mode: "CDROM" | "Disk" | null;
|
mode: "CDROM" | "Disk" | null;
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
import { CheckCircleIcon } from "@heroicons/react/16/solid";
|
import { CheckCircleIcon } from "@heroicons/react/16/solid";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import MouseIcon from "@/assets/mouse-icon.svg";
|
import MouseIcon from "@/assets/mouse-icon.svg";
|
||||||
import PointingFinger from "@/assets/pointing-finger.svg";
|
import PointingFinger from "@/assets/pointing-finger.svg";
|
||||||
import { GridCard } from "@/components/Card";
|
import { GridCard } from "@/components/Card";
|
||||||
import { Checkbox } from "@/components/Checkbox";
|
import { Checkbox } from "@/components/Checkbox";
|
||||||
import { useDeviceSettingsStore, useSettingsStore } from "@/hooks/stores";
|
import { useSettingsStore } from "@/hooks/stores";
|
||||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||||
|
|
||||||
import { FeatureFlag } from "../components/FeatureFlag";
|
|
||||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
|
||||||
import { useFeatureFlag } from "../hooks/useFeatureFlag";
|
import { useFeatureFlag } from "../hooks/useFeatureFlag";
|
||||||
|
import { cx } from "../cva.config";
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
import { SettingsItem } from "./devices.$id.settings";
|
||||||
|
|
||||||
type ScrollSensitivity = "low" | "default" | "high";
|
|
||||||
|
|
||||||
export default function SettingsKeyboardMouseRoute() {
|
export default function SettingsKeyboardMouseRoute() {
|
||||||
const hideCursor = useSettingsStore(state => state.isCursorHidden);
|
const hideCursor = useSettingsStore(state => state.isCursorHidden);
|
||||||
const setHideCursor = useSettingsStore(state => state.setCursorVisibility);
|
const setHideCursor = useSettingsStore(state => state.setCursorVisibility);
|
||||||
|
@ -25,11 +22,6 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
const mouseMode = useSettingsStore(state => state.mouseMode);
|
const mouseMode = useSettingsStore(state => state.mouseMode);
|
||||||
const setMouseMode = useSettingsStore(state => state.setMouseMode);
|
const setMouseMode = useSettingsStore(state => state.setMouseMode);
|
||||||
|
|
||||||
const scrollSensitivity = useDeviceSettingsStore(state => state.scrollSensitivity);
|
|
||||||
const setScrollSensitivity = useDeviceSettingsStore(
|
|
||||||
state => state.setScrollSensitivity,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { isEnabled: isScrollSensitivityEnabled } = useFeatureFlag("0.3.8");
|
const { isEnabled: isScrollSensitivityEnabled } = useFeatureFlag("0.3.8");
|
||||||
|
|
||||||
const [jiggler, setJiggler] = useState(false);
|
const [jiggler, setJiggler] = useState(false);
|
||||||
|
@ -41,14 +33,7 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
setJiggler(resp.result as boolean);
|
setJiggler(resp.result as boolean);
|
||||||
});
|
});
|
||||||
|
}, [isScrollSensitivityEnabled, send]);
|
||||||
if (isScrollSensitivityEnabled) {
|
|
||||||
send("getScrollSensitivity", {}, resp => {
|
|
||||||
if ("error" in resp) return;
|
|
||||||
setScrollSensitivity(resp.result as ScrollSensitivity);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isScrollSensitivityEnabled, send, setScrollSensitivity]);
|
|
||||||
|
|
||||||
const handleJigglerChange = (enabled: boolean) => {
|
const handleJigglerChange = (enabled: boolean) => {
|
||||||
send("setJigglerState", { enabled }, resp => {
|
send("setJigglerState", { enabled }, resp => {
|
||||||
|
@ -62,22 +47,6 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onScrollSensitivityChange = useCallback(
|
|
||||||
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
const sensitivity = e.target.value as ScrollSensitivity;
|
|
||||||
send("setScrollSensitivity", { sensitivity }, resp => {
|
|
||||||
if ("error" in resp) {
|
|
||||||
notifications.error(
|
|
||||||
`Failed to set scroll sensitivity: ${resp.error.data || "Unknown error"}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
notifications.success("Scroll sensitivity set successfully");
|
|
||||||
setScrollSensitivity(sensitivity);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[send, setScrollSensitivity],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsPageHeader
|
<SettingsPageHeader
|
||||||
|
@ -96,28 +65,6 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
<FeatureFlag minAppVersion="0.3.8" name="Scroll Sensitivity">
|
|
||||||
<SettingsItem
|
|
||||||
title="Scroll Sensitivity"
|
|
||||||
description="Adjust the scroll sensitivity"
|
|
||||||
>
|
|
||||||
<SelectMenuBasic
|
|
||||||
size="SM"
|
|
||||||
label=""
|
|
||||||
fullWidth
|
|
||||||
value={scrollSensitivity}
|
|
||||||
onChange={onScrollSensitivityChange}
|
|
||||||
options={
|
|
||||||
[
|
|
||||||
{ label: "Low", value: "low" },
|
|
||||||
{ label: "Default", value: "default" },
|
|
||||||
{ label: "High", value: "high" },
|
|
||||||
] as { label: string; value: ScrollSensitivity }[]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SettingsItem>
|
|
||||||
</FeatureFlag>
|
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
title="Jiggler"
|
title="Jiggler"
|
||||||
description="Simulate movement of a computer mouse. Prevents sleep mode, standby mode or the screensaver from activating"
|
description="Simulate movement of a computer mouse. Prevents sleep mode, standby mode or the screensaver from activating"
|
||||||
|
@ -131,17 +78,19 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
<SettingsItem title="Modes" description="Choose the mouse input mode" />
|
<SettingsItem title="Modes" description="Choose the mouse input mode" />
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
className="block group grow"
|
className="group block grow"
|
||||||
onClick={() => { setMouseMode("absolute"); }}
|
onClick={() => {
|
||||||
|
setMouseMode("absolute");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<GridCard>
|
<GridCard>
|
||||||
<div className="flex items-center px-4 py-3 group gap-x-4">
|
<div className="group flex w-full items-center gap-x-4 px-4 py-3">
|
||||||
<img
|
<img
|
||||||
className="w-6 shrink-0 dark:invert"
|
className="w-6 shrink-0 dark:invert"
|
||||||
src={PointingFinger}
|
src={PointingFinger}
|
||||||
alt="Finger touching a screen"
|
alt="Finger touching a screen"
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between grow">
|
<div className="flex grow items-center justify-between">
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="text-sm font-semibold text-black dark:text-white">
|
<h3 className="text-sm font-semibold text-black dark:text-white">
|
||||||
Absolute
|
Absolute
|
||||||
|
@ -150,32 +99,44 @@ export default function SettingsKeyboardMouseRoute() {
|
||||||
Most convenient
|
Most convenient
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{mouseMode === "absolute" && (
|
<CheckCircleIcon
|
||||||
<CheckCircleIcon className="w-4 h-4 text-blue-700 dark:text-blue-500" />
|
className={cx(
|
||||||
)}
|
"h-4 w-4 text-blue-700 opacity-0 transition dark:text-blue-500",
|
||||||
|
{ "opacity-100": mouseMode === "absolute" },
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GridCard>
|
</GridCard>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="block group grow"
|
className="group block grow"
|
||||||
onClick={() => { setMouseMode("relative"); }}
|
onClick={() => {
|
||||||
|
setMouseMode("relative");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<GridCard>
|
<GridCard>
|
||||||
<div className="flex items-center px-4 py-3 gap-x-4">
|
<div className="flex w-full items-center gap-x-4 px-4 py-3">
|
||||||
<img className="w-6 shrink-0 dark:invert" src={MouseIcon} alt="Mouse icon" />
|
<img
|
||||||
<div className="flex items-center justify-between grow">
|
className="w-6 shrink-0 dark:invert"
|
||||||
|
src={MouseIcon}
|
||||||
|
alt="Mouse icon"
|
||||||
|
/>
|
||||||
|
<div className="flex grow items-center justify-between">
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="text-sm font-semibold text-black dark:text-white">
|
<h3 className="text-sm font-semibold text-black dark:text-white">
|
||||||
Relative
|
Relative
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs leading-none text-slate-800 dark:text-slate-300">
|
<p className="text-xs leading-none text-slate-800 dark:text-slate-300">
|
||||||
Most Compatible (Beta)
|
Most Compatible
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{mouseMode === "relative" && (
|
<CheckCircleIcon
|
||||||
<CheckCircleIcon className="w-4 h-4 text-blue-700 dark:text-blue-500" />
|
className={cx(
|
||||||
)}
|
"h-4 w-4 text-blue-700 opacity-0 transition dark:text-blue-500",
|
||||||
|
{ "opacity-100": mouseMode === "relative" },
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GridCard>
|
</GridCard>
|
||||||
|
|
|
@ -18,11 +18,9 @@ import useWebSocket from "react-use-websocket";
|
||||||
|
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
import {
|
import {
|
||||||
DeviceSettingsState,
|
|
||||||
HidState,
|
HidState,
|
||||||
NetworkState,
|
NetworkState,
|
||||||
UpdateState,
|
UpdateState,
|
||||||
useDeviceSettingsStore,
|
|
||||||
useDeviceStore,
|
useDeviceStore,
|
||||||
useHidStore,
|
useHidStore,
|
||||||
useMountMediaStore,
|
useMountMediaStore,
|
||||||
|
@ -714,21 +712,6 @@ export default function KvmIdRoute() {
|
||||||
});
|
});
|
||||||
}, [appVersion, send, setAppVersion, setSystemVersion]);
|
}, [appVersion, send, setAppVersion, setSystemVersion]);
|
||||||
|
|
||||||
const setScrollSensitivity = useDeviceSettingsStore(
|
|
||||||
state => state.setScrollSensitivity,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialize device settings
|
|
||||||
useEffect(
|
|
||||||
function initializeDeviceSettings() {
|
|
||||||
send("getScrollSensitivity", {}, resp => {
|
|
||||||
if ("error" in resp) return;
|
|
||||||
setScrollSensitivity(resp.result as DeviceSettingsState["scrollSensitivity"]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[send, setScrollSensitivity],
|
|
||||||
);
|
|
||||||
|
|
||||||
const ConnectionStatusElement = useMemo(() => {
|
const ConnectionStatusElement = useMemo(() => {
|
||||||
const hasConnectionFailed =
|
const hasConnectionFailed =
|
||||||
connectionFailed || ["failed", "closed"].includes(peerConnectionState || "");
|
connectionFailed || ["failed", "closed"].includes(peerConnectionState || "");
|
||||||
|
|
Loading…
Reference in New Issue