mirror of https://github.com/jetkvm/kvm.git
Compare commits
14 Commits
05fde74dd9
...
8906504a0c
Author | SHA1 | Date |
---|---|---|
|
8906504a0c | |
|
5e91cfc7fa | |
|
9ffdf0c4a6 | |
|
591d512b11 | |
|
57fbee1490 | |
|
0e65c0a9a9 | |
|
2dafb5c9c1 | |
|
566305549f | |
|
1505c37e4c | |
|
564eee9b00 | |
|
fab575dbe0 | |
|
97958e7b86 | |
|
2f7042df18 | |
|
2cadda4e00 |
|
@ -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
|
63
cloud.go
63
cloud.go
|
@ -24,6 +24,18 @@ type CloudRegisterRequest struct {
|
||||||
ClientId string `json:"clientId"`
|
ClientId string `json:"clientId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CloudWebSocketConnectTimeout is the timeout for the websocket connection to the cloud
|
||||||
|
CloudWebSocketConnectTimeout = 1 * time.Minute
|
||||||
|
// CloudAPIRequestTimeout is the timeout for cloud API requests
|
||||||
|
CloudAPIRequestTimeout = 10 * time.Second
|
||||||
|
// CloudOidcRequestTimeout is the timeout for OIDC token verification requests
|
||||||
|
// should be lower than the websocket response timeout set in cloud-api
|
||||||
|
CloudOidcRequestTimeout = 10 * time.Second
|
||||||
|
// CloudWebSocketPingInterval is the interval at which the websocket client sends ping messages to the cloud
|
||||||
|
CloudWebSocketPingInterval = 15 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
func handleCloudRegister(c *gin.Context) {
|
func handleCloudRegister(c *gin.Context) {
|
||||||
var req CloudRegisterRequest
|
var req CloudRegisterRequest
|
||||||
|
|
||||||
|
@ -44,22 +56,31 @@ func handleCloudRegister(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Post(req.CloudAPI+"/devices/token", "application/json", bytes.NewBuffer(jsonPayload))
|
client := &http.Client{Timeout: CloudAPIRequestTimeout}
|
||||||
|
|
||||||
|
apiReq, err := http.NewRequest(http.MethodPost, config.CloudURL+"/devices/token", bytes.NewBuffer(jsonPayload))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{"error": "Failed to create register request: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
apiResp, err := client.Do(apiReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"error": "Failed to exchange token: " + err.Error()})
|
c.JSON(500, gin.H{"error": "Failed to exchange token: " + err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer apiResp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if apiResp.StatusCode != http.StatusOK {
|
||||||
c.JSON(resp.StatusCode, gin.H{"error": "Failed to exchange token: " + resp.Status})
|
c.JSON(apiResp.StatusCode, gin.H{"error": "Failed to exchange token: " + apiResp.Status})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenResp struct {
|
var tokenResp struct {
|
||||||
SecretToken string `json:"secretToken"`
|
SecretToken string `json:"secretToken"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
|
if err := json.NewDecoder(apiResp.Body).Decode(&tokenResp); err != nil {
|
||||||
c.JSON(500, gin.H{"error": "Failed to parse token response: " + err.Error()})
|
c.JSON(500, gin.H{"error": "Failed to parse token response: " + err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -70,7 +91,7 @@ func handleCloudRegister(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.CloudToken == "" {
|
if config.CloudToken == "" {
|
||||||
logger.Info("Starting websocket client due to adoption")
|
cloudLogger.Info("Starting websocket client due to adoption")
|
||||||
go RunWebsocketClient()
|
go RunWebsocketClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +143,7 @@ func runWebsocketClient() error {
|
||||||
header := http.Header{}
|
header := http.Header{}
|
||||||
header.Set("X-Device-ID", GetDeviceID())
|
header.Set("X-Device-ID", GetDeviceID())
|
||||||
header.Set("Authorization", "Bearer "+config.CloudToken)
|
header.Set("Authorization", "Bearer "+config.CloudToken)
|
||||||
dialCtx, cancelDial := context.WithTimeout(context.Background(), time.Minute)
|
dialCtx, cancelDial := context.WithTimeout(context.Background(), CloudWebSocketConnectTimeout)
|
||||||
defer cancelDial()
|
defer cancelDial()
|
||||||
c, _, err := websocket.Dial(dialCtx, wsURL.String(), &websocket.DialOptions{
|
c, _, err := websocket.Dial(dialCtx, wsURL.String(), &websocket.DialOptions{
|
||||||
HTTPHeader: header,
|
HTTPHeader: header,
|
||||||
|
@ -131,15 +152,15 @@ func runWebsocketClient() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer c.CloseNow()
|
defer c.CloseNow()
|
||||||
logger.Infof("WS connected to %v", wsURL.String())
|
cloudLogger.Infof("websocket connected to %s", wsURL.String())
|
||||||
runCtx, cancelRun := context.WithCancel(context.Background())
|
runCtx, cancelRun := context.WithCancel(context.Background())
|
||||||
defer cancelRun()
|
defer cancelRun()
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(CloudWebSocketPingInterval)
|
||||||
err := c.Ping(runCtx)
|
err := c.Ping(runCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warnf("websocket ping error: %v", err)
|
cloudLogger.Warnf("websocket ping error: %v", err)
|
||||||
cancelRun()
|
cancelRun()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -157,24 +178,30 @@ func runWebsocketClient() error {
|
||||||
var req WebRTCSessionRequest
|
var req WebRTCSessionRequest
|
||||||
err = json.Unmarshal(msg, &req)
|
err = json.Unmarshal(msg, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warnf("unable to parse ws message: %v", string(msg))
|
cloudLogger.Warnf("unable to parse ws message: %v", string(msg))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloudLogger.Infof("new session request: %v", req.OidcGoogle)
|
||||||
|
cloudLogger.Tracef("session request info: %v", req)
|
||||||
|
|
||||||
err = handleSessionRequest(runCtx, c, req)
|
err = handleSessionRequest(runCtx, c, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("error starting new session: %v", err)
|
cloudLogger.Infof("error starting new session: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSessionRequest) error {
|
func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSessionRequest) error {
|
||||||
oidcCtx, cancelOIDC := context.WithTimeout(ctx, time.Minute)
|
oidcCtx, cancelOIDC := context.WithTimeout(ctx, CloudOidcRequestTimeout)
|
||||||
defer cancelOIDC()
|
defer cancelOIDC()
|
||||||
provider, err := oidc.NewProvider(oidcCtx, "https://accounts.google.com")
|
provider, err := oidc.NewProvider(oidcCtx, "https://accounts.google.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to initialize OIDC provider:", err)
|
_ = wsjson.Write(context.Background(), c, gin.H{
|
||||||
|
"error": fmt.Sprintf("failed to initialize OIDC provider: %v", err),
|
||||||
|
})
|
||||||
|
cloudLogger.Errorf("failed to initialize OIDC provider: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +217,7 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess
|
||||||
|
|
||||||
googleIdentity := idToken.Audience[0] + ":" + idToken.Subject
|
googleIdentity := idToken.Audience[0] + ":" + idToken.Subject
|
||||||
if config.GoogleIdentity != googleIdentity {
|
if config.GoogleIdentity != googleIdentity {
|
||||||
|
_ = wsjson.Write(context.Background(), c, gin.H{"error": "google identity mismatch"})
|
||||||
return fmt.Errorf("google identity mismatch")
|
return fmt.Errorf("google identity mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +244,9 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess
|
||||||
_ = peerConn.Close()
|
_ = peerConn.Close()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloudLogger.Info("new session accepted")
|
||||||
|
cloudLogger.Tracef("new session accepted: %v", session)
|
||||||
currentSession = session
|
currentSession = session
|
||||||
_ = wsjson.Write(context.Background(), c, gin.H{"sd": sd})
|
_ = wsjson.Write(context.Background(), c, gin.H{"sd": sd})
|
||||||
return nil
|
return nil
|
||||||
|
@ -225,7 +256,7 @@ func RunWebsocketClient() {
|
||||||
for {
|
for {
|
||||||
err := runWebsocketClient()
|
err := runWebsocketClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Websocket client error:", err)
|
cloudLogger.Errorf("websocket client error: %v", err)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,7 +285,7 @@ func rpcDeregisterDevice() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+config.CloudToken)
|
req.Header.Set("Authorization", "Bearer "+config.CloudToken)
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
client := &http.Client{Timeout: CloudAPIRequestTimeout}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to send deregister request: %w", err)
|
return fmt.Errorf("failed to send deregister request: %w", err)
|
||||||
|
|
|
@ -47,6 +47,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadConfig() {
|
func LoadConfig() {
|
||||||
|
configLock.Lock()
|
||||||
|
defer configLock.Unlock()
|
||||||
|
|
||||||
if config != nil {
|
if config != nil {
|
||||||
logger.Info("config already loaded, skipping")
|
logger.Info("config already loaded, skipping")
|
||||||
return
|
return
|
||||||
|
|
|
@ -58,6 +58,9 @@ make build_dev
|
||||||
# Change directory to the binary output directory
|
# Change directory to the binary output directory
|
||||||
cd bin
|
cd bin
|
||||||
|
|
||||||
|
# Kill any existing instances of the application
|
||||||
|
ssh "${REMOTE_USER}@${REMOTE_HOST}" "killall jetkvm_app_debug || true"
|
||||||
|
|
||||||
# Copy the binary to the remote host
|
# Copy the binary to the remote host
|
||||||
cat jetkvm_app | ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > $REMOTE_PATH/jetkvm_app_debug"
|
cat jetkvm_app | ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > $REMOTE_PATH/jetkvm_app_debug"
|
||||||
|
|
||||||
|
@ -79,8 +82,7 @@ cd "$REMOTE_PATH"
|
||||||
chmod +x jetkvm_app_debug
|
chmod +x jetkvm_app_debug
|
||||||
|
|
||||||
# Run the application in the background
|
# Run the application in the background
|
||||||
./jetkvm_app_debug
|
PION_LOG_TRACE=jetkvm,cloud ./jetkvm_app_debug
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Deployment complete."
|
echo "Deployment complete."
|
||||||
|
|
1
log.go
1
log.go
|
@ -6,3 +6,4 @@ import "github.com/pion/logging"
|
||||||
// ref: https://github.com/pion/webrtc/wiki/Debugging-WebRTC
|
// ref: https://github.com/pion/webrtc/wiki/Debugging-WebRTC
|
||||||
var logger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm")
|
var logger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm")
|
||||||
var usbLogger = logging.NewDefaultLoggerFactory().NewLogger("usb")
|
var usbLogger = logging.NewDefaultLoggerFactory().NewLogger("usb")
|
||||||
|
var cloudLogger = logging.NewDefaultLoggerFactory().NewLogger("cloud")
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if an IP address was provided as an argument
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: $0 <JetKVM IP Address>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ip_address="$1"
|
||||||
|
|
||||||
# Print header
|
# Print header
|
||||||
echo "┌──────────────────────────────────────┐"
|
echo "┌──────────────────────────────────────┐"
|
||||||
echo "│ JetKVM Development Setup │"
|
echo "│ JetKVM Development Setup │"
|
||||||
echo "└──────────────────────────────────────┘"
|
echo "└──────────────────────────────────────┘"
|
||||||
|
|
||||||
# Prompt for IP address
|
|
||||||
printf "Please enter the IP address of your JetKVM device: "
|
|
||||||
read ip_address
|
|
||||||
|
|
||||||
# Validate input is not empty
|
|
||||||
if [ -z "$ip_address" ]; then
|
|
||||||
echo "Error: IP address cannot be empty"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set the environment variable and run Vite
|
# Set the environment variable and run Vite
|
||||||
echo "Starting development server with JetKVM device at: $ip_address"
|
echo "Starting development server with JetKVM device at: $ip_address"
|
||||||
sleep 1
|
sleep 1
|
||||||
JETKVM_PROXY_URL="http://$ip_address" vite dev --mode=device
|
JETKVM_PROXY_URL="http://$ip_address" npx vite dev --mode=device
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default function FieldLabel({
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{description && (
|
{description && (
|
||||||
<span className="my-0.5 text-[13px] font-normal text-slate-600">
|
<span className="my-0.5 text-[13px] font-normal text-slate-600 dark:text-slate-400">
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -34,12 +34,12 @@ export default function FieldLabel({
|
||||||
);
|
);
|
||||||
} else if (as === "span") {
|
} else if (as === "span") {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col select-none">
|
<div className="flex select-none flex-col">
|
||||||
<span className="font-display text-[13px] font-medium leading-snug text-black">
|
<span className="font-display text-[13px] font-medium leading-snug text-black dark:text-white">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
{description && (
|
{description && (
|
||||||
<span className="my-0.5 text-[13px] font-normal text-slate-600">
|
<span className="my-0.5 text-[13px] font-normal text-slate-600 dark:text-slate-400">
|
||||||
{description}
|
{description}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in New Issue