mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-10 06:51:49 +02:00
Compare commits
18 Commits
b084814aea
...
cgroups
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9fb9b856f | ||
|
|
66bca11d36 | ||
|
|
86e87f0d47 | ||
|
|
fadfc5d81d | ||
|
|
fc39ff1e4d | ||
|
|
82ccfc66e0 | ||
|
|
890bad1c39 | ||
|
|
9c458885f1 | ||
|
|
d2aed0dc72 | ||
|
|
3dbcb5d7da | ||
|
|
57a1a8b39e | ||
|
|
ab81c04569 | ||
|
|
0c32be3bea | ||
|
|
81d43fbf6e | ||
|
|
96f441de40 | ||
|
|
0e95caaee9 | ||
|
|
7697a12b42 | ||
|
|
94245a9ba4 |
6
.github/workflows/docker-images.yml
vendored
6
.github/workflows/docker-images.yml
vendored
@@ -93,7 +93,9 @@ jobs:
|
|||||||
|
|
||||||
# https://github.com/docker/login-action
|
# https://github.com/docker/login-action
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
if: github.event_name != 'pull_request'
|
env:
|
||||||
|
password_secret_exists: ${{ secrets[matrix.password_secret] != '' && 'true' || 'false' }}
|
||||||
|
if: github.event_name != 'pull_request' && env.password_secret_exists == 'true'
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ matrix.username || secrets[matrix.username_secret] }}
|
username: ${{ matrix.username || secrets[matrix.username_secret] }}
|
||||||
@@ -108,6 +110,6 @@ jobs:
|
|||||||
context: "${{ matrix.context }}"
|
context: "${{ matrix.context }}"
|
||||||
file: ${{ matrix.dockerfile }}
|
file: ${{ matrix.dockerfile }}
|
||||||
platforms: ${{ matrix.platforms || 'linux/amd64,linux/arm64,linux/arm/v7' }}
|
platforms: ${{ matrix.platforms || 'linux/amd64,linux/arm64,linux/arm/v7' }}
|
||||||
push: ${{ github.ref_type == 'tag' }}
|
push: ${{ github.ref_type == 'tag' && secrets[matrix.password_secret] != '' }}
|
||||||
tags: ${{ steps.metadata.outputs.tags }}
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
labels: ${{ steps.metadata.outputs.labels }}
|
labels: ${{ steps.metadata.outputs.labels }}
|
||||||
|
|||||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -51,3 +51,4 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.TOKEN || secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
|
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
IS_FORK: ${{ github.repository_owner != 'henrygd' }}
|
||||||
|
|||||||
@@ -38,12 +38,25 @@ builds:
|
|||||||
- mips64
|
- mips64
|
||||||
- riscv64
|
- riscv64
|
||||||
- mipsle
|
- mipsle
|
||||||
|
- mips
|
||||||
- ppc64le
|
- ppc64le
|
||||||
|
gomips:
|
||||||
|
- hardfloat
|
||||||
|
- softfloat
|
||||||
ignore:
|
ignore:
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
|
- goos: linux
|
||||||
|
goarch: mips64
|
||||||
|
gomips: softfloat
|
||||||
|
- goos: linux
|
||||||
|
goarch: mipsle
|
||||||
|
gomips: hardfloat
|
||||||
|
- goos: linux
|
||||||
|
goarch: mips
|
||||||
|
gomips: hardfloat
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: darwin
|
- goos: darwin
|
||||||
@@ -54,7 +67,7 @@ builds:
|
|||||||
archives:
|
archives:
|
||||||
- id: beszel-agent
|
- id: beszel-agent
|
||||||
formats: [tar.gz]
|
formats: [tar.gz]
|
||||||
builds:
|
ids:
|
||||||
- beszel-agent
|
- beszel-agent
|
||||||
name_template: >-
|
name_template: >-
|
||||||
{{ .Binary }}_
|
{{ .Binary }}_
|
||||||
@@ -66,7 +79,7 @@ archives:
|
|||||||
|
|
||||||
- id: beszel
|
- id: beszel
|
||||||
formats: [tar.gz]
|
formats: [tar.gz]
|
||||||
builds:
|
ids:
|
||||||
- beszel
|
- beszel
|
||||||
name_template: >-
|
name_template: >-
|
||||||
{{ .Binary }}_
|
{{ .Binary }}_
|
||||||
@@ -85,7 +98,7 @@ nfpms:
|
|||||||
API access.
|
API access.
|
||||||
maintainer: henrygd <hank@henrygd.me>
|
maintainer: henrygd <hank@henrygd.me>
|
||||||
section: net
|
section: net
|
||||||
builds:
|
ids:
|
||||||
- beszel-agent
|
- beszel-agent
|
||||||
formats:
|
formats:
|
||||||
- deb
|
- deb
|
||||||
@@ -122,6 +135,7 @@ scoops:
|
|||||||
homepage: "https://beszel.dev"
|
homepage: "https://beszel.dev"
|
||||||
description: "Agent for Beszel, a lightweight server monitoring platform."
|
description: "Agent for Beszel, a lightweight server monitoring platform."
|
||||||
license: MIT
|
license: MIT
|
||||||
|
skip_upload: "{{ if .Env.IS_FORK }}true{{ else }}auto{{ end }}"
|
||||||
|
|
||||||
# # Needs choco installed, so doesn't build on linux / default gh workflow :(
|
# # Needs choco installed, so doesn't build on linux / default gh workflow :(
|
||||||
# chocolateys:
|
# chocolateys:
|
||||||
@@ -155,7 +169,7 @@ brews:
|
|||||||
homepage: "https://beszel.dev"
|
homepage: "https://beszel.dev"
|
||||||
description: "Agent for Beszel, a lightweight server monitoring platform."
|
description: "Agent for Beszel, a lightweight server monitoring platform."
|
||||||
license: MIT
|
license: MIT
|
||||||
skip_upload: auto
|
skip_upload: "{{ if .Env.IS_FORK }}true{{ else }}auto{{ end }}"
|
||||||
extra_install: |
|
extra_install: |
|
||||||
(bin/"beszel-agent-launcher").write <<~EOS
|
(bin/"beszel-agent-launcher").write <<~EOS
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -187,7 +201,7 @@ winget:
|
|||||||
release_notes_url: "https://github.com/henrygd/beszel/releases/tag/v{{ .Version }}"
|
release_notes_url: "https://github.com/henrygd/beszel/releases/tag/v{{ .Version }}"
|
||||||
publisher_support_url: "https://github.com/henrygd/beszel/issues"
|
publisher_support_url: "https://github.com/henrygd/beszel/issues"
|
||||||
short_description: "Agent for Beszel, a lightweight server monitoring platform."
|
short_description: "Agent for Beszel, a lightweight server monitoring platform."
|
||||||
skip_upload: auto
|
skip_upload: "{{ if .Env.IS_FORK }}true{{ else }}auto{{ end }}"
|
||||||
description: |
|
description: |
|
||||||
Beszel is a lightweight server monitoring platform that includes Docker
|
Beszel is a lightweight server monitoring platform that includes Docker
|
||||||
statistics, historical data, and alert functions. It has a friendly web
|
statistics, historical data, and alert functions. It has a friendly web
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ SKIP_WEB ?= false
|
|||||||
# Set executable extension based on target OS
|
# Set executable extension based on target OS
|
||||||
EXE_EXT := $(if $(filter windows,$(OS)),.exe,)
|
EXE_EXT := $(if $(filter windows,$(OS)),.exe,)
|
||||||
|
|
||||||
.PHONY: tidy build-agent build-hub build clean lint dev-server dev-agent dev-hub dev generate-locales
|
.PHONY: tidy build-agent build-hub build-hub-dev build clean lint dev-server dev-agent dev-hub dev generate-locales
|
||||||
.DEFAULT_GOAL := build
|
.DEFAULT_GOAL := build
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@@ -53,6 +53,10 @@ build-agent: tidy build-dotnet-conditional
|
|||||||
build-hub: tidy $(if $(filter false,$(SKIP_WEB)),build-web-ui)
|
build-hub: tidy $(if $(filter false,$(SKIP_WEB)),build-web-ui)
|
||||||
GOOS=$(OS) GOARCH=$(ARCH) go build -o ./build/beszel_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" beszel/cmd/hub
|
GOOS=$(OS) GOARCH=$(ARCH) go build -o ./build/beszel_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" beszel/cmd/hub
|
||||||
|
|
||||||
|
build-hub-dev: tidy
|
||||||
|
mkdir -p ./site/dist && touch ./site/dist/index.html
|
||||||
|
GOOS=$(OS) GOARCH=$(ARCH) go build -tags development -o ./build/beszel-dev_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" beszel/cmd/hub
|
||||||
|
|
||||||
build: build-agent build-hub
|
build: build-agent build-hub
|
||||||
|
|
||||||
generate-locales:
|
generate-locales:
|
||||||
@@ -73,9 +77,9 @@ dev-hub: export ENV=dev
|
|||||||
dev-hub:
|
dev-hub:
|
||||||
mkdir -p ./site/dist && touch ./site/dist/index.html
|
mkdir -p ./site/dist && touch ./site/dist/index.html
|
||||||
@if command -v entr >/dev/null 2>&1; then \
|
@if command -v entr >/dev/null 2>&1; then \
|
||||||
find ./cmd/hub/*.go ./internal/{alerts,hub,records,users}/*.go | entr -r -s "cd ./cmd/hub && go run . serve --http 0.0.0.0:8090"; \
|
find ./cmd/hub/*.go ./internal/{alerts,hub,records,users}/*.go | entr -r -s "cd ./cmd/hub && go run -tags development . serve --http 0.0.0.0:8090"; \
|
||||||
else \
|
else \
|
||||||
cd ./cmd/hub && go run . serve --http 0.0.0.0:8090; \
|
cd ./cmd/hub && go run -tags development . serve --http 0.0.0.0:8090; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dev-agent:
|
dev-agent:
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"beszel"
|
"beszel"
|
||||||
"beszel/internal/agent"
|
"beszel/internal/agent"
|
||||||
"beszel/internal/agent/health"
|
"beszel/internal/agent/health"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,43 +17,24 @@ import (
|
|||||||
type cmdOptions struct {
|
type cmdOptions struct {
|
||||||
key string // key is the public key(s) for SSH authentication.
|
key string // key is the public key(s) for SSH authentication.
|
||||||
listen string // listen is the address or port to listen on.
|
listen string // listen is the address or port to listen on.
|
||||||
|
// TODO: add hubURL and token
|
||||||
|
// hubURL string // hubURL is the URL of the hub to use.
|
||||||
|
// token string // token is the token to use for authentication.
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parses the command line flags and populates the config struct.
|
// parse parses the command line flags and populates the config struct.
|
||||||
// It returns true if a subcommand was handled and the program should exit.
|
// It returns true if a subcommand was handled and the program should exit.
|
||||||
func (opts *cmdOptions) parse() bool {
|
func (opts *cmdOptions) parse() bool {
|
||||||
flag.StringVar(&opts.key, "key", "", "Public key(s) for SSH authentication")
|
|
||||||
flag.StringVar(&opts.listen, "listen", "", "Address or port to listen on")
|
|
||||||
|
|
||||||
flag.Usage = func() {
|
|
||||||
builder := strings.Builder{}
|
|
||||||
builder.WriteString("Usage: ")
|
|
||||||
builder.WriteString(os.Args[0])
|
|
||||||
builder.WriteString(" [command] [flags]\n")
|
|
||||||
builder.WriteString("\nCommands:\n")
|
|
||||||
builder.WriteString(" health Check if the agent is running\n")
|
|
||||||
builder.WriteString(" help Display this help message\n")
|
|
||||||
builder.WriteString(" update Update to the latest version\n")
|
|
||||||
builder.WriteString("\nFlags:\n")
|
|
||||||
fmt.Print(builder.String())
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
subcommand := ""
|
subcommand := ""
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
subcommand = os.Args[1]
|
subcommand = os.Args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subcommands that don't require any pflag parsing
|
||||||
switch subcommand {
|
switch subcommand {
|
||||||
case "-v", "version":
|
case "-v", "version":
|
||||||
fmt.Println(beszel.AppName+"-agent", beszel.Version)
|
fmt.Println(beszel.AppName+"-agent", beszel.Version)
|
||||||
return true
|
return true
|
||||||
case "help":
|
|
||||||
flag.Usage()
|
|
||||||
return true
|
|
||||||
case "update":
|
|
||||||
agent.Update()
|
|
||||||
return true
|
|
||||||
case "health":
|
case "health":
|
||||||
err := health.Check()
|
err := health.Check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,7 +44,57 @@ func (opts *cmdOptions) parse() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.Parse()
|
// pflag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
|
||||||
|
pflag.StringVarP(&opts.key, "key", "k", "", "Public key(s) for SSH authentication")
|
||||||
|
pflag.StringVarP(&opts.listen, "listen", "l", "", "Address or port to listen on")
|
||||||
|
// pflag.StringVarP(&opts.hubURL, "hub-url", "u", "", "URL of the hub to use")
|
||||||
|
// pflag.StringVarP(&opts.token, "token", "t", "", "Token to use for authentication")
|
||||||
|
chinaMirrors := pflag.BoolP("china-mirrors", "c", false, "Use mirror for update (gh.beszel.dev) instead of GitHub")
|
||||||
|
help := pflag.BoolP("help", "h", false, "Show this help message")
|
||||||
|
|
||||||
|
// Convert old single-dash long flags to double-dash for backward compatibility
|
||||||
|
flagsToConvert := []string{"key", "listen"}
|
||||||
|
for i, arg := range os.Args {
|
||||||
|
for _, flag := range flagsToConvert {
|
||||||
|
singleDash := "-" + flag
|
||||||
|
doubleDash := "--" + flag
|
||||||
|
if arg == singleDash {
|
||||||
|
os.Args[i] = doubleDash
|
||||||
|
break
|
||||||
|
} else if strings.HasPrefix(arg, singleDash+"=") {
|
||||||
|
os.Args[i] = doubleDash + arg[len(singleDash):]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pflag.Usage = func() {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
builder.WriteString("Usage: ")
|
||||||
|
builder.WriteString(os.Args[0])
|
||||||
|
builder.WriteString(" [command] [flags]\n")
|
||||||
|
builder.WriteString("\nCommands:\n")
|
||||||
|
builder.WriteString(" health Check if the agent is running\n")
|
||||||
|
// builder.WriteString(" help Display this help message\n")
|
||||||
|
builder.WriteString(" update Update to the latest version\n")
|
||||||
|
builder.WriteString("\nFlags:\n")
|
||||||
|
fmt.Print(builder.String())
|
||||||
|
pflag.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse all arguments with pflag
|
||||||
|
pflag.Parse()
|
||||||
|
|
||||||
|
// Must run after pflag.Parse()
|
||||||
|
switch {
|
||||||
|
case *help || subcommand == "help":
|
||||||
|
pflag.Usage()
|
||||||
|
return true
|
||||||
|
case subcommand == "update":
|
||||||
|
agent.Update(*chinaMirrors)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"beszel/internal/agent"
|
"beszel/internal/agent"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"flag"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@@ -245,7 +245,7 @@ func TestParseFlags(t *testing.T) {
|
|||||||
oldArgs := os.Args
|
oldArgs := os.Args
|
||||||
defer func() {
|
defer func() {
|
||||||
os.Args = oldArgs
|
os.Args = oldArgs
|
||||||
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -269,6 +269,22 @@ func TestParseFlags(t *testing.T) {
|
|||||||
listen: "",
|
listen: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "key flag double dash",
|
||||||
|
args: []string{"cmd", "--key", "testkey"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "testkey",
|
||||||
|
listen: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key flag short",
|
||||||
|
args: []string{"cmd", "-k", "testkey"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "testkey",
|
||||||
|
listen: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "addr flag only",
|
name: "addr flag only",
|
||||||
args: []string{"cmd", "-listen", ":8080"},
|
args: []string{"cmd", "-listen", ":8080"},
|
||||||
@@ -277,6 +293,22 @@ func TestParseFlags(t *testing.T) {
|
|||||||
listen: ":8080",
|
listen: ":8080",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "addr flag double dash",
|
||||||
|
args: []string{"cmd", "--listen", ":8080"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "",
|
||||||
|
listen: ":8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "addr flag short",
|
||||||
|
args: []string{"cmd", "-l", ":8080"},
|
||||||
|
expected: cmdOptions{
|
||||||
|
key: "",
|
||||||
|
listen: ":8080",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "both flags",
|
name: "both flags",
|
||||||
args: []string{"cmd", "-key", "testkey", "-listen", ":8080"},
|
args: []string{"cmd", "-key", "testkey", "-listen", ":8080"},
|
||||||
@@ -290,12 +322,12 @@ func TestParseFlags(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Reset flags for each test
|
// Reset flags for each test
|
||||||
flag.CommandLine = flag.NewFlagSet(tt.args[0], flag.ExitOnError)
|
pflag.CommandLine = pflag.NewFlagSet(tt.args[0], pflag.ExitOnError)
|
||||||
os.Args = tt.args
|
os.Args = tt.args
|
||||||
|
|
||||||
var opts cmdOptions
|
var opts cmdOptions
|
||||||
opts.parse()
|
opts.parse()
|
||||||
flag.Parse()
|
pflag.Parse()
|
||||||
|
|
||||||
assert.Equal(t, tt.expected, opts)
|
assert.Equal(t, tt.expected, opts)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,11 +45,13 @@ func getBaseApp() *pocketbase.PocketBase {
|
|||||||
baseApp.RootCmd.Use = beszel.AppName
|
baseApp.RootCmd.Use = beszel.AppName
|
||||||
baseApp.RootCmd.Short = ""
|
baseApp.RootCmd.Short = ""
|
||||||
// add update command
|
// add update command
|
||||||
baseApp.RootCmd.AddCommand(&cobra.Command{
|
updateCmd := &cobra.Command{
|
||||||
Use: "update",
|
Use: "update",
|
||||||
Short: "Update " + beszel.AppName + " to the latest version",
|
Short: "Update " + beszel.AppName + " to the latest version",
|
||||||
Run: hub.Update,
|
Run: hub.Update,
|
||||||
})
|
}
|
||||||
|
updateCmd.Flags().Bool("china-mirrors", false, "Use mirror (gh.beszel.dev) instead of GitHub")
|
||||||
|
baseApp.RootCmd.AddCommand(updateCmd)
|
||||||
// add health command
|
// add health command
|
||||||
baseApp.RootCmd.AddCommand(newHealthCmd())
|
baseApp.RootCmd.AddCommand(newHealthCmd())
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ require (
|
|||||||
github.com/shirou/gopsutil/v4 v4.25.6
|
github.com/shirou/gopsutil/v4 v4.25.6
|
||||||
github.com/spf13/cast v1.9.2
|
github.com/spf13/cast v1.9.2
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
|
github.com/spf13/pflag v1.0.7
|
||||||
github.com/stretchr/testify v1.11.0
|
github.com/stretchr/testify v1.11.0
|
||||||
golang.org/x/crypto v0.41.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
||||||
@@ -49,7 +50,6 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/spf13/pflag v1.0.7 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func detectRestarter() restarter {
|
|||||||
|
|
||||||
// Update checks GitHub for a newer release of beszel-agent, applies it,
|
// Update checks GitHub for a newer release of beszel-agent, applies it,
|
||||||
// fixes SELinux context if needed, and restarts the service.
|
// fixes SELinux context if needed, and restarts the service.
|
||||||
func Update() error {
|
func Update(useMirror bool) error {
|
||||||
exePath, _ := os.Executable()
|
exePath, _ := os.Executable()
|
||||||
|
|
||||||
dataDir, err := getDataDir()
|
dataDir, err := getDataDir()
|
||||||
@@ -70,6 +70,7 @@ func Update() error {
|
|||||||
updated, err := ghupdate.Update(ghupdate.Config{
|
updated, err := ghupdate.Update(ghupdate.Config{
|
||||||
ArchiveExecutable: "beszel-agent",
|
ArchiveExecutable: "beszel-agent",
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
|
UseMirror: useMirror,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -99,6 +100,8 @@ func Update() error {
|
|||||||
if err := r.Restart(); err != nil {
|
if err := r.Restart(); err != nil {
|
||||||
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to restart service: %v", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to restart service: %v", err)
|
||||||
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually.")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually.")
|
||||||
|
} else {
|
||||||
|
ghupdate.ColorPrint(ghupdate.ColorGreen, "Service restarted successfully")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ghupdate.ColorPrint(ghupdate.ColorYellow, "No supported init system detected; please restart manually if needed.")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "No supported init system detected; please restart manually if needed.")
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
colorReset = "\033[0m"
|
colorReset = "\033[0m"
|
||||||
ColorYellow = "\033[33m"
|
ColorYellow = "\033[33m"
|
||||||
colorGreen = "\033[32m"
|
ColorGreen = "\033[32m"
|
||||||
colorCyan = "\033[36m"
|
colorCyan = "\033[36m"
|
||||||
colorGray = "\033[90m"
|
colorGray = "\033[90m"
|
||||||
)
|
)
|
||||||
@@ -64,6 +64,10 @@ type Config struct {
|
|||||||
|
|
||||||
// The data directory to use when fetching and downloading the latest release.
|
// The data directory to use when fetching and downloading the latest release.
|
||||||
DataDir string
|
DataDir string
|
||||||
|
|
||||||
|
// UseMirror specifies whether to use the beszel.dev mirror instead of GitHub API.
|
||||||
|
// When false (default), always uses api.github.com. When true, uses gh.beszel.dev.
|
||||||
|
UseMirror bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type updater struct {
|
type updater struct {
|
||||||
@@ -106,21 +110,19 @@ func (p *updater) update() (updated bool, err error) {
|
|||||||
var latest *release
|
var latest *release
|
||||||
var useMirror bool
|
var useMirror bool
|
||||||
|
|
||||||
|
// Determine the API endpoint based on UseMirror flag
|
||||||
|
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", p.config.Owner, p.config.Repo)
|
||||||
|
if p.config.UseMirror {
|
||||||
|
useMirror = true
|
||||||
|
apiURL = fmt.Sprintf("https://gh.beszel.dev/repos/%s/%s/releases/latest?api=true", p.config.Owner, p.config.Repo)
|
||||||
|
ColorPrint(ColorYellow, "Using mirror for update.")
|
||||||
|
}
|
||||||
|
|
||||||
latest, err = fetchLatestRelease(
|
latest, err = fetchLatestRelease(
|
||||||
p.config.Context,
|
p.config.Context,
|
||||||
p.config.HttpClient,
|
p.config.HttpClient,
|
||||||
fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", p.config.Owner, p.config.Repo),
|
apiURL,
|
||||||
)
|
)
|
||||||
// if the first fetch fails, try the beszel.dev API (fallback for China)
|
|
||||||
if err != nil {
|
|
||||||
ColorPrint(ColorYellow, "Failed to fetch release. Trying beszel.dev mirror...")
|
|
||||||
useMirror = true
|
|
||||||
latest, err = fetchLatestRelease(
|
|
||||||
p.config.Context,
|
|
||||||
p.config.HttpClient,
|
|
||||||
fmt.Sprintf("https://gh.beszel.dev/repos/%s/%s/releases/latest?api=true", p.config.Owner, p.config.Repo),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -129,7 +131,7 @@ func (p *updater) update() (updated bool, err error) {
|
|||||||
newVersion := semver.MustParse(strings.TrimPrefix(latest.Tag, "v"))
|
newVersion := semver.MustParse(strings.TrimPrefix(latest.Tag, "v"))
|
||||||
|
|
||||||
if newVersion.LTE(currentVersion) {
|
if newVersion.LTE(currentVersion) {
|
||||||
ColorPrintf(colorGreen, "You already have the latest version %s.", p.currentVersion)
|
ColorPrintf(ColorGreen, "You already have the latest version %s.", p.currentVersion)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,14 +211,11 @@ func (p *updater) update() (updated bool, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ColorPrint(colorGray, "---")
|
ColorPrint(colorGray, "---")
|
||||||
ColorPrint(colorGreen, "Update completed successfully! You can start the executable as usual.")
|
ColorPrint(ColorGreen, "Update completed successfully!")
|
||||||
|
|
||||||
// print the release notes
|
// print the release notes
|
||||||
if latest.Body != "" {
|
if latest.Body != "" {
|
||||||
fmt.Print("\n")
|
fmt.Print("\n")
|
||||||
ColorPrintf(colorCyan, "Here is a list with some of the %s changes:", latest.Tag)
|
|
||||||
// remove the update command note to avoid "stuttering"
|
|
||||||
// (@todo consider moving to a config option)
|
|
||||||
releaseNotes := strings.TrimSpace(strings.Replace(latest.Body, "> _To update the prebuilt executable you can run `./"+p.config.ArchiveExecutable+" update`._", "", 1))
|
releaseNotes := strings.TrimSpace(strings.Replace(latest.Body, "> _To update the prebuilt executable you can run `./"+p.config.ArchiveExecutable+" update`._", "", 1))
|
||||||
ColorPrint(colorCyan, releaseNotes)
|
ColorPrint(colorCyan, releaseNotes)
|
||||||
fmt.Print("\n")
|
fmt.Print("\n")
|
||||||
|
|||||||
@@ -8,13 +8,10 @@ import (
|
|||||||
"beszel/internal/hub/systems"
|
"beszel/internal/hub/systems"
|
||||||
"beszel/internal/records"
|
"beszel/internal/records"
|
||||||
"beszel/internal/users"
|
"beszel/internal/users"
|
||||||
"beszel/site"
|
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -115,6 +112,8 @@ func (h *Hub) initialize(e *core.ServeEvent) error {
|
|||||||
// set URL if BASE_URL env is set
|
// set URL if BASE_URL env is set
|
||||||
if h.appURL != "" {
|
if h.appURL != "" {
|
||||||
settings.Meta.AppURL = h.appURL
|
settings.Meta.AppURL = h.appURL
|
||||||
|
} else {
|
||||||
|
h.appURL = settings.Meta.AppURL
|
||||||
}
|
}
|
||||||
if err := e.App.Save(settings); err != nil {
|
if err := e.App.Save(settings); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -164,55 +163,6 @@ func (h *Hub) initialize(e *core.ServeEvent) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// startServer sets up the server for Beszel
|
|
||||||
func (h *Hub) startServer(se *core.ServeEvent) error {
|
|
||||||
// TODO: exclude dev server from production binary
|
|
||||||
switch h.IsDev() {
|
|
||||||
case true:
|
|
||||||
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "localhost:5173",
|
|
||||||
})
|
|
||||||
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
|
||||||
proxy.ServeHTTP(e.Response, e.Request)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
// parse app url
|
|
||||||
parsedURL, err := url.Parse(h.appURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// fix base paths in html if using subpath
|
|
||||||
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
|
||||||
indexFile, _ := fs.ReadFile(site.DistDirFS, "index.html")
|
|
||||||
indexContent := strings.ReplaceAll(string(indexFile), "./", basePath)
|
|
||||||
indexContent = strings.Replace(indexContent, "{{V}}", beszel.Version, 1)
|
|
||||||
indexContent = strings.Replace(indexContent, "{{HUB_URL}}", h.appURL, 1)
|
|
||||||
// set up static asset serving
|
|
||||||
staticPaths := [2]string{"/static/", "/assets/"}
|
|
||||||
serveStatic := apis.Static(site.DistDirFS, false)
|
|
||||||
// get CSP configuration
|
|
||||||
csp, cspExists := GetEnv("CSP")
|
|
||||||
// add route
|
|
||||||
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
|
||||||
// serve static assets if path is in staticPaths
|
|
||||||
for i := range staticPaths {
|
|
||||||
if strings.Contains(e.Request.URL.Path, staticPaths[i]) {
|
|
||||||
e.Response.Header().Set("Cache-Control", "public, max-age=2592000")
|
|
||||||
return serveStatic(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cspExists {
|
|
||||||
e.Response.Header().Del("X-Frame-Options")
|
|
||||||
e.Response.Header().Set("Content-Security-Policy", csp)
|
|
||||||
}
|
|
||||||
return e.HTML(http.StatusOK, indexContent)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerCronJobs sets up scheduled tasks
|
// registerCronJobs sets up scheduled tasks
|
||||||
func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
|
func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
|
||||||
// delete old system_stats and alerts_history records once every hour
|
// delete old system_stats and alerts_history records once every hour
|
||||||
|
|||||||
79
beszel/internal/hub/server_development.go
Normal file
79
beszel/internal/hub/server_development.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
//go:build development
|
||||||
|
|
||||||
|
package hub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"beszel"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wraps http.RoundTripper to modify dev proxy HTML responses
|
||||||
|
type responseModifier struct {
|
||||||
|
transport http.RoundTripper
|
||||||
|
hub *Hub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := rm.transport.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
// Only modify HTML responses
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
if !strings.Contains(contentType, "text/html") {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
// Create a new response with the modified body
|
||||||
|
modifiedBody := rm.modifyHTML(string(body))
|
||||||
|
resp.Body = io.NopCloser(strings.NewReader(modifiedBody))
|
||||||
|
resp.ContentLength = int64(len(modifiedBody))
|
||||||
|
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(modifiedBody)))
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm *responseModifier) modifyHTML(html string) string {
|
||||||
|
parsedURL, err := url.Parse(rm.hub.appURL)
|
||||||
|
if err != nil {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
// fix base paths in html if using subpath
|
||||||
|
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
||||||
|
html = strings.ReplaceAll(html, "./", basePath)
|
||||||
|
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
||||||
|
html = strings.Replace(html, "{{HUB_URL}}", rm.hub.appURL, 1)
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
// startServer sets up the development server for Beszel
|
||||||
|
func (h *Hub) startServer(se *core.ServeEvent) error {
|
||||||
|
slog.Info("starting server", "appURL", h.appURL)
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "localhost:5173",
|
||||||
|
})
|
||||||
|
|
||||||
|
proxy.Transport = &responseModifier{
|
||||||
|
transport: http.DefaultTransport,
|
||||||
|
hub: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
||||||
|
proxy.ServeHTTP(e.Response, e.Request)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
51
beszel/internal/hub/server_production.go
Normal file
51
beszel/internal/hub/server_production.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//go:build !development
|
||||||
|
|
||||||
|
package hub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"beszel"
|
||||||
|
"beszel/site"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// startServer sets up the production server for Beszel
|
||||||
|
func (h *Hub) startServer(se *core.ServeEvent) error {
|
||||||
|
// parse app url
|
||||||
|
parsedURL, err := url.Parse(h.appURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// fix base paths in html if using subpath
|
||||||
|
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
||||||
|
indexFile, _ := fs.ReadFile(site.DistDirFS, "index.html")
|
||||||
|
html := strings.ReplaceAll(string(indexFile), "./", basePath)
|
||||||
|
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
||||||
|
html = strings.Replace(html, "{{HUB_URL}}", h.appURL, 1)
|
||||||
|
// set up static asset serving
|
||||||
|
staticPaths := [2]string{"/static/", "/assets/"}
|
||||||
|
serveStatic := apis.Static(site.DistDirFS, false)
|
||||||
|
// get CSP configuration
|
||||||
|
csp, cspExists := GetEnv("CSP")
|
||||||
|
// add route
|
||||||
|
se.Router.GET("/{path...}", func(e *core.RequestEvent) error {
|
||||||
|
// serve static assets if path is in staticPaths
|
||||||
|
for i := range staticPaths {
|
||||||
|
if strings.Contains(e.Request.URL.Path, staticPaths[i]) {
|
||||||
|
e.Response.Header().Set("Cache-Control", "public, max-age=2592000")
|
||||||
|
return serveStatic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cspExists {
|
||||||
|
e.Response.Header().Del("X-Frame-Options")
|
||||||
|
e.Response.Header().Set("Content-Security-Policy", csp)
|
||||||
|
}
|
||||||
|
return e.HTML(http.StatusOK, html)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Update updates beszel to the latest version
|
// Update updates beszel to the latest version
|
||||||
func Update(_ *cobra.Command, _ []string) {
|
func Update(cmd *cobra.Command, _ []string) {
|
||||||
dataDir := os.TempDir()
|
dataDir := os.TempDir()
|
||||||
|
|
||||||
// set dataDir to ./beszel_data if it exists
|
// set dataDir to ./beszel_data if it exists
|
||||||
@@ -19,9 +19,13 @@ func Update(_ *cobra.Command, _ []string) {
|
|||||||
dataDir = "./beszel_data"
|
dataDir = "./beszel_data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if china-mirrors flag is set
|
||||||
|
useMirror, _ := cmd.Flags().GetBool("china-mirrors")
|
||||||
|
|
||||||
updated, err := ghupdate.Update(ghupdate.Config{
|
updated, err := ghupdate.Update(ghupdate.Config{
|
||||||
ArchiveExecutable: "beszel",
|
ArchiveExecutable: "beszel",
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
|
UseMirror: useMirror,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -49,13 +53,13 @@ func restartService() {
|
|||||||
// Check if beszel service exists and is active
|
// Check if beszel service exists and is active
|
||||||
cmd := exec.Command("systemctl", "is-active", "beszel.service")
|
cmd := exec.Command("systemctl", "is-active", "beszel.service")
|
||||||
if err := cmd.Run(); err == nil {
|
if err := cmd.Run(); err == nil {
|
||||||
fmt.Println("Restarting beszel service...")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Restarting beszel service...")
|
||||||
restartCmd := exec.Command("systemctl", "restart", "beszel.service")
|
restartCmd := exec.Command("systemctl", "restart", "beszel.service")
|
||||||
if err := restartCmd.Run(); err != nil {
|
if err := restartCmd.Run(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to restart service: %v\n", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: Failed to restart service: %v\n", err)
|
||||||
fmt.Println("Please restart the service manually: sudo systemctl restart beszel")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually: sudo systemctl restart beszel")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Service restarted successfully")
|
ghupdate.ColorPrint(ghupdate.ColorGreen, "Service restarted successfully")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -65,17 +69,17 @@ func restartService() {
|
|||||||
if _, err := exec.LookPath("rc-service"); err == nil {
|
if _, err := exec.LookPath("rc-service"); err == nil {
|
||||||
cmd := exec.Command("rc-service", "beszel", "status")
|
cmd := exec.Command("rc-service", "beszel", "status")
|
||||||
if err := cmd.Run(); err == nil {
|
if err := cmd.Run(); err == nil {
|
||||||
fmt.Println("Restarting beszel service...")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Restarting beszel service...")
|
||||||
restartCmd := exec.Command("rc-service", "beszel", "restart")
|
restartCmd := exec.Command("rc-service", "beszel", "restart")
|
||||||
if err := restartCmd.Run(); err != nil {
|
if err := restartCmd.Run(); err != nil {
|
||||||
fmt.Printf("Warning: Failed to restart service: %v\n", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: Failed to restart service: %v\n", err)
|
||||||
fmt.Println("Please restart the service manually: sudo rc-service beszel restart")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Please restart the service manually: sudo rc-service beszel restart")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Service restarted successfully")
|
ghupdate.ColorPrint(ghupdate.ColorGreen, "Service restarted successfully")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Note: Service restart not attempted. If running as a service, restart manually.")
|
ghupdate.ColorPrint(ghupdate.ColorYellow, "Service restart not attempted. If running as a service, restart manually.")
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
188
beszel/site/package-lock.json
generated
188
beszel/site/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.12.5",
|
"version": "0.12.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.12.5",
|
"version": "0.12.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@henrygd/queue": "^1.0.7",
|
"@henrygd/queue": "^1.0.7",
|
||||||
"@henrygd/semaphore": "^0.0.2",
|
"@henrygd/semaphore": "^0.0.2",
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.15",
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
@@ -80,7 +81,7 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.27.1",
|
"@babel/helper-validator-identifier": "^7.27.1",
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
"version": "7.28.0",
|
"version": "7.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
|
||||||
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
|
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -105,7 +106,7 @@
|
|||||||
"version": "7.28.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
|
||||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
"version": "7.28.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
||||||
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.28.3",
|
"@babel/parser": "^7.28.3",
|
||||||
@@ -153,7 +154,7 @@
|
|||||||
"version": "7.27.2",
|
"version": "7.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
|
||||||
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
|
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/compat-data": "^7.27.2",
|
"@babel/compat-data": "^7.27.2",
|
||||||
@@ -170,7 +171,7 @@
|
|||||||
"version": "7.28.0",
|
"version": "7.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -180,7 +181,7 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
||||||
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/traverse": "^7.27.1",
|
"@babel/traverse": "^7.27.1",
|
||||||
@@ -194,7 +195,7 @@
|
|||||||
"version": "7.28.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
|
||||||
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
|
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-module-imports": "^7.27.1",
|
"@babel/helper-module-imports": "^7.27.1",
|
||||||
@@ -212,7 +213,7 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -222,7 +223,7 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
||||||
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -242,7 +243,7 @@
|
|||||||
"version": "7.28.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
|
||||||
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
|
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.27.2",
|
"@babel/template": "^7.27.2",
|
||||||
@@ -256,7 +257,7 @@
|
|||||||
"version": "7.28.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
|
||||||
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
|
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.28.2"
|
"@babel/types": "^7.28.2"
|
||||||
@@ -284,7 +285,7 @@
|
|||||||
"version": "7.27.2",
|
"version": "7.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@@ -299,7 +300,7 @@
|
|||||||
"version": "7.28.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
|
||||||
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
|
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@@ -318,7 +319,7 @@
|
|||||||
"version": "7.28.2",
|
"version": "7.28.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.27.1",
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
@@ -854,7 +855,7 @@
|
|||||||
"version": "29.6.3",
|
"version": "29.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
|
||||||
"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
|
"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sinclair/typebox": "^0.27.8"
|
"@sinclair/typebox": "^0.27.8"
|
||||||
@@ -867,7 +868,7 @@
|
|||||||
"version": "29.6.3",
|
"version": "29.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
|
||||||
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
|
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/schemas": "^29.6.3",
|
"@jest/schemas": "^29.6.3",
|
||||||
@@ -885,7 +886,7 @@
|
|||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -907,7 +908,7 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -917,14 +918,14 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.30",
|
"version": "0.3.30",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
|
||||||
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
|
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -945,7 +946,7 @@
|
|||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-lingui-macro/-/babel-plugin-lingui-macro-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lingui/babel-plugin-lingui-macro/-/babel-plugin-lingui-macro-5.4.1.tgz",
|
||||||
"integrity": "sha512-9IO+PDvdneY8OCI8zvI1oDXpzryTMtyRv7uq9O0U1mFCvIPVd5dWQKQDu/CpgpYAc2+JG/izn5PNl9xzPc6ckw==",
|
"integrity": "sha512-9IO+PDvdneY8OCI8zvI1oDXpzryTMtyRv7uq9O0U1mFCvIPVd5dWQKQDu/CpgpYAc2+JG/izn5PNl9xzPc6ckw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.20.12",
|
"@babel/core": "^7.20.12",
|
||||||
@@ -1165,7 +1166,7 @@
|
|||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-5.4.1.tgz",
|
||||||
"integrity": "sha512-aDkj/bMSr/mCL8Nr1TS52v0GLCuVa4YqtRz+WvUCFZw/ovVInX0hKq1TClx/bSlhu60FzB/CbclxFMBw8aLVUg==",
|
"integrity": "sha512-aDkj/bMSr/mCL8Nr1TS52v0GLCuVa4YqtRz+WvUCFZw/ovVInX0hKq1TClx/bSlhu60FzB/CbclxFMBw8aLVUg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.20.13",
|
"@babel/runtime": "^7.20.13",
|
||||||
@@ -2584,7 +2585,7 @@
|
|||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
|
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core": {
|
"node_modules/@swc/core": {
|
||||||
@@ -3130,6 +3131,23 @@
|
|||||||
"react-dom": ">=16.8"
|
"react-dom": ">=16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/react-virtual": {
|
||||||
|
"version": "3.13.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
||||||
|
"integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/virtual-core": "3.13.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/table-core": {
|
"node_modules/@tanstack/table-core": {
|
||||||
"version": "8.21.3",
|
"version": "8.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
|
||||||
@@ -3143,6 +3161,16 @@
|
|||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/virtual-core": {
|
||||||
|
"version": "3.13.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
|
||||||
|
"integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/bun": {
|
"node_modules/@types/bun": {
|
||||||
"version": "1.2.20",
|
"version": "1.2.20",
|
||||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.20.tgz",
|
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.2.20.tgz",
|
||||||
@@ -3227,14 +3255,14 @@
|
|||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||||
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
|
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/istanbul-lib-report": {
|
"node_modules/@types/istanbul-lib-report": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
|
||||||
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
|
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/istanbul-lib-coverage": "*"
|
"@types/istanbul-lib-coverage": "*"
|
||||||
@@ -3244,7 +3272,7 @@
|
|||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
|
||||||
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
|
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
@@ -3254,7 +3282,7 @@
|
|||||||
"version": "24.3.0",
|
"version": "24.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
|
||||||
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.10.0"
|
"undici-types": "~7.10.0"
|
||||||
@@ -3264,7 +3292,7 @@
|
|||||||
"version": "19.1.11",
|
"version": "19.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz",
|
||||||
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
|
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -3274,7 +3302,7 @@
|
|||||||
"version": "19.1.7",
|
"version": "19.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
|
||||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
@@ -3284,7 +3312,7 @@
|
|||||||
"version": "17.0.33",
|
"version": "17.0.33",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||||
"integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
|
"integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/yargs-parser": "*"
|
"@types/yargs-parser": "*"
|
||||||
@@ -3294,7 +3322,7 @@
|
|||||||
"version": "21.0.3",
|
"version": "21.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
|
||||||
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vitejs/plugin-react-swc": {
|
"node_modules/@vitejs/plugin-react-swc": {
|
||||||
@@ -3331,7 +3359,7 @@
|
|||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
@@ -3374,7 +3402,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/aria-hidden": {
|
"node_modules/aria-hidden": {
|
||||||
@@ -3469,7 +3497,7 @@
|
|||||||
"version": "4.25.1",
|
"version": "4.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
|
||||||
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
|
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -3540,7 +3568,7 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -3550,7 +3578,7 @@
|
|||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||||
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -3563,7 +3591,7 @@
|
|||||||
"version": "1.0.30001727",
|
"version": "1.0.30001727",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
||||||
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
|
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -3584,7 +3612,7 @@
|
|||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
@@ -3696,7 +3724,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
@@ -3709,7 +3737,7 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/colors": {
|
"node_modules/colors": {
|
||||||
@@ -3726,14 +3754,14 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cosmiconfig": {
|
"node_modules/cosmiconfig": {
|
||||||
"version": "8.3.6",
|
"version": "8.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||||
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"import-fresh": "^3.3.0",
|
"import-fresh": "^3.3.0",
|
||||||
@@ -3913,7 +3941,7 @@
|
|||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -3983,7 +4011,7 @@
|
|||||||
"version": "1.5.182",
|
"version": "1.5.182",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz",
|
||||||
"integrity": "sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==",
|
"integrity": "sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
@@ -4011,7 +4039,7 @@
|
|||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-arrayish": "^0.2.1"
|
"is-arrayish": "^0.2.1"
|
||||||
@@ -4080,7 +4108,7 @@
|
|||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -4168,7 +4196,7 @@
|
|||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -4194,7 +4222,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -4225,7 +4253,7 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parent-module": "^1.0.0",
|
"parent-module": "^1.0.0",
|
||||||
@@ -4258,7 +4286,7 @@
|
|||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/is-binary-path": {
|
"node_modules/is-binary-path": {
|
||||||
@@ -4351,7 +4379,7 @@
|
|||||||
"version": "29.6.3",
|
"version": "29.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
|
||||||
"integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
|
"integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
@@ -4361,7 +4389,7 @@
|
|||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
|
||||||
"integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
|
"integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
@@ -4379,7 +4407,7 @@
|
|||||||
"version": "1.21.7",
|
"version": "1.21.7",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
@@ -4401,7 +4429,7 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
@@ -4414,7 +4442,7 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jsesc": "bin/jsesc"
|
"jsesc": "bin/jsesc"
|
||||||
@@ -4427,14 +4455,14 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
@@ -4447,7 +4475,7 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||||
"integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
|
"integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -4696,7 +4724,7 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
@@ -4745,7 +4773,7 @@
|
|||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
@@ -4856,7 +4884,7 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@@ -4897,7 +4925,7 @@
|
|||||||
"version": "2.0.19",
|
"version": "2.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
@@ -4993,7 +5021,7 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"callsites": "^3.0.0"
|
"callsites": "^3.0.0"
|
||||||
@@ -5006,7 +5034,7 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
@@ -5035,7 +5063,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -5045,7 +5073,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@@ -5107,7 +5135,7 @@
|
|||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
||||||
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
|
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/schemas": "^29.6.3",
|
"@jest/schemas": "^29.6.3",
|
||||||
@@ -5122,7 +5150,7 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -5368,7 +5396,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
@@ -5466,7 +5494,7 @@
|
|||||||
"version": "6.3.1",
|
"version": "6.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -5649,7 +5677,7 @@
|
|||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-flag": "^4.0.0"
|
"has-flag": "^4.0.0"
|
||||||
@@ -5783,7 +5811,7 @@
|
|||||||
"version": "5.9.2",
|
"version": "5.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -5797,14 +5825,14 @@
|
|||||||
"version": "7.10.0",
|
"version": "7.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||||
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -6136,7 +6164,7 @@
|
|||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.12.5",
|
"version": "0.12.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "lingui extract --overwrite && lingui compile && vite build",
|
"build": "lingui extract --overwrite && lingui compile && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"sync": "lingui extract --overwrite && lingui compile",
|
"sync": "lingui extract --overwrite && lingui compile",
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"@radix-ui/react-toast": "^1.2.15",
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { $publicKey } from "@/lib/stores"
|
import { $publicKey } from "@/lib/stores"
|
||||||
import { cn, generateToken, tokenMap, useLocalStorage } from "@/lib/utils"
|
import { cn, generateToken, tokenMap, useBrowserStorage } from "@/lib/utils"
|
||||||
import { pb, isReadOnlyUser } from "@/lib/api"
|
import { pb, isReadOnlyUser } from "@/lib/api"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
|
||||||
@@ -77,7 +77,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
const port = useRef<HTMLInputElement>(null)
|
const port = useRef<HTMLInputElement>(null)
|
||||||
const [hostValue, setHostValue] = useState(system?.host ?? "")
|
const [hostValue, setHostValue] = useState(system?.host ?? "")
|
||||||
const isUnixSocket = hostValue.startsWith("/")
|
const isUnixSocket = hostValue.startsWith("/")
|
||||||
const [tab, setTab] = useLocalStorage("as-tab", "docker")
|
const [tab, setTab] = useBrowserStorage("as-tab", "docker")
|
||||||
const [token, setToken] = useState(system?.token ?? "")
|
const [token, setToken] = useState(system?.token ?? "")
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -133,7 +133,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
|
|||||||
>
|
>
|
||||||
<Tabs defaultValue={tab} onValueChange={setTab}>
|
<Tabs defaultValue={tab} onValueChange={setTab}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="mb-2">
|
<DialogTitle className="mb-2 max-w-100 truncate pr-8">
|
||||||
{system ? `${t`Edit`} ${system?.name}` : <Trans>Add New System</Trans>}
|
{system ? `${t`Edit`} ${system?.name}` : <Trans>Add New System</Trans>}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const alertsHistoryColumns: ColumnDef<AlertsHistoryRecord>[] = [
|
|||||||
<Trans>System</Trans>
|
<Trans>System</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => <span className="ps-2">{row.original.expand?.system?.name || row.original.system}</span>,
|
cell: ({ row }) => (
|
||||||
|
<div className="ps-2 max-w-60 truncate">{row.original.expand?.system?.name || row.original.system}</div>
|
||||||
|
),
|
||||||
filterFn: (row, _, filterValue) => {
|
filterFn: (row, _, filterValue) => {
|
||||||
const display = row.original.expand?.system?.name || row.original.system || ""
|
const display = row.original.expand?.system?.name || row.original.system || ""
|
||||||
return display.toLowerCase().includes(filterValue.toLowerCase())
|
return display.toLowerCase().includes(filterValue.toLowerCase())
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export const AlertDialogContent = memo(function AlertDialogContent({ system }: {
|
|||||||
<TabsList className="mb-1 -mt-0.5">
|
<TabsList className="mb-1 -mt-0.5">
|
||||||
<TabsTrigger value="system">
|
<TabsTrigger value="system">
|
||||||
<ServerIcon className="me-2 h-3.5 w-3.5" />
|
<ServerIcon className="me-2 h-3.5 w-3.5" />
|
||||||
{system.name}
|
<span className="truncate max-w-60">{system.name}</span>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="global">
|
<TabsTrigger value="global">
|
||||||
<GlobeIcon className="me-1.5 h-3.5 w-3.5" />
|
<GlobeIcon className="me-1.5 h-3.5 w-3.5" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, chartMargin } from "@/lib/utils"
|
import { cn, formatShortDate, chartMargin } from "@/lib/utils"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
import { ChartData, SystemStatsRecord } from "@/types"
|
import { ChartData, SystemStatsRecord } from "@/types"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
import { useYAxisWidth, cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils"
|
import { cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils"
|
||||||
// import Spinner from '../spinner'
|
// import Spinner from '../spinner'
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { $containerFilter, $userSettings } from "@/lib/stores"
|
import { $containerFilter, $userSettings } from "@/lib/stores"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { ChartType, Unit } from "@/lib/enums"
|
import { ChartType, Unit } from "@/lib/enums"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function ContainerChart({
|
export default memo(function ContainerChart({
|
||||||
dataKey,
|
dataKey,
|
||||||
chartData,
|
chartData,
|
||||||
chartType,
|
chartType,
|
||||||
|
chartConfig,
|
||||||
unit = "%",
|
unit = "%",
|
||||||
}: {
|
}: {
|
||||||
dataKey: string
|
dataKey: string
|
||||||
chartData: ChartData
|
chartData: ChartData
|
||||||
chartType: ChartType
|
chartType: ChartType
|
||||||
|
chartConfig: ChartConfig
|
||||||
unit?: string
|
unit?: string
|
||||||
}) {
|
}) {
|
||||||
const filter = useStore($containerFilter)
|
const filter = useStore($containerFilter)
|
||||||
@@ -28,40 +31,6 @@ export default memo(function ContainerChart({
|
|||||||
|
|
||||||
const isNetChart = chartType === ChartType.Network
|
const isNetChart = chartType === ChartType.Network
|
||||||
|
|
||||||
const chartConfig = useMemo(() => {
|
|
||||||
const config = {} as Record<string, { label: string; color: string }>
|
|
||||||
const totalUsage = new Map<string, number>()
|
|
||||||
|
|
||||||
// calculate total usage of each container
|
|
||||||
for (const stats of containerData) {
|
|
||||||
for (const key in stats) {
|
|
||||||
if (!key || key === "created") continue
|
|
||||||
|
|
||||||
const currentTotal = totalUsage.get(key) ?? 0
|
|
||||||
const increment = isNetChart
|
|
||||||
? (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
|
|
||||||
: // @ts-ignore
|
|
||||||
stats[key]?.[dataKey] ?? 0
|
|
||||||
|
|
||||||
totalUsage.set(key, currentTotal + increment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort keys and generate colors based on usage
|
|
||||||
const sortedEntries = Array.from(totalUsage.entries()).sort(([, a], [, b]) => b - a)
|
|
||||||
|
|
||||||
const length = sortedEntries.length
|
|
||||||
sortedEntries.forEach(([key], i) => {
|
|
||||||
const hue = ((i * 360) / length) % 360
|
|
||||||
config[key] = {
|
|
||||||
label: key,
|
|
||||||
color: `hsl(${hue}, 60%, 55%)`,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return config satisfies ChartConfig
|
|
||||||
}, [chartData])
|
|
||||||
|
|
||||||
const { toolTipFormatter, dataFunction, tickFormatter } = useMemo(() => {
|
const { toolTipFormatter, dataFunction, tickFormatter } = useMemo(() => {
|
||||||
const obj = {} as {
|
const obj = {} as {
|
||||||
toolTipFormatter: (item: any, key: string) => React.ReactNode | string
|
toolTipFormatter: (item: any, key: string) => React.ReactNode | string
|
||||||
@@ -119,7 +88,14 @@ export default memo(function ContainerChart({
|
|||||||
return obj
|
return obj
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const filterLower = filter?.toLowerCase()
|
// Filter with set lookup
|
||||||
|
const filteredKeys = useMemo(() => {
|
||||||
|
if (!filter) {
|
||||||
|
return new Set<string>()
|
||||||
|
}
|
||||||
|
const filterLower = filter.toLowerCase()
|
||||||
|
return new Set(Object.keys(chartConfig).filter((key) => !key.toLowerCase().includes(filterLower)))
|
||||||
|
}, [chartConfig, filter])
|
||||||
|
|
||||||
// console.log('rendered at', new Date())
|
// console.log('rendered at', new Date())
|
||||||
|
|
||||||
@@ -162,9 +138,9 @@ export default memo(function ContainerChart({
|
|||||||
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
|
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
|
||||||
/>
|
/>
|
||||||
{Object.keys(chartConfig).map((key) => {
|
{Object.keys(chartConfig).map((key) => {
|
||||||
const filtered = filterLower && !key.toLowerCase().includes(filterLower)
|
const filtered = filteredKeys.has(key)
|
||||||
let fillOpacity = filtered ? 0.05 : 0.4
|
const fillOpacity = filtered ? 0.05 : 0.4
|
||||||
let strokeOpacity = filtered ? 0.1 : 1
|
const strokeOpacity = filtered ? 0.1 : 1
|
||||||
return (
|
return (
|
||||||
<Area
|
<Area
|
||||||
key={key}
|
key={key}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
import { cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { useLingui } from "@lingui/react/macro"
|
import { useLingui } from "@lingui/react/macro"
|
||||||
import { Unit } from "@/lib/enums"
|
import { Unit } from "@/lib/enums"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function DiskChart({
|
export default memo(function DiskChart({
|
||||||
dataKey,
|
dataKey,
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
xAxis,
|
xAxis,
|
||||||
} from "@/components/ui/chart"
|
} from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
import { cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|||||||
107
beszel/site/src/components/charts/hooks.ts
Normal file
107
beszel/site/src/components/charts/hooks.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { useMemo, useState } from "react"
|
||||||
|
import { ChartConfig } from "@/components/ui/chart"
|
||||||
|
import { ChartData } from "@/types"
|
||||||
|
|
||||||
|
/** Chart configurations for CPU, memory, and network usage charts */
|
||||||
|
export interface ContainerChartConfigs {
|
||||||
|
cpu: ChartConfig
|
||||||
|
memory: ChartConfig
|
||||||
|
network: ChartConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates chart configurations for container metrics visualization
|
||||||
|
* @param containerData - Array of container statistics data points
|
||||||
|
* @returns Chart configurations for CPU, memory, and network metrics
|
||||||
|
*/
|
||||||
|
export function useContainerChartConfigs(containerData: ChartData["containerData"]): ContainerChartConfigs {
|
||||||
|
return useMemo(() => {
|
||||||
|
const configs = {
|
||||||
|
cpu: {} as ChartConfig,
|
||||||
|
memory: {} as ChartConfig,
|
||||||
|
network: {} as ChartConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate usage metrics for each container
|
||||||
|
const totalUsage = {
|
||||||
|
cpu: new Map<string, number>(),
|
||||||
|
memory: new Map<string, number>(),
|
||||||
|
network: new Map<string, number>(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each data point to calculate totals
|
||||||
|
for (let i = 0; i < containerData.length; i++) {
|
||||||
|
const stats = containerData[i]
|
||||||
|
const containerNames = Object.keys(stats)
|
||||||
|
|
||||||
|
for (let j = 0; j < containerNames.length; j++) {
|
||||||
|
const containerName = containerNames[j]
|
||||||
|
// Skip metadata field
|
||||||
|
if (containerName === "created") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerStats = stats[containerName]
|
||||||
|
if (!containerStats) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate metrics for CPU, memory, and network
|
||||||
|
const currentCpu = totalUsage.cpu.get(containerName) ?? 0
|
||||||
|
const currentMemory = totalUsage.memory.get(containerName) ?? 0
|
||||||
|
const currentNetwork = totalUsage.network.get(containerName) ?? 0
|
||||||
|
|
||||||
|
totalUsage.cpu.set(containerName, currentCpu + (containerStats.c ?? 0))
|
||||||
|
totalUsage.memory.set(containerName, currentMemory + (containerStats.m ?? 0))
|
||||||
|
totalUsage.network.set(containerName, currentNetwork + (containerStats.nr ?? 0) + (containerStats.ns ?? 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate chart configurations for each metric type
|
||||||
|
Object.entries(totalUsage).forEach(([chartType, usageMap]) => {
|
||||||
|
const sortedContainers = Array.from(usageMap.entries()).sort(([, a], [, b]) => b - a)
|
||||||
|
const chartConfig = {} as Record<string, { label: string; color: string }>
|
||||||
|
const count = sortedContainers.length
|
||||||
|
|
||||||
|
// Generate colors for each container
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const [containerName] = sortedContainers[i]
|
||||||
|
const hue = ((i * 360) / count) % 360
|
||||||
|
chartConfig[containerName] = {
|
||||||
|
label: containerName,
|
||||||
|
color: `hsl(${hue}, 60%, 55%)`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configs[chartType as keyof typeof configs] = chartConfig
|
||||||
|
})
|
||||||
|
|
||||||
|
return configs
|
||||||
|
}, [containerData])
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the correct width of the y axis in recharts based on the longest label */
|
||||||
|
export function useYAxisWidth() {
|
||||||
|
const [yAxisWidth, setYAxisWidth] = useState(0)
|
||||||
|
let maxChars = 0
|
||||||
|
let timeout: ReturnType<typeof setTimeout>
|
||||||
|
function updateYAxisWidth(str: string) {
|
||||||
|
if (str.length > maxChars) {
|
||||||
|
maxChars = str.length
|
||||||
|
const div = document.createElement("div")
|
||||||
|
div.className = "text-xs tabular-nums tracking-tighter table sr-only"
|
||||||
|
div.innerHTML = str
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
document.body.appendChild(div)
|
||||||
|
const width = div.offsetWidth + 24
|
||||||
|
if (width > yAxisWidth) {
|
||||||
|
setYAxisWidth(div.offsetWidth + 24)
|
||||||
|
}
|
||||||
|
document.body.removeChild(div)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return { yAxisWidth, updateYAxisWidth }
|
||||||
|
}
|
||||||
@@ -8,10 +8,11 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
xAxis,
|
xAxis,
|
||||||
} from "@/components/ui/chart"
|
} from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
import { cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
||||||
import { ChartData, SystemStats } from "@/types"
|
import { ChartData, SystemStats } from "@/types"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, decimalString, formatShortDate, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
import { cn, decimalString, formatShortDate, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { useLingui } from "@lingui/react/macro"
|
import { useLingui } from "@lingui/react/macro"
|
||||||
import { Unit } from "@/lib/enums"
|
import { Unit } from "@/lib/enums"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function MemChart({ chartData, showMax }: { chartData: ChartData; showMax: boolean }) {
|
export default memo(function MemChart({ chartData, showMax }: { chartData: ChartData; showMax: boolean }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { t } from "@lingui/core/macro"
|
|||||||
|
|
||||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { useYAxisWidth, cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
import { cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
import { $userSettings } from "@/lib/stores"
|
import { $userSettings } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|||||||
@@ -8,19 +8,12 @@ import {
|
|||||||
ChartTooltipContent,
|
ChartTooltipContent,
|
||||||
xAxis,
|
xAxis,
|
||||||
} from "@/components/ui/chart"
|
} from "@/components/ui/chart"
|
||||||
import {
|
import { cn, formatShortDate, toFixedFloat, chartMargin, formatTemperature, decimalString } from "@/lib/utils"
|
||||||
useYAxisWidth,
|
|
||||||
cn,
|
|
||||||
formatShortDate,
|
|
||||||
toFixedFloat,
|
|
||||||
chartMargin,
|
|
||||||
formatTemperature,
|
|
||||||
decimalString,
|
|
||||||
} from "@/lib/utils"
|
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
import { $temperatureFilter, $userSettings } from "@/lib/stores"
|
import { $temperatureFilter, $userSettings } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
||||||
const filter = useStore($temperatureFilter)
|
const filter = useStore($temperatureFilter)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default memo(function CommandPalette({ open, setOpen }: { open: boolean;
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Server className="me-2 size-4" />
|
<Server className="me-2 size-4" />
|
||||||
<span>{system.name}</span>
|
<span className="max-w-60 truncate">{system.name}</span>
|
||||||
<CommandShortcut>{getHostDisplayValue(system)}</CommandShortcut>
|
<CommandShortcut>{getHostDisplayValue(system)}</CommandShortcut>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Suspense, memo, useEffect, useMemo } from "react"
|
import { Suspense, memo, useEffect, useMemo } from "react"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
||||||
import { $alerts, $systems } from "@/lib/stores"
|
import { $alerts, $allSystemsById } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { GithubIcon } from "lucide-react"
|
import { GithubIcon } from "lucide-react"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { getSystemNameFromId } from "@/lib/utils"
|
import { AlertRecord } from "@/types"
|
||||||
import { pb, updateRecordList, updateSystemList } from "@/lib/api"
|
|
||||||
import { AlertRecord, SystemRecord } from "@/types"
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||||
import { $router, Link } from "../router"
|
import { $router, Link } from "../router"
|
||||||
import { Plural, Trans, useLingui } from "@lingui/react/macro"
|
import { Plural, Trans, useLingui } from "@lingui/react/macro"
|
||||||
@@ -14,8 +12,6 @@ import { getPagePath } from "@nanostores/router"
|
|||||||
import { alertInfo } from "@/lib/alerts"
|
import { alertInfo } from "@/lib/alerts"
|
||||||
import SystemsTable from "@/components/systems-table/systems-table"
|
import SystemsTable from "@/components/systems-table/systems-table"
|
||||||
|
|
||||||
// const SystemsTable = lazy(() => import("../systems-table/systems-table"))
|
|
||||||
|
|
||||||
export default memo(function () {
|
export default memo(function () {
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
|
|
||||||
@@ -23,19 +19,6 @@ export default memo(function () {
|
|||||||
document.title = t`Dashboard` + " / Beszel"
|
document.title = t`Dashboard` + " / Beszel"
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// make sure we have the latest list of systems
|
|
||||||
updateSystemList()
|
|
||||||
|
|
||||||
// subscribe to real time updates for systems / alerts
|
|
||||||
pb.collection<SystemRecord>("systems").subscribe("*", (e) => {
|
|
||||||
updateRecordList(e, $systems)
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
pb.collection("systems").unsubscribe("*")
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => (
|
() => (
|
||||||
<>
|
<>
|
||||||
@@ -44,7 +27,7 @@ export default memo(function () {
|
|||||||
<SystemsTable />
|
<SystemsTable />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 text-xs opacity-80">
|
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 mb-4 text-xs opacity-80">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/henrygd/beszel"
|
href="https://github.com/henrygd/beszel"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -69,6 +52,7 @@ export default memo(function () {
|
|||||||
|
|
||||||
const ActiveAlerts = () => {
|
const ActiveAlerts = () => {
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
|
const systems = useStore($allSystemsById)
|
||||||
|
|
||||||
const { activeAlerts, alertsKey } = useMemo(() => {
|
const { activeAlerts, alertsKey } = useMemo(() => {
|
||||||
const activeAlerts: AlertRecord[] = []
|
const activeAlerts: AlertRecord[] = []
|
||||||
@@ -112,7 +96,7 @@ const ActiveAlerts = () => {
|
|||||||
>
|
>
|
||||||
<info.icon className="h-4 w-4" />
|
<info.icon className="h-4 w-4" />
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
{getSystemNameFromId(alert.system)} {info.name().toLowerCase().replace("cpu", "CPU")}
|
{systems[alert.system]?.name} {info.name().toLowerCase().replace("cpu", "CPU")}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
{alert.name === "Status" ? (
|
{alert.name === "Status" ? (
|
||||||
@@ -125,7 +109,7 @@ const ActiveAlerts = () => {
|
|||||||
)}
|
)}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name: getSystemNameFromId(alert.system) })}
|
href={getPagePath($router, "system", { name: systems[alert.system]?.name })}
|
||||||
className="absolute inset-0 w-full h-full"
|
className="absolute inset-0 w-full h-full"
|
||||||
aria-label="View system"
|
aria-label="View system"
|
||||||
></Link>
|
></Link>
|
||||||
|
|||||||
@@ -273,13 +273,13 @@ export default function AlertsHistoryDataTable() {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<tr key={headerGroup.id} className="border-border/50">
|
||||||
{headerGroup.headers.map((header) => (
|
{headerGroup.headers.map((header) => (
|
||||||
<TableHead className="px-2" key={header.id}>
|
<TableHead className="px-2" key={header.id}>
|
||||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default function SettingsLayout() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="pt-5 px-4 pb-8 min-h-96 sm:pt-6 sm:px-7">
|
<Card className="pt-5 px-4 pb-8 min-h-96 mb-14 sm:pt-6 sm:px-7">
|
||||||
<CardHeader className="p-0">
|
<CardHeader className="p-0">
|
||||||
<CardTitle className="mb-1">
|
<CardTitle className="mb-1">
|
||||||
<Trans>Settings</Trans>
|
<Trans>Settings</Trans>
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
|
|||||||
<div className="rounded-md border overflow-hidden w-full mt-4">
|
<div className="rounded-md border overflow-hidden w-full mt-4">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<tr className="border-border/50">
|
||||||
{headerCols.map((col) => (
|
{headerCols.map((col) => (
|
||||||
<TableHead key={col.label} style={{ minWidth: col.w }}>
|
<TableHead key={col.label} style={{ minWidth: col.w }}>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
@@ -288,12 +288,14 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
|
|||||||
</span>
|
</span>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
</TableRow>
|
</tr>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody className="whitespace-pre">
|
<TableBody className="whitespace-pre">
|
||||||
{fingerprints.map((fingerprint, i) => (
|
{fingerprints.map((fingerprint, i) => (
|
||||||
<TableRow key={i}>
|
<TableRow key={i}>
|
||||||
<TableCell className="font-medium ps-5 py-2">{fingerprint.expand.system.name}</TableCell>
|
<TableCell className="font-medium ps-5 py-2 max-w-60 truncate">
|
||||||
|
{fingerprint.expand.system.name}
|
||||||
|
</TableCell>
|
||||||
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.token}</TableCell>
|
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.token}</TableCell>
|
||||||
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.fingerprint}</TableCell>
|
<TableCell className="font-mono text-[0.95em] py-2">{fingerprint.fingerprint}</TableCell>
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import {
|
|||||||
$direction,
|
$direction,
|
||||||
$maxValues,
|
$maxValues,
|
||||||
$temperatureFilter,
|
$temperatureFilter,
|
||||||
|
$allSystemsByName,
|
||||||
} from "@/lib/stores"
|
} from "@/lib/stores"
|
||||||
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
|
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
|
||||||
|
import { useContainerChartConfigs } from "@/components/charts/hooks"
|
||||||
import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums"
|
import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums"
|
||||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } from "react"
|
import React, { memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } from "react"
|
||||||
import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card"
|
import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card"
|
||||||
@@ -26,7 +28,7 @@ import {
|
|||||||
listen,
|
listen,
|
||||||
parseSemVer,
|
parseSemVer,
|
||||||
toFixedFloat,
|
toFixedFloat,
|
||||||
useLocalStorage,
|
useBrowserStorage,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import { getPbTimestamp, pb } from "@/lib/api"
|
import { getPbTimestamp, pb } from "@/lib/api"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
@@ -49,6 +51,7 @@ import SwapChart from "@/components/charts/swap-chart"
|
|||||||
import TemperatureChart from "@/components/charts/temperature-chart"
|
import TemperatureChart from "@/components/charts/temperature-chart"
|
||||||
import GpuPowerChart from "@/components/charts/gpu-power-chart"
|
import GpuPowerChart from "@/components/charts/gpu-power-chart"
|
||||||
import LoadAverageChart from "@/components/charts/load-average-chart"
|
import LoadAverageChart from "@/components/charts/load-average-chart"
|
||||||
|
import { subscribeKeys } from "nanostores"
|
||||||
|
|
||||||
const cache = new Map<string, any>()
|
const cache = new Map<string, any>()
|
||||||
|
|
||||||
@@ -117,13 +120,13 @@ function dockerOrPodman(str: string, system: SystemRecord) {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SystemDetail({ name }: { name: string }) {
|
export default memo(function SystemDetail({ name }: { name: string }) {
|
||||||
const direction = useStore($direction)
|
const direction = useStore($direction)
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
const systems = useStore($systems)
|
const systems = useStore($systems)
|
||||||
const chartTime = useStore($chartTime)
|
const chartTime = useStore($chartTime)
|
||||||
const maxValues = useStore($maxValues)
|
const maxValues = useStore($maxValues)
|
||||||
const [grid, setGrid] = useLocalStorage("grid", true)
|
const [grid, setGrid] = useBrowserStorage("grid", true)
|
||||||
const [system, setSystem] = useState({} as SystemRecord)
|
const [system, setSystem] = useState({} as SystemRecord)
|
||||||
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
|
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
|
||||||
const [containerData, setContainerData] = useState([] as ChartData["containerData"])
|
const [containerData, setContainerData] = useState([] as ChartData["containerData"])
|
||||||
@@ -149,36 +152,13 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
}, [name])
|
}, [name])
|
||||||
|
|
||||||
// function resetCharts() {
|
// find matching system and update when it changes
|
||||||
// setSystemStats([])
|
|
||||||
// setContainerData([])
|
|
||||||
// }
|
|
||||||
|
|
||||||
// useEffect(resetCharts, [chartTime])
|
|
||||||
|
|
||||||
// find matching system
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (system.id && system.name === name) {
|
return subscribeKeys($allSystemsByName, [name], (newSystems) => {
|
||||||
return
|
const sys = newSystems[name]
|
||||||
}
|
sys?.id && setSystem(sys)
|
||||||
const matchingSystem = systems.find((s) => s.name === name) as SystemRecord
|
|
||||||
if (matchingSystem) {
|
|
||||||
setSystem(matchingSystem)
|
|
||||||
}
|
|
||||||
}, [name, system, systems])
|
|
||||||
|
|
||||||
// update system when new data is available
|
|
||||||
useEffect(() => {
|
|
||||||
if (!system.id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pb.collection<SystemRecord>("systems").subscribe(system.id, (e) => {
|
|
||||||
setSystem(e.record)
|
|
||||||
})
|
})
|
||||||
return () => {
|
}, [name])
|
||||||
pb.collection("systems").unsubscribe(system.id)
|
|
||||||
}
|
|
||||||
}, [system.id])
|
|
||||||
|
|
||||||
const chartData: ChartData = useMemo(() => {
|
const chartData: ChartData = useMemo(() => {
|
||||||
const lastCreated = Math.max(
|
const lastCreated = Math.max(
|
||||||
@@ -195,6 +175,9 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
}, [systemStats, containerData, direction])
|
}, [systemStats, containerData, direction])
|
||||||
|
|
||||||
|
// Share chart config computation for all container charts
|
||||||
|
const containerChartConfigs = useContainerChartConfigs(containerData)
|
||||||
|
|
||||||
// get stats
|
// get stats
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!system.id || !chartTime) {
|
if (!system.id || !chartTime) {
|
||||||
@@ -391,7 +374,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="chartwrap" className="grid gap-4 mb-10 overflow-x-clip">
|
<div id="chartwrap" className="grid gap-4 mb-14 overflow-x-clip">
|
||||||
{/* system info */}
|
{/* system info */}
|
||||||
<Card>
|
<Card>
|
||||||
<div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
|
<div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
|
||||||
@@ -503,7 +486,12 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
description={t`Average CPU utilization of containers`}
|
description={t`Average CPU utilization of containers`}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
<ContainerChart chartData={chartData} dataKey="c" chartType={ChartType.CPU} />
|
<ContainerChart
|
||||||
|
chartData={chartData}
|
||||||
|
dataKey="c"
|
||||||
|
chartType={ChartType.CPU}
|
||||||
|
chartConfig={containerChartConfigs.cpu}
|
||||||
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -525,7 +513,12 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
description={dockerOrPodman(t`Memory usage of docker containers`, system)}
|
description={dockerOrPodman(t`Memory usage of docker containers`, system)}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
<ContainerChart chartData={chartData} dataKey="m" chartType={ChartType.Memory} />
|
<ContainerChart
|
||||||
|
chartData={chartData}
|
||||||
|
dataKey="m"
|
||||||
|
chartType={ChartType.Memory}
|
||||||
|
chartConfig={containerChartConfigs.memory}
|
||||||
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -627,8 +620,12 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
description={dockerOrPodman(t`Network traffic of docker containers`, system)}
|
description={dockerOrPodman(t`Network traffic of docker containers`, system)}
|
||||||
cornerEl={containerFilterBar}
|
cornerEl={containerFilterBar}
|
||||||
>
|
>
|
||||||
{/* @ts-ignore */}
|
<ContainerChart
|
||||||
<ContainerChart chartData={chartData} chartType={ChartType.Network} dataKey="n" />
|
chartData={chartData}
|
||||||
|
chartType={ChartType.Network}
|
||||||
|
dataKey="n"
|
||||||
|
chartConfig={containerChartConfigs.network}
|
||||||
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -835,7 +832,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
{bottomSpacing > 0 && <span className="block" style={{ height: bottomSpacing }} />}
|
{bottomSpacing > 0 && <span className="block" style={{ height: bottomSpacing }} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
|
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
|
||||||
const containerFilter = useStore(store)
|
const containerFilter = useStore(store)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
|
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { $userSettings } from "@/lib/stores"
|
import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
|
||||||
import { Trans, useLingui } from "@lingui/react/macro"
|
import { Trans, useLingui } from "@lingui/react/macro"
|
||||||
import { useMemo, useRef, useState } from "react"
|
import { useMemo, useRef, useState } from "react"
|
||||||
import { memo } from "react"
|
import { memo } from "react"
|
||||||
@@ -72,7 +72,8 @@ const STATUS_COLORS = {
|
|||||||
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
|
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
size: 200,
|
// size: 200,
|
||||||
|
size: 100,
|
||||||
minSize: 0,
|
minSize: 0,
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
id: "system",
|
id: "system",
|
||||||
@@ -111,11 +112,15 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
Icon: ServerIcon,
|
Icon: ServerIcon,
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const { name } = info.row.original
|
const { name } = info.row.original
|
||||||
|
const longestName = useStore($longestSystemNameLen)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1 md:pe-5">
|
<span className="flex gap-2 items-center font-medium text-sm text-nowrap md:ps-1">
|
||||||
<IndicatorDot system={info.row.original} />
|
<IndicatorDot system={info.row.original} />
|
||||||
{name}
|
{/* NOTE: change to 1 ch if switching to monospace font */}
|
||||||
|
<span className="truncate" style={{ width: `${longestName / 1.1}ch` }}>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name })}
|
href={getPagePath($router, "system", { name })}
|
||||||
@@ -326,9 +331,9 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
|||||||
STATUS_COLORS.down
|
STATUS_COLORS.down
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
<div className="flex gap-2 items-center tabular-nums tracking-tight w-full">
|
||||||
<span className="min-w-8">{decimalString(val, val >= 10 ? 1 : 2)}%</span>
|
<span className="min-w-8 shrink-0">{decimalString(val, val >= 10 ? 1 : 2)}%</span>
|
||||||
<span className="grow min-w-8 grid bg-muted h-[1em] rounded-sm overflow-hidden">
|
<span className="flex-1 min-w-8 grid bg-muted h-[1em] rounded-sm overflow-hidden">
|
||||||
<span className={meterClass} style={{ width: `${val}%` }}></span>
|
<span className={meterClass} style={{ width: `${val}%` }}></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Table as TableType,
|
Table as TableType,
|
||||||
} from "@tanstack/react-table"
|
} from "@tanstack/react-table"
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -35,10 +35,10 @@ import {
|
|||||||
EyeIcon,
|
EyeIcon,
|
||||||
FilterIcon,
|
FilterIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { memo, useEffect, useMemo, useState } from "react"
|
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { $systems } from "@/lib/stores"
|
import { $pausedSystems, $downSystems, $upSystems, $systems } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { cn, runOnce, useLocalStorage } from "@/lib/utils"
|
import { cn, runOnce, useBrowserStorage } from "@/lib/utils"
|
||||||
import { $router, Link } from "../router"
|
import { $router, Link } from "../router"
|
||||||
import { useLingui, Trans } from "@lingui/react/macro"
|
import { useLingui, Trans } from "@lingui/react/macro"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
|
||||||
@@ -47,21 +47,28 @@ import { getPagePath } from "@nanostores/router"
|
|||||||
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
|
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
|
||||||
import AlertButton from "../alerts/alert-button"
|
import AlertButton from "../alerts/alert-button"
|
||||||
import { SystemStatus } from "@/lib/enums"
|
import { SystemStatus } from "@/lib/enums"
|
||||||
|
import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"
|
||||||
|
|
||||||
type ViewMode = "table" | "grid"
|
type ViewMode = "table" | "grid"
|
||||||
type StatusFilter = "all" | "up" | "down" | "paused"
|
type StatusFilter = "all" | SystemRecord["status"]
|
||||||
|
|
||||||
const preloadSystemDetail = runOnce(() => import("@/components/routes/system.tsx"))
|
const preloadSystemDetail = runOnce(() => import("@/components/routes/system.tsx"))
|
||||||
|
|
||||||
export default function SystemsTable() {
|
export default function SystemsTable() {
|
||||||
const data = useStore($systems)
|
const data = useStore($systems)
|
||||||
|
const downSystems = $downSystems.get()
|
||||||
|
const upSystems = $upSystems.get()
|
||||||
|
const pausedSystems = $pausedSystems.get()
|
||||||
const { i18n, t } = useLingui()
|
const { i18n, t } = useLingui()
|
||||||
const [filter, setFilter] = useState<string>()
|
const [filter, setFilter] = useState<string>()
|
||||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
|
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
|
||||||
const [sorting, setSorting] = useState<SortingState>([{ id: "system", desc: false }])
|
const [sorting, setSorting] = useBrowserStorage<SortingState>(
|
||||||
|
"sortMode",
|
||||||
|
[{ id: "system", desc: false }],
|
||||||
|
sessionStorage
|
||||||
|
)
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||||
const [columnVisibility, setColumnVisibility] = useLocalStorage<VisibilityState>("cols", {})
|
const [columnVisibility, setColumnVisibility] = useBrowserStorage<VisibilityState>("cols", {})
|
||||||
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("viewMode", window.innerWidth > 1024 ? "table" : "grid")
|
|
||||||
|
|
||||||
const locale = i18n.locale
|
const locale = i18n.locale
|
||||||
|
|
||||||
@@ -70,9 +77,21 @@ export default function SystemsTable() {
|
|||||||
if (statusFilter === "all") {
|
if (statusFilter === "all") {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
return data.filter((system) => system.status === statusFilter)
|
if (statusFilter === SystemStatus.Up) {
|
||||||
|
return Object.values(upSystems) ?? []
|
||||||
|
}
|
||||||
|
if (statusFilter === SystemStatus.Down) {
|
||||||
|
return Object.values(downSystems) ?? []
|
||||||
|
}
|
||||||
|
return Object.values(pausedSystems) ?? []
|
||||||
}, [data, statusFilter])
|
}, [data, statusFilter])
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useBrowserStorage<ViewMode>(
|
||||||
|
"viewMode",
|
||||||
|
// show grid view on mobile if there are less than 200 systems (looks better but table is more efficient)
|
||||||
|
window.innerWidth < 1024 && filteredData.length < 200 ? "grid" : "table"
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (filter !== undefined) {
|
if (filter !== undefined) {
|
||||||
table.getColumn("system")?.setFilterValue(filter)
|
table.getColumn("system")?.setFilterValue(filter)
|
||||||
@@ -96,7 +115,6 @@ export default function SystemsTable() {
|
|||||||
columnVisibility,
|
columnVisibility,
|
||||||
},
|
},
|
||||||
defaultColumn: {
|
defaultColumn: {
|
||||||
// sortDescFirst: true,
|
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
sortUndefined: "last",
|
sortUndefined: "last",
|
||||||
minSize: 0,
|
minSize: 0,
|
||||||
@@ -108,17 +126,22 @@ export default function SystemsTable() {
|
|||||||
const rows = table.getRowModel().rows
|
const rows = table.getRowModel().rows
|
||||||
const columns = table.getAllColumns()
|
const columns = table.getAllColumns()
|
||||||
const visibleColumns = table.getVisibleLeafColumns()
|
const visibleColumns = table.getVisibleLeafColumns()
|
||||||
|
|
||||||
|
const [upSystemsLength, downSystemsLength, pausedSystemsLength] = useMemo(() => {
|
||||||
|
return [Object.values(upSystems).length, Object.values(downSystems).length, Object.values(pausedSystems).length]
|
||||||
|
}, [upSystems, downSystems, pausedSystems])
|
||||||
|
|
||||||
// TODO: hiding temp then gpu messes up table headers
|
// TODO: hiding temp then gpu messes up table headers
|
||||||
const CardHead = useMemo(() => {
|
const CardHead = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
<CardHeader className="pb-4.5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
||||||
<div className="grid md:flex gap-5 w-full items-end">
|
<div className="grid md:flex gap-5 w-full items-end">
|
||||||
<div className="px-2 sm:px-1">
|
<div className="px-2 sm:px-1">
|
||||||
<CardTitle className="mb-2.5">
|
<CardTitle className="mb-2">
|
||||||
<Trans>All Systems</Trans>
|
<Trans>All Systems</Trans>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription className="flex">
|
||||||
<Trans>Updated in real time. Click on a system to view information.</Trans>
|
<Trans>Click on a system to view more information.</Trans>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -170,13 +193,13 @@ export default function SystemsTable() {
|
|||||||
<Trans>All Systems</Trans>
|
<Trans>All Systems</Trans>
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="up" onSelect={(e) => e.preventDefault()}>
|
<DropdownMenuRadioItem value="up" onSelect={(e) => e.preventDefault()}>
|
||||||
<Trans>Up</Trans>
|
<Trans>Up ({upSystemsLength})</Trans>
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="down" onSelect={(e) => e.preventDefault()}>
|
<DropdownMenuRadioItem value="down" onSelect={(e) => e.preventDefault()}>
|
||||||
<Trans>Down</Trans>
|
<Trans>Down ({downSystemsLength})</Trans>
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="paused" onSelect={(e) => e.preventDefault()}>
|
<DropdownMenuRadioItem value="paused" onSelect={(e) => e.preventDefault()}>
|
||||||
<Trans>Paused</Trans>
|
<Trans>Paused ({pausedSystemsLength})</Trans>
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</div>
|
</div>
|
||||||
@@ -247,7 +270,16 @@ export default function SystemsTable() {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
)
|
)
|
||||||
}, [visibleColumns.length, sorting, viewMode, locale, statusFilter])
|
}, [
|
||||||
|
visibleColumns.length,
|
||||||
|
sorting,
|
||||||
|
viewMode,
|
||||||
|
locale,
|
||||||
|
statusFilter,
|
||||||
|
upSystemsLength,
|
||||||
|
downSystemsLength,
|
||||||
|
pausedSystemsLength,
|
||||||
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -255,7 +287,7 @@ export default function SystemsTable() {
|
|||||||
<div className="p-6 pt-0 max-sm:py-3 max-sm:px-2">
|
<div className="p-6 pt-0 max-sm:py-3 max-sm:px-2">
|
||||||
{viewMode === "table" ? (
|
{viewMode === "table" ? (
|
||||||
// table layout
|
// table layout
|
||||||
<div className="rounded-md border overflow-hidden">
|
<div className="rounded-md">
|
||||||
<AllSystemsTable table={table} rows={rows} colLength={visibleColumns.length} />
|
<AllSystemsTable table={table} rows={rows} colLength={visibleColumns.length} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -277,36 +309,78 @@ export default function SystemsTable() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const AllSystemsTable = memo(
|
const AllSystemsTable = memo(function ({
|
||||||
({ table, rows, colLength }: { table: TableType<SystemRecord>; rows: Row<SystemRecord>[]; colLength: number }) => {
|
table,
|
||||||
return (
|
rows,
|
||||||
<Table>
|
colLength,
|
||||||
<SystemsTableHead table={table} colLength={colLength} />
|
}: {
|
||||||
<TableBody onMouseEnter={preloadSystemDetail}>
|
table: TableType<SystemRecord>
|
||||||
{rows.length ? (
|
rows: Row<SystemRecord>[]
|
||||||
rows.map((row) => (
|
colLength: number
|
||||||
<SystemTableRow key={row.original.id} row={row} length={rows.length} colLength={colLength} />
|
}) {
|
||||||
))
|
// The virtualizer will need a reference to the scrollable container element
|
||||||
) : (
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={colLength} className="h-24 text-center">
|
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
|
||||||
<Trans>No systems found.</Trans>
|
count: rows.length,
|
||||||
</TableCell>
|
estimateSize: () => (rows.length > 10 ? 56 : 60),
|
||||||
</TableRow>
|
getScrollElement: () => scrollRef.current,
|
||||||
)}
|
overscan: 5,
|
||||||
</TableBody>
|
})
|
||||||
</Table>
|
const virtualRows = virtualizer.getVirtualItems()
|
||||||
)
|
|
||||||
}
|
const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin)
|
||||||
)
|
const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-min max-h-[calc(100dvh-17rem)] max-w-full relative overflow-auto border rounded-md",
|
||||||
|
// don't set min height if there are less than 2 rows, do set if we need to display the empty state
|
||||||
|
(!rows.length || rows.length > 2) && "min-h-50"
|
||||||
|
)}
|
||||||
|
ref={scrollRef}
|
||||||
|
>
|
||||||
|
{/* add header height to table size */}
|
||||||
|
<div style={{ height: `${virtualizer.getTotalSize() + 50}px`, paddingTop, paddingBottom }}>
|
||||||
|
<table className="text-sm w-full h-full">
|
||||||
|
<SystemsTableHead table={table} colLength={colLength} />
|
||||||
|
<TableBody onMouseEnter={preloadSystemDetail}>
|
||||||
|
{rows.length ? (
|
||||||
|
virtualRows.map((virtualRow) => {
|
||||||
|
const row = rows[virtualRow.index] as Row<SystemRecord>
|
||||||
|
return (
|
||||||
|
<SystemTableRow
|
||||||
|
key={row.id}
|
||||||
|
row={row}
|
||||||
|
virtualRow={virtualRow}
|
||||||
|
length={rows.length}
|
||||||
|
colLength={colLength}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
|
||||||
|
<Trans>No systems found.</Trans>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>; colLength: number }) {
|
function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>; colLength: number }) {
|
||||||
const { i18n } = useLingui()
|
const { i18n } = useLingui()
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<TableHeader>
|
<TableHeader className="sticky top-0 z-20 w-full border-b-2">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
return (
|
return (
|
||||||
<TableHead className="px-1.5" key={header.id}>
|
<TableHead className="px-1.5" key={header.id}>
|
||||||
@@ -314,41 +388,49 @@ function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</TableRow>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
)
|
)
|
||||||
}, [i18n.locale, colLength])
|
}, [i18n.locale, colLength])
|
||||||
}
|
}
|
||||||
|
|
||||||
const SystemTableRow = memo(
|
const SystemTableRow = memo(function ({
|
||||||
({ row, length, colLength }: { row: Row<SystemRecord>; length: number; colLength: number }) => {
|
row,
|
||||||
const system = row.original
|
virtualRow,
|
||||||
const { t } = useLingui()
|
colLength,
|
||||||
return useMemo(() => {
|
}: {
|
||||||
return (
|
row: Row<SystemRecord>
|
||||||
<TableRow
|
virtualRow: VirtualItem
|
||||||
// data-state={row.getIsSelected() && "selected"}
|
length: number
|
||||||
className={cn("cursor-pointer transition-opacity relative safari:transform-3d", {
|
colLength: number
|
||||||
"opacity-50": system.status === SystemStatus.Paused,
|
}) {
|
||||||
})}
|
const system = row.original
|
||||||
>
|
const { t } = useLingui()
|
||||||
{row.getVisibleCells().map((cell) => (
|
return useMemo(() => {
|
||||||
<TableCell
|
return (
|
||||||
key={cell.id}
|
<TableRow
|
||||||
style={{
|
// data-state={row.getIsSelected() && "selected"}
|
||||||
width: cell.column.getSize(),
|
className={cn("cursor-pointer transition-opacity relative safari:transform-3d", {
|
||||||
}}
|
"opacity-50": system.status === SystemStatus.Paused,
|
||||||
className={length > 10 ? "py-2" : "py-2.5"}
|
})}
|
||||||
>
|
>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{row.getVisibleCells().map((cell) => (
|
||||||
</TableCell>
|
<TableCell
|
||||||
))}
|
key={cell.id}
|
||||||
</TableRow>
|
style={{
|
||||||
)
|
width: cell.column.getSize(),
|
||||||
}, [system, system.status, colLength, t])
|
height: virtualRow.size,
|
||||||
}
|
}}
|
||||||
)
|
className="py-0"
|
||||||
|
>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}, [system, system.status, colLength, t])
|
||||||
|
})
|
||||||
|
|
||||||
const SystemCard = memo(
|
const SystemCard = memo(
|
||||||
({ row, table, colLength }: { row: Row<SystemRecord>; table: TableType<SystemRecord>; colLength: number }) => {
|
({ row, table, colLength }: { row: Row<SystemRecord>; table: TableType<SystemRecord>; colLength: number }) => {
|
||||||
@@ -368,13 +450,11 @@ const SystemCard = memo(
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
|
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center gap-2 w-full overflow-hidden">
|
||||||
<CardTitle className="text-base tracking-normal shrink-1 text-primary/90 flex items-center min-w-0 gap-2.5">
|
<CardTitle className="text-base tracking-normal text-primary/90 flex items-center min-w-0 flex-1 gap-2.5">
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
||||||
<IndicatorDot system={system} />
|
<IndicatorDot system={system} />
|
||||||
<CardTitle className="text-[.95em]/normal tracking-normal truncate text-primary/90">
|
<span className="text-[.95em]/normal tracking-normal text-primary/90 truncate">{system.name}</span>
|
||||||
{system.name}
|
|
||||||
</CardTitle>
|
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
{table.getColumn("actions")?.getIsVisible() && (
|
{table.getColumn("actions")?.getIsVisible() && (
|
||||||
@@ -385,27 +465,33 @@ const SystemCard = memo(
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-2.5 text-sm px-5 pt-3.5 pb-4">
|
<CardContent className="text-sm px-5 pt-3.5 pb-4">
|
||||||
{table.getAllColumns().map((column) => {
|
<div className="grid gap-2.5" style={{ gridTemplateColumns: "24px minmax(80px, max-content) 1fr" }}>
|
||||||
if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null
|
{table.getAllColumns().map((column) => {
|
||||||
const cell = row.getAllCells().find((cell) => cell.column.id === column.id)
|
if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null
|
||||||
if (!cell) return null
|
const cell = row.getAllCells().find((cell) => cell.column.id === column.id)
|
||||||
// @ts-ignore
|
if (!cell) return null
|
||||||
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
|
// @ts-ignore
|
||||||
return (
|
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
|
||||||
<div key={column.id} className="flex items-center gap-3">
|
return (
|
||||||
{column.id === "lastSeen" ? (
|
<>
|
||||||
<EyeIcon className="size-4 text-muted-foreground" />
|
<div key={`${column.id}-icon`} className="flex items-center">
|
||||||
) : (
|
{column.id === "lastSeen" ? (
|
||||||
Icon && <Icon className="size-4 text-muted-foreground" />
|
<EyeIcon className="size-4 text-muted-foreground" />
|
||||||
)}
|
) : (
|
||||||
<div className="flex items-center gap-3 flex-1">
|
Icon && <Icon className="size-4 text-muted-foreground" />
|
||||||
<span className="text-muted-foreground min-w-16">{name()}:</span>
|
)}
|
||||||
<div className="flex-1">{flexRender(cell.column.columnDef.cell, cell.getContext())}</div>
|
</div>
|
||||||
</div>
|
<div key={`${column.id}-label`} className="flex items-center text-muted-foreground pr-3">
|
||||||
</div>
|
{name()}:
|
||||||
)
|
</div>
|
||||||
})}
|
<div key={`${column.id}-value`} className="flex items-center">
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name: row.original.name })}
|
href={getPagePath($router, "system", { name: row.original.name })}
|
||||||
|
|||||||
@@ -1,33 +1,41 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { DialogTitle, type DialogProps } from "@radix-ui/react-dialog"
|
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
import { Search } from "lucide-react"
|
import { SearchIcon } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
|
|
||||||
const Command = React.forwardRef<
|
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
|
||||||
React.ElementRef<typeof CommandPrimitive>,
|
return (
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
<CommandPrimitive
|
||||||
>(({ className, ...props }, ref) => (
|
data-slot="command"
|
||||||
<CommandPrimitive
|
className={cn("bg-card flex h-full w-full flex-col overflow-hidden rounded-md", className)}
|
||||||
ref={ref}
|
{...props}
|
||||||
className={cn("flex h-full w-full flex-col overflow-hidden bg-card", className)}
|
/>
|
||||||
{...props}
|
)
|
||||||
/>
|
}
|
||||||
))
|
|
||||||
Command.displayName = CommandPrimitive.displayName
|
|
||||||
|
|
||||||
interface CommandDialogProps extends DialogProps {}
|
function CommandDialog({
|
||||||
|
title = "Command Palette",
|
||||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
description = "Search for a command to run...",
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof Dialog> & {
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
className?: string
|
||||||
|
showCloseButton?: boolean
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
<DialogHeader className="sr-only">
|
||||||
<div className="sr-only">
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogTitle>Command</DialogTitle>
|
<DialogDescription>{description}</DialogDescription>
|
||||||
</div>
|
</DialogHeader>
|
||||||
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
<DialogContent className={cn("overflow-hidden p-0", className)}>
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
{children}
|
{children}
|
||||||
</Command>
|
</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -35,89 +43,81 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommandInput = React.forwardRef<
|
function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
return (
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
|
||||||
>(({ className, ...props }, ref) => (
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
<CommandPrimitive.Input
|
||||||
<Search className="me-2 h-4 w-4 shrink-0 opacity-50" />
|
data-slot="command-input"
|
||||||
<CommandPrimitive.Input
|
className={cn(
|
||||||
ref={ref}
|
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
data-slot="command-list"
|
||||||
|
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||||
|
return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
data-slot="command-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
)
|
||||||
))
|
}
|
||||||
|
|
||||||
CommandInput.displayName = CommandPrimitive.Input.displayName
|
function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
const CommandList = React.forwardRef<
|
<CommandPrimitive.Separator
|
||||||
React.ElementRef<typeof CommandPrimitive.List>,
|
data-slot="command-separator"
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
className={cn("bg-border -mx-1 h-px", className)}
|
||||||
>(({ className, ...props }, ref) => (
|
{...props}
|
||||||
<CommandPrimitive.List
|
/>
|
||||||
ref={ref}
|
)
|
||||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
}
|
||||||
{...props}
|
|
||||||
/>
|
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||||
))
|
return (
|
||||||
|
<CommandPrimitive.Item
|
||||||
CommandList.displayName = CommandPrimitive.List.displayName
|
data-slot="command-item"
|
||||||
|
className={cn(
|
||||||
const CommandEmpty = React.forwardRef<
|
"data-[selected=true]:bg-accent/70 data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
className
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
)}
|
||||||
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />)
|
{...props}
|
||||||
|
/>
|
||||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
)
|
||||||
|
}
|
||||||
const CommandGroup = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
return (
|
||||||
>(({ className, ...props }, ref) => (
|
<span
|
||||||
<CommandPrimitive.Group
|
data-slot="command-shortcut"
|
||||||
ref={ref}
|
className={cn("text-muted-foreground ml-auto text-xs tracking-wide", className)}
|
||||||
className={cn(
|
{...props}
|
||||||
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
/>
|
||||||
className
|
)
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
|
||||||
|
|
||||||
const CommandSeparator = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} />
|
|
||||||
))
|
|
||||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
|
||||||
|
|
||||||
const CommandItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.Item
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
"relative flex cursor-default opacity-70 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden aria-selected:bg-accent/60 aria-selected:opacity-90 data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandItem.displayName = CommandPrimitive.Item.displayName
|
|
||||||
|
|
||||||
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
||||||
return <span className={cn("ms-auto text-xs tracking-wide text-muted-foreground", className)} {...props} />
|
|
||||||
}
|
}
|
||||||
CommandShortcut.displayName = "CommandShortcut"
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Command,
|
Command,
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ Table.displayName = "Table"
|
|||||||
|
|
||||||
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<thead ref={ref} className={cn("bg-muted/30 [&_tr]:border-b", className)} {...props} />
|
<thead
|
||||||
|
ref={ref}
|
||||||
|
className={cn("bg-table-header border-b border-border/50 [&_tr]:border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
TableHeader.displayName = "TableHeader"
|
TableHeader.displayName = "TableHeader"
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@custom-variant light (&:is(.light *));
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
@custom-variant safari (@supports (hanging-punctuation: first) and (-webkit-appearance: none));
|
@custom-variant safari (@supports (hanging-punctuation: first) and (-webkit-appearance: none));
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: hsl(30 8% 98%);
|
--background: hsl(30 8% 98%);
|
||||||
--foreground: hsl(30 0% 0%);
|
--foreground: hsl(30 0% 10%);
|
||||||
--card: hsl(30 0% 100%);
|
--card: hsl(30 0% 100%);
|
||||||
--card-foreground: hsl(240 6.67% 2.94%);
|
--card-foreground: hsl(240 6% 12%);
|
||||||
--popover: hsl(30 0% 100%);
|
--popover: hsl(30 0% 100%);
|
||||||
--popover-foreground: hsl(240 10% 6.2%);
|
--popover-foreground: hsl(240 10% 6.2%);
|
||||||
--primary: hsl(240 5.88% 10%);
|
--primary: hsl(240 5.88% 10%);
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
--accent: hsl(20 23.08% 94%);
|
--accent: hsl(20 23.08% 94%);
|
||||||
--accent-foreground: hsl(240 5.88% 10%);
|
--accent-foreground: hsl(240 5.88% 10%);
|
||||||
--destructive: hsl(0 66% 53%);
|
--destructive: hsl(0 66% 53%);
|
||||||
--destructive-foreground: hsl(0 0% 98.04%);
|
--destructive-foreground: hsl(0 0% 97%);
|
||||||
--border: hsl(30 8.11% 85.49%);
|
--border: hsl(30 8.11% 85.49%);
|
||||||
--input: hsl(30 4.29% 72.55%);
|
--input: hsl(30 4.29% 72.55%);
|
||||||
--ring: hsl(30 3.97% 49.41%);
|
--ring: hsl(30 3.97% 49.41%);
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
--chart-3: hsl(30 80% 55%);
|
--chart-3: hsl(30 80% 55%);
|
||||||
--chart-4: hsl(280 65% 60%);
|
--chart-4: hsl(280 65% 60%);
|
||||||
--chart-5: hsl(340 75% 55%);
|
--chart-5: hsl(340 75% 55%);
|
||||||
|
--table-header: hsl(225, 6%, 97%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@@ -49,10 +51,10 @@
|
|||||||
--accent: hsl(220 5% 15.5%);
|
--accent: hsl(220 5% 15.5%);
|
||||||
--accent-foreground: hsl(220 2% 98%);
|
--accent-foreground: hsl(220 2% 98%);
|
||||||
--destructive: hsl(0 62% 46%);
|
--destructive: hsl(0 62% 46%);
|
||||||
--destructive-foreground: hsl(0 0% 97%);
|
|
||||||
--border: hsl(220 3% 16%);
|
--border: hsl(220 3% 16%);
|
||||||
--input: hsl(220 4% 22%);
|
--input: hsl(220 4% 22%);
|
||||||
--ring: hsl(220 4% 80%);
|
--ring: hsl(220 4% 80%);
|
||||||
|
--table-header: hsl(220, 6%, 13%);
|
||||||
--radius: 0.8rem;
|
--radius: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +97,7 @@
|
|||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
--color-destructive: var(--destructive);
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
@@ -103,6 +106,7 @@
|
|||||||
--color-chart-3: var(--chart-3);
|
--color-chart-3: var(--chart-3);
|
||||||
--color-chart-4: var(--chart-4);
|
--color-chart-4: var(--chart-4);
|
||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
|
--color-table-header: var(--table-header);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { ChartTimes, SystemRecord, UserSettings } from "@/types"
|
import { ChartTimes, UserSettings } from "@/types"
|
||||||
import { $alerts, $systems, $userSettings } from "./stores"
|
import { $alerts, $allSystemsByName, $userSettings } from "./stores"
|
||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
import { t } from "@lingui/core/macro"
|
import { t } from "@lingui/core/macro"
|
||||||
import { chartTimeData } from "./utils"
|
import { chartTimeData } from "./utils"
|
||||||
import { WritableAtom } from "nanostores"
|
|
||||||
import { RecordModel, RecordSubscription } from "pocketbase"
|
|
||||||
import PocketBase from "pocketbase"
|
import PocketBase from "pocketbase"
|
||||||
import { basePath } from "@/components/router"
|
import { basePath } from "@/components/router"
|
||||||
|
|
||||||
@@ -14,7 +12,7 @@ export const pb = new PocketBase(basePath)
|
|||||||
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
||||||
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
||||||
|
|
||||||
const verifyAuth = () => {
|
export const verifyAuth = () => {
|
||||||
pb.collection("users")
|
pb.collection("users")
|
||||||
.authRefresh()
|
.authRefresh()
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -29,7 +27,7 @@ const verifyAuth = () => {
|
|||||||
|
|
||||||
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
|
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
|
||||||
export async function logOut() {
|
export async function logOut() {
|
||||||
$systems.set([])
|
$allSystemsByName.set({})
|
||||||
$alerts.set({})
|
$alerts.set({})
|
||||||
$userSettings.set({} as UserSettings)
|
$userSettings.set({} as UserSettings)
|
||||||
sessionStorage.setItem("lo", "t") // prevent auto login on logout
|
sessionStorage.setItem("lo", "t") // prevent auto login on logout
|
||||||
@@ -54,54 +52,6 @@ export async function updateUserSettings() {
|
|||||||
console.error("create settings", e)
|
console.error("create settings", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** Update systems / alerts list when records change */
|
|
||||||
export function updateRecordList<T extends RecordModel>(e: RecordSubscription<T>, $store: WritableAtom<T[]>) {
|
|
||||||
const curRecords = $store.get()
|
|
||||||
const newRecords = []
|
|
||||||
if (e.action === "delete") {
|
|
||||||
for (const server of curRecords) {
|
|
||||||
if (server.id !== e.record.id) {
|
|
||||||
newRecords.push(server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let found = 0
|
|
||||||
for (const server of curRecords) {
|
|
||||||
if (server.id === e.record.id) {
|
|
||||||
found = newRecords.push(e.record)
|
|
||||||
} else {
|
|
||||||
newRecords.push(server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
newRecords.push(e.record)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$store.set(newRecords)
|
|
||||||
}
|
|
||||||
/** Fetches updated system list from database */
|
|
||||||
export const updateSystemList = (() => {
|
|
||||||
let isFetchingSystems = false
|
|
||||||
return async () => {
|
|
||||||
if (isFetchingSystems) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isFetchingSystems = true
|
|
||||||
try {
|
|
||||||
const records = await pb
|
|
||||||
.collection<SystemRecord>("systems")
|
|
||||||
.getFullList({ sort: "+name", fields: "id,name,host,port,info,status" })
|
|
||||||
|
|
||||||
if (records.length) {
|
|
||||||
$systems.set(records)
|
|
||||||
} else {
|
|
||||||
verifyAuth()
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
isFetchingSystems = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
export function getPbTimestamp(timeString: ChartTimes, d?: Date) {
|
export function getPbTimestamp(timeString: ChartTimes, d?: Date) {
|
||||||
d ||= chartTimeData[timeString].getOffset(new Date())
|
d ||= chartTimeData[timeString].getOffset(new Date())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { atom, map } from "nanostores"
|
import { atom, computed, map, ReadableAtom } from "nanostores"
|
||||||
import { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types"
|
import { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types"
|
||||||
import { Unit } from "./enums"
|
import { Unit } from "./enums"
|
||||||
import { pb } from "./api"
|
import { pb } from "./api"
|
||||||
@@ -6,8 +6,18 @@ import { pb } from "./api"
|
|||||||
/** Store if user is authenticated */
|
/** Store if user is authenticated */
|
||||||
export const $authenticated = atom(pb.authStore.isValid)
|
export const $authenticated = atom(pb.authStore.isValid)
|
||||||
|
|
||||||
/** List of system records */
|
/** Map of system records by name */
|
||||||
export const $systems = atom<SystemRecord[]>([])
|
export const $allSystemsByName = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of system records by id */
|
||||||
|
export const $allSystemsById = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of up systems by id */
|
||||||
|
export const $upSystems = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of down systems by id */
|
||||||
|
export const $downSystems = map<Record<string, SystemRecord>>({})
|
||||||
|
/** Map of paused systems by id */
|
||||||
|
export const $pausedSystems = map<Record<string, SystemRecord>>({})
|
||||||
|
/** List of all system records */
|
||||||
|
export const $systems: ReadableAtom<SystemRecord[]> = computed($allSystemsByName, Object.values)
|
||||||
|
|
||||||
/** Map of alert records by system id and alert name */
|
/** Map of alert records by system id and alert name */
|
||||||
export const $alerts = map<AlertMap>({})
|
export const $alerts = map<AlertMap>({})
|
||||||
@@ -53,3 +63,8 @@ export const $copyContent = atom("")
|
|||||||
|
|
||||||
/** Direction for localization */
|
/** Direction for localization */
|
||||||
export const $direction = atom<"ltr" | "rtl">("ltr")
|
export const $direction = atom<"ltr" | "rtl">("ltr")
|
||||||
|
|
||||||
|
/** Longest system name length. Used to set table column width. I know this
|
||||||
|
* is stupid but the table is virtualized and I know this will work.
|
||||||
|
*/
|
||||||
|
export const $longestSystemNameLen = atom(8)
|
||||||
|
|||||||
161
beszel/site/src/lib/systemsManager.ts
Normal file
161
beszel/site/src/lib/systemsManager.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import { SystemRecord } from "@/types"
|
||||||
|
import { PreinitializedMapStore } from "nanostores"
|
||||||
|
import { pb, verifyAuth } from "@/lib/api"
|
||||||
|
import {
|
||||||
|
$allSystemsByName,
|
||||||
|
$upSystems,
|
||||||
|
$downSystems,
|
||||||
|
$pausedSystems,
|
||||||
|
$allSystemsById,
|
||||||
|
$longestSystemNameLen,
|
||||||
|
} from "@/lib/stores"
|
||||||
|
import { updateFavicon, FAVICON_DEFAULT, FAVICON_GREEN, FAVICON_RED } from "@/lib/utils"
|
||||||
|
import { SystemStatus } from "./enums"
|
||||||
|
|
||||||
|
const COLLECTION = pb.collection<SystemRecord>("systems")
|
||||||
|
const FIELDS_DEFAULT = "id,name,host,port,info,status"
|
||||||
|
|
||||||
|
/** Maximum system name length for display purposes */
|
||||||
|
const MAX_SYSTEM_NAME_LENGTH = 20
|
||||||
|
|
||||||
|
let initialized = false
|
||||||
|
let unsub: (() => void) | undefined | void
|
||||||
|
|
||||||
|
/** Initialize the systems manager and set up listeners */
|
||||||
|
export function init() {
|
||||||
|
if (initialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
initialized = true
|
||||||
|
|
||||||
|
// sync system stores on change
|
||||||
|
$allSystemsByName.listen((newSystems, oldSystems, changedKey) => {
|
||||||
|
const oldSystem = oldSystems[changedKey]
|
||||||
|
const newSystem = newSystems[changedKey]
|
||||||
|
|
||||||
|
// if system is undefined (deleted), remove it from the stores
|
||||||
|
if (oldSystem && !newSystem?.id) {
|
||||||
|
removeFromStore(oldSystem, $upSystems)
|
||||||
|
removeFromStore(oldSystem, $downSystems)
|
||||||
|
removeFromStore(oldSystem, $pausedSystems)
|
||||||
|
removeFromStore(oldSystem, $allSystemsById)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newSystem) {
|
||||||
|
onSystemsChanged(newSystems, undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStatus = newSystem.status
|
||||||
|
if (newStatus === SystemStatus.Up) {
|
||||||
|
$upSystems.setKey(newSystem.id, newSystem)
|
||||||
|
removeFromStore(newSystem, $downSystems)
|
||||||
|
removeFromStore(newSystem, $pausedSystems)
|
||||||
|
} else if (newStatus === SystemStatus.Down) {
|
||||||
|
$downSystems.setKey(newSystem.id, newSystem)
|
||||||
|
removeFromStore(newSystem, $upSystems)
|
||||||
|
removeFromStore(newSystem, $pausedSystems)
|
||||||
|
} else if (newStatus === SystemStatus.Paused) {
|
||||||
|
$pausedSystems.setKey(newSystem.id, newSystem)
|
||||||
|
removeFromStore(newSystem, $upSystems)
|
||||||
|
removeFromStore(newSystem, $downSystems)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run things that need to be done when systems change
|
||||||
|
onSystemsChanged(newSystems, newSystem)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the longest system name length and favicon based on system status */
|
||||||
|
function onSystemsChanged(_: Record<string, SystemRecord>, changedSystem: SystemRecord | undefined) {
|
||||||
|
const upSystemsStore = $upSystems.get()
|
||||||
|
const downSystemsStore = $downSystems.get()
|
||||||
|
const upSystems = Object.values(upSystemsStore)
|
||||||
|
const downSystems = Object.values(downSystemsStore)
|
||||||
|
|
||||||
|
// Update longest system name length
|
||||||
|
const longestName = $longestSystemNameLen.get()
|
||||||
|
const nameLen = Math.min(MAX_SYSTEM_NAME_LENGTH, changedSystem?.name.length || 0)
|
||||||
|
if (nameLen > longestName) {
|
||||||
|
$longestSystemNameLen.set(nameLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update favicon based on system status
|
||||||
|
if (downSystems.length > 0) {
|
||||||
|
updateFavicon(FAVICON_RED)
|
||||||
|
} else if (upSystems.length > 0) {
|
||||||
|
updateFavicon(FAVICON_GREEN)
|
||||||
|
} else {
|
||||||
|
updateFavicon(FAVICON_DEFAULT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch systems from collection */
|
||||||
|
async function fetchSystems(): Promise<SystemRecord[]> {
|
||||||
|
try {
|
||||||
|
return await COLLECTION.getFullList({ sort: "+name", fields: FIELDS_DEFAULT })
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch systems:", error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store management functions
|
||||||
|
/** Add system to both name and ID stores */
|
||||||
|
export function add(system: SystemRecord) {
|
||||||
|
$allSystemsByName.setKey(system.name, system)
|
||||||
|
$allSystemsById.setKey(system.id, system)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove system from stores */
|
||||||
|
export function remove(system: SystemRecord) {
|
||||||
|
removeFromStore(system, $allSystemsByName)
|
||||||
|
removeFromStore(system, $allSystemsById)
|
||||||
|
removeFromStore(system, $upSystems)
|
||||||
|
removeFromStore(system, $downSystems)
|
||||||
|
removeFromStore(system, $pausedSystems)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove system from specific store */
|
||||||
|
function removeFromStore(system: SystemRecord, store: PreinitializedMapStore<Record<string, SystemRecord>>) {
|
||||||
|
const key = store === $allSystemsByName ? system.name : system.id
|
||||||
|
store.setKey(key, undefined as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Action functions for subscription */
|
||||||
|
const actionFns: Record<string, (system: SystemRecord) => void> = {
|
||||||
|
create: add,
|
||||||
|
update: add,
|
||||||
|
delete: remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Subscribe to real-time system updates from the collection */
|
||||||
|
export async function subscribe() {
|
||||||
|
try {
|
||||||
|
unsub = await COLLECTION.subscribe("*", ({ action, record }) => actionFns[action]?.(record), {
|
||||||
|
fields: FIELDS_DEFAULT,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to subscribe to systems collection:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Refresh all systems with latest data from the hub */
|
||||||
|
export async function refresh() {
|
||||||
|
try {
|
||||||
|
const records = await fetchSystems()
|
||||||
|
if (!records.length) {
|
||||||
|
// No systems found, verify authentication
|
||||||
|
verifyAuth()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const record of records) {
|
||||||
|
add(record)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to refresh systems:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unsubscribe from real-time system updates */
|
||||||
|
export const unsubscribe = () => (unsub = unsub?.())
|
||||||
@@ -2,13 +2,17 @@ import { t } from "@lingui/core/macro"
|
|||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from "clsx"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { $copyContent, $systems, $userSettings } from "./stores"
|
import { $copyContent, $userSettings } from "./stores"
|
||||||
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
|
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
|
||||||
import { timeDay, timeHour } from "d3-time"
|
import { timeDay, timeHour } from "d3-time"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { MeterState, Unit } from "./enums"
|
import { MeterState, Unit } from "./enums"
|
||||||
import { prependBasePath } from "@/components/router"
|
import { prependBasePath } from "@/components/router"
|
||||||
|
|
||||||
|
export const FAVICON_DEFAULT = "favicon.svg"
|
||||||
|
export const FAVICON_GREEN = "favicon-green.svg"
|
||||||
|
export const FAVICON_RED = "favicon-red.svg"
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
@@ -104,32 +108,6 @@ export const chartTimeData: ChartTimeData = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the correct width of the y axis in recharts based on the longest label */
|
|
||||||
export function useYAxisWidth() {
|
|
||||||
const [yAxisWidth, setYAxisWidth] = useState(0)
|
|
||||||
let maxChars = 0
|
|
||||||
let timeout: Timer
|
|
||||||
function updateYAxisWidth(str: string) {
|
|
||||||
if (str.length > maxChars) {
|
|
||||||
maxChars = str.length
|
|
||||||
const div = document.createElement("div")
|
|
||||||
div.className = "text-xs tabular-nums tracking-tighter table sr-only"
|
|
||||||
div.innerHTML = str
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
document.body.appendChild(div)
|
|
||||||
const width = div.offsetWidth + 24
|
|
||||||
if (width > yAxisWidth) {
|
|
||||||
setYAxisWidth(div.offsetWidth + 24)
|
|
||||||
}
|
|
||||||
document.body.removeChild(div)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
return { yAxisWidth, updateYAxisWidth }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Format number to x decimal places, without trailing zeros */
|
/** Format number to x decimal places, without trailing zeros */
|
||||||
export function toFixedFloat(num: number, digits: number) {
|
export function toFixedFloat(num: number, digits: number) {
|
||||||
return parseFloat((digits === 0 ? Math.ceil(num) : num).toFixed(digits))
|
return parseFloat((digits === 0 ? Math.ceil(num) : num).toFixed(digits))
|
||||||
@@ -152,20 +130,20 @@ export function decimalString(num: number, digits = 2) {
|
|||||||
return formatter.format(num)
|
return formatter.format(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get value from local storage */
|
/** Get value from local or session storage */
|
||||||
function getStorageValue(key: string, defaultValue: any) {
|
function getStorageValue(key: string, defaultValue: any, storageInterface: Storage = localStorage) {
|
||||||
const saved = localStorage?.getItem(key)
|
const saved = storageInterface?.getItem(key)
|
||||||
return saved ? JSON.parse(saved) : defaultValue
|
return saved ? JSON.parse(saved) : defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hook to sync value in local storage */
|
/** Hook to sync value in local or session storage */
|
||||||
export function useLocalStorage<T>(key: string, defaultValue: T) {
|
export function useBrowserStorage<T>(key: string, defaultValue: T, storageInterface: Storage = localStorage) {
|
||||||
key = `besz-${key}`
|
key = `besz-${key}`
|
||||||
const [value, setValue] = useState(() => {
|
const [value, setValue] = useState(() => {
|
||||||
return getStorageValue(key, defaultValue)
|
return getStorageValue(key, defaultValue, storageInterface)
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage?.setItem(key, JSON.stringify(value))
|
storageInterface?.setItem(key, JSON.stringify(value))
|
||||||
}, [key, value])
|
}, [key, value])
|
||||||
|
|
||||||
return [value, setValue]
|
return [value, setValue]
|
||||||
@@ -344,28 +322,20 @@ export function debounce<T extends (...args: any[]) => any>(func: T, wait: numbe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* returns the name of a system from its id */
|
// Cache for runOnce
|
||||||
export const getSystemNameFromId = (() => {
|
const runOnceCache = new WeakMap<Function, { done: boolean; result: unknown }>()
|
||||||
const cache = new Map<string, string>()
|
|
||||||
return (systemId: string): string => {
|
|
||||||
if (cache.has(systemId)) {
|
|
||||||
return cache.get(systemId)!
|
|
||||||
}
|
|
||||||
const sysName = $systems.get().find((s) => s.id === systemId)?.name ?? ""
|
|
||||||
cache.set(systemId, sysName)
|
|
||||||
return sysName
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
/** Run a function only once */
|
/** Run a function only once */
|
||||||
export function runOnce<T extends (...args: any[]) => any>(fn: T): (...args: Parameters<T>) => ReturnType<T> {
|
export function runOnce<T extends (...args: any[]) => any>(fn: T): T {
|
||||||
let done = false
|
return ((...args: Parameters<T>) => {
|
||||||
let result: any
|
let state = runOnceCache.get(fn)
|
||||||
return (...args: any) => {
|
if (!state) {
|
||||||
if (!done) {
|
state = { done: false, result: undefined }
|
||||||
result = fn(...args)
|
runOnceCache.set(fn, state)
|
||||||
done = true
|
|
||||||
}
|
}
|
||||||
return result
|
if (!state.done) {
|
||||||
}
|
state.result = fn(...args)
|
||||||
|
state.done = true
|
||||||
|
}
|
||||||
|
return state.result
|
||||||
|
}) as T
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import { Suspense, lazy, memo, useEffect } from "react"
|
|||||||
import ReactDOM from "react-dom/client"
|
import ReactDOM from "react-dom/client"
|
||||||
import { ThemeProvider } from "./components/theme-provider.tsx"
|
import { ThemeProvider } from "./components/theme-provider.tsx"
|
||||||
import { DirectionProvider } from "@radix-ui/react-direction"
|
import { DirectionProvider } from "@radix-ui/react-direction"
|
||||||
import { $authenticated, $systems, $publicKey, $copyContent, $direction } from "./lib/stores.ts"
|
import { $authenticated, $publicKey, $copyContent, $direction } from "./lib/stores.ts"
|
||||||
import { pb, updateSystemList, updateUserSettings } from "./lib/api.ts"
|
import { pb, updateUserSettings } from "./lib/api.ts"
|
||||||
|
import * as systemsManager from "./lib/systemsManager.ts"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { Toaster } from "./components/ui/toaster.tsx"
|
import { Toaster } from "./components/ui/toaster.tsx"
|
||||||
import { $router } from "./components/router.tsx"
|
import { $router } from "./components/router.tsx"
|
||||||
import { updateFavicon } from "@/lib/utils"
|
|
||||||
import Navbar from "./components/navbar.tsx"
|
import Navbar from "./components/navbar.tsx"
|
||||||
import { I18nProvider } from "@lingui/react"
|
import { I18nProvider } from "@lingui/react"
|
||||||
import { i18n } from "@lingui/core"
|
import { i18n } from "@lingui/core"
|
||||||
import { getLocale, dynamicActivate } from "./lib/i18n"
|
import { getLocale, dynamicActivate } from "./lib/i18n"
|
||||||
import { SystemStatus } from "./lib/enums"
|
|
||||||
import { alertManager } from "./lib/alerts"
|
import { alertManager } from "./lib/alerts"
|
||||||
import Settings from "./components/routes/settings/layout.tsx"
|
import Settings from "./components/routes/settings/layout.tsx"
|
||||||
|
|
||||||
@@ -25,8 +24,6 @@ const CopyToClipboardDialog = lazy(() => import("@/components/copy-to-clipboard.
|
|||||||
|
|
||||||
const App = memo(() => {
|
const App = memo(() => {
|
||||||
const page = useStore($router)
|
const page = useStore($router)
|
||||||
const authenticated = useStore($authenticated)
|
|
||||||
const systems = useStore($systems)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// change auth store on auth change
|
// change auth store on auth change
|
||||||
@@ -37,40 +34,26 @@ const App = memo(() => {
|
|||||||
pb.send("/api/beszel/getkey", {}).then((data) => {
|
pb.send("/api/beszel/getkey", {}).then((data) => {
|
||||||
$publicKey.set(data.key)
|
$publicKey.set(data.key)
|
||||||
})
|
})
|
||||||
// get servers / alerts / settings
|
// get user settings
|
||||||
updateUserSettings()
|
updateUserSettings()
|
||||||
// need to get system list before alerts
|
// need to get system list before alerts
|
||||||
updateSystemList()
|
systemsManager.init()
|
||||||
// get alerts
|
systemsManager
|
||||||
|
// get current systems list
|
||||||
|
.refresh()
|
||||||
|
// subscribe to new system updates
|
||||||
|
.then(systemsManager.subscribe)
|
||||||
|
// get current alerts
|
||||||
.then(alertManager.refresh)
|
.then(alertManager.refresh)
|
||||||
// subscribe to new alert updates
|
// subscribe to new alert updates
|
||||||
.then(alertManager.subscribe)
|
.then(alertManager.subscribe)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
updateFavicon("favicon.svg")
|
// updateFavicon("favicon.svg")
|
||||||
alertManager.unsubscribe()
|
alertManager.unsubscribe()
|
||||||
|
systemsManager.unsubscribe()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// update favicon
|
|
||||||
useEffect(() => {
|
|
||||||
if (!systems.length || !authenticated) {
|
|
||||||
updateFavicon("favicon.svg")
|
|
||||||
} else {
|
|
||||||
let up = false
|
|
||||||
for (const system of systems) {
|
|
||||||
if (system.status === SystemStatus.Down) {
|
|
||||||
updateFavicon("favicon-red.svg")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (system.status === SystemStatus.Up) {
|
|
||||||
up = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateFavicon(up ? "favicon-green.svg" : "favicon.svg")
|
|
||||||
}
|
|
||||||
}, [systems])
|
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
return <h1 className="text-3xl text-center my-14">404</h1>
|
return <h1 className="text-3xl text-center my-14">404</h1>
|
||||||
} else if (page.route === "home") {
|
} else if (page.route === "home") {
|
||||||
@@ -102,7 +85,7 @@ const Layout = () => {
|
|||||||
<div className="container">
|
<div className="container">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
</div>
|
</div>
|
||||||
<div className="container mb-14 relative">
|
<div className="container relative">
|
||||||
<App />
|
<App />
|
||||||
{copyContent && (
|
{copyContent && (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import path from "path"
|
|||||||
import tailwindcss from "@tailwindcss/vite"
|
import tailwindcss from "@tailwindcss/vite"
|
||||||
import react from "@vitejs/plugin-react-swc"
|
import react from "@vitejs/plugin-react-swc"
|
||||||
import { lingui } from "@lingui/vite-plugin"
|
import { lingui } from "@lingui/vite-plugin"
|
||||||
import { version } from "./package.json"
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: "./",
|
base: "./",
|
||||||
@@ -13,13 +12,6 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
lingui(),
|
lingui(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
{
|
|
||||||
name: "replace version in index.html during dev",
|
|
||||||
apply: "serve",
|
|
||||||
transformIndexHtml(html) {
|
|
||||||
return html.replace("{{V}}", version).replace("{{HUB_URL}}", "")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
esbuild: {
|
esbuild: {
|
||||||
legalComments: "external",
|
legalComments: "external",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package beszel
|
|||||||
import "github.com/blang/semver"
|
import "github.com/blang/semver"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.12.5"
|
Version = "0.12.6"
|
||||||
AppName = "beszel"
|
AppName = "beszel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
import { memo, useMemo } from "react"
|
|
||||||
import { Row, TableType } from "@tanstack/react-table"
|
|
||||||
import { useLingui } from "@lingui/react"
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { Link } from "@/components/ui/link"
|
|
||||||
import { getPagePath } from "@/lib/page-path"
|
|
||||||
import { useRouter } from "next/router"
|
|
||||||
import { flexRender } from "@tanstack/react-table"
|
|
||||||
import { ColumnDef } from "@tanstack/table-core"
|
|
||||||
import { SystemRecord } from "@/lib/types"
|
|
||||||
import { IndicatorDot } from "@/components/indicator-dot"
|
|
||||||
import { AlertsButton } from "@/components/alerts-button"
|
|
||||||
import { ActionsButton } from "@/components/actions-button"
|
|
||||||
import { EyeIcon } from "@/components/icons"
|
|
||||||
|
|
||||||
const SystemCard = memo(
|
|
||||||
({ row, table, colLength }: { row: Row<SystemRecord>; table: TableType<SystemRecord>; colLength: number }) => {
|
|
||||||
const system = row.original
|
|
||||||
const { t } = useLingui()
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
key={system.id}
|
|
||||||
className={cn(
|
|
||||||
"cursor-pointer hover:shadow-md transition-all bg-transparent w-full dark:border-border duration-200 relative",
|
|
||||||
{
|
|
||||||
"opacity-50": system.status === "paused",
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
|
|
||||||
<div className="flex items-center justify-between gap-2">
|
|
||||||
<CardTitle className="text-base tracking-normal shrink-1 text-primary/90 flex items-center min-w-0 gap-2.5">
|
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
|
||||||
<IndicatorDot system={system} />
|
|
||||||
<CardTitle className="text-[.95em]/normal tracking-normal truncate text-primary/90">
|
|
||||||
{system.name}
|
|
||||||
</CardTitle>
|
|
||||||
</div>
|
|
||||||
</CardTitle>
|
|
||||||
{table.getColumn("actions")?.getIsVisible() && (
|
|
||||||
<div className="flex gap-1 flex-shrink-0 relative z-10">
|
|
||||||
<AlertsButton system={system} />
|
|
||||||
<ActionsButton system={system} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-2.5 text-sm px-5 pt-3.5 pb-4">
|
|
||||||
{table.getAllColumns().map((column) => {
|
|
||||||
if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null
|
|
||||||
const cell = row.getAllCells().find((cell) => cell.column.id === column.id)
|
|
||||||
if (!cell) return null
|
|
||||||
// @ts-ignore
|
|
||||||
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
|
|
||||||
|
|
||||||
// Special case for 'lastSeen' column: add EyeIcon before value
|
|
||||||
if (column.id === "lastSeen") {
|
|
||||||
return (
|
|
||||||
<div key={column.id} className="flex items-center gap-3">
|
|
||||||
<EyeIcon className="size-4 text-muted-foreground" />
|
|
||||||
<div className="flex items-center gap-3 flex-1">
|
|
||||||
<span className="text-muted-foreground min-w-16">{name()}:</span>
|
|
||||||
<div className="flex-1">{flexRender(cell.column.columnDef.cell, cell.getContext())}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={column.id} className="flex items-center gap-3">
|
|
||||||
{Icon && <Icon className="size-4 text-muted-foreground" />}
|
|
||||||
<div className="flex items-center gap-3 flex-1">
|
|
||||||
<span className="text-muted-foreground min-w-16">{name()}:</span>
|
|
||||||
<div className="flex-1">{flexRender(cell.column.columnDef.cell, cell.getContext())}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</CardContent>
|
|
||||||
<Link
|
|
||||||
href={getPagePath($router, "system", { name: row.original.name })}
|
|
||||||
className="inset-0 absolute w-full h-full"
|
|
||||||
>
|
|
||||||
<span className="sr-only">{row.original.name}</span>
|
|
||||||
</Link>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}, [system, colLength, t])
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -4,10 +4,16 @@
|
|||||||
|
|
||||||
- Add status filters to All Systems table.
|
- Add status filters to All Systems table.
|
||||||
|
|
||||||
|
- Virtualize All Systems table to improve performance with hundreds of systems. (#1100)
|
||||||
|
|
||||||
- Fix Safari system link CSS bug.
|
- Fix Safari system link CSS bug.
|
||||||
|
|
||||||
- Use older cuda image for increased compatibility (#1103)
|
- Use older cuda image for increased compatibility (#1103)
|
||||||
|
|
||||||
|
- Truncate long system names in All Systems table. (#1104)
|
||||||
|
|
||||||
|
- Fix update mirror and add `--china-mirrors` flag. (#1035)
|
||||||
|
|
||||||
## 0.12.5
|
## 0.12.5
|
||||||
|
|
||||||
- Downgrade `gopsutil` to `v4.25.6` to fix panic on FreeBSD (#1083)
|
- Downgrade `gopsutil` to `v4.25.6` to fix panic on FreeBSD (#1083)
|
||||||
|
|||||||
@@ -216,11 +216,11 @@ if [ "$UNINSTALL" = true ]; then
|
|||||||
echo "Removing the OpenRC service files..."
|
echo "Removing the OpenRC service files..."
|
||||||
rm -f /etc/init.d/beszel-agent
|
rm -f /etc/init.d/beszel-agent
|
||||||
|
|
||||||
# Remove the update service if it exists
|
# Remove the daily update cron job if it exists
|
||||||
echo "Removing the daily update service..."
|
echo "Removing the daily update cron job..."
|
||||||
rc-service beszel-agent-update stop 2>/dev/null
|
if crontab -u root -l 2>/dev/null | grep -q "beszel-agent.*update"; then
|
||||||
rc-update del beszel-agent-update default 2>/dev/null
|
crontab -u root -l 2>/dev/null | grep -v "beszel-agent.*update" | crontab -u root -
|
||||||
rm -f /etc/init.d/beszel-agent-update
|
fi
|
||||||
|
|
||||||
# Remove log files
|
# Remove log files
|
||||||
echo "Removing log files..."
|
echo "Removing log files..."
|
||||||
@@ -321,6 +321,9 @@ if [ -z "$KEY" ]; then
|
|||||||
read KEY
|
read KEY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Remove newlines from KEY
|
||||||
|
KEY=$(echo "$KEY" | tr -d '\n')
|
||||||
|
|
||||||
# TOKEN and HUB_URL are optional for backwards compatibility - no interactive prompts
|
# TOKEN and HUB_URL are optional for backwards compatibility - no interactive prompts
|
||||||
# They will be set as empty environment variables if not provided
|
# They will be set as empty environment variables if not provided
|
||||||
|
|
||||||
@@ -398,7 +401,7 @@ fi
|
|||||||
echo "Downloading and installing the agent..."
|
echo "Downloading and installing the agent..."
|
||||||
|
|
||||||
OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')
|
OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')
|
||||||
ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/armv6l/arm/' -e 's/armv7l/arm/' -e 's/aarch64/arm64/' -e 's/mips/mipsle/')
|
ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/armv6l/arm/' -e 's/armv7l/arm/' -e 's/aarch64/arm64/')
|
||||||
FILE_NAME="beszel-agent_${OS}_${ARCH}.tar.gz"
|
FILE_NAME="beszel-agent_${OS}_${ARCH}.tar.gz"
|
||||||
|
|
||||||
# Determine version to install
|
# Determine version to install
|
||||||
@@ -523,35 +526,19 @@ EOF
|
|||||||
elif [ "$AUTO_UPDATE_FLAG" = "false" ]; then
|
elif [ "$AUTO_UPDATE_FLAG" = "false" ]; then
|
||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
else
|
else
|
||||||
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
[Yy]*)
|
[Yy]*)
|
||||||
echo "Setting up daily automatic updates for beszel-agent..."
|
echo "Setting up daily automatic updates for beszel-agent..."
|
||||||
|
|
||||||
cat >/etc/init.d/beszel-agent-update <<EOF
|
# Create cron job to run beszel-agent update command daily at midnight
|
||||||
#!/sbin/openrc-run
|
if ! crontab -u root -l 2>/dev/null | grep -q "beszel-agent.*update"; then
|
||||||
|
(crontab -u root -l 2>/dev/null; echo "12 0 * * * /opt/beszel-agent/beszel-agent update >/dev/null 2>&1") | crontab -u root -
|
||||||
|
fi
|
||||||
|
|
||||||
name="beszel-agent-update"
|
printf "\nDaily updates have been enabled via cron job.\n"
|
||||||
description="Update beszel-agent if needed"
|
|
||||||
|
|
||||||
depend() {
|
|
||||||
need beszel-agent
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
ebegin "Checking for beszel-agent updates"
|
|
||||||
/opt/beszel-agent/beszel-agent update
|
|
||||||
eend $?
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x /etc/init.d/beszel-agent-update
|
|
||||||
rc-update add beszel-agent-update default
|
|
||||||
rc-service beszel-agent-update start
|
|
||||||
|
|
||||||
printf "\nAutomatic daily updates have been enabled.\n"
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -612,7 +599,7 @@ EOF
|
|||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
sleep 1 # give time for the service to start
|
sleep 1 # give time for the service to start
|
||||||
else
|
else
|
||||||
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
@@ -620,12 +607,12 @@ EOF
|
|||||||
echo "Setting up daily automatic updates for beszel-agent..."
|
echo "Setting up daily automatic updates for beszel-agent..."
|
||||||
|
|
||||||
cat >/etc/crontabs/beszel <<EOF
|
cat >/etc/crontabs/beszel <<EOF
|
||||||
0 0 * * * /etc/init.d/beszel-agent update
|
12 0 * * * /etc/init.d/beszel-agent update
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
/etc/init.d/cron restart
|
/etc/init.d/cron restart
|
||||||
|
|
||||||
printf "\nAutomatic daily updates have been enabled.\n"
|
printf "\nDaily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -695,7 +682,7 @@ EOF
|
|||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
sleep 1 # give time for the service to start
|
sleep 1 # give time for the service to start
|
||||||
else
|
else
|
||||||
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
@@ -730,7 +717,7 @@ EOF
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now beszel-agent-update.timer
|
systemctl enable --now beszel-agent-update.timer
|
||||||
|
|
||||||
printf "\nAutomatic daily updates have been enabled.\n"
|
printf "\nDaily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user